diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 6948c0230f6..3ae85d8c171 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -238,13 +238,15 @@ class _TimePickerModel extends InheritedModel<_TimePickerAspect> { } } -class _TimePickerHeader extends StatelessWidget { - const _TimePickerHeader({required this.helpText}); +/// The header for the time picker in dial mode. +class _DialTimePickerHeader extends StatelessWidget { + const _DialTimePickerHeader({required this.helpText}); final String helpText; @override Widget build(BuildContext context) { + assert(_debugDialTimePickerEntryMode(context)); final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of( context, ).timeOfDayFormat(alwaysUse24HourFormat: _TimePickerModel.use24HourFormatOf(context)); @@ -285,9 +287,9 @@ class _TimePickerHeader extends StatelessWidget { // Hour/minutes should not change positions in RTL locales. textDirection: TextDirection.ltr, children: [ - const Expanded(child: _HourControl()), + const Expanded(child: _DialHourControl()), _TimeSelectorSeparator(timeOfDayFormat: timeOfDayFormat), - const Expanded(child: _MinuteControl()), + const Expanded(child: _DialMinuteControl()), ], ), ), @@ -316,9 +318,9 @@ class _TimePickerHeader extends StatelessWidget { // Hour/minutes should not change positions in RTL locales. textDirection: TextDirection.ltr, children: [ - const Expanded(child: _HourControl()), + const Expanded(child: _DialHourControl()), _TimeSelectorSeparator(timeOfDayFormat: timeOfDayFormat), - const Expanded(child: _MinuteControl()), + const Expanded(child: _DialMinuteControl()), ], ), if (hourDialType == _HourDialType.twelveHour) const _DayPeriodControl(), @@ -339,8 +341,9 @@ class _TimePickerHeader extends StatelessWidget { } } -class _HourMinuteControl extends StatelessWidget { - const _HourMinuteControl({ +/// The control label for the time selector in dial mode. +class _DialTimeSelectorControl extends StatelessWidget { + const _DialTimeSelectorControl({ required this.text, required this.onTap, required this.onDoubleTap, @@ -354,6 +357,7 @@ class _HourMinuteControl extends StatelessWidget { @override Widget build(BuildContext context) { + assert(_debugDialTimePickerEntryMode(context)); final TimePickerThemeData timePickerTheme = _TimePickerModel.themeOf(context); final _TimePickerDefaults defaultTheme = _TimePickerModel.defaultThemeOf(context); final Color backgroundColor = timePickerTheme.hourMinuteColor ?? defaultTheme.hourMinuteColor; @@ -370,18 +374,8 @@ class _HourMinuteControl extends StatelessWidget { states, ).copyWith(color: effectiveTextColor); - final double height; - switch (_TimePickerModel.entryModeOf(context)) { - case TimePickerEntryMode.dial: - case TimePickerEntryMode.dialOnly: - height = defaultTheme.hourMinuteSize.height; - case TimePickerEntryMode.input: - case TimePickerEntryMode.inputOnly: - height = defaultTheme.hourMinuteInputSize.height; - } - return SizedBox( - height: height, + height: defaultTheme.hourMinuteSize.height, child: Material( color: WidgetStateProperty.resolveAs(backgroundColor, states), clipBehavior: Clip.antiAlias, @@ -398,14 +392,15 @@ class _HourMinuteControl extends StatelessWidget { } } -/// Displays the hour fragment. +/// Displays the hour fragment in dial mode. /// /// When tapped changes time picker dial mode to [_HourMinuteMode.hour]. -class _HourControl extends StatelessWidget { - const _HourControl(); +class _DialHourControl extends StatelessWidget { + const _DialHourControl(); @override Widget build(BuildContext context) { + assert(_debugDialTimePickerEntryMode(context)); assert(debugCheckHasMediaQuery(context)); final bool alwaysUse24HourFormat = MediaQuery.alwaysUse24HourFormatOf(context); final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context); @@ -453,7 +448,7 @@ class _HourControl extends StatelessWidget { onDecrease: () { _TimePickerModel.setSelectedTime(context, previousHour); }, - child: _HourMinuteControl( + child: _DialTimeSelectorControl( isSelected: _TimePickerModel.hourMinuteModeOf(context) == _HourMinuteMode.hour, text: formattedHour, onTap: () => _TimePickerModel.setHourMinuteMode(context, _HourMinuteMode.hour), @@ -538,14 +533,15 @@ class _TimeSelectorSeparator extends StatelessWidget { } } -/// Displays the minute fragment. +/// Displays the minute fragment in dial mode. /// /// When tapped changes time picker dial mode to [_HourMinuteMode.minute]. -class _MinuteControl extends StatelessWidget { - const _MinuteControl(); +class _DialMinuteControl extends StatelessWidget { + const _DialMinuteControl(); @override Widget build(BuildContext context) { + assert(_debugDialTimePickerEntryMode(context)); final MaterialLocalizations localizations = MaterialLocalizations.of(context); final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context); final String formattedMinute = localizations.formatMinute(selectedTime); @@ -569,7 +565,7 @@ class _MinuteControl extends StatelessWidget { onDecrease: () { _TimePickerModel.setSelectedTime(context, previousMinute); }, - child: _HourMinuteControl( + child: _DialTimeSelectorControl( isSelected: _TimePickerModel.hourMinuteModeOf(context) == _HourMinuteMode.minute, text: formattedMinute, onTap: () => _TimePickerModel.setHourMinuteMode(context, _HourMinuteMode.minute), @@ -1612,6 +1608,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { + assert(_debugDialTimePickerEntryMode(context)); final ThemeData theme = Theme.of(context); final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); final _TimePickerDefaults defaultTheme = theme.useMaterial3 @@ -3064,7 +3061,7 @@ class _TimePickerState extends State<_TimePicker> with RestorationMixin { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: theme.useMaterial3 ? 0 : 16), - child: _TimePickerHeader(helpText: helpText), + child: _DialTimePickerHeader(helpText: helpText), ), Expanded( child: Column( @@ -3091,7 +3088,7 @@ class _TimePickerState extends State<_TimePicker> with RestorationMixin { child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _TimePickerHeader(helpText: helpText), + _DialTimePickerHeader(helpText: helpText), Expanded(child: dial), ], ), @@ -3621,6 +3618,12 @@ class _TimePickerDefaultsM2 extends _TimePickerDefaults { } } +/// Ensure the widget is called in [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly] mode. +bool _debugDialTimePickerEntryMode(BuildContext context) { + final TimePickerEntryMode entryMode = _TimePickerModel.entryModeOf(context); + return entryMode == TimePickerEntryMode.dial || entryMode == TimePickerEntryMode.dialOnly; +} + // BEGIN GENERATED TOKEN PROPERTIES - TimePicker // Do not edit by hand. The code between the "BEGIN GENERATED" and diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 2dcc556be8a..0cc2c9f1aeb 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -1589,13 +1589,13 @@ void main() { final SemanticsNode elevenHours = semantics .nodesWith( value: 'Select hours $initialValue', - ancestor: tester.renderObject(_hourControl).debugSemantics, + ancestor: tester.renderObject(_dialHourControl).debugSemantics, ) .single; tester.binding.pipelineOwner.semanticsOwner!.performAction(elevenHours.id, action); await tester.pumpAndSettle(); expect( - find.descendant(of: _hourControl, matching: find.text(finalValue)), + find.descendant(of: _dialHourControl, matching: find.text(finalValue)), findsOneWidget, ); } @@ -1643,13 +1643,13 @@ void main() { final SemanticsNode elevenHours = semantics .nodesWith( value: 'Select minutes $initialValue', - ancestor: tester.renderObject(_minuteControl).debugSemantics, + ancestor: tester.renderObject(_dialMinuteControl).debugSemantics, ) .single; tester.binding.pipelineOwner.semanticsOwner!.performAction(elevenHours.id, action); await tester.pumpAndSettle(); expect( - find.descendant(of: _minuteControl, matching: find.text(finalValue)), + find.descendant(of: _dialMinuteControl, matching: find.text(finalValue)), findsOneWidget, ); } @@ -2660,11 +2660,11 @@ Future mediaQueryBoilerplate( await tester.pumpAndSettle(); } -final Finder _hourControl = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_HourControl', +final Finder _dialHourControl = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialHourControl', ); -final Finder _minuteControl = find.byWidgetPredicate( - (Widget widget) => '${widget.runtimeType}' == '_MinuteControl', +final Finder _dialMinuteControl = find.byWidgetPredicate( + (Widget widget) => '${widget.runtimeType}' == '_DialMinuteControl', ); final Finder _timePicker = find.byWidgetPredicate( (Widget widget) => '${widget.runtimeType}' == '_TimePicker', diff --git a/packages/flutter_localizations/test/material/time_picker_test.dart b/packages/flutter_localizations/test/material/time_picker_test.dart index 1bc803095f7..141e5181728 100644 --- a/packages/flutter_localizations/test/material/time_picker_test.dart +++ b/packages/flutter_localizations/test/material/time_picker_test.dart @@ -26,11 +26,11 @@ void main() { matching: find.byType(Text), ) .first; - final Finder hourControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_HourControl', + final Finder dialHourControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialHourControl', ); - final Finder minuteControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_MinuteControl', + final Finder dialMinuteControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialMinuteControl', ); final Finder dayPeriodControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_DayPeriodControl', @@ -54,8 +54,8 @@ void main() { useMaterial3: false, ); final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder); - final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx; - final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx; + final double hourLeftOffset = tester.getTopLeft(dialHourControlFinder).dx; + final double minuteLeftOffset = tester.getTopLeft(dialMinuteControlFinder).dx; final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx; if (locale == const Locale('en', 'US')) { @@ -111,11 +111,11 @@ void main() { matching: find.byType(Text), ) .first; - final Finder hourControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_HourControl', + final Finder dialHourControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialHourControl', ); - final Finder minuteControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_MinuteControl', + final Finder dialMinuteControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialMinuteControl', ); final Finder dayPeriodControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_DayPeriodControl', @@ -139,8 +139,8 @@ void main() { useMaterial3: true, ); final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder); - final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx; - final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx; + final double hourLeftOffset = tester.getTopLeft(dialHourControlFinder).dx; + final double minuteLeftOffset = tester.getTopLeft(dialMinuteControlFinder).dx; final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx; if (locale == const Locale('en', 'US')) { @@ -196,11 +196,11 @@ void main() { matching: find.byType(Text), ) .first; - final Finder hourControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_HourControl', + final Finder dialHourControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialHourControl', ); - final Finder minuteControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_MinuteControl', + final Finder dialMinuteControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialMinuteControl', ); final Finder dayPeriodControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_DayPeriodControl', @@ -224,9 +224,9 @@ void main() { useMaterial3: false, ); final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder); - final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx; - final double hourTopOffset = tester.getTopLeft(hourControlFinder).dy; - final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx; + final double hourLeftOffset = tester.getTopLeft(dialHourControlFinder).dx; + final double hourTopOffset = tester.getTopLeft(dialHourControlFinder).dy; + final double minuteLeftOffset = tester.getTopLeft(dialMinuteControlFinder).dx; final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx; if (locale == const Locale('en', 'US')) { @@ -286,11 +286,11 @@ void main() { matching: find.byType(Text), ) .first; - final Finder hourControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_HourControl', + final Finder dialHourControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialHourControl', ); - final Finder minuteControlFinder = find.byWidgetPredicate( - (Widget w) => '${w.runtimeType}' == '_MinuteControl', + final Finder dialMinuteControlFinder = find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_DialMinuteControl', ); final Finder dayPeriodControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_DayPeriodControl', @@ -314,9 +314,9 @@ void main() { useMaterial3: true, ); final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder); - final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx; - final double hourTopOffset = tester.getTopLeft(hourControlFinder).dy; - final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx; + final double hourLeftOffset = tester.getTopLeft(dialHourControlFinder).dx; + final double hourTopOffset = tester.getTopLeft(dialHourControlFinder).dy; + final double minuteLeftOffset = tester.getTopLeft(dialMinuteControlFinder).dx; final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx; if (locale == const Locale('en', 'US')) { @@ -365,10 +365,10 @@ void main() { testWidgets('Material2 - can localize input mode in all known formats', ( WidgetTester tester, ) async { - final Finder hourControlFinder = find.byWidgetPredicate( + final Finder inputHourControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_HourTextField', ); - final Finder minuteControlFinder = find.byWidgetPredicate( + final Finder inputMinuteControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_MinuteTextField', ); final Finder dayPeriodControlFinder = find.byWidgetPredicate( @@ -404,8 +404,8 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder); - final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx; - final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx; + final double hourLeftOffset = tester.getTopLeft(inputHourControlFinder).dx; + final double minuteLeftOffset = tester.getTopLeft(inputMinuteControlFinder).dx; final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx; if (locale == const Locale('en', 'US')) { @@ -450,10 +450,10 @@ void main() { testWidgets('Material3 - can localize input mode in all known formats', ( WidgetTester tester, ) async { - final Finder hourControlFinder = find.byWidgetPredicate( + final Finder inputHourControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_HourTextField', ); - final Finder minuteControlFinder = find.byWidgetPredicate( + final Finder inputMinuteControlFinder = find.byWidgetPredicate( (Widget w) => '${w.runtimeType}' == '_MinuteTextField', ); final Finder dayPeriodControlFinder = find.byWidgetPredicate( @@ -489,8 +489,8 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder); - final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx; - final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx; + final double hourLeftOffset = tester.getTopLeft(inputHourControlFinder).dx; + final double minuteLeftOffset = tester.getTopLeft(inputMinuteControlFinder).dx; final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx; if (locale == const Locale('en', 'US')) {