Commit e967b4b3 authored by Jason Simmons's avatar Jason Simmons Committed by GitHub

Improve the year selector UI in the date picker (#11214)

Fixes https://github.com/flutter/flutter/issues/10917
parent 6ac0f612
...@@ -22,6 +22,7 @@ import 'flat_button.dart'; ...@@ -22,6 +22,7 @@ import 'flat_button.dart';
import 'icon_button.dart'; import 'icon_button.dart';
import 'icons.dart'; import 'icons.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart';
import 'theme.dart'; import 'theme.dart';
import 'typography.dart'; import 'typography.dart';
...@@ -101,16 +102,33 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -101,16 +102,33 @@ class _DatePickerHeader extends StatelessWidget {
switch (orientation) { switch (orientation) {
case Orientation.portrait: case Orientation.portrait:
height = _kDatePickerHeaderPortraitHeight; height = _kDatePickerHeaderPortraitHeight;
padding = const EdgeInsets.symmetric(horizontal: 24.0); padding = const EdgeInsets.symmetric(horizontal: 16.0);
mainAxisAlignment = MainAxisAlignment.center; mainAxisAlignment = MainAxisAlignment.center;
break; break;
case Orientation.landscape: case Orientation.landscape:
width = _kDatePickerHeaderLandscapeWidth; width = _kDatePickerHeaderLandscapeWidth;
padding = const EdgeInsets.all(16.0); padding = const EdgeInsets.all(8.0);
mainAxisAlignment = MainAxisAlignment.start; mainAxisAlignment = MainAxisAlignment.start;
break; break;
} }
Widget yearButton = new _DateHeaderButton(
color: backgroundColor,
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),
child: new Text(new DateFormat('E, MMM\u00a0d').format(selectedDate), style: dayStyle),
);
// Disable the button for the current mode.
if (mode == _DatePickerMode.day)
dayButton = new IgnorePointer(child: dayButton);
else
yearButton = new IgnorePointer(child: yearButton);
return new Container( return new Container(
width: width, width: width,
height: height, height: height,
...@@ -119,16 +137,40 @@ class _DatePickerHeader extends StatelessWidget { ...@@ -119,16 +137,40 @@ class _DatePickerHeader extends StatelessWidget {
child: new Column( child: new Column(
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[yearButton, dayButton],
new GestureDetector(
onTap: Feedback.wrapForTap(() => _handleChangeMode(_DatePickerMode.year), context),
child: new Text(new DateFormat('yyyy').format(selectedDate), style: yearStyle),
), ),
new GestureDetector( );
onTap: Feedback.wrapForTap(() => _handleChangeMode(_DatePickerMode.day), context), }
child: new Text(new DateFormat('E, MMM\u00a0d').format(selectedDate), style: dayStyle), }
class _DateHeaderButton extends StatelessWidget {
_DateHeaderButton({
Key key,
this.onTap,
this.color,
this.child,
}) : super(key: key);
final VoidCallback onTap;
final Color color;
final Widget child;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return new Material(
type: MaterialType.button,
color: color,
child: new InkWell(
borderRadius: kMaterialEdges[MaterialType.button],
highlightColor: theme.highlightColor,
splashColor: theme.splashColor,
onTap: onTap,
child: new Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: child,
), ),
],
), ),
); );
} }
...@@ -181,6 +223,7 @@ class DayPicker extends StatelessWidget { ...@@ -181,6 +223,7 @@ class DayPicker extends StatelessWidget {
@required this.firstDate, @required this.firstDate,
@required this.lastDate, @required this.lastDate,
@required this.displayedMonth, @required this.displayedMonth,
this.onMonthHeaderTap,
this.selectableDayPredicate, this.selectableDayPredicate,
}) : assert(selectedDate != null), }) : assert(selectedDate != null),
assert(currentDate != null), assert(currentDate != null),
...@@ -201,6 +244,9 @@ class DayPicker extends StatelessWidget { ...@@ -201,6 +244,9 @@ class DayPicker extends StatelessWidget {
/// Called when the user picks a day. /// Called when the user picks a day.
final ValueChanged<DateTime> onChanged; final ValueChanged<DateTime> onChanged;
/// Called when the user taps on the header that displays the current month.
final VoidCallback onMonthHeaderTap;
/// The earliest date the user is permitted to pick. /// The earliest date the user is permitted to pick.
final DateTime firstDate; final DateTime firstDate;
...@@ -300,11 +346,14 @@ class DayPicker extends StatelessWidget { ...@@ -300,11 +346,14 @@ class DayPicker extends StatelessWidget {
new Container( new Container(
height: _kDayPickerRowHeight, height: _kDayPickerRowHeight,
child: new Center( child: new Center(
child: new GestureDetector(
onTap: onMonthHeaderTap != null ? Feedback.wrapForTap(onMonthHeaderTap, context) : null,
child: new Text(new DateFormat('yMMMM').format(displayedMonth), child: new Text(new DateFormat('yMMMM').format(displayedMonth),
style: themeData.textTheme.subhead, style: themeData.textTheme.subhead,
), ),
), ),
), ),
),
new Flexible( new Flexible(
child: new GridView.custom( child: new GridView.custom(
gridDelegate: _kDayPickerGridDelegate, gridDelegate: _kDayPickerGridDelegate,
...@@ -341,6 +390,7 @@ class MonthPicker extends StatefulWidget { ...@@ -341,6 +390,7 @@ class MonthPicker extends StatefulWidget {
@required this.firstDate, @required this.firstDate,
@required this.lastDate, @required this.lastDate,
this.selectableDayPredicate, this.selectableDayPredicate,
this.onMonthHeaderTap,
}) : assert(selectedDate != null), }) : assert(selectedDate != null),
assert(onChanged != null), assert(onChanged != null),
assert(!firstDate.isAfter(lastDate)), assert(!firstDate.isAfter(lastDate)),
...@@ -355,6 +405,9 @@ class MonthPicker extends StatefulWidget { ...@@ -355,6 +405,9 @@ class MonthPicker extends StatefulWidget {
/// Called when the user picks a month. /// Called when the user picks a month.
final ValueChanged<DateTime> onChanged; final ValueChanged<DateTime> onChanged;
/// Called when the user taps on the header that displays the current month.
final VoidCallback onMonthHeaderTap;
/// The earliest date the user is permitted to pick. /// The earliest date the user is permitted to pick.
final DateTime firstDate; final DateTime firstDate;
...@@ -426,6 +479,7 @@ class _MonthPickerState extends State<MonthPicker> { ...@@ -426,6 +479,7 @@ class _MonthPickerState extends State<MonthPicker> {
lastDate: widget.lastDate, lastDate: widget.lastDate,
displayedMonth: month, displayedMonth: month,
selectableDayPredicate: widget.selectableDayPredicate, selectableDayPredicate: widget.selectableDayPredicate,
onMonthHeaderTap: widget.onMonthHeaderTap,
); );
} }
...@@ -672,6 +726,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> { ...@@ -672,6 +726,7 @@ class _DatePickerDialogState extends State<_DatePickerDialog> {
firstDate: widget.firstDate, firstDate: widget.firstDate,
lastDate: widget.lastDate, lastDate: widget.lastDate,
selectableDayPredicate: widget.selectableDayPredicate, selectableDayPredicate: widget.selectableDayPredicate,
onMonthHeaderTap: () { _handleModeChanged(_DatePickerMode.year); },
); );
case _DatePickerMode.year: case _DatePickerMode.year:
return new YearPicker( return new YearPicker(
......
...@@ -337,6 +337,7 @@ void main() { ...@@ -337,6 +337,7 @@ void main() {
expect(feedback.hapticCount, 2); expect(feedback.hapticCount, 2);
}); });
}); });
}); });
test('days in month', () { test('days in month', () {
...@@ -347,4 +348,17 @@ void main() { ...@@ -347,4 +348,17 @@ void main() {
expect(DayPicker.getDaysInMonth(2000, 2), 29); expect(DayPicker.getDaysInMonth(2000, 2), 29);
expect(DayPicker.getDaysInMonth(1900, 2), 28); expect(DayPicker.getDaysInMonth(1900, 2), 28);
}); });
testWidgets('month header tap', (WidgetTester tester) async {
selectableDayPredicate = null;
await preparePicker(tester, (Future<DateTime> date) async {
// Switch into the year selector.
await tester.tap(find.text('January 2016'));
await tester.pump();
expect(find.text('2020'), isNotNull);
await tester.tap(find.text('CANCEL'));
expect(await date, isNull);
});
});
} }
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