LCOV - code coverage report
Current view: top level - src - i_selectable_picker.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 105 216 48.6 %
Date: 2022-02-12 14:49:12 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : 
       3             : import 'package:flutter/material.dart';
       4             : 
       5             : import 'date_period.dart';
       6             : import 'day_picker.dart' as day_picker;
       7             : import 'day_type.dart';
       8             : import 'range_picker.dart';
       9             : import 'unselectable_period_error.dart';
      10             : import 'utils.dart';
      11             : 
      12             : /// Interface for selection logic of the different date pickers.
      13             : ///
      14             : /// T - is selection type.
      15             : abstract class ISelectablePicker<T> {
      16             :   /// The earliest date the user is permitted to pick.
      17             :   ///
      18             :   /// Time is the midnight. Otherwise [isDisabled] may return undesired result.
      19             :   final DateTime firstDate;
      20             : 
      21             :   /// The latest date the user is permitted to pick.
      22             :   ///
      23             :   /// Time is the millisecond before next day midnight.
      24             :   /// Otherwise [isDisabled] may return undesired result.
      25             :   final DateTime lastDate;
      26             : 
      27             :   /// Function returns if day can be selected or not.
      28             :   final SelectableDayPredicate _selectableDayPredicate;
      29             : 
      30             :   /// StreamController for new selection (T).
      31             :   @protected
      32             :   StreamController<T> onUpdateController = StreamController<T>.broadcast();
      33             : 
      34             :   /// Stream with new selected (T) event.
      35             :   ///
      36             :   /// Throws [UnselectablePeriodException]
      37             :   /// if there is any custom disabled date in selected.
      38           0 :   Stream<T> get onUpdate => onUpdateController.stream;
      39             : 
      40             :   /// Constructor with required fields that used in non-abstract methods
      41             :   /// ([isDisabled]).
      42           5 :   ISelectablePicker(
      43             :     DateTime firstDate,
      44             :     DateTime lastDate, {
      45             :     SelectableDayPredicate? selectableDayPredicate,
      46           5 :   })  : firstDate = DatePickerUtils.startOfTheDay(firstDate),
      47           5 :         lastDate = DatePickerUtils.endOfTheDay(lastDate),
      48             :         _selectableDayPredicate =
      49             :             selectableDayPredicate ?? _defaultSelectableDayPredicate;
      50             : 
      51             :   /// If current selection exists and includes day/days that can't be selected
      52             :   /// according to the [_selectableDayPredicate]'
      53             :   bool get curSelectionIsCorrupted;
      54             : 
      55             :   /// Returns [DayType] for given [day].
      56             :   DayType getDayType(DateTime day);
      57             : 
      58             :   /// Call when user tap on the day cell.
      59             :   void onDayTapped(DateTime selectedDate);
      60             : 
      61             :   /// Returns if given day is disabled.
      62             :   ///
      63             :   /// Returns weather given day before the beginning of the [firstDate]
      64             :   /// or after the end of the [lastDate].
      65             :   ///
      66             :   /// If [_selectableDayPredicate] is set checks it as well.
      67           4 :   @protected
      68             :   bool isDisabled(DateTime day) {
      69             :     final bool customDisabled =
      70           8 :         _selectableDayPredicate != null ? !_selectableDayPredicate(day) : false;
      71             : 
      72          16 :     return day.isAfter(lastDate) || day.isBefore(firstDate) || customDisabled;
      73             :   }
      74             : 
      75             :   /// Closes [onUpdateController].
      76             :   /// After it [onUpdateController] can't get new events.
      77           0 :   void dispose() {
      78           0 :     onUpdateController.close();
      79             :   }
      80             : 
      81           1 :   static bool _defaultSelectableDayPredicate(_) => true;
      82             : }
      83             : 
      84             : /// Selection logic for WeekPicker.
      85             : class WeekSelectable extends ISelectablePicker<DatePeriod> {
      86             :   /// Initialized in ctor body.
      87             :   late DateTime _firstDayOfSelectedWeek;
      88             : 
      89             :   /// Initialized in ctor body.
      90             :   late DateTime _lastDayOfSelectedWeek;
      91             : 
      92             :   // It is int from 0 to 6 where 0 points to Sunday and 6 points to Saturday.
      93             :   // According to MaterialLocalization.firstDayOfWeekIndex.
      94             :   final int _firstDayOfWeekIndex;
      95             : 
      96           0 :   @override
      97           0 :   bool get curSelectionIsCorrupted => _checkCurSelection();
      98             : 
      99             :   /// Creates selection logic for WeekPicker.
     100             :   ///
     101             :   /// Entire week will be selected if
     102             :   /// * it is between [firstDate] and [lastDate]
     103             :   /// * it doesn't include unselectable days according to the
     104             :   ///   [selectableDayPredicate]
     105             :   ///
     106             :   /// If one or more days of the week are before [firstDate]
     107             :   /// first selection date will be the same as [firstDate].
     108             :   ///
     109             :   /// If one or more days of the week are after [lastDate]
     110             :   /// last selection date will be the same as [lastDate].
     111             :   ///
     112             :   /// If one or more days of week are not selectable according to the
     113             :   /// [selectableDayPredicate] nothing will be returned as selection
     114             :   /// but [UnselectablePeriodException] will be thrown.
     115           1 :   WeekSelectable(DateTime selectedDate, this._firstDayOfWeekIndex,
     116             :       DateTime firstDate, DateTime lastDate,
     117             :       {SelectableDayPredicate? selectableDayPredicate})
     118           1 :       : super(firstDate, lastDate,
     119             :             selectableDayPredicate: selectableDayPredicate) {
     120           1 :     DatePeriod selectedWeek = _getNewSelectedPeriod(selectedDate);
     121           2 :     _firstDayOfSelectedWeek = selectedWeek.start;
     122           2 :     _lastDayOfSelectedWeek = selectedWeek.end;
     123           1 :     _checkCurSelection();
     124             :   }
     125             : 
     126           1 :   @override
     127             :   DayType getDayType(DateTime date) {
     128             :     DayType result;
     129             : 
     130             :     DatePeriod selectedPeriod =
     131           3 :         DatePeriod(_firstDayOfSelectedWeek, _lastDayOfSelectedWeek);
     132             :     bool selectedPeriodIsBroken =
     133           2 :         _disabledDatesInPeriod(selectedPeriod).isNotEmpty;
     134             : 
     135           1 :     if (isDisabled(date)) {
     136             :       result = DayType.disabled;
     137           1 :     } else if (_isDaySelected(date) && !selectedPeriodIsBroken) {
     138             :       DateTime firstNotDisabledDayOfSelectedWeek =
     139           3 :           _firstDayOfSelectedWeek.isBefore(firstDate)
     140           0 :               ? firstDate
     141           1 :               : _firstDayOfSelectedWeek;
     142             : 
     143             :       DateTime lastNotDisabledDayOfSelectedWeek =
     144           3 :           _lastDayOfSelectedWeek.isAfter(lastDate)
     145           0 :               ? lastDate
     146           1 :               : _lastDayOfSelectedWeek;
     147             : 
     148           1 :       if (DatePickerUtils.sameDate(date, firstNotDisabledDayOfSelectedWeek) &&
     149           1 :           DatePickerUtils.sameDate(date, lastNotDisabledDayOfSelectedWeek)) {
     150             :         result = DayType.single;
     151           2 :       } else if (DatePickerUtils.sameDate(date, _firstDayOfSelectedWeek) ||
     152           2 :           DatePickerUtils.sameDate(date, firstDate)) {
     153             :         result = DayType.start;
     154           2 :       } else if (DatePickerUtils.sameDate(date, _lastDayOfSelectedWeek) ||
     155           2 :           DatePickerUtils.sameDate(date, lastDate)) {
     156             :         result = DayType.end;
     157             :       } else {
     158             :         result = DayType.middle;
     159             :       }
     160             :     } else {
     161             :       result = DayType.notSelected;
     162             :     }
     163             : 
     164             :     return result;
     165             :   }
     166             : 
     167           0 :   @override
     168             :   void onDayTapped(DateTime selectedDate) {
     169           0 :     DatePeriod newPeriod = _getNewSelectedPeriod(selectedDate);
     170           0 :     List<DateTime> customDisabledDays = _disabledDatesInPeriod(newPeriod);
     171             : 
     172           0 :     customDisabledDays.isEmpty
     173           0 :         ? onUpdateController.add(newPeriod)
     174           0 :         : onUpdateController.addError(
     175           0 :             UnselectablePeriodException(customDisabledDays, newPeriod));
     176             :   }
     177             : 
     178             :   // Returns new selected period according to tapped date.
     179             :   // Doesn't check custom disabled days.
     180             :   // You have to check it separately if it needs.
     181           1 :   DatePeriod _getNewSelectedPeriod(DateTime tappedDay) {
     182             :     DatePeriod newPeriod;
     183             : 
     184             :     DateTime firstDayOfTappedWeek =
     185           2 :         DatePickerUtils.getFirstDayOfWeek(tappedDay, _firstDayOfWeekIndex);
     186             : 
     187             :     DateTime lastDayOfTappedWeek =
     188           2 :         DatePickerUtils.getLastDayOfWeek(tappedDay, _firstDayOfWeekIndex);
     189             : 
     190             :     DateTime firstNotDisabledDayOfSelectedWeek =
     191           2 :         firstDayOfTappedWeek.isBefore(firstDate)
     192           0 :             ? firstDate
     193             :             : firstDayOfTappedWeek;
     194             : 
     195             :     DateTime lastNotDisabledDayOfSelectedWeek =
     196           2 :         lastDayOfTappedWeek.isAfter(lastDate) ? lastDate : lastDayOfTappedWeek;
     197             : 
     198           1 :     newPeriod = DatePeriod(
     199             :         firstNotDisabledDayOfSelectedWeek, lastNotDisabledDayOfSelectedWeek);
     200             :     return newPeriod;
     201             :   }
     202             : 
     203           1 :   bool _isDaySelected(DateTime date) {
     204             :     DateTime startOfTheStartDay =
     205           2 :         DatePickerUtils.startOfTheDay(_firstDayOfSelectedWeek);
     206             :     DateTime endOfTheLastDay =
     207           2 :         DatePickerUtils.endOfTheDay(_lastDayOfSelectedWeek);
     208           1 :     return !(date.isBefore(startOfTheStartDay) ||
     209           1 :         date.isAfter(endOfTheLastDay));
     210             :   }
     211             : 
     212           1 :   List<DateTime> _disabledDatesInPeriod(DatePeriod period) {
     213           1 :     List<DateTime> result = <DateTime>[];
     214             : 
     215           1 :     var date = period.start;
     216             : 
     217           2 :     while (!date.isAfter(period.end)) {
     218           1 :       if (isDisabled(date)) result.add(date);
     219             : 
     220           2 :       date = date.add(Duration(days: 1));
     221             :     }
     222             : 
     223             :     return result;
     224             :   }
     225             : 
     226             :   // Returns if current selection contains disabled dates.
     227             :   // Returns false if there is no any selection.
     228           1 :   bool _checkCurSelection() {
     229             :     bool noSelection =
     230           2 :         _firstDayOfSelectedWeek == null || _lastDayOfSelectedWeek == null;
     231             : 
     232             :     if (noSelection) return false;
     233             : 
     234             :     DatePeriod selectedPeriod =
     235           3 :         DatePeriod(_firstDayOfSelectedWeek, _lastDayOfSelectedWeek);
     236           1 :     List<DateTime> disabledDates = _disabledDatesInPeriod(selectedPeriod);
     237             : 
     238           1 :     bool selectedPeriodIsBroken = disabledDates.isNotEmpty;
     239             :     return selectedPeriodIsBroken;
     240             :   }
     241             : }
     242             : 
     243             : /// Selection logic for [day_picker.DayPicker].
     244             : class DaySelectable extends ISelectablePicker<DateTime> {
     245             :   /// Currently selected date.
     246             :   DateTime selectedDate;
     247             : 
     248           0 :   @override
     249           0 :   bool get curSelectionIsCorrupted => _checkCurSelection();
     250             : 
     251             :   /// Creates selection logic for [day_picker.DayPicker].
     252             :   ///
     253             :   /// Every day can be selected if it is between [firstDate] and [lastDate]
     254             :   /// and not unselectable according to the [selectableDayPredicate].
     255             :   ///
     256             :   /// If day is not selectable according to the [selectableDayPredicate]
     257             :   /// nothing will be returned as selection
     258             :   /// but [UnselectablePeriodException] will be thrown.
     259           1 :   DaySelectable(this.selectedDate, DateTime firstDate, DateTime lastDate,
     260             :       {SelectableDayPredicate? selectableDayPredicate})
     261           1 :       : super(firstDate, lastDate,
     262             :             selectableDayPredicate: selectableDayPredicate);
     263             : 
     264           1 :   @override
     265             :   DayType getDayType(DateTime date) {
     266             :     DayType result;
     267             : 
     268           1 :     if (isDisabled(date)) {
     269             :       result = DayType.disabled;
     270           1 :     } else if (_isDaySelected(date)) {
     271             :       result = DayType.single;
     272             :     } else {
     273             :       result = DayType.notSelected;
     274             :     }
     275             : 
     276             :     return result;
     277             :   }
     278             : 
     279           0 :   @override
     280             :   void onDayTapped(DateTime selectedDate) {
     281           0 :     DateTime newSelected = DatePickerUtils.sameDate(firstDate, selectedDate)
     282             :         ? selectedDate
     283           0 :         : DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
     284           0 :     onUpdateController.add(newSelected);
     285             :   }
     286             : 
     287           1 :   bool _isDaySelected(DateTime date) =>
     288           2 :       DatePickerUtils.sameDate(date, selectedDate);
     289             : 
     290             :   // Returns if current selection is disabled
     291             :   // according to the [_selectableDayPredicate].
     292             :   //
     293             :   // Returns false if there is no any selection.
     294           0 :   bool _checkCurSelection() {
     295           0 :     if (selectedDate == null) return false;
     296           0 :     bool selectedIsBroken = _selectableDayPredicate(selectedDate);
     297             : 
     298             :     return selectedIsBroken;
     299             :   }
     300             : }
     301             : 
     302             : /// Selection logic for [day_picker.DayPicker] where many single days can be
     303             : /// selected.
     304             : class DayMultiSelectable extends ISelectablePicker<List<DateTime>> {
     305             :   /// Currently selected dates.
     306             :   List<DateTime> selectedDates;
     307             : 
     308             :   /// Creates selection logic for [day_picker.DayPicker].
     309             :   ///
     310             :   /// Every day can be selected if it is between [firstDate] and [lastDate]
     311             :   /// and not unselectable according to the [selectableDayPredicate].
     312             :   ///
     313             :   /// If day is not selectable according to the [selectableDayPredicate]
     314             :   /// nothing will be returned as selection
     315             :   /// but [UnselectablePeriodException] will be thrown.
     316           1 :   DayMultiSelectable(this.selectedDates, DateTime firstDate, DateTime lastDate,
     317             :       {SelectableDayPredicate? selectableDayPredicate})
     318           1 :       : super(firstDate, lastDate,
     319             :             selectableDayPredicate: selectableDayPredicate);
     320             : 
     321           0 :   @override
     322           0 :   bool get curSelectionIsCorrupted => _checkCurSelection();
     323             : 
     324           1 :   @override
     325             :   DayType getDayType(DateTime date) {
     326             :     DayType result;
     327             : 
     328           1 :     if (isDisabled(date)) {
     329             :       result = DayType.disabled;
     330           1 :     } else if (_isDaySelected(date)) {
     331             :       result = DayType.single;
     332             :     } else {
     333             :       result = DayType.notSelected;
     334             :     }
     335             : 
     336             :     return result;
     337             :   }
     338             : 
     339           0 :   @override
     340             :   void onDayTapped(DateTime selectedDate) {
     341             :     bool alreadyExist =
     342           0 :         selectedDates.any((d) => DatePickerUtils.sameDate(d, selectedDate));
     343             : 
     344             :     if (alreadyExist) {
     345           0 :       List<DateTime> newSelectedDates = List.from(selectedDates)
     346           0 :         ..removeWhere((d) => DatePickerUtils.sameDate(d, selectedDate));
     347             : 
     348           0 :       onUpdateController.add(newSelectedDates);
     349             :     } else {
     350           0 :       DateTime newSelected = DatePickerUtils.sameDate(firstDate, selectedDate)
     351             :           ? selectedDate
     352           0 :           : DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
     353             : 
     354           0 :       List<DateTime> newSelectedDates = List.from(selectedDates)
     355           0 :         ..add(newSelected);
     356             : 
     357           0 :       onUpdateController.add(newSelectedDates);
     358             :     }
     359             :   }
     360             : 
     361           1 :   bool _isDaySelected(DateTime date) =>
     362           4 :       selectedDates.any((d) => DatePickerUtils.sameDate(date, d));
     363             : 
     364             :   // Returns if current selection is disabled
     365             :   // according to the [_selectableDayPredicate].
     366             :   //
     367             :   // Returns false if there is no any selection.
     368           0 :   bool _checkCurSelection() {
     369           0 :     if (selectedDates == null || selectedDates.isEmpty) return false;
     370           0 :     bool selectedIsBroken = selectedDates.every(_selectableDayPredicate);
     371             : 
     372             :     return selectedIsBroken;
     373             :   }
     374             : }
     375             : 
     376             : /// Selection logic for [RangePicker].
     377             : class RangeSelectable extends ISelectablePicker<DatePeriod> {
     378             :   /// Initially selected period.
     379             :   ///
     380             :   /// [selectedPeriod.start] time is midnight.
     381             :   /// [selectedPeriod.end] time is millisecond before next day midnight.
     382             :   DatePeriod selectedPeriod;
     383             : 
     384           0 :   @override
     385           0 :   bool get curSelectionIsCorrupted => _checkCurSelection();
     386             : 
     387             :   /// Creates selection logic for [RangePicker].
     388             :   ///
     389             :   /// Period can be selected if
     390             :   /// * it is between [firstDate] and [lastDate]
     391             :   /// * it doesn't include unselectable days according to the
     392             :   ///   [selectableDayPredicate]
     393             :   ///
     394             :   ///
     395             :   /// If one or more days of the period are not selectable according to the
     396             :   /// [selectableDayPredicate] nothing will be returned as selection
     397             :   /// but [UnselectablePeriodException] will be thrown.
     398           1 :   RangeSelectable(
     399             :       DatePeriod selectedPeriod, DateTime firstDate, DateTime lastDate,
     400             :       {SelectableDayPredicate? selectableDayPredicate})
     401           1 :       : selectedPeriod = DatePeriod(
     402           2 :           DatePickerUtils.startOfTheDay(selectedPeriod.start),
     403           2 :           DatePickerUtils.endOfTheDay(selectedPeriod.end),
     404             :         ),
     405           1 :         super(firstDate, lastDate,
     406             :             selectableDayPredicate: selectableDayPredicate);
     407             : 
     408           1 :   @override
     409             :   DayType getDayType(DateTime date) {
     410             :     DayType result;
     411             : 
     412             :     bool selectedPeriodIsBroken =
     413           3 :         _disabledDatesInPeriod(selectedPeriod).isNotEmpty;
     414             : 
     415           1 :     if (isDisabled(date)) {
     416             :       result = DayType.disabled;
     417           1 :     } else if (_isDaySelected(date) && !selectedPeriodIsBroken) {
     418           3 :       if (DatePickerUtils.sameDate(date, selectedPeriod.start) &&
     419           3 :           DatePickerUtils.sameDate(date, selectedPeriod.end)) {
     420             :         result = DayType.single;
     421           3 :       } else if (DatePickerUtils.sameDate(date, selectedPeriod.start) ||
     422           2 :           DatePickerUtils.sameDate(date, firstDate)) {
     423             :         result = DayType.start;
     424           3 :       } else if (DatePickerUtils.sameDate(date, selectedPeriod.end) ||
     425           2 :           DatePickerUtils.sameDate(date, lastDate)) {
     426             :         result = DayType.end;
     427             :       } else {
     428             :         result = DayType.middle;
     429             :       }
     430             :     } else {
     431             :       result = DayType.notSelected;
     432             :     }
     433             : 
     434             :     return result;
     435             :   }
     436             : 
     437           0 :   @override
     438             :   void onDayTapped(DateTime selectedDate) {
     439           0 :     DatePeriod newPeriod = _getNewSelectedPeriod(selectedDate);
     440           0 :     List<DateTime> customDisabledDays = _disabledDatesInPeriod(newPeriod);
     441             : 
     442           0 :     customDisabledDays.isEmpty
     443           0 :         ? onUpdateController.add(newPeriod)
     444           0 :         : onUpdateController.addError(
     445           0 :             UnselectablePeriodException(customDisabledDays, newPeriod));
     446             :   }
     447             : 
     448             :   // Returns new selected period according to tapped date.
     449           0 :   DatePeriod _getNewSelectedPeriod(DateTime tappedDate) {
     450             :     // check if was selected only one date and we should generate period
     451             :     bool sameDate =
     452           0 :         DatePickerUtils.sameDate(selectedPeriod.start, selectedPeriod.end);
     453             :     DatePeriod newPeriod;
     454             : 
     455             :     // Was selected one-day-period.
     456             :     // With new user tap will be generated 2 dates as a period.
     457             :     if (sameDate) {
     458             :       // if user tap on the already selected single day
     459             :       bool selectedAlreadySelectedDay =
     460           0 :           DatePickerUtils.sameDate(tappedDate, selectedPeriod.end);
     461           0 :       bool isSelectedFirstDay = DatePickerUtils.sameDate(tappedDate, firstDate);
     462           0 :       bool isSelectedLastDay = DatePickerUtils.sameDate(tappedDate, lastDate);
     463             : 
     464             :       if (selectedAlreadySelectedDay) {
     465             :         if (isSelectedFirstDay && isSelectedLastDay) {
     466           0 :           newPeriod = DatePeriod(firstDate, lastDate);
     467             :         } else if (isSelectedFirstDay) {
     468             :           newPeriod =
     469           0 :               DatePeriod(firstDate, DatePickerUtils.endOfTheDay(firstDate));
     470             :         } else if (isSelectedLastDay) {
     471             :           newPeriod =
     472           0 :               DatePeriod(DatePickerUtils.startOfTheDay(lastDate), lastDate);
     473             :         } else {
     474           0 :           newPeriod = DatePeriod(DatePickerUtils.startOfTheDay(tappedDate),
     475           0 :               DatePickerUtils.endOfTheDay(tappedDate));
     476             :         }
     477             :       } else {
     478             :         DateTime startOfTheSelectedDay =
     479           0 :             DatePickerUtils.startOfTheDay(selectedPeriod.start);
     480             : 
     481           0 :         if (!tappedDate.isAfter(startOfTheSelectedDay)) {
     482           0 :           newPeriod = DatePickerUtils.sameDate(tappedDate, firstDate)
     483           0 :               ? DatePeriod(firstDate, selectedPeriod.end)
     484           0 :               : DatePeriod(DatePickerUtils.startOfTheDay(tappedDate),
     485           0 :                   selectedPeriod.end);
     486             :         } else {
     487           0 :           newPeriod = DatePickerUtils.sameDate(tappedDate, lastDate)
     488           0 :               ? DatePeriod(selectedPeriod.start, lastDate)
     489           0 :               : DatePeriod(selectedPeriod.start,
     490           0 :                   DatePickerUtils.endOfTheDay(tappedDate));
     491             :         }
     492             :       }
     493             : 
     494             :       // Was selected 2 dates as a period.
     495             :       // With new user tap new one-day-period will be generated.
     496             :     } else {
     497           0 :       bool sameAsFirst = DatePickerUtils.sameDate(tappedDate, firstDate);
     498           0 :       bool sameAsLast = DatePickerUtils.sameDate(tappedDate, lastDate);
     499             : 
     500             :       if (sameAsFirst && sameAsLast) {
     501           0 :         newPeriod = DatePeriod(firstDate, lastDate);
     502             :       } else if (sameAsFirst) {
     503             :         newPeriod =
     504           0 :             DatePeriod(firstDate, DatePickerUtils.endOfTheDay(firstDate));
     505             :       } else if (sameAsLast) {
     506             :         newPeriod =
     507           0 :             DatePeriod(DatePickerUtils.startOfTheDay(tappedDate), lastDate);
     508             :       } else {
     509           0 :         newPeriod = DatePeriod(DatePickerUtils.startOfTheDay(tappedDate),
     510           0 :             DatePickerUtils.endOfTheDay(tappedDate));
     511             :       }
     512             :     }
     513             : 
     514             :     return newPeriod;
     515             :   }
     516             : 
     517             :   // Returns if current selection contains disabled dates.
     518             :   // Returns false if there is no any selection.
     519           0 :   bool _checkCurSelection() {
     520           0 :     if (selectedPeriod == null) return false;
     521           0 :     List<DateTime> disabledDates = _disabledDatesInPeriod(selectedPeriod);
     522             : 
     523           0 :     bool selectedPeriodIsBroken = disabledDates.isNotEmpty;
     524             :     return selectedPeriodIsBroken;
     525             :   }
     526             : 
     527           1 :   List<DateTime> _disabledDatesInPeriod(DatePeriod period) {
     528           1 :     List<DateTime> result = <DateTime>[];
     529             : 
     530           1 :     var date = period.start;
     531             : 
     532           2 :     while (!date.isAfter(period.end)) {
     533           1 :       if (isDisabled(date)) result.add(date);
     534             : 
     535           2 :       date = date.add(Duration(days: 1));
     536             :     }
     537             : 
     538             :     return result;
     539             :   }
     540             : 
     541           1 :   bool _isDaySelected(DateTime date) {
     542           2 :     DateTime startOfTheStartDay = selectedPeriod.start;
     543           2 :     DateTime endOfTheLastDay = selectedPeriod.end;
     544             : 
     545           1 :     return !(date.isBefore(startOfTheStartDay) ||
     546           1 :         date.isAfter(endOfTheLastDay));
     547             :   }
     548             : }
     549             : 
     550             : /// Selection logic for [day_picker.MonthPicker.single].
     551             : class MonthSelectable extends ISelectablePicker<DateTime> {
     552             :   /// Currently selected date.
     553             :   DateTime selectedDate;
     554             : 
     555           0 :   @override
     556           0 :   bool get curSelectionIsCorrupted => _checkCurSelection();
     557             : 
     558             :   /// Creates selection logic for [day_picker.MonthPicker] with single selection.
     559             :   ///
     560             :   /// Every date can be selected if it is between [firstDate] and [lastDate]
     561             :   /// and not unselectable according to the [selectableDayPredicate].
     562             :   ///
     563             :   /// If date is not selectable according to the [selectableDayPredicate]
     564             :   /// nothing will be returned as selection
     565             :   /// but [UnselectablePeriodException] will be thrown.
     566           1 :   MonthSelectable(this.selectedDate, DateTime firstDate, DateTime lastDate,
     567             :       {SelectableDayPredicate? selectableDayPredicate})
     568           1 :       : super(firstDate, lastDate,
     569             :             selectableDayPredicate: selectableDayPredicate);
     570             : 
     571           1 :   @override
     572             :   DayType getDayType(DateTime date) {
     573             :     DayType result;
     574             : 
     575           1 :     if (isDisabled(date)) {
     576             :       result = DayType.disabled;
     577           1 :     } else if (_isDaySelected(date)) {
     578             :       result = DayType.single;
     579             :     } else {
     580             :       result = DayType.notSelected;
     581             :     }
     582             : 
     583             :     return result;
     584             :   }
     585             : 
     586           0 :   @override
     587             :   void onDayTapped(DateTime selectedDate) {
     588           0 :     DateTime newSelected = DatePickerUtils.sameMonth(firstDate, selectedDate)
     589             :         ? selectedDate
     590           0 :         : DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
     591           0 :     onUpdateController.add(newSelected);
     592             :   }
     593             : 
     594           1 :   bool _isDaySelected(DateTime date) =>
     595           2 :       DatePickerUtils.sameMonth(date, selectedDate);
     596             : 
     597             :   // Returns if current selection is disabled
     598             :   // according to the [_selectableDayPredicate].
     599             :   //
     600             :   // Returns false if there is no any selection.
     601           0 :   bool _checkCurSelection() {
     602           0 :     if (selectedDate == null) return false;
     603           0 :     bool selectedIsBroken = _selectableDayPredicate(selectedDate);
     604             : 
     605             :     return selectedIsBroken;
     606             :   }
     607             : 
     608             :   // We only need to know if month of passed day
     609             :   // before the month of the firstDate or after the month of the lastDate.
     610             :   //
     611             :   // Don't need to compare day and time.
     612           1 :   @protected
     613             :   @override
     614             :   bool isDisabled(DateTime month) {
     615             :     DateTime beginningOfTheFirstDateMonth =
     616           5 :         DateTime(firstDate.year, firstDate.month);
     617             : 
     618           1 :     DateTime endOfTheLastDateMonth = DateTime(
     619           2 :       lastDate.year,
     620           2 :       lastDate.month,
     621           5 :       DatePickerUtils.getDaysInMonth(lastDate.year, lastDate.month),
     622             :       23,
     623             :       59,
     624             :       59,
     625             :       999,
     626             :     );
     627             : 
     628           1 :     return month.isAfter(endOfTheLastDateMonth) ||
     629           1 :         month.isBefore(beginningOfTheFirstDateMonth);
     630             :   }
     631             : }
     632             : 
     633             : /// Selection logic for [day_picker.MonthPicker.multi]
     634             : /// where many single months can be selected.
     635             : class MonthMultiSelectable extends ISelectablePicker<List<DateTime>> {
     636             :   /// Currently selected dates.
     637             :   List<DateTime> selectedDates;
     638             : 
     639             :   /// Creates selection logic for [day_picker.MonthPicker] with multi selection.
     640             :   ///
     641             :   /// Every day can be selected if it is between [firstDate] and [lastDate]
     642             :   /// and not unselectable according to the [selectableDayPredicate].
     643             :   ///
     644             :   /// If day is not selectable according to the [selectableDayPredicate]
     645             :   /// nothing will be returned as selection
     646             :   /// but [UnselectablePeriodException] will be thrown.
     647           0 :   MonthMultiSelectable(
     648             :     this.selectedDates,
     649             :     DateTime firstDate,
     650             :     DateTime lastDate, {
     651             :     SelectableDayPredicate? selectableDayPredicate,
     652           0 :   }) : super(firstDate, lastDate,
     653             :             selectableDayPredicate: selectableDayPredicate);
     654             : 
     655           0 :   @override
     656           0 :   bool get curSelectionIsCorrupted => _checkCurSelection();
     657             : 
     658           0 :   @override
     659             :   DayType getDayType(DateTime date) {
     660             :     DayType result;
     661             : 
     662           0 :     if (isDisabled(date)) {
     663             :       result = DayType.disabled;
     664           0 :     } else if (_isDaySelected(date)) {
     665             :       result = DayType.single;
     666             :     } else {
     667             :       result = DayType.notSelected;
     668             :     }
     669             : 
     670             :     return result;
     671             :   }
     672             : 
     673           0 :   @override
     674             :   void onDayTapped(DateTime selectedDate) {
     675             :     bool alreadyExist =
     676           0 :         selectedDates.any((d) => DatePickerUtils.sameMonth(d, selectedDate));
     677             : 
     678             :     if (alreadyExist) {
     679           0 :       List<DateTime> newSelectedDates = List.from(selectedDates)
     680           0 :         ..removeWhere((d) => DatePickerUtils.sameMonth(d, selectedDate));
     681             : 
     682           0 :       onUpdateController.add(newSelectedDates);
     683             :     } else {
     684           0 :       DateTime newSelected = DatePickerUtils.sameMonth(firstDate, selectedDate)
     685           0 :           ? firstDate
     686           0 :           : DateTime(selectedDate.year, selectedDate.month);
     687             : 
     688           0 :       List<DateTime> newSelectedDates = List.from(selectedDates)
     689           0 :         ..add(newSelected);
     690             : 
     691           0 :       onUpdateController.add(newSelectedDates);
     692             :     }
     693             :   }
     694             : 
     695           0 :   bool _isDaySelected(DateTime date) =>
     696           0 :       selectedDates.any((d) => DatePickerUtils.sameMonth(date, d));
     697             : 
     698             :   // Returns if current selection is disabled
     699             :   // according to the [_selectableDayPredicate].
     700             :   //
     701             :   // Returns false if there is no any selection.
     702           0 :   bool _checkCurSelection() {
     703           0 :     if (selectedDates == null || selectedDates.isEmpty) return false;
     704           0 :     bool selectedIsBroken = selectedDates.every(_selectableDayPredicate);
     705             : 
     706             :     return selectedIsBroken;
     707             :   }
     708             : }

Generated by: LCOV version 1.15