Skip to content

Commit c150c5a

Browse files
TimePicker : Ability to define dialOnly / inputOnly modes (#104491)
1 parent e6d31b9 commit c150c5a

File tree

2 files changed

+56
-14
lines changed

2 files changed

+56
-14
lines changed

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

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,25 @@ const ShapeBorder _kDefaultShape = RoundedRectangleBorder(borderRadius: _kDefaul
6565
/// TimePickerEntryMode.input] mode, [TextField]s are displayed and the user
6666
/// types in the time they wish to select.
6767
enum TimePickerEntryMode {
68-
/// Tapping/dragging on a clock dial.
68+
/// User picks time from a clock dial.
69+
///
70+
/// Can switch to [input] by activating a mode button in the dialog.
6971
dial,
7072

71-
/// Text input.
73+
/// User can input the time by typing it into text fields.
74+
///
75+
/// Can switch to [dial] by activating a mode button in the dialog.
7276
input,
77+
78+
/// User can only pick time from a clock dial.
79+
///
80+
/// There is no user interface to switch to another mode.
81+
dialOnly,
82+
83+
/// User can only input the time by typing it into text fields.
84+
///
85+
/// There is no user interface to switch to another mode.
86+
inputOnly
7387
}
7488

7589
/// Provides properties for rendering time picker header fragments.
@@ -2055,6 +2069,10 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
20552069
_autofocusMinute.value = false;
20562070
_entryMode.value = TimePickerEntryMode.dial;
20572071
break;
2072+
case TimePickerEntryMode.dialOnly:
2073+
case TimePickerEntryMode.inputOnly:
2074+
FlutterError('Can not change entry mode from $_entryMode');
2075+
break;
20582076
}
20592077
});
20602078
}
@@ -2118,7 +2136,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
21182136
}
21192137

21202138
void _handleOk() {
2121-
if (_entryMode.value == TimePickerEntryMode.input) {
2139+
if (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) {
21222140
final FormState form = _formKey.currentState!;
21232141
if (!form.validate()) {
21242142
setState(() { _autovalidateMode.value = AutovalidateMode.always; });
@@ -2141,6 +2159,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
21412159
final double timePickerHeight;
21422160
switch (_entryMode.value) {
21432161
case TimePickerEntryMode.dial:
2162+
case TimePickerEntryMode.dialOnly:
21442163
switch (orientation) {
21452164
case Orientation.portrait:
21462165
timePickerWidth = _kTimePickerWidthPortrait;
@@ -2157,6 +2176,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
21572176
}
21582177
break;
21592178
case TimePickerEntryMode.input:
2179+
case TimePickerEntryMode.inputOnly:
21602180
timePickerWidth = _kTimePickerWidthPortrait;
21612181
timePickerHeight = _kTimePickerHeightInput;
21622182
break;
@@ -2177,16 +2197,17 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
21772197
final Widget actions = Row(
21782198
children: <Widget>[
21792199
const SizedBox(width: 10.0),
2180-
IconButton(
2181-
color: TimePickerTheme.of(context).entryModeIconColor ?? theme.colorScheme.onSurface.withOpacity(
2182-
theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
2200+
if (_entryMode.value == TimePickerEntryMode.dial || _entryMode.value == TimePickerEntryMode.input)
2201+
IconButton(
2202+
color: TimePickerTheme.of(context).entryModeIconColor ?? theme.colorScheme.onSurface.withOpacity(
2203+
theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
2204+
),
2205+
onPressed: _handleEntryModeToggle,
2206+
icon: Icon(_entryMode.value == TimePickerEntryMode.dial ? Icons.keyboard : Icons.access_time),
2207+
tooltip: _entryMode.value == TimePickerEntryMode.dial
2208+
? MaterialLocalizations.of(context).inputTimeModeButtonLabel
2209+
: MaterialLocalizations.of(context).dialModeButtonLabel,
21832210
),
2184-
onPressed: _handleEntryModeToggle,
2185-
icon: Icon(_entryMode.value == TimePickerEntryMode.dial ? Icons.keyboard : Icons.access_time),
2186-
tooltip: _entryMode.value == TimePickerEntryMode.dial
2187-
? MaterialLocalizations.of(context).inputTimeModeButtonLabel
2188-
: MaterialLocalizations.of(context).dialModeButtonLabel,
2189-
),
21902211
Expanded(
21912212
child: Container(
21922213
alignment: AlignmentDirectional.centerEnd,
@@ -2214,6 +2235,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
22142235
final Widget picker;
22152236
switch (_entryMode.value) {
22162237
case TimePickerEntryMode.dial:
2238+
case TimePickerEntryMode.dialOnly:
22172239
final Widget dial = Padding(
22182240
padding: orientation == Orientation.portrait ? const EdgeInsets.symmetric(horizontal: 36, vertical: 24) : const EdgeInsets.all(24),
22192241
child: ExcludeSemantics(
@@ -2280,6 +2302,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
22802302
}
22812303
break;
22822304
case TimePickerEntryMode.input:
2305+
case TimePickerEntryMode.inputOnly:
22832306
picker = Form(
22842307
key: _formKey,
22852308
autovalidateMode: _autovalidateMode.value,
@@ -2313,7 +2336,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
23132336
backgroundColor: TimePickerTheme.of(context).backgroundColor ?? theme.colorScheme.surface,
23142337
insetPadding: EdgeInsets.symmetric(
23152338
horizontal: 16.0,
2316-
vertical: _entryMode.value == TimePickerEntryMode.input ? 0.0 : 24.0,
2339+
vertical: (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) ? 0.0 : 24.0,
23172340
),
23182341
child: AnimatedContainer(
23192342
width: dialogSize.width,

packages/flutter/test/material/time_picker_test.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,13 +1007,32 @@ void _testsInput() {
10071007
expect(find.text(errorInvalidText), findsOneWidget);
10081008
});
10091009

1010-
testWidgets('Can toggle to dial entry mode', (WidgetTester tester) async {
1010+
testWidgets('Can switch from input to dial entry mode', (WidgetTester tester) async {
10111011
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input);
10121012
await tester.tap(find.byIcon(Icons.access_time));
10131013
await tester.pumpAndSettle();
10141014
expect(find.byType(TextField), findsNothing);
10151015
});
10161016

1017+
testWidgets('Can switch from dial to input entry mode', (WidgetTester tester) async {
1018+
await mediaQueryBoilerplate(tester, true);
1019+
await tester.tap(find.byIcon(Icons.keyboard));
1020+
await tester.pumpAndSettle();
1021+
expect(find.byType(TextField), findsWidgets);
1022+
});
1023+
1024+
testWidgets('Can not switch out of inputOnly mode', (WidgetTester tester) async {
1025+
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.inputOnly);
1026+
expect(find.byType(TextField), findsWidgets);
1027+
expect(find.byIcon(Icons.access_time), findsNothing);
1028+
});
1029+
1030+
testWidgets('Can not switch out of dialOnly mode', (WidgetTester tester) async {
1031+
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.dialOnly);
1032+
expect(find.byType(TextField), findsNothing);
1033+
expect(find.byIcon(Icons.keyboard), findsNothing);
1034+
});
1035+
10171036
testWidgets('Switching to dial entry mode triggers entry callback', (WidgetTester tester) async {
10181037
bool triggeredCallback = false;
10191038

0 commit comments

Comments
 (0)