diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index e88577e439..4917058631 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -26,7 +26,18 @@ import 'material.dart'; import 'theme.dart'; import 'typography.dart'; -enum _DatePickerMode { day, year } +/// Date picker UI mode for either showing a list of available years or a +/// monthly calendar. +/// +/// Also see: +/// +/// * +enum DatePickerMode { + /// Show a date picker UI for choosing a month and day. + day, + /// Show a date picker UI for choosing a year. + year, +} const double _kDatePickerHeaderPortraitHeight = 100.0; const double _kDatePickerHeaderLandscapeWidth = 168.0; @@ -57,11 +68,11 @@ class _DatePickerHeader extends StatelessWidget { super(key: key); final DateTime selectedDate; - final _DatePickerMode mode; - final ValueChanged<_DatePickerMode> onModeChanged; + final DatePickerMode mode; + final ValueChanged onModeChanged; final Orientation orientation; - void _handleChangeMode(_DatePickerMode value) { + void _handleChangeMode(DatePickerMode value) { if (value != mode) onModeChanged(value); } @@ -74,12 +85,12 @@ class _DatePickerHeader extends StatelessWidget { Color yearColor; switch(themeData.primaryColorBrightness) { case Brightness.light: - dayColor = mode == _DatePickerMode.day ? Colors.black87 : Colors.black54; - yearColor = mode == _DatePickerMode.year ? Colors.black87 : Colors.black54; + dayColor = mode == DatePickerMode.day ? Colors.black87 : Colors.black54; + yearColor = mode == DatePickerMode.year ? Colors.black87 : Colors.black54; break; case Brightness.dark: - dayColor = mode == _DatePickerMode.day ? Colors.white : Colors.white70; - yearColor = mode == _DatePickerMode.year ? Colors.white : Colors.white70; + dayColor = mode == DatePickerMode.day ? Colors.white : Colors.white70; + yearColor = mode == DatePickerMode.year ? Colors.white : Colors.white70; break; } final TextStyle dayStyle = headerTextTheme.display1.copyWith(color: dayColor, height: 1.4); @@ -114,17 +125,17 @@ class _DatePickerHeader extends StatelessWidget { Widget yearButton = new _DateHeaderButton( color: backgroundColor, - onTap: Feedback.wrapForTap(() => _handleChangeMode(_DatePickerMode.year), context), + onTap: Feedback.wrapForTap(() => _handleChangeMode(DatePickerMode.year), context), child: new Text(new DateFormat('yyyy').format(selectedDate), style: yearStyle), ); Widget dayButton = new _DateHeaderButton( color: backgroundColor, - onTap: Feedback.wrapForTap(() => _handleChangeMode(_DatePickerMode.day), context), + onTap: Feedback.wrapForTap(() => _handleChangeMode(DatePickerMode.day), context), child: new Text(new DateFormat('E, MMM\u00a0d').format(selectedDate), style: dayStyle), ); // Disable the button for the current mode. - if (mode == _DatePickerMode.day) + if (mode == DatePickerMode.day) dayButton = new IgnorePointer(child: dayButton); else yearButton = new IgnorePointer(child: yearButton); @@ -658,12 +669,14 @@ class _DatePickerDialog extends StatefulWidget { this.firstDate, this.lastDate, this.selectableDayPredicate, + this.initialDatePickerMode, }) : super(key: key); final DateTime initialDate; final DateTime firstDate; final DateTime lastDate; final SelectableDayPredicate selectableDayPredicate; + final DatePickerMode initialDatePickerMode; @override _DatePickerDialogState createState() => new _DatePickerDialogState(); @@ -674,10 +687,11 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { void initState() { super.initState(); _selectedDate = widget.initialDate; + _mode = widget.initialDatePickerMode; } DateTime _selectedDate; - _DatePickerMode _mode = _DatePickerMode.day; + DatePickerMode _mode; final GlobalKey _pickerKey = new GlobalKey(); void _vibrate() { @@ -691,7 +705,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { } } - void _handleModeChanged(_DatePickerMode mode) { + void _handleModeChanged(DatePickerMode mode) { _vibrate(); setState(() { _mode = mode; @@ -701,7 +715,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { void _handleYearChanged(DateTime value) { _vibrate(); setState(() { - _mode = _DatePickerMode.day; + _mode = DatePickerMode.day; _selectedDate = value; }); } @@ -724,7 +738,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { Widget _buildPicker() { assert(_mode != null); switch (_mode) { - case _DatePickerMode.day: + case DatePickerMode.day: return new MonthPicker( key: _pickerKey, selectedDate: _selectedDate, @@ -732,9 +746,9 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { firstDate: widget.firstDate, lastDate: widget.lastDate, selectableDayPredicate: widget.selectableDayPredicate, - onMonthHeaderTap: () { _handleModeChanged(_DatePickerMode.year); }, + onMonthHeaderTap: () { _handleModeChanged(DatePickerMode.year); }, ); - case _DatePickerMode.year: + case DatePickerMode.year: return new YearPicker( key: _pickerKey, selectedDate: _selectedDate, @@ -831,6 +845,10 @@ typedef bool SelectableDayPredicate(DateTime day); /// the days to enable for selection. If provided, only the days that /// [selectableDayPredicate] returned true for will be selectable. /// +/// An optional [initialDatePickerMode] argument can be used to display the +/// date picker initially in the year or month+day picker mode. It defaults +/// to month+day, but cannot be null. +/// /// See also: /// /// * [showTimePicker] @@ -841,6 +859,7 @@ Future showDatePicker({ @required DateTime firstDate, @required DateTime lastDate, SelectableDayPredicate selectableDayPredicate, + DatePickerMode initialDatePickerMode: DatePickerMode.day, }) async { assert(!initialDate.isBefore(firstDate), 'initialDate must be on or after firstDate'); assert(!initialDate.isAfter(lastDate), 'initialDate must be on or before lastDate'); @@ -849,6 +868,7 @@ Future showDatePicker({ selectableDayPredicate == null || selectableDayPredicate(initialDate), 'Provided initialDate must satisfy provided selectableDayPredicate' ); + assert(initialDatePickerMode != null, 'initialDatePickerMode cannot be null'); return await showDialog( context: context, child: new _DatePickerDialog( @@ -856,6 +876,7 @@ Future showDatePicker({ firstDate: firstDate, lastDate: lastDate, selectableDayPredicate: selectableDayPredicate, + initialDatePickerMode: initialDatePickerMode, ) ); } diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index bfd0aecd9b..bee812ec10 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -13,11 +13,14 @@ void main() { DateTime lastDate; DateTime initialDate; SelectableDayPredicate selectableDayPredicate; + DatePickerMode initialDatePickerMode; setUp(() { firstDate = new DateTime(2001, DateTime.JANUARY, 1); lastDate = new DateTime(2031, DateTime.DECEMBER, 31); initialDate = new DateTime(2016, DateTime.JANUARY, 15); + selectableDayPredicate = null; + initialDatePickerMode = null; }); testWidgets('tap-select a day', (WidgetTester tester) async { @@ -138,13 +141,25 @@ void main() { await tester.tap(find.text('Go')); expect(buttonContext, isNotNull); - final Future date = showDatePicker( - context: buttonContext, - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate, - selectableDayPredicate: selectableDayPredicate - ); + final Future date = initialDatePickerMode == null + // Exercise the argument default for initialDatePickerMode. + ? + showDatePicker( + context: buttonContext, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + selectableDayPredicate: selectableDayPredicate, + ) + : + showDatePicker( + context: buttonContext, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + selectableDayPredicate: selectableDayPredicate, + initialDatePickerMode: initialDatePickerMode, + ); await tester.pumpAndSettle(const Duration(seconds: 1)); await callback(date); @@ -283,6 +298,19 @@ void main() { }); }); + testWidgets('Can select initial date picker mode', (WidgetTester tester) async { + initialDate = new DateTime(2014, DateTime.JANUARY, 15); + initialDatePickerMode = DatePickerMode.year; + await preparePicker(tester, (Future date) async { + await tester.pump(); + // 2018 wouldn't be available if the year picker wasn't showing. + // The initial current year is 2014. + await tester.tap(find.text('2018')); + await tester.tap(find.text('OK')); + expect(await date, equals(new DateTime(2018, DateTime.JANUARY, 15))); + }); + }); + group('haptic feedback', () { const Duration kHapticFeedbackInterval = const Duration(milliseconds: 10); FeedbackTester feedback;