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,
       ),
     ),
   );