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.
This commit is contained in:
Bruno Leroux 2024-08-10 00:04:56 +02:00 committed by GitHub
parent ed47c4bb1f
commit 5609019500
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 116 additions and 22 deletions

View File

@ -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<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed));
properties.add(DiagnosticsProperty<Color>('iconColor', iconColor, defaultValue: defaultTheme.iconColor));
properties.add(DiagnosticsProperty<Color>('prefixIconColor', prefixIconColor, defaultValue: defaultTheme.prefixIconColor));
properties.add(DiagnosticsProperty<BoxConstraints>('prefixIconConstraints', prefixIconConstraints, defaultValue: defaultTheme.prefixIconConstraints));
properties.add(DiagnosticsProperty<TextStyle>('prefixStyle', prefixStyle, defaultValue: defaultTheme.prefixStyle));
properties.add(DiagnosticsProperty<Color>('suffixIconColor', suffixIconColor, defaultValue: defaultTheme.suffixIconColor));
properties.add(DiagnosticsProperty<BoxConstraints>('suffixIconConstraints', suffixIconConstraints, defaultValue: defaultTheme.suffixIconConstraints));
properties.add(DiagnosticsProperty<TextStyle>('suffixStyle', suffixStyle, defaultValue: defaultTheme.suffixStyle));
properties.add(DiagnosticsProperty<TextStyle>('counterStyle', counterStyle, defaultValue: defaultTheme.counterStyle));
properties.add(DiagnosticsProperty<bool>('filled', filled, defaultValue: defaultTheme.filled));

View File

@ -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<String> description = builder.properties
.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[
'labelStyle: TextStyle(<all styles inherited>)',
'floatingLabelStyle: TextStyle(<all styles inherited>)',
'helperStyle: TextStyle(<all styles inherited>)',
'helperMaxLines: 6',
'hintStyle: TextStyle(<all styles inherited>)',
'errorStyle: TextStyle(<all styles inherited>)',
'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(<all styles inherited>)',
'suffixIconColor: MaterialColor(primary value: Color(0xff2196f3))',
'suffixIconConstraints: BoxConstraints(w=10.0, h=30.0)',
'suffixStyle: TextStyle(<all styles inherited>)',
'counterStyle: TextStyle(<all styles inherited>)',
'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)',
]);
});