From 99876ba2ceb9060cb9ff784ff9f9305377671fed Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Fri, 3 May 2024 17:16:05 +0200 Subject: [PATCH] Always relies on floatingLabelStyle when FloatingLabelBehavior.always (#147374) ## Description With this PR, when `InputDecorator.floatingLabelBehavior` is set to `FloatingLabelBehavior.always` the label style is always set to `InputDecorator.floatingLabelStyle`, previously `InputDecorator.labelStyle` was used when the field was not focused or was empty. ## Related Issue Fixes https://github.com/flutter/flutter/issues/147231 ## Tests Adds 1 test for this particular issue and several missing tests. --- .../lib/src/material/input_decorator.dart | 18 +- .../test/material/input_decorator_test.dart | 174 ++++++++++++++++++ 2 files changed, 183 insertions(+), 9 deletions(-) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index c69ede66934..dce8c75ea7d 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -1828,8 +1828,11 @@ class InputDecorator extends StatefulWidget { /// Whether the label needs to get out of the way of the input, either by /// floating or disappearing. /// - /// Will withdraw when not empty, or when focused while enabled. - bool get _labelShouldWithdraw => !isEmpty || (isFocused && decoration.enabled); + /// Will withdraw when not empty, when focused while enabled, or when + /// floating behavior is [FloatingLabelBehavior.always]. + bool get _labelShouldWithdraw => !isEmpty + || (isFocused && decoration.enabled) + || decoration.floatingLabelBehavior == FloatingLabelBehavior.always; @override State createState() => _InputDecoratorState(); @@ -1872,9 +1875,8 @@ class _InputDecoratorState extends State with TickerProviderStat void initState() { super.initState(); - final bool labelIsInitiallyFloating = widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always - || (widget.decoration.floatingLabelBehavior != FloatingLabelBehavior.never && - widget._labelShouldWithdraw); + final bool labelIsInitiallyFloating = widget.decoration.floatingLabelBehavior != FloatingLabelBehavior.never + && widget._labelShouldWithdraw; _floatingLabelController = AnimationController( duration: _kTransitionDuration, @@ -1937,8 +1939,7 @@ class _InputDecoratorState extends State with TickerProviderStat final bool floatBehaviorChanged = widget.decoration.floatingLabelBehavior != old.decoration.floatingLabelBehavior; if (widget._labelShouldWithdraw != old._labelShouldWithdraw || floatBehaviorChanged) { - if (_floatingLabelEnabled - && (widget._labelShouldWithdraw || widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always)) { + if (_floatingLabelEnabled && widget._labelShouldWithdraw) { _floatingLabelController.forward(); } else { _floatingLabelController.reverse(); @@ -2028,8 +2029,7 @@ class _InputDecoratorState extends State with TickerProviderStat // hint would. bool get _hasInlineLabel { return !widget._labelShouldWithdraw - && (decoration.labelText != null || decoration.label != null) - && decoration.floatingLabelBehavior != FloatingLabelBehavior.always; + && (decoration.labelText != null || decoration.label != null); } // If the label is a floating placeholder, it's always shown. diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 4efdd2a05b1..88e90345bfa 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -2032,6 +2032,137 @@ void main() { }); }); }); + + testWidgets('floatingLabelStyle overrides default style', (WidgetTester tester) async { + const TextStyle floatingLabelStyle = TextStyle(color: Colors.indigo, fontSize: 16.0); + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, // Label appears floating above input field. + decoration: const InputDecoration( + labelText: labelText, + floatingLabelStyle: floatingLabelStyle, + ), + ), + ); + + expect(getLabelStyle(tester).color, floatingLabelStyle.color); + expect(getLabelStyle(tester).fontSize, floatingLabelStyle.fontSize); + }); + + testWidgets('floatingLabelStyle defaults to labelStyle', (WidgetTester tester) async { + const TextStyle labelStyle = TextStyle(color: Colors.amber, fontSize: 16.0); + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, // Label appears floating above input field. + decoration: const InputDecoration( + labelText: labelText, + labelStyle: labelStyle, + ), + ), + ); + + expect(getLabelStyle(tester).color, labelStyle.color); + expect(getLabelStyle(tester).fontSize, labelStyle.fontSize); + }); + + testWidgets('floatingLabelStyle takes precedence over labelStyle', (WidgetTester tester) async { + const TextStyle labelStyle = TextStyle(color: Colors.amber, fontSize: 16.0); + const TextStyle floatingLabelStyle = TextStyle(color: Colors.indigo, fontSize: 16.0); + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, // Label appears floating above input field. + decoration: const InputDecoration( + labelText: labelText, + labelStyle: labelStyle, + floatingLabelStyle: floatingLabelStyle, + ), + ), + ); + + expect(getLabelStyle(tester).color, floatingLabelStyle.color); + expect(getLabelStyle(tester).fontSize, floatingLabelStyle.fontSize); + }); + + testWidgets('InputDecorationTheme labelStyle overrides default style', (WidgetTester tester) async { + const TextStyle labelStyle = TextStyle(color: Colors.amber, fontSize: 16.0); + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, // Label appears inline, on top of the input field. + inputDecorationTheme: const InputDecorationTheme( + labelStyle: labelStyle, + ), + decoration: const InputDecoration( + labelText: labelText, + ), + ), + ); + + expect(getLabelStyle(tester).color, labelStyle.color); + }); + + testWidgets('InputDecorationTheme floatingLabelStyle overrides default style', (WidgetTester tester) async { + const TextStyle floatingLabelStyle = TextStyle(color: Colors.indigo, fontSize: 16.0); + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, // Label appears floating above input field. + inputDecorationTheme: const InputDecorationTheme( + floatingLabelStyle: floatingLabelStyle, + ), + decoration: const InputDecoration( + labelText: labelText, + ), + ), + ); + + expect(getLabelStyle(tester).color, floatingLabelStyle.color); + }); + + testWidgets('floatingLabelStyle is always used when FloatingLabelBehavior.always', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/147231. + const TextStyle labelStyle = TextStyle(color: Colors.amber, fontSize: 16.0); + const TextStyle floatingLabelStyle = TextStyle(color: Colors.indigo, fontSize: 16.0); + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + decoration: const InputDecoration( + labelText: labelText, + labelStyle: labelStyle, + floatingLabelStyle: floatingLabelStyle, + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + ), + ); + + expect(getLabelStyle(tester).color, floatingLabelStyle.color); + expect(getLabelStyle(tester).fontSize, floatingLabelStyle.fontSize); + + // Focus the input decorator. + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: true, + decoration: const InputDecoration( + labelText: labelText, + labelStyle: labelStyle, + floatingLabelStyle: floatingLabelStyle, + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + ), + ); + + expect(getLabelStyle(tester).color, floatingLabelStyle.color); + expect(getLabelStyle(tester).fontSize, floatingLabelStyle.fontSize); + }); }); group('Material3 - InputDecoration labelText layout', () { @@ -4915,6 +5046,49 @@ void main() { expect(getOpacity(tester, prefixText), 1.0); }); + testWidgets('Prefix and suffix are not visible when decorator is empty', (WidgetTester tester) async { + const String prefixText = 'Prefix'; + const String suffixText = 'Suffix'; + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + decoration: const InputDecoration( + filled: true, + labelText: labelText, + prefixText: prefixText, + suffixText: suffixText, + ), + ), + ); + + // Prefix and suffix are hidden. + expect(getOpacity(tester, prefixText), 0.0); + expect(getOpacity(tester, suffixText), 0.0); + }); + + testWidgets('Prefix and suffix are visible when decorator is empty and floating behavior is FloatingBehavior.always', (WidgetTester tester) async { + const String prefixText = 'Prefix'; + const String suffixText = 'Suffix'; + + await tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + decoration: const InputDecoration( + filled: true, + labelText: labelText, + prefixText: prefixText, + suffixText: suffixText, + floatingLabelBehavior: FloatingLabelBehavior.always, + ), + ), + ); + + // Prefix and suffix are visible. + expect(getOpacity(tester, prefixText), 1.0); + expect(getOpacity(tester, suffixText), 1.0); + }); + testWidgets('OutlineInputBorder and InputDecorator long labels and in Floating, the width should ignore the icon width', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/64427. const String labelText = 'Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.';