fix: Update time picker dialog input size (#163184)
fix: Update time picker dialog input size
Fixes: #162796
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index e210e3d..5d1ed4d 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -2324,7 +2324,8 @@
static const Size _kTimePickerPortraitSize = Size(310, 468);
static const Size _kTimePickerLandscapeSize = Size(524, 342);
static const Size _kTimePickerLandscapeSizeM2 = Size(508, 300);
- static const Size _kTimePickerInputSize = Size(312, 216);
+ static const Size _kTimePickerInputSize = Size(312, 252);
+ static const double _kTimePickerInputMinimumHeight = 216;
// Absolute minimum dialog sizes, which is the point at which it begins
// scrolling to fit everything in.
@@ -2606,33 +2607,45 @@
restorationId: 'time_picker_scroll_view_vertical',
child: AnimatedContainer(
width: allowedSize.width,
- height: allowedSize.height,
duration: _kDialogSizeAnimationDuration,
curve: Curves.easeIn,
+ constraints: BoxConstraints(
+ minHeight: _kTimePickerInputMinimumHeight,
+ maxHeight: allowedSize.height,
+ ),
child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
- Expanded(
- child: Form(
- key: _formKey,
- autovalidateMode: _autovalidateMode.value,
- child: _TimePicker(
- time: widget.initialTime,
- onTimeChanged: _handleTimeChanged,
- helpText: widget.helpText,
- cancelText: widget.cancelText,
- confirmText: widget.confirmText,
- errorInvalidText: widget.errorInvalidText,
- hourLabelText: widget.hourLabelText,
- minuteLabelText: widget.minuteLabelText,
- restorationId: 'time_picker',
- entryMode: _entryMode.value,
- orientation: widget.orientation,
- onEntryModeChanged: _handleEntryModeChanged,
- switchToInputEntryModeIcon: widget.switchToInputEntryModeIcon,
- switchToTimerEntryModeIcon: widget.switchToTimerEntryModeIcon,
- ),
- ),
+ Builder(
+ builder: (BuildContext context) {
+ final Widget child = Form(
+ key: _formKey,
+ autovalidateMode: _autovalidateMode.value,
+ child: _TimePicker(
+ time: widget.initialTime,
+ onTimeChanged: _handleTimeChanged,
+ helpText: widget.helpText,
+ cancelText: widget.cancelText,
+ confirmText: widget.confirmText,
+ errorInvalidText: widget.errorInvalidText,
+ hourLabelText: widget.hourLabelText,
+ minuteLabelText: widget.minuteLabelText,
+ restorationId: 'time_picker',
+ entryMode: _entryMode.value,
+ orientation: widget.orientation,
+ onEntryModeChanged: _handleEntryModeChanged,
+ switchToInputEntryModeIcon: widget.switchToInputEntryModeIcon,
+ switchToTimerEntryModeIcon: widget.switchToTimerEntryModeIcon,
+ ),
+ );
+ if (_entryMode.value != TimePickerEntryMode.input &&
+ _entryMode.value != TimePickerEntryMode.inputOnly) {
+ return Flexible(child: child);
+ }
+ return child;
+ },
),
actions,
],
diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart
index 3091816..a35f663 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -72,7 +72,7 @@
testWidgets('Material2 - Dialog size - input mode', (WidgetTester tester) async {
const TimePickerEntryMode entryMode = TimePickerEntryMode.input;
- const Size timePickerInputSize = Size(312, 216);
+ const Size timePickerInputSize = Size(312, 252);
const Size dayPeriodPortraitSize = Size(52, 80);
const EdgeInsets padding = EdgeInsets.fromLTRB(8, 18, 8, 8);
final double height = timePickerInputSize.height + padding.vertical;
@@ -81,7 +81,9 @@
await mediaQueryBoilerplate(tester, entryMode: entryMode, materialType: MaterialType.material2);
width = timePickerInputSize.width + padding.horizontal;
- expect(tester.getSize(find.byWidget(getMaterialFromDialog(tester))), Size(width, height));
+ Size size = tester.getSize(find.byWidget(getMaterialFromDialog(tester)));
+ expect(size.width, width);
+ expect(size.height, lessThan(height));
await tester.tap(find.text(okString)); // dismiss the dialog
await tester.pumpAndSettle();
@@ -93,7 +95,9 @@
materialType: MaterialType.material2,
);
width = timePickerInputSize.width - dayPeriodPortraitSize.width - 12 + padding.horizontal + 16;
- expect(tester.getSize(find.byWidget(getMaterialFromDialog(tester))), Size(width, height));
+ size = tester.getSize(find.byWidget(getMaterialFromDialog(tester)));
+ expect(size.width, width);
+ expect(size.height, lessThan(height));
});
testWidgets('Material2 - respects MediaQueryData.alwaysUse24HourFormat == true', (
@@ -163,7 +167,7 @@
final ThemeData theme = ThemeData();
const TimePickerEntryMode entryMode = TimePickerEntryMode.input;
const double textScaleFactor = 1.0;
- const Size timePickerMinInputSize = Size(312, 216);
+ const Size timePickerMinInputSize = Size(312, 252);
const Size dayPeriodPortraitSize = Size(52, 80);
const EdgeInsets padding = EdgeInsets.all(24.0);
final double height = timePickerMinInputSize.height * textScaleFactor + padding.vertical;
@@ -172,7 +176,9 @@
await mediaQueryBoilerplate(tester, entryMode: entryMode, materialType: MaterialType.material3);
width = timePickerMinInputSize.width - (theme.useMaterial3 ? 32 : 0) + padding.horizontal;
- expect(tester.getSize(find.byWidget(getMaterialFromDialog(tester))), Size(width, height));
+ Size size = tester.getSize(find.byWidget(getMaterialFromDialog(tester)));
+ expect(size.width, width);
+ expect(size.height, lessThan(height));
await tester.tap(find.text(okString)); // dismiss the dialog
await tester.pumpAndSettle();
@@ -185,7 +191,9 @@
);
width = timePickerMinInputSize.width - dayPeriodPortraitSize.width - 12 + padding.horizontal;
- expect(tester.getSize(find.byWidget(getMaterialFromDialog(tester))), Size(width, height));
+ size = tester.getSize(find.byWidget(getMaterialFromDialog(tester)));
+ expect(size.width, width);
+ expect(size.height, lessThan(height));
});
testWidgets('Material3 - respects MediaQueryData.alwaysUse24HourFormat == true', (
@@ -594,6 +602,24 @@
expect(find.text('Cancel'), findsOneWidget);
});
+ testWidgets(
+ 'Material3 - large actions label should not overflow in input mode',
+ (WidgetTester tester) async {
+ await startPicker(
+ tester,
+ (TimeOfDay? time) {},
+ entryMode: TimePickerEntryMode.input,
+ materialType: MaterialType.material3,
+ cancelText: 'Very very very long cancel text',
+ confirmText: 'Very very very long confirm text',
+ );
+
+ // Verify that no overflow errors occur.
+ expect(tester.takeException(), isNull);
+ },
+ variant: TargetPlatformVariant.mobile(),
+ );
+
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (
WidgetTester tester,
) async {
@@ -2325,11 +2351,15 @@
required this.onChanged,
this.entryMode = TimePickerEntryMode.dial,
this.restorationId,
+ this.cancelText,
+ this.confirmText,
});
final ValueChanged<TimeOfDay?> onChanged;
final TimePickerEntryMode entryMode;
final String? restorationId;
+ final String? cancelText;
+ final String? confirmText;
@override
_TimePickerLauncherState createState() => _TimePickerLauncherState();
@@ -2346,7 +2376,11 @@
onPresent: (NavigatorState navigator, Object? arguments) {
return navigator.restorablePush(
_timePickerRoute,
- arguments: <String, String>{'entry_mode': widget.entryMode.name},
+ arguments: <String, String>{
+ 'entry_mode': widget.entryMode.name,
+ if (widget.cancelText != null) 'cancel_text': widget.cancelText!,
+ if (widget.confirmText != null) 'confirm_text': widget.confirmText!,
+ },
);
},
);
@@ -2363,6 +2397,8 @@
final TimePickerEntryMode entryMode = TimePickerEntryMode.values.firstWhere(
(TimePickerEntryMode element) => element.name == args['entry_mode'],
);
+ final String? cancelText = args['cancel_text'] as String?;
+ final String? confirmText = args['confirm_text'] as String?;
return DialogRoute<TimeOfDay>(
context: context,
builder: (BuildContext context) {
@@ -2370,6 +2406,8 @@
restorationId: 'time_picker_dialog',
initialTime: const TimeOfDay(hour: 7, minute: 0),
initialEntryMode: entryMode,
+ cancelText: cancelText,
+ confirmText: confirmText,
);
},
);
@@ -2426,6 +2464,8 @@
String? restorationId,
ThemeData? theme,
MaterialType? materialType,
+ String? cancelText,
+ String? confirmText,
}) async {
await tester.pumpWidget(
MaterialApp(
@@ -2436,6 +2476,8 @@
onChanged: onChanged,
entryMode: entryMode,
restorationId: restorationId,
+ cancelText: cancelText,
+ confirmText: confirmText,
),
),
);