diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 05a7ad38774..c6214150fc0 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -859,6 +859,8 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin return !decoration.isCollapsed && decoration.border.isOutline; } + Offset get _densityOffset => decoration.visualDensity.baseSizeAdjustment; + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { if (icon != null) { @@ -1025,9 +1027,8 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin // The height of the input needs to accommodate label above and counter and // helperError below, when they exist. final double bottomHeight = subtextSize?.bottomHeight ?? 0.0; - final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment; final BoxConstraints inputConstraints = boxConstraints - .deflate(EdgeInsets.only(top: contentPadding.vertical + topHeight + bottomHeight + densityOffset.dy)) + .deflate(EdgeInsets.only(top: contentPadding.vertical + topHeight + bottomHeight + _densityOffset.dy)) .tighten(width: inputWidth); final RenderBox? input = this.input; @@ -1067,10 +1068,10 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin + inputHeight + fixBelowInput + contentPadding.bottom - + densityOffset.dy, + + _densityOffset.dy, ); final double minContainerHeight = decoration.isDense! || decoration.isCollapsed || expands - ? 0.0 + ? inputHeight : kMinInteractiveDimension; final double maxContainerHeight = math.max(0.0, boxConstraints.maxHeight - bottomHeight); final double containerHeight = expands @@ -1101,8 +1102,8 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin + inputInternalBaseline + baselineAdjustment + interactiveAdjustment - + densityOffset.dy / 2.0; - final double maxContentHeight = containerHeight - contentPadding.vertical - topHeight - densityOffset.dy; + + _densityOffset.dy / 2.0; + final double maxContentHeight = containerHeight - contentPadding.vertical - topHeight - _densityOffset.dy; final double alignableHeight = fixAboveInput + inputHeight + fixBelowInput; final double maxVerticalOffset = maxContentHeight - alignableHeight; @@ -1234,12 +1235,11 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin final double inputHeight = _lineHeight(availableInputWidth, [input, hint]); final double inputMaxHeight = [inputHeight, prefixHeight, suffixHeight].reduce(math.max); - final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment; final double contentHeight = contentPadding.top + (label == null ? 0.0 : decoration.floatingLabelHeight) + inputMaxHeight + contentPadding.bottom - + densityOffset.dy; + + _densityOffset.dy; final double containerHeight = [iconHeight, contentHeight, prefixIconHeight, suffixIconHeight].reduce(math.max); final double minContainerHeight = decoration.isDense! || expands ? 0.0 @@ -1491,8 +1491,7 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028 // Center the scaled label relative to the border. final double outlinedFloatingY = (-labelHeight * _kFinalLabelScale) / 2.0 + borderWeight / 2.0; - final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment; - final double floatingY = isOutlineBorder ? outlinedFloatingY : contentPadding.top + densityOffset.dy / 2; + final double floatingY = isOutlineBorder ? outlinedFloatingY : contentPadding.top + _densityOffset.dy / 2; final double scale = lerpDouble(1.0, _kFinalLabelScale, t)!; final double centeredFloatX = _boxParentData(container!).offset.dx + _boxSize(container).width / 2.0 - floatWidth / 2.0; diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 7573ee04d19..6a9b78864d8 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -6463,6 +6463,120 @@ void main() { }); }); + group('Material3 - InputDecoration collapsed', () { + // Overall height for a collapsed InputDecorator is 24dp which is the input + // height (font size = 16, line height = 1.5). + const double inputHeight = 24.0; + + testWidgets('Decoration height is set to input height on mobile', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration.collapsed( + hintText: hintText, + ), + ), + ); + + expect(getDecoratorRect(tester).size, const Size(800.0, inputHeight)); + expect(getInputRect(tester).height, inputHeight); + expect(getInputRect(tester).top, 0.0); + expect(getHintOpacity(tester), 0.0); + + // The hint should appear. + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, + decoration: const InputDecoration.collapsed( + hintText: hintText, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(getDecoratorRect(tester).size, const Size(800.0, inputHeight)); + expect(getInputRect(tester).height, inputHeight); + expect(getInputRect(tester).top, 0.0); + expect(getHintOpacity(tester), 1.0); + expect(getHintRect(tester).height, inputHeight); + expect(getHintRect(tester).top, 0.0); + }, variant: TargetPlatformVariant.mobile()); + + testWidgets('Decoration height is set to input height on desktop', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/150763. + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration.collapsed( + hintText: hintText, + ), + ), + ); + + expect(getDecoratorRect(tester).size, const Size(800.0, inputHeight)); + expect(getInputRect(tester).height, inputHeight); + expect(getInputRect(tester).top, 0.0); + expect(getHintOpacity(tester), 0.0); + + // The hint should appear. + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, + decoration: const InputDecoration.collapsed( + hintText: hintText, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(getDecoratorRect(tester).size, const Size(800.0, inputHeight)); + expect(getInputRect(tester).height, inputHeight); + expect(getInputRect(tester).top, 0.0); + expect(getHintOpacity(tester), 1.0); + expect(getHintRect(tester).height, inputHeight); + expect(getHintRect(tester).top, 0.0); + }, variant: TargetPlatformVariant.desktop()); + + testWidgets('InputDecoration.collapsed defaults to no border', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration.collapsed( + hintText: hintText, + ), + ), + ); + + expect(getBorderWeight(tester), 0.0); + }); + + test('InputDecorationTheme.isCollapsed is applied', () { + final InputDecoration decoration = const InputDecoration( + hintText: 'Hello, Flutter!', + ).applyDefaults(const InputDecorationTheme( + isCollapsed: true, + )); + + expect(decoration.isCollapsed, true); + }); + + test('InputDecorationTheme.isCollapsed defaults to false', () { + final InputDecoration decoration = const InputDecoration( + hintText: 'Hello, Flutter!', + ).applyDefaults(const InputDecorationTheme()); + + expect(decoration.isCollapsed, false); + }); + + test('InputDecorationTheme.isCollapsed can be overriden', () { + final InputDecoration decoration = const InputDecoration( + isCollapsed: true, + hintText: 'Hello, Flutter!', + ).applyDefaults(const InputDecorationTheme()); + + expect(decoration.isCollapsed, true); + }); + }); + testWidgets('InputDecorator counter text, widget, and null', (WidgetTester tester) async { Widget buildFrame({ InputCounterWidgetBuilder? buildCounter, @@ -7482,27 +7596,6 @@ void main() { expect(tester.takeException(), isNull); }); - group('isCollapsed parameter works with themeData', () { - test('parameter is provided in InputDecorationTheme', () { - final InputDecoration decoration = const InputDecoration( - hintText: 'Hello, Flutter!', - ).applyDefaults(const InputDecorationTheme( - isCollapsed: true, - )); - - expect(decoration.isCollapsed, true); - }); - - test('parameter is provided in InputDecoration', () { - final InputDecoration decoration = const InputDecoration( - isCollapsed: true, - hintText: 'Hello, Flutter!', - ).applyDefaults(const InputDecorationTheme()); - - expect(decoration.isCollapsed, true); - }); - }); - testWidgets('Ensure the height of labelStyle remains unchanged when TextField is focused', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/141448. final FocusNode focusNode = FocusNode();