diff --git a/modules/ensemble/lib/action/get_network_info_action.dart b/modules/ensemble/lib/action/get_network_info_action.dart index ceb6e06e4..b5b78ec4c 100644 --- a/modules/ensemble/lib/action/get_network_info_action.dart +++ b/modules/ensemble/lib/action/get_network_info_action.dart @@ -20,26 +20,33 @@ class GetNetworkInfoAction extends EnsembleAction { EnsembleAction? onDenied; EnsembleAction? onLocationDisabled; - GetNetworkInfoAction({super.initiator, - super.inputs, - this.onSuccess, - this.onError, - this.onDenied, - this.onLocationDisabled}); + GetNetworkInfoAction( + {super.initiator, + super.inputs, + this.onSuccess, + this.onError, + this.onDenied, + this.onLocationDisabled}); - factory GetNetworkInfoAction.from({Invokable? initiator,dynamic payload}) => - GetNetworkInfoAction.fromYaml(initiator: initiator, payload: Utils.getYamlMap(payload)); + factory GetNetworkInfoAction.from({Invokable? initiator, dynamic payload}) => + GetNetworkInfoAction.fromYaml( + initiator: initiator, payload: Utils.getYamlMap(payload)); factory GetNetworkInfoAction.fromYaml({Invokable? initiator, Map? payload}) { if (payload?['onSuccess'] == null) { - throw LanguageError('onSuccess is required',recovery:'Please specify onSuccess method to call when network info is retrieved'); + throw LanguageError('onSuccess is required', + recovery: + 'Please specify onSuccess method to call when network info is retrieved'); } return GetNetworkInfoAction( initiator: initiator, inputs: Utils.getMap(payload?['inputs']), - onSuccess: EnsembleAction.from(payload?['onSuccess'], initiator: initiator), + onSuccess: + EnsembleAction.from(payload?['onSuccess'], initiator: initiator), onError: EnsembleAction.from(payload?['onError'], initiator: initiator), - onDenied: EnsembleAction.from(payload?['onDenied'], initiator: initiator), - onLocationDisabled: EnsembleAction.from(payload?['onLocationDisabled'], initiator: initiator)); + onDenied: + EnsembleAction.from(payload?['onDenied'], initiator: initiator), + onLocationDisabled: EnsembleAction.from(payload?['onLocationDisabled'], + initiator: initiator)); } @override @@ -51,35 +58,31 @@ class GetNetworkInfoAction extends EnsembleAction { data: {'status': 'error'})); } networkInfo.getLocationStatus().then((locationStatus) { - if ((locationStatus == LocationPermissionStatus.denied.name - || locationStatus == LocationPermissionStatus.deniedForever.name) - && onDenied != null) { + if ((locationStatus == LocationPermissionStatus.denied.name || + locationStatus == LocationPermissionStatus.deniedForever.name) && + onDenied != null) { return ScreenController().executeAction(context, onDenied!, - event: EnsembleEvent(initiator, - data: {'status': locationStatus})); - } else if (locationStatus == LocationStatus.disabled.name - && onLocationDisabled != null) { + event: EnsembleEvent(initiator, data: {'status': locationStatus})); + } else if (locationStatus == LocationStatus.disabled.name && + onLocationDisabled != null) { return ScreenController().executeAction(context, onLocationDisabled!, - event: EnsembleEvent(initiator, - data: {'status': locationStatus})); - } else - if (locationStatus == LocationPermissionStatus.unableToDetermine.name - && onError != null) { + event: EnsembleEvent(initiator, data: {'status': locationStatus})); + } else if (locationStatus == + LocationPermissionStatus.unableToDetermine.name && + onError != null) { return ScreenController().executeAction(context, onError!, - event: EnsembleEvent(initiator, - data: { - 'status': locationStatus - })); + event: EnsembleEvent(initiator, data: {'status': locationStatus})); } else { try { - networkInfo.getNetworkInfo().then((info) => - ScreenController().executeAction(context, onSuccess!, - event: EnsembleEvent(initiator, - data: { + networkInfo + .getNetworkInfo() + .then((info) => + ScreenController().executeAction(context, onSuccess!, + event: EnsembleEvent(initiator, data: { 'status': locationStatus, 'networkInfo': info, - } - ))).onError((error, stackTrace) { + }))) + .onError((error, stackTrace) { if (onError != null) { return ScreenController().executeAction(context, onError!, event: EnsembleEvent(initiator, @@ -91,25 +94,32 @@ class GetNetworkInfoAction extends EnsembleAction { if (onError != null) { return ScreenController().executeAction(context, onError!, event: EnsembleEvent(initiator, - error: e.toString(), - data: {'status': locationStatus})); + error: e.toString(), data: {'status': locationStatus})); } } } }).onError((e, stackTrace) { if (onError != null) { return ScreenController().executeAction(context, onError!, - event: EnsembleEvent(initiator, - error: e.toString(), - data: {'status': 'error: '+e.toString(), 'networkInfo': null})); + event: EnsembleEvent(initiator, error: e.toString(), data: { + 'status': 'error: ' + e.toString(), + 'networkInfo': null + })); } print('Failed to get location status: $e'); }); return Future.value(null); } } + class InvokableNetworkInfo extends Object with Invokable { - String? wifiName,wifiIPv6,wifiIPv4,wifiGatewayIP,wifiSubmask,wifiBroadcast,wifiBSSID; + String? wifiName, + wifiIPv6, + wifiIPv4, + wifiGatewayIP, + wifiSubmask, + wifiBroadcast, + wifiBSSID; InvokableNetworkInfo({ this.wifiName, this.wifiIPv4, @@ -120,7 +130,6 @@ class InvokableNetworkInfo extends Object with Invokable { this.wifiBSSID, }); - @override Map getters() { return { @@ -143,4 +152,4 @@ class InvokableNetworkInfo extends Object with Invokable { Map setters() { return {}; } -} \ No newline at end of file +} diff --git a/modules/ensemble/lib/framework/stub/network_info.dart b/modules/ensemble/lib/framework/stub/network_info.dart index 496a59dba..213d06380 100644 --- a/modules/ensemble/lib/framework/stub/network_info.dart +++ b/modules/ensemble/lib/framework/stub/network_info.dart @@ -11,8 +11,7 @@ abstract class NetworkInfoManager { } class NetworkInfoManagerStub implements NetworkInfoManager { - NetworkInfoManagerStub() { - } + NetworkInfoManagerStub() {} @override Future getNetworkInfo() { if (kIsWeb) { @@ -22,6 +21,7 @@ class NetworkInfoManagerStub implements NetworkInfoManager { throw ConfigError( "NetworkInfo module is not enabled. Please review the Ensemble documentation."); } + @override Future checkPermission() { throw ConfigError( @@ -33,4 +33,4 @@ class NetworkInfoManagerStub implements NetworkInfoManager { throw ConfigError( "NetworkInfo module is not enabled. Please review the Ensemble documentation."); } -} \ No newline at end of file +} diff --git a/modules/ensemble/lib/framework/theme/theme_loader.dart b/modules/ensemble/lib/framework/theme/theme_loader.dart index 1ac71ebe5..92a29d17f 100644 --- a/modules/ensemble/lib/framework/theme/theme_loader.dart +++ b/modules/ensemble/lib/framework/theme/theme_loader.dart @@ -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/theme/theme_utils.dart'; import 'package:ensemble/model/text_scale.dart'; import 'package:ensemble/util/utils.dart'; import 'package:flutter/material.dart'; @@ -9,21 +10,94 @@ import 'package:yaml/yaml.dart'; mixin ThemeLoader { final EdgeInsets _buttonPadding = const EdgeInsets.only(left: 15, top: 5, right: 15, bottom: 5); - final int _buttonBorderRadius = 3; + final int _buttonBorderRadius = 8; final Color _buttonBorderOutlineColor = Colors.black12; - ThemeData getAppTheme(YamlMap? overrides) { - final seedColor = Utils.getColor(overrides?['Colors']?['seed']); + /** + * Build the App's theme based on the theme configuration + */ + ThemeData getAppTheme(YamlMap? themeConfig) { + var colorScheme = _getColorScheme(themeConfig); + var baselineTheme = _getBaselineTheme(themeConfig); + + final themeData = baselineTheme.copyWith( + useMaterial3: Utils.getBool(themeConfig?['material3'], fallback: true), + colorScheme: colorScheme, + disabledColor: Utils.getColor(themeConfig?['Colors']?['disabled']), + inputDecorationTheme: _buildInputTheme(themeConfig?['Widgets']?['Input'], + colorScheme: colorScheme), + textButtonTheme: TextButtonThemeData( + style: _buildButtonTheme(getProp(themeConfig, ['Widgets', 'Button']), + baselineTheme: baselineTheme, isOutline: true)), + filledButtonTheme: FilledButtonThemeData( + style: _buildButtonTheme(getProp(themeConfig, ['Widgets', 'Button']), + baselineTheme: baselineTheme, isOutline: false)), + bottomNavigationBarTheme: const BottomNavigationBarThemeData(), + switchTheme: const SwitchThemeData(), + checkboxTheme: _buildCheckboxTheme( + themeConfig?['Widgets']?['Checkbox'], colorScheme), + ); + + // extends ThemeData + return themeData.copyWith(extensions: [ + EnsembleThemeExtension( + appTheme: AppTheme( + textScale: TextScale( + enabled: Utils.optionalBool( + getProp(themeConfig, ['App', 'textScale', 'enabled'])), + minFactor: Utils.optionalDouble( + getProp(themeConfig, ['App', 'textScale', 'minFactor']), + min: 0), + maxFactor: Utils.optionalDouble( + getProp(themeConfig, ['App', 'textScale', 'maxFactor']), + min: 0))), + loadingScreenBackgroundColor: + Utils.getColor(themeConfig?['Screen']?['loadingBackgroundColor']) ?? + Utils.getColor( + themeConfig?['Colors']?['loadingScreenBackgroundColor']), + loadingScreenIndicatorColor: Utils.getColor( + themeConfig?['Colors']?['loadingScreenIndicatorColor']), + transitions: Utils.getMap(themeConfig?['Transitions']), + ) + ]); + } - ThemeData defaultTheme = ThemeData( + /** + * Generate a Color Scheme for our App based on the seed and/or specific Color functions + */ + ColorScheme _getColorScheme(Map? themeConfig) { + // generate Colors from seed if specified + final seedColor = Utils.getColor(getProp(themeConfig, ['Colors', 'seed'])); + var colorScheme = seedColor == null + ? defaultColorScheme + : ColorScheme.fromSeed(seedColor: seedColor); + + // then further override with specific Color functions + return colorScheme.copyWith( + primary: Utils.getColor(getProp(themeConfig, ['Colors', 'primary'])), + onPrimary: Utils.getColor(getProp(themeConfig, ['Colors', 'onPrimary'])), + secondary: Utils.getColor(getProp(themeConfig, ['Colors', 'secondary'])), + onSecondary: + Utils.getColor(getProp(themeConfig, ['Colors', 'onSecondary'])), + ); + } + + /** + * return the baseline theme from the theme configuration. + * We need this when overriding certain widgets/attributes + */ + ThemeData _getBaselineTheme(Map? themeConfig) { + final seedColor = Utils.getColor(getProp(themeConfig, ['Colors', 'seed'])); + + return ThemeData( useMaterial3: true, colorScheme: seedColor == null ? defaultColorScheme : ColorScheme.fromSeed(seedColor: seedColor), scaffoldBackgroundColor: - Utils.getColor(overrides?['Screen']?['backgroundColor']) ?? + Utils.getColor(getProp(themeConfig, ['Screen', 'backgroundColor'])) ?? DesignSystem.scaffoldBackgroundColor, - appBarTheme: _getAppBarTheme(overrides?['Screen']), + appBarTheme: _getAppBarTheme(getProp(themeConfig, ['Screen'])), disabledColor: DesignSystem.disableColor, inputDecorationTheme: InputDecorationTheme( filled: true, @@ -49,26 +123,7 @@ mixin ThemeLoader { BorderSide(color: DesignSystem.inputBorderColor, width: 2), ), ), - textTheme: _buildTextTheme(), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - textStyle: const TextStyle(fontSize: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - ), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - textStyle: const TextStyle(fontSize: 16), - ), - ), - filledButtonTheme: FilledButtonThemeData( - style: FilledButton.styleFrom( - textStyle: const TextStyle(fontSize: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0))), - ), + textTheme: _buildTextTheme(themeConfig?['Text']), tabBarTheme: TabBarTheme( labelColor: DesignSystem.primary, ), @@ -79,75 +134,10 @@ mixin ThemeLoader { ), ), ); - - final customColorScheme = defaultTheme.colorScheme.copyWith( - primary: Utils.getColor(overrides?['Colors']?['primary']), - onPrimary: Utils.getColor(overrides?['Colors']?['onPrimary']), - secondary: Utils.getColor(overrides?['Colors']?['secondary']), - onSecondary: Utils.getColor(overrides?['Colors']?['onSecondary']), - ); - - final customTheme = defaultTheme.copyWith( - useMaterial3: Utils.getBool(overrides?['material3'], fallback: true), - colorScheme: customColorScheme, - disabledColor: Utils.getColor(overrides?['Colors']?['disabled']), - textTheme: _buildTextTheme(overrides?['Text']), - inputDecorationTheme: _buildInputTheme(overrides?['Widgets']?['Input'], - colorScheme: customColorScheme), - outlinedButtonTheme: OutlinedButtonThemeData( - style: _buildButtonTheme(overrides?['Widgets']?['Button'], - isOutline: true, colorScheme: customColorScheme) ?? - defaultTheme.outlinedButtonTheme.style, - ), - textButtonTheme: TextButtonThemeData( - style: _buildButtonTheme(overrides?['Widgets']?['Button'], - isOutline: true, colorScheme: customColorScheme) ?? - defaultTheme.outlinedButtonTheme.style, - ), - filledButtonTheme: FilledButtonThemeData( - style: _buildButtonTheme(overrides?['Widgets']?['Button'], - isOutline: false, colorScheme: customColorScheme) ?? - defaultTheme.filledButtonTheme.style, - ), - bottomNavigationBarTheme: const BottomNavigationBarThemeData(), - switchTheme: const SwitchThemeData(), - checkboxTheme: _buildCheckboxTheme( - overrides?['Widgets']?['Checkbox'], customColorScheme), - ); - - var appTheme = AppTheme( - textScale: TextScale( - enabled: Utils.optionalBool( - getProp(overrides, ['App', 'textScale', 'enabled'])), - minFactor: Utils.optionalDouble( - getProp(overrides, ['App', 'textScale', 'minFactor']), - min: 0), - maxFactor: Utils.optionalDouble( - getProp(overrides, ['App', 'textScale', 'maxFactor']), - min: 0))); - - // extends ThemeData - return customTheme.copyWith(extensions: [ - EnsembleThemeExtension( - appTheme: appTheme, - loadingScreenBackgroundColor: - Utils.getColor(overrides?['Screen']?['loadingBackgroundColor']) ?? - Utils.getColor( - overrides?['Colors']?['loadingScreenBackgroundColor']), - loadingScreenIndicatorColor: Utils.getColor( - overrides?['Colors']?['loadingScreenIndicatorColor']), - transitions: Utils.getMap(overrides?['Transitions']), - ) - ]); } - dynamic getProp(Map? root, List paths) { - dynamic result = root; - for (var path in paths) { - if (result == null) return null; - result = result[path]; - } - return result; + _resolveButtonTextStyle(ThemeData baselineTheme) { + return baselineTheme.textTheme.labelLarge; } AppBarTheme? _getAppBarTheme(YamlMap? screenMap) { @@ -170,6 +160,8 @@ mixin ThemeLoader { fontWeight: FontWeight.w400, color: defaultThemeColor); + var fontFamily = defaultStyle.fontFamily; + return ThemeData.light() .textTheme .copyWith( @@ -324,36 +316,45 @@ mixin ThemeLoader { } ButtonStyle? _buildButtonTheme(YamlMap? input, - {required ColorScheme colorScheme, required bool isOutline}) { + {required ThemeData baselineTheme, required bool isOutline}) { // outline button can simply use backgroundColor as borderColor (if not set) - if (input == null) return null; - Color? borderColor = Utils.getColor(input['borderColor']); + Color? borderColor = Utils.getColor(input?['borderColor']); if (borderColor == null && isOutline) { - borderColor = - Utils.getColor(input['backgroundColor']) ?? _buttonBorderOutlineColor; + borderColor = Utils.getColor(input?['backgroundColor']) ?? + _buttonBorderOutlineColor; } // outline button ignores backgroundColor Color? backgroundColor = - isOutline ? null : Utils.getColor(input['backgroundColor']); + isOutline ? null : Utils.getColor(input?['backgroundColor']); RoundedRectangleBorder border = RoundedRectangleBorder( borderRadius: BorderRadius.circular( - Utils.getInt(input['borderRadius'], fallback: _buttonBorderRadius) + Utils.getInt(input?['borderRadius'], fallback: _buttonBorderRadius) .toDouble()), side: borderColor == null ? BorderSide.none : BorderSide( color: borderColor, - width: Utils.getInt(input['borderWidth'], fallback: 1) + width: Utils.getInt(input?['borderWidth'], fallback: 1) .toDouble())); - return getButtonStyle( + // labelStyle starts at Text->labelLarge and overriden at Widgets->Button + var textStyle = Utils.getTextStyle(input?["labelStyle"]); + var labelStyle = + baselineTheme.textTheme.labelLarge?.merge(textStyle) ?? textStyle; + + var buttonStyle = _getButtonStyle( isOutline: isOutline, backgroundColor: backgroundColor, border: border, - padding: Utils.optionalInsets(input['padding']) ?? _buttonPadding, - labelStyle: Utils.getTextStyle('labelStyle')); + padding: Utils.optionalInsets(input?['padding']) ?? _buttonPadding, + // this is important. This is the only way to get the fontFamily/textStyle + // set at the theme Text's root or labelLarge (which maps to button label). + // Also note that this is only important initially at the theme level. + // Subsequently with the Context we will already fallback properly + labelStyle: labelStyle); + return buttonStyle; } InputBorder? getInputBorder( @@ -377,22 +378,16 @@ mixin ThemeLoader { return null; } - /// this function is also called while building the button, so make sure we don't use any fallback - /// to ensure the style reverts to the button theming - ButtonStyle getButtonStyle( + ButtonStyle _getButtonStyle( {required bool isOutline, Color? backgroundColor, RoundedRectangleBorder? border, EdgeInsets? padding, - double? buttonWidth, - double? buttonHeight, TextStyle? labelStyle}) { if (isOutline) { return OutlinedButton.styleFrom( padding: padding, tapTargetSize: MaterialTapTargetSize.shrinkWrap, - fixedSize: Size(buttonWidth ?? Size.infinite.width, - buttonHeight ?? Size.infinite.height), shape: border, textStyle: labelStyle); } else { @@ -400,8 +395,6 @@ mixin ThemeLoader { backgroundColor: backgroundColor, padding: padding, tapTargetSize: MaterialTapTargetSize.shrinkWrap, - fixedSize: Size(buttonWidth ?? Size.infinite.width, - buttonHeight ?? Size.infinite.height), shape: border, textStyle: labelStyle, ); diff --git a/modules/ensemble/lib/framework/theme/theme_utils.dart b/modules/ensemble/lib/framework/theme/theme_utils.dart new file mode 100644 index 000000000..378f6b679 --- /dev/null +++ b/modules/ensemble/lib/framework/theme/theme_utils.dart @@ -0,0 +1,11 @@ +/** + * Trarverse the given "paths" from "root" and return the value + */ +dynamic getProp(Map? root, List paths) { + dynamic result = root; + for (var path in paths) { + if (result == null) return null; + result = result[path]; + } + return result; +} diff --git a/modules/ensemble/lib/framework/theme_manager.dart b/modules/ensemble/lib/framework/theme_manager.dart index f456b0db7..d5f451da9 100644 --- a/modules/ensemble/lib/framework/theme_manager.dart +++ b/modules/ensemble/lib/framework/theme_manager.dart @@ -54,6 +54,7 @@ class EnsembleThemeManager { } return currentTheme()?.resolveStyles(context, hasStyles); } + void configureStyles( DataContext dataContext, HasStyles model, HasStyles hasStyles) { //we have to set all these so we can resolve when styles change at runtime through app logic diff --git a/modules/ensemble/lib/page_model.dart b/modules/ensemble/lib/page_model.dart index 78f368986..836c4ca75 100644 --- a/modules/ensemble/lib/page_model.dart +++ b/modules/ensemble/lib/page_model.dart @@ -58,7 +58,8 @@ abstract class PageModel { } on Error catch (e) { throw LanguageError("Invalid page definition.", recovery: "Please double check your page syntax.", - detailedError: e.toString() + "\n" + (e.stackTrace?.toString() ?? '')); + detailedError: + e.toString() + "\n" + (e.stackTrace?.toString() ?? '')); } } diff --git a/modules/ensemble/lib/util/utils.dart b/modules/ensemble/lib/util/utils.dart index 683db35c2..2ead8d787 100644 --- a/modules/ensemble/lib/util/utils.dart +++ b/modules/ensemble/lib/util/utils.dart @@ -507,15 +507,18 @@ class Utils { return null; } - static TextStyleComposite getTextStyleAsComposite( + static TextStyleComposite? getTextStyleAsComposite( WidgetController widgetController, {dynamic style}) { - return TextStyleComposite( - widgetController, - textGradient: Utils.getBackgroundGradient(style['gradient']), - textAlign: style['textAlign'], - styleWithFontFamily: getTextStyle(style), - ); + if (style is Map && style.isNotEmpty) { + return TextStyleComposite( + widgetController, + textGradient: Utils.getBackgroundGradient(style['gradient']), + textAlign: style['textAlign'], + styleWithFontFamily: getTextStyle(style), + ); + } + return null; } static TextStyle? getTextStyle(dynamic style) { @@ -551,13 +554,15 @@ class Utils { } else if (style is String) {} return null; } + //fontFamily could either be a string or a map where the key is the language code and the value is the font family name static TextStyle? getFontFamily(dynamic name) { String? fontFamily; // Check if the name is a map with language codes if (name is Map) { // Retrieve the current language code - String? languageCode = UserLocale.from(Ensemble().getLocale())?.languageCode; + String? languageCode = + UserLocale.from(Ensemble().getLocale())?.languageCode; if (languageCode != null && name.containsKey(languageCode)) { fontFamily = name[languageCode]?.toString(); } @@ -566,7 +571,8 @@ class Utils { if (fontFamily == null || fontFamily.isEmpty) { fontFamily = name['default']?.toString(); } - } else if (name is String) { // Handle the case where name is a string + } else if (name is String) { + // Handle the case where name is a string fontFamily = name; } // If a valid font family is found, apply it @@ -581,7 +587,6 @@ class Utils { return null; } - static TextAlign? getTextAlignment(dynamic align) { TextAlign? textAlign; switch (align) { diff --git a/modules/ensemble/lib/widget/button.dart b/modules/ensemble/lib/widget/button.dart index ac5ef267d..d6dd35319 100644 --- a/modules/ensemble/lib/widget/button.dart +++ b/modules/ensemble/lib/widget/button.dart @@ -38,8 +38,8 @@ class Button extends StatefulWidget 'gap': () => Utils.getInt(_controller.gap, fallback: 0), 'enabled': () => Utils.getBool(_controller.enabled, fallback: true), 'outline': () => Utils.getBool(_controller.outline, fallback: false), - 'width': () => _controller.buttonWidth, - 'height': () => _controller.buttonHeight + 'width': () => _controller.width, + 'height': () => _controller.height }; } @@ -66,8 +66,6 @@ class Button extends StatefulWidget _controller.validateFields = Utils.getList(items), 'enabled': (value) => _controller.enabled = Utils.optionalBool(value), 'outline': (value) => _controller.outline = Utils.optionalBool(value), - 'width': (value) => _controller.buttonWidth = Utils.optionalInt(value), - 'height': (value) => _controller.buttonHeight = Utils.optionalInt(value), }; } @@ -85,8 +83,10 @@ class ButtonController extends BoxController { String? onTapHaptic; TextStyleComposite? _labelStyle; + TextStyleComposite get labelStyle => _labelStyle ??= TextStyleComposite(this); - set labelStyle(TextStyleComposite style) => _labelStyle = style; + + set labelStyle(TextStyleComposite? style) => _labelStyle = style; /// whether to trigger a form submission. /// This has no effect if the button is not inside a form @@ -100,8 +100,6 @@ class ButtonController extends BoxController { List? validateFields; bool? enabled; bool? outline; - int? buttonWidth; - int? buttonHeight; int? gap; IconModel? startingIcon; @@ -124,10 +122,15 @@ class ButtonState extends WidgetState