From 0775536b4ed9ae6b906f1071cc436b7367eca81c Mon Sep 17 00:00:00 2001 From: Pourqavam Date: Thu, 16 Jun 2022 16:18:07 +0430 Subject: [PATCH 1/3] Cancel old timer of text selection double tap Pass the "A Focused text-field will not lose focus when clicking on its decoration" test (A Timer is still pending even after the widget tree was disposed) --- packages/flutter/lib/src/widgets/text_selection.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 807e4972b872c..2317770ee26a6 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -2015,6 +2015,7 @@ class _TextSelectionGestureDetectorState extends State Date: Thu, 16 Jun 2022 16:22:03 +0430 Subject: [PATCH 2/3] Add text field label animation duration and curve test Update the old tests based on the new duration and curve --- .../test/material/input_decorator_test.dart | 85 +++++++++++++++---- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index a7b63da15ff42..16d6a46cbebd2 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -256,7 +256,7 @@ void main() { ); // The label animates downwards from it's initial position - // above the input text. The animation's duration is 200ms. + // above the input text. The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double labelY50ms = tester.getTopLeft(find.text('label')).dy; @@ -286,7 +286,7 @@ void main() { ); // The label animates upwards from it's initial position - // above the input text. The animation's duration is 200ms. + // above the input text. The animation's duration is 167ms. await tester.pump(const Duration(milliseconds: 50)); final double labelY50ms = tester.getTopLeft(find.text('label')).dy; expect(labelY50ms, inExclusiveRange(12.0, 28.0)); @@ -541,7 +541,7 @@ void main() { ); // The label animates downwards from it's initial position - // above the input text. The animation's duration is 200ms. + // above the input text. The animation's duration is 167ms. await tester.pump(const Duration(milliseconds: 50)); final double labelY50ms = tester.getTopLeft(find.byKey(key)).dy; expect(labelY50ms, inExclusiveRange(12.0, 20.0)); @@ -581,7 +581,7 @@ void main() { ); // The label animates upwards from it's initial position - // above the input text. The animation's duration is 200ms. + // above the input text. The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double labelY50ms = tester.getTopLeft(find.byKey(key)).dy; @@ -690,6 +690,55 @@ void main() { expect(tester.getBottomLeft(find.byKey(key)).dy, tester.getBottomLeft(find.text('hint')).dy); }); + testWidgets('InputDecorator floating label animation duration and curve', (WidgetTester tester) async { + Future pumpInputDecorator({ + required bool isFocused, + }) async { + return tester.pumpWidget( + buildInputDecorator( + isEmpty: true, + isFocused: isFocused, + decoration: const InputDecoration( + labelText: 'label', + floatingLabelBehavior: FloatingLabelBehavior.auto, + ), + ), + ); + } + await pumpInputDecorator(isFocused: false); + expect(tester.getTopLeft(find.text('label')).dy, 20.0); + + // The label animates upwards and scales down. + // The animation duration is 167ms and the curve is fastOutSlowIn. + await pumpInputDecorator(isFocused: true); + await tester.pump(const Duration(milliseconds: 42)); + expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.06, 0.5)); + await tester.pump(const Duration(milliseconds: 42)); + expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.78, 0.5)); + await tester.pump(const Duration(milliseconds: 42)); + expect(tester.getTopLeft(find.text('label')).dy, closeTo(12.31, 0.5)); + await tester.pump(const Duration(milliseconds: 41)); + expect(tester.getTopLeft(find.text('label')).dy, 12.0); + + // If the animation changes direction without first reaching the + // AnimationStatus.completed or AnimationStatus.dismissed status, + // the CurvedAnimation stays on the same curve in the opposite direction. + // The pumpAndSettle is used to prevent this behavior. + await tester.pumpAndSettle(); + + // The label animates downwards and scales up. + // The animation duration is 167ms and the curve is fastOutSlowIn. + await pumpInputDecorator(isFocused: false); + await tester.pump(const Duration(milliseconds: 42)); + expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.94, 0.5)); + await tester.pump(const Duration(milliseconds: 42)); + expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.22, 0.5)); + await tester.pump(const Duration(milliseconds: 42)); + expect(tester.getTopLeft(find.text('label')).dy, closeTo(19.69, 0.5)); + await tester.pump(const Duration(milliseconds: 41)); + expect(tester.getTopLeft(find.text('label')).dy, 20.0); + }); + group('alignLabelWithHint', () { group('expands false', () { testWidgets('multiline TextField no-strut', (WidgetTester tester) async { @@ -980,7 +1029,7 @@ void main() { ); // The hint's opacity animates from 0.0 to 1.0. - // The animation's duration is 200ms. + // The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double hintOpacity50ms = getOpacity(tester, 'hint'); @@ -1013,7 +1062,7 @@ void main() { ); // The hint's opacity animates from 1.0 to 0.0. - // The animation's duration is 200ms. + // The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double hintOpacity50ms = getOpacity(tester, 'hint'); @@ -1886,7 +1935,7 @@ void main() { ); // The hint's opacity animates from 0.0 to 1.0. - // The animation's duration is 200ms. + // The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double hintOpacity50ms = getOpacity(tester, 'hint'); @@ -1920,7 +1969,7 @@ void main() { ); // The hint's opacity animates from 1.0 to 0.0. - // The animation's duration is 200ms. + // The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double hintOpacity50ms = getOpacity(tester, 'hint'); @@ -1980,7 +2029,7 @@ void main() { ); // The hint's opacity animates from 0.0 to 1.0. - // The animation's duration is 200ms. + // The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double hintOpacity50ms = getOpacity(tester, 'hint'); @@ -2014,7 +2063,7 @@ void main() { ); // The hint's opacity animates from 1.0 to 0.0. - // The animation's duration is 200ms. + // The animation's duration is 167ms. { await tester.pump(const Duration(milliseconds: 50)); final double hintOpacity50ms = getOpacity(tester, 'hint'); @@ -4218,17 +4267,17 @@ void main() { await pumpDecorator(hovering: true, filled: false); expect(getBorderColor(tester), equals(enabledBorderColor)); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 167)); expect(getBorderColor(tester), equals(blendedHoverColor)); await pumpDecorator(hovering: false, filled: false); expect(getBorderColor(tester), equals(blendedHoverColor)); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 167)); expect(getBorderColor(tester), equals(enabledBorderColor)); await pumpDecorator(hovering: false, filled: false, enabled: false); expect(getBorderColor(tester), equals(enabledBorderColor)); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 167)); expect(getBorderColor(tester), equals(disabledColor)); await pumpDecorator(hovering: true, filled: false, enabled: false); @@ -4271,17 +4320,17 @@ void main() { await pumpDecorator(focused: true, filled: false); expect(getBorderColor(tester), equals(enabledBorderColor)); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 167)); expect(getBorderColor(tester), equals(focusColor)); await pumpDecorator(focused: false, filled: false); expect(getBorderColor(tester), equals(focusColor)); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 167)); expect(getBorderColor(tester), equals(enabledBorderColor)); await pumpDecorator(focused: false, filled: false, enabled: false); expect(getBorderColor(tester), equals(enabledBorderColor)); - await tester.pump(const Duration(milliseconds: 200)); + await tester.pump(const Duration(milliseconds: 167)); expect(getBorderColor(tester), equals(disabledColor)); await pumpDecorator(focused: true, filled: false, enabled: false); @@ -5278,8 +5327,8 @@ void main() { // Click for Focus. await tester.tap(find.byType(TextField)); - // Default animation duration is 200 millisecond. - await tester.pumpFrames(target, const Duration(milliseconds: 100)); + // Default animation duration is 167ms. + await tester.pumpFrames(target, const Duration(milliseconds: 80)); expect(getLabelRect(tester).width > labelWidth, isTrue); expect(getLabelRect(tester).width < floatedLabelWidth, isTrue); From 4cdb0ccc745150f5ae590a560d9e0cc614bc1c0d Mon Sep 17 00:00:00 2001 From: Pourqavam <39864929+Pourqavam@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:27:34 +0430 Subject: [PATCH 3/3] Fix text field label animation duration and curve --- .../lib/src/material/input_decorator.dart | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index dbbb4b2b50323..31d6204d58b6e 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -16,7 +16,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; -const Duration _kTransitionDuration = Duration(milliseconds: 200); +// The duration value extracted from: +// https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/textfield/TextInputLayout.java +const Duration _kTransitionDuration = Duration(milliseconds: 167); const Curve _kTransitionCurve = Curves.fastOutSlowIn; const double _kFinalLabelScale = 0.75; @@ -186,6 +188,7 @@ class _BorderContainerState extends State<_BorderContainer> with TickerProviderS _borderAnimation = CurvedAnimation( parent: _controller, curve: _kTransitionCurve, + reverseCurve: _kTransitionCurve.flipped, ); _border = _InputBorderTween( begin: widget.border, @@ -1861,8 +1864,9 @@ class InputDecorator extends StatefulWidget { } class _InputDecoratorState extends State with TickerProviderStateMixin { - late AnimationController _floatingLabelController; - late AnimationController _shakingLabelController; + late final AnimationController _floatingLabelController; + late final Animation _floatingLabelAnimation; + late final AnimationController _shakingLabelController; final _InputBorderGap _borderGap = _InputBorderGap(); @override @@ -1879,6 +1883,11 @@ class _InputDecoratorState extends State with TickerProviderStat value: labelIsInitiallyFloating ? 1.0 : 0.0, ); _floatingLabelController.addListener(_handleChange); + _floatingLabelAnimation = CurvedAnimation( + parent: _floatingLabelController, + curve: _kTransitionCurve, + reverseCurve: _kTransitionCurve.flipped, + ); _shakingLabelController = AnimationController( duration: _kTransitionDuration, @@ -2186,7 +2195,7 @@ class _InputDecoratorState extends State with TickerProviderStat final Widget container = _BorderContainer( border: border, gap: _borderGap, - gapAnimation: _floatingLabelController.view, + gapAnimation: _floatingLabelAnimation, fillColor: _getFillColor(themeData), hoverColor: _getHoverColor(themeData), isHovering: isHovering, @@ -2351,7 +2360,7 @@ class _InputDecoratorState extends State with TickerProviderStat isCollapsed: decoration!.isCollapsed, floatingLabelHeight: floatingLabelHeight, floatingLabelAlignment: decoration!.floatingLabelAlignment!, - floatingLabelProgress: _floatingLabelController.value, + floatingLabelProgress: _floatingLabelAnimation.value, border: border, borderGap: _borderGap, alignLabelWithHint: decoration!.alignLabelWithHint ?? false,