mirror of
https://github.com/flutter/flutter
synced 2024-08-27 12:00:58 +00:00
TimePicker : Ability to define dialOnly / inputOnly modes (#104491)
This commit is contained in:
parent
e6d31b9d56
commit
c150c5a03d
|
@ -65,11 +65,25 @@ const ShapeBorder _kDefaultShape = RoundedRectangleBorder(borderRadius: _kDefaul
|
||||||
/// TimePickerEntryMode.input] mode, [TextField]s are displayed and the user
|
/// TimePickerEntryMode.input] mode, [TextField]s are displayed and the user
|
||||||
/// types in the time they wish to select.
|
/// types in the time they wish to select.
|
||||||
enum TimePickerEntryMode {
|
enum TimePickerEntryMode {
|
||||||
/// Tapping/dragging on a clock dial.
|
/// User picks time from a clock dial.
|
||||||
|
///
|
||||||
|
/// Can switch to [input] by activating a mode button in the dialog.
|
||||||
dial,
|
dial,
|
||||||
|
|
||||||
/// Text input.
|
/// User can input the time by typing it into text fields.
|
||||||
|
///
|
||||||
|
/// Can switch to [dial] by activating a mode button in the dialog.
|
||||||
input,
|
input,
|
||||||
|
|
||||||
|
/// User can only pick time from a clock dial.
|
||||||
|
///
|
||||||
|
/// There is no user interface to switch to another mode.
|
||||||
|
dialOnly,
|
||||||
|
|
||||||
|
/// User can only input the time by typing it into text fields.
|
||||||
|
///
|
||||||
|
/// There is no user interface to switch to another mode.
|
||||||
|
inputOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides properties for rendering time picker header fragments.
|
/// Provides properties for rendering time picker header fragments.
|
||||||
|
@ -2055,6 +2069,10 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
_autofocusMinute.value = false;
|
_autofocusMinute.value = false;
|
||||||
_entryMode.value = TimePickerEntryMode.dial;
|
_entryMode.value = TimePickerEntryMode.dial;
|
||||||
break;
|
break;
|
||||||
|
case TimePickerEntryMode.dialOnly:
|
||||||
|
case TimePickerEntryMode.inputOnly:
|
||||||
|
FlutterError('Can not change entry mode from $_entryMode');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2118,7 +2136,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOk() {
|
void _handleOk() {
|
||||||
if (_entryMode.value == TimePickerEntryMode.input) {
|
if (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) {
|
||||||
final FormState form = _formKey.currentState!;
|
final FormState form = _formKey.currentState!;
|
||||||
if (!form.validate()) {
|
if (!form.validate()) {
|
||||||
setState(() { _autovalidateMode.value = AutovalidateMode.always; });
|
setState(() { _autovalidateMode.value = AutovalidateMode.always; });
|
||||||
|
@ -2141,6 +2159,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
final double timePickerHeight;
|
final double timePickerHeight;
|
||||||
switch (_entryMode.value) {
|
switch (_entryMode.value) {
|
||||||
case TimePickerEntryMode.dial:
|
case TimePickerEntryMode.dial:
|
||||||
|
case TimePickerEntryMode.dialOnly:
|
||||||
switch (orientation) {
|
switch (orientation) {
|
||||||
case Orientation.portrait:
|
case Orientation.portrait:
|
||||||
timePickerWidth = _kTimePickerWidthPortrait;
|
timePickerWidth = _kTimePickerWidthPortrait;
|
||||||
|
@ -2157,6 +2176,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TimePickerEntryMode.input:
|
case TimePickerEntryMode.input:
|
||||||
|
case TimePickerEntryMode.inputOnly:
|
||||||
timePickerWidth = _kTimePickerWidthPortrait;
|
timePickerWidth = _kTimePickerWidthPortrait;
|
||||||
timePickerHeight = _kTimePickerHeightInput;
|
timePickerHeight = _kTimePickerHeightInput;
|
||||||
break;
|
break;
|
||||||
|
@ -2177,16 +2197,17 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
final Widget actions = Row(
|
final Widget actions = Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const SizedBox(width: 10.0),
|
const SizedBox(width: 10.0),
|
||||||
IconButton(
|
if (_entryMode.value == TimePickerEntryMode.dial || _entryMode.value == TimePickerEntryMode.input)
|
||||||
color: TimePickerTheme.of(context).entryModeIconColor ?? theme.colorScheme.onSurface.withOpacity(
|
IconButton(
|
||||||
theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
|
color: TimePickerTheme.of(context).entryModeIconColor ?? theme.colorScheme.onSurface.withOpacity(
|
||||||
|
theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
|
||||||
|
),
|
||||||
|
onPressed: _handleEntryModeToggle,
|
||||||
|
icon: Icon(_entryMode.value == TimePickerEntryMode.dial ? Icons.keyboard : Icons.access_time),
|
||||||
|
tooltip: _entryMode.value == TimePickerEntryMode.dial
|
||||||
|
? MaterialLocalizations.of(context).inputTimeModeButtonLabel
|
||||||
|
: MaterialLocalizations.of(context).dialModeButtonLabel,
|
||||||
),
|
),
|
||||||
onPressed: _handleEntryModeToggle,
|
|
||||||
icon: Icon(_entryMode.value == TimePickerEntryMode.dial ? Icons.keyboard : Icons.access_time),
|
|
||||||
tooltip: _entryMode.value == TimePickerEntryMode.dial
|
|
||||||
? MaterialLocalizations.of(context).inputTimeModeButtonLabel
|
|
||||||
: MaterialLocalizations.of(context).dialModeButtonLabel,
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
@ -2214,6 +2235,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
final Widget picker;
|
final Widget picker;
|
||||||
switch (_entryMode.value) {
|
switch (_entryMode.value) {
|
||||||
case TimePickerEntryMode.dial:
|
case TimePickerEntryMode.dial:
|
||||||
|
case TimePickerEntryMode.dialOnly:
|
||||||
final Widget dial = Padding(
|
final Widget dial = Padding(
|
||||||
padding: orientation == Orientation.portrait ? const EdgeInsets.symmetric(horizontal: 36, vertical: 24) : const EdgeInsets.all(24),
|
padding: orientation == Orientation.portrait ? const EdgeInsets.symmetric(horizontal: 36, vertical: 24) : const EdgeInsets.all(24),
|
||||||
child: ExcludeSemantics(
|
child: ExcludeSemantics(
|
||||||
|
@ -2280,6 +2302,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TimePickerEntryMode.input:
|
case TimePickerEntryMode.input:
|
||||||
|
case TimePickerEntryMode.inputOnly:
|
||||||
picker = Form(
|
picker = Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
autovalidateMode: _autovalidateMode.value,
|
autovalidateMode: _autovalidateMode.value,
|
||||||
|
@ -2313,7 +2336,7 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
|
||||||
backgroundColor: TimePickerTheme.of(context).backgroundColor ?? theme.colorScheme.surface,
|
backgroundColor: TimePickerTheme.of(context).backgroundColor ?? theme.colorScheme.surface,
|
||||||
insetPadding: EdgeInsets.symmetric(
|
insetPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
vertical: _entryMode.value == TimePickerEntryMode.input ? 0.0 : 24.0,
|
vertical: (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) ? 0.0 : 24.0,
|
||||||
),
|
),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
width: dialogSize.width,
|
width: dialogSize.width,
|
||||||
|
|
|
@ -1007,13 +1007,32 @@ void _testsInput() {
|
||||||
expect(find.text(errorInvalidText), findsOneWidget);
|
expect(find.text(errorInvalidText), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Can toggle to dial entry mode', (WidgetTester tester) async {
|
testWidgets('Can switch from input to dial entry mode', (WidgetTester tester) async {
|
||||||
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input);
|
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input);
|
||||||
await tester.tap(find.byIcon(Icons.access_time));
|
await tester.tap(find.byIcon(Icons.access_time));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byType(TextField), findsNothing);
|
expect(find.byType(TextField), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Can switch from dial to input entry mode', (WidgetTester tester) async {
|
||||||
|
await mediaQueryBoilerplate(tester, true);
|
||||||
|
await tester.tap(find.byIcon(Icons.keyboard));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(TextField), findsWidgets);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Can not switch out of inputOnly mode', (WidgetTester tester) async {
|
||||||
|
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.inputOnly);
|
||||||
|
expect(find.byType(TextField), findsWidgets);
|
||||||
|
expect(find.byIcon(Icons.access_time), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Can not switch out of dialOnly mode', (WidgetTester tester) async {
|
||||||
|
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.dialOnly);
|
||||||
|
expect(find.byType(TextField), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.keyboard), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Switching to dial entry mode triggers entry callback', (WidgetTester tester) async {
|
testWidgets('Switching to dial entry mode triggers entry callback', (WidgetTester tester) async {
|
||||||
bool triggeredCallback = false;
|
bool triggeredCallback = false;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue