summaryrefslogtreecommitdiffstats
path: root/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart
diff options
context:
space:
mode:
Diffstat (limited to 'packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart')
-rw-r--r--packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart1214
1 files changed, 1214 insertions, 0 deletions
diff --git a/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart b/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart
new file mode 100644
index 0000000..14e6cd8
--- /dev/null
+++ b/packages/flutter_calendar_carousel/lib/flutter_calendar_carousel.dart
@@ -0,0 +1,1214 @@
+library flutter_calendar_dooboo;
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_calendar_carousel/classes/event.dart';
+import 'package:flutter_calendar_carousel/classes/event_list.dart';
+import 'package:flutter_calendar_carousel/src/calendar_header.dart';
+import 'package:flutter_calendar_carousel/src/default_styles.dart';
+import 'package:flutter_calendar_carousel/src/weekday_row.dart';
+import 'package:intl/date_symbol_data_local.dart';
+import 'package:intl/intl.dart' show DateFormat;
+
+import 'classes/multiple_marked_dates.dart';
+
+export 'package:flutter_calendar_carousel/classes/event_list.dart';
+
+typedef MarkedDateIconBuilder<T> = Widget? Function(T event);
+typedef void OnDayLongPressed(DateTime day);
+
+/// This builder is called for every day in the calendar.
+/// If you want to build only few custom day containers, return null for the days you want to leave with default looks
+/// All characteristics like circle border are also applied to the custom day container [DayBuilder] provides.
+/// (if supplied function returns null, Calendar's function will be called for [day]).
+/// [isSelectable] - is between [CalendarCarousel.minSelectedDate] and [CalendarCarousel.maxSelectedDate]
+/// [index] - DOES NOT equal day number! Index of the day built in current visible field
+/// [isSelectedDay] - if the day is selected
+/// [isToday] - if the day is similar to [DateTime.now()]
+/// [isPrevMonthDay] - if the day is from previous month
+/// [textStyle] - text style that would have been applied by the calendar if it was to build the day.
+/// Example: if the user provided [CalendarCarousel.todayTextStyle] and [isToday] is true,
+/// [CalendarCarousel.todayTextStyle] would be sent into [DayBuilder]'s [textStyle]. If user didn't
+/// provide it, default [CalendarCarousel]'s textStyle would be sent. Same applies to all text styles like
+/// [CalendarCarousel.prevDaysTextStyle], [CalendarCarousel.daysTextStyle] etc.
+/// [isNextMonthDay] - if the day is from next month
+/// [isThisMonthDay] - if the day is from next month
+/// [day] - day being built.
+typedef Widget? DayBuilder(
+ bool isSelectable,
+ int index,
+ bool isSelectedDay,
+ bool isToday,
+ bool isPrevMonthDay,
+ TextStyle textStyle,
+ bool isNextMonthDay,
+ bool isThisMonthDay,
+ DateTime day);
+
+/// This builder is called for every weekday container (7 times, from Mon to Sun).
+/// [weekday] - weekday built, from 0 to 6.
+/// [weekdayName] - string representation of the weekday (Mon, Tue, Wed, etc).
+typedef Widget WeekdayBuilder(int weekday, String weekdayName);
+
+class CalendarCarousel<T extends EventInterface> extends StatefulWidget {
+ final double viewportFraction;
+ final TextStyle? prevDaysTextStyle;
+ final TextStyle? daysTextStyle;
+ final TextStyle? nextDaysTextStyle;
+ final Color prevMonthDayBorderColor;
+ final Color thisMonthDayBorderColor;
+ final Color nextMonthDayBorderColor;
+ final double dayPadding;
+ final double height;
+ final double width;
+ final TextStyle? todayTextStyle;
+ final Color dayButtonColor;
+ final Color todayBorderColor;
+ final Color todayButtonColor;
+ final DateTime? selectedDateTime;
+ final DateTime? targetDateTime;
+ final TextStyle? selectedDayTextStyle;
+ final Color selectedDayButtonColor;
+ final Color selectedDayBorderColor;
+ final bool? daysHaveCircularBorder;
+ final bool disableDayPressed;
+ final Function(DateTime, List<T>)? onDayPressed;
+ final TextStyle? weekdayTextStyle;
+ final Color iconColor;
+ final TextStyle? headerTextStyle;
+ final String? headerText;
+ final TextStyle? weekendTextStyle;
+ final EventList<T>? markedDatesMap;
+
+ /// Change `makredDateWidget` when `markedDateShowIcon` is set to false.
+ final Widget? markedDateWidget;
+
+ /// Change `OutlinedBorder` when `markedDateShowIcon` is set to false.
+ final OutlinedBorder? markedDateCustomShapeBorder;
+
+ /// Change `TextStyle` when `markedDateShowIcon` is set to false.
+ final TextStyle? markedDateCustomTextStyle;
+
+ /// Icon will overlap the [Day] widget when `markedDateShowIcon` is set to true.
+ /// This will also make below parameters work.
+ final bool markedDateShowIcon;
+ final Color? markedDateIconBorderColor;
+ final int markedDateIconMaxShown;
+ final double markedDateIconMargin;
+ final double markedDateIconOffset;
+ final MarkedDateIconBuilder<T>? markedDateIconBuilder;
+
+ /// null - no indicator, true - show the total events, false - show the total of hidden events
+ final bool? markedDateMoreShowTotal;
+ final Decoration? markedDateMoreCustomDecoration;
+ final TextStyle? markedDateMoreCustomTextStyle;
+ final EdgeInsets headerMargin;
+ final double childAspectRatio;
+ final EdgeInsets weekDayMargin;
+ final EdgeInsets weekDayPadding;
+ final WeekdayBuilder? customWeekDayBuilder;
+ final DayBuilder? customDayBuilder;
+ final Color weekDayBackgroundColor;
+ final bool weekFormat;
+ final bool showWeekDays;
+ final bool showHeader;
+ final bool showHeaderButton;
+ final MultipleMarkedDates? multipleMarkedDates;
+ final Widget? leftButtonIcon;
+ final Widget? rightButtonIcon;
+ final ScrollPhysics? customGridViewPhysics;
+ final Function(DateTime)? onCalendarChanged;
+ final String locale;
+ final int? firstDayOfWeek;
+ final DateTime? minSelectedDate;
+ final DateTime? maxSelectedDate;
+ final TextStyle? inactiveDaysTextStyle;
+ final TextStyle? inactiveWeekendTextStyle;
+ final bool headerTitleTouchable;
+ final Function? onHeaderTitlePressed;
+ final Function? onLeftArrowPressed;
+ final Function? onRightArrowPressed;
+ final WeekdayFormat weekDayFormat;
+ final bool staticSixWeekFormat;
+ final bool isScrollable;
+ final Axis scrollDirection;
+ final bool showOnlyCurrentMonthDate;
+ final bool pageSnapping;
+ final OnDayLongPressed? onDayLongPressed;
+ final CrossAxisAlignment dayCrossAxisAlignment;
+ final MainAxisAlignment dayMainAxisAlignment;
+ final bool showIconBehindDayText;
+ final ScrollPhysics pageScrollPhysics;
+ final bool shouldShowTransform;
+
+ CalendarCarousel({
+ Key? key,
+ this.viewportFraction = 1.0,
+ this.prevDaysTextStyle,
+ this.daysTextStyle,
+ this.nextDaysTextStyle,
+ this.prevMonthDayBorderColor = Colors.transparent,
+ this.thisMonthDayBorderColor = Colors.transparent,
+ this.nextMonthDayBorderColor = Colors.transparent,
+ this.dayPadding = 2.0,
+ this.height = double.infinity,
+ this.width = double.infinity,
+ this.todayTextStyle,
+ this.dayButtonColor = Colors.transparent,
+ this.todayBorderColor = Colors.red,
+ this.todayButtonColor = Colors.red,
+ this.selectedDateTime,
+ this.targetDateTime,
+ this.selectedDayTextStyle,
+ this.selectedDayBorderColor = Colors.green,
+ this.selectedDayButtonColor = Colors.green,
+ this.daysHaveCircularBorder,
+ this.disableDayPressed = false,
+ this.onDayPressed,
+ this.weekdayTextStyle = const TextStyle(),
+ this.iconColor = Colors.blueAccent,
+ this.headerTextStyle,
+ this.headerText,
+ this.weekendTextStyle,
+ this.markedDatesMap,
+ this.markedDateShowIcon = false,
+ this.markedDateIconBorderColor,
+ this.markedDateIconMaxShown = 2,
+ this.markedDateIconMargin = 5.0,
+ this.markedDateIconOffset = 5.0,
+ this.markedDateIconBuilder,
+ this.markedDateMoreShowTotal,
+ this.markedDateMoreCustomDecoration,
+ this.markedDateCustomShapeBorder,
+ this.markedDateCustomTextStyle,
+ this.markedDateMoreCustomTextStyle,
+ this.markedDateWidget,
+ this.multipleMarkedDates,
+ this.headerMargin = const EdgeInsets.symmetric(vertical: 16.0),
+ this.childAspectRatio = 1.0,
+ this.weekDayMargin = const EdgeInsets.only(bottom: 4.0),
+ this.weekDayPadding = const EdgeInsets.all(0.0),
+ this.weekDayBackgroundColor = Colors.transparent,
+ this.customWeekDayBuilder,
+ this.customDayBuilder,
+ this.showWeekDays = true,
+ this.weekFormat = false,
+ this.showHeader = true,
+ this.showHeaderButton = true,
+ this.leftButtonIcon,
+ this.rightButtonIcon,
+ this.customGridViewPhysics,
+ this.onCalendarChanged,
+ this.locale = "en",
+ this.firstDayOfWeek,
+ this.minSelectedDate,
+ this.maxSelectedDate,
+ this.inactiveDaysTextStyle,
+ this.inactiveWeekendTextStyle,
+ this.headerTitleTouchable = false,
+ this.onHeaderTitlePressed,
+ this.onLeftArrowPressed,
+ this.onRightArrowPressed,
+ this.weekDayFormat = WeekdayFormat.short,
+ this.staticSixWeekFormat = false,
+ this.isScrollable = true,
+ this.scrollDirection = Axis.horizontal,
+ this.showOnlyCurrentMonthDate = false,
+ this.pageSnapping = false,
+ this.onDayLongPressed,
+ this.dayCrossAxisAlignment = CrossAxisAlignment.center,
+ this.dayMainAxisAlignment = MainAxisAlignment.center,
+ this.showIconBehindDayText = false,
+ this.pageScrollPhysics = const ScrollPhysics(),
+ this.shouldShowTransform = true,
+ }) : super(key: key);
+
+ @override
+ _CalendarState<T> createState() => _CalendarState<T>();
+}
+
+enum WeekdayFormat {
+ weekdays,
+ standalone,
+ short,
+ standaloneShort,
+ narrow,
+ standaloneNarrow,
+}
+
+class _CalendarState<T extends EventInterface>
+ extends State<CalendarCarousel<T>> {
+ late PageController _controller;
+ late List<DateTime> _dates;
+ late List<List<DateTime>> _weeks;
+ DateTime _selectedDate = DateTime.now();
+ late DateTime _targetDate;
+ int _startWeekday = 0;
+ int _endWeekday = 0;
+ late DateFormat _localeDate;
+ int _pageNum = 0;
+ late DateTime minDate;
+ late DateTime maxDate;
+
+ /// When FIRSTDAYOFWEEK is 0 in dart-intl, it represents Monday. However it is the second day in the arrays of Weekdays.
+ /// Therefore we need to add 1 modulo 7 to pick the right weekday from intl. (cf. [GlobalMaterialLocalizations])
+ late int firstDayOfWeek;
+
+ /// If the setState called from this class, don't reload the selectedDate, but it should reload selected date if called from external class
+
+ @override
+ initState() {
+ super.initState();
+ initializeDateFormatting();
+
+ minDate = widget.minSelectedDate ?? DateTime(2018);
+ maxDate = widget.maxSelectedDate ??
+ DateTime(
+ DateTime.now().year + 1, DateTime.now().month, DateTime.now().day);
+
+ final selectedDateTime = widget.selectedDateTime;
+ if (selectedDateTime != null) _selectedDate = selectedDateTime;
+
+ _init();
+
+ /// setup pageController
+ _controller = PageController(
+ initialPage: this._pageNum,
+ keepPage: true,
+ viewportFraction: widget.viewportFraction,
+
+ /// width percentage
+ );
+
+ _localeDate = DateFormat.yMMM(widget.locale);
+ firstDayOfWeek = widget.firstDayOfWeek ??
+ (_localeDate.dateSymbols.FIRSTDAYOFWEEK + 1) % 7;
+
+ _setDate();
+ }
+
+ @override
+ void didUpdateWidget(CalendarCarousel<T> oldWidget) {
+ if (widget.targetDateTime != null && widget.targetDateTime != _targetDate) {
+ _init();
+ _setDate(_pageNum);
+ }
+
+ super.didUpdateWidget(oldWidget);
+ }
+
+ @override
+ dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ _init() {
+ final targetDateTime = widget.targetDateTime;
+ if (targetDateTime != null) {
+ if (targetDateTime.difference(minDate).inDays < 0) {
+ _targetDate = minDate;
+ } else if (targetDateTime.difference(maxDate).inDays > 0) {
+ _targetDate = maxDate;
+ } else {
+ _targetDate = targetDateTime;
+ }
+ } else {
+ _targetDate = _selectedDate;
+ }
+ if (widget.weekFormat) {
+ _pageNum = _targetDate.difference(_firstDayOfWeek(minDate)).inDays ~/ 7;
+ } else {
+ _pageNum = (_targetDate.year - minDate.year) * 12 +
+ _targetDate.month -
+ minDate.month;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final headerText = widget.headerText;
+ return Container(
+ width: widget.width,
+ height: widget.height,
+ child: Column(
+ children: <Widget>[
+ CalendarHeader(
+ showHeader: widget.showHeader,
+ headerMargin: widget.headerMargin,
+ headerTitle: headerText != null
+ ? headerText
+ : widget.weekFormat
+ ? '${_localeDate.format(this._weeks[this._pageNum].first)}'
+ : '${_localeDate.format(this._dates[this._pageNum])}',
+ headerTextStyle: widget.headerTextStyle,
+ showHeaderButtons: widget.showHeaderButton,
+ headerIconColor: widget.iconColor,
+ leftButtonIcon: widget.leftButtonIcon,
+ rightButtonIcon: widget.rightButtonIcon,
+ onLeftButtonPressed: () {
+ widget.onLeftArrowPressed?.call();
+
+ if (this._pageNum > 0) _setDate(this._pageNum - 1);
+ },
+ onRightButtonPressed: () {
+ widget.onRightArrowPressed?.call();
+
+ if (widget.weekFormat) {
+ if (this._weeks.length - 1 > this._pageNum)
+ _setDate(this._pageNum + 1);
+ } else {
+ if (this._dates.length - 1 > this._pageNum)
+ _setDate(this._pageNum + 1);
+ }
+ },
+ onHeaderTitlePressed: widget.headerTitleTouchable
+ ? () {
+ final onHeaderTitlePressed = widget.onHeaderTitlePressed;
+ if (onHeaderTitlePressed != null) {
+ onHeaderTitlePressed();
+ } else {
+ _selectDateFromPicker();
+ }
+ }
+ : null,
+ ),
+ WeekdayRow(
+ firstDayOfWeek,
+ widget.customWeekDayBuilder,
+ showWeekdays: widget.showWeekDays,
+ weekdayFormat: widget.weekDayFormat,
+ weekdayMargin: widget.weekDayMargin,
+ weekdayPadding: widget.weekDayPadding,
+ weekdayBackgroundColor: widget.weekDayBackgroundColor,
+ weekdayTextStyle: widget.weekdayTextStyle,
+ localeDate: _localeDate,
+ ),
+ Expanded(
+ child: PageView.builder(
+ itemCount:
+ widget.weekFormat ? this._weeks.length : this._dates.length,
+ physics: widget.isScrollable
+ ? widget.pageScrollPhysics
+ : NeverScrollableScrollPhysics(),
+ scrollDirection: widget.scrollDirection,
+ onPageChanged: (index) {
+ this._setDate(index);
+ },
+ controller: _controller,
+ itemBuilder: (context, index) {
+ return widget.weekFormat ? weekBuilder(index) : builder(index);
+ },
+ pageSnapping: widget.pageSnapping,
+ )),
+ ],
+ ),
+ );
+ }
+
+ Widget getDefaultDayContainer(
+ bool isSelectable,
+ int index,
+ bool isSelectedDay,
+ bool isToday,
+ bool isPrevMonthDay,
+ TextStyle? textStyle,
+ TextStyle defaultTextStyle,
+ bool isNextMonthDay,
+ bool isThisMonthDay,
+ DateTime now,
+ ) {
+ return Container(
+ width: double.infinity,
+ height: double.infinity,
+ child: Row(
+ crossAxisAlignment: widget.dayCrossAxisAlignment,
+ mainAxisAlignment: widget.dayMainAxisAlignment,
+ children: <Widget>[
+ DefaultTextStyle(
+ style: getDefaultDayStyle(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay),
+ child: Text(
+ '${now.day}',
+ semanticsLabel: now.day.toString(),
+ style: getDayStyle(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now),
+ maxLines: 1,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget renderDay(
+ bool isSelectable,
+ int index,
+ bool isSelectedDay,
+ bool isToday,
+ bool isPrevMonthDay,
+ TextStyle? textStyle,
+ TextStyle defaultTextStyle,
+ bool isNextMonthDay,
+ bool isThisMonthDay,
+ DateTime now,
+ ) {
+ // If day is in Multiple selection mode, get its color
+ bool isMultipleMarked = widget.multipleMarkedDates?.isMarked(now) ?? false;
+ Color? multipleMarkedColor = widget.multipleMarkedDates?.getColor(now);
+
+ final markedDatesMap = widget.markedDatesMap;
+ return Container(
+ margin: EdgeInsets.all(widget.dayPadding),
+ child: GestureDetector(
+ onLongPress: () => _onDayLongPressed(now),
+ child: TextButton(
+ style: TextButton.styleFrom(
+ shape: widget.markedDateCustomShapeBorder != null &&
+ markedDatesMap != null &&
+ markedDatesMap.getEvents(now).length > 0
+ ? widget.markedDateCustomShapeBorder
+ : widget.daysHaveCircularBorder == null
+ ? CircleBorder()
+ : widget.daysHaveCircularBorder ?? false
+ ? CircleBorder(
+ side: BorderSide(
+ color: isSelectedDay
+ ? widget.selectedDayBorderColor
+ : isToday
+ ? widget.todayBorderColor
+ : isPrevMonthDay
+ ? widget.prevMonthDayBorderColor
+ : isNextMonthDay
+ ? widget.nextMonthDayBorderColor
+ : widget.thisMonthDayBorderColor,
+ ),
+ )
+ : RoundedRectangleBorder(
+ side: BorderSide(
+ color: isSelectedDay
+ ? widget.selectedDayBorderColor
+ : isToday
+ ? widget.todayBorderColor
+ : isPrevMonthDay
+ ? widget.prevMonthDayBorderColor
+ : isNextMonthDay
+ ? widget.nextMonthDayBorderColor
+ : widget.thisMonthDayBorderColor,
+ ),
+ ),
+ backgroundColor: isSelectedDay
+ ? widget.selectedDayButtonColor
+ : isToday
+ ? widget.todayButtonColor
+
+ // If day is in Multiple selection mode, apply a different color
+ : isMultipleMarked
+ ? multipleMarkedColor
+ : widget.dayButtonColor,
+ padding: EdgeInsets.all(widget.dayPadding),
+ ),
+ onPressed: widget.disableDayPressed ? null : () => _onDayPressed(now),
+ child: Stack(
+ children: widget.showIconBehindDayText
+ ? <Widget>[
+ widget.markedDatesMap != null
+ ? _renderMarkedMapContainer(now)
+ : Container(),
+ getDayContainer(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now),
+ ]
+ : <Widget>[
+ getDayContainer(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now),
+ widget.markedDatesMap != null
+ ? _renderMarkedMapContainer(now)
+ : Container(),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ AnimatedBuilder builder(int slideIndex) {
+ _startWeekday = _dates[slideIndex].weekday - firstDayOfWeek;
+ if (_startWeekday == 7) {
+ _startWeekday = 0;
+ }
+ _endWeekday =
+ DateTime(_dates[slideIndex].year, _dates[slideIndex].month + 1, 1)
+ .weekday -
+ firstDayOfWeek;
+ double screenWidth = MediaQuery.of(context).size.width;
+ int totalItemCount = widget.staticSixWeekFormat
+ ? 42
+ : DateTime(
+ _dates[slideIndex].year,
+ _dates[slideIndex].month + 1,
+ 0,
+ ).day +
+ _startWeekday +
+ (7 - _endWeekday);
+ int year = _dates[slideIndex].year;
+ int month = _dates[slideIndex].month;
+
+ return AnimatedBuilder(
+ animation: _controller,
+ builder: (context, child) {
+ if (!widget.shouldShowTransform) {
+ return child!;
+ }
+ double value = 1.0;
+ if (_controller.position.haveDimensions) {
+ value = _controller.page! - slideIndex;
+ value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
+ }
+
+ return Center(
+ child: SizedBox(
+ height: Curves.easeOut.transform(value) * widget.height,
+ width: Curves.easeOut.transform(value) * screenWidth,
+ child: child,
+ ),
+ );
+ },
+ child: Stack(
+ children: <Widget>[
+ Positioned(
+ child: Container(
+ width: double.infinity,
+ height: double.infinity,
+ child: GridView.count(
+ physics: widget.customGridViewPhysics,
+ crossAxisCount: 7,
+ childAspectRatio: widget.childAspectRatio,
+ padding: EdgeInsets.zero,
+ children: List.generate(totalItemCount,
+
+ /// last day of month + weekday
+ (index) {
+ final selectedDateTime = widget.selectedDateTime;
+ bool isToday =
+ DateTime.now().day == index + 1 - _startWeekday &&
+ DateTime.now().month == month &&
+ DateTime.now().year == year;
+ bool isSelectedDay = selectedDateTime != null &&
+ selectedDateTime.year == year &&
+ selectedDateTime.month == month &&
+ selectedDateTime.day == index + 1 - _startWeekday;
+ bool isPrevMonthDay = index < _startWeekday;
+ bool isNextMonthDay = index >=
+ (DateTime(year, month + 1, 0).day) + _startWeekday;
+ bool isThisMonthDay = !isPrevMonthDay && !isNextMonthDay;
+
+ DateTime now = DateTime(year, month, 1);
+ TextStyle? textStyle;
+ TextStyle defaultTextStyle;
+ if (isPrevMonthDay && !widget.showOnlyCurrentMonthDate) {
+ now = now.subtract(Duration(days: _startWeekday - index));
+ textStyle = widget.prevDaysTextStyle;
+ defaultTextStyle = defaultPrevDaysTextStyle;
+ } else if (isThisMonthDay) {
+ now = DateTime(year, month, index + 1 - _startWeekday);
+ textStyle = isSelectedDay
+ ? widget.selectedDayTextStyle
+ : isToday
+ ? widget.todayTextStyle
+ : widget.daysTextStyle;
+ defaultTextStyle = isSelectedDay
+ ? defaultSelectedDayTextStyle
+ : isToday
+ ? defaultTodayTextStyle
+ : defaultDaysTextStyle;
+ } else if (!widget.showOnlyCurrentMonthDate) {
+ now = DateTime(year, month, index + 1 - _startWeekday);
+ textStyle = widget.nextDaysTextStyle;
+ defaultTextStyle = defaultNextDaysTextStyle;
+ } else {
+ return Container();
+ }
+ final markedDatesMap = widget.markedDatesMap;
+ if (widget.markedDateCustomTextStyle != null &&
+ markedDatesMap != null &&
+ markedDatesMap.getEvents(now).length > 0) {
+ textStyle = widget.markedDateCustomTextStyle;
+ }
+ bool isSelectable = true;
+ if (now.millisecondsSinceEpoch <
+ minDate.millisecondsSinceEpoch)
+ isSelectable = false;
+ else if (now.millisecondsSinceEpoch >
+ maxDate.millisecondsSinceEpoch) isSelectable = false;
+ return renderDay(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now);
+ }),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ AnimatedBuilder weekBuilder(int slideIndex) {
+ double screenWidth = MediaQuery.of(context).size.width;
+ List<DateTime> weekDays = _weeks[slideIndex];
+
+ weekDays = weekDays
+ .map((weekDay) => weekDay.add(Duration(days: firstDayOfWeek)))
+ .toList();
+
+ return AnimatedBuilder(
+ animation: _controller,
+ builder: (context, child) {
+ double value = 1.0;
+ if (_controller.position.haveDimensions) {
+ value = _controller.page! - slideIndex;
+ value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
+ }
+
+ return Center(
+ child: SizedBox(
+ height: Curves.easeOut.transform(value) * widget.height,
+ width: Curves.easeOut.transform(value) * screenWidth,
+ child: child,
+ ),
+ );
+ },
+ child: Stack(
+ children: <Widget>[
+ Positioned(
+ child: Container(
+ width: double.infinity,
+ height: double.infinity,
+ child: GridView.count(
+ physics: widget.customGridViewPhysics,
+ crossAxisCount: 7,
+ childAspectRatio: widget.childAspectRatio,
+ padding: EdgeInsets.zero,
+ children: List.generate(weekDays.length, (index) {
+ /// last day of month + weekday
+ bool isToday = weekDays[index].day == DateTime.now().day &&
+ weekDays[index].month == DateTime.now().month &&
+ weekDays[index].year == DateTime.now().year;
+ bool isSelectedDay =
+ this._selectedDate.year == weekDays[index].year &&
+ this._selectedDate.month == weekDays[index].month &&
+ this._selectedDate.day == weekDays[index].day;
+ bool isPrevMonthDay =
+ weekDays[index].month < this._targetDate.month;
+ bool isNextMonthDay =
+ weekDays[index].month > this._targetDate.month;
+ bool isThisMonthDay = !isPrevMonthDay && !isNextMonthDay;
+
+ DateTime now = DateTime(weekDays[index].year,
+ weekDays[index].month, weekDays[index].day);
+ TextStyle? textStyle;
+ TextStyle defaultTextStyle;
+ if (isPrevMonthDay && !widget.showOnlyCurrentMonthDate) {
+ textStyle = widget.prevDaysTextStyle;
+ defaultTextStyle = defaultPrevDaysTextStyle;
+ } else if (isThisMonthDay) {
+ textStyle = isSelectedDay
+ ? widget.selectedDayTextStyle
+ : isToday
+ ? widget.todayTextStyle
+ : widget.daysTextStyle;
+ defaultTextStyle = isSelectedDay
+ ? defaultSelectedDayTextStyle
+ : isToday
+ ? defaultTodayTextStyle
+ : defaultDaysTextStyle;
+ } else if (!widget.showOnlyCurrentMonthDate) {
+ textStyle = widget.nextDaysTextStyle;
+ defaultTextStyle = defaultNextDaysTextStyle;
+ } else {
+ return Container();
+ }
+ bool isSelectable = true;
+ if (now.millisecondsSinceEpoch <
+ minDate.millisecondsSinceEpoch)
+ isSelectable = false;
+ else if (now.millisecondsSinceEpoch >
+ maxDate.millisecondsSinceEpoch) isSelectable = false;
+ return renderDay(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now);
+ }),
+ ),
+ ),
+ ),
+ ],
+ ));
+ }
+
+ List<DateTime> _getDaysInWeek([DateTime? selectedDate]) {
+ if (selectedDate == null) selectedDate = new DateTime.now();
+
+ var firstDayOfCurrentWeek = _firstDayOfWeek(selectedDate);
+ var lastDayOfCurrentWeek = _lastDayOfWeek(selectedDate);
+
+ return _daysInRange(firstDayOfCurrentWeek, lastDayOfCurrentWeek).toList();
+ }
+
+ DateTime _firstDayOfWeek(DateTime date) {
+ var day = _createUTCMiddayDateTime(date);
+ return day.subtract(new Duration(days: date.weekday % 7));
+ }
+
+ DateTime _lastDayOfWeek(DateTime date) {
+ var day = _createUTCMiddayDateTime(date);
+ return day.add(new Duration(days: 7 - day.weekday % 7));
+ }
+
+ DateTime _createUTCMiddayDateTime(DateTime date) {
+ // Magic const: 12 is to maintain compatibility with date_utils
+ return new DateTime.utc(date.year, date.month, date.day, 12, 0, 0);
+ }
+
+ Iterable<DateTime> _daysInRange(DateTime start, DateTime end) {
+ var offset = start.timeZoneOffset;
+
+ return List<int>.generate(end.difference(start).inDays, (i) => i + 1)
+ .map((int i) {
+ var d = start.add(Duration(days: i - 1));
+
+ var timeZoneDiff = d.timeZoneOffset - offset;
+ if (timeZoneDiff.inSeconds != 0) {
+ offset = d.timeZoneOffset;
+ d = d.subtract(new Duration(seconds: timeZoneDiff.inSeconds));
+ }
+ return d;
+ });
+ }
+
+ void _onDayLongPressed(DateTime picked) {
+ widget.onDayLongPressed?.call(picked);
+ }
+
+ void _onDayPressed(DateTime picked) {
+ if (picked.millisecondsSinceEpoch < minDate.millisecondsSinceEpoch) return;
+ if (picked.millisecondsSinceEpoch > maxDate.millisecondsSinceEpoch) return;
+
+ setState(() {
+ _selectedDate = picked;
+ });
+ widget.onDayPressed
+ ?.call(picked, widget.markedDatesMap?.getEvents(picked) ?? const []);
+ }
+
+ Future<Null> _selectDateFromPicker() async {
+ DateTime? selected = await showDatePicker(
+ context: context,
+ initialDate: _selectedDate,
+ firstDate: minDate,
+ lastDate: maxDate,
+ );
+
+ if (selected != null) {
+ // updating selected date range based on selected week
+ setState(() {
+ _selectedDate = selected;
+ });
+ widget.onDayPressed?.call(
+ selected, widget.markedDatesMap?.getEvents(selected) ?? const []);
+ }
+ }
+
+ void _setDatesAndWeeks() {
+ /// Setup default calendar format
+ List<DateTime> date = [];
+ int currentDateIndex = 0;
+ for (int _cnt = 0;
+ 0 >=
+ DateTime(minDate.year, minDate.month + _cnt)
+ .difference(DateTime(maxDate.year, maxDate.month))
+ .inDays;
+ _cnt++) {
+ date.add(DateTime(minDate.year, minDate.month + _cnt, 1));
+ if (0 ==
+ date.last
+ .difference(
+ DateTime(this._targetDate.year, this._targetDate.month))
+ .inDays) {
+ currentDateIndex = _cnt;
+ }
+ }
+
+ /// Setup week-only format
+ List<List<DateTime>> week = [];
+ for (int _cnt = 0;
+ 0 >=
+ minDate
+ .add(Duration(days: 7 * _cnt))
+ .difference(maxDate.add(Duration(days: 7)))
+ .inDays;
+ _cnt++) {
+ week.add(_getDaysInWeek(minDate.add(new Duration(days: 7 * _cnt))));
+ }
+
+ _startWeekday = date[currentDateIndex].weekday - firstDayOfWeek;
+ /*if (widget.showOnlyCurrentMonthDate) {
+ _startWeekday--;
+ }*/
+ if (/*widget.showOnlyCurrentMonthDate && */ _startWeekday == 7) {
+ _startWeekday = 0;
+ }
+ _endWeekday = DateTime(date[currentDateIndex].year,
+ date[currentDateIndex].month + 1, 1)
+ .weekday -
+ firstDayOfWeek;
+ this._dates = date;
+ this._weeks = week;
+// this._selectedDate = widget.selectedDateTime != null
+// ? widget.selectedDateTime
+// : DateTime.now();
+ }
+
+ void _setDate([int page = -1]) {
+ if (page == -1) {
+ setState(() {
+ _setDatesAndWeeks();
+ });
+ } else {
+ if (widget.weekFormat) {
+ setState(() {
+ this._pageNum = page;
+ this._targetDate = this._weeks[page].first;
+ });
+
+ _controller.animateToPage(page,
+ duration: Duration(milliseconds: 1), curve: Threshold(0.0));
+ } else {
+ setState(() {
+ this._pageNum = page;
+ this._targetDate = this._dates[page];
+ _startWeekday = _dates[page].weekday - firstDayOfWeek;
+ _endWeekday = _lastDayOfWeek(_dates[page]).weekday - firstDayOfWeek;
+ });
+ _controller.animateToPage(page,
+ duration: Duration(milliseconds: 1), curve: Threshold(0.0));
+ }
+
+ //call callback
+ final onCalendarChanged = widget.onCalendarChanged;
+ if (onCalendarChanged != null) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ onCalendarChanged(!widget.weekFormat
+ ? this._dates[page]
+ : this._weeks[page][firstDayOfWeek]);
+ });
+ }
+ }
+ }
+
+ Widget _renderMarkedMapContainer(DateTime now) {
+ if (widget.markedDateShowIcon) {
+ return Stack(
+ children: _renderMarkedMap(now),
+ );
+ } else {
+ return Container(
+ height: double.infinity,
+ padding: EdgeInsets.only(bottom: 4.0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: _renderMarkedMap(now),
+ ),
+ );
+ }
+ }
+
+ List<Widget> _renderMarkedMap(DateTime now) {
+ final markedEvents = widget.markedDatesMap?.getEvents(now) ?? [];
+ final markedDateIconBuilder = widget.markedDateIconBuilder;
+ final markedDateWidget = widget.markedDateWidget;
+ final markedDateMoreShowTotal = widget.markedDateMoreShowTotal;
+ final markedDateMoreCustomTextStyle = widget.markedDateMoreCustomTextStyle;
+ final markedDateIconMargin = widget.markedDateIconMargin;
+ final markedDateShowIcon = widget.markedDateShowIcon;
+ final markedDateIconMaxShown = widget.markedDateIconMaxShown;
+ final markedDateIconOffset = widget.markedDateIconOffset;
+ final markedDateMoreCustomDecoration =
+ widget.markedDateMoreCustomDecoration;
+
+ if (markedEvents.length > 0) {
+ List<Widget> tmp = [];
+ int count = 0;
+ int eventIndex = 0;
+ double offset = 0.0;
+ double padding = markedDateIconMargin;
+ markedEvents.forEach((T event) {
+ if (markedDateShowIcon) {
+ if (tmp.length > 0 && tmp.length < markedDateIconMaxShown) {
+ offset += markedDateIconOffset;
+ }
+ if (tmp.length < markedDateIconMaxShown &&
+ markedDateIconBuilder != null) {
+ tmp.add(Center(
+ child: new Container(
+ padding: EdgeInsets.only(
+ top: padding + offset,
+ left: padding + offset,
+ right: padding - offset,
+ bottom: padding - offset,
+ ),
+ width: double.infinity,
+ height: double.infinity,
+ child: markedDateIconBuilder(event),
+ )));
+ } else {
+ count++;
+ }
+ if (count > 0 && markedDateMoreShowTotal != null) {
+ tmp.add(
+ Positioned(
+ bottom: 0.0,
+ right: 0.0,
+ child: Container(
+ padding: EdgeInsets.all(4.0),
+ width: markedDateMoreShowTotal ? 18 : null,
+ height: markedDateMoreShowTotal ? 18 : null,
+ decoration: markedDateMoreCustomDecoration == null
+ ? new BoxDecoration(
+ color: Colors.red,
+ borderRadius:
+ BorderRadius.all(Radius.circular(1000.0)),
+ )
+ : markedDateMoreCustomDecoration,
+ child: Center(
+ child: Text(
+ markedDateMoreShowTotal
+ ? (count + markedDateIconMaxShown).toString()
+ : (count.toString() + '+'),
+ semanticsLabel: markedDateMoreShowTotal
+ ? (count + markedDateIconMaxShown).toString()
+ : (count.toString() + '+'),
+ style: markedDateMoreCustomTextStyle == null
+ ? TextStyle(
+ fontSize: 9.0,
+ color: Colors.white,
+ fontWeight: FontWeight.normal)
+ : markedDateMoreCustomTextStyle,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+ } else {
+ //max 5 dots
+ if (eventIndex < 5) {
+ Widget? widget;
+
+ if (markedDateIconBuilder != null) {
+ widget = markedDateIconBuilder(event);
+ }
+
+ if (widget != null) {
+ tmp.add(widget);
+ } else {
+ final dot = event.getDot();
+ if (dot != null) {
+ tmp.add(dot);
+ } else if (markedDateWidget != null) {
+ tmp.add(markedDateWidget);
+ } else {
+ tmp.add(defaultMarkedDateWidget);
+ }
+ }
+ }
+ }
+
+ eventIndex++;
+ });
+ return tmp;
+ }
+ return [];
+ }
+
+ TextStyle getDefaultDayStyle(
+ bool isSelectable,
+ int index,
+ bool isSelectedDay,
+ bool isToday,
+ bool isPrevMonthDay,
+ TextStyle? textStyle,
+ TextStyle defaultTextStyle,
+ bool isNextMonthDay,
+ bool isThisMonthDay,
+ ) {
+ return !isSelectable
+ ? defaultInactiveDaysTextStyle
+ : (_localeDate.dateSymbols.WEEKENDRANGE
+ .contains((index - 1 + firstDayOfWeek) % 7)) &&
+ !isSelectedDay &&
+ !isToday
+ ? (isPrevMonthDay
+ ? defaultPrevDaysTextStyle
+ : isNextMonthDay
+ ? defaultNextDaysTextStyle
+ : isSelectable
+ ? defaultWeekendTextStyle
+ : defaultInactiveWeekendTextStyle)
+ : isToday
+ ? defaultTodayTextStyle
+ : isSelectable && textStyle != null
+ ? textStyle
+ : defaultTextStyle;
+ }
+
+ TextStyle? getDayStyle(
+ bool isSelectable,
+ int index,
+ bool isSelectedDay,
+ bool isToday,
+ bool isPrevMonthDay,
+ TextStyle? textStyle,
+ TextStyle defaultTextStyle,
+ bool isNextMonthDay,
+ bool isThisMonthDay,
+ DateTime now) {
+ // If day is in multiple selection get its style(if available)
+ bool isMultipleMarked = widget.multipleMarkedDates?.isMarked(now) ?? false;
+ TextStyle? mutipleMarkedTextStyle =
+ widget.multipleMarkedDates?.getTextStyle(now);
+
+ return isSelectedDay && widget.selectedDayTextStyle != null
+ ? widget.selectedDayTextStyle
+ : isMultipleMarked
+ ? mutipleMarkedTextStyle
+ : (_localeDate.dateSymbols.WEEKENDRANGE
+ .contains((index - 1 + firstDayOfWeek) % 7)) &&
+ !isSelectedDay &&
+ isThisMonthDay &&
+ !isToday
+ ? (isSelectable
+ ? widget.weekendTextStyle
+ : widget.inactiveWeekendTextStyle)
+ : !isSelectable
+ ? widget.inactiveDaysTextStyle
+ : isPrevMonthDay
+ ? widget.prevDaysTextStyle
+ : isNextMonthDay
+ ? widget.nextDaysTextStyle
+ : isToday
+ ? widget.todayTextStyle
+ : widget.daysTextStyle;
+ }
+
+ Widget getDayContainer(
+ bool isSelectable,
+ int index,
+ bool isSelectedDay,
+ bool isToday,
+ bool isPrevMonthDay,
+ TextStyle? textStyle,
+ TextStyle defaultTextStyle,
+ bool isNextMonthDay,
+ bool isThisMonthDay,
+ DateTime now) {
+ final customDayBuilder = widget.customDayBuilder;
+
+ Widget? dayContainer;
+ if (customDayBuilder != null) {
+ final appTextStyle = DefaultTextStyle.of(context).style;
+
+ final dayStyle = getDayStyle(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now,
+ );
+
+ final styleForBuilder = appTextStyle.merge(dayStyle);
+
+ dayContainer = customDayBuilder(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ styleForBuilder,
+ isNextMonthDay,
+ isThisMonthDay,
+ now);
+ }
+
+ return dayContainer ??
+ getDefaultDayContainer(
+ isSelectable,
+ index,
+ isSelectedDay,
+ isToday,
+ isPrevMonthDay,
+ textStyle,
+ defaultTextStyle,
+ isNextMonthDay,
+ isThisMonthDay,
+ now,
+ );
+ }
+}