[Time Picker] Double tapping hours/minutes will switch time picker to input mode (#67076)

This commit is contained in:
Rami 2020-10-02 13:53:36 -04:00 committed by GitHub
parent a2eef79fe4
commit 9e715205b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 133 additions and 0 deletions

View file

@ -82,6 +82,8 @@ class _TimePickerFragmentContext {
@required this.mode,
@required this.onTimeChange,
@required this.onModeChange,
@required this.onHourDoubleTapped,
@required this.onMinuteDoubleTapped,
@required this.use24HourDials,
}) : assert(selectedTime != null),
assert(mode != null),
@ -93,6 +95,8 @@ class _TimePickerFragmentContext {
final _TimePickerMode mode;
final ValueChanged<TimeOfDay> onTimeChange;
final ValueChanged<_TimePickerMode> onModeChange;
final GestureTapCallback onHourDoubleTapped;
final GestureTapCallback onMinuteDoubleTapped;
final bool use24HourDials;
}
@ -103,6 +107,8 @@ class _TimePickerHeader extends StatelessWidget {
@required this.orientation,
@required this.onModeChanged,
@required this.onChanged,
@required this.onHourDoubleTapped,
@required this.onMinuteDoubleTapped,
@required this.use24HourDials,
@required this.helpText,
}) : assert(selectedTime != null),
@ -115,6 +121,8 @@ class _TimePickerHeader extends StatelessWidget {
final Orientation orientation;
final ValueChanged<_TimePickerMode> onModeChanged;
final ValueChanged<TimeOfDay> onChanged;
final GestureTapCallback onHourDoubleTapped;
final GestureTapCallback onMinuteDoubleTapped;
final bool use24HourDials;
final String helpText;
@ -136,6 +144,8 @@ class _TimePickerHeader extends StatelessWidget {
mode: mode,
onTimeChange: onChanged,
onModeChange: _handleChangeMode,
onHourDoubleTapped: onHourDoubleTapped,
onMinuteDoubleTapped: onMinuteDoubleTapped,
use24HourDials: use24HourDials,
);
@ -246,6 +256,7 @@ class _HourMinuteControl extends StatelessWidget {
const _HourMinuteControl({
@required this.text,
@required this.onTap,
@required this.onDoubleTap,
@required this.isSelected,
}) : assert(text != null),
assert(onTap != null),
@ -253,6 +264,7 @@ class _HourMinuteControl extends StatelessWidget {
final String text;
final GestureTapCallback onTap;
final GestureTapCallback onDoubleTap;
final bool isSelected;
@override
@ -284,6 +296,7 @@ class _HourMinuteControl extends StatelessWidget {
shape: shape,
child: InkWell(
onTap: onTap,
onDoubleTap: isSelected ? onDoubleTap : null,
child: Center(
child: Text(
text,
@ -359,6 +372,7 @@ class _HourControl extends StatelessWidget {
isSelected: fragmentContext.mode == _TimePickerMode.hour,
text: formattedHour,
onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.hour), context),
onDoubleTap: fragmentContext.onHourDoubleTapped,
),
);
}
@ -448,6 +462,7 @@ class _MinuteControl extends StatelessWidget {
isSelected: fragmentContext.mode == _TimePickerMode.minute,
text: formattedMinute,
onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.minute), context),
onDoubleTap: fragmentContext.onMinuteDoubleTapped,
),
);
}
@ -1265,6 +1280,8 @@ class _TimePickerInput extends StatefulWidget {
Key key,
@required this.initialSelectedTime,
@required this.helpText,
@required this.autofocusHour,
@required this.autofocusMinute,
@required this.onChanged,
}) : assert(initialSelectedTime != null),
assert(onChanged != null),
@ -1276,6 +1293,10 @@ class _TimePickerInput extends StatefulWidget {
/// Optionally provide your own help text to the time picker.
final String helpText;
final bool autofocusHour;
final bool autofocusMinute;
final ValueChanged<TimeOfDay> onChanged;
@override
@ -1430,6 +1451,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
_HourTextField(
selectedTime: _selectedTime,
style: hourMinuteStyle,
autofocus: widget.autofocusHour,
validator: _validateHour,
onSavedSubmitted: _handleHourSavedSubmitted,
onChanged: _handleHourChanged,
@ -1460,6 +1482,7 @@ class _TimePickerInputState extends State<_TimePickerInput> {
_MinuteTextField(
selectedTime: _selectedTime,
style: hourMinuteStyle,
autofocus: widget.autofocusMinute,
validator: _validateMinute,
onSavedSubmitted: _handleMinuteSavedSubmitted,
),
@ -1507,6 +1530,7 @@ class _HourTextField extends StatelessWidget {
Key key,
@required this.selectedTime,
@required this.style,
@required this.autofocus,
@required this.validator,
@required this.onSavedSubmitted,
@required this.onChanged,
@ -1514,6 +1538,7 @@ class _HourTextField extends StatelessWidget {
final TimeOfDay selectedTime;
final TextStyle style;
final bool autofocus;
final FormFieldValidator<String> validator;
final ValueChanged<String> onSavedSubmitted;
final ValueChanged<String> onChanged;
@ -1523,6 +1548,7 @@ class _HourTextField extends StatelessWidget {
return _HourMinuteTextField(
selectedTime: selectedTime,
isHour: true,
autofocus: autofocus,
style: style,
semanticHintText: MaterialLocalizations.of(context).timePickerHourLabel,
validator: validator,
@ -1537,12 +1563,14 @@ class _MinuteTextField extends StatelessWidget {
Key key,
@required this.selectedTime,
@required this.style,
@required this.autofocus,
@required this.validator,
@required this.onSavedSubmitted,
}) : super(key: key);
final TimeOfDay selectedTime;
final TextStyle style;
final bool autofocus;
final FormFieldValidator<String> validator;
final ValueChanged<String> onSavedSubmitted;
@ -1551,6 +1579,7 @@ class _MinuteTextField extends StatelessWidget {
return _HourMinuteTextField(
selectedTime: selectedTime,
isHour: false,
autofocus: autofocus,
style: style,
semanticHintText: MaterialLocalizations.of(context).timePickerMinuteLabel,
validator: validator,
@ -1564,6 +1593,7 @@ class _HourMinuteTextField extends StatefulWidget {
Key key,
@required this.selectedTime,
@required this.isHour,
@required this.autofocus,
@required this.style,
@required this.semanticHintText,
@required this.validator,
@ -1573,6 +1603,7 @@ class _HourMinuteTextField extends StatefulWidget {
final TimeOfDay selectedTime;
final bool isHour;
final bool autofocus;
final TextStyle style;
final String semanticHintText;
final FormFieldValidator<String> validator;
@ -1658,6 +1689,7 @@ class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
child: MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: TextFormField(
autofocus: widget.autofocus ?? false,
expands: true,
maxLines: null,
inputFormatters: <TextInputFormatter>[
@ -1746,6 +1778,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_TimePickerMode _mode = _TimePickerMode.hour;
_TimePickerMode _lastModeAnnounced;
bool _autoValidate;
bool _autofocusHour;
bool _autofocusMinute;
TimeOfDay get selectedTime => _selectedTime;
TimeOfDay _selectedTime;
@ -1788,6 +1822,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
break;
case TimePickerEntryMode.input:
_formKey.currentState.save();
_autofocusHour = false;
_autofocusMinute = false;
_entryMode = TimePickerEntryMode.dial;
break;
}
@ -1833,6 +1869,16 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
});
}
void _handleHourDoubleTapped() {
_autofocusHour = true;
_handleEntryModeToggle();
}
void _handleMinuteDoubleTapped() {
_autofocusMinute = true;
_handleEntryModeToggle();
}
void _handleHourSelected() {
setState(() {
_mode = _TimePickerMode.minute;
@ -1962,6 +2008,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
orientation: orientation,
onModeChanged: _handleModeChanged,
onChanged: _handleTimeChanged,
onHourDoubleTapped: _handleHourDoubleTapped,
onMinuteDoubleTapped: _handleMinuteDoubleTapped,
use24HourDials: use24HourDials,
helpText: widget.helpText,
);
@ -2014,6 +2062,8 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
_TimePickerInput(
initialSelectedTime: _selectedTime,
helpText: widget.helpText,
autofocusHour: _autofocusHour,
autofocusMinute: _autofocusMinute,
onChanged: _handleTimeChanged,
),
actions,

View file

@ -803,6 +803,89 @@ void _testsInput() {
expect(find.byType(TextField), findsNothing);
});
testWidgets('Can double tap hours (when selected) to enter input mode', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, false, entryMode: TimePickerEntryMode.dial);
final Finder hourFinder = find.ancestor(
of: find.text('7'),
matching: find.byType(InkWell),
);
expect(find.byType(TextField), findsNothing);
// Double tap the hour.
await tester.tap(hourFinder);
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(hourFinder);
await tester.pumpAndSettle();
expect(find.byType(TextField), findsWidgets);
});
testWidgets('Can not double tap hours (when not selected) to enter input mode', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, false, entryMode: TimePickerEntryMode.dial);
final Finder hourFinder = find.ancestor(
of: find.text('7'),
matching: find.byType(InkWell),
);
final Finder minuteFinder = find.ancestor(
of: find.text('00'),
matching: find.byType(InkWell),
);
expect(find.byType(TextField), findsNothing);
// Switch to minutes mode.
await tester.tap(minuteFinder);
await tester.pumpAndSettle();
// Double tap the hour.
await tester.tap(hourFinder);
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(hourFinder);
await tester.pumpAndSettle();
expect(find.byType(TextField), findsNothing);
});
testWidgets('Can double tap minutes (when selected) to enter input mode', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, false, entryMode: TimePickerEntryMode.dial);
final Finder minuteFinder = find.ancestor(
of: find.text('00'),
matching: find.byType(InkWell),
);
expect(find.byType(TextField), findsNothing);
// Switch to minutes mode.
await tester.tap(minuteFinder);
await tester.pumpAndSettle();
// Double tap the minutes.
await tester.tap(minuteFinder);
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(minuteFinder);
await tester.pumpAndSettle();
expect(find.byType(TextField), findsWidgets);
});
testWidgets('Can not double tap minutes (when not selected) to enter input mode', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, false, entryMode: TimePickerEntryMode.dial);
final Finder minuteFinder = find.ancestor(
of: find.text('00'),
matching: find.byType(InkWell),
);
expect(find.byType(TextField), findsNothing);
// Double tap the minutes.
await tester.tap(minuteFinder);
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(minuteFinder);
await tester.pumpAndSettle();
expect(find.byType(TextField), findsNothing);
});
testWidgets('Entered text returns time', (WidgetTester tester) async {
TimeOfDay result;