Unverified Commit eee9f100 authored by Todor Ginchev's avatar Todor Ginchev Committed by GitHub

Add optional custom localization for TimePicker, the same way DatePicker has it (#84566)

parent b63bcaa3
...@@ -1290,6 +1290,9 @@ class _TimePickerInput extends StatefulWidget { ...@@ -1290,6 +1290,9 @@ class _TimePickerInput extends StatefulWidget {
Key? key, Key? key,
required this.initialSelectedTime, required this.initialSelectedTime,
required this.helpText, required this.helpText,
required this.errorInvalidText,
required this.hourLabelText,
required this.minuteLabelText,
required this.autofocusHour, required this.autofocusHour,
required this.autofocusMinute, required this.autofocusMinute,
required this.onChanged, required this.onChanged,
...@@ -1304,6 +1307,15 @@ class _TimePickerInput extends StatefulWidget { ...@@ -1304,6 +1307,15 @@ class _TimePickerInput extends StatefulWidget {
/// Optionally provide your own help text to the time picker. /// Optionally provide your own help text to the time picker.
final String? helpText; final String? helpText;
/// Optionally provide your own validation error text.
final String? errorInvalidText;
/// Optionally provide your own hour label text.
final String? hourLabelText;
/// Optionally provide your own minute label text.
final String? minuteLabelText;
final bool? autofocusHour; final bool? autofocusHour;
final bool? autofocusMinute; final bool? autofocusMinute;
...@@ -1480,12 +1492,13 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi ...@@ -1480,12 +1492,13 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
validator: _validateHour, validator: _validateHour,
onSavedSubmitted: _handleHourSavedSubmitted, onSavedSubmitted: _handleHourSavedSubmitted,
onChanged: _handleHourChanged, onChanged: _handleHourChanged,
hourLabelText: widget.hourLabelText,
), ),
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
if (!hourHasError.value && !minuteHasError.value) if (!hourHasError.value && !minuteHasError.value)
ExcludeSemantics( ExcludeSemantics(
child: Text( child: Text(
MaterialLocalizations.of(context).timePickerHourLabel, widget.hourLabelText ?? MaterialLocalizations.of(context).timePickerHourLabel,
style: theme.textTheme.caption, style: theme.textTheme.caption,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
...@@ -1511,12 +1524,13 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi ...@@ -1511,12 +1524,13 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
autofocus: widget.autofocusMinute, autofocus: widget.autofocusMinute,
validator: _validateMinute, validator: _validateMinute,
onSavedSubmitted: _handleMinuteSavedSubmitted, onSavedSubmitted: _handleMinuteSavedSubmitted,
minuteLabelText: widget.minuteLabelText,
), ),
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
if (!hourHasError.value && !minuteHasError.value) if (!hourHasError.value && !minuteHasError.value)
ExcludeSemantics( ExcludeSemantics(
child: Text( child: Text(
MaterialLocalizations.of(context).timePickerMinuteLabel, widget.minuteLabelText ?? MaterialLocalizations.of(context).timePickerMinuteLabel,
style: theme.textTheme.caption, style: theme.textTheme.caption,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
...@@ -1540,7 +1554,7 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi ...@@ -1540,7 +1554,7 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
), ),
if (hourHasError.value || minuteHasError.value) if (hourHasError.value || minuteHasError.value)
Text( Text(
MaterialLocalizations.of(context).invalidTimeLabel, widget.errorInvalidText ?? MaterialLocalizations.of(context).invalidTimeLabel,
style: theme.textTheme.bodyText2!.copyWith(color: theme.colorScheme.error), style: theme.textTheme.bodyText2!.copyWith(color: theme.colorScheme.error),
) )
else else
...@@ -1560,6 +1574,7 @@ class _HourTextField extends StatelessWidget { ...@@ -1560,6 +1574,7 @@ class _HourTextField extends StatelessWidget {
required this.validator, required this.validator,
required this.onSavedSubmitted, required this.onSavedSubmitted,
required this.onChanged, required this.onChanged,
required this.hourLabelText,
this.restorationId, this.restorationId,
}) : super(key: key); }) : super(key: key);
...@@ -1569,6 +1584,7 @@ class _HourTextField extends StatelessWidget { ...@@ -1569,6 +1584,7 @@ class _HourTextField extends StatelessWidget {
final FormFieldValidator<String> validator; final FormFieldValidator<String> validator;
final ValueChanged<String?> onSavedSubmitted; final ValueChanged<String?> onSavedSubmitted;
final ValueChanged<String> onChanged; final ValueChanged<String> onChanged;
final String? hourLabelText;
final String? restorationId; final String? restorationId;
@override @override
...@@ -1579,7 +1595,7 @@ class _HourTextField extends StatelessWidget { ...@@ -1579,7 +1595,7 @@ class _HourTextField extends StatelessWidget {
isHour: true, isHour: true,
autofocus: autofocus, autofocus: autofocus,
style: style, style: style,
semanticHintText: MaterialLocalizations.of(context).timePickerHourLabel, semanticHintText: hourLabelText ?? MaterialLocalizations.of(context).timePickerHourLabel,
validator: validator, validator: validator,
onSavedSubmitted: onSavedSubmitted, onSavedSubmitted: onSavedSubmitted,
onChanged: onChanged, onChanged: onChanged,
...@@ -1595,6 +1611,7 @@ class _MinuteTextField extends StatelessWidget { ...@@ -1595,6 +1611,7 @@ class _MinuteTextField extends StatelessWidget {
required this.autofocus, required this.autofocus,
required this.validator, required this.validator,
required this.onSavedSubmitted, required this.onSavedSubmitted,
required this.minuteLabelText,
this.restorationId, this.restorationId,
}) : super(key: key); }) : super(key: key);
...@@ -1603,6 +1620,7 @@ class _MinuteTextField extends StatelessWidget { ...@@ -1603,6 +1620,7 @@ class _MinuteTextField extends StatelessWidget {
final bool? autofocus; final bool? autofocus;
final FormFieldValidator<String> validator; final FormFieldValidator<String> validator;
final ValueChanged<String?> onSavedSubmitted; final ValueChanged<String?> onSavedSubmitted;
final String? minuteLabelText;
final String? restorationId; final String? restorationId;
@override @override
...@@ -1613,7 +1631,7 @@ class _MinuteTextField extends StatelessWidget { ...@@ -1613,7 +1631,7 @@ class _MinuteTextField extends StatelessWidget {
isHour: false, isHour: false,
autofocus: autofocus, autofocus: autofocus,
style: style, style: style,
semanticHintText: MaterialLocalizations.of(context).timePickerMinuteLabel, semanticHintText: minuteLabelText ?? MaterialLocalizations.of(context).timePickerMinuteLabel,
validator: validator, validator: validator,
onSavedSubmitted: onSavedSubmitted, onSavedSubmitted: onSavedSubmitted,
); );
...@@ -1784,6 +1802,9 @@ class TimePickerDialog extends StatefulWidget { ...@@ -1784,6 +1802,9 @@ class TimePickerDialog extends StatefulWidget {
this.cancelText, this.cancelText,
this.confirmText, this.confirmText,
this.helpText, this.helpText,
this.errorInvalidText,
this.hourLabelText,
this.minuteLabelText,
this.restorationId, this.restorationId,
this.initialEntryMode = TimePickerEntryMode.dial, this.initialEntryMode = TimePickerEntryMode.dial,
}) : assert(initialTime != null), }) : assert(initialTime != null),
...@@ -1808,6 +1829,15 @@ class TimePickerDialog extends StatefulWidget { ...@@ -1808,6 +1829,15 @@ class TimePickerDialog extends StatefulWidget {
/// Optionally provide your own help text to the header of the time picker. /// Optionally provide your own help text to the header of the time picker.
final String? helpText; final String? helpText;
/// Optionally provide your own validation error text.
final String? errorInvalidText;
/// Optionally provide your own hour label text.
final String? hourLabelText;
/// Optionally provide your own minute label text.
final String? minuteLabelText;
/// Restoration ID to save and restore the state of the [TimePickerDialog]. /// Restoration ID to save and restore the state of the [TimePickerDialog].
/// ///
/// If it is non-null, the time picker will persist and restore the /// If it is non-null, the time picker will persist and restore the
...@@ -2220,6 +2250,9 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix ...@@ -2220,6 +2250,9 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
_TimePickerInput( _TimePickerInput(
initialSelectedTime: _selectedTime.value, initialSelectedTime: _selectedTime.value,
helpText: widget.helpText, helpText: widget.helpText,
errorInvalidText: widget.errorInvalidText,
hourLabelText: widget.hourLabelText,
minuteLabelText: widget.minuteLabelText,
autofocusHour: _autofocusHour.value, autofocusHour: _autofocusHour.value,
autofocusMinute: _autofocusMinute.value, autofocusMinute: _autofocusMinute.value,
onChanged: _handleTimeChanged, onChanged: _handleTimeChanged,
...@@ -2286,8 +2319,9 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix ...@@ -2286,8 +2319,9 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
/// determine the initial time entry selection of the picker (either a clock /// determine the initial time entry selection of the picker (either a clock
/// dial or text input). /// dial or text input).
/// ///
/// Optional strings for the [helpText], [cancelText], and [confirmText] can be /// Optional strings for the [helpText], [cancelText], [errorInvalidText],
/// provided to override the default values. /// [hourLabelText], [minuteLabelText] and [confirmText] can be provided to
/// override the default values.
/// ///
/// By default, the time picker gets its colors from the overall theme's /// By default, the time picker gets its colors from the overall theme's
/// [ColorScheme]. The time picker can be further customized by providing a /// [ColorScheme]. The time picker can be further customized by providing a
...@@ -2342,6 +2376,9 @@ Future<TimeOfDay?> showTimePicker({ ...@@ -2342,6 +2376,9 @@ Future<TimeOfDay?> showTimePicker({
String? cancelText, String? cancelText,
String? confirmText, String? confirmText,
String? helpText, String? helpText,
String? errorInvalidText,
String? hourLabelText,
String? minuteLabelText,
RouteSettings? routeSettings, RouteSettings? routeSettings,
}) async { }) async {
assert(context != null); assert(context != null);
...@@ -2356,6 +2393,9 @@ Future<TimeOfDay?> showTimePicker({ ...@@ -2356,6 +2393,9 @@ Future<TimeOfDay?> showTimePicker({
cancelText: cancelText, cancelText: cancelText,
confirmText: confirmText, confirmText: confirmText,
helpText: helpText, helpText: helpText,
errorInvalidText: errorInvalidText,
hourLabelText: hourLabelText,
minuteLabelText: minuteLabelText,
); );
return showDialog<TimeOfDay>( return showDialog<TimeOfDay>(
context: context, context: context,
......
...@@ -861,6 +861,31 @@ void _testsInput() { ...@@ -861,6 +861,31 @@ void _testsInput() {
expect(find.text(helpText), findsOneWidget); expect(find.text(helpText), findsOneWidget);
}); });
testWidgets('Hour label text is used - Input', (WidgetTester tester) async {
const String hourLabelText = 'Custom hour label';
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input, hourLabelText: hourLabelText);
expect(find.text(hourLabelText), findsOneWidget);
});
testWidgets('Minute label text is used - Input', (WidgetTester tester) async {
const String minuteLabelText = 'Custom minute label';
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input, minuteLabelText: minuteLabelText);
expect(find.text(minuteLabelText), findsOneWidget);
});
testWidgets('Invalid error text is used - Input', (WidgetTester tester) async {
const String errorInvalidText = 'Custom validation error';
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input, errorInvalidText: errorInvalidText);
// Input invalid time (hour) to force validation error
await tester.enterText(find.byType(TextField).first, '88');
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(TextButton).first));
// Tap the ok button to trigger the validation error with custom translation
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text(errorInvalidText), findsOneWidget);
});
testWidgets('Can toggle to dial entry mode', (WidgetTester tester) async { testWidgets('Can toggle 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));
...@@ -1124,6 +1149,9 @@ Future<void> mediaQueryBoilerplate( ...@@ -1124,6 +1149,9 @@ Future<void> mediaQueryBoilerplate(
double textScaleFactor = 1.0, double textScaleFactor = 1.0,
TimePickerEntryMode entryMode = TimePickerEntryMode.dial, TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
String? helpText, String? helpText,
String? hourLabelText,
String? minuteLabelText,
String? errorInvalidText,
bool accessibleNavigation = false, bool accessibleNavigation = false,
}) async { }) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -1152,6 +1180,9 @@ Future<void> mediaQueryBoilerplate( ...@@ -1152,6 +1180,9 @@ Future<void> mediaQueryBoilerplate(
initialTime: initialTime, initialTime: initialTime,
initialEntryMode: entryMode, initialEntryMode: entryMode,
helpText: helpText, helpText: helpText,
hourLabelText: hourLabelText,
minuteLabelText: minuteLabelText,
errorInvalidText: errorInvalidText
); );
}, },
child: const Text('X'), child: const Text('X'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment