mirror of
https://github.com/nisrulz/flutter-examples.git
synced 2025-11-09 13:09:03 +00:00
New Example - Calendar (#91)
This commit is contained in:
347
flutter_date_pickers-master/lib/src/basic_day_based_widget.dart
Normal file
347
flutter_date_pickers-master/lib/src/basic_day_based_widget.dart
Normal file
@@ -0,0 +1,347 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'date_picker_mixin.dart';
|
||||
import 'date_picker_styles.dart';
|
||||
import 'day_type.dart';
|
||||
import 'event_decoration.dart';
|
||||
import 'i_selectable_picker.dart';
|
||||
import 'layout_settings.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Widget for date pickers based on days and cover entire month.
|
||||
/// Each cell of this picker is day.
|
||||
class DayBasedPicker<T> extends StatelessWidget with CommonDatePickerFunctions {
|
||||
/// Selection logic.
|
||||
final ISelectablePicker selectablePicker;
|
||||
|
||||
/// The current date at the time the picker is displayed.
|
||||
final DateTime currentDate;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
/// (only year, month and day matter, time doesn't matter)
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
/// (only year, month and day matter, time doesn't matter)
|
||||
final DateTime lastDate;
|
||||
|
||||
/// The month whose days are displayed by this picker.
|
||||
final DateTime displayedMonth;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Key fo selected month (useful for integration tests)
|
||||
final Key? selectedPeriodKey;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerRangeStyles datePickerStyles;
|
||||
|
||||
/// Builder to get event decoration for each date.
|
||||
///
|
||||
/// All event styles are overridden by selected styles
|
||||
/// except days with dayType is [DayType.notSelected].
|
||||
final EventDecorationBuilder? eventDecorationBuilder;
|
||||
|
||||
/// Localizations used to get strings for prev/next button tooltips,
|
||||
/// weekday headers and display values for days numbers.
|
||||
final MaterialLocalizations localizations;
|
||||
|
||||
/// Creates main date picker view where every cell is day.
|
||||
DayBasedPicker(
|
||||
{Key? key,
|
||||
required this.currentDate,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
required this.displayedMonth,
|
||||
required this.datePickerLayoutSettings,
|
||||
required this.datePickerStyles,
|
||||
required this.selectablePicker,
|
||||
required this.localizations,
|
||||
this.selectedPeriodKey,
|
||||
this.eventDecorationBuilder})
|
||||
: assert(!firstDate.isAfter(lastDate)),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> labels = <Widget>[];
|
||||
|
||||
List<Widget> headers = _buildHeaders(localizations);
|
||||
List<Widget> daysBeforeMonthStart = _buildCellsBeforeStart(localizations);
|
||||
List<Widget> monthDays = _buildMonthCells(localizations);
|
||||
List<Widget> daysAfterMonthEnd = _buildCellsAfterEnd(localizations);
|
||||
|
||||
labels.addAll(headers);
|
||||
labels.addAll(daysBeforeMonthStart);
|
||||
labels.addAll(monthDays);
|
||||
labels.addAll(daysAfterMonthEnd);
|
||||
|
||||
return Padding(
|
||||
padding: datePickerLayoutSettings.contentPadding,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: GridView.custom(
|
||||
physics: datePickerLayoutSettings.scrollPhysics,
|
||||
gridDelegate: datePickerLayoutSettings.dayPickerGridDelegate,
|
||||
childrenDelegate:
|
||||
SliverChildListDelegate(labels, addRepaintBoundaries: false),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildHeaders(MaterialLocalizations localizations) {
|
||||
final int firstDayOfWeekIndex = datePickerStyles.firstDayOfeWeekIndex ??
|
||||
localizations.firstDayOfWeekIndex;
|
||||
|
||||
DayHeaderStyleBuilder dayHeaderStyleBuilder =
|
||||
datePickerStyles.dayHeaderStyleBuilder ??
|
||||
// ignore: avoid_types_on_closure_parameters
|
||||
(int i) => datePickerStyles.dayHeaderStyle;
|
||||
|
||||
List<Widget> headers = getDayHeaders(dayHeaderStyleBuilder,
|
||||
localizations.narrowWeekdays, firstDayOfWeekIndex);
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
List<Widget> _buildCellsBeforeStart(MaterialLocalizations localizations) {
|
||||
List<Widget> result = [];
|
||||
|
||||
final int year = displayedMonth.year;
|
||||
final int month = displayedMonth.month;
|
||||
final int firstDayOfWeekIndex = datePickerStyles.firstDayOfeWeekIndex ??
|
||||
localizations.firstDayOfWeekIndex;
|
||||
final int firstDayOffset =
|
||||
computeFirstDayOffset(year, month, firstDayOfWeekIndex);
|
||||
|
||||
final bool showDates = datePickerLayoutSettings.showPrevMonthEnd;
|
||||
if (showDates) {
|
||||
int prevMonth = month - 1;
|
||||
if (prevMonth < 1) prevMonth = 12;
|
||||
int prevYear = prevMonth == 12 ? year - 1 : year;
|
||||
|
||||
int daysInPrevMonth = DatePickerUtils.getDaysInMonth(prevYear, prevMonth);
|
||||
List<Widget> days = List.generate(firstDayOffset, (index) => index)
|
||||
.reversed
|
||||
.map((i) => daysInPrevMonth - i)
|
||||
.map((day) => _buildCell(prevYear, prevMonth, day))
|
||||
.toList();
|
||||
|
||||
result = days;
|
||||
} else {
|
||||
result = List.generate(firstDayOffset, (_) => const SizedBox.shrink());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Widget> _buildMonthCells(MaterialLocalizations localizations) {
|
||||
List<Widget> result = [];
|
||||
|
||||
final int year = displayedMonth.year;
|
||||
final int month = displayedMonth.month;
|
||||
final int daysInMonth = DatePickerUtils.getDaysInMonth(year, month);
|
||||
|
||||
for (int i = 1; i <= daysInMonth; i += 1) {
|
||||
Widget dayWidget = _buildCell(year, month, i);
|
||||
result.add(dayWidget);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Widget> _buildCellsAfterEnd(MaterialLocalizations localizations) {
|
||||
List<Widget> result = [];
|
||||
final bool showDates = datePickerLayoutSettings.showNextMonthStart;
|
||||
if (!showDates) return result;
|
||||
|
||||
final int year = displayedMonth.year;
|
||||
final int month = displayedMonth.month;
|
||||
final int firstDayOfWeekIndex = datePickerStyles.firstDayOfeWeekIndex ??
|
||||
localizations.firstDayOfWeekIndex;
|
||||
final int firstDayOffset =
|
||||
computeFirstDayOffset(year, month, firstDayOfWeekIndex);
|
||||
final int daysInMonth = DatePickerUtils.getDaysInMonth(year, month);
|
||||
final int totalFilledDays = firstDayOffset + daysInMonth;
|
||||
|
||||
int reminder = totalFilledDays % 7;
|
||||
if (reminder == 0) return result;
|
||||
final int emptyCellsNum = 7 - reminder;
|
||||
|
||||
int nextMonth = month + 1;
|
||||
result = List.generate(emptyCellsNum, (i) => i + 1)
|
||||
.map((day) => _buildCell(year, nextMonth, day))
|
||||
.toList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildCell(int year, int month, int day) {
|
||||
DateTime dayToBuild = DateTime(year, month, day);
|
||||
dayToBuild = _checkDateTime(dayToBuild);
|
||||
|
||||
DayType dayType = selectablePicker.getDayType(dayToBuild);
|
||||
|
||||
Widget dayWidget = _DayCell(
|
||||
day: dayToBuild,
|
||||
currentDate: currentDate,
|
||||
selectablePicker: selectablePicker,
|
||||
datePickerStyles: datePickerStyles,
|
||||
eventDecorationBuilder: eventDecorationBuilder,
|
||||
localizations: localizations,
|
||||
);
|
||||
|
||||
if (dayType != DayType.disabled) {
|
||||
dayWidget = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => selectablePicker.onDayTapped(dayToBuild),
|
||||
child: dayWidget,
|
||||
);
|
||||
}
|
||||
|
||||
return dayWidget;
|
||||
}
|
||||
|
||||
/// Checks if [DateTime] is same day as [lastDate] or [firstDate]
|
||||
/// and returns dt corrected (with time of [lastDate] or [firstDate]).
|
||||
DateTime _checkDateTime(DateTime dt) {
|
||||
DateTime result = dt;
|
||||
|
||||
// If dayToBuild is the first day we need to save original time for it.
|
||||
if (DatePickerUtils.sameDate(dt, firstDate)) result = firstDate;
|
||||
|
||||
// If dayToBuild is the last day we need to save original time for it.
|
||||
if (DatePickerUtils.sameDate(dt, lastDate)) result = lastDate;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class _DayCell extends StatelessWidget {
|
||||
/// Day for this cell.
|
||||
final DateTime day;
|
||||
|
||||
/// Selection logic.
|
||||
final ISelectablePicker selectablePicker;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerRangeStyles datePickerStyles;
|
||||
|
||||
/// The current date at the time the picker is displayed.
|
||||
final DateTime currentDate;
|
||||
|
||||
/// Builder to get event decoration for each date.
|
||||
///
|
||||
/// All event styles are overridden by selected styles
|
||||
/// except days with dayType is [DayType.notSelected].
|
||||
final EventDecorationBuilder? eventDecorationBuilder;
|
||||
|
||||
final MaterialLocalizations localizations;
|
||||
|
||||
const _DayCell(
|
||||
{Key? key,
|
||||
required this.day,
|
||||
required this.selectablePicker,
|
||||
required this.datePickerStyles,
|
||||
required this.currentDate,
|
||||
required this.localizations,
|
||||
this.eventDecorationBuilder})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
DayType dayType = selectablePicker.getDayType(day);
|
||||
|
||||
BoxDecoration? decoration;
|
||||
TextStyle? itemStyle;
|
||||
|
||||
if (dayType != DayType.disabled && dayType != DayType.notSelected) {
|
||||
itemStyle = _getSelectedTextStyle(dayType);
|
||||
decoration = _getSelectedDecoration(dayType);
|
||||
} else if (dayType == DayType.disabled) {
|
||||
itemStyle = datePickerStyles.disabledDateStyle;
|
||||
} else if (DatePickerUtils.sameDate(currentDate, day)) {
|
||||
itemStyle = datePickerStyles.currentDateStyle;
|
||||
} else {
|
||||
itemStyle = datePickerStyles.defaultDateTextStyle;
|
||||
}
|
||||
|
||||
// Merges decoration and textStyle with [EventDecoration].
|
||||
//
|
||||
// Merges only in cases if [dayType] is DayType.notSelected.
|
||||
// If day is current day it is also gets event decoration
|
||||
// instead of decoration for current date.
|
||||
if (dayType == DayType.notSelected && eventDecorationBuilder != null) {
|
||||
EventDecoration? eDecoration = eventDecorationBuilder != null
|
||||
? eventDecorationBuilder!.call(day)
|
||||
: null;
|
||||
|
||||
decoration = eDecoration?.boxDecoration ?? decoration;
|
||||
itemStyle = eDecoration?.textStyle ?? itemStyle;
|
||||
}
|
||||
|
||||
String semanticLabel = '${localizations.formatDecimal(day.day)}, '
|
||||
'${localizations.formatFullDate(day)}';
|
||||
|
||||
bool daySelected =
|
||||
dayType != DayType.disabled && dayType != DayType.notSelected;
|
||||
|
||||
Widget dayWidget = Container(
|
||||
decoration: decoration,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
// We want the day of month to be spoken first irrespective of the
|
||||
// locale-specific preferences or TextDirection. This is because
|
||||
// an accessibility user is more likely to be interested in the
|
||||
// day of month before the rest of the date, as they are looking
|
||||
// for the day of month. To do that we prepend day of month to the
|
||||
// formatted full date.
|
||||
label: semanticLabel,
|
||||
selected: daySelected,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(localizations.formatDecimal(day.day), style: itemStyle),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return dayWidget;
|
||||
}
|
||||
|
||||
BoxDecoration? _getSelectedDecoration(DayType dayType) {
|
||||
BoxDecoration? result;
|
||||
|
||||
if (dayType == DayType.single) {
|
||||
result = datePickerStyles.selectedSingleDateDecoration;
|
||||
} else if (dayType == DayType.start) {
|
||||
result = datePickerStyles.selectedPeriodStartDecoration;
|
||||
} else if (dayType == DayType.end) {
|
||||
result = datePickerStyles.selectedPeriodLastDecoration;
|
||||
} else {
|
||||
result = datePickerStyles.selectedPeriodMiddleDecoration;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TextStyle? _getSelectedTextStyle(DayType dayType) {
|
||||
TextStyle? result;
|
||||
|
||||
if (dayType == DayType.single) {
|
||||
result = datePickerStyles.selectedDateStyle;
|
||||
} else if (dayType == DayType.start) {
|
||||
result = datePickerStyles.selectedPeriodStartTextStyle;
|
||||
} else if (dayType == DayType.end) {
|
||||
result = datePickerStyles.selectedPeriodEndTextStyle;
|
||||
} else {
|
||||
result = datePickerStyles.selectedPeriodMiddleTextStyle;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
13
flutter_date_pickers-master/lib/src/date_period.dart
Normal file
13
flutter_date_pickers-master/lib/src/date_period.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
/// Date period.
|
||||
class DatePeriod {
|
||||
/// Start of this period.
|
||||
final DateTime start;
|
||||
|
||||
/// End of this period.
|
||||
final DateTime end;
|
||||
|
||||
///
|
||||
const DatePeriod(this.start, this.end)
|
||||
: assert(start != null),
|
||||
assert(end != null);
|
||||
}
|
||||
19
flutter_date_pickers-master/lib/src/date_picker_keys.dart
Normal file
19
flutter_date_pickers-master/lib/src/date_picker_keys.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Keys for some date picker's widgets.
|
||||
///
|
||||
/// Useful for integration tests to find widgets.
|
||||
class DatePickerKeys {
|
||||
/// Key for the previous page icon widget.
|
||||
final Key previousPageIconKey;
|
||||
|
||||
/// Key for the next page icon widget.
|
||||
final Key nextPageIconKey;
|
||||
|
||||
/// Key for showing month.
|
||||
final Key selectedPeriodKeys;
|
||||
|
||||
///
|
||||
DatePickerKeys(
|
||||
this.previousPageIconKey, this.nextPageIconKey, this.selectedPeriodKeys);
|
||||
}
|
||||
99
flutter_date_pickers-master/lib/src/date_picker_mixin.dart
Normal file
99
flutter_date_pickers-master/lib/src/date_picker_mixin.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'date_picker_styles.dart';
|
||||
|
||||
///
|
||||
mixin CommonDatePickerFunctions {
|
||||
|
||||
/// Builds widgets showing abbreviated days of week. The first widget in the
|
||||
/// returned list corresponds to the first day of week for the current locale.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// ```
|
||||
/// ┌ Sunday is the first day of week in the US (en_US)
|
||||
/// |
|
||||
/// S M T W T F S <-- the returned list contains these widgets
|
||||
/// _ _ _ _ _ 1 2
|
||||
/// 3 4 5 6 7 8 9
|
||||
///
|
||||
/// ┌ But it's Monday in the UK (en_GB)
|
||||
/// |
|
||||
/// M T W T F S S <-- the returned list contains these widgets
|
||||
/// _ _ _ _ 1 2 3
|
||||
/// 4 5 6 7 8 9 10
|
||||
/// ```
|
||||
List<Widget> getDayHeaders(
|
||||
DayHeaderStyleBuilder headerStyleBuilder,
|
||||
List<String> narrowWeekdays,
|
||||
int firstDayOfWeekIndex) {
|
||||
|
||||
final List<Widget> result = <Widget>[];
|
||||
|
||||
for (int i = firstDayOfWeekIndex; true; i = (i + 1) % 7) {
|
||||
DayHeaderStyle? headerStyle = headerStyleBuilder(i);
|
||||
final String weekday = narrowWeekdays[i];
|
||||
|
||||
Widget header = ExcludeSemantics(
|
||||
child: Container(
|
||||
decoration: headerStyle?.decoration,
|
||||
child: Center(
|
||||
child: Text(
|
||||
weekday,
|
||||
style: headerStyle?.textStyle
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
result.add(header);
|
||||
if (i == (firstDayOfWeekIndex - 1) % 7) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Computes the offset from the first day of week that the first day of the
|
||||
/// [month] falls on.
|
||||
///
|
||||
/// For example, September 1, 2017 falls on a Friday, which in the calendar
|
||||
/// localized for United States English appears as:
|
||||
///
|
||||
/// ```
|
||||
/// S M T W T F S
|
||||
/// _ _ _ _ _ 1 2
|
||||
/// ```
|
||||
///
|
||||
/// The offset for the first day of the months is the number of leading blanks
|
||||
/// in the calendar, i.e. 5.
|
||||
///
|
||||
/// The same date localized for the Russian calendar has a different offset,
|
||||
/// because the first day of week is Monday rather than Sunday:
|
||||
///
|
||||
/// ```
|
||||
/// M T W T F S S
|
||||
/// _ _ _ _ 1 2 3
|
||||
/// ```
|
||||
///
|
||||
/// So the offset is 4, rather than 5.
|
||||
///
|
||||
/// This code consolidates the following:
|
||||
///
|
||||
/// - [DateTime.weekday] provides a 1-based index into days of week, with 1
|
||||
/// falling on Monday.
|
||||
/// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
|
||||
/// into the [MaterialLocalizations.narrowWeekdays] list.
|
||||
/// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
|
||||
/// days of week, always starting with Sunday and ending with Saturday.
|
||||
int computeFirstDayOffset(
|
||||
int year, int month, int firstDayOfWeekFromSunday) {
|
||||
// 0-based day of week, with 0 representing Monday.
|
||||
final int weekdayFromMonday = DateTime(year, month).weekday - 1;
|
||||
// firstDayOfWeekFromSunday recomputed to be Monday-based
|
||||
final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
|
||||
// Number of days between the first day of week appearing on the calendar,
|
||||
// and the day corresponding to the 1-st of the month.
|
||||
return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
|
||||
}
|
||||
}
|
||||
386
flutter_date_pickers-master/lib/src/date_picker_styles.dart
Normal file
386
flutter_date_pickers-master/lib/src/date_picker_styles.dart
Normal file
@@ -0,0 +1,386 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'range_picker.dart';
|
||||
import 'week_picker.dart';
|
||||
|
||||
/// 0 points to Sunday, and 6 points to Saturday.
|
||||
typedef DayHeaderStyleBuilder = DayHeaderStyle? Function(int dayOfTheWeek);
|
||||
|
||||
/// Common styles for date pickers.
|
||||
///
|
||||
/// To define more styles for date pickers which allow select some range
|
||||
/// (e.g. [RangePicker], [WeekPicker]) use [DatePickerRangeStyles].
|
||||
@immutable
|
||||
class DatePickerStyles {
|
||||
/// Styles for title of displayed period
|
||||
/// (e.g. month for day picker and year for month picker).
|
||||
final TextStyle? displayedPeriodTitle;
|
||||
|
||||
/// Style for the number of current date.
|
||||
final TextStyle? currentDateStyle;
|
||||
|
||||
/// Style for the numbers of disabled dates.
|
||||
final TextStyle? disabledDateStyle;
|
||||
|
||||
/// Style for the number of selected date.
|
||||
final TextStyle? selectedDateStyle;
|
||||
|
||||
/// Used for date which is neither current nor disabled nor selected.
|
||||
final TextStyle? defaultDateTextStyle;
|
||||
|
||||
/// Day cell decoration for selected date in case only one date is selected.
|
||||
final BoxDecoration? selectedSingleDateDecoration;
|
||||
|
||||
/// Style for the day header.
|
||||
///
|
||||
/// If you need to customize day header's style depends on day of the week
|
||||
/// use [dayHeaderStyleBuilder] instead.
|
||||
final DayHeaderStyle? dayHeaderStyle;
|
||||
|
||||
/// Builder to customize styles for day headers depends on day of the week.
|
||||
/// Where 0 points to Sunday and 6 points to Saturday.
|
||||
///
|
||||
/// Builder must return not null value for every weekday from 0 to 6.
|
||||
///
|
||||
/// If styles should be the same for any day of the week
|
||||
/// use [dayHeaderStyle] instead.
|
||||
final DayHeaderStyleBuilder? dayHeaderStyleBuilder;
|
||||
|
||||
/// Widget which will be shown left side of the shown page title.
|
||||
/// User goes to previous data period by click on it.
|
||||
final Widget prevIcon;
|
||||
|
||||
/// Widget which will be shown right side of the shown page title.
|
||||
/// User goes to next data period by click on it.
|
||||
final Widget nextIcon;
|
||||
|
||||
/// Index of the first day of week, where 0 points to Sunday, and 6 points to
|
||||
/// Saturday. Must not be less 0 or more then 6.
|
||||
///
|
||||
/// Can be null. In this case value from current locale will be used.
|
||||
final int? firstDayOfeWeekIndex;
|
||||
|
||||
/// Styles for date picker.
|
||||
DatePickerStyles({
|
||||
this.displayedPeriodTitle,
|
||||
this.currentDateStyle,
|
||||
this.disabledDateStyle,
|
||||
this.selectedDateStyle,
|
||||
this.selectedSingleDateDecoration,
|
||||
this.defaultDateTextStyle,
|
||||
this.dayHeaderStyleBuilder,
|
||||
this.dayHeaderStyle,
|
||||
this.firstDayOfeWeekIndex,
|
||||
this.prevIcon = const Icon(Icons.chevron_left),
|
||||
this.nextIcon = const Icon(Icons.chevron_right)
|
||||
}) : assert(!(dayHeaderStyle != null && dayHeaderStyleBuilder != null),
|
||||
"Should be only one from: dayHeaderStyleBuilder, dayHeaderStyle."),
|
||||
assert(dayHeaderStyleBuilder == null
|
||||
|| _validateDayHeaderStyleBuilder(dayHeaderStyleBuilder),
|
||||
"dayHeaderStyleBuilder must return not null value from every weekday "
|
||||
"(from 0 to 6)."),
|
||||
assert(_validateFirstDayOfWeek(firstDayOfeWeekIndex),
|
||||
"firstDayOfeWeekIndex must be null or in correct range (from 0 to 6).");
|
||||
|
||||
/// Return new [DatePickerStyles] object where fields
|
||||
/// with null values set with defaults from theme.
|
||||
DatePickerStyles fulfillWithTheme(ThemeData theme) {
|
||||
Color accentColor = theme.accentColor;
|
||||
|
||||
TextStyle? _displayedPeriodTitle =
|
||||
displayedPeriodTitle ?? theme.textTheme.subtitle1;
|
||||
TextStyle? _currentDateStyle = currentDateStyle ??
|
||||
theme.textTheme.bodyText1?.copyWith(color: theme.accentColor);
|
||||
TextStyle? _disabledDateStyle = disabledDateStyle ??
|
||||
theme.textTheme.bodyText2?.copyWith(color: theme.disabledColor);
|
||||
TextStyle? _selectedDateStyle =
|
||||
selectedDateStyle ?? theme.accentTextTheme.bodyText1;
|
||||
TextStyle? _defaultDateTextStyle =
|
||||
defaultDateTextStyle ?? theme.textTheme.bodyText2;
|
||||
BoxDecoration _selectedSingleDateDecoration =
|
||||
selectedSingleDateDecoration ??
|
||||
BoxDecoration(
|
||||
color: accentColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)));
|
||||
|
||||
DayHeaderStyle? _dayHeaderStyle = dayHeaderStyle;
|
||||
if (dayHeaderStyleBuilder == null && _dayHeaderStyle == null) {
|
||||
_dayHeaderStyle = DayHeaderStyle(textStyle: theme.textTheme.caption);
|
||||
}
|
||||
|
||||
return DatePickerStyles(
|
||||
disabledDateStyle: _disabledDateStyle,
|
||||
currentDateStyle: _currentDateStyle,
|
||||
displayedPeriodTitle: _displayedPeriodTitle,
|
||||
selectedDateStyle: _selectedDateStyle,
|
||||
selectedSingleDateDecoration: _selectedSingleDateDecoration,
|
||||
defaultDateTextStyle: _defaultDateTextStyle,
|
||||
dayHeaderStyle: _dayHeaderStyle,
|
||||
dayHeaderStyleBuilder: dayHeaderStyleBuilder,
|
||||
nextIcon: nextIcon,
|
||||
prevIcon: prevIcon
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
|
||||
return other is DatePickerStyles
|
||||
&& other.displayedPeriodTitle == displayedPeriodTitle
|
||||
&& other.currentDateStyle == currentDateStyle
|
||||
&& other.disabledDateStyle == disabledDateStyle
|
||||
&& other.selectedDateStyle == selectedDateStyle
|
||||
&& other.defaultDateTextStyle == defaultDateTextStyle
|
||||
&& other.selectedSingleDateDecoration == selectedSingleDateDecoration
|
||||
&& other.dayHeaderStyle == dayHeaderStyle
|
||||
&& other.dayHeaderStyleBuilder == dayHeaderStyleBuilder
|
||||
&& other.prevIcon == prevIcon
|
||||
&& other.nextIcon == nextIcon
|
||||
&& other.firstDayOfeWeekIndex == firstDayOfeWeekIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
hashValues(
|
||||
displayedPeriodTitle,
|
||||
currentDateStyle,
|
||||
disabledDateStyle,
|
||||
selectedDateStyle,
|
||||
defaultDateTextStyle,
|
||||
selectedSingleDateDecoration,
|
||||
dayHeaderStyle,
|
||||
dayHeaderStyleBuilder,
|
||||
prevIcon,
|
||||
nextIcon,
|
||||
firstDayOfeWeekIndex
|
||||
);
|
||||
|
||||
static bool _validateDayHeaderStyleBuilder(DayHeaderStyleBuilder builder) {
|
||||
List<int> weekdays = const [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
// ignore: avoid_types_on_closure_parameters
|
||||
bool valid = weekdays.every((int weekday) => builder(weekday) != null);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool _validateFirstDayOfWeek(int? index) {
|
||||
if (index == null) return true;
|
||||
|
||||
bool valid = index >= 0 && index <= 6;
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
|
||||
/// Styles for date pickers which allow select some range
|
||||
/// (e.g. RangePicker, WeekPicker).
|
||||
@immutable
|
||||
class DatePickerRangeStyles extends DatePickerStyles {
|
||||
/// Decoration for the first date of the selected range.
|
||||
final BoxDecoration? selectedPeriodStartDecoration;
|
||||
|
||||
/// Text style for the first date of the selected range.
|
||||
///
|
||||
/// If null - default [DatePickerStyles.selectedDateStyle] will be used.
|
||||
final TextStyle? selectedPeriodStartTextStyle;
|
||||
|
||||
/// Decoration for the last date of the selected range.
|
||||
final BoxDecoration? selectedPeriodLastDecoration;
|
||||
|
||||
/// Text style for the last date of the selected range.
|
||||
///
|
||||
/// If null - default [DatePickerStyles.selectedDateStyle] will be used.
|
||||
final TextStyle? selectedPeriodEndTextStyle;
|
||||
|
||||
/// Decoration for the date of the selected range
|
||||
/// which is not first date and not end date of this range.
|
||||
///
|
||||
/// If there is only one date selected
|
||||
/// [DatePickerStyles.selectedSingleDateDecoration] will be used.
|
||||
final BoxDecoration? selectedPeriodMiddleDecoration;
|
||||
|
||||
/// Text style for the middle date of the selected range.
|
||||
///
|
||||
/// If null - default [DatePickerStyles.selectedDateStyle] will be used.
|
||||
final TextStyle? selectedPeriodMiddleTextStyle;
|
||||
|
||||
/// Return new [DatePickerRangeStyles] object
|
||||
/// where fields with null values set with defaults from given theme.
|
||||
@override
|
||||
DatePickerRangeStyles fulfillWithTheme(ThemeData theme) {
|
||||
Color accentColor = theme.accentColor;
|
||||
|
||||
DatePickerStyles commonStyles = super.fulfillWithTheme(theme);
|
||||
|
||||
final BoxDecoration _selectedPeriodStartDecoration =
|
||||
selectedPeriodStartDecoration ??
|
||||
BoxDecoration(
|
||||
color: accentColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(10.0),
|
||||
bottomLeft: Radius.circular(10.0)),
|
||||
);
|
||||
|
||||
final BoxDecoration _selectedPeriodLastDecoration =
|
||||
selectedPeriodLastDecoration ??
|
||||
BoxDecoration(
|
||||
color: accentColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(10.0),
|
||||
bottomRight: Radius.circular(10.0)),
|
||||
);
|
||||
|
||||
final BoxDecoration _selectedPeriodMiddleDecoration =
|
||||
selectedPeriodMiddleDecoration ??
|
||||
BoxDecoration(
|
||||
color: accentColor,
|
||||
shape: BoxShape.rectangle,
|
||||
);
|
||||
|
||||
final TextStyle? _selectedPeriodStartTextStyle =
|
||||
selectedPeriodStartTextStyle ?? commonStyles.selectedDateStyle;
|
||||
|
||||
final TextStyle? _selectedPeriodMiddleTextStyle =
|
||||
selectedPeriodMiddleTextStyle ?? commonStyles.selectedDateStyle;
|
||||
|
||||
final TextStyle? _selectedPeriodEndTextStyle =
|
||||
selectedPeriodEndTextStyle ?? commonStyles.selectedDateStyle;
|
||||
|
||||
return DatePickerRangeStyles(
|
||||
disabledDateStyle: commonStyles.disabledDateStyle,
|
||||
currentDateStyle: commonStyles.currentDateStyle,
|
||||
displayedPeriodTitle: commonStyles.displayedPeriodTitle,
|
||||
selectedDateStyle: commonStyles.selectedDateStyle,
|
||||
selectedSingleDateDecoration: commonStyles.selectedSingleDateDecoration,
|
||||
defaultDateTextStyle: commonStyles.defaultDateTextStyle,
|
||||
dayHeaderStyle: commonStyles.dayHeaderStyle,
|
||||
dayHeaderStyleBuilder: commonStyles.dayHeaderStyleBuilder,
|
||||
firstDayOfWeekIndex: firstDayOfeWeekIndex,
|
||||
selectedPeriodStartDecoration: _selectedPeriodStartDecoration,
|
||||
selectedPeriodMiddleDecoration: _selectedPeriodMiddleDecoration,
|
||||
selectedPeriodLastDecoration: _selectedPeriodLastDecoration,
|
||||
selectedPeriodStartTextStyle: _selectedPeriodStartTextStyle,
|
||||
selectedPeriodMiddleTextStyle: _selectedPeriodMiddleTextStyle,
|
||||
selectedPeriodEndTextStyle: _selectedPeriodEndTextStyle,
|
||||
);
|
||||
}
|
||||
|
||||
/// Styles for the pickers that allows to select range ([RangePicker],
|
||||
/// [WeekPicker]).
|
||||
DatePickerRangeStyles({
|
||||
displayedPeriodTitle,
|
||||
currentDateStyle,
|
||||
disabledDateStyle,
|
||||
selectedDateStyle,
|
||||
selectedSingleDateDecoration,
|
||||
defaultDateTextStyle,
|
||||
dayHeaderStyle,
|
||||
dayHeaderStyleBuilder,
|
||||
Widget nextIcon = const Icon(Icons.chevron_right),
|
||||
Widget prevIcon = const Icon(Icons.chevron_left),
|
||||
firstDayOfWeekIndex,
|
||||
this.selectedPeriodLastDecoration,
|
||||
this.selectedPeriodMiddleDecoration,
|
||||
this.selectedPeriodStartDecoration,
|
||||
this.selectedPeriodStartTextStyle,
|
||||
this.selectedPeriodMiddleTextStyle,
|
||||
this.selectedPeriodEndTextStyle,
|
||||
}) : super(
|
||||
displayedPeriodTitle: displayedPeriodTitle,
|
||||
currentDateStyle: currentDateStyle,
|
||||
disabledDateStyle: disabledDateStyle,
|
||||
selectedDateStyle: selectedDateStyle,
|
||||
selectedSingleDateDecoration: selectedSingleDateDecoration,
|
||||
defaultDateTextStyle: defaultDateTextStyle,
|
||||
dayHeaderStyle: dayHeaderStyle,
|
||||
dayHeaderStyleBuilder: dayHeaderStyleBuilder,
|
||||
nextIcon: nextIcon,
|
||||
prevIcon: prevIcon,
|
||||
firstDayOfeWeekIndex: firstDayOfWeekIndex
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
|
||||
return other is DatePickerRangeStyles
|
||||
&& other.selectedPeriodStartDecoration == selectedPeriodStartDecoration
|
||||
&& other.selectedPeriodStartTextStyle == selectedPeriodStartTextStyle
|
||||
&& other.selectedPeriodLastDecoration == selectedPeriodLastDecoration
|
||||
&& other.selectedPeriodEndTextStyle == selectedPeriodEndTextStyle
|
||||
&& other.selectedPeriodMiddleDecoration ==selectedPeriodMiddleDecoration
|
||||
&& other.selectedPeriodMiddleTextStyle == selectedPeriodMiddleTextStyle
|
||||
&& other.displayedPeriodTitle == displayedPeriodTitle
|
||||
&& other.currentDateStyle == currentDateStyle
|
||||
&& other.disabledDateStyle == disabledDateStyle
|
||||
&& other.selectedDateStyle == selectedDateStyle
|
||||
&& other.defaultDateTextStyle == defaultDateTextStyle
|
||||
&& other.selectedSingleDateDecoration == selectedSingleDateDecoration
|
||||
&& other.dayHeaderStyle == dayHeaderStyle
|
||||
&& other.dayHeaderStyleBuilder == dayHeaderStyleBuilder
|
||||
&& other.prevIcon == prevIcon
|
||||
&& other.nextIcon == nextIcon
|
||||
&& other.firstDayOfeWeekIndex == firstDayOfeWeekIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
hashValues(
|
||||
selectedPeriodStartDecoration,
|
||||
selectedPeriodStartTextStyle,
|
||||
selectedPeriodLastDecoration,
|
||||
selectedPeriodEndTextStyle,
|
||||
selectedPeriodMiddleDecoration,
|
||||
selectedPeriodMiddleTextStyle,
|
||||
displayedPeriodTitle,
|
||||
currentDateStyle,
|
||||
disabledDateStyle,
|
||||
selectedDateStyle,
|
||||
defaultDateTextStyle,
|
||||
selectedSingleDateDecoration,
|
||||
dayHeaderStyle,
|
||||
dayHeaderStyleBuilder,
|
||||
prevIcon,
|
||||
nextIcon,
|
||||
firstDayOfeWeekIndex
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// User styles for the day header in date picker.
|
||||
@immutable
|
||||
class DayHeaderStyle {
|
||||
/// If null - textTheme.caption from the Theme will be used.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// If null - no decoration will be applied for the day header;
|
||||
final BoxDecoration? decoration;
|
||||
|
||||
/// Creates styles for the day headers in date pickers.
|
||||
///
|
||||
/// See also:
|
||||
/// * [DatePickerStyles.dayHeaderStyleBuilder]
|
||||
const DayHeaderStyle({
|
||||
this.textStyle,
|
||||
this.decoration
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
|
||||
return other is DayHeaderStyle
|
||||
&& other.textStyle == textStyle
|
||||
&& other.decoration == decoration;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(
|
||||
textStyle,
|
||||
decoration
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'basic_day_based_widget.dart';
|
||||
import 'date_picker_keys.dart';
|
||||
import 'date_picker_styles.dart';
|
||||
import 'day_based_changeable_picker_presenter.dart';
|
||||
import 'day_picker_selection.dart';
|
||||
import 'day_type.dart';
|
||||
import 'event_decoration.dart';
|
||||
import 'i_selectable_picker.dart';
|
||||
import 'layout_settings.dart';
|
||||
import 'month_navigation_row.dart';
|
||||
import 'semantic_sorting.dart';
|
||||
import 'typedefs.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
|
||||
const Locale _defaultLocale = Locale('en', 'US');
|
||||
|
||||
/// Date picker based on [DayBasedPicker] picker (for days, weeks, ranges).
|
||||
/// Allows select previous/next month.
|
||||
class DayBasedChangeablePicker<T> extends StatefulWidget {
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DayPickerSelection selection;
|
||||
|
||||
/// Called when the user picks a new T.
|
||||
final ValueChanged<T> onChanged;
|
||||
|
||||
/// Called when the error was thrown after user selection.
|
||||
final OnSelectionError? onSelectionError;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Date for defining what month should be shown initially.
|
||||
///
|
||||
/// Default value is [selection.earliest].
|
||||
final DateTime initiallyShowDate;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerRangeStyles datePickerStyles;
|
||||
|
||||
/// Some keys useful for integration tests
|
||||
final DatePickerKeys? datePickerKeys;
|
||||
|
||||
/// Logic for date selections.
|
||||
final ISelectablePicker<T> selectablePicker;
|
||||
|
||||
/// Builder to get event decoration for each date.
|
||||
///
|
||||
/// All event styles are overridden by selected styles
|
||||
/// except days with dayType is [DayType.notSelected].
|
||||
final EventDecorationBuilder? eventDecorationBuilder;
|
||||
|
||||
/// Called when the user changes the month
|
||||
final ValueChanged<DateTime>? onMonthChanged;
|
||||
|
||||
/// Create picker with option to change month.
|
||||
DayBasedChangeablePicker(
|
||||
{Key? key,
|
||||
required this.selection,
|
||||
required this.onChanged,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
required this.datePickerLayoutSettings,
|
||||
required this.datePickerStyles,
|
||||
required this.selectablePicker,
|
||||
DateTime? initiallyShownDate,
|
||||
this.datePickerKeys,
|
||||
this.onSelectionError,
|
||||
this.eventDecorationBuilder,
|
||||
this.onMonthChanged})
|
||||
: initiallyShowDate = initiallyShownDate ?? selection.earliest,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<DayBasedChangeablePicker<T>> createState() =>
|
||||
_DayBasedChangeablePickerState<T>();
|
||||
}
|
||||
|
||||
// todo: Check initial selection and call onSelectionError in case it has error
|
||||
// todo: (ISelectablePicker.curSelectionIsCorrupted);
|
||||
class _DayBasedChangeablePickerState<T>
|
||||
extends State<DayBasedChangeablePicker<T>> {
|
||||
DateTime _todayDate = DateTime.now();
|
||||
|
||||
Locale curLocale = _defaultLocale;
|
||||
|
||||
MaterialLocalizations localizations = _defaultLocalizations;
|
||||
|
||||
PageController _dayPickerController = PageController();
|
||||
|
||||
// Styles from widget fulfilled with current Theme.
|
||||
DatePickerRangeStyles _resultStyles = DatePickerRangeStyles();
|
||||
|
||||
DayBasedChangeablePickerPresenter _presenter = _defaultPresenter;
|
||||
|
||||
Timer? _timer;
|
||||
StreamSubscription<T>? _changesSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Initially display the pre-selected date.
|
||||
final int monthPage = _getInitPage();
|
||||
_dayPickerController = PageController(initialPage: monthPage);
|
||||
|
||||
_changesSubscription = widget.selectablePicker.onUpdate
|
||||
.listen((newSelectedDate) => widget.onChanged(newSelectedDate))
|
||||
..onError((e) => widget.onSelectionError != null
|
||||
? widget.onSelectionError!.call(e)
|
||||
: print(e.toString()));
|
||||
|
||||
_updateCurrentDate();
|
||||
_initPresenter();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DayBasedChangeablePicker<T> oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (widget.datePickerStyles != oldWidget.datePickerStyles) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
_resultStyles = widget.datePickerStyles.fulfillWithTheme(theme);
|
||||
}
|
||||
|
||||
if (widget.selectablePicker != oldWidget.selectablePicker) {
|
||||
_changesSubscription = widget.selectablePicker.onUpdate
|
||||
.listen((newSelectedDate) => widget.onChanged(newSelectedDate))
|
||||
..onError((e) => widget.onSelectionError != null
|
||||
? widget.onSelectionError!.call(e)
|
||||
: print(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
curLocale = Localizations.localeOf(context);
|
||||
|
||||
MaterialLocalizations? curLocalizations =
|
||||
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
|
||||
if (curLocalizations != null && localizations != curLocalizations) {
|
||||
localizations = curLocalizations;
|
||||
_initPresenter();
|
||||
}
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
_resultStyles = widget.datePickerStyles.fulfillWithTheme(theme);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: widget.datePickerLayoutSettings.monthPickerPortraitWidth,
|
||||
height: widget.datePickerLayoutSettings.maxDayPickerHeight,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
widget.datePickerLayoutSettings.hideMonthNavigationRow
|
||||
? const SizedBox()
|
||||
: SizedBox(
|
||||
height: widget.datePickerLayoutSettings.dayPickerRowHeight,
|
||||
child: Padding(
|
||||
//match _DayPicker main layout padding
|
||||
padding: widget.datePickerLayoutSettings.contentPadding,
|
||||
child: _buildMonthNavigationRow()),
|
||||
),
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
sortKey: MonthPickerSortKey.calendar,
|
||||
child: _buildDayPickerPageView(),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_dayPickerController.dispose();
|
||||
_changesSubscription?.cancel();
|
||||
widget.selectablePicker.dispose();
|
||||
_presenter.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateCurrentDate() {
|
||||
_todayDate = DateTime.now();
|
||||
final DateTime tomorrow =
|
||||
DateTime(_todayDate.year, _todayDate.month, _todayDate.day + 1);
|
||||
Duration timeUntilTomorrow = tomorrow.difference(_todayDate);
|
||||
timeUntilTomorrow +=
|
||||
const Duration(seconds: 1); // so we don't miss it by rounding
|
||||
_timer?.cancel();
|
||||
_timer = Timer(timeUntilTomorrow, () {
|
||||
setState(_updateCurrentDate);
|
||||
});
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget _buildMonthNavigationRow() {
|
||||
return StreamBuilder<DayBasedChangeablePickerState>(
|
||||
stream: _presenter.data,
|
||||
initialData: _presenter.lastVal,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
DayBasedChangeablePickerState state = snapshot.data!;
|
||||
|
||||
return MonthNavigationRow(
|
||||
previousPageIconKey: widget.datePickerKeys?.previousPageIconKey,
|
||||
nextPageIconKey: widget.datePickerKeys?.nextPageIconKey,
|
||||
previousMonthTooltip: state.prevTooltip,
|
||||
nextMonthTooltip: state.nextTooltip,
|
||||
onPreviousMonthTapped:
|
||||
state.isFirstMonth ? null : _presenter.gotoPrevMonth,
|
||||
onNextMonthTapped:
|
||||
state.isLastMonth ? null : _presenter.gotoNextMonth,
|
||||
title: Text(
|
||||
state.curMonthDis,
|
||||
key: widget.datePickerKeys?.selectedPeriodKeys,
|
||||
style: _resultStyles.displayedPeriodTitle,
|
||||
),
|
||||
nextIcon: widget.datePickerStyles.nextIcon,
|
||||
prevIcon: widget.datePickerStyles.prevIcon,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildDayPickerPageView() => PageView.builder(
|
||||
controller: _dayPickerController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount:
|
||||
DatePickerUtils.monthDelta(widget.firstDate, widget.lastDate) + 1,
|
||||
itemBuilder: _buildCalendar,
|
||||
onPageChanged: _handleMonthPageChanged,
|
||||
);
|
||||
|
||||
Widget _buildCalendar(BuildContext context, int index) {
|
||||
final DateTime targetDate =
|
||||
DatePickerUtils.addMonthsToMonthDate(widget.firstDate, index);
|
||||
|
||||
return DayBasedPicker(
|
||||
key: ValueKey<DateTime>(targetDate),
|
||||
selectablePicker: widget.selectablePicker,
|
||||
currentDate: _todayDate,
|
||||
firstDate: widget.firstDate,
|
||||
lastDate: widget.lastDate,
|
||||
displayedMonth: targetDate,
|
||||
datePickerLayoutSettings: widget.datePickerLayoutSettings,
|
||||
selectedPeriodKey: widget.datePickerKeys?.selectedPeriodKeys,
|
||||
datePickerStyles: _resultStyles,
|
||||
eventDecorationBuilder: widget.eventDecorationBuilder,
|
||||
localizations: localizations,
|
||||
);
|
||||
}
|
||||
|
||||
// Returns appropriate date to be shown for init.
|
||||
// If [widget.initiallyShowDate] is out of bounds [widget.firstDate]
|
||||
// - [widget.lastDate], nearest bound will be used.
|
||||
DateTime _getCheckedInitialDate() {
|
||||
DateTime initiallyShowDateChecked = widget.initiallyShowDate;
|
||||
if (initiallyShowDateChecked.isBefore(widget.firstDate)) {
|
||||
initiallyShowDateChecked = widget.firstDate;
|
||||
}
|
||||
|
||||
if (initiallyShowDateChecked.isAfter(widget.lastDate)) {
|
||||
initiallyShowDateChecked = widget.lastDate;
|
||||
}
|
||||
|
||||
return initiallyShowDateChecked;
|
||||
}
|
||||
|
||||
int _getInitPage() {
|
||||
final initialDate = _getCheckedInitialDate();
|
||||
int initPage = DatePickerUtils.monthDelta(
|
||||
widget.firstDate, initialDate
|
||||
);
|
||||
|
||||
return initPage;
|
||||
}
|
||||
|
||||
void _initPresenter() {
|
||||
_presenter.dispose();
|
||||
|
||||
_presenter = DayBasedChangeablePickerPresenter(
|
||||
firstDate: widget.firstDate,
|
||||
lastDate: widget.lastDate,
|
||||
localizations: localizations,
|
||||
showPrevMonthDates: widget.datePickerLayoutSettings.showPrevMonthEnd,
|
||||
showNextMonthDates: widget.datePickerLayoutSettings.showNextMonthStart,
|
||||
firstDayOfWeekIndex: widget.datePickerStyles.firstDayOfeWeekIndex);
|
||||
_presenter.data.listen(_onStateChanged);
|
||||
|
||||
// date used to define what month should be shown
|
||||
DateTime initSelection = _getCheckedInitialDate();
|
||||
|
||||
// Give information about initial selection to presenter.
|
||||
// It should be done after first frame when PageView is already created.
|
||||
// Otherwise event from presenter will cause a error.
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
_presenter.setSelectedDate(initSelection);
|
||||
});
|
||||
}
|
||||
|
||||
void _onStateChanged(DayBasedChangeablePickerState newState) {
|
||||
DateTime newMonth = newState.currentMonth;
|
||||
final int monthPage =
|
||||
DatePickerUtils.monthDelta(widget.firstDate, newMonth);
|
||||
_dayPickerController.animateToPage(monthPage,
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.easeInOut);
|
||||
}
|
||||
|
||||
void _handleMonthPageChanged(int monthPage) {
|
||||
DateTime firstMonth = widget.firstDate;
|
||||
DateTime newMonth = DateTime(firstMonth.year, firstMonth.month + monthPage);
|
||||
_presenter.changeMonth(newMonth);
|
||||
|
||||
widget.onMonthChanged?.call(newMonth);
|
||||
}
|
||||
|
||||
static MaterialLocalizations get _defaultLocalizations =>
|
||||
MaterialLocalizationEn(
|
||||
twoDigitZeroPaddedFormat:
|
||||
intl.NumberFormat('00', _defaultLocale.toString()),
|
||||
fullYearFormat: intl.DateFormat.y(_defaultLocale.toString()),
|
||||
longDateFormat: intl.DateFormat.yMMMMEEEEd(_defaultLocale.toString()),
|
||||
shortMonthDayFormat: intl.DateFormat.MMMd(_defaultLocale.toString()),
|
||||
decimalFormat:
|
||||
intl.NumberFormat.decimalPattern(_defaultLocale.toString()),
|
||||
shortDateFormat: intl.DateFormat.yMMMd(_defaultLocale.toString()),
|
||||
mediumDateFormat: intl.DateFormat.MMMEd(_defaultLocale.toString()),
|
||||
compactDateFormat: intl.DateFormat.yMd(_defaultLocale.toString()),
|
||||
yearMonthFormat: intl.DateFormat.yMMMM(_defaultLocale.toString()),
|
||||
);
|
||||
|
||||
static DayBasedChangeablePickerPresenter get _defaultPresenter =>
|
||||
DayBasedChangeablePickerPresenter(
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime.now(),
|
||||
localizations: _defaultLocalizations,
|
||||
showPrevMonthDates: false,
|
||||
showNextMonthDates: false,
|
||||
firstDayOfWeekIndex: 1);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'day_based_changable_picker.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Presenter for [DayBasedChangeablePicker] to handle month changes.
|
||||
class DayBasedChangeablePickerPresenter {
|
||||
/// First date user can select.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// Last date user can select.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Localization.
|
||||
final MaterialLocalizations localizations;
|
||||
|
||||
/// If empty day cells before 1st day of showing month should be filled with
|
||||
/// date from the last week of the previous month.
|
||||
final bool showPrevMonthDates;
|
||||
|
||||
/// If empty day cells after last day of showing month should be filled with
|
||||
/// date from the first week of the next month.
|
||||
final bool showNextMonthDates;
|
||||
|
||||
/// Index of the first day in week.
|
||||
/// 0 is Sunday, 6 is Saturday.
|
||||
final int firstDayOfWeekIndex;
|
||||
|
||||
/// View model stream for the [DayBasedChangeablePicker].
|
||||
Stream<DayBasedChangeablePickerState> get data => _controller.stream;
|
||||
|
||||
/// Last view model state of the [DayBasedChangeablePicker].
|
||||
DayBasedChangeablePickerState? get lastVal => _lastVal;
|
||||
|
||||
/// Creates presenter to use for [DayBasedChangeablePicker].
|
||||
DayBasedChangeablePickerPresenter({
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
required this.localizations,
|
||||
required this.showPrevMonthDates,
|
||||
required this.showNextMonthDates,
|
||||
int? firstDayOfWeekIndex
|
||||
}): firstDayOfWeekIndex = firstDayOfWeekIndex
|
||||
?? localizations.firstDayOfWeekIndex;
|
||||
|
||||
/// Update state according to the [selectedDate] if it needs.
|
||||
void setSelectedDate(DateTime selectedDate) {
|
||||
// bool firstAndLastNotNull = _firstShownDate != null
|
||||
// && _lastShownDate != null;
|
||||
//
|
||||
// bool selectedOnCurPage = firstAndLastNotNull
|
||||
// && !selectedDate.isBefore(_firstShownDate)
|
||||
// && !selectedDate.isAfter(_lastShownDate);
|
||||
// if (selectedOnCurPage) return;
|
||||
|
||||
changeMonth(selectedDate);
|
||||
}
|
||||
|
||||
/// Update state to show previous month.
|
||||
void gotoPrevMonth() {
|
||||
DateTime oldCur = _lastVal!.currentMonth;
|
||||
DateTime newCurDate = DateTime(oldCur.year, oldCur.month - 1);
|
||||
|
||||
changeMonth(newCurDate);
|
||||
}
|
||||
|
||||
/// Update state to show next month.
|
||||
void gotoNextMonth() {
|
||||
DateTime oldCur = _lastVal!.currentMonth;
|
||||
DateTime newCurDate = DateTime(oldCur.year, oldCur.month + 1);
|
||||
|
||||
changeMonth(newCurDate);
|
||||
}
|
||||
|
||||
/// Update state to change month to the [newMonth].
|
||||
void changeMonth(DateTime newMonth) {
|
||||
bool sameMonth = _lastVal != null
|
||||
&& DatePickerUtils.sameMonth(_lastVal!.currentMonth, newMonth);
|
||||
if (sameMonth) return;
|
||||
|
||||
int monthPage = DatePickerUtils.monthDelta(firstDate, newMonth);
|
||||
DateTime prevMonth = DatePickerUtils
|
||||
.addMonthsToMonthDate(firstDate, monthPage - 1);
|
||||
|
||||
DateTime curMonth = DatePickerUtils
|
||||
.addMonthsToMonthDate(firstDate, monthPage);
|
||||
|
||||
DateTime nextMonth = DatePickerUtils
|
||||
.addMonthsToMonthDate(firstDate, monthPage + 1);
|
||||
|
||||
String prevMonthStr = localizations.formatMonthYear(prevMonth);
|
||||
String curMonthStr = localizations.formatMonthYear(curMonth);
|
||||
String nextMonthStr = localizations.formatMonthYear(nextMonth);
|
||||
|
||||
bool isFirstMonth = DatePickerUtils.sameMonth(curMonth, firstDate);
|
||||
bool isLastMonth = DatePickerUtils.sameMonth(curMonth, lastDate);
|
||||
|
||||
String? prevTooltip = isFirstMonth
|
||||
? null
|
||||
: "${localizations.previousMonthTooltip} $prevMonthStr";
|
||||
|
||||
String? nextTooltip = isLastMonth
|
||||
? null
|
||||
: "${localizations.nextMonthTooltip} $nextMonthStr";
|
||||
|
||||
DayBasedChangeablePickerState newState = DayBasedChangeablePickerState(
|
||||
currentMonth: curMonth,
|
||||
curMonthDis: curMonthStr,
|
||||
prevMonthDis: prevMonthStr,
|
||||
nextMonthDis: nextMonthStr,
|
||||
prevTooltip: prevTooltip,
|
||||
nextTooltip: nextTooltip,
|
||||
isFirstMonth: isFirstMonth,
|
||||
isLastMonth: isLastMonth
|
||||
);
|
||||
|
||||
_updateState(newState);
|
||||
}
|
||||
|
||||
/// Closes controller.
|
||||
void dispose () {
|
||||
_controller.close();
|
||||
}
|
||||
|
||||
void _updateState(DayBasedChangeablePickerState newState) {
|
||||
_lastVal = newState;
|
||||
_controller.add(newState);
|
||||
}
|
||||
|
||||
final StreamController<DayBasedChangeablePickerState> _controller =
|
||||
StreamController.broadcast();
|
||||
|
||||
DayBasedChangeablePickerState? _lastVal;
|
||||
}
|
||||
|
||||
|
||||
/// View Model for the [DayBasedChangeablePicker].
|
||||
class DayBasedChangeablePickerState {
|
||||
|
||||
/// Display name of the current month.
|
||||
final String curMonthDis;
|
||||
|
||||
/// Display name of the previous month.
|
||||
final String prevMonthDis;
|
||||
|
||||
/// Display name of the next month.
|
||||
final String nextMonthDis;
|
||||
|
||||
/// Tooltip for the previous month icon.
|
||||
final String? prevTooltip;
|
||||
|
||||
/// Tooltip for the next month icon.
|
||||
final String? nextTooltip;
|
||||
|
||||
/// Tooltip for the current month icon.
|
||||
final DateTime currentMonth;
|
||||
|
||||
/// If selected month is the month contains last date user can select.
|
||||
final bool isLastMonth;
|
||||
|
||||
/// If selected month is the month contains first date user can select.
|
||||
final bool isFirstMonth;
|
||||
|
||||
/// Creates view model for the [DayBasedChangeablePicker].
|
||||
DayBasedChangeablePickerState({
|
||||
required this.curMonthDis,
|
||||
required this.prevMonthDis,
|
||||
required this.nextMonthDis,
|
||||
required this.currentMonth,
|
||||
required this.isLastMonth,
|
||||
required this.isFirstMonth,
|
||||
this.prevTooltip,
|
||||
this.nextTooltip,
|
||||
});
|
||||
}
|
||||
189
flutter_date_pickers-master/lib/src/day_picker.dart
Normal file
189
flutter_date_pickers-master/lib/src/day_picker.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'date_picker_keys.dart';
|
||||
import 'date_picker_styles.dart';
|
||||
import 'day_based_changable_picker.dart';
|
||||
import 'day_picker_selection.dart';
|
||||
import 'day_type.dart';
|
||||
import 'event_decoration.dart';
|
||||
import 'i_selectable_picker.dart';
|
||||
import 'layout_settings.dart';
|
||||
|
||||
/// Date picker for selection one day.
|
||||
class DayPicker<T extends Object> extends StatelessWidget {
|
||||
DayPicker._({Key? key,
|
||||
required this.onChanged,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
required this.selectionLogic,
|
||||
required this.selection,
|
||||
this.initiallyShowDate,
|
||||
this.datePickerLayoutSettings = const DatePickerLayoutSettings(),
|
||||
this.datePickerStyles,
|
||||
this.datePickerKeys,
|
||||
this.selectableDayPredicate,
|
||||
this.eventDecorationBuilder,
|
||||
this.onMonthChanged}) : super(key: key);
|
||||
|
||||
/// Creates a day picker where only one single day can be selected.
|
||||
///
|
||||
/// See also:
|
||||
/// * [DayPicker.multi] - day picker where many single days can be selected.
|
||||
static DayPicker<DateTime> single({
|
||||
Key? key,
|
||||
required DateTime selectedDate,
|
||||
required ValueChanged<DateTime> onChanged,
|
||||
required DateTime firstDate,
|
||||
required DateTime lastDate,
|
||||
DatePickerLayoutSettings datePickerLayoutSettings
|
||||
= const DatePickerLayoutSettings(),
|
||||
DateTime? initiallyShowDate,
|
||||
DatePickerRangeStyles? datePickerStyles,
|
||||
DatePickerKeys? datePickerKeys,
|
||||
SelectableDayPredicate? selectableDayPredicate,
|
||||
EventDecorationBuilder? eventDecorationBuilder,
|
||||
ValueChanged<DateTime>? onMonthChanged
|
||||
})
|
||||
{
|
||||
assert(!firstDate.isAfter(lastDate));
|
||||
assert(!lastDate.isBefore(firstDate));
|
||||
assert(!selectedDate.isBefore(firstDate));
|
||||
assert(!selectedDate.isAfter(lastDate));
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isAfter(lastDate));
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isBefore(firstDate));
|
||||
|
||||
final selection = DayPickerSingleSelection(selectedDate);
|
||||
final selectionLogic = DaySelectable(
|
||||
selectedDate, firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
return DayPicker<DateTime>._(
|
||||
onChanged: onChanged,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
initiallyShowDate: initiallyShowDate,
|
||||
selectionLogic: selectionLogic,
|
||||
selection: selection,
|
||||
eventDecorationBuilder: eventDecorationBuilder,
|
||||
onMonthChanged: onMonthChanged,
|
||||
selectableDayPredicate: selectableDayPredicate,
|
||||
datePickerKeys: datePickerKeys,
|
||||
datePickerStyles: datePickerStyles,
|
||||
datePickerLayoutSettings: datePickerLayoutSettings,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// Creates a day picker where many single days can be selected.
|
||||
///
|
||||
/// See also:
|
||||
/// * [DayPicker.single] - day picker where only one single day
|
||||
/// can be selected.
|
||||
static DayPicker<List<DateTime>> multi({Key? key,
|
||||
required List<DateTime> selectedDates,
|
||||
required ValueChanged<List<DateTime>> onChanged,
|
||||
required DateTime firstDate,
|
||||
required DateTime lastDate,
|
||||
DatePickerLayoutSettings datePickerLayoutSettings
|
||||
= const DatePickerLayoutSettings(),
|
||||
DateTime? initiallyShowDate,
|
||||
DatePickerRangeStyles? datePickerStyles,
|
||||
DatePickerKeys? datePickerKeys,
|
||||
SelectableDayPredicate? selectableDayPredicate,
|
||||
EventDecorationBuilder? eventDecorationBuilder,
|
||||
ValueChanged<DateTime>? onMonthChanged})
|
||||
{
|
||||
assert(!firstDate.isAfter(lastDate));
|
||||
assert(!lastDate.isBefore(firstDate));
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isAfter(lastDate));
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isBefore(lastDate));
|
||||
|
||||
final selection = DayPickerMultiSelection(selectedDates);
|
||||
final selectionLogic = DayMultiSelectable(
|
||||
selectedDates, firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
return DayPicker<List<DateTime>>._(
|
||||
onChanged: onChanged,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
initiallyShowDate: initiallyShowDate,
|
||||
selectionLogic: selectionLogic,
|
||||
selection: selection,
|
||||
eventDecorationBuilder: eventDecorationBuilder,
|
||||
onMonthChanged: onMonthChanged,
|
||||
selectableDayPredicate: selectableDayPredicate,
|
||||
datePickerKeys: datePickerKeys,
|
||||
datePickerStyles: datePickerStyles,
|
||||
datePickerLayoutSettings: datePickerLayoutSettings,
|
||||
);
|
||||
}
|
||||
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DayPickerSelection selection;
|
||||
|
||||
/// Called when the user picks a day.
|
||||
final ValueChanged<T> onChanged;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Date for defining what month should be shown initially.
|
||||
///
|
||||
/// In case of null earliest of the [selection] will be shown.
|
||||
final DateTime? initiallyShowDate;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerRangeStyles? datePickerStyles;
|
||||
|
||||
/// Some keys useful for integration tests
|
||||
final DatePickerKeys? datePickerKeys;
|
||||
|
||||
/// Function returns if day can be selected or not.
|
||||
///
|
||||
/// If null
|
||||
final SelectableDayPredicate? selectableDayPredicate;
|
||||
|
||||
/// Builder to get event decoration for each date.
|
||||
///
|
||||
/// All event styles are overriden by selected styles
|
||||
/// except days with dayType is [DayType.notSelected].
|
||||
final EventDecorationBuilder? eventDecorationBuilder;
|
||||
|
||||
// Called when the user changes the month.
|
||||
/// New DateTime object represents first day of new month and 00:00 time.
|
||||
final ValueChanged<DateTime>? onMonthChanged;
|
||||
|
||||
/// Logic to handle user's selections.
|
||||
final ISelectablePicker<T> selectionLogic;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return DayBasedChangeablePicker<T>(
|
||||
selectablePicker: selectionLogic,
|
||||
selection: selection,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
initiallyShownDate: initiallyShowDate,
|
||||
onChanged: onChanged,
|
||||
datePickerLayoutSettings: datePickerLayoutSettings,
|
||||
datePickerStyles: datePickerStyles ?? DatePickerRangeStyles(),
|
||||
datePickerKeys: datePickerKeys,
|
||||
eventDecorationBuilder: eventDecorationBuilder,
|
||||
onMonthChanged: onMonthChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
120
flutter_date_pickers-master/lib/src/day_picker_selection.dart
Normal file
120
flutter_date_pickers-master/lib/src/day_picker_selection.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
import 'date_period.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Base class for day based pickers selection.
|
||||
abstract class DayPickerSelection {
|
||||
|
||||
/// If this is before [dateTime].
|
||||
bool isBefore(DateTime dateTime);
|
||||
|
||||
/// If this is after [dateTime].
|
||||
bool isAfter(DateTime dateTime);
|
||||
|
||||
/// Returns earliest [DateTime] in this selection.
|
||||
DateTime get earliest;
|
||||
|
||||
/// If this selection is empty.
|
||||
bool get isEmpty;
|
||||
|
||||
/// If this selection is not empty.
|
||||
bool get isNotEmpty;
|
||||
|
||||
/// Constructor to allow children to have constant constructor.
|
||||
const DayPickerSelection();
|
||||
}
|
||||
|
||||
/// Selection with only one selected date.
|
||||
///
|
||||
/// See also:
|
||||
/// * [DayPickerMultiSelection] - selection with one or many single dates.
|
||||
/// * [DayPickerRangeSelection] - date period selection.
|
||||
class DayPickerSingleSelection extends DayPickerSelection {
|
||||
|
||||
/// Selected date.
|
||||
final DateTime selectedDate;
|
||||
|
||||
/// Creates selection with only one selected date.
|
||||
const DayPickerSingleSelection(this.selectedDate)
|
||||
: assert(selectedDate != null);
|
||||
|
||||
@override
|
||||
bool isAfter(DateTime dateTime) => selectedDate.isAfter(dateTime);
|
||||
|
||||
@override
|
||||
bool isBefore(DateTime dateTime) => selectedDate.isAfter(dateTime);
|
||||
|
||||
@override
|
||||
DateTime get earliest => selectedDate;
|
||||
|
||||
@override
|
||||
bool get isEmpty => selectedDate == null;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => selectedDate != null;
|
||||
}
|
||||
|
||||
|
||||
/// Selection with one or many single dates.
|
||||
///
|
||||
/// See also:
|
||||
/// * [DayPickerSingleSelection] - selection with only one selected date.
|
||||
/// * [DayPickerRangeSelection] - date period selection.
|
||||
class DayPickerMultiSelection extends DayPickerSelection {
|
||||
|
||||
/// List of the selected dates.
|
||||
final List<DateTime> selectedDates;
|
||||
|
||||
/// Selection with one or many single dates.
|
||||
DayPickerMultiSelection(this.selectedDates)
|
||||
: assert(selectedDates != null);
|
||||
|
||||
|
||||
@override
|
||||
bool isAfter(DateTime dateTime)
|
||||
=> selectedDates.every((d) => d.isAfter(dateTime));
|
||||
|
||||
@override
|
||||
bool isBefore(DateTime dateTime)
|
||||
=> selectedDates.every((d) => d.isBefore(dateTime));
|
||||
|
||||
@override
|
||||
DateTime get earliest => DatePickerUtils.getEarliestFromList(selectedDates);
|
||||
|
||||
@override
|
||||
bool get isEmpty => selectedDates.isEmpty;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => selectedDates.isNotEmpty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Date period selection.
|
||||
///
|
||||
/// See also:
|
||||
/// * [DayPickerSingleSelection] - selection with only one selected date.
|
||||
/// * [DayPickerMultiSelection] - selection with one or many single dates.
|
||||
class DayPickerRangeSelection extends DayPickerSelection {
|
||||
|
||||
/// Selected period.
|
||||
final DatePeriod selectedRange;
|
||||
|
||||
/// Date period selection.
|
||||
const DayPickerRangeSelection(this.selectedRange)
|
||||
: assert(selectedRange != null);
|
||||
|
||||
@override
|
||||
DateTime get earliest => selectedRange.start;
|
||||
|
||||
@override
|
||||
bool isAfter(DateTime dateTime) => selectedRange.start.isAfter(dateTime);
|
||||
|
||||
@override
|
||||
bool isBefore(DateTime dateTime) => selectedRange.end.isBefore(dateTime);
|
||||
|
||||
@override
|
||||
bool get isEmpty => selectedRange == null;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => selectedRange != null;
|
||||
}
|
||||
20
flutter_date_pickers-master/lib/src/day_type.dart
Normal file
20
flutter_date_pickers-master/lib/src/day_type.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
/// Type of the day in day based date picker.
|
||||
enum DayType {
|
||||
/// start of the selected period
|
||||
start,
|
||||
|
||||
/// middle of the selected period
|
||||
middle,
|
||||
|
||||
/// end of the selected period
|
||||
end,
|
||||
|
||||
/// selected single day
|
||||
single,
|
||||
|
||||
/// disabled day
|
||||
disabled,
|
||||
|
||||
/// not selected day (but not disabled)
|
||||
notSelected
|
||||
}
|
||||
35
flutter_date_pickers-master/lib/src/event_decoration.dart
Normal file
35
flutter_date_pickers-master/lib/src/event_decoration.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'day_picker.dart';
|
||||
import 'range_picker.dart';
|
||||
import 'week_picker.dart';
|
||||
|
||||
|
||||
/// Signature for function which is used to set set specific decoration for
|
||||
/// some days in [DayPicker], [WeekPicker] and [RangePicker].
|
||||
///
|
||||
/// See also:
|
||||
/// * [DayPicker.eventDecorationBuilder]
|
||||
/// * [WeekPicker.eventDecorationBuilder]
|
||||
/// * [RangePicker.eventDecorationBuilder]
|
||||
typedef EventDecorationBuilder = EventDecoration? Function(DateTime date);
|
||||
|
||||
|
||||
/// Class to store styles for event (specific day in the date picker).
|
||||
@immutable
|
||||
class EventDecoration {
|
||||
|
||||
/// Cell decoration for the specific day in the date picker (event).
|
||||
final BoxDecoration? boxDecoration;
|
||||
|
||||
/// Style for number of the specific day in the date picker (event).
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// Creates decoration for special day.
|
||||
///
|
||||
/// Used for [EventDecorationBuilder] function which is usually passed to
|
||||
/// [DayPicker.eventDecorationBuilder], [WeekPicker.eventDecorationBuilder]
|
||||
/// and [RangePicker.eventDecorationBuilder] to set specific decoration for
|
||||
/// some days.
|
||||
const EventDecoration({this.boxDecoration, this.textStyle});
|
||||
}
|
||||
537
flutter_date_pickers-master/lib/src/i_selectable_picker.dart
Normal file
537
flutter_date_pickers-master/lib/src/i_selectable_picker.dart
Normal file
@@ -0,0 +1,537 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'date_period.dart';
|
||||
import 'day_picker.dart' as day_picker;
|
||||
import 'day_type.dart';
|
||||
import 'range_picker.dart';
|
||||
import 'unselectable_period_error.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/// Interface for selection logic of the different date pickers.
|
||||
///
|
||||
/// T - is selection type.
|
||||
abstract class ISelectablePicker<T> {
|
||||
/// The earliest date the user is permitted to pick.
|
||||
/// (only year, month and day matter, time doesn't matter)
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
/// (only year, month and day matter, time doesn't matter)
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Function returns if day can be selected or not.
|
||||
final SelectableDayPredicate _selectableDayPredicate;
|
||||
|
||||
/// StreamController for new selection (T).
|
||||
@protected
|
||||
StreamController<T> onUpdateController = StreamController<T>.broadcast();
|
||||
|
||||
/// Stream with new selected (T) event.
|
||||
///
|
||||
/// Throws [UnselectablePeriodException]
|
||||
/// if there is any custom disabled date in selected.
|
||||
Stream<T> get onUpdate => onUpdateController.stream;
|
||||
|
||||
/// Constructor with required fields that used in non-abstract methods
|
||||
/// ([isDisabled]).
|
||||
ISelectablePicker(this.firstDate, this.lastDate,
|
||||
{SelectableDayPredicate? selectableDayPredicate})
|
||||
: _selectableDayPredicate =
|
||||
selectableDayPredicate ?? _defaultSelectableDayPredicate;
|
||||
|
||||
/// If current selection exists and includes day/days that can't be selected
|
||||
/// according to the [_selectableDayPredicate]'
|
||||
bool get curSelectionIsCorrupted;
|
||||
|
||||
/// Returns [DayType] for given [day].
|
||||
DayType getDayType(DateTime day);
|
||||
|
||||
/// Call when user tap on the day cell.
|
||||
void onDayTapped(DateTime selectedDate);
|
||||
|
||||
/// Returns if given day is disabled.
|
||||
///
|
||||
/// Returns weather given day before the beginning of the [firstDate]
|
||||
/// or after the end of the [lastDate].
|
||||
///
|
||||
/// If [_selectableDayPredicate] is set checks it as well.
|
||||
@protected
|
||||
bool isDisabled(DateTime day) {
|
||||
final DateTime beginOfTheFirstDay =
|
||||
DatePickerUtils.startOfTheDay(firstDate);
|
||||
final DateTime endOfTheLastDay = DatePickerUtils.endOfTheDay(lastDate);
|
||||
final bool customDisabled =
|
||||
_selectableDayPredicate != null ? !_selectableDayPredicate(day) : false;
|
||||
|
||||
return day.isAfter(endOfTheLastDay) ||
|
||||
day.isBefore(beginOfTheFirstDay) ||
|
||||
customDisabled;
|
||||
}
|
||||
|
||||
/// Closes [onUpdateController].
|
||||
/// After it [onUpdateController] can't get new events.
|
||||
void dispose() {
|
||||
onUpdateController.close();
|
||||
}
|
||||
|
||||
static bool _defaultSelectableDayPredicate(_) => true;
|
||||
}
|
||||
|
||||
/// Selection logic for WeekPicker.
|
||||
class WeekSelectable extends ISelectablePicker<DatePeriod> {
|
||||
/// Initialized in ctor body.
|
||||
late DateTime _firstDayOfSelectedWeek;
|
||||
|
||||
/// Initialized in ctor body.
|
||||
late DateTime _lastDayOfSelectedWeek;
|
||||
|
||||
// It is int from 0 to 6 where 0 points to Sunday and 6 points to Saturday.
|
||||
// According to MaterialLocalization.firstDayOfWeekIndex.
|
||||
final int _firstDayOfWeekIndex;
|
||||
|
||||
@override
|
||||
bool get curSelectionIsCorrupted => _checkCurSelection();
|
||||
|
||||
/// Creates selection logic for WeekPicker.
|
||||
///
|
||||
/// Entire week will be selected if
|
||||
/// * it is between [firstDate] and [lastDate]
|
||||
/// * it doesn't include unselectable days according to the
|
||||
/// [selectableDayPredicate]
|
||||
///
|
||||
/// If one or more days of the week are before [firstDate]
|
||||
/// first selection date will be the same as [firstDate].
|
||||
///
|
||||
/// If one or more days of the week are after [lastDate]
|
||||
/// last selection date will be the same as [lastDate].
|
||||
///
|
||||
/// If one or more days of week are not selectable according to the
|
||||
/// [selectableDayPredicate] nothing will be returned as selection
|
||||
/// but [UnselectablePeriodException] will be thrown.
|
||||
WeekSelectable(DateTime selectedDate, this._firstDayOfWeekIndex,
|
||||
DateTime firstDate, DateTime lastDate,
|
||||
{SelectableDayPredicate? selectableDayPredicate})
|
||||
: super(firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate) {
|
||||
DatePeriod selectedWeek = _getNewSelectedPeriod(selectedDate);
|
||||
_firstDayOfSelectedWeek = selectedWeek.start;
|
||||
_lastDayOfSelectedWeek = selectedWeek.end;
|
||||
_checkCurSelection();
|
||||
}
|
||||
|
||||
@override
|
||||
DayType getDayType(DateTime date) {
|
||||
DayType result;
|
||||
|
||||
DatePeriod selectedPeriod =
|
||||
DatePeriod(_firstDayOfSelectedWeek, _lastDayOfSelectedWeek);
|
||||
bool selectedPeriodIsBroken =
|
||||
_disabledDatesInPeriod(selectedPeriod).isNotEmpty;
|
||||
|
||||
if (isDisabled(date)) {
|
||||
result = DayType.disabled;
|
||||
} else if (_isDaySelected(date) && !selectedPeriodIsBroken) {
|
||||
DateTime firstNotDisabledDayOfSelectedWeek =
|
||||
_firstDayOfSelectedWeek.isBefore(firstDate)
|
||||
? firstDate
|
||||
: _firstDayOfSelectedWeek;
|
||||
|
||||
DateTime lastNotDisabledDayOfSelectedWeek =
|
||||
_lastDayOfSelectedWeek.isAfter(lastDate)
|
||||
? lastDate
|
||||
: _lastDayOfSelectedWeek;
|
||||
|
||||
if (DatePickerUtils.sameDate(date, firstNotDisabledDayOfSelectedWeek) &&
|
||||
DatePickerUtils.sameDate(date, lastNotDisabledDayOfSelectedWeek)) {
|
||||
result = DayType.single;
|
||||
} else if (DatePickerUtils.sameDate(date, _firstDayOfSelectedWeek) ||
|
||||
DatePickerUtils.sameDate(date, firstDate)) {
|
||||
result = DayType.start;
|
||||
} else if (DatePickerUtils.sameDate(date, _lastDayOfSelectedWeek) ||
|
||||
DatePickerUtils.sameDate(date, lastDate)) {
|
||||
result = DayType.end;
|
||||
} else {
|
||||
result = DayType.middle;
|
||||
}
|
||||
} else {
|
||||
result = DayType.notSelected;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDayTapped(DateTime selectedDate) {
|
||||
DatePeriod newPeriod = _getNewSelectedPeriod(selectedDate);
|
||||
List<DateTime> customDisabledDays = _disabledDatesInPeriod(newPeriod);
|
||||
|
||||
customDisabledDays.isEmpty
|
||||
? onUpdateController.add(newPeriod)
|
||||
: onUpdateController.addError(
|
||||
UnselectablePeriodException(customDisabledDays, newPeriod));
|
||||
}
|
||||
|
||||
// Returns new selected period according to tapped date.
|
||||
// Doesn't check custom disabled days.
|
||||
// You have to check it separately if it needs.
|
||||
DatePeriod _getNewSelectedPeriod(DateTime tappedDay) {
|
||||
DatePeriod newPeriod;
|
||||
|
||||
DateTime firstDayOfTappedWeek =
|
||||
DatePickerUtils.getFirstDayOfWeek(tappedDay, _firstDayOfWeekIndex);
|
||||
DateTime lastDayOfTappedWeek =
|
||||
DatePickerUtils.getLastDayOfWeek(tappedDay, _firstDayOfWeekIndex);
|
||||
|
||||
DateTime firstNotDisabledDayOfSelectedWeek =
|
||||
firstDayOfTappedWeek.isBefore(firstDate)
|
||||
? firstDate
|
||||
: firstDayOfTappedWeek;
|
||||
|
||||
DateTime lastNotDisabledDayOfSelectedWeek =
|
||||
lastDayOfTappedWeek.isAfter(lastDate) ? lastDate : lastDayOfTappedWeek;
|
||||
|
||||
newPeriod = DatePeriod(
|
||||
firstNotDisabledDayOfSelectedWeek, lastNotDisabledDayOfSelectedWeek);
|
||||
return newPeriod;
|
||||
}
|
||||
|
||||
bool _isDaySelected(DateTime date) {
|
||||
DateTime startOfTheStartDay =
|
||||
DatePickerUtils.startOfTheDay(_firstDayOfSelectedWeek);
|
||||
DateTime endOfTheLastDay =
|
||||
DatePickerUtils.endOfTheDay(_lastDayOfSelectedWeek);
|
||||
return !(date.isBefore(startOfTheStartDay) ||
|
||||
date.isAfter(endOfTheLastDay));
|
||||
}
|
||||
|
||||
List<DateTime> _disabledDatesInPeriod(DatePeriod period) {
|
||||
List<DateTime> result = <DateTime>[];
|
||||
|
||||
var date = period.start;
|
||||
|
||||
while (!date.isAfter(period.end)) {
|
||||
if (isDisabled(date)) result.add(date);
|
||||
|
||||
date = date.add(Duration(days: 1));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns if current selection contains disabled dates.
|
||||
// Returns false if there is no any selection.
|
||||
bool _checkCurSelection() {
|
||||
bool noSelection =
|
||||
_firstDayOfSelectedWeek == null || _lastDayOfSelectedWeek == null;
|
||||
|
||||
if (noSelection) return false;
|
||||
|
||||
DatePeriod selectedPeriod =
|
||||
DatePeriod(_firstDayOfSelectedWeek, _lastDayOfSelectedWeek);
|
||||
List<DateTime> disabledDates = _disabledDatesInPeriod(selectedPeriod);
|
||||
|
||||
bool selectedPeriodIsBroken = disabledDates.isNotEmpty;
|
||||
return selectedPeriodIsBroken;
|
||||
}
|
||||
}
|
||||
|
||||
/// Selection logic for [day_picker.DayPicker].
|
||||
class DaySelectable extends ISelectablePicker<DateTime> {
|
||||
/// Currently selected date.
|
||||
DateTime selectedDate;
|
||||
|
||||
@override
|
||||
bool get curSelectionIsCorrupted => _checkCurSelection();
|
||||
|
||||
/// Creates selection logic for [day_picker.DayPicker].
|
||||
///
|
||||
/// Every day can be selected if it is between [firstDate] and [lastDate]
|
||||
/// and not unselectable according to the [selectableDayPredicate].
|
||||
///
|
||||
/// If day is not selectable according to the [selectableDayPredicate]
|
||||
/// nothing will be returned as selection
|
||||
/// but [UnselectablePeriodException] will be thrown.
|
||||
DaySelectable(this.selectedDate, DateTime firstDate, DateTime lastDate,
|
||||
{SelectableDayPredicate? selectableDayPredicate})
|
||||
: super(firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
@override
|
||||
DayType getDayType(DateTime date) {
|
||||
DayType result;
|
||||
|
||||
if (isDisabled(date)) {
|
||||
result = DayType.disabled;
|
||||
} else if (_isDaySelected(date)) {
|
||||
result = DayType.single;
|
||||
} else {
|
||||
result = DayType.notSelected;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDayTapped(DateTime selectedDate) {
|
||||
DateTime newSelected = DatePickerUtils.sameDate(firstDate, selectedDate)
|
||||
? selectedDate
|
||||
: DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
|
||||
onUpdateController.add(newSelected);
|
||||
}
|
||||
|
||||
bool _isDaySelected(DateTime date) =>
|
||||
DatePickerUtils.sameDate(date, selectedDate);
|
||||
|
||||
// Returns if current selection is disabled
|
||||
// according to the [_selectableDayPredicate].
|
||||
//
|
||||
// Returns false if there is no any selection.
|
||||
bool _checkCurSelection() {
|
||||
if (selectedDate == null) return false;
|
||||
bool selectedIsBroken = _selectableDayPredicate(selectedDate);
|
||||
|
||||
return selectedIsBroken;
|
||||
}
|
||||
}
|
||||
|
||||
/// Selection logic for [day_picker.DayPicker] where many single days can be
|
||||
/// selected.
|
||||
class DayMultiSelectable extends ISelectablePicker<List<DateTime>> {
|
||||
/// Currently selected dates.
|
||||
List<DateTime> selectedDates;
|
||||
|
||||
/// Creates selection logic for [day_picker.DayPicker].
|
||||
///
|
||||
/// Every day can be selected if it is between [firstDate] and [lastDate]
|
||||
/// and not unselectable according to the [selectableDayPredicate].
|
||||
///
|
||||
/// If day is not selectable according to the [selectableDayPredicate]
|
||||
/// nothing will be returned as selection
|
||||
/// but [UnselectablePeriodException] will be thrown.
|
||||
DayMultiSelectable(this.selectedDates, DateTime firstDate, DateTime lastDate,
|
||||
{SelectableDayPredicate? selectableDayPredicate})
|
||||
: super(firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
@override
|
||||
bool get curSelectionIsCorrupted => _checkCurSelection();
|
||||
|
||||
@override
|
||||
DayType getDayType(DateTime date) {
|
||||
DayType result;
|
||||
|
||||
if (isDisabled(date)) {
|
||||
result = DayType.disabled;
|
||||
} else if (_isDaySelected(date)) {
|
||||
result = DayType.single;
|
||||
} else {
|
||||
result = DayType.notSelected;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDayTapped(DateTime selectedDate) {
|
||||
bool alreadyExist =
|
||||
selectedDates.any((d) => DatePickerUtils.sameDate(d, selectedDate));
|
||||
|
||||
if (alreadyExist) {
|
||||
List<DateTime> newSelectedDates = List.from(selectedDates)
|
||||
..removeWhere((d) => DatePickerUtils.sameDate(d, selectedDate));
|
||||
|
||||
onUpdateController.add(newSelectedDates);
|
||||
} else {
|
||||
DateTime newSelected = DatePickerUtils.sameDate(firstDate, selectedDate)
|
||||
? selectedDate
|
||||
: DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
|
||||
|
||||
List<DateTime> newSelectedDates = List.from(selectedDates)
|
||||
..add(newSelected);
|
||||
|
||||
onUpdateController.add(newSelectedDates);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDaySelected(DateTime date) =>
|
||||
selectedDates.any((d) => DatePickerUtils.sameDate(date, d));
|
||||
|
||||
// Returns if current selection is disabled
|
||||
// according to the [_selectableDayPredicate].
|
||||
//
|
||||
// Returns false if there is no any selection.
|
||||
bool _checkCurSelection() {
|
||||
if (selectedDates == null || selectedDates.isEmpty) return false;
|
||||
bool selectedIsBroken = selectedDates.every(_selectableDayPredicate);
|
||||
|
||||
return selectedIsBroken;
|
||||
}
|
||||
}
|
||||
|
||||
/// Selection logic for [RangePicker].
|
||||
class RangeSelectable extends ISelectablePicker<DatePeriod> {
|
||||
/// Initially selected period.
|
||||
DatePeriod selectedPeriod;
|
||||
|
||||
@override
|
||||
bool get curSelectionIsCorrupted => _checkCurSelection();
|
||||
|
||||
/// Creates selection logic for [RangePicker].
|
||||
///
|
||||
/// Period can be selected if
|
||||
/// * it is between [firstDate] and [lastDate]
|
||||
/// * it doesn't include unselectable days according to the
|
||||
/// [selectableDayPredicate]
|
||||
///
|
||||
///
|
||||
/// If one or more days of the period are not selectable according to the
|
||||
/// [selectableDayPredicate] nothing will be returned as selection
|
||||
/// but [UnselectablePeriodException] will be thrown.
|
||||
RangeSelectable(this.selectedPeriod, DateTime firstDate, DateTime lastDate,
|
||||
{SelectableDayPredicate? selectableDayPredicate})
|
||||
: super(firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
@override
|
||||
DayType getDayType(DateTime date) {
|
||||
DayType result;
|
||||
|
||||
bool selectedPeriodIsBroken =
|
||||
_disabledDatesInPeriod(selectedPeriod).isNotEmpty;
|
||||
|
||||
if (isDisabled(date)) {
|
||||
result = DayType.disabled;
|
||||
} else if (_isDaySelected(date) && !selectedPeriodIsBroken) {
|
||||
if (DatePickerUtils.sameDate(date, selectedPeriod.start) &&
|
||||
DatePickerUtils.sameDate(date, selectedPeriod.end)) {
|
||||
result = DayType.single;
|
||||
} else if (DatePickerUtils.sameDate(date, selectedPeriod.start) ||
|
||||
DatePickerUtils.sameDate(date, firstDate)) {
|
||||
result = DayType.start;
|
||||
} else if (DatePickerUtils.sameDate(date, selectedPeriod.end) ||
|
||||
DatePickerUtils.sameDate(date, lastDate)) {
|
||||
result = DayType.end;
|
||||
} else {
|
||||
result = DayType.middle;
|
||||
}
|
||||
} else {
|
||||
result = DayType.notSelected;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDayTapped(DateTime selectedDate) {
|
||||
DatePeriod newPeriod = _getNewSelectedPeriod(selectedDate);
|
||||
List<DateTime> customDisabledDays = _disabledDatesInPeriod(newPeriod);
|
||||
|
||||
customDisabledDays.isEmpty
|
||||
? onUpdateController.add(newPeriod)
|
||||
: onUpdateController.addError(
|
||||
UnselectablePeriodException(customDisabledDays, newPeriod));
|
||||
}
|
||||
|
||||
// Returns new selected period according to tapped date.
|
||||
DatePeriod _getNewSelectedPeriod(DateTime tappedDate) {
|
||||
// check if was selected only one date and we should generate period
|
||||
bool sameDate =
|
||||
DatePickerUtils.sameDate(selectedPeriod.start, selectedPeriod.end);
|
||||
DatePeriod newPeriod;
|
||||
|
||||
// Was selected one-day-period.
|
||||
// With new user tap will be generated 2 dates as a period.
|
||||
if (sameDate) {
|
||||
// if user tap on the already selected single day
|
||||
bool selectedAlreadySelectedDay =
|
||||
DatePickerUtils.sameDate(tappedDate, selectedPeriod.end);
|
||||
bool isSelectedFirstDay = DatePickerUtils.sameDate(tappedDate, firstDate);
|
||||
bool isSelectedLastDay = DatePickerUtils.sameDate(tappedDate, lastDate);
|
||||
|
||||
if (selectedAlreadySelectedDay) {
|
||||
if (isSelectedFirstDay && isSelectedLastDay) {
|
||||
newPeriod = DatePeriod(firstDate, lastDate);
|
||||
} else if (isSelectedFirstDay) {
|
||||
newPeriod =
|
||||
DatePeriod(firstDate, DatePickerUtils.endOfTheDay(firstDate));
|
||||
} else if (isSelectedLastDay) {
|
||||
newPeriod =
|
||||
DatePeriod(DatePickerUtils.startOfTheDay(lastDate), lastDate);
|
||||
} else {
|
||||
newPeriod = DatePeriod(DatePickerUtils.startOfTheDay(tappedDate),
|
||||
DatePickerUtils.endOfTheDay(tappedDate));
|
||||
}
|
||||
} else {
|
||||
DateTime startOfTheSelectedDay =
|
||||
DatePickerUtils.startOfTheDay(selectedPeriod.start);
|
||||
|
||||
if (!tappedDate.isAfter(startOfTheSelectedDay)) {
|
||||
newPeriod = DatePickerUtils.sameDate(tappedDate, firstDate)
|
||||
? DatePeriod(firstDate, selectedPeriod.end)
|
||||
: DatePeriod(DatePickerUtils.startOfTheDay(tappedDate),
|
||||
selectedPeriod.end);
|
||||
} else {
|
||||
newPeriod = DatePickerUtils.sameDate(tappedDate, lastDate)
|
||||
? DatePeriod(selectedPeriod.start, lastDate)
|
||||
: DatePeriod(selectedPeriod.start,
|
||||
DatePickerUtils.endOfTheDay(tappedDate));
|
||||
}
|
||||
}
|
||||
|
||||
// Was selected 2 dates as a period.
|
||||
// With new user tap new one-day-period will be generated.
|
||||
} else {
|
||||
bool sameAsFirst = DatePickerUtils.sameDate(tappedDate, firstDate);
|
||||
bool sameAsLast = DatePickerUtils.sameDate(tappedDate, lastDate);
|
||||
|
||||
if (sameAsFirst && sameAsLast) {
|
||||
newPeriod = DatePeriod(firstDate, lastDate);
|
||||
} else if (sameAsFirst) {
|
||||
newPeriod =
|
||||
DatePeriod(firstDate, DatePickerUtils.endOfTheDay(firstDate));
|
||||
} else if (sameAsLast) {
|
||||
newPeriod =
|
||||
DatePeriod(DatePickerUtils.startOfTheDay(tappedDate), lastDate);
|
||||
} else {
|
||||
newPeriod = DatePeriod(DatePickerUtils.startOfTheDay(tappedDate),
|
||||
DatePickerUtils.endOfTheDay(tappedDate));
|
||||
}
|
||||
}
|
||||
|
||||
return newPeriod;
|
||||
}
|
||||
|
||||
// Returns if current selection contains disabled dates.
|
||||
// Returns false if there is no any selection.
|
||||
bool _checkCurSelection() {
|
||||
if (selectedPeriod == null) return false;
|
||||
List<DateTime> disabledDates = _disabledDatesInPeriod(selectedPeriod);
|
||||
|
||||
bool selectedPeriodIsBroken = disabledDates.isNotEmpty;
|
||||
return selectedPeriodIsBroken;
|
||||
}
|
||||
|
||||
List<DateTime> _disabledDatesInPeriod(DatePeriod period) {
|
||||
List<DateTime> result = <DateTime>[];
|
||||
|
||||
var date = period.start;
|
||||
|
||||
while (!date.isAfter(period.end)) {
|
||||
if (isDisabled(date)) result.add(date);
|
||||
|
||||
date = date.add(Duration(days: 1));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _isDaySelected(DateTime date) {
|
||||
DateTime startOfTheStartDay =
|
||||
DatePickerUtils.startOfTheDay(selectedPeriod.start);
|
||||
DateTime endOfTheLastDay = DatePickerUtils.endOfTheDay(selectedPeriod.end);
|
||||
return !(date.isBefore(startOfTheStartDay) ||
|
||||
date.isAfter(endOfTheLastDay));
|
||||
}
|
||||
}
|
||||
54
flutter_date_pickers-master/lib/src/icon_btn.dart
Normal file
54
flutter_date_pickers-master/lib/src/icon_btn.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Icon button widget built different
|
||||
/// depends on [MaterialApp] or [CupertinoApp] ancestor.
|
||||
class IconBtn extends StatelessWidget {
|
||||
/// Widget to use inside button.
|
||||
///
|
||||
/// Typically [Icon] widget.
|
||||
final Widget icon;
|
||||
|
||||
/// Function called when user tap on the button.
|
||||
///
|
||||
/// Can be null. In this case button will be disabled.
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// Tooltip for button.
|
||||
///
|
||||
/// Applied only for material style buttons.
|
||||
/// It means only if widget has [MaterialApp] ancestor.
|
||||
final String? tooltip;
|
||||
|
||||
/// Creates button with [icon] different
|
||||
/// depends on [MaterialApp] or [CupertinoApp] ancestor.
|
||||
const IconBtn({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
this.onTap,
|
||||
this.tooltip
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isMaterial = Material.of(context) != null;
|
||||
|
||||
return isMaterial
|
||||
? _materialBtn()
|
||||
: _cupertinoBtn();
|
||||
}
|
||||
|
||||
Widget _cupertinoBtn() =>
|
||||
CupertinoButton(
|
||||
padding: const EdgeInsets.all(0.0),
|
||||
child: icon,
|
||||
onPressed: onTap,
|
||||
);
|
||||
|
||||
Widget _materialBtn() =>
|
||||
IconButton(
|
||||
icon: icon,
|
||||
tooltip: tooltip ?? "",
|
||||
onPressed: onTap,
|
||||
);
|
||||
}
|
||||
115
flutter_date_pickers-master/lib/src/layout_settings.dart
Normal file
115
flutter_date_pickers-master/lib/src/layout_settings.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'day_picker.dart';
|
||||
import 'month_picker.dart';
|
||||
import 'range_picker.dart';
|
||||
import 'week_picker.dart';
|
||||
|
||||
// layout defaults
|
||||
const Duration _kPageScrollDuration = Duration(milliseconds: 200);
|
||||
const double _kDayPickerRowHeight = 42.0;
|
||||
const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
|
||||
const double _kMonthPickerPortraitWidth = 330.0;
|
||||
const EdgeInsetsGeometry _kContentPadding =
|
||||
EdgeInsets.symmetric(horizontal: 8.0);
|
||||
|
||||
/// Settings for the layout of the [DayPicker], [WeekPicker], [RangePicker]
|
||||
/// and [MonthPicker].
|
||||
class DatePickerLayoutSettings {
|
||||
/// Duration for scroll to previous or next page.
|
||||
final Duration pagesScrollDuration;
|
||||
|
||||
/// Determines the scroll physics of a date picker widget.
|
||||
///
|
||||
/// Can be null. In this case default physics for [ScrollView] will be used.
|
||||
final ScrollPhysics? scrollPhysics;
|
||||
|
||||
/// Height of the one row in picker including headers.
|
||||
///
|
||||
/// Default is [_kDayPickerRowHeight].
|
||||
final double dayPickerRowHeight;
|
||||
|
||||
/// Width of the day based pickers.
|
||||
final double monthPickerPortraitWidth;
|
||||
|
||||
///
|
||||
final int maxDayPickerRowCount;
|
||||
|
||||
/// Padding for the entire picker.
|
||||
final EdgeInsetsGeometry contentPadding;
|
||||
|
||||
/// If the first dates from the next month should be shown
|
||||
/// to complete last week of the selected month.
|
||||
///
|
||||
/// false by default.
|
||||
final bool showNextMonthStart;
|
||||
|
||||
/// If the last dates from the previous month should be shown
|
||||
/// to complete first week of the selected month.
|
||||
///
|
||||
/// false by default.
|
||||
final bool showPrevMonthEnd;
|
||||
|
||||
/// Hide Month navigation row
|
||||
/// false by default.
|
||||
final bool hideMonthNavigationRow;
|
||||
|
||||
/// Grid delegate for the picker according to [dayPickerRowHeight] and
|
||||
/// [maxDayPickerRowCount].
|
||||
SliverGridDelegate get dayPickerGridDelegate =>
|
||||
_DayPickerGridDelegate(dayPickerRowHeight, maxDayPickerRowCount);
|
||||
|
||||
/// Maximum height of the day based picker according to [dayPickerRowHeight]
|
||||
/// and [maxDayPickerRowCount].
|
||||
///
|
||||
/// Two extra rows:
|
||||
/// one for the day-of-week header and one for the month header.
|
||||
double get maxDayPickerHeight =>
|
||||
dayPickerRowHeight * (maxDayPickerRowCount + 2);
|
||||
|
||||
/// Creates layout settings for the date picker.
|
||||
///
|
||||
/// Usually used in [DayPicker], [WeekPicker], [RangePicker]
|
||||
/// and [MonthPicker].
|
||||
const DatePickerLayoutSettings({
|
||||
this.pagesScrollDuration = _kPageScrollDuration,
|
||||
this.dayPickerRowHeight = _kDayPickerRowHeight,
|
||||
this.monthPickerPortraitWidth = _kMonthPickerPortraitWidth,
|
||||
this.maxDayPickerRowCount = _kMaxDayPickerRowCount,
|
||||
this.contentPadding = _kContentPadding,
|
||||
this.showNextMonthStart = false,
|
||||
this.showPrevMonthEnd = false,
|
||||
this.hideMonthNavigationRow = false,
|
||||
this.scrollPhysics
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class _DayPickerGridDelegate extends SliverGridDelegate {
|
||||
final double _dayPickerRowHeight;
|
||||
final int _maxDayPickerRowCount;
|
||||
|
||||
const _DayPickerGridDelegate(
|
||||
this._dayPickerRowHeight, this._maxDayPickerRowCount);
|
||||
|
||||
@override
|
||||
SliverGridLayout getLayout(SliverConstraints constraints) {
|
||||
const int columnCount = DateTime.daysPerWeek;
|
||||
final double tileWidth = constraints.crossAxisExtent / columnCount;
|
||||
final double tileHeight = math.min(_dayPickerRowHeight,
|
||||
constraints.viewportMainAxisExtent / (_maxDayPickerRowCount + 1));
|
||||
return SliverGridRegularTileLayout(
|
||||
crossAxisCount: columnCount,
|
||||
mainAxisStride: tileHeight,
|
||||
crossAxisStride: tileWidth,
|
||||
childMainAxisExtent: tileHeight,
|
||||
childCrossAxisExtent: tileWidth,
|
||||
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(SliverGridDelegate oldDelegate) => false;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'day_picker.dart' as day_picker;
|
||||
import 'icon_btn.dart';
|
||||
import 'range_picker.dart';
|
||||
import 'semantic_sorting.dart';
|
||||
import 'week_picker.dart';
|
||||
|
||||
/// Month navigation widget for day based date pickers like
|
||||
/// [day_picker.DayPicker],
|
||||
/// [WeekPicker],
|
||||
/// [RangePicker].
|
||||
///
|
||||
/// It is row with [title] of showing month in the center and icons to selects
|
||||
/// previous and next month around it.
|
||||
class MonthNavigationRow extends StatelessWidget {
|
||||
/// Key for previous page icon.
|
||||
///
|
||||
/// Can be useful in integration tests to find icon.
|
||||
final Key? previousPageIconKey;
|
||||
|
||||
/// Key for next page icon.
|
||||
///
|
||||
/// Can be useful in integration tests to find icon.
|
||||
final Key? nextPageIconKey;
|
||||
|
||||
/// Function called when [nextIcon] is tapped.
|
||||
final VoidCallback? onNextMonthTapped;
|
||||
|
||||
/// Function called when [prevIcon] is tapped.
|
||||
final VoidCallback? onPreviousMonthTapped;
|
||||
|
||||
/// Tooltip for the [nextIcon].
|
||||
final String? nextMonthTooltip;
|
||||
|
||||
/// Tooltip for the [prevIcon].
|
||||
final String? previousMonthTooltip;
|
||||
|
||||
/// Widget to use at the end of this row (after title).
|
||||
final Widget nextIcon;
|
||||
|
||||
/// Widget to use at the beginning of this row (before title).
|
||||
final Widget prevIcon;
|
||||
|
||||
/// Usually [Text] widget.
|
||||
final Widget? title;
|
||||
|
||||
/// Creates month navigation row.
|
||||
const MonthNavigationRow({
|
||||
Key? key,
|
||||
this.previousPageIconKey,
|
||||
this.nextPageIconKey,
|
||||
this.onNextMonthTapped,
|
||||
this.onPreviousMonthTapped,
|
||||
this.nextMonthTooltip,
|
||||
this.previousMonthTooltip,
|
||||
this.title,
|
||||
required this.nextIcon,
|
||||
required this.prevIcon
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Semantics(
|
||||
sortKey: MonthPickerSortKey.previousMonth,
|
||||
child: IconBtn(
|
||||
key: previousPageIconKey,
|
||||
icon: prevIcon,
|
||||
tooltip: previousMonthTooltip,
|
||||
onTap: onPreviousMonthTapped,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: ExcludeSemantics(
|
||||
child: title,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Semantics(
|
||||
sortKey: MonthPickerSortKey.nextMonth,
|
||||
child: IconBtn(
|
||||
key: nextPageIconKey,
|
||||
icon: nextIcon,
|
||||
tooltip: nextMonthTooltip,
|
||||
onTap: onNextMonthTapped,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
440
flutter_date_pickers-master/lib/src/month_picker.dart
Normal file
440
flutter_date_pickers-master/lib/src/month_picker.dart
Normal file
@@ -0,0 +1,440 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'date_picker_keys.dart';
|
||||
import 'date_picker_styles.dart';
|
||||
import 'layout_settings.dart';
|
||||
import 'semantic_sorting.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const Locale _defaultLocale = Locale('en', 'US');
|
||||
|
||||
/// Month picker widget.
|
||||
class MonthPicker extends StatefulWidget {
|
||||
/// Month picker widget.
|
||||
MonthPicker(
|
||||
{Key? key,
|
||||
required this.selectedDate,
|
||||
required this.onChanged,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
this.datePickerLayoutSettings = const DatePickerLayoutSettings(),
|
||||
this.datePickerKeys,
|
||||
required this.datePickerStyles})
|
||||
: assert(!firstDate.isAfter(lastDate)),
|
||||
assert(!selectedDate.isBefore(firstDate)),
|
||||
assert(!selectedDate.isAfter(lastDate)),
|
||||
super(key: key);
|
||||
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DateTime selectedDate;
|
||||
|
||||
/// Called when the user picks a month.
|
||||
final ValueChanged<DateTime> onChanged;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Some keys useful for integration tests
|
||||
final DatePickerKeys? datePickerKeys;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerStyles datePickerStyles;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _MonthPickerState();
|
||||
}
|
||||
|
||||
class _MonthPickerState extends State<MonthPicker> {
|
||||
PageController _monthPickerController = PageController();
|
||||
|
||||
Locale locale = _defaultLocale;
|
||||
MaterialLocalizations localizations = _defaultLocalizations;
|
||||
|
||||
TextDirection textDirection = TextDirection.ltr;
|
||||
|
||||
DateTime _todayDate = DateTime.now();
|
||||
DateTime _previousYearDate = DateTime(DateTime.now().year - 1);
|
||||
DateTime _nextYearDate = DateTime(DateTime.now().year + 1);
|
||||
|
||||
DateTime _currentDisplayedYearDate = DateTime.now();
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
/// True if the earliest allowable year is displayed.
|
||||
bool get _isDisplayingFirstYear =>
|
||||
!_currentDisplayedYearDate.isAfter(DateTime(widget.firstDate.year));
|
||||
|
||||
/// True if the latest allowable year is displayed.
|
||||
bool get _isDisplayingLastYear =>
|
||||
!_currentDisplayedYearDate.isBefore(DateTime(widget.lastDate.year));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initially display the pre-selected date.
|
||||
final int yearPage =
|
||||
DatePickerUtils.yearDelta(widget.firstDate, widget.selectedDate);
|
||||
|
||||
_monthPickerController.dispose();
|
||||
_monthPickerController = PageController(initialPage: yearPage);
|
||||
_handleYearPageChanged(yearPage);
|
||||
_updateCurrentDate();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MonthPicker oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.selectedDate != oldWidget.selectedDate) {
|
||||
final int yearPage =
|
||||
DatePickerUtils.yearDelta(widget.firstDate, widget.selectedDate);
|
||||
_monthPickerController = PageController(initialPage: yearPage);
|
||||
_handleYearPageChanged(yearPage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
try {
|
||||
locale = Localizations.localeOf(context);
|
||||
|
||||
MaterialLocalizations? curLocalizations =
|
||||
Localizations.of<MaterialLocalizations>(
|
||||
context, MaterialLocalizations);
|
||||
if (curLocalizations != null && localizations != curLocalizations) {
|
||||
localizations = curLocalizations;
|
||||
}
|
||||
|
||||
textDirection = Directionality.of(context);
|
||||
|
||||
// No MaterialLocalizations or Directionality or Locale was found
|
||||
// and ".of" method throws error
|
||||
// trying to cast null to MaterialLocalizations.
|
||||
} on TypeError catch (_) {}
|
||||
}
|
||||
|
||||
void _updateCurrentDate() {
|
||||
_todayDate = DateTime.now();
|
||||
final DateTime tomorrow =
|
||||
DateTime(_todayDate.year, _todayDate.month, _todayDate.day + 1);
|
||||
Duration timeUntilTomorrow = tomorrow.difference(_todayDate);
|
||||
timeUntilTomorrow +=
|
||||
const Duration(seconds: 1); // so we don't miss it by rounding
|
||||
_timer?.cancel();
|
||||
_timer = Timer(timeUntilTomorrow, () {
|
||||
setState(_updateCurrentDate);
|
||||
});
|
||||
}
|
||||
|
||||
/// Add years to a year truncated date.
|
||||
DateTime _addYearsToYearDate(DateTime yearDate, int yearsToAdd) =>
|
||||
DateTime(yearDate.year + yearsToAdd);
|
||||
|
||||
Widget _buildItems(BuildContext context, int index) {
|
||||
final DateTime year = _addYearsToYearDate(widget.firstDate, index);
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
DatePickerStyles styles = widget.datePickerStyles;
|
||||
styles = styles.fulfillWithTheme(theme);
|
||||
|
||||
return _MonthPicker(
|
||||
key: ValueKey<DateTime>(year),
|
||||
selectedDate: widget.selectedDate,
|
||||
currentDate: _todayDate,
|
||||
onChanged: widget.onChanged,
|
||||
firstDate: widget.firstDate,
|
||||
lastDate: widget.lastDate,
|
||||
datePickerLayoutSettings: widget.datePickerLayoutSettings,
|
||||
displayedYear: year,
|
||||
selectedPeriodKey: widget.datePickerKeys?.selectedPeriodKeys,
|
||||
datePickerStyles: styles,
|
||||
locale: locale,
|
||||
localizations: localizations,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleNextYear() {
|
||||
if (!_isDisplayingLastYear) {
|
||||
String yearStr = localizations.formatYear(_nextYearDate);
|
||||
SemanticsService.announce(yearStr, textDirection);
|
||||
_monthPickerController.nextPage(
|
||||
duration: widget.datePickerLayoutSettings.pagesScrollDuration,
|
||||
curve: Curves.ease);
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePreviousYear() {
|
||||
if (!_isDisplayingFirstYear) {
|
||||
String yearStr = localizations.formatYear(_previousYearDate);
|
||||
SemanticsService.announce(yearStr, textDirection);
|
||||
_monthPickerController.previousPage(
|
||||
duration: widget.datePickerLayoutSettings.pagesScrollDuration,
|
||||
curve: Curves.ease);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleYearPageChanged(int yearPage) {
|
||||
setState(() {
|
||||
_previousYearDate = _addYearsToYearDate(widget.firstDate, yearPage - 1);
|
||||
_currentDisplayedYearDate =
|
||||
_addYearsToYearDate(widget.firstDate, yearPage);
|
||||
_nextYearDate = _addYearsToYearDate(widget.firstDate, yearPage + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
int yearsCount =
|
||||
DatePickerUtils.yearDelta(widget.firstDate, widget.lastDate) + 1;
|
||||
|
||||
return SizedBox(
|
||||
width: widget.datePickerLayoutSettings.monthPickerPortraitWidth,
|
||||
height: widget.datePickerLayoutSettings.maxDayPickerHeight,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Semantics(
|
||||
sortKey: YearPickerSortKey.calendar,
|
||||
child: PageView.builder(
|
||||
key: ValueKey<DateTime>(widget.selectedDate),
|
||||
controller: _monthPickerController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: yearsCount,
|
||||
itemBuilder: _buildItems,
|
||||
onPageChanged: _handleYearPageChanged,
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
top: 0.0,
|
||||
start: 8.0,
|
||||
child: Semantics(
|
||||
sortKey: YearPickerSortKey.previousYear,
|
||||
child: IconButton(
|
||||
key: widget.datePickerKeys?.previousPageIconKey,
|
||||
icon: widget.datePickerStyles.prevIcon,
|
||||
tooltip: _isDisplayingFirstYear
|
||||
? null
|
||||
: '${localizations.formatYear(_previousYearDate)}',
|
||||
onPressed: _isDisplayingFirstYear ? null : _handlePreviousYear,
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
top: 0.0,
|
||||
end: 8.0,
|
||||
child: Semantics(
|
||||
sortKey: YearPickerSortKey.nextYear,
|
||||
child: IconButton(
|
||||
key: widget.datePickerKeys?.nextPageIconKey,
|
||||
icon: widget.datePickerStyles.nextIcon,
|
||||
tooltip: _isDisplayingLastYear
|
||||
? null
|
||||
: '${localizations.formatYear(_nextYearDate)}',
|
||||
onPressed: _isDisplayingLastYear ? null : _handleNextYear,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static MaterialLocalizations get _defaultLocalizations =>
|
||||
MaterialLocalizationEn(
|
||||
twoDigitZeroPaddedFormat:
|
||||
intl.NumberFormat('00', _defaultLocale.toString()),
|
||||
fullYearFormat: intl.DateFormat.y(_defaultLocale.toString()),
|
||||
longDateFormat: intl.DateFormat.yMMMMEEEEd(_defaultLocale.toString()),
|
||||
shortMonthDayFormat: intl.DateFormat.MMMd(_defaultLocale.toString()),
|
||||
decimalFormat:
|
||||
intl.NumberFormat.decimalPattern(_defaultLocale.toString()),
|
||||
shortDateFormat: intl.DateFormat.yMMMd(_defaultLocale.toString()),
|
||||
mediumDateFormat: intl.DateFormat.MMMEd(_defaultLocale.toString()),
|
||||
compactDateFormat: intl.DateFormat.yMd(_defaultLocale.toString()),
|
||||
yearMonthFormat: intl.DateFormat.yMMMM(_defaultLocale.toString()),
|
||||
);
|
||||
}
|
||||
|
||||
class _MonthPicker extends StatelessWidget {
|
||||
/// The month whose days are displayed by this picker.
|
||||
final DateTime displayedYear;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DateTime selectedDate;
|
||||
|
||||
/// The current date at the time the picker is displayed.
|
||||
final DateTime currentDate;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Called when the user picks a day.
|
||||
final ValueChanged<DateTime> onChanged;
|
||||
|
||||
/// Key fo selected month (useful for integration tests)
|
||||
final Key? selectedPeriodKey;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerStyles datePickerStyles;
|
||||
|
||||
final MaterialLocalizations localizations;
|
||||
|
||||
final Locale locale;
|
||||
|
||||
_MonthPicker(
|
||||
{required this.displayedYear,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
required this.selectedDate,
|
||||
required this.currentDate,
|
||||
required this.onChanged,
|
||||
required this.datePickerLayoutSettings,
|
||||
required this.datePickerStyles,
|
||||
required this.localizations,
|
||||
required this.locale,
|
||||
this.selectedPeriodKey,
|
||||
Key? key})
|
||||
: assert(!firstDate.isAfter(lastDate)),
|
||||
assert(selectedDate.isAfter(firstDate) ||
|
||||
selectedDate.isAtSameMomentAs(firstDate)),
|
||||
super(key: key);
|
||||
|
||||
// We only need to know if month of passed day
|
||||
// before the month of the firstDate or after the month of the lastDate.
|
||||
//
|
||||
// Don't need to compare day and time.
|
||||
bool _isDisabled(DateTime month) {
|
||||
DateTime beginningOfTheFirstDateMonth =
|
||||
DateTime(firstDate.year, firstDate.month);
|
||||
DateTime endOfTheLastDateMonth = DateTime(lastDate.year, lastDate.month + 1)
|
||||
.subtract(Duration(microseconds: 1));
|
||||
|
||||
return month.isAfter(endOfTheLastDateMonth) ||
|
||||
month.isBefore(beginningOfTheFirstDateMonth);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final int monthsInYear = 12;
|
||||
final int year = displayedYear.year;
|
||||
final int day = 1;
|
||||
|
||||
final List<Widget> labels = <Widget>[];
|
||||
|
||||
for (int i = 0; i < monthsInYear; i += 1) {
|
||||
final int month = i + 1;
|
||||
final DateTime monthToBuild = DateTime(year, month, day);
|
||||
|
||||
final bool disabled = _isDisabled(monthToBuild);
|
||||
final bool isSelectedMonth =
|
||||
selectedDate.year == year && selectedDate.month == month;
|
||||
|
||||
BoxDecoration? decoration;
|
||||
TextStyle? itemStyle = themeData.textTheme.bodyText2;
|
||||
|
||||
if (isSelectedMonth) {
|
||||
itemStyle = datePickerStyles.selectedDateStyle;
|
||||
decoration = datePickerStyles.selectedSingleDateDecoration;
|
||||
} else if (disabled) {
|
||||
itemStyle = datePickerStyles.disabledDateStyle;
|
||||
} else if (currentDate.year == year && currentDate.month == month) {
|
||||
// The current month gets a different text color.
|
||||
itemStyle = datePickerStyles.currentDateStyle;
|
||||
} else {
|
||||
itemStyle = datePickerStyles.defaultDateTextStyle;
|
||||
}
|
||||
|
||||
String monthStr = _getMonthStr(monthToBuild);
|
||||
|
||||
Widget monthWidget = Container(
|
||||
decoration: decoration,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
// We want the day of month to be spoken first irrespective of the
|
||||
// locale-specific preferences or TextDirection. This is because
|
||||
// an accessibility user is more likely to be interested in the
|
||||
// day of month before the rest of the date, as they are looking
|
||||
// for the day of month. To do that we prepend day of month to the
|
||||
// formatted full date.
|
||||
label: '${localizations.formatDecimal(month)}, '
|
||||
'${localizations.formatFullDate(monthToBuild)}',
|
||||
selected: isSelectedMonth,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(monthStr, style: itemStyle),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!disabled) {
|
||||
monthWidget = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
DatePickerUtils.sameMonth(firstDate, monthToBuild)
|
||||
? onChanged(firstDate)
|
||||
: onChanged(monthToBuild);
|
||||
},
|
||||
child: monthWidget,
|
||||
);
|
||||
}
|
||||
labels.add(monthWidget);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: datePickerLayoutSettings.dayPickerRowHeight,
|
||||
child: Center(
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
localizations.formatYear(displayedYear),
|
||||
key: selectedPeriodKey,
|
||||
style: datePickerStyles.displayedPeriodTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: GridView.count(
|
||||
physics: datePickerLayoutSettings.scrollPhysics,
|
||||
crossAxisCount: 4,
|
||||
children: labels,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Returns only month made with intl.DateFormat.MMM() for current [locale].
|
||||
// We can'r use [localizations] here because MaterialLocalizations doesn't
|
||||
// provide short month string.
|
||||
String _getMonthStr(DateTime date) {
|
||||
String month = intl.DateFormat.MMM(locale.toString()).format(date);
|
||||
return month;
|
||||
}
|
||||
}
|
||||
109
flutter_date_pickers-master/lib/src/range_picker.dart
Normal file
109
flutter_date_pickers-master/lib/src/range_picker.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'date_period.dart';
|
||||
import 'date_picker_keys.dart';
|
||||
import 'date_picker_styles.dart';
|
||||
import 'day_based_changable_picker.dart';
|
||||
import 'day_picker_selection.dart';
|
||||
import 'day_type.dart';
|
||||
import 'event_decoration.dart';
|
||||
import 'i_selectable_picker.dart';
|
||||
import 'layout_settings.dart';
|
||||
import 'typedefs.dart';
|
||||
|
||||
/// Date picker for range selection.
|
||||
class RangePicker extends StatelessWidget {
|
||||
/// Creates a range picker.
|
||||
RangePicker(
|
||||
{Key? key,
|
||||
required this.selectedPeriod,
|
||||
required this.onChanged,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
this.initiallyShowDate,
|
||||
this.datePickerLayoutSettings = const DatePickerLayoutSettings(),
|
||||
this.datePickerStyles,
|
||||
this.datePickerKeys,
|
||||
this.selectableDayPredicate,
|
||||
this.onSelectionError,
|
||||
this.eventDecorationBuilder,
|
||||
this.onMonthChanged})
|
||||
: assert(!firstDate.isAfter(lastDate)),
|
||||
assert(!lastDate.isBefore(firstDate)),
|
||||
assert(!selectedPeriod.start.isBefore(firstDate)),
|
||||
assert(!selectedPeriod.end.isAfter(lastDate)),
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isAfter(lastDate)),
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isBefore(firstDate)),
|
||||
super(key: key);
|
||||
|
||||
/// The currently selected period.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DatePeriod selectedPeriod;
|
||||
|
||||
/// Called when the user picks a week.
|
||||
final ValueChanged<DatePeriod> onChanged;
|
||||
|
||||
/// Called when the error was thrown after user selection.
|
||||
/// (e.g. when user selected a range with one or more days
|
||||
/// that can't be selected)
|
||||
final OnSelectionError? onSelectionError;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Date for defining what month should be shown initially.
|
||||
///
|
||||
/// In case of null start of the [selectedPeriod] will be shown.
|
||||
final DateTime? initiallyShowDate;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Some keys useful for integration tests
|
||||
final DatePickerKeys? datePickerKeys;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerRangeStyles? datePickerStyles;
|
||||
|
||||
/// Function returns if day can be selected or not.
|
||||
final SelectableDayPredicate? selectableDayPredicate;
|
||||
|
||||
/// Builder to get event decoration for each date.
|
||||
///
|
||||
/// All event styles are overridden by selected styles
|
||||
/// except days with dayType is [DayType.notSelected].
|
||||
final EventDecorationBuilder? eventDecorationBuilder;
|
||||
|
||||
/// Called when the user changes the month.
|
||||
/// New DateTime object represents first day of new month and 00:00 time.
|
||||
final ValueChanged<DateTime>? onMonthChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ISelectablePicker<DatePeriod> rangeSelectablePicker = RangeSelectable(
|
||||
selectedPeriod, firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
return DayBasedChangeablePicker<DatePeriod>(
|
||||
selectablePicker: rangeSelectablePicker,
|
||||
selection: DayPickerRangeSelection(selectedPeriod),
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
initiallyShownDate: initiallyShowDate,
|
||||
onChanged: onChanged,
|
||||
onSelectionError: onSelectionError,
|
||||
datePickerLayoutSettings: datePickerLayoutSettings,
|
||||
datePickerStyles: datePickerStyles ?? DatePickerRangeStyles(),
|
||||
datePickerKeys: datePickerKeys,
|
||||
eventDecorationBuilder: eventDecorationBuilder,
|
||||
onMonthChanged: onMonthChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
33
flutter_date_pickers-master/lib/src/semantic_sorting.dart
Normal file
33
flutter_date_pickers-master/lib/src/semantic_sorting.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/semantics.dart';
|
||||
|
||||
/// Defines semantic traversal order of the top-level widgets
|
||||
/// inside the day or week picker.
|
||||
class MonthPickerSortKey extends OrdinalSortKey {
|
||||
/// Previous month key.
|
||||
static const MonthPickerSortKey previousMonth = MonthPickerSortKey(1.0);
|
||||
|
||||
/// Next month key.
|
||||
static const MonthPickerSortKey nextMonth = MonthPickerSortKey(2.0);
|
||||
|
||||
/// Calendar key.
|
||||
static const MonthPickerSortKey calendar = MonthPickerSortKey(3.0);
|
||||
|
||||
///
|
||||
const MonthPickerSortKey(double order) : super(order);
|
||||
}
|
||||
|
||||
/// Defines semantic traversal order of the top-level widgets
|
||||
/// inside the month picker.
|
||||
class YearPickerSortKey extends OrdinalSortKey {
|
||||
/// Previous year key.
|
||||
static const YearPickerSortKey previousYear = YearPickerSortKey(1.0);
|
||||
|
||||
/// Next year key.
|
||||
static const YearPickerSortKey nextYear = YearPickerSortKey(2.0);
|
||||
|
||||
/// Calendar key.
|
||||
static const YearPickerSortKey calendar = YearPickerSortKey(3.0);
|
||||
|
||||
///
|
||||
const YearPickerSortKey(double order) : super(order);
|
||||
}
|
||||
11
flutter_date_pickers-master/lib/src/typedefs.dart
Normal file
11
flutter_date_pickers-master/lib/src/typedefs.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'range_picker.dart';
|
||||
import 'unselectable_period_error.dart';
|
||||
import 'week_picker.dart';
|
||||
|
||||
|
||||
/// Signature for function that can be used to handle incorrect selections.
|
||||
///
|
||||
/// See also:
|
||||
/// * [WeekPicker.onSelectionError]
|
||||
/// * [RangePicker.onSelectionError]
|
||||
typedef OnSelectionError = void Function(UnselectablePeriodException e);
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'date_period.dart';
|
||||
import 'range_picker.dart';
|
||||
import 'week_picker.dart';
|
||||
|
||||
|
||||
/// Exception thrown when selected period contains custom disabled days.
|
||||
class UnselectablePeriodException implements Exception {
|
||||
/// Dates inside selected period what can't be selected
|
||||
/// according custom rules.
|
||||
final List<DateTime> customDisabledDates;
|
||||
|
||||
/// Selected period wanted by the user.
|
||||
final DatePeriod period;
|
||||
|
||||
/// Creates exception that stores dates that can not be selected.
|
||||
///
|
||||
/// See also:
|
||||
/// *[WeekPicker.onSelectionError]
|
||||
/// *[RangePicker.onSelectionError]
|
||||
UnselectablePeriodException(this.customDisabledDates, this.period);
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
"UnselectablePeriodException:"
|
||||
" ${customDisabledDates.length} dates inside selected period "
|
||||
"(${period.start} - ${period.end}) "
|
||||
"can't be selected according custom rules (selectable pridicate). "
|
||||
"Check 'customDisabledDates' property "
|
||||
"to get entire list of such dates.";
|
||||
}
|
||||
251
flutter_date_pickers-master/lib/src/utils.dart
Normal file
251
flutter_date_pickers-master/lib/src/utils.dart
Normal file
@@ -0,0 +1,251 @@
|
||||
/// Bunch of useful functions for date pickers.
|
||||
class DatePickerUtils {
|
||||
/// Returns if two objects have same year, month and day.
|
||||
/// Time doesn't matter.
|
||||
static bool sameDate(DateTime dateTimeOne, DateTime dateTimeTwo) =>
|
||||
dateTimeOne.year == dateTimeTwo.year &&
|
||||
dateTimeOne.month == dateTimeTwo.month &&
|
||||
dateTimeOne.day == dateTimeTwo.day;
|
||||
|
||||
/// Returns if two objects have same year and month.
|
||||
/// Day and time don't matter/
|
||||
static bool sameMonth(DateTime dateTimeOne, DateTime dateTimeTwo) =>
|
||||
dateTimeOne.year == dateTimeTwo.year
|
||||
&& dateTimeOne.month == dateTimeTwo.month;
|
||||
|
||||
|
||||
// Do not use this directly - call getDaysInMonth instead.
|
||||
static const List<int> _daysInMonth = <int>[
|
||||
31,
|
||||
-1,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
31,
|
||||
30,
|
||||
31,
|
||||
30,
|
||||
31
|
||||
];
|
||||
|
||||
|
||||
/// Returns the number of days in a month, according to the proleptic
|
||||
/// Gregorian calendar.
|
||||
///
|
||||
/// This applies the leap year logic introduced by the Gregorian reforms of
|
||||
/// 1582. It will not give valid results for dates prior to that time.
|
||||
static int getDaysInMonth(int year, int month) {
|
||||
if (month == DateTime.february) {
|
||||
final bool isLeapYear =
|
||||
(year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
|
||||
return isLeapYear ? 29 : 28;
|
||||
}
|
||||
return _daysInMonth[month - 1];
|
||||
}
|
||||
|
||||
|
||||
/// Returns number of months between [startDate] and [endDate]
|
||||
static int monthDelta(DateTime startDate, DateTime endDate) =>
|
||||
(endDate.year - startDate.year) * 12 +
|
||||
endDate.month -
|
||||
startDate.month;
|
||||
|
||||
|
||||
/// Add months to a month truncated date.
|
||||
static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) =>
|
||||
// year is switched automatically if new month > 12
|
||||
DateTime(monthDate.year, monthDate.month + monthsToAdd);
|
||||
|
||||
|
||||
/// Returns number of years between [startDate] and [endDate]
|
||||
static int yearDelta(DateTime startDate, DateTime endDate) =>
|
||||
endDate.year - startDate.year;
|
||||
|
||||
|
||||
/// Returns start of the first day of the week with given day.
|
||||
///
|
||||
/// Start of the week calculated using firstDayIndex which is int from 0 to 6
|
||||
/// where 0 points to Sunday and 6 points to Saturday.
|
||||
/// (according to MaterialLocalization.firstDayIfWeekIndex)
|
||||
static DateTime getFirstDayOfWeek(DateTime day, int firstDayIndex) {
|
||||
// from 1 to 7 where 1 points to Monday and 7 points to Sunday
|
||||
int weekday = day.weekday;
|
||||
|
||||
// to match weekdays where Sunday is 7 not 0
|
||||
if (firstDayIndex == 0) firstDayIndex = 7;
|
||||
|
||||
int diff = weekday - firstDayIndex;
|
||||
if (diff < 0) diff = 7 + diff;
|
||||
|
||||
DateTime firstDayOfWeek = day.subtract(Duration(days: diff));
|
||||
firstDayOfWeek = startOfTheDay(firstDayOfWeek);
|
||||
return firstDayOfWeek;
|
||||
}
|
||||
|
||||
/// Returns end of the last day of the week with given day.
|
||||
///
|
||||
/// Start of the week calculated using firstDayIndex which is int from 0 to 6
|
||||
/// where 0 points to Sunday and 6 points to Saturday.
|
||||
/// (according to MaterialLocalization.firstDayIfWeekIndex)
|
||||
static DateTime getLastDayOfWeek(DateTime day, int firstDayIndex) {
|
||||
// from 1 to 7 where 1 points to Monday and 7 points to Sunday
|
||||
int weekday = day.weekday;
|
||||
|
||||
// to match weekdays where Sunday is 7 not 0
|
||||
if (firstDayIndex == 0) firstDayIndex = 7;
|
||||
|
||||
int lastDayIndex = firstDayIndex - 1;
|
||||
if (lastDayIndex == 0) lastDayIndex = 7;
|
||||
|
||||
int diff = lastDayIndex - weekday;
|
||||
if (diff < 0) diff = 7 + diff;
|
||||
|
||||
DateTime lastDayOfWeek = day.add(Duration(days: diff));
|
||||
lastDayOfWeek = endOfTheDay(lastDayOfWeek);
|
||||
return lastDayOfWeek;
|
||||
}
|
||||
|
||||
/// Returns end of the given day.
|
||||
///
|
||||
/// End time is 1 millisecond before start of the next day.
|
||||
static DateTime endOfTheDay(DateTime date) {
|
||||
DateTime tomorrowStart = DateTime(date.year, date.month, date.day + 1);
|
||||
DateTime result = tomorrowStart.subtract(const Duration(milliseconds: 1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// Returns start of the given day.
|
||||
///
|
||||
/// Start time is 00:00:00.
|
||||
static DateTime startOfTheDay(DateTime date) =>
|
||||
DateTime(date.year, date.month, date.day);
|
||||
|
||||
/// Returns first shown date for the [curMonth].
|
||||
///
|
||||
/// First shown date is not always 1st day of the [curMonth].
|
||||
/// It can be day from previous month if [showEndOfPrevMonth] is true.
|
||||
///
|
||||
/// If [showEndOfPrevMonth] is true empty day cells before 1st [curMonth]
|
||||
/// are filled with days from the previous month.
|
||||
static DateTime firstShownDate({
|
||||
required DateTime curMonth,
|
||||
required bool showEndOfPrevMonth,
|
||||
required int firstDayOfWeekFromSunday}) {
|
||||
|
||||
DateTime result = DateTime(curMonth.year, curMonth.month, 1);
|
||||
|
||||
if (showEndOfPrevMonth) {
|
||||
int firstDayOffset = computeFirstDayOffset(curMonth.year, curMonth.month,
|
||||
firstDayOfWeekFromSunday);
|
||||
if (firstDayOffset == 0) return result;
|
||||
|
||||
|
||||
int prevMonth = curMonth.month - 1;
|
||||
if (prevMonth < 1) prevMonth = 12;
|
||||
|
||||
int prevYear = prevMonth == 12
|
||||
? curMonth.year - 1
|
||||
: curMonth.year;
|
||||
|
||||
int daysInPrevMonth = getDaysInMonth(prevYear, prevMonth);
|
||||
int firstShownDay = daysInPrevMonth - firstDayOffset + 1;
|
||||
result = DateTime(prevYear, prevMonth, firstShownDay);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// Returns last shown date for the [curMonth].
|
||||
///
|
||||
/// Last shown date is not always last day of the [curMonth].
|
||||
/// It can be day from next month if [showStartNextMonth] is true.
|
||||
///
|
||||
/// If [showStartNextMonth] is true empty day cells after last day
|
||||
/// of [curMonth] are filled with days from the next month.
|
||||
static DateTime lastShownDate({
|
||||
required DateTime curMonth,
|
||||
required bool showStartNextMonth,
|
||||
required int firstDayOfWeekFromSunday}) {
|
||||
|
||||
int daysInCurMonth = getDaysInMonth(curMonth.year, curMonth.month);
|
||||
DateTime result = DateTime(curMonth.year, curMonth.month, daysInCurMonth);
|
||||
|
||||
if (showStartNextMonth) {
|
||||
int firstDayOffset = computeFirstDayOffset(curMonth.year, curMonth.month,
|
||||
firstDayOfWeekFromSunday);
|
||||
|
||||
int totalDays = firstDayOffset + daysInCurMonth;
|
||||
int trailingDaysCount = 7 - totalDays % 7;
|
||||
bool fullWeekTrailing = trailingDaysCount == 7;
|
||||
if (fullWeekTrailing) return result;
|
||||
|
||||
result = DateTime(curMonth.year, curMonth.month + 1, trailingDaysCount);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Computes the offset from the first day of week that the first day of the
|
||||
/// [month] falls on.
|
||||
///
|
||||
/// For example, September 1, 2017 falls on a Friday, which in the calendar
|
||||
/// localized for United States English appears as:
|
||||
///
|
||||
/// ```
|
||||
/// S M T W T F S
|
||||
/// _ _ _ _ _ 1 2
|
||||
/// ```
|
||||
///
|
||||
/// The offset for the first day of the months is the number of leading blanks
|
||||
/// in the calendar, i.e. 5.
|
||||
///
|
||||
/// The same date localized for the Russian calendar has a different offset,
|
||||
/// because the first day of week is Monday rather than Sunday:
|
||||
///
|
||||
/// ```
|
||||
/// M T W T F S S
|
||||
/// _ _ _ _ 1 2 3
|
||||
/// ```
|
||||
///
|
||||
/// So the offset is 4, rather than 5.
|
||||
///
|
||||
/// This code consolidates the following:
|
||||
///
|
||||
/// - [DateTime.weekday] provides a 1-based index into days of week, with 1
|
||||
/// falling on Monday.
|
||||
/// - MaterialLocalizations.firstDayOfWeekIndex provides a 0-based index
|
||||
/// into the MaterialLocalizations.narrowWeekdays list.
|
||||
/// - MaterialLocalizations.narrowWeekdays list provides localized names of
|
||||
/// days of week, always starting with Sunday and ending with Saturday.
|
||||
static int computeFirstDayOffset(
|
||||
int year, int month, int firstDayOfWeekFromSunday) {
|
||||
// 0-based day of week, with 0 representing Monday.
|
||||
final int weekdayFromMonday = DateTime(year, month).weekday - 1;
|
||||
// firstDayOfWeekFromSunday recomputed to be Monday-based
|
||||
final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
|
||||
// Number of days between the first day of week appearing on the calendar,
|
||||
// and the day corresponding to the 1-st of the month.
|
||||
return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
|
||||
}
|
||||
|
||||
/// Returns earliest [DateTime] from the list.
|
||||
///
|
||||
/// [dates] must not be null.
|
||||
/// In case it is null, [ArgumentError] will be thrown.
|
||||
static DateTime getEarliestFromList(List<DateTime> dates) {
|
||||
ArgumentError.checkNotNull(dates, "dates");
|
||||
|
||||
return dates.fold(dates[0], getEarliest);
|
||||
}
|
||||
|
||||
/// Returns earliest [DateTime] from two.
|
||||
///
|
||||
/// If two [DateTime]s is the same moment first ([a]) will be return.
|
||||
static DateTime getEarliest(DateTime a, DateTime b)
|
||||
=> a.isBefore(b) ? a : b;
|
||||
}
|
||||
117
flutter_date_pickers-master/lib/src/week_picker.dart
Normal file
117
flutter_date_pickers-master/lib/src/week_picker.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'date_period.dart';
|
||||
import 'date_picker_keys.dart';
|
||||
import 'date_picker_styles.dart';
|
||||
import 'day_based_changable_picker.dart';
|
||||
import 'day_picker_selection.dart';
|
||||
import 'day_type.dart';
|
||||
import 'event_decoration.dart';
|
||||
import 'i_selectable_picker.dart';
|
||||
import 'layout_settings.dart';
|
||||
import 'typedefs.dart';
|
||||
|
||||
/// Date picker for selection a week.
|
||||
class WeekPicker extends StatelessWidget {
|
||||
/// Creates a month picker.
|
||||
WeekPicker(
|
||||
{Key? key,
|
||||
required this.selectedDate,
|
||||
required this.onChanged,
|
||||
required this.firstDate,
|
||||
required this.lastDate,
|
||||
this.initiallyShowDate,
|
||||
this.datePickerLayoutSettings = const DatePickerLayoutSettings(),
|
||||
this.datePickerStyles,
|
||||
this.datePickerKeys,
|
||||
this.selectableDayPredicate,
|
||||
this.onSelectionError,
|
||||
this.eventDecorationBuilder,
|
||||
this.onMonthChanged})
|
||||
: assert(!firstDate.isAfter(lastDate)),
|
||||
assert(!lastDate.isBefore(firstDate)),
|
||||
assert(!selectedDate.isBefore(firstDate)),
|
||||
assert(!selectedDate.isAfter(lastDate)),
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isAfter(lastDate)),
|
||||
assert(initiallyShowDate == null
|
||||
|| !initiallyShowDate.isBefore(firstDate)),
|
||||
super(key: key);
|
||||
|
||||
/// The currently selected date.
|
||||
///
|
||||
/// This date is highlighted in the picker.
|
||||
final DateTime selectedDate;
|
||||
|
||||
/// Called when the user picks a week.
|
||||
final ValueChanged<DatePeriod> onChanged;
|
||||
|
||||
/// Called when the error was thrown after user selection.
|
||||
/// (e.g. when user selected a week with one or more days
|
||||
/// what can't be selected)
|
||||
final OnSelectionError? onSelectionError;
|
||||
|
||||
/// The earliest date the user is permitted to pick.
|
||||
final DateTime firstDate;
|
||||
|
||||
/// The latest date the user is permitted to pick.
|
||||
final DateTime lastDate;
|
||||
|
||||
/// Date for defining what month should be shown initially.
|
||||
///
|
||||
/// In case of null month with earliest date of the selected week
|
||||
/// will be shown.
|
||||
final DateTime? initiallyShowDate;
|
||||
|
||||
/// Layout settings what can be customized by user
|
||||
final DatePickerLayoutSettings datePickerLayoutSettings;
|
||||
|
||||
/// Some keys useful for integration tests
|
||||
final DatePickerKeys? datePickerKeys;
|
||||
|
||||
/// Styles what can be customized by user
|
||||
final DatePickerRangeStyles? datePickerStyles;
|
||||
|
||||
/// Function returns if day can be selected or not.
|
||||
final SelectableDayPredicate? selectableDayPredicate;
|
||||
|
||||
/// Builder to get event decoration for each date.
|
||||
///
|
||||
/// All event styles are overriden by selected styles
|
||||
/// except days with dayType is [DayType.notSelected].
|
||||
final EventDecorationBuilder? eventDecorationBuilder;
|
||||
|
||||
/// Called when the user changes the month.
|
||||
/// New DateTime object represents first day of new month and 00:00 time.
|
||||
final ValueChanged<DateTime>? onMonthChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
|
||||
int firstDayOfWeekIndex = datePickerStyles?.firstDayOfeWeekIndex ??
|
||||
localizations.firstDayOfWeekIndex;
|
||||
|
||||
ISelectablePicker<DatePeriod> weekSelectablePicker = WeekSelectable(
|
||||
selectedDate, firstDayOfWeekIndex, firstDate, lastDate,
|
||||
selectableDayPredicate: selectableDayPredicate);
|
||||
|
||||
return DayBasedChangeablePicker<DatePeriod>(
|
||||
selectablePicker: weekSelectablePicker,
|
||||
// todo: maybe create selection for week
|
||||
// todo: and change logic here to work with it
|
||||
selection: DayPickerSingleSelection(selectedDate),
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
initiallyShownDate: initiallyShowDate,
|
||||
onChanged: onChanged,
|
||||
onSelectionError: onSelectionError,
|
||||
datePickerLayoutSettings: datePickerLayoutSettings,
|
||||
datePickerStyles: datePickerStyles ?? DatePickerRangeStyles(),
|
||||
datePickerKeys: datePickerKeys,
|
||||
eventDecorationBuilder: eventDecorationBuilder,
|
||||
onMonthChanged: onMonthChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user