Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 362 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion modules/adobe_analytics/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

flutter_aepcore: ^5.0.0
Expand Down
2 changes: 1 addition & 1 deletion modules/auth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

ensemble_ts_interpreter: ^1.0.7
Expand Down
2 changes: 1 addition & 1 deletion modules/bracket/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

dev_dependencies:
Expand Down
4 changes: 2 additions & 2 deletions modules/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble
ensemble_ts_interpreter: ^1.0.7

# QR Code scanner module (for backward compatibility re-export)
ensemble_qr_scanner:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/qr_scanner

collection: ^1.17.1
Expand Down
2 changes: 1 addition & 1 deletion modules/chat/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

ensemble_ts_interpreter: ^1.0.7
Expand Down
2 changes: 1 addition & 1 deletion modules/connect/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

plaid_flutter: ^3.1.2
Expand Down
2 changes: 1 addition & 1 deletion modules/contacts/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

flutter_contacts: ^1.1.7+1
Expand Down
2 changes: 1 addition & 1 deletion modules/deeplink/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
ensemble:
git:
url: https://github.com/EnsembleUI/ensemble.git
ref: ensemble-v1.2.39
ref: ensemble-v1.2.38-beta.6
path: modules/ensemble

flutter_branch_sdk: ^7.0.1
Expand Down
40 changes: 40 additions & 0 deletions modules/ensemble/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 1.2.38-beta.6

- releaseing new beta version

## 1.2.38-beta.5

- Releasing new beta version for TV

## 1.2.38-beta.4

- **FIX**(incorrect header format): explicit convertion of header entry into string. ([84ead788](https://github.com/ensembleUI/ensemble/commit/84ead788a7cd0b1aee50292eabf07bc8ae3c490d))
- **FEAT**(image): enhance header evaluation for dynamic HTTP headers in image requests. ([e24544fe](https://github.com/ensembleUI/ensemble/commit/e24544fe2587a4119f6c4a8242e9b51948eef57c))
- **FEAT**(image): add support for custom HTTP headers in image requests. ([3a304a2c](https://github.com/ensembleUI/ensemble/commit/3a304a2cd01a2af6046a327a9380cf259cb1e37e))

## 1.2.39

- **FIX**(incorrect header format): explicit convertion of header entry into string. ([84ead788](https://github.com/ensembleUI/ensemble/commit/84ead788a7cd0b1aee50292eabf07bc8ae3c490d))
Expand All @@ -9,6 +23,26 @@
- **FEAT**(action): add ActionType.executeAction to ActionInvokable class. ([b5cc5a4a](https://github.com/ensembleUI/ensemble/commit/b5cc5a4af5ac95b0ed4971349f5cf5ab9a481672))
- **FEAT**(lottie): add custom Lottie decoder for .lottie file ext support. ([cc73e7cf](https://github.com/ensembleUI/ensemble/commit/cc73e7cf87475a7e538b0c88a099a90c5c63af21))

## 1.2.38-beta.3

- **FIX**(invoke_api_action): handle FirestoreResponse in error handling. ([c8cb1c7b](https://github.com/ensembleUI/ensemble/commit/c8cb1c7b1bb97405bc61893338b1aeab53838a8f))
- **FEAT**(dotenv): implement dotenv bundle parsing and refactor config loading. ([0a987c94](https://github.com/ensembleUI/ensemble/commit/0a987c94aef06e2b220fca85c1d8f43c707fd506))

## 1.2.38-beta.2

- Releasing new beta version 1.2.38.2

## 1.2.38-beta.1

- **FIX**(phone_contact): replace RuntimeError with debugPrint for missing contact photo. ([b36b399d](https://github.com/ensembleUI/ensemble/commit/b36b399d91fad26e46e14f0845c624a3f8b768c9))
- **FIX**(firestore_types): handle FirestoreTimestamp conversion in EnsembleFieldValue class. ([a4e8dba0](https://github.com/ensembleUI/ensemble/commit/a4e8dba0142250eee12b09fd012ae85e5ac18f2f))
- **FIX**(page_model): convert keys to strings in merged global actions. ([4dcb7e4a](https://github.com/ensembleUI/ensemble/commit/4dcb7e4a888a6259cb4f1a8daceb6338731ec6c8))
- **FEAT**(action): add ActionType.executeAction to ActionInvokable class. ([b5cc5a4a](https://github.com/ensembleUI/ensemble/commit/b5cc5a4af5ac95b0ed4971349f5cf5ab9a481672))
- **FEAT**(lottie): add custom Lottie decoder for .lottie file ext support. ([cc73e7cf](https://github.com/ensembleUI/ensemble/commit/cc73e7cf87475a7e538b0c88a099a90c5c63af21))
- **FEAT**(env): enhance environment variable loading and parsing. ([b7666ceb](https://github.com/ensembleUI/ensemble/commit/b7666ceb292427ad24445cc3080a68e9aca8c47a))
- **FEAT**(cdn_provider): add runtime translation refresh and testing capabilities. ([c9ba1fd2](https://github.com/ensembleUI/ensemble/commit/c9ba1fd23c34031c96e2248f1b05cf2ba2b4bc88))
- **FEAT**(secure_storage): enhance secure storage actions with optional encryption parameters. ([dee0bb57](https://github.com/ensembleUI/ensemble/commit/dee0bb571152e95b4cdc658924b2399c6b4f58b4))

## 1.2.38

- **FEAT**(env): enhance environment variable loading and parsing. ([b7666ceb](https://github.com/ensembleUI/ensemble/commit/b7666ceb292427ad24445cc3080a68e9aca8c47a))
Expand All @@ -33,6 +67,12 @@
- **FIX**(page_model): add 'Actions' to the list of available types in PageModel. ([6dc07f06](https://github.com/ensembleUI/ensemble/commit/6dc07f06e447c5cdbf49be6f29a54e74fa6987e5))
- **FEAT**(actions): introduce reusable action execution framework. ([482d7de9](https://github.com/ensembleUI/ensemble/commit/482d7de922433bb41a282cfdd018f13866fe511f))

## 1.2.35-beta.1

- **FIX**(execute_action): update payload key from 'action' to 'body' in ExecuteActionAction class. ([7e1b8466](https://github.com/ensembleUI/ensemble/commit/7e1b846611b4da27f21fa3474d8a6de05b40b768))
- **FIX**(page_model): add 'Actions' to the list of available types in PageModel. ([6dc07f06](https://github.com/ensembleUI/ensemble/commit/6dc07f06e447c5cdbf49be6f29a54e74fa6987e5))
- **FEAT**(actions): introduce reusable action execution framework. ([482d7de9](https://github.com/ensembleUI/ensemble/commit/482d7de922433bb41a282cfdd018f13866fe511f))

## 1.2.34

- **FEAT**(remote_config): add Firebase Remote Config integration. ([906f0133](https://github.com/ensembleUI/ensemble/commit/906f013322dcda45a1740db24b5e21f63ea372e5))
Expand Down
31 changes: 31 additions & 0 deletions modules/ensemble/lib/ensemble_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:ensemble/framework/event/change_locale_events.dart';
import 'package:ensemble/framework/storage_manager.dart';
import 'package:ensemble/framework/theme/theme_loader.dart';
import 'package:ensemble/framework/theme_manager.dart';
import 'package:ensemble/framework/tv/tv_focus_provider.dart';
import 'package:ensemble/framework/widget/error_screen.dart';
import 'package:ensemble/framework/widget/screen.dart';
import 'package:ensemble/ios_deep_link_manager.dart';
Expand Down Expand Up @@ -108,6 +109,7 @@ class EnsembleApp extends StatefulWidget {
this.onAppLoad,
this.forcedLocale,
this.child,
this.tvFocusProvider,
GlobalKey<NavigatorState>? navigatorKey,
ScrollController? screenScroller,
}) {
Expand Down Expand Up @@ -139,6 +141,12 @@ class EnsembleApp extends StatefulWidget {
/// use this if you want the App to start out with this local
final Locale? forcedLocale;

/// Optional TV focus provider from host app.
/// When provided, Ensemble widgets will use the host app's focus system
/// instead of Ensemble's built-in TVFocusWidget. This enables seamless
/// D-pad navigation between host app and Ensemble content.
final TVFocusProvider? tvFocusProvider;

@override
State<StatefulWidget> createState() => EnsembleAppState();
}
Expand Down Expand Up @@ -421,6 +429,19 @@ class EnsembleAppState extends State<EnsembleApp> with WidgetsBindingObserver {
EnsembleThemeManager().currentTheme()?.appThemeData == null) {
//backward compatibility in case apps are using the old style of App level theming that is at the root level
theme = config.getAppTheme();

// Preserve tvFocusTheme from EnsembleThemeManager if available
// This ensures TV focus styling works even with legacy themes
final currentThemeData = EnsembleThemeManager().currentTheme()?.appThemeData;
final tvFocusTheme = currentThemeData?.extension<EnsembleThemeExtension>()?.tvFocusTheme;
if (tvFocusTheme != null) {
final existingExtension = theme.extension<EnsembleThemeExtension>();
if (existingExtension != null) {
theme = theme.copyWith(
extensions: [existingExtension.copyWith(tvFocusTheme: tvFocusTheme)],
);
}
}
} else {
theme = EnsembleThemeManager().currentTheme()!.appThemeData;
}
Expand Down Expand Up @@ -472,6 +493,16 @@ class EnsembleAppState extends State<EnsembleApp> with WidgetsBindingObserver {
// child: app,
// );
// }

// Wrap with TV focus provider if provided by host app
// This enables host app's focus system to manage Ensemble widgets
if (widget.tvFocusProvider != null) {
app = TVFocusProviderScope(
provider: widget.tvFocusProvider!,
child: app,
);
}

return app;
}

Expand Down
27 changes: 27 additions & 0 deletions modules/ensemble/lib/framework/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class Device
"macOsInfo": () => DeviceMacOsInfo(),
"windowsInfo": () => DeviceWindowsInfo(),

// TV detection
"isTV": () => isTV,

// @deprecated. backward compatibility
DevicePlatform.web.name: () => DeviceWebInfo()
};
Expand All @@ -74,6 +77,7 @@ class Device
'isWeb': () => platform == DevicePlatform.web,
'isMacOS': () => platform == DevicePlatform.macos,
'isWindows': () => platform == DevicePlatform.windows,
'isTV': () => isTV,

// deprecated. Should be using Action instead
'openAppSettings': (target) => openAppSettings(target),
Expand Down Expand Up @@ -134,8 +138,31 @@ mixin DeviceInfoCapability {
static MacOsDeviceInfo? macOsInfo;
static WindowsDeviceInfo? windowsInfo;

// Android TV detection cache
static bool? _isTV;

DevicePlatform? get platform => _platform;

/// Returns true if the device is an Android TV
/// Checks for TV-specific system features
bool get isTV {
if (_isTV != null) return _isTV!;

// Only Android devices can be TVs (for now)
if (kIsWeb || _platform != DevicePlatform.android || androidInfo == null) {
_isTV = false;
return false;
}

// Check for TV system features
final systemFeatures = androidInfo!.systemFeatures;
_isTV = systemFeatures.contains('android.hardware.type.television') ||
systemFeatures.contains('android.software.leanback') ||
systemFeatures.contains('android.software.leanback_only');

return _isTV!;
}

/// initialize device info
void initDeviceInfo() async {
try {
Expand Down
66 changes: 54 additions & 12 deletions modules/ensemble/lib/framework/theme/theme_loader.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:ensemble/framework/extensions.dart';
import 'package:ensemble/framework/theme/default_theme.dart';
import 'package:ensemble/framework/theme/theme_manager.dart';
import 'package:ensemble/framework/tv/tv_focus_theme.dart';
import 'package:ensemble/model/text_scale.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble/widget/image.dart';
Expand All @@ -23,6 +24,7 @@ mixin ThemeLoader {
YamlMap? colorOverrides,
YamlMap? screenOverrides,
YamlMap? widgetOverrides,
YamlMap? tokensOverrides,
}) {

if (appOverrides == null) {
Expand All @@ -37,6 +39,9 @@ mixin ThemeLoader {
if (widgetOverrides == null) {
widgetOverrides = overrides?['Widgets'];
}
if (tokensOverrides == null) {
tokensOverrides = overrides?['Tokens'];
}
final seedColor = Utils.getColor(colorOverrides?['seed']);
String _defaultFontFamily = appOverrides?['fontFamily']?? appOverrides?['textStyle']?['fontFamily'] ?? 'Inter';
TextStyle? defaultFontFamily = Utils.getFontFamily(_defaultFontFamily) ?? TextStyle();
Expand Down Expand Up @@ -174,6 +179,7 @@ mixin ThemeLoader {
loadingScreenIndicatorColor: Utils.getColor(
colorOverrides?['loadingScreenIndicatorColor']),
transitions: Utils.getMap(overrides?['Transitions']),
tvFocusTheme: _parseTVFocusTheme(tokensOverrides),
)
]);
}
Expand Down Expand Up @@ -509,6 +515,30 @@ mixin ThemeLoader {
///------------ publicly available theme getters -------------
BorderRadius getInputDefaultBorderRadius(InputVariant? variant) =>
BorderRadius.all(Radius.circular(variant == InputVariant.box ? 8 : 0));

/// Parses TV focus theme configuration from theme tokens.
///
/// Looks for TV configuration under Tokens.TV in the theme YAML:
/// ```yaml
/// Tokens:
/// TV:
/// focusColor: 0xFF00AAFF
/// focusBorderWidth: 3
/// focusBorderRadius: 8
/// focusAnimationDuration: 150
/// ```
TVFocusTheme? _parseTVFocusTheme(YamlMap? tokens) {
final tvTokens = tokens?['TV'];
if (tvTokens == null) return null;

return TVFocusTheme(
focusColor: Utils.getColor(tvTokens['focusColor']),
focusBorderWidth: Utils.optionalDouble(tvTokens['focusBorderWidth']),
focusBorderRadius: Utils.optionalDouble(tvTokens['focusBorderRadius']),
focusAnimationDurationMs:
Utils.optionalInt(tvTokens['focusAnimationDuration']),
);
}
}

/// Configures image cache settings from App.imageCache in theme.yaml.
Expand Down Expand Up @@ -548,26 +578,36 @@ extension CheckboxThemeDataExtension on CheckboxThemeData {

/// extend Theme to add our own special color parameters
class EnsembleThemeExtension extends ThemeExtension<EnsembleThemeExtension> {
EnsembleThemeExtension(
{this.appTheme,
this.loadingScreenBackgroundColor,
this.loadingScreenIndicatorColor,
this.transitions});
EnsembleThemeExtension({
this.appTheme,
this.loadingScreenBackgroundColor,
this.loadingScreenIndicatorColor,
this.transitions,
this.tvFocusTheme,
});

final AppTheme? appTheme;
final Color? loadingScreenBackgroundColor;
final Color? loadingScreenIndicatorColor; // should deprecate this
final Map<String, dynamic>? transitions;

/// TV focus styling configuration parsed from theme.yaml.
/// Used as the highest priority source for TV focus indicator styling.
final TVFocusTheme? tvFocusTheme;

@override
ThemeExtension<EnsembleThemeExtension> copyWith(
{Color? loadingScreenBackgroundColor,
Color? loadingScreenIndicatorColor}) {
ThemeExtension<EnsembleThemeExtension> copyWith({
Color? loadingScreenBackgroundColor,
Color? loadingScreenIndicatorColor,
TVFocusTheme? tvFocusTheme,
}) {
return EnsembleThemeExtension(
loadingScreenBackgroundColor:
loadingScreenBackgroundColor ?? this.loadingScreenBackgroundColor,
loadingScreenIndicatorColor:
loadingScreenIndicatorColor ?? this.loadingScreenIndicatorColor);
loadingScreenBackgroundColor:
loadingScreenBackgroundColor ?? this.loadingScreenBackgroundColor,
loadingScreenIndicatorColor:
loadingScreenIndicatorColor ?? this.loadingScreenIndicatorColor,
tvFocusTheme: tvFocusTheme ?? this.tvFocusTheme,
);
}

@override
Expand All @@ -581,6 +621,8 @@ class EnsembleThemeExtension extends ThemeExtension<EnsembleThemeExtension> {
loadingScreenBackgroundColor, other.loadingScreenBackgroundColor, t),
loadingScreenIndicatorColor: Color.lerp(
loadingScreenIndicatorColor, other.loadingScreenIndicatorColor, t),
// TV focus theme doesn't need lerping - use target value
tvFocusTheme: t < 0.5 ? tvFocusTheme : other.tvFocusTheme,
);
}
}
Expand Down
10 changes: 9 additions & 1 deletion modules/ensemble/lib/framework/theme_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,17 @@ class EnsembleTheme {
initialized = true;
return this;
}
/// Initialize app-level ThemeData with tokens and styles.
/// Pass tokensOverrides so TV-related tokens (e.g., Tokens.TV.focusColor)
/// are parsed and included in the theme's EnsembleThemeExtension.
void initAppThemeData() {
YamlMap? yamlStyles = styles != null ? YamlMap.wrap(styles) : null;
appThemeData = ThemeManager().getAppTheme(yamlStyles,widgetOverrides: yamlStyles);
YamlMap? yamlTokens = tokens.isNotEmpty ? YamlMap.wrap(tokens) : null;
appThemeData = ThemeManager().getAppTheme(
yamlStyles,
widgetOverrides: yamlStyles,
tokensOverrides: yamlTokens,
);
}
Map<String, dynamic>? getIDStyles(String? id) {
return (id == null) ? {} : styles['#$id'];
Expand Down
Loading
Loading