From a7eaca934ddca15c9180492a3eb4637bff283fdb Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 04:33:30 +0000 Subject: [PATCH] Reverts "Add ability to provide selectableDayPredicate for showDateRangePicker (#150355)" (#154089) Reverts: flutter/flutter#150355 Initiated by: chingjun Reason for reverting: this is breaking an internal test. Original PR Author: Chuckame Reviewed By: {victorsanni, MitchellGoodwin} This change reverts the following previous change: - Closes #63973 - Now able to provide a `selectableDayPredicate` - No breaking change (same behavior as before if not set) - Reuse the same feature as the DatePicker: non-selectable days are greyed and not clickable - Reuse the same error message as if the user set a wrong date range - Made public `CalendarDateRangePicker`, actually the same for `CalendarDatePicker`, to allow using the range picker outside the `showDateRangePicker` bottom sheet modal ## Examples - Disable days after the next non selectable day when start day has been selected https://github.com/flutter/flutter/assets/16419143/2a2be325-1e2f-470c-8b17-b4ed5b2ad43e - Select a range including non-selectable days image --- .../flutter/lib/src/material/date_picker.dart | 100 ++++-------------- .../test/material/date_range_picker_test.dart | 98 ----------------- 2 files changed, 19 insertions(+), 179 deletions(-) diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 609f0515921..d5ca94ef947 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -1012,18 +1012,6 @@ class _DatePickerHeader extends StatelessWidget { } } -/// Signature for predicating enabled dates in date range pickers. -/// -/// The [selectedStartDay] and [selectedEndDay] are the currently selected start -/// and end dates of a date range, which conditionally enables or disables each -/// date in the picker based on the user selection. (Example: in a hostel's room -/// selection, you are not able to select the end date after the next -/// non-selectable day). -/// -/// See [showDateRangePicker], which has a [SelectableDayForRangePredicate] -/// parameter used to specify allowable days in the date range picker. -typedef SelectableDayForRangePredicate = bool Function(DateTime day, DateTime? selectedStartDay, DateTime? selectedEndDay); - /// Shows a full screen modal dialog containing a Material Design date range /// picker. /// @@ -1152,8 +1140,11 @@ Future showDateRangePicker({ TextInputType keyboardType = TextInputType.datetime, final Icon? switchToInputEntryModeIcon, final Icon? switchToCalendarEntryModeIcon, - SelectableDayForRangePredicate? selectableDayPredicate, }) async { + assert( + initialDateRange == null || !initialDateRange.start.isAfter(initialDateRange.end), + "initialDateRange's start date must not be after it's end date.", + ); initialDateRange = initialDateRange == null ? null : DateUtils.datesOnly(initialDateRange); firstDate = DateUtils.dateOnly(firstDate); lastDate = DateUtils.dateOnly(lastDate); @@ -1177,16 +1168,6 @@ Future showDateRangePicker({ initialDateRange == null || !initialDateRange.end.isAfter(lastDate), "initialDateRange's end date must be on or before lastDate $lastDate.", ); - assert( - initialDateRange == null || selectableDayPredicate == null || - selectableDayPredicate(initialDateRange.start, initialDateRange.start, initialDateRange.end), - "initialDateRange's start date must be selectable.", - ); - assert( - initialDateRange == null || selectableDayPredicate == null || - selectableDayPredicate(initialDateRange.end, initialDateRange.start, initialDateRange.end), - "initialDateRange's end date must be selectable.", - ); currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()); assert(debugCheckHasMaterialLocalizations(context)); @@ -1195,7 +1176,6 @@ Future showDateRangePicker({ firstDate: firstDate, lastDate: lastDate, currentDate: currentDate, - selectableDayPredicate: selectableDayPredicate, initialEntryMode: initialEntryMode, helpText: helpText, cancelText: cancelText, @@ -1304,7 +1284,6 @@ class DateRangePickerDialog extends StatefulWidget { this.restorationId, this.switchToInputEntryModeIcon, this.switchToCalendarEntryModeIcon, - this.selectableDayPredicate, }); /// The date range that the date range picker starts with when it opens. @@ -1433,9 +1412,6 @@ class DateRangePickerDialog extends StatefulWidget { /// {@macro flutter.material.date_picker.switchToCalendarEntryModeIcon} final Icon? switchToCalendarEntryModeIcon; - /// Function to provide full control over which [DateTime] can be selected. - final SelectableDayForRangePredicate? selectableDayPredicate; - @override State createState() => _DateRangePickerDialogState(); } @@ -1498,14 +1474,18 @@ class _DateRangePickerDialogState extends State with Rest case DatePickerEntryMode.input: // Validate the range dates - if (_selectedStart.value != null && _selectedEnd.value != null && _selectedStart.value!.isAfter(_selectedEnd.value!)) { - _selectedEnd.value = null; - } - if (_selectedStart.value != null && !_isDaySelectable(_selectedStart.value!)) { + if (_selectedStart.value != null && + (_selectedStart.value!.isBefore(widget.firstDate) || _selectedStart.value!.isAfter(widget.lastDate))) { _selectedStart.value = null; // With no valid start date, having an end date makes no sense for the UI. _selectedEnd.value = null; - } else if (_selectedEnd.value != null && !_isDaySelectable(_selectedEnd.value!)) { + } + if (_selectedEnd.value != null && + (_selectedEnd.value!.isBefore(widget.firstDate) || _selectedEnd.value!.isAfter(widget.lastDate))) { + _selectedEnd.value = null; + } + // If invalid range (start after end), then just use the start date + if (_selectedStart.value != null && _selectedEnd.value != null && _selectedStart.value!.isAfter(_selectedEnd.value!)) { _selectedEnd.value = null; } _entryMode.value = DatePickerEntryMode.calendar; @@ -1517,16 +1497,6 @@ class _DateRangePickerDialogState extends State with Rest }); } - bool _isDaySelectable(DateTime day) { - if (day.isBefore(widget.firstDate) || day.isAfter(widget.lastDate)) { - return false; - } - if (widget.selectableDayPredicate == null) { - return true; - } - return widget.selectableDayPredicate!(day, _selectedStart.value, _selectedEnd.value); - } - void _handleStartDateChanged(DateTime? date) { setState(() => _selectedStart.value = date); } @@ -1565,7 +1535,6 @@ class _DateRangePickerDialogState extends State with Rest selectedEndDate: _selectedEnd.value, firstDate: widget.firstDate, lastDate: widget.lastDate, - selectableDayPredicate: widget.selectableDayPredicate, currentDate: widget.currentDate, onStartDateChanged: _handleStartDateChanged, onEndDateChanged: _handleEndDateChanged, @@ -1618,7 +1587,6 @@ class _DateRangePickerDialogState extends State with Rest initialEndDate: _selectedEnd.value, firstDate: widget.firstDate, lastDate: widget.lastDate, - selectableDayPredicate: widget.selectableDayPredicate, onStartDateChanged: _handleStartDateChanged, onEndDateChanged: _handleEndDateChanged, autofocus: true, @@ -1714,7 +1682,6 @@ class _CalendarRangePickerDialog extends StatelessWidget { required this.onCancel, required this.confirmText, required this.helpText, - required this.selectableDayPredicate, this.entryModeButton, }); @@ -1722,7 +1689,6 @@ class _CalendarRangePickerDialog extends StatelessWidget { final DateTime? selectedEndDate; final DateTime firstDate; final DateTime lastDate; - final SelectableDayForRangePredicate? selectableDayPredicate; final DateTime? currentDate; final ValueChanged onStartDateChanged; final ValueChanged onEndDateChanged; @@ -1847,7 +1813,6 @@ class _CalendarRangePickerDialog extends StatelessWidget { currentDate: currentDate, onStartDateChanged: onStartDateChanged, onEndDateChanged: onEndDateChanged, - selectableDayPredicate: selectableDayPredicate, ), ), ); @@ -1873,7 +1838,6 @@ class _CalendarDateRangePicker extends StatefulWidget { DateTime? initialEndDate, required DateTime firstDate, required DateTime lastDate, - required this.selectableDayPredicate, DateTime? currentDate, required this.onStartDateChanged, required this.onEndDateChanged, @@ -1904,9 +1868,6 @@ class _CalendarDateRangePicker extends StatefulWidget { /// The latest allowable [DateTime] that the user can select. final DateTime lastDate; - /// Function to provide full control over which [DateTime] can be selected. - final SelectableDayForRangePredicate? selectableDayPredicate; - /// The [DateTime] representing today. It will be highlighted in the day grid. final DateTime currentDate; @@ -1917,7 +1878,7 @@ class _CalendarDateRangePicker extends StatefulWidget { final ValueChanged? onEndDateChanged; @override - State<_CalendarDateRangePicker> createState() => _CalendarDateRangePickerState(); + _CalendarDateRangePickerState createState() => _CalendarDateRangePickerState(); } class _CalendarDateRangePickerState extends State<_CalendarDateRangePicker> { @@ -2020,7 +1981,6 @@ class _CalendarDateRangePickerState extends State<_CalendarDateRangePicker> { lastDate: widget.lastDate, displayedMonth: month, onChanged: _updateSelection, - selectableDayPredicate: widget.selectableDayPredicate, ); } @@ -2409,7 +2369,6 @@ class _MonthItem extends StatefulWidget { required this.firstDate, required this.lastDate, required this.displayedMonth, - required this.selectableDayPredicate, }) : assert(!firstDate.isAfter(lastDate)), assert(selectedDateStart == null || !selectedDateStart.isBefore(firstDate)), assert(selectedDateEnd == null || !selectedDateEnd.isBefore(firstDate)), @@ -2442,8 +2401,6 @@ class _MonthItem extends StatefulWidget { /// The month whose days are displayed by this picker. final DateTime displayedMonth; - final SelectableDayForRangePredicate? selectableDayPredicate; - @override _MonthItemState createState() => _MonthItemState(); } @@ -2509,10 +2466,7 @@ class _MonthItemState extends State<_MonthItem> { Widget _buildDayItem(BuildContext context, DateTime dayToBuild, int firstDayOffset, int daysInMonth) { final int day = dayToBuild.day; - final bool isDisabled = dayToBuild.isAfter(widget.lastDate) || - dayToBuild.isBefore(widget.firstDate) || - widget.selectableDayPredicate != null && - !widget.selectableDayPredicate!(dayToBuild, widget.selectedDateStart, widget.selectedDateEnd); + final bool isDisabled = dayToBuild.isAfter(widget.lastDate) || dayToBuild.isBefore(widget.firstDate); final bool isRangeSelected = widget.selectedDateStart != null && widget.selectedDateEnd != null; final bool isSelectedDayStart = widget.selectedDateStart != null && dayToBuild.isAtSameMomentAs(widget.selectedDateStart!); final bool isSelectedDayEnd = widget.selectedDateEnd != null && dayToBuild.isAtSameMomentAs(widget.selectedDateEnd!); @@ -2754,7 +2708,7 @@ class _DayItemState extends State<_DayItem> { if (widget.isSelectedDayStart || widget.isSelectedDayEnd) { // The selected start and end dates gets a circle background // highlight, and a contrasting text color. - itemStyle = itemStyle?.apply(color: dayForegroundColor); + itemStyle = textTheme.bodyMedium?.apply(color: dayForegroundColor); decoration = BoxDecoration( color: dayBackgroundColor, shape: BoxShape.circle, @@ -2777,15 +2731,12 @@ class _DayItemState extends State<_DayItem> { style: _HighlightPainterStyle.highlightAll, textDirection: textDirection, ); - if (widget.isDisabled) { - itemStyle = itemStyle?.apply(color: colorScheme.onSurface.withOpacity(0.38)); - } } else if (widget.isDisabled) { - itemStyle = itemStyle?.apply(color: colorScheme.onSurface.withOpacity(0.38)); + itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.onSurface.withOpacity(0.38)); } else if (widget.isToday) { // The current day gets a different text color and a circle stroke // border. - itemStyle = itemStyle?.apply(color: colorScheme.primary); + itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.primary); decoration = BoxDecoration( border: Border.all(color: colorScheme.primary), shape: BoxShape.circle, @@ -3071,7 +3022,6 @@ class _InputDateRangePicker extends StatefulWidget { required DateTime lastDate, required this.onStartDateChanged, required this.onEndDateChanged, - required this.selectableDayPredicate, this.helpText, this.errorFormatText, this.errorInvalidText, @@ -3145,8 +3095,6 @@ class _InputDateRangePicker extends StatefulWidget { /// {@macro flutter.material.datePickerDialog} final TextInputType keyboardType; - final SelectableDayForRangePredicate? selectableDayPredicate; - @override _InputDateRangePickerState createState() => _InputDateRangePickerState(); } @@ -3226,22 +3174,12 @@ class _InputDateRangePickerState extends State<_InputDateRangePicker> { String? _validateDate(DateTime? date) { if (date == null) { return widget.errorFormatText ?? MaterialLocalizations.of(context).invalidDateFormatLabel; - } else if (!_isDaySelectable(date)) { + } else if (date.isBefore(widget.firstDate) || date.isAfter(widget.lastDate)) { return widget.errorInvalidText ?? MaterialLocalizations.of(context).dateOutOfRangeLabel; } return null; } - bool _isDaySelectable(DateTime day) { - if (day.isBefore(widget.firstDate) || day.isAfter(widget.lastDate)) { - return false; - } - if (widget.selectableDayPredicate == null) { - return true; - } - return widget.selectableDayPredicate!(day, _startDate, _endDate); - } - void _updateController(TextEditingController controller, String text, bool selectText) { TextEditingValue textEditingValue = controller.value.copyWith(text: text); if (selectText) { diff --git a/packages/flutter/test/material/date_range_picker_test.dart b/packages/flutter/test/material/date_range_picker_test.dart index 04c21da1556..dbe3cdd4ee9 100644 --- a/packages/flutter/test/material/date_range_picker_test.dart +++ b/packages/flutter/test/material/date_range_picker_test.dart @@ -61,7 +61,6 @@ void main() { Future Function(Future date) callback, { TextDirection textDirection = TextDirection.ltr, bool useMaterial3 = false, - SelectableDayForRangePredicate? selectableDayPredicate, }) async { late BuildContext buttonContext; await tester.pumpWidget(MaterialApp( @@ -101,7 +100,6 @@ void main() { fieldEndLabelText: fieldEndLabelText, helpText: helpText, saveText: saveText, - selectableDayPredicate: selectableDayPredicate, builder: (BuildContext context, Widget? child) { return Directionality( textDirection: textDirection, @@ -406,71 +404,6 @@ void main() { }); }); - testWidgets('Can select a range even if the range includes non selectable days', (WidgetTester tester) async { - await preparePicker(tester, (Future range) async { - await tester.tap(find.text('12').first); - await tester.tap(find.text('14').first); - await tester.tap(find.text('SAVE')); - // The day 13 is not selectable, but the range is still valid. - expect(await range, DateTimeRange( - start: DateTime(2016, DateTime.january, 12), - end: DateTime(2016, DateTime.january, 14), - )); - }, selectableDayPredicate: (DateTime day, _, __) => day.day != 13); - }); - - testWidgets('Cannot select a day inside bounds but not selectable', (WidgetTester tester) async { - initialDateRange = DateTimeRange( - start: DateTime(2017, DateTime.january, 13), - end: DateTime(2017, DateTime.january, 14), - ); - firstDate = DateTime(2017, DateTime.january, 12); - lastDate = DateTime(2017, DateTime.january, 16); - await preparePicker(tester, (Future range) async { - // Non-selectable date. Should be ignored. - await tester.tap(find.text('15')); - await tester.tap(find.text('SAVE')); - // We should still be on the initial date. - expect(await range, initialDateRange); - }, selectableDayPredicate: (DateTime day, _, __) => day.day != 15); - }); - - testWidgets('Selectable date becoming non selectable when selected start day', (WidgetTester tester) async { - await preparePicker(tester, (Future range) async { - await tester.tap(find.text('12').first); - await tester.pumpAndSettle(); - await tester.tap(find.text('11').first); - await tester.pumpAndSettle(); - await tester.tap(find.text('14').first); - await tester.pumpAndSettle(); - await tester.tap(find.text('SAVE')); - expect(await range, DateTimeRange( - start: DateTime(2016, DateTime.january, 12), - end: DateTime(2016, DateTime.january, 14), - )); - }, selectableDayPredicate: (DateTime day, DateTime? selectedStart, DateTime? selectedEnd) { - if (selectedEnd == null && selectedStart != null) { - return day == selectedStart || day.isAfter(selectedStart); - } - return true; - }); - }); - - testWidgets('selectableDayPredicate should be called with the selected start and end dates', (WidgetTester tester) async { - initialDateRange = DateTimeRange( - start: DateTime(2017, DateTime.january, 13), - end: DateTime(2017, DateTime.january, 15), - ); - firstDate = DateTime(2017, DateTime.january, 12); - lastDate = DateTime(2017, DateTime.january, 16); - await preparePicker(tester, (Future range) async { - }, selectableDayPredicate: (DateTime day, DateTime? selectedStartDate, DateTime? selectedEndDate) { - expect(selectedStartDate, DateTime(2017, DateTime.january, 13)); - expect(selectedEndDate, DateTime(2017, DateTime.january, 15)); - return true; - }); - }); - testWidgets('Can switch from calendar to input entry mode', (WidgetTester tester) async { await preparePicker(tester, (Future range) async { expect(find.byType(TextField), findsNothing); @@ -553,22 +486,6 @@ void main() { }); }); - testWidgets('Non-selectable start date', (WidgetTester tester) async { - // Even if start and end dates are selected, the start date is not selectable - // ending up to no date selected at all in calendar mode. - await preparePicker(tester, (Future range) async { - await tester.enterText(find.byType(TextField).at(0), '12/24/2016'); - await tester.enterText(find.byType(TextField).at(1), '12/25/2016'); - await tester.tap(find.byIcon(Icons.calendar_today)); - await tester.pumpAndSettle(); - - expect(find.text('Start Date'), findsOneWidget); - expect(find.text('End Date'), findsOneWidget); - }, selectableDayPredicate: (DateTime day, DateTime? selectedStart, DateTime? selectedEnd) { - return day != DateTime(2016, DateTime.december, 24); - }); - }); - testWidgets('Invalid end date', (WidgetTester tester) async { // Invalid end date should only have a start date selected await preparePicker(tester, (Future range) async { @@ -582,21 +499,6 @@ void main() { }); }); - testWidgets('Non-selectable end date', (WidgetTester tester) async { - // The end date is not selectable, so only the start date should be selected. - await preparePicker(tester, (Future range) async { - await tester.enterText(find.byType(TextField).at(0), '12/24/2016'); - await tester.enterText(find.byType(TextField).at(1), '12/25/2016'); - await tester.tap(find.byIcon(Icons.calendar_today)); - await tester.pumpAndSettle(); - - expect(find.text('Dec 24'), findsOneWidget); - expect(find.text('End Date'), findsOneWidget); - }, selectableDayPredicate: (DateTime day, DateTime? selectedStart, DateTime? selectedEnd) { - return day != DateTime(2016, DateTime.december, 25); - }); - }); - testWidgets('Invalid range', (WidgetTester tester) async { // Start date after end date should just use the start date await preparePicker(tester, (Future range) async {