mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add optional custom localization for TimePicker, the same way DatePicker has it (#84566)
This commit is contained in:
parent
b63bcaa3c9
commit
eee9f100d0
@ -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'),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user