From d7b092e42d8d8e8d3cacfdb32f9aceea67f3c3fb Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Fri, 23 Aug 2024 20:15:36 +0300 Subject: [PATCH] Fix `TimePicker` hour and minute inputs are resized on error (#154008) Fixes [Defining inputDecorationTheme in TimePickerThemeData Causes Misalignment of Hour and Minute Input Boxes](https://github.com/flutter/flutter/issues/153549) ### Code sample
expand to view the code sample ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( timePickerTheme: const TimePickerThemeData( inputDecorationTheme: InputDecorationTheme(), ), ), home: Scaffold( body: Center( child: Builder(builder: (BuildContext context) { return ElevatedButton( onPressed: () async { await showTimePicker( context: context, initialEntryMode: TimePickerEntryMode.input, initialTime: TimeOfDay.now(), ); }, child: const Text('Show Time Picker'), ); }), ), ), ); } } ```
### Before Screenshot 2024-08-23 at 16 49 25 ### After Screenshot 2024-08-23 at 16 51 03 --- .../lib/time_picker_template.dart | 2 +- .../flutter/lib/src/material/time_picker.dart | 13 ++++-- .../test/material/time_picker_test.dart | 45 +++++++++++++++++++ .../test/material/time_picker_theme_test.dart | 43 ++++++++++++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/dev/tools/gen_defaults/lib/time_picker_template.dart b/dev/tools/gen_defaults/lib/time_picker_template.dart index 3c4aad5a8e1..7a98a6f9f0e 100644 --- a/dev/tools/gen_defaults/lib/time_picker_template.dart +++ b/dev/tools/gen_defaults/lib/time_picker_template.dart @@ -331,7 +331,7 @@ class _${blockName}DefaultsM3 extends _TimePickerDefaults { // TODO(rami-a): Remove this workaround once // https://github.com/flutter/flutter/issues/54104 // is fixed. - errorStyle: const TextStyle(fontSize: 0, height: 0), + errorStyle: const TextStyle(fontSize: 0), ); } diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index a4fbec21568..e795bbdeb96 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -2047,7 +2047,14 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> with Restora final bool alwaysUse24HourFormat = MediaQuery.alwaysUse24HourFormatOf(context); final InputDecorationTheme inputDecorationTheme = timePickerTheme.inputDecorationTheme ?? defaultTheme.inputDecorationTheme; - InputDecoration inputDecoration = const InputDecoration().applyDefaults(inputDecorationTheme); + InputDecoration inputDecoration = InputDecoration( + // Prevent the error text from appearing when + // timePickerTheme.inputDecorationTheme is used. + // TODO(tahatesser): Remove this workaround once + // https://github.com/flutter/flutter/issues/54104 + // is fixed. + errorStyle: defaultTheme.inputDecorationTheme.errorStyle, + ).applyDefaults(inputDecorationTheme); // Remove the hint text when focused because the centered cursor // appears odd above the hint text. final String? hintText = focusNode.hasFocus ? null : _formattedValue; @@ -3341,7 +3348,7 @@ class _TimePickerDefaultsM2 extends _TimePickerDefaults { // TODO(rami-a): Remove this workaround once // https://github.com/flutter/flutter/issues/54104 // is fixed. - errorStyle: const TextStyle(fontSize: 0, height: 0), + errorStyle: const TextStyle(fontSize: 0, height: 1), ); } @@ -3676,7 +3683,7 @@ class _TimePickerDefaultsM3 extends _TimePickerDefaults { // TODO(rami-a): Remove this workaround once // https://github.com/flutter/flutter/issues/54104 // is fixed. - errorStyle: const TextStyle(fontSize: 0, height: 0), + errorStyle: const TextStyle(fontSize: 0), ); } diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 0b23fa26ed1..ff9634495b0 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -19,10 +19,18 @@ void main() { const String okString = 'OK'; const String amString = 'AM'; const String pmString = 'PM'; + Material getMaterialFromDialog(WidgetTester tester) { return tester.widget(find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first); } + Finder findBorderPainter() { + return find.descendant( + of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'), + matching: find.byWidgetPredicate((Widget w) => w is CustomPaint), + ); + } + testWidgets('Material2 - Dialog size - dial mode', (WidgetTester tester) async { addTearDown(tester.view.reset); @@ -2013,6 +2021,43 @@ void main() { expect(paragraph.text.style!.color, theme.colorScheme.onSurface); expect(paragraph.text.style!.fontSize, 56.0); }); + + // This is a regression test for https://github.com/flutter/flutter/issues/153549. + testWidgets('Time picker hour minute does not resize on error', (WidgetTester tester) async { + await startPicker( + entryMode: TimePickerEntryMode.input, + tester, + (TimeOfDay? value) { }, + ); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); + + // Enter invalid hour. + await tester.enterText(find.byType(TextField).first, 'AB'); + await tester.tap(find.text(okString)); + await tester.pumpAndSettle(); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); + }); + + // This is a regression test for https://github.com/flutter/flutter/issues/153549. + testWidgets('Material2 - Time picker hour minute does not resize on error', (WidgetTester tester) async { + await startPicker( + entryMode: TimePickerEntryMode.input, + tester, + (TimeOfDay? value) { }, + materialType: MaterialType.material2, + ); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); + + // Enter invalid hour. + await tester.enterText(find.byType(TextField).first, 'AB'); + await tester.tap(find.text(okString)); + await tester.pumpAndSettle(); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); + }); } final Finder findDialPaint = find.descendant( diff --git a/packages/flutter/test/material/time_picker_theme_test.dart b/packages/flutter/test/material/time_picker_theme_test.dart index b1b05d95446..19ccb3fac08 100644 --- a/packages/flutter/test/material/time_picker_theme_test.dart +++ b/packages/flutter/test/material/time_picker_theme_test.dart @@ -858,6 +858,42 @@ void main() { expect(paragraph.text.style!.fontSize, 35.0); expect(paragraph.text.style!.fontStyle, FontStyle.italic); }); + + // This is a regression test for https://github.com/flutter/flutter/issues/153549. + testWidgets('Time picker hour minute does not resize on error', (WidgetTester tester) async { + final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true); + final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme); + await tester.pumpWidget(_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0)); + + // Enter invalid hour. + await tester.enterText(find.byType(TextField).first, 'AB'); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0)); + }); + + // This is a regression test for https://github.com/flutter/flutter/issues/153549. + testWidgets('Material2 - Time picker hour minute does not resize on error', (WidgetTester tester) async { + final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true); + final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme, useMaterial3: false); + await tester.pumpWidget(_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); + + // Enter invalid hour. + await tester.enterText(find.byType(TextField).first, 'AB'); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); + }); } final Color _selectedColor = Colors.green[100]!; @@ -970,3 +1006,10 @@ final Finder findDialPaint = find.descendant( ButtonStyle _actionButtonStyle(WidgetTester tester, String text) { return tester.widget(find.widgetWithText(TextButton, text)).style!; } + +Finder findBorderPainter() { + return find.descendant( + of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'), + matching: find.byWidgetPredicate((Widget w) => w is CustomPaint), + ); +}