diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index f4e3f8534a..91c5a44545 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -9,8 +9,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:intl/date_symbols.dart' as intl show DateSymbols; -import 'package:intl/intl.dart' as intl show DateFormat; import 'button.dart'; import 'button_bar.dart'; @@ -83,6 +81,7 @@ class _DatePickerHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final MaterialLocalizations localizations = MaterialLocalizations.of(context); final ThemeData themeData = Theme.of(context); final TextTheme headerTextTheme = themeData.primaryTextTheme; Color dayColor; @@ -130,12 +129,12 @@ class _DatePickerHeader extends StatelessWidget { Widget yearButton = new _DateHeaderButton( color: backgroundColor, onTap: Feedback.wrapForTap(() => _handleChangeMode(DatePickerMode.year), context), - child: new Text(new intl.DateFormat('yyyy').format(selectedDate), style: yearStyle), + child: new Text(localizations.formatYear(selectedDate), style: yearStyle), ); Widget dayButton = new _DateHeaderButton( color: backgroundColor, onTap: Feedback.wrapForTap(() => _handleChangeMode(DatePickerMode.day), context), - child: new Text(new intl.DateFormat('E, MMM\u00a0d').format(selectedDate), style: dayStyle), + child: new Text(localizations.formatMediumDate(selectedDate), style: dayStyle), ); // Disable the button for the current mode. @@ -275,12 +274,33 @@ class DayPicker extends StatelessWidget { /// Optional user supplied predicate function to customize selectable days. final SelectableDayPredicate selectableDayPredicate; - List _getDayHeaders(TextStyle headerStyle) { - final intl.DateFormat dateFormat = new intl.DateFormat(); - final intl.DateSymbols symbols = dateFormat.dateSymbols; - return symbols.NARROWWEEKDAYS.map((String weekDay) { - return new Center(child: new Text(weekDay, style: headerStyle)); - }).toList(growable: false); + /// Builds widgets showing abbreviated days of week. The first widget in the + /// returned list corresponds to the first day of week for the current locale. + /// + /// Examples: + /// + /// ``` + /// ┌ Sunday is the first day of week in the US (en_US) + /// | + /// S M T W T F S <-- the returned list contains these widgets + /// _ _ _ _ _ 1 2 + /// 3 4 5 6 7 8 9 + /// + /// ┌ But it's Monday in the UK (en_GB) + /// | + /// M T W T F S S <-- the returned list contains these widgets + /// _ _ _ _ 1 2 3 + /// 4 5 6 7 8 9 10 + /// ``` + List _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) { + final List result = []; + for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { + final String weekDay = localizations.narrowWeekDays[i]; + result.add(new Center(child: new Text(weekDay, style: headerStyle))); + if (i == (localizations.firstDayOfWeekIndex - 1) % 7) + break; + } + return result; } // Do not use this directly - call getDaysInMonth instead. @@ -301,18 +321,64 @@ class DayPicker extends StatelessWidget { return _kDaysInMonth[month - 1]; } + /// Computes the offset from the first day of week that the first day of the + /// [month] falls on. + /// + /// For example, September 1, 2017 falls on a Friday, which in the calendar + /// localized for United States English appears as: + /// + /// ``` + /// S M T W T F S + /// _ _ _ _ _ 1 2 + /// ``` + /// + /// The offset for the first day of the months is the number of leading blanks + /// in the calendar, i.e. 5. + /// + /// The same date localized for the Russian calendar has a different offset, + /// because the first day of week is Monday rather than Sunday: + /// + /// ``` + /// M T W T F S S + /// _ _ _ _ 1 2 3 + /// ``` + /// + /// So the offset is 4, rather than 5. + /// + /// This code consolidates the following: + /// + /// - [DateTime.weekday] provides a 1-based index into days of week, with 1 + /// falling on Monday. + /// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index + /// into the [MaterialLocalizations.narrowWeekDays] list. + /// - [MaterialLocalizations.narrowWeekDays] list provides localized names of + /// days of week, always starting with Sunday and ending with Saturday. + int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) { + // 0-based day of week, with 0 representing Monday. + final int weekDayFromMonday = new DateTime(year, month).weekday - 1; + // 0-based day of week, with 0 representing Sunday. + final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex; + // firstDayOfWeekFromSunday recomputed to be Monday-based + final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7; + // Number of days between the first day of week appearing on the calendar, + // and the day corresponding to the 1-st of the month. + return (weekDayFromMonday - firstDayOfWeekFromMonday) % 7; + } + @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); + final MaterialLocalizations localizations = MaterialLocalizations.of(context); final int year = displayedMonth.year; final int month = displayedMonth.month; final int daysInMonth = getDaysInMonth(year, month); - // This assumes a start day of SUNDAY, but could be changed. - final int firstWeekday = new DateTime(year, month).weekday % 7; + final int firstDayOffset = _computeFirstDayOffset(year, month, localizations); final List labels = []; - labels.addAll(_getDayHeaders(themeData.textTheme.caption)); - for (int i = 0; true; ++i) { - final int day = i - firstWeekday + 1; + labels.addAll(_getDayHeaders(themeData.textTheme.caption, localizations)); + for (int i = 0; true; i += 1) { + // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on + // a leap year. + final int day = i - firstDayOffset + 1; if (day > daysInMonth) break; if (day < 1) { @@ -370,7 +436,7 @@ class DayPicker extends StatelessWidget { child: new Center( child: new GestureDetector( onTap: onMonthHeaderTap != null ? Feedback.wrapForTap(onMonthHeaderTap, context) : null, - child: new Text(new intl.DateFormat('yMMMM').format(displayedMonth), + child: new Text(localizations.formatMonthYear(displayedMonth), style: themeData.textTheme.subhead, ), ), @@ -558,6 +624,7 @@ class _MonthPickerState extends State { @override Widget build(BuildContext context) { final TextDirection textDirection = Directionality.of(context); + final MaterialLocalizations localizations = MaterialLocalizations.of(context); return new SizedBox( width: _kMonthPickerPortraitWidth, height: _kMaxDayPickerHeight, @@ -576,7 +643,7 @@ class _MonthPickerState extends State { start: 8.0, child: new IconButton( icon: _getPreviousMonthIcon(textDirection), - tooltip: MaterialLocalizations.of(context).previousMonthTooltip, + tooltip: localizations.previousMonthTooltip, onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth, ), ), @@ -585,7 +652,7 @@ class _MonthPickerState extends State { end: 8.0, child: new IconButton( icon: _getNextMonthIcon(textDirection), - tooltip: MaterialLocalizations.of(context).nextMonthTooltip, + tooltip: localizations.nextMonthTooltip, onPressed: _isDisplayingLastMonth ? null : _handleNextMonth, ), ), @@ -882,6 +949,14 @@ typedef bool SelectableDayPredicate(DateTime day); /// date picker initially in the year or month+day picker mode. It defaults /// to month+day, and must not be null. /// +/// An optional [locale] argument can be used to set the locale for the date +/// picker. It defaults to the ambient locale provided by [Localizations]. +/// +/// An optional [textDirection] argument can be used to set the text direction +/// (RTL or LTR) for the date picker. It defaults to the ambient text direction +/// provided by [Directionality]. If both [locale] and [textDirection] are not +/// null, [textDirection] overrides the direction chosen for the [locale]. +/// /// See also: /// /// * [showTimePicker] @@ -893,6 +968,8 @@ Future showDatePicker({ @required DateTime lastDate, SelectableDayPredicate selectableDayPredicate, DatePickerMode initialDatePickerMode: DatePickerMode.day, + Locale locale, + TextDirection textDirection, }) async { assert(!initialDate.isBefore(firstDate), 'initialDate must be on or after firstDate'); assert(!initialDate.isAfter(lastDate), 'initialDate must be on or before lastDate'); @@ -902,14 +979,32 @@ Future showDatePicker({ 'Provided initialDate must satisfy provided selectableDayPredicate' ); assert(initialDatePickerMode != null, 'initialDatePickerMode must not be null'); - return await showDialog( + + Widget child = new _DatePickerDialog( + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + selectableDayPredicate: selectableDayPredicate, + initialDatePickerMode: initialDatePickerMode, + ); + + if (textDirection != null) { + child = new Directionality( + textDirection: textDirection, + child: child, + ); + } + + if (locale != null) { + child = new Localizations.override( + context: context, + locale: locale, + child: child, + ); + } + + return await showDialog( context: context, - child: new _DatePickerDialog( - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate, - selectableDayPredicate: selectableDayPredicate, - initialDatePickerMode: initialDatePickerMode, - ) + child: child, ); } diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart index 000cc46e54..690e060f70 100644 --- a/packages/flutter/lib/src/material/material_localizations.dart +++ b/packages/flutter/lib/src/material/material_localizations.dart @@ -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 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 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 _icuTimeOfDayToEnum = const [ + 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; + }, + onMonthHeaderTap: () { + headerTapped = true; + }, + ), + ], + ), + ), + ); + + await tester.pumpWidget(widget); + + expect(currentValue, isNull); + expect(headerTapped, false); + await tester.tap(find.text('June 2015')); + expect(headerTapped, true); + }); + Future preparePicker(WidgetTester tester, Future callback(Future date)) async { BuildContext buttonContext; await tester.pumpWidget(new MaterialApp( @@ -207,7 +240,10 @@ void main() { await tester.pump(); await tester.tap(find.text('2017')); await tester.pump(); - final String dayLabel = new DateFormat('E, MMM\u00a0d').format(new DateTime(2017, DateTime.JANUARY, 15)); + final MaterialLocalizations localizations = MaterialLocalizations.of( + tester.element(find.byType(DayPicker)) + ); + final String dayLabel = localizations.formatMediumDate(new DateTime(2017, DateTime.JANUARY, 15)); await tester.tap(find.text(dayLabel)); await tester.pump(); await tester.tap(find.text('19')); @@ -383,4 +419,250 @@ void main() { expect(await date, isNull); }); }); + + group(DayPicker, () { + final Map> testLocales = >{ + // Tests the default. + const Locale('en', 'US'): { + 'textDirection': TextDirection.ltr, + 'expectedDaysOfWeek': ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + 'expectedDaysOfMonth': new List.generate(30, (int i) => '${i + 1}'), + 'expectedMonthYearHeader': 'September 2017', + }, + // Tests a different first day of week. + const Locale('ru', 'RU'): { + 'textDirection': TextDirection.ltr, + 'expectedDaysOfWeek': ['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс'], + 'expectedDaysOfMonth': new List.generate(30, (int i) => '${i + 1}'), + 'expectedMonthYearHeader': 'сентябрь 2017 г.', + }, + // Tests RTL. + // TODO: change to Arabic numerals when these are fixed: + // TODO: https://github.com/dart-lang/intl/issues/143 + // TODO: https://github.com/flutter/flutter/issues/12289 + const Locale('ar', 'AR'): { + 'textDirection': TextDirection.rtl, + 'expectedDaysOfWeek': ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], + 'expectedDaysOfMonth': new List.generate(30, (int i) => '${i + 1}'), + 'expectedMonthYearHeader': 'سبتمبر 2017', + }, + }; + + for (Locale locale in testLocales.keys) { + testWidgets('shows dates for $locale', (WidgetTester tester) async { + final List expectedDaysOfWeek = testLocales[locale]['expectedDaysOfWeek']; + final List expectedDaysOfMonth = testLocales[locale]['expectedDaysOfMonth']; + final String expectedMonthYearHeader = testLocales[locale]['expectedMonthYearHeader']; + final TextDirection textDirection = testLocales[locale]['textDirection']; + final DateTime baseDate = new DateTime(2017, 9, 27); + + await _pumpBoilerplate(tester, new DayPicker( + selectedDate: baseDate, + currentDate: baseDate, + onChanged: (DateTime newValue) {}, + firstDate: baseDate.subtract(const Duration(days: 90)), + lastDate: baseDate.add(const Duration(days: 90)), + displayedMonth: baseDate, + ), locale: locale, textDirection: textDirection); + + expect(find.text(expectedMonthYearHeader), findsOneWidget); + + expectedDaysOfWeek.forEach((String dayOfWeek) { + expect(find.text(dayOfWeek), findsWidgets); + }); + + Offset previousCellOffset; + expectedDaysOfMonth.forEach((String dayOfMonth) { + final Finder dayCell = find.descendant(of: find.byType(GridView), matching: find.text(dayOfMonth)); + expect(dayCell, findsOneWidget); + + // Check that cells are correctly positioned relative to each other, + // taking text direction into account. + final Offset offset = tester.getCenter(dayCell); + if (previousCellOffset != null) { + if (textDirection == TextDirection.ltr) { + expect(offset.dx > previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true); + } else { + expect(offset.dx < previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true); + } + } + previousCellOffset = offset; + }); + }); + } + }); + + testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async { + await tester.pumpWidget(new MaterialApp( + locale: const Locale('en', 'US'), + supportedLocales: const [ + const Locale('en', 'US'), + const Locale('fr', 'CA'), + ], + home: new Material( + child: new Builder( + builder: (BuildContext context) { + return new FlatButton( + onPressed: () async { + await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + locale: const Locale('fr', 'CA'), + ); + }, + child: const Text('X'), + ); + }, + ), + ), + )); + + await tester.tap(find.text('X')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final Element dayPicker = tester.element(find.byType(DayPicker)); + expect( + Localizations.localeOf(dayPicker), + const Locale('fr', 'CA'), + ); + + expect( + Directionality.of(dayPicker), + TextDirection.ltr, + ); + + await tester.tap(find.text('ANNULER')); + }); + + testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async { + await tester.pumpWidget(new MaterialApp( + locale: const Locale('en', 'US'), + supportedLocales: const [ + const Locale('en', 'US'), + ], + home: new Material( + child: new Builder( + builder: (BuildContext context) { + return new FlatButton( + onPressed: () async { + await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + textDirection: TextDirection.rtl, + ); + }, + child: const Text('X'), + ); + }, + ), + ), + )); + + await tester.tap(find.text('X')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final Element dayPicker = tester.element(find.byType(DayPicker)); + expect( + Directionality.of(dayPicker), + TextDirection.rtl, + ); + + await tester.tap(find.text('CANCEL')); + }); + + testWidgets('textDirection parameter takes precendence over locale parameter', (WidgetTester tester) async { + await tester.pumpWidget(new MaterialApp( + locale: const Locale('en', 'US'), + supportedLocales: const [ + const Locale('en', 'US'), + const Locale('fr', 'CA'), + ], + home: new Material( + child: new Builder( + builder: (BuildContext context) { + return new FlatButton( + onPressed: () async { + await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + locale: const Locale('fr', 'CA'), + textDirection: TextDirection.rtl, + ); + }, + child: const Text('X'), + ); + }, + ), + ), + )); + + await tester.tap(find.text('X')); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + final Element dayPicker = tester.element(find.byType(DayPicker)); + expect( + Localizations.localeOf(dayPicker), + const Locale('fr', 'CA'), + ); + + expect( + Directionality.of(dayPicker), + TextDirection.rtl, + ); + + await tester.tap(find.text('ANNULER')); + }); +} + +Future _pumpBoilerplate( + WidgetTester tester, + Widget child, { + Locale locale = const Locale('en', 'US'), + TextDirection textDirection: TextDirection.ltr +}) async { + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Localizations( + locale: locale, + delegates: >[ + new _MaterialLocalizationsDelegate( + new DefaultMaterialLocalizations(locale), + ), + const DefaultWidgetsLocalizationsDelegate(), + ], + child: child, + ), + )); +} + +class _MaterialLocalizationsDelegate extends LocalizationsDelegate { + const _MaterialLocalizationsDelegate(this.localizations); + + final MaterialLocalizations localizations; + + @override + Future load(Locale locale) { + return new SynchronousFuture(localizations); + } + + @override + bool shouldReload(_MaterialLocalizationsDelegate old) => false; +} + +class DefaultWidgetsLocalizationsDelegate extends LocalizationsDelegate { + const DefaultWidgetsLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return new SynchronousFuture(new DefaultWidgetsLocalizations(locale)); + } + + @override + bool shouldReload(DefaultWidgetsLocalizationsDelegate old) => false; } diff --git a/packages/flutter/test/widgets/date_picker_test.dart b/packages/flutter/test/widgets/date_picker_test.dart deleted file mode 100644 index 7416d30010..0000000000 --- a/packages/flutter/test/widgets/date_picker_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -// 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: [ - 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); -}