diff --git a/example/lib/main.dart b/example/lib/main.dart index 82c655f..3220c19 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -100,6 +100,17 @@ class ExampleApp extends StatelessWidget { ), ), ), + SizedBox( + width: buttonWidth, + child: ElevatedButton( + onPressed: () { + _openDateTimePicker( + context, + ); + }, + child: Text('Date time Picker', textAlign: TextAlign.center), + ), + ), SizedBox( width: buttonWidth, child: ElevatedButton( @@ -599,6 +610,9 @@ class ExampleApp extends StatelessWidget { BuildContext context, ) { BottomPicker.dateTime( + hourPredicate: (hour) { + return hour > 6; + }, headerBuilder: (context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/bottom_picker.dart b/lib/bottom_picker.dart index 6f9ee5e..e6c658d 100644 --- a/lib/bottom_picker.dart +++ b/lib/bottom_picker.dart @@ -277,6 +277,7 @@ class BottomPicker extends StatefulWidget { this.headerBuilder, this.calendarDays = CupertinoDatePickerWidget.fullWeek, this.diameterRatio = 1.1, + this.hourPredicate, }) { datePickerMode = CupertinoDatePickerMode.dateAndTime; bottomPickerType = BottomPickerType.dateTime; @@ -811,6 +812,9 @@ class BottomPicker extends StatefulWidget { /// The bottom picker selector diameter ratio. final double diameterRatio; + /// A predicate that can be used to select which hours are selectable. + SelectableHourPredicate? hourPredicate; + ///display the bottom picker popup ///[context] the app context to display the popup void show(BuildContext context) { @@ -1023,6 +1027,7 @@ class BottomPickerState extends State { itemExtent: widget.itemExtent, showTimeSeparator: widget.showTimeSeparator, pickerThemeData: widget.pickerThemeData, + hourPredicate: widget.hourPredicate, ) : widget.bottomPickerType == BottomPickerType.year diff --git a/lib/cupertino/cupertino_date_picker.dart b/lib/cupertino/cupertino_date_picker.dart index 2d12a9c..0950d79 100644 --- a/lib/cupertino/cupertino_date_picker.dart +++ b/lib/cupertino/cupertino_date_picker.dart @@ -4,7 +4,6 @@ library; import 'dart:math' as math; - import 'package:flutter/cupertino.dart'; import 'package:flutter/scheduler.dart'; @@ -47,6 +46,9 @@ const double _kTimerPickerLabelFontSize = 17.0; // The width of each column of the countdown time picker. const double _kTimerPickerColumnIntrinsicWidth = 106; +/// Signature for predicating hour for enabled hours selections. +typedef SelectableHourPredicate = bool Function(int hour); + TextStyle _themeTextStyle(BuildContext context, {bool isValid = true}) { final TextStyle style = CupertinoTheme.of(context).textTheme.dateTimePickerTextStyle; @@ -284,6 +286,7 @@ class CupertinoDatePickerWidget extends StatefulWidget { this.selectionOverlayBuilder, this.showTimeSeparator = false, this.calendarDays = fullWeek, + this.selectableHourPredicate, }) : initialDateTime = initialDateTime ?? DateTime.now(), assert( itemExtent > 0, @@ -336,6 +339,11 @@ class CupertinoDatePickerWidget extends StatefulWidget { this.initialDateTime.minute % minuteInterval == 0, 'initial minute is not divisible by minute interval', ); + assert( + (selectableHourPredicate == null) || + (selectableHourPredicate?.call(this.initialDateTime.hour) == true), + 'Hour must satisfy the hour predicate', + ); } /// The mode of the date picker as one of [CupertinoDatePickerMode]. Defaults @@ -468,6 +476,9 @@ class CupertinoDatePickerWidget extends StatefulWidget { /// {@end-tool} final SelectionOverlayBuilder? selectionOverlayBuilder; + //TODO add docs + final SelectableHourPredicate? selectableHourPredicate; + static const List fullWeek = [ DateTime.monday, DateTime.tuesday, @@ -853,6 +864,8 @@ class _CupertinoDatePickerDateTimeState if (isDateInvalid) { return; + } else if (widget.selectableHourPredicate?.call(selected.hour) == false) { + return; } widget.onDateTimeChanged(selected); @@ -939,18 +952,23 @@ class _CupertinoDatePickerDateTimeState // `hourIndex`, is it possible to change the value of the minute picker, so // that the resulting date stays in the valid range. bool _isValidHour(int meridiemIndex, int hourIndex) { + int selectedHour = _selectedHour(meridiemIndex, hourIndex); final DateTime rangeStart = DateTime( initialDateTime.year, initialDateTime.month, initialDateTime.day + selectedDayFromInitial, - _selectedHour(meridiemIndex, hourIndex), + selectedHour, ); // The end value of the range is exclusive, i.e. [rangeStart, rangeEnd). final DateTime rangeEnd = rangeStart.add(const Duration(hours: 1)); + bool hourInIntevalPredicate = + widget.selectableHourPredicate?.call(selectedHour) ?? true; + return (widget.minimumDate?.isBefore(rangeEnd) ?? true) && - !(widget.maximumDate?.isBefore(rangeStart) ?? false); + !(widget.maximumDate?.isBefore(rangeStart) ?? false) && + hourInIntevalPredicate; } Widget _buildHourPicker( @@ -1176,6 +1194,24 @@ class _CupertinoDatePickerDateTimeState } } + void _checkOnHourDisplay() { + final DateTime selectedDate = selectedDateTime; + final bool minCheck = widget.minimumDate?.isAfter(selectedDate) ?? false; + + if (widget.selectableHourPredicate?.call(selectedDate.hour) == false) { + const int daysThreshold = 1; + final DateTime targetDate = + selectedDate.add(const Duration(hours: daysThreshold)); + + _scrollToDate( + targetDate, + selectedDate, + minCheck, + newItemIndex: dateController.selectedItem + daysThreshold, + ); + } + } + // One or more pickers have just stopped scrolling. void _pickerDidStopScrolling() { // Call setState to update the greyed out date/hour/minute/meridiem. @@ -1193,6 +1229,7 @@ class _CupertinoDatePickerDateTimeState final bool maxCheck = widget.maximumDate?.isBefore(selectedDate) ?? false; _checkOnCustomDaysDisplay(); + _checkOnHourDisplay(); if (minCheck || maxCheck) { // We have minCheck === !maxCheck. final DateTime targetDate = diff --git a/lib/widgets/date_picker.dart b/lib/widgets/date_picker.dart index a58014f..41483ae 100644 --- a/lib/widgets/date_picker.dart +++ b/lib/widgets/date_picker.dart @@ -15,6 +15,7 @@ class DatePicker extends StatelessWidget { final bool showTimeSeparator; final List calendarDays; final CupertinoTextThemeData? pickerThemeData; + final SelectableHourPredicate? hourPredicate; const DatePicker({ super.key, @@ -31,6 +32,7 @@ class DatePicker extends StatelessWidget { this.showTimeSeparator = false, this.calendarDays = CupertinoDatePickerWidget.fullWeek, this.pickerThemeData, + this.hourPredicate, }); @override @@ -46,6 +48,7 @@ class DatePicker extends StatelessWidget { itemExtent: itemExtent ?? 0, showTimeSeparator: showTimeSeparator, mode: mode, + selectableHourPredicate: hourPredicate, onDateTimeChanged: onDateChanged, initialDateTime: initialDateTime, minuteInterval: minuteInterval,