Unverified Commit f4b0ccd9 authored by Yegor's avatar Yegor Committed by GitHub

Use alwaysUse24HourFormat when formatting time of day (#12517)

* alwaysUse24HourFormat in MediaQuery and time picker

* docs; dead code

* address some comments

* MaterialLocalizations.timeOfDayFormat is the single source of 24-hour-formattedness

* Make TimePickerDialog private again

* wire up MediaQueryData.fromWindow to Window
parent 82dbd128
......@@ -95,7 +95,7 @@ abstract class MaterialLocalizations {
///
/// The documentation for [TimeOfDayFormat] enum values provides details on
/// each supported layout.
TimeOfDayFormat get timeOfDayFormat;
TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false });
/// Provides geometric text preferences for the current locale.
///
......@@ -120,14 +120,22 @@ abstract class MaterialLocalizations {
/// Formats [TimeOfDay.hour] in the given time of day according to the value
/// of [timeOfDayFormat].
String formatHour(TimeOfDay timeOfDay);
///
/// If [alwaysUse24HourFormat] is true, formats hour using [HourFormat.HH]
/// rather than the default for the current locale.
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false });
/// Formats [TimeOfDay.minute] in the given time of day according to the value
/// of [timeOfDayFormat].
String formatMinute(TimeOfDay timeOfDay);
/// Formats [timeOfDay] according to the value of [timeOfDayFormat].
String formatTimeOfDay(TimeOfDay timeOfDay);
///
/// If [alwaysUse24HourFormat] is true, formats hour using [HourFormat.HH]
/// rather than the default for the current locale. This value is usually
/// passed from [MediaQueryData.alwaysUse24HourFormat], which has platform-
/// specific behavior.
String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false });
/// Full unabbreviated year format, e.g. 2017 rather than 17.
String formatYear(DateTime date);
......@@ -274,9 +282,27 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
];
@override
String formatHour(TimeOfDay timeOfDay) {
assert(hourFormat(of: timeOfDayFormat) == HourFormat.h);
return formatDecimal(timeOfDay.hour);
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
final TimeOfDayFormat format = timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat);
switch (format) {
case TimeOfDayFormat.h_colon_mm_space_a:
return formatDecimal(timeOfDay.hourOfPeriod == 0 ? 12 : timeOfDay.hourOfPeriod);
case TimeOfDayFormat.HH_colon_mm:
return _formatTwoDigitZeroPad(timeOfDay.hour);
default:
throw new AssertionError('$runtimeType does not support $format.');
}
}
/// Formats [number] using two digits, assuming it's in the 0-99 inclusive
/// range. Not designed to format values outside this range.
String _formatTwoDigitZeroPad(int number) {
assert(0 <= number && number < 100);
if (number < 10)
return '0$number';
return '$number';
}
@override
......@@ -335,8 +361,7 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
}
@override
String formatTimeOfDay(TimeOfDay timeOfDay) {
assert(timeOfDayFormat == TimeOfDayFormat.h_colon_mm_space_a);
String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
// Not using intl.DateFormat for two reasons:
//
// - DateFormat supports more formats than our material time picker does,
......@@ -345,7 +370,24 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
// - DateFormat operates on DateTime, which is sensitive to time eras and
// time zones, while here we want to format hour and minute within one day
// no matter what date the day falls on.
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
final StringBuffer buffer = new StringBuffer();
// Add hour:minute.
buffer
..write(formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat))
..write(':')
..write(formatMinute(timeOfDay));
if (alwaysUse24HourFormat) {
// There's no AM/PM indicator in 24-hour format.
return '$buffer';
}
// Add AM/PM indicator.
buffer
..write(' ')
..write(_formatDayPeriod(timeOfDay));
return '$buffer';
}
@override
......@@ -434,7 +476,11 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
String get postMeridiemAbbreviation => 'PM';
@override
TimeOfDayFormat get timeOfDayFormat => TimeOfDayFormat.h_colon_mm_space_a;
TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false }) {
return alwaysUse24HourFormat
? TimeOfDayFormat.HH_colon_mm
: TimeOfDayFormat.h_colon_mm_space_a;
}
/// Looks up text geometry defined in [MaterialTextGeometry].
@override
......
......@@ -84,8 +84,12 @@ class TimeOfDay {
///
/// This is a shortcut for [MaterialLocalizations.formatTimeOfDay].
String format(BuildContext context) {
debugCheckHasMediaQuery(context);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
return localizations.formatTimeOfDay(this);
return localizations.formatTimeOfDay(
this,
alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
);
}
@override
......
......@@ -40,7 +40,8 @@ class MediaQueryData {
this.size: Size.zero,
this.devicePixelRatio: 1.0,
this.textScaleFactor: 1.0,
this.padding: EdgeInsets.zero
this.padding: EdgeInsets.zero,
this.alwaysUse24HourFormat: false,
});
/// Creates data for a media query based on the given window.
......@@ -53,7 +54,8 @@ class MediaQueryData {
: size = window.physicalSize / window.devicePixelRatio,
devicePixelRatio = window.devicePixelRatio,
textScaleFactor = window.textScaleFactor,
padding = new EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio);
padding = new EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
alwaysUse24HourFormat = window.alwaysUse24HourFormat;
/// The size of the media in logical pixel (e.g, the size of the screen).
///
......@@ -77,6 +79,19 @@ class MediaQueryData {
/// The padding around the edges of the media (e.g., the screen).
final EdgeInsets padding;
/// Whether to use 24-hour format when formatting time.
///
/// The behavior of this flag is different across platforms:
///
/// - On Android this flag is reported directly from the user settings called
/// "Use 24-hour format". It applies to any locale used by the application,
/// whether it is the system-wide locale, or the custom locale set by the
/// application.
/// - On iOS this flag is set to true when the user setting called "24-Hour
/// Time" is set or the system-wide locale's default uses 24-hour
/// formatting.
final bool alwaysUse24HourFormat;
/// The orientation of the media (e.g., whether the device is in landscape or portrait mode).
Orientation get orientation {
return size.width > size.height ? Orientation.landscape : Orientation.portrait;
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
......@@ -29,7 +31,7 @@ class _TimePickerLauncher extends StatelessWidget {
onPressed: () async {
onChanged(await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 7, minute: 0)
initialTime: const TimeOfDay(hour: 7, minute: 0),
));
}
);
......@@ -41,17 +43,15 @@ class _TimePickerLauncher extends StatelessWidget {
}
}
Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged,
{ Locale locale: const Locale('en', 'US') }) async {
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: locale,));
Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged) async {
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: const Locale('en', 'US')));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
return tester.getCenter(find.byKey(const Key('time-picker-dial')));
}
Future<Null> finishPicker(WidgetTester tester) async {
final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(RaisedButton)));
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1));
}
......@@ -205,4 +205,72 @@ void main() {
expect(feedback.hapticCount, 3);
});
});
const List<String> labels12To11 = const <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
const List<String> labels12To11TwoDigit = const <String>['12', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11'];
const List<String> labels00To23 = const <String>['00', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
Future<Null> mediaQueryBoilerplate(WidgetTester tester, bool alwaysUse24HourFormat) async {
await tester.pumpWidget(
new Localizations(
locale: const Locale('en', 'US'),
delegates: <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
child: new MediaQuery(
data: new MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
child: new Directionality(
textDirection: TextDirection.ltr,
child: new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute<dynamic>(builder: (BuildContext context) {
showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0));
return new Container();
});
},
),
),
),
),
);
// Pump once, because the dialog shows up asynchronously.
await tester.pump();
}
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, false);
final CustomPaint dialPaint = tester.widget(find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
matching: find.byType(CustomPaint),
));
final dynamic dialPainter = dialPaint.painter;
final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
expect(dialPainter.primaryInnerLabels, null);
final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
expect(dialPainter.secondaryInnerLabels, null);
});
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, true);
final CustomPaint dialPaint = tester.widget(find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
matching: find.byType(CustomPaint),
));
final dynamic dialPainter = dialPaint.painter;
final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
final List<TextPainter> primaryInnerLabels = dialPainter.primaryInnerLabels;
expect(primaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
final List<TextPainter> secondaryInnerLabels = dialPainter.secondaryInnerLabels;
expect(secondaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
});
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('TimeOfDay.format', () {
testWidgets('respects alwaysUse24HourFormat option', (WidgetTester tester) async {
Future<String> pumpTest(bool alwaysUse24HourFormat) async {
String formattedValue;
await tester.pumpWidget(new MaterialApp(
home: new MediaQuery(
data: new MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
child: new Builder(builder: (BuildContext context) {
formattedValue = const TimeOfDay(hour: 7, minute: 0).format(context);
return new Container();
}),
),
));
return formattedValue;
}
expect(await pumpTest(false), '7:00 AM');
expect(await pumpTest(true), '07:00');
});
});
}
......@@ -141,8 +141,8 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
}
@override
String formatHour(TimeOfDay timeOfDay) {
switch (hourFormat(of: timeOfDayFormat)) {
String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
switch (hourFormat(of: timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat))) {
case HourFormat.HH:
return _twoDigitZeroPaddedFormat.format(timeOfDay.hour);
case HourFormat.H:
......@@ -188,7 +188,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
}
@override
String formatTimeOfDay(TimeOfDay timeOfDay) {
String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
// Not using intl.DateFormat for two reasons:
//
// - DateFormat supports more formats than our material time picker does,
......@@ -197,18 +197,20 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
// - DateFormat operates on DateTime, which is sensitive to time eras and
// time zones, while here we want to format hour and minute within one day
// no matter what date the day falls on.
switch (timeOfDayFormat) {
final String hour = formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat);
final String minute = formatMinute(timeOfDay);
switch (timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat)) {
case TimeOfDayFormat.h_colon_mm_space_a:
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
return '$hour:$minute ${_formatDayPeriod(timeOfDay)}';
case TimeOfDayFormat.H_colon_mm:
case TimeOfDayFormat.HH_colon_mm:
return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
return '$hour:$minute';
case TimeOfDayFormat.HH_dot_mm:
return '${formatHour(timeOfDay)}.${formatMinute(timeOfDay)}';
return '$hour.$minute';
case TimeOfDayFormat.a_space_h_colon_mm:
return '${_formatDayPeriod(timeOfDay)} ${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
return '${_formatDayPeriod(timeOfDay)} $hour:$minute';
case TimeOfDayFormat.frenchCanadian:
return '${formatHour(timeOfDay)} h ${formatMinute(timeOfDay)}';
return '$hour h $minute';
}
return null;
......@@ -328,7 +330,7 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
/// * http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US shows the
/// short time pattern used in locale en_US
@override
TimeOfDayFormat get timeOfDayFormat {
TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false }) {
final String icuShortTimePattern = _nameToValue['timeOfDayFormat'];
assert(() {
......@@ -343,7 +345,12 @@ class GlobalMaterialLocalizations implements MaterialLocalizations {
return true;
}());
return _icuTimeOfDayToEnum[icuShortTimePattern];
final TimeOfDayFormat icuFormat = _icuTimeOfDayToEnum[icuShortTimePattern];
if (alwaysUse24HourFormat)
return _get24HourVersionOf(icuFormat);
return icuFormat;
}
/// Looks up text geometry defined in [MaterialTextGeometry].
......@@ -403,6 +410,23 @@ const Map<String, TimeOfDayFormat> _icuTimeOfDayToEnum = const <String, TimeOfDa
'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
};
/// Finds the [TimeOfDayFormat] to use instead of the `original` when the
/// `original` uses 12-hour format and [MediaQueryData.alwaysUse24HourFormat]
/// is true.
TimeOfDayFormat _get24HourVersionOf(TimeOfDayFormat original) {
switch (original) {
case TimeOfDayFormat.HH_colon_mm:
case TimeOfDayFormat.HH_dot_mm:
case TimeOfDayFormat.frenchCanadian:
case TimeOfDayFormat.H_colon_mm:
return original;
case TimeOfDayFormat.h_colon_mm_space_a:
case TimeOfDayFormat.a_space_h_colon_mm:
return TimeOfDayFormat.HH_colon_mm;
}
return TimeOfDayFormat.HH_colon_mm;
}
/// Tracks if date i18n data has been loaded.
bool _dateIntlDataInitialized = false;
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -24,40 +26,44 @@ void main() {
});
group('formatHour', () {
test('formats h', () {
GlobalMaterialLocalizations localizations;
localizations = new GlobalMaterialLocalizations(const Locale('en', 'US'));
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '10');
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '8');
localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '١٠');
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '٨');
Future<String> formatHour(WidgetTester tester, Locale locale, TimeOfDay timeOfDay) async {
final Completer<String> completer = new Completer<String>();
await tester.pumpWidget(new MaterialApp(
supportedLocales: <Locale>[locale],
locale: locale,
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
GlobalMaterialLocalizations.delegate,
],
home: new Builder(builder: (BuildContext context) {
completer.complete(MaterialLocalizations.of(context).formatHour(timeOfDay));
return new Container();
}),
));
return completer.future;
}
testWidgets('formats h', (WidgetTester tester) async {
expect(await formatHour(tester, const Locale('en', 'US'), const TimeOfDay(hour: 10, minute: 0)), '10');
expect(await formatHour(tester, const Locale('en', 'US'), const TimeOfDay(hour: 20, minute: 0)), '8');
expect(await formatHour(tester, const Locale('ar', ''), const TimeOfDay(hour: 10, minute: 0)), '١٠');
expect(await formatHour(tester, const Locale('ar', ''), const TimeOfDay(hour: 20, minute: 0)), '٨');
});
test('formats HH', () {
GlobalMaterialLocalizations localizations;
testWidgets('formats HH', (WidgetTester tester) async {
expect(await formatHour(tester, const Locale('de', ''), const TimeOfDay(hour: 9, minute: 0)), '09');
expect(await formatHour(tester, const Locale('de', ''), const TimeOfDay(hour: 20, minute: 0)), '20');
localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
localizations = new GlobalMaterialLocalizations(const Locale('en', 'GB'));
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
expect(await formatHour(tester, const Locale('en', 'GB'), const TimeOfDay(hour: 9, minute: 0)), '09');
expect(await formatHour(tester, const Locale('en', 'GB'), const TimeOfDay(hour: 20, minute: 0)), '20');
});
test('formats H', () {
GlobalMaterialLocalizations localizations;
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '9');
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
testWidgets('formats H', (WidgetTester tester) async {
expect(await formatHour(tester, const Locale('es', ''), const TimeOfDay(hour: 9, minute: 0)), '9');
expect(await formatHour(tester, const Locale('es', ''), const TimeOfDay(hour: 20, minute: 0)), '20');
localizations = new GlobalMaterialLocalizations(const Locale('fa', ''));
expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '۹');
expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '۲۰');
expect(await formatHour(tester, const Locale('fa', ''), const TimeOfDay(hour: 9, minute: 0)), '۹');
expect(await formatHour(tester, const Locale('fa', ''), const TimeOfDay(hour: 20, minute: 0)), '۲۰');
});
});
......@@ -74,48 +80,49 @@ void main() {
});
group('formatTimeOfDay', () {
test('formats ${TimeOfDayFormat.h_colon_mm_space_a}', () {
GlobalMaterialLocalizations localizations;
localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
localizations = new GlobalMaterialLocalizations(const Locale('en', ''));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
Future<String> formatTimeOfDay(WidgetTester tester, Locale locale, TimeOfDay timeOfDay) async {
final Completer<String> completer = new Completer<String>();
await tester.pumpWidget(new MaterialApp(
supportedLocales: <Locale>[locale],
locale: locale,
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
GlobalMaterialLocalizations.delegate,
],
home: new Builder(builder: (BuildContext context) {
completer.complete(MaterialLocalizations.of(context).formatTimeOfDay(timeOfDay));
return new Container();
}),
));
return completer.future;
}
testWidgets('formats ${TimeOfDayFormat.h_colon_mm_space_a}', (WidgetTester tester) async {
expect(await formatTimeOfDay(tester, const Locale('ar', ''), const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
expect(await formatTimeOfDay(tester, const Locale('ar', ''), const TimeOfDay(hour: 20, minute: 32)), '٨:٣٢ م');
expect(await formatTimeOfDay(tester, const Locale('en', ''), const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
expect(await formatTimeOfDay(tester, const Locale('en', ''), const TimeOfDay(hour: 20, minute: 32)), '8:32 PM');
});
test('formats ${TimeOfDayFormat.HH_colon_mm}', () {
GlobalMaterialLocalizations localizations;
localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
localizations = new GlobalMaterialLocalizations(const Locale('en', 'ZA'));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
testWidgets('formats ${TimeOfDayFormat.HH_colon_mm}', (WidgetTester tester) async {
expect(await formatTimeOfDay(tester, const Locale('de', ''), const TimeOfDay(hour: 9, minute: 32)), '09:32');
expect(await formatTimeOfDay(tester, const Locale('en', 'ZA'), const TimeOfDay(hour: 9, minute: 32)), '09:32');
});
test('formats ${TimeOfDayFormat.H_colon_mm}', () {
GlobalMaterialLocalizations localizations;
testWidgets('formats ${TimeOfDayFormat.H_colon_mm}', (WidgetTester tester) async {
expect(await formatTimeOfDay(tester, const Locale('es', ''), const TimeOfDay(hour: 9, minute: 32)), '9:32');
expect(await formatTimeOfDay(tester, const Locale('es', ''), const TimeOfDay(hour: 20, minute: 32)), '20:32');
localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
localizations = new GlobalMaterialLocalizations(const Locale('ja', ''));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
expect(await formatTimeOfDay(tester, const Locale('ja', ''), const TimeOfDay(hour: 9, minute: 32)), '9:32');
expect(await formatTimeOfDay(tester, const Locale('ja', ''), const TimeOfDay(hour: 20, minute: 32)), '20:32');
});
test('formats ${TimeOfDayFormat.frenchCanadian}', () {
GlobalMaterialLocalizations localizations;
localizations = new GlobalMaterialLocalizations(const Locale('fr', 'CA'));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
testWidgets('formats ${TimeOfDayFormat.frenchCanadian}', (WidgetTester tester) async {
expect(await formatTimeOfDay(tester, const Locale('fr', 'CA'), const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
});
test('formats ${TimeOfDayFormat.a_space_h_colon_mm}', () {
GlobalMaterialLocalizations localizations;
localizations = new GlobalMaterialLocalizations(const Locale('zh', ''));
expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
testWidgets('formats ${TimeOfDayFormat.a_space_h_colon_mm}', (WidgetTester tester) async {
expect(await formatTimeOfDay(tester, const Locale('zh', ''), const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
});
});
});
......
......@@ -48,8 +48,7 @@ Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChange
}
Future<Null> finishPicker(WidgetTester tester) async {
final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(RaisedButton)));
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1));
}
......@@ -58,11 +57,11 @@ void main() {
testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final Map<Locale, List<String>> locales = <Locale, List<String>>{
const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
const Locale('en', 'US'): const <String>['hour', 'string :', 'minute', 'period'], //'h:mm a'
const Locale('en', 'GB'): const <String>['hour', 'string :', 'minute'], //'HH:mm'
const Locale('es', 'ES'): const <String>['hour', 'string :', 'minute'], //'H:mm'
const Locale('fr', 'CA'): const <String>['hour', 'string h', 'minute'], //'HH \'h\' mm'
const Locale('zh', 'ZH'): const <String>['period', 'hour', 'string :', 'minute'], //'ah:mm'
};
for (Locale locale in locales.keys) {
......@@ -77,7 +76,7 @@ void main() {
} else if (fragmentType == '_DayPeriodControl') {
actual.add('period');
} else if (fragmentType == '_HourControl') {
actual.add('hour ${widget.hourFormat.toString().split('.').last}');
actual.add('hour');
} else if (fragmentType == '_StringFragment') {
actual.add('string ${widget.value}');
} else {
......@@ -126,4 +125,72 @@ void main() {
}
}
});
const List<String> labels12To11 = const <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
const List<String> labels12To11TwoDigit = const <String>['12', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11'];
const List<String> labels00To23 = const <String>['00', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
Future<Null> mediaQueryBoilerplate(WidgetTester tester, bool alwaysUse24HourFormat) async {
await tester.pumpWidget(
new Localizations(
locale: const Locale('en', 'US'),
delegates: <LocalizationsDelegate<dynamic>>[
GlobalMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
child: new MediaQuery(
data: new MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
child: new Directionality(
textDirection: TextDirection.ltr,
child: new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute<dynamic>(builder: (BuildContext context) {
showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0));
return new Container();
});
},
),
),
),
),
);
// Pump once, because the dialog shows up asynchronously.
await tester.pump();
}
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, false);
final CustomPaint dialPaint = tester.widget(find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
matching: find.byType(CustomPaint),
));
final dynamic dialPainter = dialPaint.painter;
final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
expect(dialPainter.primaryInnerLabels, null);
final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
expect(dialPainter.secondaryInnerLabels, null);
});
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, true);
final CustomPaint dialPaint = tester.widget(find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
matching: find.byType(CustomPaint),
));
final dynamic dialPainter = dialPaint.painter;
final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
final List<TextPainter> primaryInnerLabels = dialPainter.primaryInnerLabels;
expect(primaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
final List<TextPainter> secondaryInnerLabels = dialPainter.secondaryInnerLabels;
expect(secondaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
});
}
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