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 = 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 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)? onDayPressed; final TextStyle? weekdayTextStyle; final Color iconColor; final TextStyle? headerTextStyle; final String? headerText; final TextStyle? weekendTextStyle; final EventList? 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? 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 createState() => _CalendarState(); } enum WeekdayFormat { weekdays, standalone, short, standaloneShort, narrow, standaloneNarrow, } class _CalendarState extends State> { late PageController _controller; late List _dates; late List> _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 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: [ 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: [ 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.markedDatesMap != null ? _renderMarkedMapContainer(now) : Container(), getDayContainer( isSelectable, index, isSelectedDay, isToday, isPrevMonthDay, textStyle, defaultTextStyle, isNextMonthDay, isThisMonthDay, now), ] : [ 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: [ 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 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: [ 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 _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 _daysInRange(DateTime start, DateTime end) { var offset = start.timeZoneOffset; return List.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 _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 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> 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 _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 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, ); } }