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
---
.../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 {