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

          Line data    Source code
       1             : import 'package:flutter/material.dart';
       2             : import 'package:intl/intl.dart' as intl hide Locale;
       3             : 
       4             : import 'date_picker_mixin.dart';
       5             : import 'day_type.dart';
       6             : import 'i_selectable_picker.dart';
       7             : import 'styles/date_picker_styles.dart';
       8             : import 'styles/event_decoration.dart';
       9             : import 'styles/layout_settings.dart';
      10             : import 'utils.dart';
      11             : 
      12             : /// Widget for date pickers based on days and cover entire month.
      13             : /// Each cell of this picker is day.
      14             : class DayBasedPicker<T> extends StatelessWidget with CommonDatePickerFunctions {
      15             :   /// Selection logic.
      16             :   final ISelectablePicker selectablePicker;
      17             : 
      18             :   /// The current date at the time the picker is displayed.
      19             :   final DateTime currentDate;
      20             : 
      21             :   /// The earliest date the user is permitted to pick.
      22             :   /// (only year, month and day matter, time doesn't matter)
      23             :   final DateTime firstDate;
      24             : 
      25             :   /// The latest date the user is permitted to pick.
      26             :   /// (only year, month and day matter, time doesn't matter)
      27             :   final DateTime lastDate;
      28             : 
      29             :   /// The month whose days are displayed by this picker.
      30             :   final DateTime displayedMonth;
      31             : 
      32             :   /// Layout settings what can be customized by user
      33             :   final DatePickerLayoutSettings datePickerLayoutSettings;
      34             : 
      35             :   ///  Key fo selected month (useful for integration tests)
      36             :   final Key? selectedPeriodKey;
      37             : 
      38             :   /// Styles what can be customized by user
      39             :   final DatePickerRangeStyles datePickerStyles;
      40             : 
      41             :   /// Builder to get event decoration for each date.
      42             :   ///
      43             :   /// All event styles are overridden by selected styles
      44             :   /// except days with dayType is [DayType.notSelected].
      45             :   final EventDecorationBuilder? eventDecorationBuilder;
      46             : 
      47             :   /// Localizations used to get strings for prev/next button tooltips,
      48             :   /// weekday headers and display values for days numbers.
      49             :   ///
      50             :   // ignore: comment_references
      51             :   /// If day headers builder is provided [datePickerStyles.dayHeaderBuilder]
      52             :   /// it will be used for building weekday headers instead of localizations.
      53             :   final MaterialLocalizations localizations;
      54             : 
      55             :   /// Creates main date picker view where every cell is day.
      56           0 :   DayBasedPicker(
      57             :       {Key? key,
      58             :       required this.currentDate,
      59             :       required this.firstDate,
      60             :       required this.lastDate,
      61             :       required this.displayedMonth,
      62             :       required this.datePickerLayoutSettings,
      63             :       required this.datePickerStyles,
      64             :       required this.selectablePicker,
      65             :       required this.localizations,
      66             :       this.selectedPeriodKey,
      67             :       this.eventDecorationBuilder})
      68           0 :       : assert(!firstDate.isAfter(lastDate)),
      69           0 :         super(key: key);
      70             : 
      71           0 :   @override
      72             :   Widget build(BuildContext context) {
      73           0 :     final List<Widget> labels = <Widget>[];
      74             : 
      75           0 :     List<Widget> headers = _buildHeaders(localizations, context);
      76           0 :     List<Widget> daysBeforeMonthStart = _buildCellsBeforeStart(localizations);
      77           0 :     List<Widget> monthDays = _buildMonthCells(localizations);
      78           0 :     List<Widget> daysAfterMonthEnd = _buildCellsAfterEnd(localizations);
      79             : 
      80           0 :     labels.addAll(headers);
      81           0 :     labels.addAll(daysBeforeMonthStart);
      82           0 :     labels.addAll(monthDays);
      83           0 :     labels.addAll(daysAfterMonthEnd);
      84             : 
      85           0 :     return Padding(
      86           0 :       padding: datePickerLayoutSettings.contentPadding,
      87           0 :       child: Column(
      88           0 :         children: <Widget>[
      89           0 :           Flexible(
      90           0 :             child: GridView.custom(
      91           0 :               physics: datePickerLayoutSettings.scrollPhysics,
      92           0 :               gridDelegate: datePickerLayoutSettings.dayPickerGridDelegate,
      93             :               childrenDelegate:
      94           0 :                   SliverChildListDelegate(labels, addRepaintBoundaries: false),
      95             :             ),
      96             :           ),
      97             :         ],
      98             :       ),
      99             :     );
     100             :   }
     101             : 
     102           0 :   List<Widget> _buildHeaders(
     103             :     MaterialLocalizations localizations,
     104             :     BuildContext context,
     105             :   ) {
     106           0 :     final int firstDayOfWeekIndex = datePickerStyles.firstDayOfeWeekIndex ??
     107           0 :         localizations.firstDayOfWeekIndex;
     108             : 
     109             :     DayHeaderStyleBuilder dayHeaderStyleBuilder =
     110           0 :         datePickerStyles.dayHeaderStyleBuilder ??
     111             :             // ignore: avoid_types_on_closure_parameters
     112           0 :             (int i) => datePickerStyles.dayHeaderStyle;
     113             : 
     114           0 :     final weekdayTitles = _getWeekdayTitles(context);
     115           0 :     List<Widget> headers = getDayHeaders(
     116             :       dayHeaderStyleBuilder,
     117             :       weekdayTitles,
     118             :       firstDayOfWeekIndex,
     119             :     );
     120             : 
     121             :     return headers;
     122             :   }
     123             : 
     124           0 :   List<String> _getWeekdayTitles(BuildContext context) {
     125           0 :     final curLocale = Localizations.maybeLocaleOf(context) ?? _defaultLocale;
     126             : 
     127             :     // There is no access to weekdays full titles from [MaterialLocalizations]
     128             :     // so use intl to get it.
     129             :     final fullLocalizedWeekdayHeaders =
     130           0 :         intl.DateFormat.E(curLocale.toLanguageTag()).dateSymbols.WEEKDAYS;
     131             : 
     132           0 :     final narrowLocalizedWeekdayHeaders = localizations.narrowWeekdays;
     133             : 
     134             :     final weekdayTitles =
     135           0 :         List.generate(fullLocalizedWeekdayHeaders.length, (dayOfWeek) {
     136           0 :       final builtHeader = datePickerStyles.dayHeaderTitleBuilder
     137             :           ?.call(dayOfWeek, fullLocalizedWeekdayHeaders);
     138           0 :       final result = builtHeader ?? narrowLocalizedWeekdayHeaders[dayOfWeek];
     139             : 
     140             :       return result;
     141             :     });
     142             : 
     143             :     return weekdayTitles;
     144             :   }
     145             : 
     146           0 :   List<Widget> _buildCellsBeforeStart(MaterialLocalizations localizations) {
     147           0 :     List<Widget> result = [];
     148             : 
     149           0 :     final int year = displayedMonth.year;
     150           0 :     final int month = displayedMonth.month;
     151           0 :     final int firstDayOfWeekIndex = datePickerStyles.firstDayOfeWeekIndex ??
     152           0 :         localizations.firstDayOfWeekIndex;
     153             :     final int firstDayOffset =
     154           0 :         computeFirstDayOffset(year, month, firstDayOfWeekIndex);
     155             : 
     156           0 :     final bool showDates = datePickerLayoutSettings.showPrevMonthEnd;
     157             :     if (showDates) {
     158           0 :       int prevMonth = month - 1;
     159           0 :       if (prevMonth < 1) prevMonth = 12;
     160           0 :       int prevYear = prevMonth == 12 ? year - 1 : year;
     161             : 
     162           0 :       int daysInPrevMonth = DatePickerUtils.getDaysInMonth(prevYear, prevMonth);
     163           0 :       List<Widget> days = List.generate(firstDayOffset, (index) => index)
     164           0 :           .reversed
     165           0 :           .map((i) => daysInPrevMonth - i)
     166           0 :           .map((day) => _buildCell(prevYear, prevMonth, day))
     167           0 :           .toList();
     168             : 
     169             :       result = days;
     170             :     } else {
     171           0 :       result = List.generate(firstDayOffset, (_) => const SizedBox.shrink());
     172             :     }
     173             : 
     174             :     return result;
     175             :   }
     176             : 
     177           0 :   List<Widget> _buildMonthCells(MaterialLocalizations localizations) {
     178           0 :     List<Widget> result = [];
     179             : 
     180           0 :     final int year = displayedMonth.year;
     181           0 :     final int month = displayedMonth.month;
     182           0 :     final int daysInMonth = DatePickerUtils.getDaysInMonth(year, month);
     183             : 
     184           0 :     for (int i = 1; i <= daysInMonth; i += 1) {
     185           0 :       Widget dayWidget = _buildCell(year, month, i);
     186           0 :       result.add(dayWidget);
     187             :     }
     188             : 
     189             :     return result;
     190             :   }
     191             : 
     192           0 :   List<Widget> _buildCellsAfterEnd(MaterialLocalizations localizations) {
     193           0 :     List<Widget> result = [];
     194           0 :     final bool showDates = datePickerLayoutSettings.showNextMonthStart;
     195             :     if (!showDates) return result;
     196             : 
     197           0 :     final int year = displayedMonth.year;
     198           0 :     final int month = displayedMonth.month;
     199           0 :     final int firstDayOfWeekIndex = datePickerStyles.firstDayOfeWeekIndex ??
     200           0 :         localizations.firstDayOfWeekIndex;
     201             :     final int firstDayOffset =
     202           0 :         computeFirstDayOffset(year, month, firstDayOfWeekIndex);
     203           0 :     final int daysInMonth = DatePickerUtils.getDaysInMonth(year, month);
     204           0 :     final int totalFilledDays = firstDayOffset + daysInMonth;
     205             : 
     206           0 :     int reminder = totalFilledDays % 7;
     207           0 :     if (reminder == 0) return result;
     208           0 :     final int emptyCellsNum = 7 - reminder;
     209             : 
     210           0 :     int nextMonth = month + 1;
     211           0 :     result = List.generate(emptyCellsNum, (i) => i + 1)
     212           0 :         .map((day) => _buildCell(year, nextMonth, day))
     213           0 :         .toList();
     214             : 
     215             :     return result;
     216             :   }
     217             : 
     218           0 :   Widget _buildCell(int year, int month, int day) {
     219           0 :     DateTime dayToBuild = DateTime(year, month, day);
     220           0 :     dayToBuild = _checkDateTime(dayToBuild);
     221             : 
     222           0 :     DayType dayType = selectablePicker.getDayType(dayToBuild);
     223             : 
     224           0 :     Widget dayWidget = _DayCell(
     225             :       day: dayToBuild,
     226           0 :       currentDate: currentDate,
     227           0 :       selectablePicker: selectablePicker,
     228           0 :       datePickerStyles: datePickerStyles,
     229           0 :       eventDecorationBuilder: eventDecorationBuilder,
     230           0 :       localizations: localizations,
     231             :     );
     232             : 
     233           0 :     if (dayType != DayType.disabled) {
     234           0 :       dayWidget = GestureDetector(
     235             :         behavior: HitTestBehavior.opaque,
     236           0 :         onTap: () => selectablePicker.onDayTapped(dayToBuild),
     237             :         child: dayWidget,
     238             :       );
     239             :     }
     240             : 
     241             :     return dayWidget;
     242             :   }
     243             : 
     244             :   /// Checks if [DateTime] is same day as [lastDate] or [firstDate]
     245             :   /// and returns dt corrected (with time of [lastDate] or [firstDate]).
     246           0 :   DateTime _checkDateTime(DateTime dt) {
     247             :     DateTime result = dt;
     248             : 
     249             :     // If dayToBuild is the first day we need to save original time for it.
     250           0 :     if (DatePickerUtils.sameDate(dt, firstDate)) result = firstDate;
     251             : 
     252             :     // If dayToBuild is the last day we need to save original time for it.
     253           0 :     if (DatePickerUtils.sameDate(dt, lastDate)) result = lastDate;
     254             : 
     255             :     return result;
     256             :   }
     257             : }
     258             : 
     259             : class _DayCell extends StatelessWidget {
     260             :   /// Day for this cell.
     261             :   final DateTime day;
     262             : 
     263             :   /// Selection logic.
     264             :   final ISelectablePicker selectablePicker;
     265             : 
     266             :   /// Styles what can be customized by user
     267             :   final DatePickerRangeStyles datePickerStyles;
     268             : 
     269             :   /// The current date at the time the picker is displayed.
     270             :   final DateTime currentDate;
     271             : 
     272             :   /// Builder to get event decoration for each date.
     273             :   ///
     274             :   /// All event styles are overridden by selected styles
     275             :   /// except days with dayType is [DayType.notSelected].
     276             :   final EventDecorationBuilder? eventDecorationBuilder;
     277             : 
     278             :   final MaterialLocalizations localizations;
     279             : 
     280           0 :   const _DayCell(
     281             :       {Key? key,
     282             :       required this.day,
     283             :       required this.selectablePicker,
     284             :       required this.datePickerStyles,
     285             :       required this.currentDate,
     286             :       required this.localizations,
     287             :       this.eventDecorationBuilder})
     288           0 :       : super(key: key);
     289             : 
     290           0 :   @override
     291             :   Widget build(BuildContext context) {
     292           0 :     DayType dayType = selectablePicker.getDayType(day);
     293             : 
     294             :     BoxDecoration? decoration;
     295             :     TextStyle? itemStyle;
     296             : 
     297           0 :     if (dayType != DayType.disabled && dayType != DayType.notSelected) {
     298           0 :       itemStyle = _getSelectedTextStyle(dayType);
     299           0 :       decoration = _getSelectedDecoration(dayType);
     300           0 :     } else if (dayType == DayType.disabled) {
     301           0 :       itemStyle = datePickerStyles.disabledDateStyle;
     302           0 :     } else if (DatePickerUtils.sameDate(currentDate, day)) {
     303           0 :       itemStyle = datePickerStyles.currentDateStyle;
     304             :     } else {
     305           0 :       itemStyle = datePickerStyles.defaultDateTextStyle;
     306             :     }
     307             : 
     308             :     // Merges decoration and textStyle with [EventDecoration].
     309             :     //
     310             :     // Merges only in cases if [dayType] is DayType.notSelected.
     311             :     // If day is current day it is also gets event decoration
     312             :     // instead of decoration for current date.
     313           0 :     if (dayType == DayType.notSelected && eventDecorationBuilder != null) {
     314           0 :       EventDecoration? eDecoration = eventDecorationBuilder != null
     315           0 :           ? eventDecorationBuilder!.call(day)
     316             :           : null;
     317             : 
     318           0 :       decoration = eDecoration?.boxDecoration ?? decoration;
     319           0 :       itemStyle = eDecoration?.textStyle ?? itemStyle;
     320             :     }
     321             : 
     322           0 :     String semanticLabel = '${localizations.formatDecimal(day.day)}, '
     323           0 :         '${localizations.formatFullDate(day)}';
     324             : 
     325             :     bool daySelected =
     326           0 :         dayType != DayType.disabled && dayType != DayType.notSelected;
     327             : 
     328           0 :     Widget dayWidget = Container(
     329             :       decoration: decoration,
     330           0 :       child: Center(
     331           0 :         child: Semantics(
     332             :           // We want the day of month to be spoken first irrespective of the
     333             :           // locale-specific preferences or TextDirection. This is because
     334             :           // an accessibility user is more likely to be interested in the
     335             :           // day of month before the rest of the date, as they are looking
     336             :           // for the day of month. To do that we prepend day of month to the
     337             :           // formatted full date.
     338             :           label: semanticLabel,
     339             :           selected: daySelected,
     340           0 :           child: ExcludeSemantics(
     341           0 :             child: Text(localizations.formatDecimal(day.day), style: itemStyle),
     342             :           ),
     343             :         ),
     344             :       ),
     345             :     );
     346             : 
     347             :     return dayWidget;
     348             :   }
     349             : 
     350           0 :   BoxDecoration? _getSelectedDecoration(DayType dayType) {
     351             :     BoxDecoration? result;
     352             : 
     353           0 :     if (dayType == DayType.single) {
     354           0 :       result = datePickerStyles.selectedSingleDateDecoration;
     355           0 :     } else if (dayType == DayType.start) {
     356           0 :       result = datePickerStyles.selectedPeriodStartDecoration;
     357           0 :     } else if (dayType == DayType.end) {
     358           0 :       result = datePickerStyles.selectedPeriodLastDecoration;
     359             :     } else {
     360           0 :       result = datePickerStyles.selectedPeriodMiddleDecoration;
     361             :     }
     362             : 
     363             :     return result;
     364             :   }
     365             : 
     366           0 :   TextStyle? _getSelectedTextStyle(DayType dayType) {
     367             :     TextStyle? result;
     368             : 
     369           0 :     if (dayType == DayType.single) {
     370           0 :       result = datePickerStyles.selectedDateStyle;
     371           0 :     } else if (dayType == DayType.start) {
     372           0 :       result = datePickerStyles.selectedPeriodStartTextStyle;
     373           0 :     } else if (dayType == DayType.end) {
     374           0 :       result = datePickerStyles.selectedPeriodEndTextStyle;
     375             :     } else {
     376           0 :       result = datePickerStyles.selectedPeriodMiddleTextStyle;
     377             :     }
     378             : 
     379             :     return result;
     380             :   }
     381             : }
     382             : 
     383           0 : Locale _defaultLocale = const Locale('en', 'US');

Generated by: LCOV version 1.15