mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Allow date pickers to not have selected date (#132343)
This enables our various date picker classes to have a null `initialDate`. It also fixes the logic of some of the widgets which used to do something when you _changed_ the `initial*` parameters, which is wrong for `initial*` properties (they by definition should only impact the initial state) and wrong for properties in general (behaviour should not change based on whether the widget was built with a new value or not, that violates the reactive design principles). Fixes https://github.com/flutter/flutter/issues/638.
This commit is contained in:
parent
3f34b480c8
commit
d19fb632ec
@ -62,7 +62,7 @@ class _DateTimePicker extends StatelessWidget {
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: selectedDate!,
|
||||
initialDate: selectedDate,
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
@ -120,9 +120,9 @@ class DateAndTimePickerDemo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo> {
|
||||
DateTime _fromDate = DateTime.now();
|
||||
DateTime? _fromDate = DateTime.now();
|
||||
TimeOfDay _fromTime = const TimeOfDay(hour: 7, minute: 28);
|
||||
DateTime _toDate = DateTime.now();
|
||||
DateTime? _toDate = DateTime.now();
|
||||
TimeOfDay _toTime = const TimeOfDay(hour: 8, minute: 28);
|
||||
final List<String> _allActivities = <String>['hiking', 'swimming', 'boating', 'fishing'];
|
||||
String? _activity = 'fishing';
|
||||
|
@ -60,8 +60,9 @@ const double _monthNavButtonsWidth = 108.0;
|
||||
class CalendarDatePicker extends StatefulWidget {
|
||||
/// Creates a calendar date picker.
|
||||
///
|
||||
/// It will display a grid of days for the [initialDate]'s month. The day
|
||||
/// indicated by [initialDate] will be selected.
|
||||
/// It will display a grid of days for the [initialDate]'s month, or, if that
|
||||
/// is null, the [currentDate]'s month. The day indicated by [initialDate] will
|
||||
/// be selected if it is not null.
|
||||
///
|
||||
/// The optional [onDisplayedMonthChanged] callback can be used to track
|
||||
/// the currently displayed month.
|
||||
@ -71,23 +72,20 @@ class CalendarDatePicker extends StatefulWidget {
|
||||
/// to start in the year selection interface with [initialCalendarMode] set
|
||||
/// to [DatePickerMode.year].
|
||||
///
|
||||
/// The [initialDate], [firstDate], [lastDate], [onDateChanged], and
|
||||
/// [initialCalendarMode] must be non-null.
|
||||
/// The [lastDate] must be after or equal to [firstDate].
|
||||
///
|
||||
/// [lastDate] must be after or equal to [firstDate].
|
||||
/// The [initialDate], if provided, must be between [firstDate] and [lastDate]
|
||||
/// or equal to one of them.
|
||||
///
|
||||
/// [initialDate] must be between [firstDate] and [lastDate] or equal to
|
||||
/// one of them.
|
||||
///
|
||||
/// [currentDate] represents the current day (i.e. today). This
|
||||
/// The [currentDate] represents the current day (i.e. today). This
|
||||
/// date will be highlighted in the day grid. If null, the date of
|
||||
/// `DateTime.now()` will be used.
|
||||
///
|
||||
/// If [selectableDayPredicate] is non-null, it must return `true` for the
|
||||
/// [initialDate].
|
||||
/// If [selectableDayPredicate] and [initialDate] are both non-null,
|
||||
/// [selectableDayPredicate] must return `true` for the [initialDate].
|
||||
CalendarDatePicker({
|
||||
super.key,
|
||||
required DateTime initialDate,
|
||||
required DateTime? initialDate,
|
||||
required DateTime firstDate,
|
||||
required DateTime lastDate,
|
||||
DateTime? currentDate,
|
||||
@ -95,7 +93,7 @@ class CalendarDatePicker extends StatefulWidget {
|
||||
this.onDisplayedMonthChanged,
|
||||
this.initialCalendarMode = DatePickerMode.day,
|
||||
this.selectableDayPredicate,
|
||||
}) : initialDate = DateUtils.dateOnly(initialDate),
|
||||
}) : initialDate = initialDate == null ? null : DateUtils.dateOnly(initialDate),
|
||||
firstDate = DateUtils.dateOnly(firstDate),
|
||||
lastDate = DateUtils.dateOnly(lastDate),
|
||||
currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()) {
|
||||
@ -104,21 +102,26 @@ class CalendarDatePicker extends StatefulWidget {
|
||||
'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.',
|
||||
);
|
||||
assert(
|
||||
!this.initialDate.isBefore(this.firstDate),
|
||||
this.initialDate == null || !this.initialDate!.isBefore(this.firstDate),
|
||||
'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.',
|
||||
);
|
||||
assert(
|
||||
!this.initialDate.isAfter(this.lastDate),
|
||||
this.initialDate == null || !this.initialDate!.isAfter(this.lastDate),
|
||||
'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.',
|
||||
);
|
||||
assert(
|
||||
selectableDayPredicate == null || selectableDayPredicate!(this.initialDate),
|
||||
selectableDayPredicate == null || this.initialDate == null || selectableDayPredicate!(this.initialDate!),
|
||||
'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate.',
|
||||
);
|
||||
}
|
||||
|
||||
/// The initially selected [DateTime] that the picker should display.
|
||||
final DateTime initialDate;
|
||||
///
|
||||
/// Subsequently changing this has no effect. To change the selected date,
|
||||
/// change the [key] to create a new instance of the [CalendarDatePicker], and
|
||||
/// provide that widget the new [initialDate]. This will reset the widget's
|
||||
/// interactive state.
|
||||
final DateTime? initialDate;
|
||||
|
||||
/// The earliest allowable [DateTime] that the user can select.
|
||||
final DateTime firstDate;
|
||||
@ -136,6 +139,11 @@ class CalendarDatePicker extends StatefulWidget {
|
||||
final ValueChanged<DateTime>? onDisplayedMonthChanged;
|
||||
|
||||
/// The initial display of the calendar picker.
|
||||
///
|
||||
/// Subsequently changing this has no effect. To change the calendar mode,
|
||||
/// change the [key] to create a new instance of the [CalendarDatePicker], and
|
||||
/// provide that widget a new [initialCalendarMode]. This will reset the
|
||||
/// widget's interactive state.
|
||||
final DatePickerMode initialCalendarMode;
|
||||
|
||||
/// Function to provide full control over which dates in the calendar can be selected.
|
||||
@ -149,7 +157,7 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
bool _announcedInitialDate = false;
|
||||
late DatePickerMode _mode;
|
||||
late DateTime _currentDisplayedMonthDate;
|
||||
late DateTime _selectedDate;
|
||||
DateTime? _selectedDate;
|
||||
final GlobalKey _monthPickerKey = GlobalKey();
|
||||
final GlobalKey _yearPickerKey = GlobalKey();
|
||||
late MaterialLocalizations _localizations;
|
||||
@ -159,18 +167,9 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_mode = widget.initialCalendarMode;
|
||||
_currentDisplayedMonthDate = DateTime(widget.initialDate.year, widget.initialDate.month);
|
||||
_selectedDate = widget.initialDate;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CalendarDatePicker oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.initialCalendarMode != oldWidget.initialCalendarMode) {
|
||||
_mode = widget.initialCalendarMode;
|
||||
}
|
||||
if (!DateUtils.isSameDay(widget.initialDate, oldWidget.initialDate)) {
|
||||
_currentDisplayedMonthDate = DateTime(widget.initialDate.year, widget.initialDate.month);
|
||||
final DateTime currentDisplayedDate = widget.initialDate ?? widget.currentDate;
|
||||
_currentDisplayedMonthDate = DateTime(currentDisplayedDate.year, currentDisplayedDate.month);
|
||||
if (widget.initialDate != null) {
|
||||
_selectedDate = widget.initialDate;
|
||||
}
|
||||
}
|
||||
@ -183,12 +182,13 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
assert(debugCheckHasDirectionality(context));
|
||||
_localizations = MaterialLocalizations.of(context);
|
||||
_textDirection = Directionality.of(context);
|
||||
if (!_announcedInitialDate) {
|
||||
if (!_announcedInitialDate && widget.initialDate != null) {
|
||||
assert(_selectedDate != null);
|
||||
_announcedInitialDate = true;
|
||||
final bool isToday = DateUtils.isSameDay(widget.currentDate, _selectedDate);
|
||||
final String semanticLabelSuffix = isToday ? ', ${_localizations.currentDateLabel}' : '';
|
||||
SemanticsService.announce(
|
||||
'${_localizations.formatFullDate(_selectedDate)}$semanticLabelSuffix',
|
||||
'${_localizations.formatFullDate(_selectedDate!)}$semanticLabelSuffix',
|
||||
_textDirection,
|
||||
);
|
||||
}
|
||||
@ -211,16 +211,18 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
_vibrate();
|
||||
setState(() {
|
||||
_mode = mode;
|
||||
if (_mode == DatePickerMode.day) {
|
||||
SemanticsService.announce(
|
||||
_localizations.formatMonthYear(_selectedDate),
|
||||
_textDirection,
|
||||
);
|
||||
} else {
|
||||
SemanticsService.announce(
|
||||
_localizations.formatYear(_selectedDate),
|
||||
_textDirection,
|
||||
);
|
||||
if (_selectedDate != null) {
|
||||
if (_mode == DatePickerMode.day) {
|
||||
SemanticsService.announce(
|
||||
_localizations.formatMonthYear(_selectedDate!),
|
||||
_textDirection,
|
||||
);
|
||||
} else {
|
||||
SemanticsService.announce(
|
||||
_localizations.formatYear(_selectedDate!),
|
||||
_textDirection,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -238,7 +240,7 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
_vibrate();
|
||||
|
||||
final int daysInMonth = DateUtils.getDaysInMonth(value.year, value.month);
|
||||
final int preferredDay = math.min(_selectedDate.day, daysInMonth);
|
||||
final int preferredDay = math.min(_selectedDate?.day ?? 1, daysInMonth);
|
||||
value = value.copyWith(day: preferredDay);
|
||||
|
||||
if (value.isBefore(widget.firstDate)) {
|
||||
@ -253,7 +255,7 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
|
||||
if (_isSelectable(value)) {
|
||||
_selectedDate = value;
|
||||
widget.onDateChanged(_selectedDate);
|
||||
widget.onDateChanged(_selectedDate!);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -262,7 +264,7 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
_vibrate();
|
||||
setState(() {
|
||||
_selectedDate = value;
|
||||
widget.onDateChanged(_selectedDate);
|
||||
widget.onDateChanged(_selectedDate!);
|
||||
});
|
||||
}
|
||||
|
||||
@ -292,7 +294,6 @@ class _CalendarDatePickerState extends State<CalendarDatePicker> {
|
||||
currentDate: widget.currentDate,
|
||||
firstDate: widget.firstDate,
|
||||
lastDate: widget.lastDate,
|
||||
initialDate: _currentDisplayedMonthDate,
|
||||
selectedDate: _currentDisplayedMonthDate,
|
||||
onChanged: _handleYearChanged,
|
||||
),
|
||||
@ -452,10 +453,15 @@ class _MonthPicker extends StatefulWidget {
|
||||
required this.onDisplayedMonthChanged,
|
||||
this.selectableDayPredicate,
|
||||
}) : assert(!firstDate.isAfter(lastDate)),
|
||||
assert(!selectedDate.isBefore(firstDate)),
|
||||
assert(!selectedDate.isAfter(lastDate));
|
||||
assert(selectedDate == null || !selectedDate.isBefore(firstDate)),
|
||||
assert(selectedDate == null || !selectedDate.isAfter(lastDate));
|
||||
|
||||
/// The initial month to display.
|
||||
///
|
||||
/// Subsequently changing this has no effect. To change the selected month,
|
||||
/// change the [key] to create a new instance of the [_MonthPicker], and
|
||||
/// provide that widget the new [initialMonth]. This will reset the widget's
|
||||
/// interactive state.
|
||||
final DateTime initialMonth;
|
||||
|
||||
/// The current date.
|
||||
@ -476,7 +482,7 @@ class _MonthPicker extends StatefulWidget {
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DateTime selectedDate;
|
||||
final DateTime? selectedDate;
|
||||
|
||||
/// Called when the user picks a day.
|
||||
final ValueChanged<DateTime> onChanged;
|
||||
@ -528,17 +534,6 @@ class _MonthPickerState extends State<_MonthPicker> {
|
||||
_textDirection = Directionality.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(_MonthPicker oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.initialMonth != oldWidget.initialMonth && widget.initialMonth != _currentMonth) {
|
||||
// We can't interrupt this widget build with a scroll, so do it next frame
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(Duration timeStamp) => _showMonth(widget.initialMonth, jump: true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
@ -834,13 +829,13 @@ class _DayPicker extends StatefulWidget {
|
||||
required this.onChanged,
|
||||
this.selectableDayPredicate,
|
||||
}) : assert(!firstDate.isAfter(lastDate)),
|
||||
assert(!selectedDate.isBefore(firstDate)),
|
||||
assert(!selectedDate.isAfter(lastDate));
|
||||
assert(selectedDate == null || !selectedDate.isBefore(firstDate)),
|
||||
assert(selectedDate == null || !selectedDate.isAfter(lastDate));
|
||||
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DateTime selectedDate;
|
||||
final DateTime? selectedDate;
|
||||
|
||||
/// The current date at the time the picker is displayed.
|
||||
final DateTime currentDate;
|
||||
@ -1105,13 +1100,18 @@ class YearPicker extends StatefulWidget {
|
||||
DateTime? currentDate,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
@Deprecated(
|
||||
'This parameter has no effect and can be removed. Previously it controlled '
|
||||
'the month that was used in "onChanged" when a new year was selected, but '
|
||||
'now that role is filled by "selectedDate" instead. '
|
||||
'This feature was deprecated after v3.13.0-0.3.pre.'
|
||||
)
|
||||
DateTime? initialDate,
|
||||
required this.selectedDate,
|
||||
required this.onChanged,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
}) : assert(!firstDate.isAfter(lastDate)),
|
||||
currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
|
||||
initialDate = DateUtils.dateOnly(initialDate ?? selectedDate);
|
||||
currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now());
|
||||
|
||||
/// The current date.
|
||||
///
|
||||
@ -1124,13 +1124,10 @@ class YearPicker extends StatefulWidget {
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// The initial date to center the year display around.
|
||||
final DateTime initialDate;
|
||||
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DateTime selectedDate;
|
||||
final DateTime? selectedDate;
|
||||
|
||||
/// Called when the user picks a year.
|
||||
final ValueChanged<DateTime> onChanged;
|
||||
@ -1151,14 +1148,14 @@ class _YearPickerState extends State<YearPicker> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController(initialScrollOffset: _scrollOffsetForYear(widget.selectedDate));
|
||||
_scrollController = ScrollController(initialScrollOffset: _scrollOffsetForYear(widget.selectedDate ?? widget.firstDate));
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(YearPicker oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.selectedDate != oldWidget.selectedDate) {
|
||||
_scrollController.jumpTo(_scrollOffsetForYear(widget.selectedDate));
|
||||
if (widget.selectedDate != oldWidget.selectedDate && widget.selectedDate != null) {
|
||||
_scrollController.jumpTo(_scrollOffsetForYear(widget.selectedDate!));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1189,7 +1186,7 @@ class _YearPickerState extends State<YearPicker> {
|
||||
// Backfill the _YearPicker with disabled years if necessary.
|
||||
final int offset = _itemCount < minYears ? (minYears - _itemCount) ~/ 2 : 0;
|
||||
final int year = widget.firstDate.year + index - offset;
|
||||
final bool isSelected = year == widget.selectedDate.year;
|
||||
final bool isSelected = year == widget.selectedDate?.year;
|
||||
final bool isCurrentYear = year == widget.currentDate.year;
|
||||
final bool isDisabled = year < widget.firstDate.year || year > widget.lastDate.year;
|
||||
const double decorationHeight = 36.0;
|
||||
@ -1241,9 +1238,19 @@ class _YearPickerState extends State<YearPicker> {
|
||||
child: yearItem,
|
||||
);
|
||||
} else {
|
||||
DateTime date = DateTime(year, widget.selectedDate?.month ?? DateTime.january);
|
||||
if (date.isBefore(DateTime(widget.firstDate.year, widget.firstDate.month))) {
|
||||
// Ignore firstDate.day because we're just working in years and months here.
|
||||
assert(date.year == widget.firstDate.year);
|
||||
date = DateTime(year, widget.firstDate.month);
|
||||
} else if (date.isAfter(widget.lastDate)) {
|
||||
// No need to ignore the day here because it can only be bigger than what we care about.
|
||||
assert(date.year == widget.lastDate.year);
|
||||
date = DateTime(year, widget.lastDate.month);
|
||||
}
|
||||
yearItem = InkWell(
|
||||
key: ValueKey<int>(year),
|
||||
onTap: () => widget.onChanged(DateTime(year, widget.initialDate.month)),
|
||||
onTap: () => widget.onChanged(date),
|
||||
statesController: MaterialStatesController(states),
|
||||
overlayColor: overlayColor,
|
||||
child: yearItem,
|
||||
|
@ -53,18 +53,19 @@ const double _kMaxTextScaleFactor = 1.3;
|
||||
/// The returned [Future] resolves to the date selected by the user when the
|
||||
/// user confirms the dialog. If the user cancels the dialog, null is returned.
|
||||
///
|
||||
/// When the date picker is first displayed, it will show the month of
|
||||
/// [initialDate], with [initialDate] selected.
|
||||
/// When the date picker is first displayed, if [initialDate] is not null, it
|
||||
/// will show the month of [initialDate], with [initialDate] selected. Otherwise
|
||||
/// it will show the [currentDate]'s month.
|
||||
///
|
||||
/// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
|
||||
/// allowable date. [initialDate] must either fall between these dates,
|
||||
/// or be equal to one of them. For each of these [DateTime] parameters, only
|
||||
/// their dates are considered. Their time fields are ignored. They must all
|
||||
/// be non-null.
|
||||
/// allowable date. If [initialDate] is not null, it must either fall between
|
||||
/// these dates, or be equal to one of them. For each of these [DateTime]
|
||||
/// parameters, only their dates are considered. Their time fields are ignored.
|
||||
/// They must all be non-null.
|
||||
///
|
||||
/// The [currentDate] represents the current day (i.e. today). This
|
||||
/// date will be highlighted in the day grid. If null, the date of
|
||||
/// `DateTime.now()` will be used.
|
||||
/// [DateTime.now] will be used.
|
||||
///
|
||||
/// An optional [initialEntryMode] argument can be used to display the date
|
||||
/// picker in the [DatePickerEntryMode.calendar] (a calendar month grid)
|
||||
@ -123,8 +124,7 @@ const double _kMaxTextScaleFactor = 1.3;
|
||||
///
|
||||
/// An optional [initialDatePickerMode] argument can be used to have the
|
||||
/// calendar date picker initially appear in the [DatePickerMode.year] or
|
||||
/// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and
|
||||
/// must be non-null.
|
||||
/// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day].
|
||||
///
|
||||
/// {@macro flutter.widgets.RawDialogRoute}
|
||||
///
|
||||
@ -157,10 +157,9 @@ const double _kMaxTextScaleFactor = 1.3;
|
||||
/// * [DisplayFeatureSubScreen], which documents the specifics of how
|
||||
/// [DisplayFeature]s can split the screen into sub-screens.
|
||||
/// * [showTimePicker], which shows a dialog that contains a Material Design time picker.
|
||||
///
|
||||
Future<DateTime?> showDatePicker({
|
||||
required BuildContext context,
|
||||
required DateTime initialDate,
|
||||
DateTime? initialDate,
|
||||
required DateTime firstDate,
|
||||
required DateTime lastDate,
|
||||
DateTime? currentDate,
|
||||
@ -188,7 +187,7 @@ Future<DateTime?> showDatePicker({
|
||||
final Icon? switchToInputEntryModeIcon,
|
||||
final Icon? switchToCalendarEntryModeIcon,
|
||||
}) async {
|
||||
initialDate = DateUtils.dateOnly(initialDate);
|
||||
initialDate = initialDate == null ? null : DateUtils.dateOnly(initialDate);
|
||||
firstDate = DateUtils.dateOnly(firstDate);
|
||||
lastDate = DateUtils.dateOnly(lastDate);
|
||||
assert(
|
||||
@ -196,15 +195,15 @@ Future<DateTime?> showDatePicker({
|
||||
'lastDate $lastDate must be on or after firstDate $firstDate.',
|
||||
);
|
||||
assert(
|
||||
!initialDate.isBefore(firstDate),
|
||||
initialDate == null || !initialDate.isBefore(firstDate),
|
||||
'initialDate $initialDate must be on or after firstDate $firstDate.',
|
||||
);
|
||||
assert(
|
||||
!initialDate.isAfter(lastDate),
|
||||
initialDate == null || !initialDate.isAfter(lastDate),
|
||||
'initialDate $initialDate must be on or before lastDate $lastDate.',
|
||||
);
|
||||
assert(
|
||||
selectableDayPredicate == null || selectableDayPredicate(initialDate),
|
||||
selectableDayPredicate == null || initialDate == null || selectableDayPredicate(initialDate),
|
||||
'Provided initialDate $initialDate must satisfy provided selectableDayPredicate.',
|
||||
);
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
@ -272,7 +271,7 @@ class DatePickerDialog extends StatefulWidget {
|
||||
/// A Material-style date picker dialog.
|
||||
DatePickerDialog({
|
||||
super.key,
|
||||
required DateTime initialDate,
|
||||
DateTime? initialDate,
|
||||
required DateTime firstDate,
|
||||
required DateTime lastDate,
|
||||
DateTime? currentDate,
|
||||
@ -291,7 +290,7 @@ class DatePickerDialog extends StatefulWidget {
|
||||
this.onDatePickerModeChange,
|
||||
this.switchToInputEntryModeIcon,
|
||||
this.switchToCalendarEntryModeIcon,
|
||||
}) : initialDate = DateUtils.dateOnly(initialDate),
|
||||
}) : initialDate = initialDate == null ? null : DateUtils.dateOnly(initialDate),
|
||||
firstDate = DateUtils.dateOnly(firstDate),
|
||||
lastDate = DateUtils.dateOnly(lastDate),
|
||||
currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()) {
|
||||
@ -300,21 +299,24 @@ class DatePickerDialog extends StatefulWidget {
|
||||
'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.',
|
||||
);
|
||||
assert(
|
||||
!this.initialDate.isBefore(this.firstDate),
|
||||
initialDate == null || !this.initialDate!.isBefore(this.firstDate),
|
||||
'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.',
|
||||
);
|
||||
assert(
|
||||
!this.initialDate.isAfter(this.lastDate),
|
||||
initialDate == null || !this.initialDate!.isAfter(this.lastDate),
|
||||
'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.',
|
||||
);
|
||||
assert(
|
||||
selectableDayPredicate == null || selectableDayPredicate!(this.initialDate),
|
||||
selectableDayPredicate == null || initialDate == null || selectableDayPredicate!(this.initialDate!),
|
||||
'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate',
|
||||
);
|
||||
}
|
||||
|
||||
/// The initially selected [DateTime] that the picker should display.
|
||||
final DateTime initialDate;
|
||||
///
|
||||
/// If this is null, there is no selected date. A date must be selected to
|
||||
/// submit the dialog.
|
||||
final DateTime? initialDate;
|
||||
|
||||
/// The earliest allowable [DateTime] that the user can select.
|
||||
final DateTime firstDate;
|
||||
@ -410,7 +412,7 @@ class DatePickerDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMixin {
|
||||
late final RestorableDateTime _selectedDate = RestorableDateTime(widget.initialDate);
|
||||
late final RestorableDateTimeN _selectedDate = RestorableDateTimeN(widget.initialDate);
|
||||
late final _RestorableDatePickerEntryMode _entryMode = _RestorableDatePickerEntryMode(widget.initialEntryMode);
|
||||
final _RestorableAutovalidateMode _autovalidateMode = _RestorableAutovalidateMode(AutovalidateMode.disabled);
|
||||
|
||||
@ -639,7 +641,7 @@ class _DatePickerDialogState extends State<DatePickerDialog> with RestorationMix
|
||||
? localizations.datePickerHelpText
|
||||
: localizations.datePickerHelpText.toUpperCase()
|
||||
),
|
||||
titleText: localizations.formatMediumDate(_selectedDate.value),
|
||||
titleText: _selectedDate.value == null ? '' : localizations.formatMediumDate(_selectedDate.value!),
|
||||
titleStyle: headlineStyle,
|
||||
orientation: orientation,
|
||||
isShort: orientation == Orientation.landscape,
|
||||
@ -1365,7 +1367,7 @@ class _DateRangePickerDialogState extends State<DateRangePickerDialog> with Rest
|
||||
_entryMode.value = DatePickerEntryMode.input;
|
||||
|
||||
case DatePickerEntryMode.input:
|
||||
// Validate the range dates
|
||||
// Validate the range dates
|
||||
if (_selectedStart.value != null &&
|
||||
(_selectedStart.value!.isBefore(widget.firstDate) || _selectedStart.value!.isAfter(widget.lastDate))) {
|
||||
_selectedStart.value = null;
|
||||
|
@ -34,7 +34,7 @@ void main() {
|
||||
textDirection: textDirection,
|
||||
child: CalendarDatePicker(
|
||||
key: key,
|
||||
initialDate: initialDate ?? DateTime(2016, DateTime.january, 15),
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate ?? DateTime(2001),
|
||||
lastDate: lastDate ?? DateTime(2031, DateTime.december, 31),
|
||||
currentDate: currentDate ?? DateTime(2016, DateTime.january, 3),
|
||||
@ -65,7 +65,6 @@ void main() {
|
||||
child: YearPicker(
|
||||
key: key,
|
||||
selectedDate: selectedDate ?? DateTime(2016, DateTime.january, 15),
|
||||
initialDate: initialDate ?? DateTime(2016, DateTime.january, 15),
|
||||
firstDate: firstDate ?? DateTime(2001),
|
||||
lastDate: lastDate ?? DateTime(2031, DateTime.december, 31),
|
||||
currentDate: currentDate ?? DateTime(2016, DateTime.january, 3),
|
||||
@ -78,6 +77,16 @@ void main() {
|
||||
|
||||
group('CalendarDatePicker', () {
|
||||
testWidgets('Can select a day', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
));
|
||||
await tester.tap(find.text('12'));
|
||||
expect(selectedDate, equals(DateTime(2016, DateTime.january, 12)));
|
||||
});
|
||||
|
||||
testWidgets('Can select a day with nothing first selected', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
@ -87,6 +96,31 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Can select a month', (WidgetTester tester) async {
|
||||
DateTime? displayedMonth;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
));
|
||||
expect(find.text('January 2016'), findsOneWidget);
|
||||
|
||||
// Go back two months
|
||||
await tester.tap(previousMonthIcon);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('December 2015'), findsOneWidget);
|
||||
expect(displayedMonth, equals(DateTime(2015, DateTime.december)));
|
||||
await tester.tap(previousMonthIcon);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('November 2015'), findsOneWidget);
|
||||
expect(displayedMonth, equals(DateTime(2015, DateTime.november)));
|
||||
|
||||
// Go forward a month
|
||||
await tester.tap(nextMonthIcon);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('December 2015'), findsOneWidget);
|
||||
expect(displayedMonth, equals(DateTime(2015, DateTime.december)));
|
||||
});
|
||||
|
||||
testWidgets('Can select a month with nothing first selected', (WidgetTester tester) async {
|
||||
DateTime? displayedMonth;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
@ -111,6 +145,21 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Can select a year', (WidgetTester tester) async {
|
||||
DateTime? displayedMonth;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
));
|
||||
|
||||
await tester.tap(find.text('January 2016')); // Switch to year mode.
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('2018'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('January 2018'), findsOneWidget);
|
||||
expect(displayedMonth, equals(DateTime(2018)));
|
||||
});
|
||||
|
||||
testWidgets('Can select a year with nothing first selected', (WidgetTester tester) async {
|
||||
DateTime? displayedMonth;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
@ -150,6 +199,7 @@ void main() {
|
||||
testWidgets('Changing year does change selected date', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
));
|
||||
await tester.tap(find.text('4'));
|
||||
@ -183,6 +233,7 @@ void main() {
|
||||
testWidgets('Changing year does not change the month', (WidgetTester tester) async {
|
||||
DateTime? displayedMonth;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
));
|
||||
await tester.tap(nextMonthIcon);
|
||||
@ -200,6 +251,7 @@ void main() {
|
||||
testWidgets('Can select a year and then a day', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
));
|
||||
await tester.tap(find.text('January 2016')); // Switch to year mode.
|
||||
@ -308,14 +360,39 @@ void main() {
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
));
|
||||
// Selected date is now 2018-05-04 (initialDate).
|
||||
await tester.tap(find.text('May 2018'));
|
||||
// Selected date is still 2018-05-04.
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('2019'));
|
||||
// Selected date would become 2019-05-04 but gets clamped to the month of lastDate, so 2019-01-04.
|
||||
await tester.pumpAndSettle();
|
||||
// Month should be clamped to January as the range ends at January 2019.
|
||||
expect(find.text('January 2019'), findsOneWidget);
|
||||
expect(displayedMonth, DateTime(2019));
|
||||
expect(selectedDate, DateTime(2019, DateTime.january, 15));
|
||||
expect(selectedDate, DateTime(2019, DateTime.january, 4));
|
||||
});
|
||||
|
||||
testWidgets('Selecting lastDate year respects lastDate', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
DateTime? displayedMonth;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
firstDate: DateTime(2016, DateTime.june, 9),
|
||||
initialDate: DateTime(2018, DateTime.may, 15),
|
||||
lastDate: DateTime(2019, DateTime.january, 4),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
onDisplayedMonthChanged: (DateTime date) => displayedMonth = date,
|
||||
));
|
||||
// Selected date is now 2018-05-15 (initialDate).
|
||||
await tester.tap(find.text('May 2018'));
|
||||
// Selected date is still 2018-05-15.
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('2019'));
|
||||
// Selected date would become 2019-05-15 but gets clamped to the month of lastDate, so 2019-01-15.
|
||||
// Day is now beyond the lastDate so that also gets clamped, to 2019-01-04.
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('January 2019'), findsOneWidget);
|
||||
expect(displayedMonth, DateTime(2019));
|
||||
expect(selectedDate, DateTime(2019, DateTime.january, 4));
|
||||
});
|
||||
|
||||
testWidgets('Only predicate days are selectable', (WidgetTester tester) async {
|
||||
@ -350,6 +427,7 @@ void main() {
|
||||
testWidgets('Material2 - currentDate is highlighted', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
useMaterial3: false,
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
currentDate: DateTime(2016, 1, 2),
|
||||
));
|
||||
const Color todayColor = Color(0xff2196f3); // default primary color
|
||||
@ -367,6 +445,7 @@ void main() {
|
||||
testWidgets('Material3 - currentDate is highlighted', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
useMaterial3: true,
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
currentDate: DateTime(2016, 1, 2),
|
||||
));
|
||||
const Color todayColor = Color(0xff6750a4); // default primary color
|
||||
@ -437,107 +516,63 @@ void main() {
|
||||
expect(find.text('2017'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Material2 - Updates to initialDate parameter is reflected in the state', (WidgetTester tester) async {
|
||||
final Key pickerKey = UniqueKey();
|
||||
final DateTime initialDate = DateTime(2020, 1, 21);
|
||||
final DateTime updatedDate = DateTime(1976, 2, 23);
|
||||
final DateTime firstDate = DateTime(1970);
|
||||
final DateTime lastDate = DateTime(2099, 31, 12);
|
||||
const Color selectedColor = Color(0xff2196f3); // default primary color
|
||||
for (final bool useMaterial3 in <bool>[false, true]) {
|
||||
testWidgets('Updates to initialDate parameter are not reflected in the state (useMaterial3=$useMaterial3)', (WidgetTester tester) async {
|
||||
final Key pickerKey = UniqueKey();
|
||||
final DateTime initialDate = DateTime(2020, 1, 21);
|
||||
final DateTime updatedDate = DateTime(1976, 2, 23);
|
||||
final DateTime firstDate = DateTime(1970);
|
||||
final DateTime lastDate = DateTime(2099, 31, 12);
|
||||
final Color selectedColor = useMaterial3 ? const Color(0xff6750a4) : const Color(0xff2196f3); // default primary color
|
||||
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
useMaterial3: false,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
onDateChanged: (DateTime value) {},
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
useMaterial3: useMaterial3,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
onDateChanged: (DateTime value) {},
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Month should show as January 2020
|
||||
expect(find.text('January 2020'), findsOneWidget);
|
||||
// Selected date should be painted with a colored circle.
|
||||
expect(
|
||||
Material.of(tester.element(find.text('21'))),
|
||||
paints..circle(color: selectedColor, style: PaintingStyle.fill),
|
||||
);
|
||||
// Month should show as January 2020.
|
||||
expect(find.text('January 2020'), findsOneWidget);
|
||||
// Selected date should be painted with a colored circle.
|
||||
expect(
|
||||
Material.of(tester.element(find.text('21'))),
|
||||
paints..circle(color: selectedColor, style: PaintingStyle.fill),
|
||||
);
|
||||
|
||||
// Change to the updated initialDate
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
useMaterial3: false,
|
||||
initialDate: updatedDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
onDateChanged: (DateTime value) {},
|
||||
));
|
||||
// Wait for the page scroll animation to finish.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
// Change to the updated initialDate.
|
||||
// This should have no effect, the initialDate is only the _initial_ date.
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
useMaterial3: useMaterial3,
|
||||
initialDate: updatedDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
onDateChanged: (DateTime value) {},
|
||||
));
|
||||
// Wait for the page scroll animation to finish.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
|
||||
// Month should show as February 1976
|
||||
expect(find.text('January 2020'), findsNothing);
|
||||
expect(find.text('February 1976'), findsOneWidget);
|
||||
// Selected date should be painted with a colored circle.
|
||||
expect(
|
||||
Material.of(tester.element(find.text('23'))),
|
||||
paints..circle(color: selectedColor, style: PaintingStyle.fill),
|
||||
);
|
||||
});
|
||||
// Month should show as January 2020 still.
|
||||
expect(find.text('January 2020'), findsOneWidget);
|
||||
expect(find.text('February 1976'), findsNothing);
|
||||
// Selected date should be painted with a colored circle.
|
||||
expect(
|
||||
Material.of(tester.element(find.text('21'))),
|
||||
paints..circle(color: selectedColor, style: PaintingStyle.fill),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('Material3 - Updates to initialDate parameter is reflected in the state', (WidgetTester tester) async {
|
||||
final Key pickerKey = UniqueKey();
|
||||
final DateTime initialDate = DateTime(2020, 1, 21);
|
||||
final DateTime updatedDate = DateTime(1976, 2, 23);
|
||||
final DateTime firstDate = DateTime(1970);
|
||||
final DateTime lastDate = DateTime(2099, 31, 12);
|
||||
const Color selectedColor = Color(0xff6750a4); // default primary color
|
||||
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
useMaterial3: true,
|
||||
initialDate: initialDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
onDateChanged: (DateTime value) {},
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Month should show as January 2020
|
||||
expect(find.text('January 2020'), findsOneWidget);
|
||||
// Selected date should be painted with a colored circle.
|
||||
expect(
|
||||
Material.of(tester.element(find.text('21'))),
|
||||
paints..circle(color: selectedColor, style: PaintingStyle.fill),
|
||||
);
|
||||
|
||||
// Change to the updated initialDate
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
useMaterial3: true,
|
||||
initialDate: updatedDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
onDateChanged: (DateTime value) {},
|
||||
));
|
||||
// Wait for the page scroll animation to finish.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
|
||||
// Month should show as February 1976
|
||||
expect(find.text('January 2020'), findsNothing);
|
||||
expect(find.text('February 1976'), findsOneWidget);
|
||||
// Selected date should be painted with a colored circle.
|
||||
expect(
|
||||
Material.of(tester.element(find.text('23'))),
|
||||
paints..circle(color: selectedColor, style: PaintingStyle.fill),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Updates to initialCalendarMode parameter is reflected in the state', (WidgetTester tester) async {
|
||||
testWidgets('Updates to initialCalendarMode parameter is not reflected in the state', (WidgetTester tester) async {
|
||||
final Key pickerKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
initialCalendarMode: DatePickerMode.year,
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
@ -549,17 +584,20 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
key: pickerKey,
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Should be in day mode.
|
||||
// Should be in year mode still; updating an _initial_ parameter has no effect.
|
||||
expect(find.text('January 2016'), findsOneWidget); // Day/year selector
|
||||
expect(find.text('15'), findsOneWidget); // day 15 in grid
|
||||
expect(find.text('2016'), findsNothing); // 2016 in year grid
|
||||
expect(find.text('15'), findsNothing); // day 15 in grid
|
||||
expect(find.text('2016'), findsOneWidget); // 2016 in year grid
|
||||
});
|
||||
|
||||
testWidgets('Dragging more than half the width should not cause a jump', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker());
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(PageView)));
|
||||
// This initial drag is required for the PageView to recognize the gesture, as it uses DragStartBehavior.start.
|
||||
@ -579,7 +617,9 @@ void main() {
|
||||
|
||||
group('Keyboard navigation', () {
|
||||
testWidgets('Can toggle to year mode', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker());
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
expect(find.text('2016'), findsNothing);
|
||||
expect(find.text('January 2016'), findsOneWidget);
|
||||
// Navigate to the year selector and activate it.
|
||||
@ -592,7 +632,9 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Can navigate next/previous months', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker());
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
expect(find.text('January 2016'), findsOneWidget);
|
||||
// Navigate to the previous month button and activate it twice.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
@ -621,6 +663,7 @@ void main() {
|
||||
testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
));
|
||||
// Navigate to the grid.
|
||||
@ -650,6 +693,7 @@ void main() {
|
||||
testWidgets('Navigating with arrow keys scrolls months', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
));
|
||||
// Navigate to the grid.
|
||||
@ -690,6 +734,7 @@ void main() {
|
||||
testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
|
||||
DateTime? selectedDate;
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
onDateChanged: (DateTime date) => selectedDate = date,
|
||||
textDirection: TextDirection.rtl,
|
||||
));
|
||||
@ -731,7 +776,9 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Selecting date vibrates', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker());
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
await tester.tap(find.text('10'));
|
||||
await tester.pump(hapticFeedbackInterval);
|
||||
expect(feedback.hapticCount, 1);
|
||||
@ -760,7 +807,9 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Changing modes and year vibrates', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(calendarDatePicker());
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
await tester.tap(find.text('January 2016'));
|
||||
await tester.pump(hapticFeedbackInterval);
|
||||
expect(feedback.hapticCount, 1);
|
||||
@ -774,7 +823,9 @@ void main() {
|
||||
testWidgets('day mode', (WidgetTester tester) async {
|
||||
final SemanticsHandle semantics = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(calendarDatePicker());
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
));
|
||||
|
||||
// Year mode drop down button.
|
||||
expect(tester.getSemantics(find.text('January 2016')), matchesSemantics(
|
||||
@ -989,6 +1040,7 @@ void main() {
|
||||
final SemanticsHandle semantics = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(calendarDatePicker(
|
||||
initialDate: DateTime(2016, DateTime.january, 15),
|
||||
initialCalendarMode: DatePickerMode.year,
|
||||
));
|
||||
|
||||
@ -1034,7 +1086,7 @@ void main() {
|
||||
DateTime? selectedYear;
|
||||
await tester.pumpWidget(yearPicker(
|
||||
firstDate: DateTime(2018, DateTime.june, 9),
|
||||
initialDate: DateTime(2018, DateTime.july, 4),
|
||||
selectedDate: DateTime(2018, DateTime.july, 4),
|
||||
lastDate: DateTime(2018, DateTime.december, 15),
|
||||
onChanged: (DateTime date) => selectedYear = date,
|
||||
));
|
||||
@ -1048,5 +1100,43 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(selectedYear, equals(DateTime(2018, DateTime.july)));
|
||||
});
|
||||
|
||||
testWidgets('Selecting year with no selected month uses earliest month', (WidgetTester tester) async {
|
||||
DateTime? selectedYear;
|
||||
await tester.pumpWidget(yearPicker(
|
||||
firstDate: DateTime(2018, DateTime.june, 9),
|
||||
lastDate: DateTime(2019, DateTime.december, 15),
|
||||
onChanged: (DateTime date) => selectedYear = date,
|
||||
));
|
||||
await tester.tap(find.text('2018'));
|
||||
expect(selectedYear, equals(DateTime(2018, DateTime.june)));
|
||||
await tester.pumpWidget(yearPicker(
|
||||
firstDate: DateTime(2018, DateTime.june, 9),
|
||||
lastDate: DateTime(2019, DateTime.december, 15),
|
||||
selectedDate: DateTime(2018, DateTime.june),
|
||||
onChanged: (DateTime date) => selectedYear = date,
|
||||
));
|
||||
await tester.tap(find.text('2019'));
|
||||
expect(selectedYear, equals(DateTime(2019, DateTime.june)));
|
||||
});
|
||||
|
||||
testWidgets('Selecting year with no selected month uses January', (WidgetTester tester) async {
|
||||
DateTime? selectedYear;
|
||||
await tester.pumpWidget(yearPicker(
|
||||
firstDate: DateTime(2018, DateTime.june, 9),
|
||||
lastDate: DateTime(2019, DateTime.december, 15),
|
||||
onChanged: (DateTime date) => selectedYear = date,
|
||||
));
|
||||
await tester.tap(find.text('2019'));
|
||||
expect(selectedYear, equals(DateTime(2019))); // january implied
|
||||
await tester.pumpWidget(yearPicker(
|
||||
firstDate: DateTime(2018, DateTime.june, 9),
|
||||
lastDate: DateTime(2019, DateTime.december, 15),
|
||||
selectedDate: DateTime(2018),
|
||||
onChanged: (DateTime date) => selectedYear = date,
|
||||
));
|
||||
await tester.tap(find.text('2018'));
|
||||
expect(selectedYear, equals(DateTime(2018, DateTime.june)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ void main() {
|
||||
|
||||
late DateTime firstDate;
|
||||
late DateTime lastDate;
|
||||
late DateTime initialDate;
|
||||
late DateTime? initialDate;
|
||||
late DateTime today;
|
||||
late SelectableDayPredicate? selectableDayPredicate;
|
||||
late DatePickerEntryMode initialEntryMode;
|
||||
@ -1044,6 +1044,37 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Can select a day with no initial date', (WidgetTester tester) async {
|
||||
initialDate = null;
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
await tester.tap(find.text('12'));
|
||||
await tester.tap(find.text('OK'));
|
||||
expect(await date, equals(DateTime(2016, DateTime.january, 12)));
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Can select a month with no initial date', (WidgetTester tester) async {
|
||||
initialDate = null;
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
await tester.tap(previousMonthIcon);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
await tester.tap(find.text('25'));
|
||||
await tester.tap(find.text('OK'));
|
||||
expect(await date, DateTime(2015, DateTime.december, 25));
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Can select a year with no initial date', (WidgetTester tester) async {
|
||||
initialDate = null;
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
await tester.tap(find.text('January 2016')); // Switch to year mode.
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('2018'));
|
||||
await tester.pump();
|
||||
expect(find.text('January 2018'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Selecting date does not change displayed month', (WidgetTester tester) async {
|
||||
initialDate = DateTime(2020, DateTime.march, 15);
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
@ -1105,8 +1136,8 @@ void main() {
|
||||
|
||||
testWidgets('Cannot select a day outside bounds', (WidgetTester tester) async {
|
||||
initialDate = DateTime(2017, DateTime.january, 15);
|
||||
firstDate = initialDate;
|
||||
lastDate = initialDate;
|
||||
firstDate = initialDate!;
|
||||
lastDate = initialDate!;
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
// Earlier than firstDate. Should be ignored.
|
||||
await tester.tap(find.text('10'));
|
||||
@ -1120,7 +1151,7 @@ void main() {
|
||||
|
||||
testWidgets('Cannot select a month past last date', (WidgetTester tester) async {
|
||||
initialDate = DateTime(2017, DateTime.january, 15);
|
||||
firstDate = initialDate;
|
||||
firstDate = initialDate!;
|
||||
lastDate = DateTime(2017, DateTime.february, 20);
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
await tester.tap(nextMonthIcon);
|
||||
@ -1133,7 +1164,7 @@ void main() {
|
||||
testWidgets('Cannot select a month before first date', (WidgetTester tester) async {
|
||||
initialDate = DateTime(2017, DateTime.january, 15);
|
||||
firstDate = DateTime(2016, DateTime.december, 10);
|
||||
lastDate = initialDate;
|
||||
lastDate = initialDate!;
|
||||
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
||||
await tester.tap(previousMonthIcon);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
Loading…
Reference in New Issue
Block a user