Commit 150c5830 authored by Yegor's avatar Yegor Committed by GitHub

Date picker i18n (#12324)

* formatYear

* localize date picker

* tests

* clean-ups

* address comments
parent b6185b66
......@@ -7,6 +7,8 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart' as intl;
import 'package:intl/date_symbols.dart' as intl;
import 'package:intl/date_symbol_data_local.dart' as intl_local_date_data;
import 'i18n/localizations.dart';
import 'time.dart';
......@@ -121,6 +123,52 @@ abstract class MaterialLocalizations {
/// Formats [timeOfDay] according to the value of [timeOfDayFormat].
String formatTimeOfDay(TimeOfDay timeOfDay);
/// Full unabbreviated year format, e.g. 2017 rather than 17.
String formatYear(DateTime date);
/// Formats the date using a medium-width format.
///
/// Abbreviates month and days of week. This appears in the header of the date
/// picker invoked using [showDatePicker].
///
/// Examples:
///
/// - US English: Wed, Sep 27
/// - Russian: ср, сент. 27
String formatMediumDate(DateTime date);
/// Formats the month and the year of the given [date].
///
/// The returned string does not contain the day of the month. This appears
/// in the date picker invoked using [showDatePicker].
String formatMonthYear(DateTime date);
/// List of week day names in narrow format, usually 1- or 2-letter
/// abbreviations of full names.
///
/// The list begins with the value corresponding to Sunday and ends with
/// Saturday. Use [firstDayOfWeekIndex] to find the first day of week in this
/// list.
///
/// Examples:
///
/// - US English: S, M, T, W, T, F, S
/// - Russian: вс, пн, вт, ср, чт, пт, сб - notice that the list begins with
/// вс (Sunday) even though the first day of week for Russian is Monday.
List<String> get narrowWeekDays;
/// Index of the first day of week, where 0 points to Sunday, and 6 points to
/// Saturday.
///
/// This getter is compatible with [narrowWeekDays]. For example:
///
/// ```dart
/// var localizations = MaterialLocalizations.of(context);
/// // The name of the first day of week for the current locale.
/// var firstDayOfWeek = localizations.narrowWeekDays[localizations.firstDayOfWeekIndex];
/// ```
int get firstDayOfWeekIndex;
/// The `MaterialLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
......@@ -146,13 +194,30 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
/// [LocalizationsDelegate] implementations typically call the static [load]
/// function, rather than constructing this class directly.
DefaultMaterialLocalizations(this.locale)
: this._localeName = _computeLocaleName(locale) {
assert(locale != null);
: assert(locale != null),
this._localeName = _computeLocaleName(locale) {
_loadDateIntlDataIfNotLoaded();
if (localizations.containsKey(locale.languageCode))
_nameToValue.addAll(localizations[locale.languageCode]);
if (localizations.containsKey(_localeName))
_nameToValue.addAll(localizations[_localeName]);
const String kMediumDatePattern = 'E, MMM\u00a0d';
if (intl.DateFormat.localeExists(_localeName)) {
_fullYearFormat = new intl.DateFormat.y(_localeName);
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, _localeName);
_yearMonthFormat = new intl.DateFormat('yMMMM', _localeName);
} else if (intl.DateFormat.localeExists(locale.languageCode)) {
_fullYearFormat = new intl.DateFormat.y(locale.languageCode);
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern, locale.languageCode);
_yearMonthFormat = new intl.DateFormat('yMMMM', locale.languageCode);
} else {
_fullYearFormat = new intl.DateFormat.y();
_mediumDateFormat = new intl.DateFormat(kMediumDatePattern);
_yearMonthFormat = new intl.DateFormat('yMMMM');
}
if (intl.NumberFormat.localeExists(_localeName)) {
_decimalFormat = new intl.NumberFormat.decimalPattern(_localeName);
_twoDigitZeroPaddedFormat = new intl.NumberFormat('00', _localeName);
......@@ -183,6 +248,13 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
/// If the number is less than 10, zero-pads it.
intl.NumberFormat _twoDigitZeroPaddedFormat;
/// Full unabbreviated year format, e.g. 2017 rather than 17.
intl.DateFormat _fullYearFormat;
intl.DateFormat _mediumDateFormat;
intl.DateFormat _yearMonthFormat;
static String _computeLocaleName(Locale locale) {
final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
return intl.Intl.canonicalizedLocale(localeName);
......@@ -225,6 +297,29 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
return _twoDigitZeroPaddedFormat.format(timeOfDay.minute);
}
@override
String formatYear(DateTime date) {
return _fullYearFormat.format(date);
}
@override
String formatMediumDate(DateTime date) {
return _mediumDateFormat.format(date);
}
@override
String formatMonthYear(DateTime date) {
return _yearMonthFormat.format(date);
}
@override
List<String> get narrowWeekDays {
return _fullYearFormat.dateSymbols.NARROWWEEKDAYS;
}
@override
int get firstDayOfWeekIndex => (_fullYearFormat.dateSymbols.FIRSTDAYOFWEEK + 1) % 7;
/// Formats a [number] using local decimal number format.
///
/// Inserts locale-appropriate thousands separator, if necessary.
......@@ -415,3 +510,20 @@ const Map<String, TimeOfDayFormat> _icuTimeOfDayToEnum = const <String, TimeOfDa
'a h:mm': TimeOfDayFormat.a_space_h_colon_mm,
'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
};
/// Tracks if date i18n data has been loaded.
bool _dateIntlDataInitialized = false;
/// Loads i18n data for dates if it hasn't be loaded yet.
///
/// Only the first invocation of this function has the effect of loading the
/// data. Subsequent invocations have no effect.
void _loadDateIntlDataIfNotLoaded() {
if (!_dateIntlDataInitialized) {
// The returned Future is intentionally dropped on the floor. The
// function only returns it to be compatible with the async counterparts.
// The Future has no value otherwise.
intl_local_date_data.initializeDateFormatting();
_dateIntlDataInitialized = true;
}
}
// Copyright 2015 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_test/flutter_test.dart';
import 'package:flutter/material.dart';
void main() {
testWidgets('Can select a day', (WidgetTester tester) async {
DateTime currentValue;
final Widget widget = new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new ListView(
children: <Widget>[
new MonthPicker(
selectedDate: new DateTime.utc(2015, 6, 9, 7, 12),
firstDate: new DateTime.utc(2013),
lastDate: new DateTime.utc(2018),
onChanged: (DateTime dateTime) {
currentValue = dateTime;
},
),
],
),
),
);
await tester.pumpWidget(widget);
expect(currentValue, isNull);
await tester.tap(find.text('2015'));
await tester.pumpWidget(widget);
await tester.tap(find.text('2014'));
await tester.pumpWidget(widget);
expect(currentValue, equals(new DateTime(2014, 6, 9)));
await tester.tap(find.text('30'));
expect(currentValue, equals(new DateTime(2013, 1, 30)));
}, skip: true);
}
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