diff --git a/AUTHORS b/AUTHORS index 71d7f107b1b..ef495b3bb39 100644 --- a/AUTHORS +++ b/AUTHORS @@ -83,3 +83,4 @@ Mirko Mucaria Karol Czeryna Callum Moffat Koutaro Mori +Sergei Smitskoi diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 67a5749093b..333fed66601 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -971,11 +971,17 @@ class _RenderDecoration extends RenderBox { final BoxConstraints boxConstraints = layoutConstraints.loosen(); // Layout all the widgets used by InputDecorator - boxToBaseline[prefix] = _layoutLineBox(prefix, boxConstraints); - boxToBaseline[suffix] = _layoutLineBox(suffix, boxConstraints); boxToBaseline[icon] = _layoutLineBox(icon, boxConstraints); - boxToBaseline[prefixIcon] = _layoutLineBox(prefixIcon, boxConstraints); - boxToBaseline[suffixIcon] = _layoutLineBox(suffixIcon, boxConstraints); + final BoxConstraints containerConstraints = boxConstraints.copyWith( + maxWidth: boxConstraints.maxWidth - _boxSize(icon).width, + ); + boxToBaseline[prefixIcon] = _layoutLineBox(prefixIcon, containerConstraints); + boxToBaseline[suffixIcon] = _layoutLineBox(suffixIcon, containerConstraints); + final BoxConstraints contentConstraints = containerConstraints.copyWith( + maxWidth: containerConstraints.maxWidth - contentPadding.horizontal, + ); + boxToBaseline[prefix] = _layoutLineBox(prefix, contentConstraints); + boxToBaseline[suffix] = _layoutLineBox(suffix, contentConstraints); final double inputWidth = math.max( 0.0, @@ -1011,18 +1017,14 @@ class _RenderDecoration extends RenderBox { hint, boxConstraints.copyWith(minWidth: inputWidth, maxWidth: inputWidth), ); - boxToBaseline[counter] = _layoutLineBox(counter, boxConstraints); + boxToBaseline[counter] = _layoutLineBox(counter, contentConstraints); // The helper or error text can occupy the full width less the space // occupied by the icon and counter. boxToBaseline[helperError] = _layoutLineBox( helperError, - boxConstraints.copyWith( - maxWidth: math.max(0.0, boxConstraints.maxWidth - - _boxSize(icon).width - - _boxSize(counter).width - - contentPadding.horizontal, - ), + contentConstraints.copyWith( + maxWidth: math.max(0.0, contentConstraints.maxWidth - _boxSize(counter).width), ), ); @@ -1267,15 +1269,45 @@ class _RenderDecoration extends RenderBox { @override double computeMinIntrinsicHeight(double width) { - double subtextHeight = _lineHeight(width, [helperError, counter]); + final double iconHeight = _minHeight(icon, width); + final double iconWidth = _minWidth(icon, iconHeight); + + width = math.max(width - iconWidth, 0.0); + + final double prefixIconHeight = _minHeight(prefixIcon, width); + final double prefixIconWidth = _minWidth(prefixIcon, prefixIconHeight); + + final double suffixIconHeight = _minHeight(suffixIcon, width); + final double suffixIconWidth = _minWidth(suffixIcon, suffixIconHeight); + + width = math.max(width - contentPadding.horizontal, 0.0); + + final double counterHeight = _minHeight(counter, width); + final double counterWidth = _minWidth(counter, counterHeight); + + final double helperErrorAvailableWidth = math.max(width - counterWidth, 0.0); + final double helperErrorHeight = _minHeight(helperError, helperErrorAvailableWidth); + double subtextHeight = math.max(counterHeight, helperErrorHeight); if (subtextHeight > 0.0) subtextHeight += subtextGap; + + final double prefixHeight = _minHeight(prefix, width); + final double prefixWidth = _minWidth(prefix, prefixHeight); + + final double suffixHeight = _minHeight(suffix, width); + final double suffixWidth = _minWidth(suffix, suffixHeight); + + final double availableInputWidth = math.max(width - prefixWidth - suffixWidth - prefixIconWidth - suffixIconWidth, 0.0); + final double inputHeight = _lineHeight(availableInputWidth, [input, hint]); + final double inputMaxHeight = [inputHeight, prefixHeight, suffixHeight].reduce(math.max); + final Offset densityOffset = decoration.visualDensity!.baseSizeAdjustment; - final double containerHeight = contentPadding.top + final double contentHeight = contentPadding.top + (label == null ? 0.0 : decoration.floatingLabelHeight) - + _lineHeight(width, [prefix, input, suffix]) + + inputMaxHeight + contentPadding.bottom + densityOffset.dy; + final double containerHeight = [iconHeight, contentHeight, prefixIconHeight, suffixIconHeight].reduce(math.max); final double minContainerHeight = decoration.isDense! || expands ? 0.0 : kMinInteractiveDimension; diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index ee74400108e..080eb5c6f0d 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -5023,6 +5023,146 @@ void main() { expect(tester.takeException(), isNull); }); + testWidgets('min intrinsic height for TextField with prefix icon', (WidgetTester tester) async { + // Regression test for: https://github.com/flutter/flutter/issues/87403 + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: SizedBox( + width: 100.0, + child: IntrinsicHeight( + child: Column( + children: [ + TextField( + controller: TextEditingController(text: 'input'), + maxLines: null, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.search), + ), + ), + ], + ), + ), + ), + ), + ), + )); + + expect(tester.takeException(), isNull); + }); + + testWidgets('min intrinsic height for TextField with suffix icon', (WidgetTester tester) async { + // Regression test for: https://github.com/flutter/flutter/issues/87403 + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: SizedBox( + width: 100.0, + child: IntrinsicHeight( + child: Column( + children: [ + TextField( + controller: TextEditingController(text: 'input'), + maxLines: null, + decoration: const InputDecoration( + suffixIcon: Icon(Icons.search), + ), + ), + ], + ), + ), + ), + ), + ), + )); + + expect(tester.takeException(), isNull); + }); + + testWidgets('min intrinsic height for TextField with prefix', (WidgetTester tester) async { + // Regression test for: https://github.com/flutter/flutter/issues/87403 + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: SizedBox( + width: 100.0, + child: IntrinsicHeight( + child: Column( + children: [ + TextField( + controller: TextEditingController(text: 'input'), + maxLines: null, + decoration: const InputDecoration( + prefix: Text('prefix'), + ), + ), + ], + ), + ), + ), + ), + ), + )); + + expect(tester.takeException(), isNull); + }); + + testWidgets('min intrinsic height for TextField with suffix', (WidgetTester tester) async { + // Regression test for: https://github.com/flutter/flutter/issues/87403 + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: SizedBox( + width: 100.0, + child: IntrinsicHeight( + child: Column( + children: [ + TextField( + controller: TextEditingController(text: 'input'), + maxLines: null, + decoration: const InputDecoration( + suffix: Text('suffix'), + ), + ), + ], + ), + ), + ), + ), + ), + )); + + expect(tester.takeException(), isNull); + }); + + testWidgets('min intrinsic height for TextField with icon', (WidgetTester tester) async { + // Regression test for: https://github.com/flutter/flutter/issues/87403 + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: SizedBox( + width: 100.0, + child: IntrinsicHeight( + child: Column( + children: [ + TextField( + controller: TextEditingController(text: 'input'), + maxLines: null, + decoration: const InputDecoration( + icon: Icon(Icons.search), + ), + ), + ], + ), + ), + ), + ), + ), + )); + + expect(tester.takeException(), isNull); + }); + testWidgets('InputDecorationTheme floatingLabelStyle overrides label widget styles when the widget is a text widget (focused)', (WidgetTester tester) async { const TextStyle style16 = TextStyle(fontFamily: 'Ahem', fontSize: 16.0); final TextStyle floatingLabelStyle = style16.merge(const TextStyle(color: Colors.indigo));