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
......
......@@ -224,14 +224,13 @@ class _DayPeriodControl extends StatelessWidget {
class _HourControl extends StatelessWidget {
const _HourControl({
@required this.fragmentContext,
@required this.hourFormat,
});
final _TimePickerFragmentContext fragmentContext;
final HourFormat hourFormat;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final TextStyle hourStyle = fragmentContext.mode == _TimePickerMode.hour
? fragmentContext.activeStyle
......@@ -239,7 +238,10 @@ class _HourControl extends StatelessWidget {
return new GestureDetector(
onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.hour), context),
child: new Text(localizations.formatHour(fragmentContext.selectedTime), style: hourStyle),
child: new Text(localizations.formatHour(
fragmentContext.selectedTime,
alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
), style: hourStyle),
);
}
}
......@@ -285,15 +287,16 @@ class _MinuteControl extends StatelessWidget {
}
/// Provides time picker header layout configuration for the given
/// [timeOfDayFormat] passing [context] to each widget in the configuration.
/// [timeOfDayFormat] passing [context] to each widget in the
/// configuration.
///
/// The [timeOfDayFormat] and [context] arguments must not be null.
_TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _TimePickerFragmentContext context) {
// Creates an hour fragment.
_TimePickerHeaderFragment hour(HourFormat hourFormat) {
_TimePickerHeaderFragment hour() {
return new _TimePickerHeaderFragment(
layoutId: _TimePickerHeaderId.hour,
widget: new _HourControl(fragmentContext: context, hourFormat: hourFormat),
widget: new _HourControl(fragmentContext: context),
startMargin: _kPeriodGap,
);
}
......@@ -327,7 +330,7 @@ _TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _Tim
}
// Convenience function for creating a time header format with up to two pieces.
_TimePickerHeaderFormat format(int centrepieceIndex, _TimePickerHeaderPiece piece1,
_TimePickerHeaderFormat format(_TimePickerHeaderPiece piece1,
[ _TimePickerHeaderPiece piece2 ]) {
final List<_TimePickerHeaderPiece> pieces = <_TimePickerHeaderPiece>[];
switch (context.textDirection) {
......@@ -340,9 +343,15 @@ _TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _Tim
if (piece2 != null)
pieces.add(piece2);
pieces.add(piece1);
centrepieceIndex = pieces.length - centrepieceIndex - 1;
break;
}
int centrepieceIndex;
for (int i = 0; i < pieces.length; i += 1) {
if (pieces[i].pivotIndex >= 0) {
centrepieceIndex = i;
}
}
assert(centrepieceIndex != null);
return new _TimePickerHeaderFormat(centrepieceIndex, pieces);
}
......@@ -361,10 +370,9 @@ _TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _Tim
switch (timeOfDayFormat) {
case TimeOfDayFormat.h_colon_mm_space_a:
return format(
0,
piece(
pivotIndex: 1,
fragment1: hour(HourFormat.h),
fragment1: hour(),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
),
......@@ -374,44 +382,43 @@ _TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _Tim
),
);
case TimeOfDayFormat.H_colon_mm:
return format(0, piece(
return format(piece(
pivotIndex: 1,
fragment1: hour(HourFormat.H),
fragment1: hour(),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
));
case TimeOfDayFormat.HH_dot_mm:
return format(0, piece(
return format(piece(
pivotIndex: 1,
fragment1: hour(HourFormat.HH),
fragment1: hour(),
fragment2: string(_TimePickerHeaderId.dot, '.'),
fragment3: minute(),
));
case TimeOfDayFormat.a_space_h_colon_mm:
return format(
1,
piece(
bottomMargin: _kVerticalGap,
fragment1: dayPeriod(),
),
piece(
pivotIndex: 1,
fragment1: hour(HourFormat.h),
fragment1: hour(),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
),
);
case TimeOfDayFormat.frenchCanadian:
return format(0, piece(
return format(piece(
pivotIndex: 1,
fragment1: hour(HourFormat.HH),
fragment1: hour(),
fragment2: string(_TimePickerHeaderId.hString, 'h'),
fragment3: minute(),
));
case TimeOfDayFormat.HH_colon_mm:
return format(0, piece(
return format(piece(
pivotIndex: 1,
fragment1: hour(HourFormat.HH),
fragment1: hour(),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
));
......@@ -571,8 +578,11 @@ class _TimePickerHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final ThemeData themeData = Theme.of(context);
final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat;
final MediaQueryData media = MediaQuery.of(context);
final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context)
.timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
EdgeInsets padding;
double height;
......@@ -651,7 +661,7 @@ class _TimePickerHeader extends StatelessWidget {
}
}
List<TextPainter> _initPainters(TextTheme textTheme, List<String> labels) {
List<TextPainter> _buildPainters(TextTheme textTheme, List<String> labels) {
final TextStyle style = textTheme.subhead;
final List<TextPainter> painters = new List<TextPainter>(labels.length);
for (int i = 0; i < painters.length; ++i) {
......@@ -671,67 +681,6 @@ enum _DialRing {
inner,
}
const List<TimeOfDay> _amHours = const <TimeOfDay>[
const TimeOfDay(hour: 0, minute: 0),
const TimeOfDay(hour: 1, minute: 0),
const TimeOfDay(hour: 2, minute: 0),
const TimeOfDay(hour: 3, minute: 0),
const TimeOfDay(hour: 4, minute: 0),
const TimeOfDay(hour: 5, minute: 0),
const TimeOfDay(hour: 6, minute: 0),
const TimeOfDay(hour: 7, minute: 0),
const TimeOfDay(hour: 8, minute: 0),
const TimeOfDay(hour: 9, minute: 0),
const TimeOfDay(hour: 10, minute: 0),
const TimeOfDay(hour: 11, minute: 0),
];
const List<TimeOfDay> _pmHours = const <TimeOfDay>[
const TimeOfDay(hour: 12, minute: 0),
const TimeOfDay(hour: 13, minute: 0),
const TimeOfDay(hour: 14, minute: 0),
const TimeOfDay(hour: 15, minute: 0),
const TimeOfDay(hour: 16, minute: 0),
const TimeOfDay(hour: 17, minute: 0),
const TimeOfDay(hour: 18, minute: 0),
const TimeOfDay(hour: 19, minute: 0),
const TimeOfDay(hour: 20, minute: 0),
const TimeOfDay(hour: 21, minute: 0),
const TimeOfDay(hour: 22, minute: 0),
const TimeOfDay(hour: 23, minute: 0),
];
List<TextPainter> _init24HourInnerRing(TextTheme textTheme, MaterialLocalizations localizations) {
return _initPainters(textTheme, _amHours.map(localizations.formatHour).toList());
}
List<TextPainter> _init24HourOuterRing(TextTheme textTheme, MaterialLocalizations localizations) {
return _initPainters(textTheme, _pmHours.map(localizations.formatHour).toList());
}
List<TextPainter> _init12HourOuterRing(TextTheme textTheme, MaterialLocalizations localizations) {
return _initPainters(textTheme, _amHours.map(localizations.formatHour).toList());
}
const List<TimeOfDay> _minuteMarkerValues = const <TimeOfDay>[
const TimeOfDay(hour: 0, minute: 0),
const TimeOfDay(hour: 0, minute: 5),
const TimeOfDay(hour: 0, minute: 10),
const TimeOfDay(hour: 0, minute: 15),
const TimeOfDay(hour: 0, minute: 20),
const TimeOfDay(hour: 0, minute: 25),
const TimeOfDay(hour: 0, minute: 30),
const TimeOfDay(hour: 0, minute: 35),
const TimeOfDay(hour: 0, minute: 40),
const TimeOfDay(hour: 0, minute: 45),
const TimeOfDay(hour: 0, minute: 50),
const TimeOfDay(hour: 0, minute: 55),
];
List<TextPainter> _initMinutes(TextTheme textTheme, MaterialLocalizations localizations) {
return _initPainters(textTheme, _minuteMarkerValues.map(localizations.formatMinute).toList());
}
class _DialPainter extends CustomPainter {
const _DialPainter({
@required this.primaryOuterLabels,
......@@ -830,13 +779,13 @@ class _Dial extends StatefulWidget {
const _Dial({
@required this.selectedTime,
@required this.mode,
@required this.is24h,
@required this.use24HourDials,
@required this.onChanged
}) : assert(selectedTime != null);
final TimeOfDay selectedTime;
final _TimePickerMode mode;
final bool is24h;
final bool use24HourDials;
final ValueChanged<TimeOfDay> onChanged;
@override
......@@ -858,6 +807,19 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
))..addListener(() => setState(() { }));
}
ThemeData themeData;
MaterialLocalizations localizations;
MediaQueryData media;
@override
void didChangeDependencies() {
super.didChangeDependencies();
assert(debugCheckHasMediaQuery(context));
themeData = Theme.of(context);
localizations = MaterialLocalizations.of(context);
media = MediaQuery.of(context);
}
@override
void didUpdateWidget(_Dial oldWidget) {
super.didUpdateWidget(oldWidget);
......@@ -865,7 +827,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
if (!_dragging)
_animateTo(_getThetaForTime(widget.selectedTime));
}
if (widget.mode == _TimePickerMode.hour && widget.is24h && widget.selectedTime.period == DayPeriod.am) {
if (widget.mode == _TimePickerMode.hour && widget.use24HourDials && widget.selectedTime.period == DayPeriod.am) {
_activeRing = _DialRing.inner;
} else {
_activeRing = _DialRing.outer;
......@@ -910,7 +872,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
if (widget.mode == _TimePickerMode.hour) {
int newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod;
if (widget.is24h) {
if (widget.use24HourDials) {
if (_activeRing == _DialRing.outer) {
if (newHour != 0)
newHour = (newHour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay;
......@@ -945,7 +907,7 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
..end = angle; // The controller doesn't animate during the pan gesture.
final RenderBox box = context.findRenderObject();
final double radius = box.size.shortestSide / 2.0;
if (widget.mode == _TimePickerMode.hour && widget.is24h) {
if (widget.mode == _TimePickerMode.hour && widget.use24HourDials) {
if (offset.distance * 1.5 < radius)
_activeRing = _DialRing.inner;
else
......@@ -982,11 +944,81 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
_animateTo(_getThetaForTime(widget.selectedTime));
}
static const List<TimeOfDay> _amHours = const <TimeOfDay>[
const TimeOfDay(hour: 12, minute: 0),
const TimeOfDay(hour: 1, minute: 0),
const TimeOfDay(hour: 2, minute: 0),
const TimeOfDay(hour: 3, minute: 0),
const TimeOfDay(hour: 4, minute: 0),
const TimeOfDay(hour: 5, minute: 0),
const TimeOfDay(hour: 6, minute: 0),
const TimeOfDay(hour: 7, minute: 0),
const TimeOfDay(hour: 8, minute: 0),
const TimeOfDay(hour: 9, minute: 0),
const TimeOfDay(hour: 10, minute: 0),
const TimeOfDay(hour: 11, minute: 0),
];
static const List<TimeOfDay> _pmHours = const <TimeOfDay>[
const TimeOfDay(hour: 0, minute: 0),
const TimeOfDay(hour: 13, minute: 0),
const TimeOfDay(hour: 14, minute: 0),
const TimeOfDay(hour: 15, minute: 0),
const TimeOfDay(hour: 16, minute: 0),
const TimeOfDay(hour: 17, minute: 0),
const TimeOfDay(hour: 18, minute: 0),
const TimeOfDay(hour: 19, minute: 0),
const TimeOfDay(hour: 20, minute: 0),
const TimeOfDay(hour: 21, minute: 0),
const TimeOfDay(hour: 22, minute: 0),
const TimeOfDay(hour: 23, minute: 0),
];
List<TextPainter> _build24HourInnerRing(TextTheme textTheme) {
return _buildPainters(textTheme, _amHours
.map((TimeOfDay timeOfDay) {
return localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat);
})
.toList());
}
List<TextPainter> _build24HourOuterRing(TextTheme textTheme) {
return _buildPainters(textTheme, _pmHours
.map((TimeOfDay timeOfDay) {
return localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat);
})
.toList());
}
List<TextPainter> _build12HourOuterRing(TextTheme textTheme) {
return _buildPainters(textTheme, _amHours
.map((TimeOfDay timeOfDay) {
return localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat);
})
.toList());
}
List<TextPainter> _buildMinutes(TextTheme textTheme) {
const List<TimeOfDay> _minuteMarkerValues = const <TimeOfDay>[
const TimeOfDay(hour: 0, minute: 0),
const TimeOfDay(hour: 0, minute: 5),
const TimeOfDay(hour: 0, minute: 10),
const TimeOfDay(hour: 0, minute: 15),
const TimeOfDay(hour: 0, minute: 20),
const TimeOfDay(hour: 0, minute: 25),
const TimeOfDay(hour: 0, minute: 30),
const TimeOfDay(hour: 0, minute: 35),
const TimeOfDay(hour: 0, minute: 40),
const TimeOfDay(hour: 0, minute: 45),
const TimeOfDay(hour: 0, minute: 50),
const TimeOfDay(hour: 0, minute: 55),
];
return _buildPainters(textTheme, _minuteMarkerValues.map(localizations.formatMinute).toList());
}
@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
Color backgroundColor;
switch (themeData.brightness) {
case Brightness.light:
......@@ -1004,20 +1036,20 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
List<TextPainter> secondaryInnerLabels;
switch (widget.mode) {
case _TimePickerMode.hour:
if (widget.is24h) {
primaryOuterLabels = _init24HourOuterRing(theme.textTheme, localizations);
secondaryOuterLabels = _init24HourOuterRing(theme.accentTextTheme, localizations);
primaryInnerLabels = _init24HourInnerRing(theme.textTheme, localizations);
secondaryInnerLabels = _init24HourInnerRing(theme.accentTextTheme, localizations);
if (widget.use24HourDials) {
primaryOuterLabels = _build24HourOuterRing(theme.textTheme);
secondaryOuterLabels = _build24HourOuterRing(theme.accentTextTheme);
primaryInnerLabels = _build24HourInnerRing(theme.textTheme);
secondaryInnerLabels = _build24HourInnerRing(theme.accentTextTheme);
} else {
primaryOuterLabels = _init12HourOuterRing(theme.textTheme, localizations);
secondaryOuterLabels = _init12HourOuterRing(theme.accentTextTheme, localizations);
primaryOuterLabels = _build12HourOuterRing(theme.textTheme);
secondaryOuterLabels = _build12HourOuterRing(theme.accentTextTheme);
}
break;
case _TimePickerMode.minute:
primaryOuterLabels = _initMinutes(theme.textTheme, localizations);
primaryOuterLabels = _buildMinutes(theme.textTheme);
primaryInnerLabels = null;
secondaryOuterLabels = _initMinutes(theme.accentTextTheme, localizations);
secondaryOuterLabels = _buildMinutes(theme.accentTextTheme);
secondaryInnerLabels = null;
break;
}
......@@ -1043,13 +1075,23 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
}
}
/// A material design time picker designed to appear inside a popup dialog.
///
/// Pass this widget to [showDialog]. The value returned by [showDialog] is the
/// selected [TimeOfDay] if the user taps the "OK" button, or null if the user
/// taps the "CANCEL" button. The selected time is reported by calling
/// [Navigator.pop].
class _TimePickerDialog extends StatefulWidget {
/// Creates a material time picker.
///
/// [initialTime] must not be null.
const _TimePickerDialog({
Key key,
@required this.initialTime
}) : assert(initialTime != null),
super(key: key);
/// The time initially selected when the dialog is shown.
final TimeOfDay initialTime;
@override
......@@ -1106,8 +1148,10 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat;
final MediaQueryData media = MediaQuery.of(context);
final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
final Widget picker = new Padding(
padding: const EdgeInsets.all(16.0),
......@@ -1115,7 +1159,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
aspectRatio: 1.0,
child: new _Dial(
mode: _mode,
is24h: hourFormat(of: timeOfDayFormat) != HourFormat.h,
use24HourDials: hourFormat(of: timeOfDayFormat) != HourFormat.h,
selectedTime: _selectedTime,
onChanged: _handleTimeChanged,
)
......@@ -1222,7 +1266,7 @@ Future<TimeOfDay> showTimePicker({
}) async {
assert(context != null);
assert(initialTime != null);
return await showDialog(
return await showDialog<TimeOfDay>(
context: context,
child: new _TimePickerDialog(initialTime: initialTime),
);
......
......@@ -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