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 {
Key? key,
required this.initialSelectedTime,
required this.helpText,
required this.errorInvalidText,
required this.hourLabelText,
required this.minuteLabelText,
required this.autofocusHour,
required this.autofocusMinute,
required this.onChanged,
......@@ -1304,6 +1307,15 @@ class _TimePickerInput extends StatefulWidget {
/// Optionally provide your own help text to the time picker.
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? autofocusMinute;
......@@ -1480,12 +1492,13 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
validator: _validateHour,
onSavedSubmitted: _handleHourSavedSubmitted,
onChanged: _handleHourChanged,
hourLabelText: widget.hourLabelText,
),
const SizedBox(height: 8.0),
if (!hourHasError.value && !minuteHasError.value)
ExcludeSemantics(
child: Text(
MaterialLocalizations.of(context).timePickerHourLabel,
widget.hourLabelText ?? MaterialLocalizations.of(context).timePickerHourLabel,
style: theme.textTheme.caption,
maxLines: 1,
overflow: TextOverflow.ellipsis,
......@@ -1511,12 +1524,13 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
autofocus: widget.autofocusMinute,
validator: _validateMinute,
onSavedSubmitted: _handleMinuteSavedSubmitted,
minuteLabelText: widget.minuteLabelText,
),
const SizedBox(height: 8.0),
if (!hourHasError.value && !minuteHasError.value)
ExcludeSemantics(
child: Text(
MaterialLocalizations.of(context).timePickerMinuteLabel,
widget.minuteLabelText ?? MaterialLocalizations.of(context).timePickerMinuteLabel,
style: theme.textTheme.caption,
maxLines: 1,
overflow: TextOverflow.ellipsis,
......@@ -1540,7 +1554,7 @@ class _TimePickerInputState extends State<_TimePickerInput> with RestorationMixi
),
if (hourHasError.value || minuteHasError.value)
Text(
MaterialLocalizations.of(context).invalidTimeLabel,
widget.errorInvalidText ?? MaterialLocalizations.of(context).invalidTimeLabel,
style: theme.textTheme.bodyText2!.copyWith(color: theme.colorScheme.error),
)
else
......@@ -1560,6 +1574,7 @@ class _HourTextField extends StatelessWidget {
required this.validator,
required this.onSavedSubmitted,
required this.onChanged,
required this.hourLabelText,
this.restorationId,
}) : super(key: key);
......@@ -1569,6 +1584,7 @@ class _HourTextField extends StatelessWidget {
final FormFieldValidator<String> validator;
final ValueChanged<String?> onSavedSubmitted;
final ValueChanged<String> onChanged;
final String? hourLabelText;
final String? restorationId;
@override
......@@ -1579,7 +1595,7 @@ class _HourTextField extends StatelessWidget {
isHour: true,
autofocus: autofocus,
style: style,
semanticHintText: MaterialLocalizations.of(context).timePickerHourLabel,
semanticHintText: hourLabelText ?? MaterialLocalizations.of(context).timePickerHourLabel,
validator: validator,
onSavedSubmitted: onSavedSubmitted,
onChanged: onChanged,
......@@ -1595,6 +1611,7 @@ class _MinuteTextField extends StatelessWidget {
required this.autofocus,
required this.validator,
required this.onSavedSubmitted,
required this.minuteLabelText,
this.restorationId,
}) : super(key: key);
......@@ -1603,6 +1620,7 @@ class _MinuteTextField extends StatelessWidget {
final bool? autofocus;
final FormFieldValidator<String> validator;
final ValueChanged<String?> onSavedSubmitted;
final String? minuteLabelText;
final String? restorationId;
@override
......@@ -1613,7 +1631,7 @@ class _MinuteTextField extends StatelessWidget {
isHour: false,
autofocus: autofocus,
style: style,
semanticHintText: MaterialLocalizations.of(context).timePickerMinuteLabel,
semanticHintText: minuteLabelText ?? MaterialLocalizations.of(context).timePickerMinuteLabel,
validator: validator,
onSavedSubmitted: onSavedSubmitted,
);
......@@ -1784,6 +1802,9 @@ class TimePickerDialog extends StatefulWidget {
this.cancelText,
this.confirmText,
this.helpText,
this.errorInvalidText,
this.hourLabelText,
this.minuteLabelText,
this.restorationId,
this.initialEntryMode = TimePickerEntryMode.dial,
}) : assert(initialTime != null),
......@@ -1808,6 +1829,15 @@ class TimePickerDialog extends StatefulWidget {
/// Optionally provide your own help text to the header of the time picker.
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].
///
/// If it is non-null, the time picker will persist and restore the
......@@ -2220,6 +2250,9 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
_TimePickerInput(
initialSelectedTime: _selectedTime.value,
helpText: widget.helpText,
errorInvalidText: widget.errorInvalidText,
hourLabelText: widget.hourLabelText,
minuteLabelText: widget.minuteLabelText,
autofocusHour: _autofocusHour.value,
autofocusMinute: _autofocusMinute.value,
onChanged: _handleTimeChanged,
......@@ -2286,8 +2319,9 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
/// determine the initial time entry selection of the picker (either a clock
/// dial or text input).
///
/// Optional strings for the [helpText], [cancelText], and [confirmText] can be
/// provided to override the default values.
/// Optional strings for the [helpText], [cancelText], [errorInvalidText],
/// [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
/// [ColorScheme]. The time picker can be further customized by providing a
......@@ -2342,6 +2376,9 @@ Future<TimeOfDay?> showTimePicker({
String? cancelText,
String? confirmText,
String? helpText,
String? errorInvalidText,
String? hourLabelText,
String? minuteLabelText,
RouteSettings? routeSettings,
}) async {
assert(context != null);
......@@ -2356,6 +2393,9 @@ Future<TimeOfDay?> showTimePicker({
cancelText: cancelText,
confirmText: confirmText,
helpText: helpText,
errorInvalidText: errorInvalidText,
hourLabelText: hourLabelText,
minuteLabelText: minuteLabelText,
);
return showDialog<TimeOfDay>(
context: context,
......
......@@ -861,6 +861,31 @@ void _testsInput() {
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 {
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input);
await tester.tap(find.byIcon(Icons.access_time));
......@@ -1124,6 +1149,9 @@ Future<void> mediaQueryBoilerplate(
double textScaleFactor = 1.0,
TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
String? helpText,
String? hourLabelText,
String? minuteLabelText,
String? errorInvalidText,
bool accessibleNavigation = false,
}) async {
await tester.pumpWidget(
......@@ -1152,6 +1180,9 @@ Future<void> mediaQueryBoilerplate(
initialTime: initialTime,
initialEntryMode: entryMode,
helpText: helpText,
hourLabelText: hourLabelText,
minuteLabelText: minuteLabelText,
errorInvalidText: errorInvalidText
);
},
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