LCOV - code coverage report
Current view: top level - src - day_based_changable_picker.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 154 0.0 %
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             : import 'package:flutter_localizations/flutter_localizations.dart';
       5             : 
       6             : import 'package:intl/intl.dart' as intl;
       7             : 
       8             : import 'basic_day_based_widget.dart';
       9             : import 'date_picker_keys.dart';
      10             : import 'day_based_changeable_picker_presenter.dart';
      11             : import 'day_picker_selection.dart';
      12             : import 'day_type.dart';
      13             : import 'i_selectable_picker.dart';
      14             : import 'month_navigation_row.dart';
      15             : import 'semantic_sorting.dart';
      16             : import 'styles/date_picker_styles.dart';
      17             : import 'styles/event_decoration.dart';
      18             : import 'styles/layout_settings.dart';
      19             : import 'typedefs.dart';
      20             : import 'utils.dart';
      21             : 
      22             : const Locale _defaultLocale = Locale('en', 'US');
      23             : 
      24             : /// Date picker based on [DayBasedPicker] picker (for days, weeks, ranges).
      25             : /// Allows select previous/next month.
      26             : class DayBasedChangeablePicker<T> extends StatefulWidget {
      27             :   /// The currently selected date.
      28             :   ///
      29             :   /// This date is highlighted in the picker.
      30             :   final DayPickerSelection selection;
      31             : 
      32             :   /// Called when the user picks a new T.
      33             :   final ValueChanged<T> onChanged;
      34             : 
      35             :   /// Called when the error was thrown after user selection.
      36             :   final OnSelectionError? onSelectionError;
      37             : 
      38             :   /// The earliest date the user is permitted to pick.
      39             :   final DateTime firstDate;
      40             : 
      41             :   /// The latest date the user is permitted to pick.
      42             :   final DateTime lastDate;
      43             : 
      44             :   /// Date for defining what month should be shown initially.
      45             :   ///
      46             :   /// Default value is [selection.earliest] for non empty selection
      47             :   /// and [DateTime.now()] for empty selection.
      48             :   final DateTime initiallyShowDate;
      49             : 
      50             :   /// Layout settings what can be customized by user
      51             :   final DatePickerLayoutSettings datePickerLayoutSettings;
      52             : 
      53             :   /// Styles what can be customized by user
      54             :   final DatePickerRangeStyles datePickerStyles;
      55             : 
      56             :   /// Some keys useful for integration tests
      57             :   final DatePickerKeys? datePickerKeys;
      58             : 
      59             :   /// Logic for date selections.
      60             :   final ISelectablePicker<T> selectablePicker;
      61             : 
      62             :   /// Builder to get event decoration for each date.
      63             :   ///
      64             :   /// All event styles are overridden by selected styles
      65             :   /// except days with dayType is [DayType.notSelected].
      66             :   final EventDecorationBuilder? eventDecorationBuilder;
      67             : 
      68             :   /// Called when the user changes the month
      69             :   final ValueChanged<DateTime>? onMonthChanged;
      70             : 
      71             :   /// Create picker with option to change month.
      72           0 :   DayBasedChangeablePicker(
      73             :       {Key? key,
      74             :       required this.selection,
      75             :       required this.onChanged,
      76             :       required this.firstDate,
      77             :       required this.lastDate,
      78             :       required this.datePickerLayoutSettings,
      79             :       required this.datePickerStyles,
      80             :       required this.selectablePicker,
      81             :       DateTime? initiallyShownDate,
      82             :       this.datePickerKeys,
      83             :       this.onSelectionError,
      84             :       this.eventDecorationBuilder,
      85             :       this.onMonthChanged})
      86             :       : initiallyShowDate =
      87           0 :             _getInitiallyShownDate(initiallyShownDate, selection),
      88           0 :         super(key: key);
      89             : 
      90           0 :   @override
      91             :   State<DayBasedChangeablePicker<T>> createState() =>
      92           0 :       _DayBasedChangeablePickerState<T>();
      93             : }
      94             : 
      95             : // todo: Check initial selection and call onSelectionError in case it has error
      96             : // todo: (ISelectablePicker.curSelectionIsCorrupted);
      97             : class _DayBasedChangeablePickerState<T>
      98             :     extends State<DayBasedChangeablePicker<T>> {
      99             :   DateTime _todayDate = DateTime.now();
     100             : 
     101             :   Locale curLocale = _defaultLocale;
     102             : 
     103             :   MaterialLocalizations localizations = _defaultLocalizations;
     104             : 
     105             :   PageController _dayPickerController = PageController();
     106             : 
     107             :   // Styles from widget fulfilled with current Theme.
     108             :   DatePickerRangeStyles _resultStyles = DatePickerRangeStyles();
     109             : 
     110             :   DayBasedChangeablePickerPresenter _presenter = _defaultPresenter;
     111             : 
     112             :   Timer? _timer;
     113             :   StreamSubscription<T>? _changesSubscription;
     114             : 
     115           0 :   @override
     116             :   void initState() {
     117           0 :     super.initState();
     118             : 
     119             :     // Initially display the pre-selected date.
     120           0 :     final int monthPage = _getInitPage();
     121           0 :     _dayPickerController = PageController(initialPage: monthPage);
     122             : 
     123           0 :     _changesSubscription = widget.selectablePicker.onUpdate
     124           0 :         .listen((newSelectedDate) => widget.onChanged(newSelectedDate))
     125           0 :       ..onError((e) => widget.onSelectionError != null
     126           0 :           ? widget.onSelectionError!.call(e)
     127           0 :           : print(e.toString()));
     128             : 
     129           0 :     _updateCurrentDate();
     130           0 :     _initPresenter();
     131             :   }
     132             : 
     133           0 :   @override
     134             :   void didUpdateWidget(DayBasedChangeablePicker<T> oldWidget) {
     135           0 :     super.didUpdateWidget(oldWidget);
     136             : 
     137           0 :     if (widget.datePickerStyles != oldWidget.datePickerStyles) {
     138           0 :       final ThemeData theme = Theme.of(context);
     139           0 :       _resultStyles = widget.datePickerStyles.fulfillWithTheme(theme);
     140             :     }
     141             : 
     142           0 :     if (widget.selectablePicker != oldWidget.selectablePicker) {
     143           0 :       _changesSubscription = widget.selectablePicker.onUpdate
     144           0 :           .listen((newSelectedDate) => widget.onChanged(newSelectedDate))
     145           0 :         ..onError((e) => widget.onSelectionError != null
     146           0 :             ? widget.onSelectionError!.call(e)
     147           0 :             : print(e.toString()));
     148             :     }
     149             :   }
     150             : 
     151           0 :   @override
     152             :   void didChangeDependencies() {
     153           0 :     super.didChangeDependencies();
     154           0 :     curLocale = Localizations.localeOf(context);
     155             : 
     156             :     MaterialLocalizations? curLocalizations =
     157           0 :         Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
     158           0 :     if (curLocalizations != null && localizations != curLocalizations) {
     159           0 :       localizations = curLocalizations;
     160           0 :       _initPresenter();
     161             :     }
     162             : 
     163           0 :     final ThemeData theme = Theme.of(context);
     164           0 :     _resultStyles = widget.datePickerStyles.fulfillWithTheme(theme);
     165             :   }
     166             : 
     167           0 :   @override
     168             :   // ignore: prefer_expression_function_bodies
     169             :   Widget build(BuildContext context) {
     170           0 :     return SizedBox(
     171           0 :         width: widget.datePickerLayoutSettings.monthPickerPortraitWidth,
     172           0 :         height: widget.datePickerLayoutSettings.maxDayPickerHeight,
     173           0 :         child: Column(
     174           0 :           children: <Widget>[
     175           0 :             widget.datePickerLayoutSettings.hideMonthNavigationRow
     176             :                 ? const SizedBox()
     177           0 :                 : SizedBox(
     178           0 :                     height: widget.datePickerLayoutSettings.dayPickerRowHeight,
     179           0 :                     child: Padding(
     180             :                         //match _DayPicker main layout padding
     181           0 :                         padding: widget.datePickerLayoutSettings.contentPadding,
     182           0 :                         child: _buildMonthNavigationRow()),
     183             :                   ),
     184           0 :             Expanded(
     185           0 :               child: Semantics(
     186             :                 sortKey: MonthPickerSortKey.calendar,
     187           0 :                 child: _buildDayPickerPageView(),
     188             :               ),
     189             :             ),
     190             :           ],
     191             :         ));
     192             :   }
     193             : 
     194           0 :   @override
     195             :   void dispose() {
     196           0 :     _timer?.cancel();
     197           0 :     _dayPickerController.dispose();
     198           0 :     _changesSubscription?.cancel();
     199           0 :     widget.selectablePicker.dispose();
     200           0 :     _presenter.dispose();
     201           0 :     super.dispose();
     202             :   }
     203             : 
     204           0 :   void _updateCurrentDate() {
     205           0 :     _todayDate = DateTime.now();
     206             :     final DateTime tomorrow =
     207           0 :         DateTime(_todayDate.year, _todayDate.month, _todayDate.day + 1);
     208           0 :     Duration timeUntilTomorrow = tomorrow.difference(_todayDate);
     209           0 :     timeUntilTomorrow +=
     210             :         const Duration(seconds: 1); // so we don't miss it by rounding
     211           0 :     _timer?.cancel();
     212           0 :     _timer = Timer(timeUntilTomorrow, () {
     213           0 :       setState(_updateCurrentDate);
     214             :     });
     215             :   }
     216             : 
     217             :   // ignore: prefer_expression_function_bodies
     218           0 :   Widget _buildMonthNavigationRow() {
     219           0 :     return StreamBuilder<DayBasedChangeablePickerState>(
     220           0 :         stream: _presenter.data,
     221           0 :         initialData: _presenter.lastVal,
     222           0 :         builder: (context, snapshot) {
     223           0 :           if (!snapshot.hasData) {
     224             :             return const SizedBox();
     225             :           }
     226             : 
     227           0 :           DayBasedChangeablePickerState state = snapshot.data!;
     228             : 
     229           0 :           return MonthNavigationRow(
     230           0 :             previousPageIconKey: widget.datePickerKeys?.previousPageIconKey,
     231           0 :             nextPageIconKey: widget.datePickerKeys?.nextPageIconKey,
     232           0 :             previousMonthTooltip: state.prevTooltip,
     233           0 :             nextMonthTooltip: state.nextTooltip,
     234             :             onPreviousMonthTapped:
     235           0 :                 state.isFirstMonth ? null : _presenter.gotoPrevMonth,
     236             :             onNextMonthTapped:
     237           0 :                 state.isLastMonth ? null : _presenter.gotoNextMonth,
     238           0 :             title: Text(
     239           0 :               state.curMonthDis,
     240           0 :               key: widget.datePickerKeys?.selectedPeriodKeys,
     241           0 :               style: _resultStyles.displayedPeriodTitle,
     242             :             ),
     243           0 :             nextIcon: widget.datePickerStyles.nextIcon,
     244           0 :             prevIcon: widget.datePickerStyles.prevIcon,
     245             :           );
     246             :         });
     247             :   }
     248             : 
     249           0 :   Widget _buildDayPickerPageView() => PageView.builder(
     250           0 :         controller: _dayPickerController,
     251             :         scrollDirection: Axis.horizontal,
     252             :         itemCount:
     253           0 :             DatePickerUtils.monthDelta(widget.firstDate, widget.lastDate) + 1,
     254           0 :         itemBuilder: _buildCalendar,
     255           0 :         onPageChanged: _handleMonthPageChanged,
     256             :       );
     257             : 
     258           0 :   Widget _buildCalendar(BuildContext context, int index) {
     259             :     final DateTime targetDate =
     260           0 :         DatePickerUtils.addMonthsToMonthDate(widget.firstDate, index);
     261             : 
     262           0 :     return DayBasedPicker(
     263           0 :       key: ValueKey<DateTime>(targetDate),
     264           0 :       selectablePicker: widget.selectablePicker,
     265           0 :       currentDate: _todayDate,
     266           0 :       firstDate: widget.firstDate,
     267           0 :       lastDate: widget.lastDate,
     268             :       displayedMonth: targetDate,
     269           0 :       datePickerLayoutSettings: widget.datePickerLayoutSettings,
     270           0 :       selectedPeriodKey: widget.datePickerKeys?.selectedPeriodKeys,
     271           0 :       datePickerStyles: _resultStyles,
     272           0 :       eventDecorationBuilder: widget.eventDecorationBuilder,
     273           0 :       localizations: localizations,
     274             :     );
     275             :   }
     276             : 
     277             :   // Returns appropriate date to be shown for init.
     278             :   // If [widget.initiallyShowDate] is out of bounds [widget.firstDate]
     279             :   // - [widget.lastDate], nearest bound will be used.
     280           0 :   DateTime _getCheckedInitialDate() {
     281           0 :     DateTime initiallyShowDateChecked = widget.initiallyShowDate;
     282           0 :     if (initiallyShowDateChecked.isBefore(widget.firstDate)) {
     283           0 :       initiallyShowDateChecked = widget.firstDate;
     284             :     }
     285             : 
     286           0 :     if (initiallyShowDateChecked.isAfter(widget.lastDate)) {
     287           0 :       initiallyShowDateChecked = widget.lastDate;
     288             :     }
     289             : 
     290             :     return initiallyShowDateChecked;
     291             :   }
     292             : 
     293           0 :   int _getInitPage() {
     294           0 :     final initialDate = _getCheckedInitialDate();
     295           0 :     int initPage = DatePickerUtils.monthDelta(widget.firstDate, initialDate);
     296             : 
     297             :     return initPage;
     298             :   }
     299             : 
     300           0 :   void _initPresenter() {
     301           0 :     _presenter.dispose();
     302             : 
     303           0 :     _presenter = DayBasedChangeablePickerPresenter(
     304           0 :         firstDate: widget.firstDate,
     305           0 :         lastDate: widget.lastDate,
     306           0 :         localizations: localizations,
     307           0 :         showPrevMonthDates: widget.datePickerLayoutSettings.showPrevMonthEnd,
     308           0 :         showNextMonthDates: widget.datePickerLayoutSettings.showNextMonthStart,
     309           0 :         firstDayOfWeekIndex: widget.datePickerStyles.firstDayOfeWeekIndex);
     310           0 :     _presenter.data.listen(_onStateChanged);
     311             : 
     312             :     // date used to define what month should be shown
     313           0 :     DateTime initSelection = _getCheckedInitialDate();
     314             : 
     315             :     // Give information about initial selection to presenter.
     316             :     // It should be done after first frame when PageView is already created.
     317             :     // Otherwise event from presenter will cause a error.
     318           0 :     WidgetsBinding.instance!.addPostFrameCallback((_) {
     319           0 :       _presenter.setSelectedDate(initSelection);
     320             :     });
     321             :   }
     322             : 
     323           0 :   void _onStateChanged(DayBasedChangeablePickerState newState) {
     324           0 :     DateTime newMonth = newState.currentMonth;
     325             :     final int monthPage =
     326           0 :         DatePickerUtils.monthDelta(widget.firstDate, newMonth);
     327           0 :     _dayPickerController.animateToPage(monthPage,
     328             :         duration: const Duration(milliseconds: 200), curve: Curves.easeInOut);
     329             :   }
     330             : 
     331           0 :   void _handleMonthPageChanged(int monthPage) {
     332           0 :     DateTime firstMonth = widget.firstDate;
     333           0 :     DateTime newMonth = DateTime(firstMonth.year, firstMonth.month + monthPage);
     334           0 :     _presenter.changeMonth(newMonth);
     335             : 
     336           0 :     widget.onMonthChanged?.call(newMonth);
     337             :   }
     338             : 
     339           0 :   static MaterialLocalizations get _defaultLocalizations =>
     340           0 :       MaterialLocalizationEn(
     341             :         twoDigitZeroPaddedFormat:
     342           0 :             intl.NumberFormat('00', _defaultLocale.toString()),
     343           0 :         fullYearFormat: intl.DateFormat.y(_defaultLocale.toString()),
     344           0 :         longDateFormat: intl.DateFormat.yMMMMEEEEd(_defaultLocale.toString()),
     345           0 :         shortMonthDayFormat: intl.DateFormat.MMMd(_defaultLocale.toString()),
     346             :         decimalFormat:
     347           0 :             intl.NumberFormat.decimalPattern(_defaultLocale.toString()),
     348           0 :         shortDateFormat: intl.DateFormat.yMMMd(_defaultLocale.toString()),
     349           0 :         mediumDateFormat: intl.DateFormat.MMMEd(_defaultLocale.toString()),
     350           0 :         compactDateFormat: intl.DateFormat.yMd(_defaultLocale.toString()),
     351           0 :         yearMonthFormat: intl.DateFormat.yMMMM(_defaultLocale.toString()),
     352             :       );
     353             : 
     354           0 :   static DayBasedChangeablePickerPresenter get _defaultPresenter =>
     355           0 :       DayBasedChangeablePickerPresenter(
     356           0 :           firstDate: DateTime.now(),
     357           0 :           lastDate: DateTime.now(),
     358           0 :           localizations: _defaultLocalizations,
     359             :           showPrevMonthDates: false,
     360             :           showNextMonthDates: false,
     361             :           firstDayOfWeekIndex: 1);
     362             : }
     363             : 
     364           0 : DateTime _getInitiallyShownDate(
     365             :   DateTime? initiallyShowDate,
     366             :   DayPickerSelection selection,
     367             : ) {
     368             :   if (initiallyShowDate != null) return initiallyShowDate;
     369           0 :   if (selection.isNotEmpty) return selection.earliest;
     370           0 :   return DateTime.now();
     371             : }

Generated by: LCOV version 1.15