From 56090195000ec108cfe3fc99c14ad2fa6f015448 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Sat, 10 Aug 2024 00:04:56 +0200 Subject: [PATCH] Expose affixes icon constraints in InputDecorationTheme (#153089) ## Description This PR makes the existing `InputDecoration.prefixIconConstraints` and `InputDecoration.suffixIconConstraints` configurable from an `InputDecorationTheme`. ## Related Issue Related to https://github.com/flutter/flutter/issues/138691 (this is needed before providing a fix or a workaround for it). ## Tests Update and split one existing test into two different tests. Update the existing test related to debugFillDescription by adding all the non tested properties. --- .../lib/src/material/input_decorator.dart | 64 ++++++++++++++-- .../test/material/input_decorator_test.dart | 74 +++++++++++++++---- 2 files changed, 116 insertions(+), 22 deletions(-) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 8132f09caff..7afb9803cac 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -3089,9 +3089,10 @@ class InputDecoration { /// This can be used to modify the [BoxConstraints] surrounding [prefixIcon]. /// /// This property is particularly useful for getting the decoration's height - /// less than 48px. This can be achieved by setting [isDense] to true and - /// setting the constraints' minimum height and width to a value lower than - /// 48px. + /// less than the minimum tappable height (which is 48px when the visual + /// density is set to [VisualDensity.standard]). This can be achieved by + /// setting [isDense] to true and setting the constraints' minimum height + /// and width to a value lower than the minimum tappable size. /// /// {@tool dartpad} /// This example shows the differences between two `TextField` widgets when @@ -3263,9 +3264,10 @@ class InputDecoration { /// This can be used to modify the [BoxConstraints] surrounding [suffixIcon]. /// /// This property is particularly useful for getting the decoration's height - /// less than 48px. This can be achieved by setting [isDense] to true and - /// setting the constraints' minimum height and width to a value lower than - /// 48px. + /// less than the minimum tappable height (which is 48px when the visual + /// density is set to [VisualDensity.standard]). This can be achieved by + /// setting [isDense] to true and setting the constraints' minimum height + /// and width to a value lower than the minimum tappable size. /// /// If null, a [BoxConstraints] with a minimum width and height of 48px is /// used. @@ -3692,8 +3694,10 @@ class InputDecoration { iconColor: iconColor ?? theme.iconColor, prefixStyle: prefixStyle ?? theme.prefixStyle, prefixIconColor: prefixIconColor ?? theme.prefixIconColor, + prefixIconConstraints: prefixIconConstraints ?? theme.prefixIconConstraints, suffixStyle: suffixStyle ?? theme.suffixStyle, suffixIconColor: suffixIconColor ?? theme.suffixIconColor, + suffixIconConstraints: suffixIconConstraints ?? theme.suffixIconConstraints, counterStyle: counterStyle ?? theme.counterStyle, filled: filled ?? theme.filled, fillColor: fillColor ?? theme.fillColor, @@ -3921,8 +3925,10 @@ class InputDecorationTheme with Diagnosticable { this.iconColor, this.prefixStyle, this.prefixIconColor, + this.prefixIconConstraints, this.suffixStyle, this.suffixIconColor, + this.suffixIconConstraints, this.counterStyle, this.filled = false, this.fillColor, @@ -4063,6 +4069,21 @@ class InputDecorationTheme with Diagnosticable { /// If null, defaults to the [ColorScheme.primary]. final Color? prefixIconColor; + /// The constraints to use for [InputDecoration.prefixIconConstraints]. + /// + /// This can be used to modify the [BoxConstraints] surrounding + /// [InputDecoration.prefixIcon]. + /// + /// This property is particularly useful for getting the decoration's height + /// less than the minimum tappable height (which is 48px when the visual + /// density is set to [VisualDensity.standard]). This can be achieved by + /// setting [isDense] to true and setting the constraints' minimum height + /// and width to a value lower than the minimum tappable size. + /// + /// If null, [BoxConstraints] with a minimum width and height of 48px is + /// used. + final BoxConstraints? prefixIconConstraints; + /// The style to use for the [InputDecoration.suffixText]. /// /// If [suffixStyle] is a [WidgetStateTextStyle], then the effective @@ -4081,6 +4102,21 @@ class InputDecorationTheme with Diagnosticable { /// If null, defaults to the [ColorScheme.primary]. final Color? suffixIconColor; + /// The constraints to use for [InputDecoration.suffixIconConstraints]. + /// + /// This can be used to modify the [BoxConstraints] surrounding + /// [InputDecoration.suffixIcon]. + /// + /// This property is particularly useful for getting the decoration's height + /// less than the minimum tappable height (which is 48px when the visual + /// density is set to [VisualDensity.standard]). This can be achieved by + /// setting [isDense] to true and setting the constraints' minimum height + /// and width to a value lower than the minimum tappable size. + /// + /// If null, [BoxConstraints] with a minimum width and height of 48px is + /// used. + final BoxConstraints? suffixIconConstraints; + /// The style to use for the [InputDecoration.counterText]. /// /// If [counterStyle] is a [WidgetStateTextStyle], then the effective @@ -4332,8 +4368,10 @@ class InputDecorationTheme with Diagnosticable { Color? iconColor, TextStyle? prefixStyle, Color? prefixIconColor, + BoxConstraints? prefixIconConstraints, TextStyle? suffixStyle, Color? suffixIconColor, + BoxConstraints? suffixIconConstraints, TextStyle? counterStyle, bool? filled, Color? fillColor, @@ -4367,8 +4405,10 @@ class InputDecorationTheme with Diagnosticable { isCollapsed: isCollapsed ?? this.isCollapsed, prefixStyle: prefixStyle ?? this.prefixStyle, prefixIconColor: prefixIconColor ?? this.prefixIconColor, + prefixIconConstraints: prefixIconConstraints ?? this.prefixIconConstraints, suffixStyle: suffixStyle ?? this.suffixStyle, suffixIconColor: suffixIconColor ?? this.suffixIconColor, + suffixIconConstraints: suffixIconConstraints ?? this.suffixIconConstraints, counterStyle: counterStyle ?? this.counterStyle, filled: filled ?? this.filled, fillColor: fillColor ?? this.fillColor, @@ -4413,8 +4453,10 @@ class InputDecorationTheme with Diagnosticable { iconColor: iconColor ?? inputDecorationTheme.iconColor, prefixStyle: prefixStyle ?? inputDecorationTheme.prefixStyle, prefixIconColor: prefixIconColor ?? inputDecorationTheme.prefixIconColor, + prefixIconConstraints: prefixIconConstraints ?? inputDecorationTheme.prefixIconConstraints, suffixStyle: suffixStyle ?? inputDecorationTheme.suffixStyle, suffixIconColor: suffixIconColor ?? inputDecorationTheme.suffixIconColor, + suffixIconConstraints: suffixIconConstraints ?? inputDecorationTheme.suffixIconConstraints, counterStyle: counterStyle ?? inputDecorationTheme.counterStyle, fillColor: fillColor ?? inputDecorationTheme.fillColor, activeIndicatorBorder: activeIndicatorBorder ?? inputDecorationTheme.activeIndicatorBorder, @@ -4448,11 +4490,13 @@ class InputDecorationTheme with Diagnosticable { iconColor, prefixStyle, prefixIconColor, + prefixIconConstraints, suffixStyle, suffixIconColor, - counterStyle, - filled, + suffixIconConstraints, Object.hash( + counterStyle, + filled, fillColor, activeIndicatorBorder, outlineBorder, @@ -4493,8 +4537,10 @@ class InputDecorationTheme with Diagnosticable { && other.iconColor == iconColor && other.prefixStyle == prefixStyle && other.prefixIconColor == prefixIconColor + && other.prefixIconConstraints == prefixIconConstraints && other.suffixStyle == suffixStyle && other.suffixIconColor == suffixIconColor + && other.suffixIconConstraints == suffixIconConstraints && other.counterStyle == counterStyle && other.floatingLabelBehavior == floatingLabelBehavior && other.floatingLabelAlignment == floatingLabelAlignment @@ -4534,8 +4580,10 @@ class InputDecorationTheme with Diagnosticable { properties.add(DiagnosticsProperty('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed)); properties.add(DiagnosticsProperty('iconColor', iconColor, defaultValue: defaultTheme.iconColor)); properties.add(DiagnosticsProperty('prefixIconColor', prefixIconColor, defaultValue: defaultTheme.prefixIconColor)); + properties.add(DiagnosticsProperty('prefixIconConstraints', prefixIconConstraints, defaultValue: defaultTheme.prefixIconConstraints)); properties.add(DiagnosticsProperty('prefixStyle', prefixStyle, defaultValue: defaultTheme.prefixStyle)); properties.add(DiagnosticsProperty('suffixIconColor', suffixIconColor, defaultValue: defaultTheme.suffixIconColor)); + properties.add(DiagnosticsProperty('suffixIconConstraints', suffixIconConstraints, defaultValue: defaultTheme.suffixIconConstraints)); properties.add(DiagnosticsProperty('suffixStyle', suffixStyle, defaultValue: defaultTheme.suffixStyle)); properties.add(DiagnosticsProperty('counterStyle', counterStyle, defaultValue: defaultTheme.counterStyle)); properties.add(DiagnosticsProperty('filled', filled, defaultValue: defaultTheme.filled)); diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 38a7963df05..ded7b21a4a6 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -6777,7 +6777,7 @@ void main() { expect(nodeValues.length, 11); }); - testWidgets('InputDecorationTheme.inputDecoration', (WidgetTester tester) async { + testWidgets('InputDecorationTheme.applyDefaults initializes empty field', (WidgetTester tester) async { const TextStyle themeStyle = TextStyle(color: Color(0xFF00FFFF)); const Color themeColor = Color(0xFF00FF00); const InputBorder themeInputBorder = OutlineInputBorder( @@ -6785,16 +6785,8 @@ void main() { color: Color(0xFF0000FF), ), ); - const TextStyle decorationStyle = TextStyle(color: Color(0xFFFFFF00)); - const Color decorationColor = Color(0xFF0000FF); - const InputBorder decorationInputBorder = OutlineInputBorder( - borderSide: BorderSide( - color: Color(0xFFFF00FF), - ), - ); - // InputDecorationTheme arguments define InputDecoration properties. - InputDecoration decoration = const InputDecoration().applyDefaults( + final InputDecoration decoration = const InputDecoration().applyDefaults( const InputDecorationTheme( labelStyle: themeStyle, floatingLabelStyle: themeStyle, @@ -6810,8 +6802,10 @@ void main() { iconColor: themeColor, prefixStyle: themeStyle, prefixIconColor: themeColor, + prefixIconConstraints: BoxConstraints(minWidth: 10, maxWidth: 10, minHeight: 30, maxHeight: 30), suffixStyle: themeStyle, suffixIconColor: themeColor, + suffixIconConstraints: BoxConstraints(minWidth: 20, maxWidth: 20, minHeight: 40, maxHeight: 40), counterStyle: themeStyle, filled: true, fillColor: themeColor, @@ -6842,8 +6836,10 @@ void main() { expect(decoration.iconColor, themeColor); expect(decoration.prefixStyle, themeStyle); expect(decoration.prefixIconColor, themeColor); + expect(decoration.prefixIconConstraints, const BoxConstraints(minWidth: 10, maxWidth: 10, minHeight: 30, maxHeight: 30)); expect(decoration.suffixStyle, themeStyle); expect(decoration.suffixIconColor, themeColor); + expect(decoration.suffixIconConstraints, const BoxConstraints(minWidth: 20, maxWidth: 20, minHeight: 40, maxHeight: 40)); expect(decoration.counterStyle, themeStyle); expect(decoration.filled, true); expect(decoration.fillColor, themeColor); @@ -6857,9 +6853,26 @@ void main() { expect(decoration.border, InputBorder.none); expect(decoration.alignLabelWithHint, true); expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40)); + }); - // InputDecoration (baseDecoration) defines InputDecoration properties - decoration = const InputDecoration( + testWidgets('InputDecorationTheme.applyDefaults does not override non-null fields', (WidgetTester tester) async { + const TextStyle themeStyle = TextStyle(color: Color(0xFF00FFFF)); + const Color themeColor = Color(0xFF00FF00); + const InputBorder themeInputBorder = OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF0000FF), + ), + ); + const TextStyle decorationStyle = TextStyle(color: Color(0xFFFFFF00)); + const Color decorationColor = Color(0xFF0000FF); + const InputBorder decorationInputBorder = OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF00FF), + ), + ); + const BoxConstraints decorationConstraints = BoxConstraints(minWidth: 40, maxWidth: 50, minHeight: 60, maxHeight: 70); + + final InputDecoration decoration = const InputDecoration( labelStyle: decorationStyle, floatingLabelStyle: decorationStyle, helperStyle: decorationStyle, @@ -6874,8 +6887,10 @@ void main() { iconColor: decorationColor, prefixStyle: decorationStyle, prefixIconColor: decorationColor, + prefixIconConstraints: decorationConstraints, suffixStyle: decorationStyle, suffixIconColor: decorationColor, + suffixIconConstraints: decorationConstraints, counterStyle: decorationStyle, filled: false, fillColor: decorationColor, @@ -6888,7 +6903,7 @@ void main() { enabledBorder: decorationInputBorder, border: OutlineInputBorder(), alignLabelWithHint: false, - constraints: BoxConstraints(minWidth: 40, maxWidth: 50, minHeight: 60, maxHeight: 70), + constraints: decorationConstraints, ).applyDefaults( const InputDecorationTheme( labelStyle: themeStyle, @@ -6937,8 +6952,10 @@ void main() { expect(decoration.iconColor, decorationColor); expect(decoration.prefixStyle, decorationStyle); expect(decoration.prefixIconColor, decorationColor); + expect(decoration.prefixIconConstraints, decorationConstraints); expect(decoration.suffixStyle, decorationStyle); expect(decoration.suffixIconColor, decorationColor); + expect(decoration.suffixIconConstraints, decorationConstraints); expect(decoration.counterStyle, decorationStyle); expect(decoration.filled, false); expect(decoration.fillColor, decorationColor); @@ -6951,7 +6968,7 @@ void main() { expect(decoration.enabledBorder, decorationInputBorder); expect(decoration.border, const OutlineInputBorder()); expect(decoration.alignLabelWithHint, false); - expect(decoration.constraints, const BoxConstraints(minWidth: 40, maxWidth: 50, minHeight: 60, maxHeight: 70)); + expect(decoration.constraints, decorationConstraints); }); testWidgets('InputDecorationTheme.inputDecoration with MaterialState', (WidgetTester tester) async { @@ -7131,20 +7148,34 @@ void main() { testWidgets('InputDecorationTheme implements debugFillDescription', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); + const BoxConstraints constraints = BoxConstraints(minWidth: 10, maxWidth: 10, minHeight: 30, maxHeight: 30); const InputDecorationTheme( labelStyle: TextStyle(), + floatingLabelStyle: TextStyle(), helperStyle: TextStyle(), helperMaxLines: 6, hintStyle: TextStyle(), + errorStyle: TextStyle(), errorMaxLines: 5, floatingLabelBehavior: FloatingLabelBehavior.never, + floatingLabelAlignment: FloatingLabelAlignment.center, + isDense: true, contentPadding: EdgeInsetsDirectional.only(start: 40.0, top: 12.0, bottom: 12.0), + isCollapsed: true, + iconColor: Colors.red, + prefixIconColor: Colors.blue, + prefixIconConstraints: constraints, prefixStyle: TextStyle(), + suffixIconColor: Colors.blue, + suffixIconConstraints: constraints, suffixStyle: TextStyle(), counterStyle: TextStyle(), filled: true, fillColor: Colors.red, + activeIndicatorBorder: BorderSide(), + outlineBorder: BorderSide(), focusColor: Colors.blue, + hoverColor: Colors.green, errorBorder: UnderlineInputBorder(), focusedBorder: UnderlineInputBorder(), focusedErrorBorder: UnderlineInputBorder(), @@ -7152,24 +7183,38 @@ void main() { enabledBorder: UnderlineInputBorder(), border: UnderlineInputBorder(), alignLabelWithHint: true, + constraints: constraints, ).debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode n) => n.toString()).toList(); expect(description, [ 'labelStyle: TextStyle()', + 'floatingLabelStyle: TextStyle()', 'helperStyle: TextStyle()', 'helperMaxLines: 6', 'hintStyle: TextStyle()', + 'errorStyle: TextStyle()', 'errorMaxLines: 5', 'floatingLabelBehavior: FloatingLabelBehavior.never', + 'floatingLabelAlignment: FloatingLabelAlignment.center', + 'isDense: true', 'contentPadding: EdgeInsetsDirectional(40.0, 12.0, 0.0, 12.0)', + 'isCollapsed: true', + 'iconColor: MaterialColor(primary value: Color(0xfff44336))', + 'prefixIconColor: MaterialColor(primary value: Color(0xff2196f3))', + 'prefixIconConstraints: BoxConstraints(w=10.0, h=30.0)', 'prefixStyle: TextStyle()', + 'suffixIconColor: MaterialColor(primary value: Color(0xff2196f3))', + 'suffixIconConstraints: BoxConstraints(w=10.0, h=30.0)', 'suffixStyle: TextStyle()', 'counterStyle: TextStyle()', 'filled: true', 'fillColor: MaterialColor(primary value: Color(0xfff44336))', + 'activeIndicatorBorder: BorderSide', + 'outlineBorder: BorderSide', 'focusColor: MaterialColor(primary value: Color(0xff2196f3))', + 'hoverColor: MaterialColor(primary value: Color(0xff4caf50))', 'errorBorder: UnderlineInputBorder()', 'focusedBorder: UnderlineInputBorder()', 'focusedErrorBorder: UnderlineInputBorder()', @@ -7177,6 +7222,7 @@ void main() { 'enabledBorder: UnderlineInputBorder()', 'border: UnderlineInputBorder()', 'alignLabelWithHint: true', + 'constraints: BoxConstraints(w=10.0, h=30.0)', ]); });