Skip to content

Commit d7b092e

Browse files
authored
Fix TimePicker hour and minute inputs are resized on error (#154008)
Fixes [Defining inputDecorationTheme in TimePickerThemeData Causes Misalignment of Hour and Minute Input Boxes](flutter/flutter#153549) ### Code sample <details> <summary>expand to view the code sample</summary> ```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'), ); }), ), ), ); } } ``` </details> ### Before <img width="578" alt="Screenshot 2024-08-23 at 16 49 25" src="https://github.com/user-attachments/assets/f5da2495-551e-4110-85ea-120323cd38d2"> ### After <img width="578" alt="Screenshot 2024-08-23 at 16 51 03" src="https://github.com/user-attachments/assets/80224a10-e9d2-46d1-b2eb-f16358699744">
1 parent b2de4df commit d7b092e

4 files changed

Lines changed: 99 additions & 4 deletions

File tree

dev/tools/gen_defaults/lib/time_picker_template.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ class _${blockName}DefaultsM3 extends _TimePickerDefaults {
331331
// TODO(rami-a): Remove this workaround once
332332
// https://github.com/flutter/flutter/issues/54104
333333
// is fixed.
334-
errorStyle: const TextStyle(fontSize: 0, height: 0),
334+
errorStyle: const TextStyle(fontSize: 0),
335335
);
336336
}
337337

packages/flutter/lib/src/material/time_picker.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,7 +2047,14 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> with Restora
20472047
final bool alwaysUse24HourFormat = MediaQuery.alwaysUse24HourFormatOf(context);
20482048

20492049
final InputDecorationTheme inputDecorationTheme = timePickerTheme.inputDecorationTheme ?? defaultTheme.inputDecorationTheme;
2050-
InputDecoration inputDecoration = const InputDecoration().applyDefaults(inputDecorationTheme);
2050+
InputDecoration inputDecoration = InputDecoration(
2051+
// Prevent the error text from appearing when
2052+
// timePickerTheme.inputDecorationTheme is used.
2053+
// TODO(tahatesser): Remove this workaround once
2054+
// https://github.com/flutter/flutter/issues/54104
2055+
// is fixed.
2056+
errorStyle: defaultTheme.inputDecorationTheme.errorStyle,
2057+
).applyDefaults(inputDecorationTheme);
20512058
// Remove the hint text when focused because the centered cursor
20522059
// appears odd above the hint text.
20532060
final String? hintText = focusNode.hasFocus ? null : _formattedValue;
@@ -3341,7 +3348,7 @@ class _TimePickerDefaultsM2 extends _TimePickerDefaults {
33413348
// TODO(rami-a): Remove this workaround once
33423349
// https://github.com/flutter/flutter/issues/54104
33433350
// is fixed.
3344-
errorStyle: const TextStyle(fontSize: 0, height: 0),
3351+
errorStyle: const TextStyle(fontSize: 0, height: 1),
33453352
);
33463353
}
33473354

@@ -3676,7 +3683,7 @@ class _TimePickerDefaultsM3 extends _TimePickerDefaults {
36763683
// TODO(rami-a): Remove this workaround once
36773684
// https://github.com/flutter/flutter/issues/54104
36783685
// is fixed.
3679-
errorStyle: const TextStyle(fontSize: 0, height: 0),
3686+
errorStyle: const TextStyle(fontSize: 0),
36803687
);
36813688
}
36823689

packages/flutter/test/material/time_picker_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@ void main() {
1919
const String okString = 'OK';
2020
const String amString = 'AM';
2121
const String pmString = 'PM';
22+
2223
Material getMaterialFromDialog(WidgetTester tester) {
2324
return tester.widget<Material>(find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first);
2425
}
2526

27+
Finder findBorderPainter() {
28+
return find.descendant(
29+
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'),
30+
matching: find.byWidgetPredicate((Widget w) => w is CustomPaint),
31+
);
32+
}
33+
2634
testWidgets('Material2 - Dialog size - dial mode', (WidgetTester tester) async {
2735
addTearDown(tester.view.reset);
2836

@@ -2013,6 +2021,43 @@ void main() {
20132021
expect(paragraph.text.style!.color, theme.colorScheme.onSurface);
20142022
expect(paragraph.text.style!.fontSize, 56.0);
20152023
});
2024+
2025+
// This is a regression test for https://github.com/flutter/flutter/issues/153549.
2026+
testWidgets('Time picker hour minute does not resize on error', (WidgetTester tester) async {
2027+
await startPicker(
2028+
entryMode: TimePickerEntryMode.input,
2029+
tester,
2030+
(TimeOfDay? value) { },
2031+
);
2032+
2033+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
2034+
2035+
// Enter invalid hour.
2036+
await tester.enterText(find.byType(TextField).first, 'AB');
2037+
await tester.tap(find.text(okString));
2038+
await tester.pumpAndSettle();
2039+
2040+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
2041+
});
2042+
2043+
// This is a regression test for https://github.com/flutter/flutter/issues/153549.
2044+
testWidgets('Material2 - Time picker hour minute does not resize on error', (WidgetTester tester) async {
2045+
await startPicker(
2046+
entryMode: TimePickerEntryMode.input,
2047+
tester,
2048+
(TimeOfDay? value) { },
2049+
materialType: MaterialType.material2,
2050+
);
2051+
2052+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
2053+
2054+
// Enter invalid hour.
2055+
await tester.enterText(find.byType(TextField).first, 'AB');
2056+
await tester.tap(find.text(okString));
2057+
await tester.pumpAndSettle();
2058+
2059+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
2060+
});
20162061
}
20172062

20182063
final Finder findDialPaint = find.descendant(

packages/flutter/test/material/time_picker_theme_test.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,42 @@ void main() {
858858
expect(paragraph.text.style!.fontSize, 35.0);
859859
expect(paragraph.text.style!.fontStyle, FontStyle.italic);
860860
});
861+
862+
// This is a regression test for https://github.com/flutter/flutter/issues/153549.
863+
testWidgets('Time picker hour minute does not resize on error', (WidgetTester tester) async {
864+
final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true);
865+
final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme);
866+
await tester.pumpWidget(_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input));
867+
await tester.tap(find.text('X'));
868+
await tester.pumpAndSettle(const Duration(seconds: 1));
869+
870+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
871+
872+
// Enter invalid hour.
873+
await tester.enterText(find.byType(TextField).first, 'AB');
874+
await tester.tap(find.text('OK'));
875+
await tester.pumpAndSettle();
876+
877+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
878+
});
879+
880+
// This is a regression test for https://github.com/flutter/flutter/issues/153549.
881+
testWidgets('Material2 - Time picker hour minute does not resize on error', (WidgetTester tester) async {
882+
final TimePickerThemeData timePickerTheme = _timePickerTheme(includeInputDecoration: true);
883+
final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme, useMaterial3: false);
884+
await tester.pumpWidget(_TimePickerLauncher(themeData: theme, entryMode: TimePickerEntryMode.input));
885+
await tester.tap(find.text('X'));
886+
await tester.pumpAndSettle(const Duration(seconds: 1));
887+
888+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
889+
890+
// Enter invalid hour.
891+
await tester.enterText(find.byType(TextField).first, 'AB');
892+
await tester.tap(find.text('OK'));
893+
await tester.pumpAndSettle();
894+
895+
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
896+
});
861897
}
862898

863899
final Color _selectedColor = Colors.green[100]!;
@@ -970,3 +1006,10 @@ final Finder findDialPaint = find.descendant(
9701006
ButtonStyle _actionButtonStyle(WidgetTester tester, String text) {
9711007
return tester.widget<TextButton>(find.widgetWithText(TextButton, text)).style!;
9721008
}
1009+
1010+
Finder findBorderPainter() {
1011+
return find.descendant(
1012+
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'),
1013+
matching: find.byWidgetPredicate((Widget w) => w is CustomPaint),
1014+
);
1015+
}

0 commit comments

Comments
 (0)