Improve the year selector UI in the date picker (#11214)
Fixes https://github.com/flutter/flutter/issues/10917
This commit is contained in:
parent
6ac0f61234
commit
e967b4b3c5
@ -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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
@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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
@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 {
|
|||||||
/// 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> {
|
|||||||
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> {
|
|||||||
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() {
|
|||||||
expect(feedback.hapticCount, 2);
|
expect(feedback.hapticCount, 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('days in month', () {
|
test('days in month', () {
|
||||||
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user