diff --git a/example/lib/main.dart b/example/lib/main.dart index c17d9d24..7ba50c2d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -43,12 +43,18 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State with TickerProviderStateMixin { DateTime _selectedDay; + CalendarFormat _calendarFormat; Map _events; Map _visibleEvents; Map _visibleHolidays; List _selectedEvents; AnimationController _controller; + // TODO: add default CalendarController + CalendarController _calendarController; + + // GlobalKey _key; + @override void initState() { super.initState(); @@ -75,6 +81,12 @@ class _MyHomePageState extends State with TickerProviderStateMixin { _visibleEvents = _events; _visibleHolidays = _holidays; + _calendarFormat = CalendarFormat.twoWeeks; + + // _key = GlobalKey(); + + _calendarController = CalendarController(); + _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 400), @@ -84,6 +96,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { } void _onDaySelected(DateTime day, List events) { + print('ON DAY SELECTED'); setState(() { _selectedDay = day; _selectedEvents = events; @@ -91,6 +104,12 @@ class _MyHomePageState extends State with TickerProviderStateMixin { } void _onVisibleDaysChanged(DateTime first, DateTime last, CalendarFormat format) { + print('ON VISIBLE DAYS CHANGED'); + + // print(first); + // print(last); + + // THIS SET STATE BUGS THE APP setState(() { _visibleEvents = Map.fromEntries( _events.entries.where( @@ -98,6 +117,12 @@ class _MyHomePageState extends State with TickerProviderStateMixin { entry.key.isAfter(first.subtract(const Duration(days: 1))) && entry.key.isBefore(last.add(const Duration(days: 1))), ), + // (entry) { + // final day = DateTime.utc(entry.key.year, entry.key.month, entry.key.day, 12); + + // return day.isAfter(first.subtract(const Duration(days: 1))) && + // day.isBefore(last.add(const Duration(days: 1))); + // }), ); _visibleHolidays = Map.fromEntries( @@ -105,8 +130,19 @@ class _MyHomePageState extends State with TickerProviderStateMixin { (entry) => entry.key.isAfter(first.subtract(const Duration(days: 1))) && entry.key.isBefore(last.add(const Duration(days: 1))), + // (entry) { + // final day = DateTime.utc(entry.key.year, entry.key.month, entry.key.day, 12); + + // return day.isAfter(first.subtract(const Duration(days: 1))) && + // day.isBefore(last.add(const Duration(days: 1))); + // }), ), ); + + // if (format == CalendarFormat.month) { + // // _selectedDay = first; + // _key.currentState.setSelectedDay(first); + // } }); } @@ -123,8 +159,65 @@ class _MyHomePageState extends State with TickerProviderStateMixin { //----------------------- _buildTableCalendar(), // _buildTableCalendarWithBuilders(), - const SizedBox(height: 8.0), - Expanded(child: _buildEventList()), + // const SizedBox(height: 8.0), + // Expanded(child: _buildEventList()), + const SizedBox(height: 20.0), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + child: Text('month'), + onPressed: () { + // setState(() { + // _calendarFormat = CalendarFormat.month; + // }); + // _key.currentState.setCalendarFormat(CalendarFormat.month); + _calendarController.setCalendarFormat(CalendarFormat.month); + }, + ), + RaisedButton( + child: Text('2 weeks'), + onPressed: () { + // setState(() { + // _calendarFormat = CalendarFormat.twoWeeks; + // }); + // _key.currentState.setCalendarFormat(CalendarFormat.twoWeeks); + _calendarController.setCalendarFormat(CalendarFormat.twoWeeks); + }, + ), + RaisedButton( + child: Text('week'), + onPressed: () { + // setState(() { + // _calendarFormat = CalendarFormat.week; + // }); + // _key.currentState.setCalendarFormat(CalendarFormat.week); + _calendarController.setCalendarFormat(CalendarFormat.week); + }, + ), + ], + ), + const SizedBox(height: 12.0), + Builder( + builder: (context) { + return RaisedButton( + child: Text('set 16.07.2019'), + onPressed: () { + // setState(() { + // // _selectedDay = DateTime(2019, 7, 16); + // _selectedDay = DateTime(2019, 11, 4); + // }); + // TableCalendar.of(context).setSelectedDay(DateTime(2019, 7, 16)); + // _key.currentState.setSelectedDay(DateTime(2019, 7, 16)); + + // TODO: watch a video on keys + + _calendarController.setSelectedDay(DateTime(2019, 4, 10), runCallback: true); + }, + ); + }, + ), ], ), ); @@ -133,10 +226,15 @@ class _MyHomePageState extends State with TickerProviderStateMixin { // Simple TableCalendar configuration (using Styles) Widget _buildTableCalendar() { return TableCalendar( + // key: _key, locale: 'en_US', + controller: _calendarController, events: _visibleEvents, holidays: _visibleHolidays, - initialCalendarFormat: CalendarFormat.week, + // selectedDay: _selectedDay, + // calendarFormat: _calendarFormat, + // forcedCalendarFormat: _calendarFormat, + // initialCalendarFormat: CalendarFormat.week, formatAnimation: FormatAnimation.slide, startingDayOfWeek: StartingDayOfWeek.monday, availableGestures: AvailableGestures.all, @@ -149,6 +247,7 @@ class _MyHomePageState extends State with TickerProviderStateMixin { selectedColor: Colors.deepOrange[400], todayColor: Colors.deepOrange[200], markersColor: Colors.brown[700], + outsideDaysVisible: false, ), headerStyle: HeaderStyle( formatButtonTextStyle: TextStyle().copyWith(color: Colors.white, fontSize: 15.0), @@ -162,6 +261,38 @@ class _MyHomePageState extends State with TickerProviderStateMixin { ); } + // // Simple TableCalendar configuration (using Styles) + // Widget _buildTableCalendar() { + // return TableCalendar( + // locale: 'en_US', + // events: _visibleEvents, + // holidays: _visibleHolidays, + // initialCalendarFormat: CalendarFormat.week, + // formatAnimation: FormatAnimation.slide, + // startingDayOfWeek: StartingDayOfWeek.monday, + // availableGestures: AvailableGestures.all, + // availableCalendarFormats: const { + // CalendarFormat.month: 'Month', + // CalendarFormat.twoWeeks: '2 weeks', + // CalendarFormat.week: 'Week', + // }, + // calendarStyle: CalendarStyle( + // selectedColor: Colors.deepOrange[400], + // todayColor: Colors.deepOrange[200], + // markersColor: Colors.brown[700], + // ), + // headerStyle: HeaderStyle( + // formatButtonTextStyle: TextStyle().copyWith(color: Colors.white, fontSize: 15.0), + // formatButtonDecoration: BoxDecoration( + // color: Colors.deepOrange[400], + // borderRadius: BorderRadius.circular(16.0), + // ), + // ), + // onDaySelected: _onDaySelected, + // onVisibleDaysChanged: _onVisibleDaysChanged, + // ); + // } + // More advanced TableCalendar configuration (using Builders & Styles) Widget _buildTableCalendarWithBuilders() { return TableCalendar( diff --git a/lib/src/calendar.dart b/lib/src/calendar.dart new file mode 100644 index 00000000..7875ca0b --- /dev/null +++ b/lib/src/calendar.dart @@ -0,0 +1,637 @@ +// Copyright (c) 2019 Aleksander Woźniak +// Licensed under Apache License v2.0 + +part of table_calendar; + +/// Callback exposing currently selected day. +typedef void OnDaySelected(DateTime day, List events); + +/// Callback exposing currently visible days (first and last of them), as well as current `CalendarFormat`. +typedef void OnVisibleDaysChanged(DateTime first, DateTime last, CalendarFormat format); + +/// Builder signature for any text that can be localized and formatted with `DateFormat`. +typedef String TextBuilder(DateTime date, dynamic locale); + +/// Format to display the `TableCalendar` with. +enum CalendarFormat { month, twoWeeks, week } + +/// Available animations to update the `CalendarFormat` with. +enum FormatAnimation { slide, scale } + +/// Available day of week formats. `TableCalendar` will start the week with chosen day. +/// * `StartingDayOfWeek.monday`: Monday - Sunday +/// * `StartingDayOfWeek.sunday`: Sunday - Saturday +enum StartingDayOfWeek { monday, sunday } + +/// Gestures available to interal `TableCalendar`'s logic. +enum AvailableGestures { none, verticalSwipe, horizontalSwipe, all } + +/// Highly customizable, feature-packed Flutter Calendar with gestures, animations and multiple formats. +class TableCalendar extends StatefulWidget { + /// Locale to format `TableCalendar` dates with, for example: `'en_US'`. + /// + /// If nothing is provided, a default locale will be used. + final dynamic locale; + + /// Contains a `List` of objects (eg. events) assigned to particular `DateTime`s. + /// Each `DateTime` inside this `Map` should get its own `List` of above mentioned objects. + final Map events; + + /// `List`s of holidays associated to particular `DateTime`s. + /// This property allows you to provide custom holiday rules. + final Map holidays; + + /// Called whenever any day gets tapped. + final OnDaySelected onDaySelected; + + /// Called whenever any unavailable day gets tapped. + /// Replaces `onDaySelected` for those days. + final VoidCallback onUnavailableDaySelected; + + /// Called whenever the range of visible days changes. + final OnVisibleDaysChanged onVisibleDaysChanged; + + /// Initially selected DateTime. Usually it will be `DateTime.now()`. + /// This property can be used to programmatically select a new date. + /// + /// If `TableCalendar` Widget gets rebuilt with a different `selectedDay` than previously, + /// `onDaySelected` callback will run. + /// + /// To animate programmatic selection, use `animateProgSelectedDay` property. + final DateTime selectedDay; + + /// The first day of `TableCalendar`. + /// Days before it will use `unavailableStyle` and run `onUnavailableDaySelected` callback. + final DateTime startDay; + + /// The last day of `TableCalendar`. + /// Days after it will use `unavailableStyle` and run `onUnavailableDaySelected` callback. + final DateTime endDay; + + /// `CalendarFormat` which will be displayed first. + final CalendarFormat initialCalendarFormat; + + /// `CalendarFormat` which overrides any internal logic. + /// Use if you need total programmatic control over `TableCalendar`'s format. + /// + /// Makes `initialCalendarFormat` and `availableCalendarFormats` obsolete. + final CalendarFormat forcedCalendarFormat; + + /// `Map` of `CalendarFormat`s and `String` names associated with them. + /// Those `CalendarFormat`s will be used by internal logic to manage displayed format. + /// + /// To ensure proper vertical Swipe behavior, `CalendarFormat`s should be in descending order (eg. from biggest to smallest). + /// + /// For example: + /// ```dart + /// availableCalendarFormats: const { + /// CalendarFormat.month: 'Month', + /// CalendarFormat.week: 'Week', + /// } + /// ``` + final Map availableCalendarFormats; + + /// Used to show/hide Header. + final bool headerVisible; + + /// Used for setting the height of `TableCalendar`'s rows. + final double rowHeight; + + /// Used to enable animations for programmatically set `selectedDay`. + /// Most of the time it should be `false`. + final bool animateProgSelectedDay; + + /// Animation to run when `CalendarFormat` gets changed. + final FormatAnimation formatAnimation; + + /// `TableCalendar` will start weeks with provided day. + /// Use `StartingDayOfWeek.monday` for Monday - Sunday week format. + /// Use `StartingDayOfWeek.sunday` for Sunday - Saturday week format. + final StartingDayOfWeek startingDayOfWeek; + + /// `HitTestBehavior` for every day cell inside `TableCalendar`. + final HitTestBehavior dayHitTestBehavior; + + /// Specify Gestures available to `TableCalendar`. + /// If `AvailableGestures.none` is used, the Calendar will only be interactive via buttons. + final AvailableGestures availableGestures; + + /// Configuration for vertical Swipe detector. + final SimpleSwipeConfig simpleSwipeConfig; + + /// Style for `TableCalendar`'s content. + final CalendarStyle calendarStyle; + + /// Style for DaysOfWeek displayed between `TableCalendar`'s Header and content. + final DaysOfWeekStyle daysOfWeekStyle; + + /// Style for `TableCalendar`'s Header. + final HeaderStyle headerStyle; + + /// Set of Builders for `TableCalendar` to work with. + final CalendarBuilders builders; + + final CalendarController controller; + + TableCalendar({ + Key key, + this.locale, + this.controller, + this.events = const {}, + this.holidays = const {}, + this.onDaySelected, + this.onUnavailableDaySelected, + this.onVisibleDaysChanged, + this.selectedDay, + this.startDay, + this.endDay, + this.initialCalendarFormat = CalendarFormat.month, + this.forcedCalendarFormat, + this.availableCalendarFormats = const { + CalendarFormat.month: 'Month', + CalendarFormat.twoWeeks: '2 weeks', + CalendarFormat.week: 'Week', + }, + this.headerVisible = true, + this.rowHeight, + this.animateProgSelectedDay = false, + this.formatAnimation = FormatAnimation.slide, + this.startingDayOfWeek = StartingDayOfWeek.sunday, + this.dayHitTestBehavior = HitTestBehavior.deferToChild, + this.availableGestures = AvailableGestures.all, + this.simpleSwipeConfig = const SimpleSwipeConfig( + verticalThreshold: 25.0, + swipeDetectionBehavior: SwipeDetectionBehavior.continuousDistinct, + ), + this.calendarStyle = const CalendarStyle(), + this.daysOfWeekStyle = const DaysOfWeekStyle(), + this.headerStyle = const HeaderStyle(), + this.builders = const CalendarBuilders(), + }) : assert(availableCalendarFormats.keys.contains(initialCalendarFormat)), + assert(availableCalendarFormats.length <= CalendarFormat.values.length), + super(key: key); + + @override + _TableCalendarState createState() => _TableCalendarState(); +} + +class _TableCalendarState extends State with SingleTickerProviderStateMixin { + // CalendarController widget.controller; + + @override + void initState() { + super.initState(); + // _calendarLogic = CalendarController( + // widget.availableCalendarFormats, + // widget.startingDayOfWeek, + // widget.headerStyle.formatButtonShowsNext, + // initialFormat: widget.initialCalendarFormat, + // initialDay: widget.selectedDay, + // onVisibleDaysChanged: widget.onVisibleDaysChanged, + // includeInvisibleDays: widget.calendarStyle.outsideDaysVisible, + // ); + + widget.controller._init( + _demoSelectedDayCallback, + widget.availableCalendarFormats, + widget.startingDayOfWeek, + widget.headerStyle.formatButtonShowsNext, + initialFormat: widget.initialCalendarFormat, + initialDay: widget.selectedDay, + onVisibleDaysChanged: widget.onVisibleDaysChanged, + includeInvisibleDays: widget.calendarStyle.outsideDaysVisible, + ); + } + + @override + void dispose() { + // _calendarLogic.dispose(); + super.dispose(); + } + + // void setSelectedDay(DateTime date) { + // _calendarLogic.setSelectedDay(date); + + // // TODO: shouldRunCallback ! + + // if (widget.onDaySelected != null) { + // final key = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); + // widget.onDaySelected(date, widget.events[key] ?? []); + // } + // } + + // void setCalendarFormat(CalendarFormat format) { + // _calendarLogic.setCalendarFormat(format); + + // // TODO: shouldRunCallback ? + // } + + void _demoSelectedDayCallback(DateTime day) { + final key = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, day), orElse: () => null); + widget.onDaySelected(day, widget.events[key] ?? []); + } + + void _selectPrevious() { + setState(() { + widget.controller._selectPrevious(); + }); + } + + void _selectNext() { + setState(() { + widget.controller._selectNext(); + }); + } + + void _selectDate(DateTime date) { + setState(() { + widget.controller.setSelectedDay(date, isProgrammatic: false); + + if (widget.onDaySelected != null) { + final key = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); + widget.onDaySelected(date, widget.events[key] ?? []); + } + }); + } + + void _toggleCalendarFormat() { + setState(() { + widget.controller.toggleCalendarFormat(); + }); + } + + void _onHorizontalSwipe(DismissDirection direction) { + if (direction == DismissDirection.startToEnd) { + // Swipe right + _selectPrevious(); + } else { + // Swipe left + _selectNext(); + } + } + + void _onUnavailableDaySelected() { + if (widget.onUnavailableDaySelected != null) { + widget.onUnavailableDaySelected(); + } + } + + bool _isDayUnavailable(DateTime day) { + return (widget.startDay != null && day.isBefore(widget.startDay)) || + (widget.endDay != null && day.isAfter(widget.endDay)); + } + + @override + Widget build(BuildContext context) { + final children = []; + + if (widget.headerVisible) { + children.addAll([ + const SizedBox(height: 6.0), + _buildHeader(), + ]); + } + + children.addAll([ + const SizedBox(height: 10.0), + _buildCalendarContent(), + const SizedBox(height: 4.0), + ]); + + return ClipRect( + child: Column( + mainAxisSize: MainAxisSize.min, + children: children, + ), + ); + } + + Widget _buildHeader() { + final children = [ + _CustomIconButton( + icon: widget.headerStyle.leftChevronIcon, + onTap: _selectPrevious, + margin: widget.headerStyle.leftChevronMargin, + padding: widget.headerStyle.leftChevronPadding, + ), + Expanded( + child: Text( + widget.headerStyle.titleTextBuilder != null + ? widget.headerStyle.titleTextBuilder(widget.controller.focusedDay, widget.locale) + : DateFormat.yMMMM(widget.locale).format(widget.controller.focusedDay), + style: widget.headerStyle.titleTextStyle, + textAlign: widget.headerStyle.centerHeaderTitle ? TextAlign.center : TextAlign.start, + ), + ), + _CustomIconButton( + icon: widget.headerStyle.rightChevronIcon, + onTap: _selectNext, + margin: widget.headerStyle.rightChevronMargin, + padding: widget.headerStyle.rightChevronPadding, + ), + ]; + + if (widget.headerStyle.formatButtonVisible && + widget.availableCalendarFormats.length > 1 && + widget.forcedCalendarFormat == null) { + children.insert(2, const SizedBox(width: 8.0)); + children.insert(3, _buildFormatButton()); + } + + return Row( + mainAxisSize: MainAxisSize.max, + children: children, + ); + } + + Widget _buildFormatButton() { + return GestureDetector( + onTap: _toggleCalendarFormat, + child: Container( + decoration: widget.headerStyle.formatButtonDecoration, + padding: widget.headerStyle.formatButtonPadding, + child: Text( + widget.controller.formatButtonText, + style: widget.headerStyle.formatButtonTextStyle, + ), + ), + ); + } + + Widget _buildCalendarContent() { + if (widget.formatAnimation == FormatAnimation.slide) { + return AnimatedSize( + duration: Duration(milliseconds: widget.controller.calendarFormat == CalendarFormat.month ? 330 : 220), + curve: Curves.fastOutSlowIn, + alignment: Alignment(0, -1), + vsync: this, + child: _buildWrapper(), + ); + } else { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + transitionBuilder: (child, animation) { + return SizeTransition( + sizeFactor: animation, + child: ScaleTransition( + scale: animation, + child: child, + ), + ); + }, + child: _buildWrapper( + key: ValueKey(widget.controller.calendarFormat), + ), + ); + } + } + + Widget _buildWrapper({Key key}) { + Widget wrappedChild = _buildTable(); + + switch (widget.availableGestures) { + case AvailableGestures.all: + wrappedChild = _buildVerticalSwipeWrapper( + child: _buildHorizontalSwipeWrapper( + child: wrappedChild, + ), + ); + break; + case AvailableGestures.verticalSwipe: + wrappedChild = _buildVerticalSwipeWrapper( + child: wrappedChild, + ); + break; + case AvailableGestures.horizontalSwipe: + wrappedChild = _buildHorizontalSwipeWrapper( + child: wrappedChild, + ); + break; + case AvailableGestures.none: + break; + } + + return Container( + key: key, + margin: const EdgeInsets.symmetric(horizontal: 8.0), + child: wrappedChild, + ); + } + + Widget _buildVerticalSwipeWrapper({Widget child}) { + return SimpleGestureDetector( + child: child, + onVerticalSwipe: (direction) { + setState(() { + widget.controller.swipeCalendarFormat(direction == SwipeDirection.up); + }); + }, + swipeConfig: widget.simpleSwipeConfig, + ); + } + + Widget _buildHorizontalSwipeWrapper({Widget child}) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + switchInCurve: Curves.decelerate, + transitionBuilder: (child, animation) { + return SlideTransition( + position: Tween(begin: Offset(widget.controller._dx, 0), end: Offset(0, 0)).animate(animation), + child: child, + ); + }, + layoutBuilder: (currentChild, _) => currentChild, + child: Dismissible( + key: ValueKey(widget.controller._pageId), + resizeDuration: null, + onDismissed: _onHorizontalSwipe, + direction: DismissDirection.horizontal, + child: child, + ), + ); + } + + Widget _buildTable() { + final daysInWeek = 7; + final children = [ + _buildDaysOfWeek(), + ]; + + int x = 0; + while (x < widget.controller.visibleDays.length) { + children.add(_buildTableRow(widget.controller.visibleDays.skip(x).take(daysInWeek).toList())); + x += daysInWeek; + } + + return Table( + // Makes this Table fill its parent horizontally + defaultColumnWidth: FractionColumnWidth(1.0 / daysInWeek), + children: children, + ); + } + + TableRow _buildDaysOfWeek() { + return TableRow( + children: widget.controller.visibleDays.take(7).map((date) { + return Center( + child: Text( + widget.daysOfWeekStyle.dowTextBuilder != null + ? widget.daysOfWeekStyle.dowTextBuilder(date, widget.locale) + : DateFormat.E(widget.locale).format(date), + style: widget.controller._isWeekend(date) + ? widget.daysOfWeekStyle.weekendStyle + : widget.daysOfWeekStyle.weekdayStyle, + ), + ); + }).toList(), + ); + } + + TableRow _buildTableRow(List days) { + return TableRow(children: days.map((date) => _buildTableCell(date)).toList()); + } + + // TableCell will have equal width and height + Widget _buildTableCell(DateTime date) { + return LayoutBuilder( + builder: (context, constraints) => ConstrainedBox( + constraints: BoxConstraints( + maxHeight: widget.rowHeight ?? constraints.maxWidth, + minHeight: widget.rowHeight ?? constraints.maxWidth, + ), + child: _buildCell(date), + ), + ); + } + + Widget _buildCell(DateTime date) { + if (!widget.calendarStyle.outsideDaysVisible && + widget.controller._isExtraDay(date) && + widget.controller.calendarFormat == CalendarFormat.month) { + return Container(); + } + + Widget content = _buildCellContent(date); + + final eventKey = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); + final holidayKey = widget.holidays.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); + final key = eventKey ?? holidayKey; + + if (key != null) { + final children = [content]; + final events = eventKey != null ? widget.events[eventKey].take(widget.calendarStyle.markersMaxAmount) : []; + final holidays = holidayKey != null ? widget.holidays[holidayKey] : []; + + if (!_isDayUnavailable(date)) { + if (widget.builders.markersBuilder != null) { + children.addAll( + widget.builders.markersBuilder( + context, + key, + events.toList(), + holidays, + ), + ); + } else { + children.add( + Positioned( + top: widget.calendarStyle.markersPositionTop, + bottom: widget.calendarStyle.markersPositionBottom, + left: widget.calendarStyle.markersPositionLeft, + right: widget.calendarStyle.markersPositionRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: events.map((event) => _buildMarker(eventKey, event)).toList(), + ), + ), + ); + } + } + + if (children.length > 1) { + content = Stack( + alignment: widget.calendarStyle.markersAlignment, + children: children, + overflow: widget.calendarStyle.canEventMarkersOverflow ? Overflow.visible : Overflow.clip, + ); + } + } + + return GestureDetector( + behavior: widget.dayHitTestBehavior, + onTap: () => _isDayUnavailable(date) ? _onUnavailableDaySelected() : _selectDate(date), + child: content, + ); + } + + Widget _buildCellContent(DateTime date) { + final eventKey = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); + final holidayKey = widget.holidays.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); + + final tIsUnavailable = _isDayUnavailable(date); + final tIsSelected = widget.controller.isSelected(date); + final tIsToday = widget.controller.isToday(date); + final tIsOutside = widget.controller._isExtraDay(date); + final tIsHoliday = widget.holidays.containsKey(holidayKey); + final tIsWeekend = widget.controller._isWeekend(date); + + final isUnavailable = widget.builders.unavailableDayBuilder != null && tIsUnavailable; + final isSelected = widget.builders.selectedDayBuilder != null && tIsSelected; + final isToday = widget.builders.todayDayBuilder != null && tIsToday; + final isOutsideHoliday = widget.builders.outsideHolidayDayBuilder != null && tIsOutside && tIsHoliday; + final isHoliday = widget.builders.holidayDayBuilder != null && !tIsOutside && tIsHoliday; + final isOutsideWeekend = + widget.builders.outsideWeekendDayBuilder != null && tIsOutside && tIsWeekend && !tIsHoliday; + final isOutside = widget.builders.outsideDayBuilder != null && tIsOutside && !tIsWeekend && !tIsHoliday; + final isWeekend = widget.builders.weekendDayBuilder != null && !tIsOutside && tIsWeekend && !tIsHoliday; + + if (isUnavailable) { + return widget.builders.unavailableDayBuilder(context, date, widget.events[eventKey]); + } else if (isSelected && widget.calendarStyle.renderSelectedFirst) { + return widget.builders.selectedDayBuilder(context, date, widget.events[eventKey]); + } else if (isToday) { + return widget.builders.todayDayBuilder(context, date, widget.events[eventKey]); + } else if (isSelected) { + return widget.builders.selectedDayBuilder(context, date, widget.events[eventKey]); + } else if (isOutsideHoliday) { + return widget.builders.outsideHolidayDayBuilder(context, date, widget.events[eventKey]); + } else if (isHoliday) { + return widget.builders.holidayDayBuilder(context, date, widget.events[eventKey]); + } else if (isOutsideWeekend) { + return widget.builders.outsideWeekendDayBuilder(context, date, widget.events[eventKey]); + } else if (isOutside) { + return widget.builders.outsideDayBuilder(context, date, widget.events[eventKey]); + } else if (isWeekend) { + return widget.builders.weekendDayBuilder(context, date, widget.events[eventKey]); + } else if (widget.builders.dayBuilder != null) { + return widget.builders.dayBuilder(context, date, widget.events[eventKey]); + } else { + return _CellWidget( + text: '${date.day}', + isUnavailable: tIsUnavailable, + isSelected: tIsSelected, + isToday: tIsToday, + isWeekend: tIsWeekend, + isOutsideMonth: tIsOutside, + isHoliday: tIsHoliday, + calendarStyle: widget.calendarStyle, + ); + } + } + + Widget _buildMarker(DateTime date, dynamic event) { + if (widget.builders.singleMarkerBuilder != null) { + return widget.builders.singleMarkerBuilder(context, date, event); + } else { + return Container( + width: 8.0, + height: 8.0, + margin: const EdgeInsets.symmetric(horizontal: 0.3), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.calendarStyle.markersColor, + ), + ); + } + } +} diff --git a/lib/src/logic/calendar_logic.dart b/lib/src/calendar_controller.dart similarity index 71% rename from lib/src/logic/calendar_logic.dart rename to lib/src/calendar_controller.dart index 3e0fc3f2..3170477a 100644 --- a/lib/src/logic/calendar_logic.dart +++ b/lib/src/calendar_controller.dart @@ -1,20 +1,29 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:date_utils/date_utils.dart'; -import 'package:flutter/foundation.dart'; - -import '../../table_calendar.dart'; +part of table_calendar; const double _dxMax = 1.2; const double _dxMin = -1.2; -class CalendarLogic { +typedef void _SelectedDayCallback(DateTime day); + +class CalendarController { + // DateTime get focusedDay => _focusedDay; + // DateTime get selectedDay => _selectedDay; + + // int get pageId => _pageId; + // double get dx => _dx; + // CalendarFormat get calendarFormat => _calendarFormat.value; + // List get visibleDays => _visibleDays.value; + // String get formatButtonText => _useNextCalendarFormat + // ? _availableCalendarFormats[_nextFormat()] + // : _availableCalendarFormats[_calendarFormat.value]; + DateTime get focusedDay => _focusedDay; DateTime get selectedDay => _selectedDay; - int get pageId => _pageId; - double get dx => _dx; CalendarFormat get calendarFormat => _calendarFormat.value; + List get visibleDays => _visibleDays.value; String get formatButtonText => _useNextCalendarFormat ? _availableCalendarFormats[_nextFormat()] @@ -32,16 +41,72 @@ class CalendarLogic { double _dx; bool _useNextCalendarFormat; - CalendarLogic( - this._availableCalendarFormats, - this._startingDayOfWeek, - this._useNextCalendarFormat, { + // CalendarController( + // this._availableCalendarFormats, + // this._startingDayOfWeek, + // this._useNextCalendarFormat, { + // DateTime initialDay, + // CalendarFormat initialFormat, + // OnVisibleDaysChanged onVisibleDaysChanged, + // bool includeInvisibleDays = false, + // }) : _pageId = 0, + // _dx = 0 { + // final now = DateTime.now(); + // _focusedDay = initialDay ?? DateTime(now.year, now.month, now.day); + // _selectedDay = _focusedDay; + // _calendarFormat = ValueNotifier(initialFormat); + // _visibleDays = ValueNotifier(_getVisibleDays()); + // _previousFirstDay = _visibleDays.value.first; + // _previousLastDay = _visibleDays.value.last; + + // _calendarFormat.addListener(() { + // _visibleDays.value = _getVisibleDays(); + // }); + + // if (onVisibleDaysChanged != null) { + // _visibleDays.addListener(() { + // if (!Utils.isSameDay(_visibleDays.value.first, _previousFirstDay) || + // !Utils.isSameDay(_visibleDays.value.last, _previousLastDay)) { + // _previousFirstDay = _visibleDays.value.first; + // _previousLastDay = _visibleDays.value.last; + // onVisibleDaysChanged( + // _getFirstDay(includeInvisible: includeInvisibleDays), + // _getLastDay(includeInvisible: includeInvisibleDays), + // _calendarFormat.value, + // ); + // } + // }); + // } + // } + + _SelectedDayCallback _selectedDayCallback; + + CalendarController() {} + + void dispose() { + _calendarFormat.dispose(); + _visibleDays.dispose(); + } + + void _init( + _SelectedDayCallback selectedDayCallback, + Map availableCalendarFormats, + StartingDayOfWeek startingDayOfWeek, + bool useNextCalendarFormat, { DateTime initialDay, CalendarFormat initialFormat, OnVisibleDaysChanged onVisibleDaysChanged, bool includeInvisibleDays = false, - }) : _pageId = 0, - _dx = 0 { + }) { + _availableCalendarFormats = availableCalendarFormats; + _startingDayOfWeek = startingDayOfWeek; + _useNextCalendarFormat = useNextCalendarFormat; + + _selectedDayCallback = selectedDayCallback; + + _pageId = 0; + _dx = 0; + final now = DateTime.now(); _focusedDay = initialDay ?? DateTime(now.year, now.month, now.day); _selectedDay = _focusedDay; @@ -70,11 +135,6 @@ class CalendarLogic { } } - void dispose() { - _calendarFormat.dispose(); - _visibleDays.dispose(); - } - CalendarFormat _nextFormat() { final formats = _availableCalendarFormats.keys.toList(); int id = formats.indexOf(_calendarFormat.value); @@ -101,11 +161,16 @@ class CalendarLogic { _calendarFormat.value = formats[id]; } - bool setSelectedDay(DateTime value, {bool isAnimated = true, bool isProgrammatic = false}) { - if (Utils.isSameDay(value, _selectedDay)) { - return false; - } + void setCalendarFormat(CalendarFormat value) { + _calendarFormat.value = value; + } + void setSelectedDay( + DateTime value, { + bool isAnimated = true, + bool isProgrammatic = true, + bool runCallback = false, + }) { if (isAnimated) { if (value.isBefore(_getFirstDay(includeInvisible: false))) { _decrementPage(); @@ -116,15 +181,26 @@ class CalendarLogic { _selectedDay = value; _focusedDay = value; + _updateVisibleDays(isProgrammatic); + if (isProgrammatic && runCallback && _selectedDayCallback != null) { + _selectedDayCallback(value); + } + } + + /// Use to set displayed month/year without changing the SelectedDay + void setFocusedDay(DateTime value) { + _focusedDay = value; + _updateVisibleDays(true); + } + + void _updateVisibleDays(bool isProgrammatic) { if (calendarFormat != CalendarFormat.twoWeeks || isProgrammatic) { _visibleDays.value = _getVisibleDays(); } - - return true; } - void selectPrevious() { + void _selectPrevious() { if (calendarFormat == CalendarFormat.month) { _selectPreviousMonth(); } else if (calendarFormat == CalendarFormat.twoWeeks) { @@ -137,7 +213,7 @@ class CalendarLogic { _decrementPage(); } - void selectNext() { + void _selectNext() { if (calendarFormat == CalendarFormat.month) { _selectNextMonth(); } else if (calendarFormat == CalendarFormat.twoWeeks) { @@ -248,7 +324,10 @@ class CalendarLogic { final last = _lastDayOfWeek(week); final days = Utils.daysInRange(first, last); - return days.map((day) => DateTime(day.year, day.month, day.day)).toList(); + // TODO: wtf - why map? + // TODO: .utc , hour: 12 ? + // return days.map((day) => DateTime(day.year, day.month, day.day)).toList(); + return days.toList(); } DateTime _firstDayOfWeek(DateTime day) { @@ -283,11 +362,11 @@ class CalendarLogic { return Utils.isSameDay(day, DateTime.now()); } - bool isWeekend(DateTime day) { + bool _isWeekend(DateTime day) { return day.weekday == DateTime.saturday || day.weekday == DateTime.sunday; } - bool isExtraDay(DateTime day) { + bool _isExtraDay(DateTime day) { return _isExtraDayBefore(day) || _isExtraDayAfter(day); } diff --git a/lib/src/customization/calendar_builders.dart b/lib/src/customization/calendar_builders.dart index 5f3df0c4..cd5726a2 100644 --- a/lib/src/customization/calendar_builders.dart +++ b/lib/src/customization/calendar_builders.dart @@ -1,7 +1,7 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:flutter/material.dart'; +part of table_calendar; /// Main Builder signature for `TableCalendar`. Contains `date` and list of all `events` associated with that `date`. /// Note that most of the time, `events` param will be ommited, however it is there if needed. diff --git a/lib/src/customization/calendar_style.dart b/lib/src/customization/calendar_style.dart index 9a918372..e3647019 100644 --- a/lib/src/customization/calendar_style.dart +++ b/lib/src/customization/calendar_style.dart @@ -1,7 +1,7 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:flutter/material.dart'; +part of table_calendar; /// Class containing styling for `TableCalendar`'s content. class CalendarStyle { diff --git a/lib/src/customization/customization.dart b/lib/src/customization/customization.dart deleted file mode 100644 index a86a6ec4..00000000 --- a/lib/src/customization/customization.dart +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) 2019 Aleksander Woźniak -// Licensed under Apache License v2.0 - -export 'calendar_builders.dart'; -export 'calendar_style.dart'; -export 'days_of_week_style.dart'; -export 'header_style.dart'; diff --git a/lib/src/customization/days_of_week_style.dart b/lib/src/customization/days_of_week_style.dart index bfd6cb66..93419ee3 100644 --- a/lib/src/customization/days_of_week_style.dart +++ b/lib/src/customization/days_of_week_style.dart @@ -1,8 +1,7 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:flutter/material.dart'; -import '../../table_calendar.dart'; +part of table_calendar; /// Class containing styling for `TableCalendar`'s days of week panel. class DaysOfWeekStyle { diff --git a/lib/src/customization/header_style.dart b/lib/src/customization/header_style.dart index b99bbb7f..db888314 100644 --- a/lib/src/customization/header_style.dart +++ b/lib/src/customization/header_style.dart @@ -1,9 +1,7 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:flutter/material.dart'; - -import '../../table_calendar.dart'; +part of table_calendar; /// Class containing styling and configuration of `TableCalendar`'s header. class HeaderStyle { diff --git a/lib/src/widgets/cell_widget.dart b/lib/src/widgets/cell_widget.dart index 050b33f8..d8f80fbc 100644 --- a/lib/src/widgets/cell_widget.dart +++ b/lib/src/widgets/cell_widget.dart @@ -1,11 +1,9 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:flutter/material.dart'; +part of table_calendar; -import '../customization/calendar_style.dart'; - -class CellWidget extends StatelessWidget { +class _CellWidget extends StatelessWidget { final String text; final bool isUnavailable; final bool isSelected; @@ -15,7 +13,7 @@ class CellWidget extends StatelessWidget { final bool isHoliday; final CalendarStyle calendarStyle; - const CellWidget({ + const _CellWidget({ Key key, @required this.text, this.isUnavailable = false, diff --git a/lib/src/widgets/custom_icon_button.dart b/lib/src/widgets/custom_icon_button.dart index 10da3e29..06be1983 100644 --- a/lib/src/widgets/custom_icon_button.dart +++ b/lib/src/widgets/custom_icon_button.dart @@ -1,15 +1,15 @@ // Copyright (c) 2019 Aleksander Woźniak // Licensed under Apache License v2.0 -import 'package:flutter/material.dart'; +part of table_calendar; -class CustomIconButton extends StatelessWidget { +class _CustomIconButton extends StatelessWidget { final Icon icon; final VoidCallback onTap; final EdgeInsets margin; final EdgeInsets padding; - const CustomIconButton({ + const _CustomIconButton({ Key key, @required this.icon, @required this.onTap, diff --git a/lib/src/widgets/widgets.dart b/lib/src/widgets/widgets.dart deleted file mode 100644 index be97f716..00000000 --- a/lib/src/widgets/widgets.dart +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2019 Aleksander Woźniak -// Licensed under Apache License v2.0 - -export 'cell_widget.dart'; -export 'custom_icon_button.dart'; diff --git a/lib/table_calendar.dart b/lib/table_calendar.dart index 3addfedd..b46b5d35 100644 --- a/lib/table_calendar.dart +++ b/lib/table_calendar.dart @@ -5,637 +5,14 @@ library table_calendar; import 'package:date_utils/date_utils.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:intl/intl.dart'; import 'package:simple_gesture_detector/simple_gesture_detector.dart'; -import 'src/customization/customization.dart'; -import 'src/logic/calendar_logic.dart'; -import 'src/widgets/widgets.dart'; - -export 'src/customization/customization.dart'; - -/// Callback exposing currently selected day. -typedef void OnDaySelected(DateTime day, List events); - -/// Callback exposing currently visible days (first and last of them), as well as current `CalendarFormat`. -typedef void OnVisibleDaysChanged(DateTime first, DateTime last, CalendarFormat format); - -/// Builder signature for any text that can be localized and formatted with `DateFormat`. -typedef String TextBuilder(DateTime date, dynamic locale); - -/// Format to display the `TableCalendar` with. -enum CalendarFormat { month, twoWeeks, week } - -/// Available animations to update the `CalendarFormat` with. -enum FormatAnimation { slide, scale } - -/// Available day of week formats. `TableCalendar` will start the week with chosen day. -/// * `StartingDayOfWeek.monday`: Monday - Sunday -/// * `StartingDayOfWeek.sunday`: Sunday - Saturday -enum StartingDayOfWeek { monday, sunday } - -/// Gestures available to interal `TableCalendar`'s logic. -enum AvailableGestures { none, verticalSwipe, horizontalSwipe, all } - -/// Highly customizable, feature-packed Flutter Calendar with gestures, animations and multiple formats. -class TableCalendar extends StatefulWidget { - /// Locale to format `TableCalendar` dates with, for example: `'en_US'`. - /// - /// If nothing is provided, a default locale will be used. - final dynamic locale; - - /// Contains a `List` of objects (eg. events) assigned to particular `DateTime`s. - /// Each `DateTime` inside this `Map` should get its own `List` of above mentioned objects. - final Map events; - - /// `List`s of holidays associated to particular `DateTime`s. - /// This property allows you to provide custom holiday rules. - final Map holidays; - - /// Called whenever any day gets tapped. - final OnDaySelected onDaySelected; - - /// Called whenever any unavailable day gets tapped. - /// Replaces `onDaySelected` for those days. - final VoidCallback onUnavailableDaySelected; - - /// Called whenever the range of visible days changes. - final OnVisibleDaysChanged onVisibleDaysChanged; - - /// Initially selected DateTime. Usually it will be `DateTime.now()`. - /// This property can be used to programmatically select a new date. - /// - /// If `TableCalendar` Widget gets rebuilt with a different `selectedDay` than previously, - /// `onDaySelected` callback will run. - /// - /// To animate programmatic selection, use `animateProgSelectedDay` property. - final DateTime selectedDay; - - /// The first day of `TableCalendar`. - /// Days before it will use `unavailableStyle` and run `onUnavailableDaySelected` callback. - final DateTime startDay; - - /// The last day of `TableCalendar`. - /// Days after it will use `unavailableStyle` and run `onUnavailableDaySelected` callback. - final DateTime endDay; - - /// `CalendarFormat` which will be displayed first. - final CalendarFormat initialCalendarFormat; - - /// `CalendarFormat` which overrides any internal logic. - /// Use if you need total programmatic control over `TableCalendar`'s format. - /// - /// Makes `initialCalendarFormat` and `availableCalendarFormats` obsolete. - final CalendarFormat forcedCalendarFormat; - - /// `Map` of `CalendarFormat`s and `String` names associated with them. - /// Those `CalendarFormat`s will be used by internal logic to manage displayed format. - /// - /// To ensure proper vertical Swipe behavior, `CalendarFormat`s should be in descending order (eg. from biggest to smallest). - /// - /// For example: - /// ```dart - /// availableCalendarFormats: const { - /// CalendarFormat.month: 'Month', - /// CalendarFormat.week: 'Week', - /// } - /// ``` - final Map availableCalendarFormats; - - /// Used to show/hide Header. - final bool headerVisible; - - /// Used for setting the height of `TableCalendar`'s rows. - final double rowHeight; - - /// Used to enable animations for programmatically set `selectedDay`. - /// Most of the time it should be `false`. - final bool animateProgSelectedDay; - - /// Animation to run when `CalendarFormat` gets changed. - final FormatAnimation formatAnimation; - - /// `TableCalendar` will start weeks with provided day. - /// Use `StartingDayOfWeek.monday` for Monday - Sunday week format. - /// Use `StartingDayOfWeek.sunday` for Sunday - Saturday week format. - final StartingDayOfWeek startingDayOfWeek; - - /// `HitTestBehavior` for every day cell inside `TableCalendar`. - final HitTestBehavior dayHitTestBehavior; - - /// Specify Gestures available to `TableCalendar`. - /// If `AvailableGestures.none` is used, the Calendar will only be interactive via buttons. - final AvailableGestures availableGestures; - - /// Configuration for vertical Swipe detector. - final SimpleSwipeConfig simpleSwipeConfig; - - /// Style for `TableCalendar`'s content. - final CalendarStyle calendarStyle; - - /// Style for DaysOfWeek displayed between `TableCalendar`'s Header and content. - final DaysOfWeekStyle daysOfWeekStyle; - - /// Style for `TableCalendar`'s Header. - final HeaderStyle headerStyle; - - /// Set of Builders for `TableCalendar` to work with. - final CalendarBuilders builders; - - TableCalendar({ - Key key, - this.locale, - this.events = const {}, - this.holidays = const {}, - this.onDaySelected, - this.onUnavailableDaySelected, - this.onVisibleDaysChanged, - this.selectedDay, - this.startDay, - this.endDay, - this.initialCalendarFormat = CalendarFormat.month, - this.forcedCalendarFormat, - this.availableCalendarFormats = const { - CalendarFormat.month: 'Month', - CalendarFormat.twoWeeks: '2 weeks', - CalendarFormat.week: 'Week', - }, - this.headerVisible = true, - this.rowHeight, - this.animateProgSelectedDay = false, - this.formatAnimation = FormatAnimation.slide, - this.startingDayOfWeek = StartingDayOfWeek.sunday, - this.dayHitTestBehavior = HitTestBehavior.deferToChild, - this.availableGestures = AvailableGestures.all, - this.simpleSwipeConfig = const SimpleSwipeConfig( - verticalThreshold: 25.0, - swipeDetectionBehavior: SwipeDetectionBehavior.continuousDistinct, - ), - this.calendarStyle = const CalendarStyle(), - this.daysOfWeekStyle = const DaysOfWeekStyle(), - this.headerStyle = const HeaderStyle(), - this.builders = const CalendarBuilders(), - }) : assert(availableCalendarFormats.keys.contains(initialCalendarFormat)), - assert(availableCalendarFormats.length <= CalendarFormat.values.length), - super(key: key); - - @override - _TableCalendarState createState() { - return new _TableCalendarState(); - } -} - -class _TableCalendarState extends State with SingleTickerProviderStateMixin { - CalendarLogic _calendarLogic; - - @override - void initState() { - super.initState(); - _calendarLogic = CalendarLogic( - widget.availableCalendarFormats, - widget.startingDayOfWeek, - widget.headerStyle.formatButtonShowsNext, - initialFormat: widget.initialCalendarFormat, - initialDay: widget.selectedDay, - onVisibleDaysChanged: widget.onVisibleDaysChanged, - includeInvisibleDays: widget.calendarStyle.outsideDaysVisible, - ); - } - - @override - void didUpdateWidget(TableCalendar oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.selectedDay != null && widget.selectedDay != null) { - if (!Utils.isSameDay(oldWidget.selectedDay, widget.selectedDay)) { - SchedulerBinding.instance.addPostFrameCallback((_) { - setState(() { - final runCallback = _calendarLogic.setSelectedDay( - widget.selectedDay, - isAnimated: widget.animateProgSelectedDay, - isProgrammatic: true, - ); - - if (runCallback && widget.onDaySelected != null) { - final key = widget.events.keys.firstWhere( - (it) => Utils.isSameDay(it, widget.selectedDay), - orElse: () => null, - ); - widget.onDaySelected(widget.selectedDay, widget.events[key] ?? []); - } - }); - }); - } - } - } - - @override - void dispose() { - _calendarLogic.dispose(); - super.dispose(); - } - - void _selectPrevious() { - setState(() { - _calendarLogic.selectPrevious(); - }); - } - - void _selectNext() { - setState(() { - _calendarLogic.selectNext(); - }); - } - - void _selectDate(DateTime date) { - setState(() { - _calendarLogic.setSelectedDay(date); - - if (widget.onDaySelected != null) { - final key = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); - widget.onDaySelected(date, widget.events[key] ?? []); - } - }); - } - - void _toggleCalendarFormat() { - setState(() { - _calendarLogic.toggleCalendarFormat(); - }); - } - - void _onHorizontalSwipe(DismissDirection direction) { - if (direction == DismissDirection.startToEnd) { - // Swipe right - _selectPrevious(); - } else { - // Swipe left - _selectNext(); - } - } - - void _onUnavailableDaySelected() { - if (widget.onUnavailableDaySelected != null) { - widget.onUnavailableDaySelected(); - } - } - - bool _isDayUnavailable(DateTime day) { - return (widget.startDay != null && day.isBefore(widget.startDay)) || - (widget.endDay != null && day.isAfter(widget.endDay)); - } - - @override - Widget build(BuildContext context) { - final children = []; - - if (widget.headerVisible) { - children.addAll([ - const SizedBox(height: 6.0), - _buildHeader(), - ]); - } - - children.addAll([ - const SizedBox(height: 10.0), - _buildCalendarContent(), - const SizedBox(height: 4.0), - ]); - - return ClipRect( - child: Column( - mainAxisSize: MainAxisSize.min, - children: children, - ), - ); - } - - Widget _buildHeader() { - final children = [ - CustomIconButton( - icon: widget.headerStyle.leftChevronIcon, - onTap: _selectPrevious, - margin: widget.headerStyle.leftChevronMargin, - padding: widget.headerStyle.leftChevronPadding, - ), - Expanded( - child: Text( - widget.headerStyle.titleTextBuilder != null - ? widget.headerStyle.titleTextBuilder(_calendarLogic.focusedDay, widget.locale) - : DateFormat.yMMMM(widget.locale).format(_calendarLogic.focusedDay), - style: widget.headerStyle.titleTextStyle, - textAlign: widget.headerStyle.centerHeaderTitle ? TextAlign.center : TextAlign.start, - ), - ), - CustomIconButton( - icon: widget.headerStyle.rightChevronIcon, - onTap: _selectNext, - margin: widget.headerStyle.rightChevronMargin, - padding: widget.headerStyle.rightChevronPadding, - ), - ]; - - if (widget.headerStyle.formatButtonVisible && - widget.availableCalendarFormats.length > 1 && - widget.forcedCalendarFormat == null) { - children.insert(2, const SizedBox(width: 8.0)); - children.insert(3, _buildFormatButton()); - } - - return Row( - mainAxisSize: MainAxisSize.max, - children: children, - ); - } - - Widget _buildFormatButton() { - return GestureDetector( - onTap: _toggleCalendarFormat, - child: Container( - decoration: widget.headerStyle.formatButtonDecoration, - padding: widget.headerStyle.formatButtonPadding, - child: Text( - _calendarLogic.formatButtonText, - style: widget.headerStyle.formatButtonTextStyle, - ), - ), - ); - } - - Widget _buildCalendarContent() { - if (widget.formatAnimation == FormatAnimation.slide) { - return AnimatedSize( - duration: Duration(milliseconds: _calendarLogic.calendarFormat == CalendarFormat.month ? 330 : 220), - curve: Curves.fastOutSlowIn, - alignment: Alignment(0, -1), - vsync: this, - child: _buildWrapper(), - ); - } else { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 350), - transitionBuilder: (child, animation) { - return SizeTransition( - sizeFactor: animation, - child: ScaleTransition( - scale: animation, - child: child, - ), - ); - }, - child: _buildWrapper( - key: ValueKey(_calendarLogic.calendarFormat), - ), - ); - } - } - - Widget _buildWrapper({Key key}) { - Widget wrappedChild = _buildTable(); - - switch (widget.availableGestures) { - case AvailableGestures.all: - wrappedChild = _buildVerticalSwipeWrapper( - child: _buildHorizontalSwipeWrapper( - child: wrappedChild, - ), - ); - break; - case AvailableGestures.verticalSwipe: - wrappedChild = _buildVerticalSwipeWrapper( - child: wrappedChild, - ); - break; - case AvailableGestures.horizontalSwipe: - wrappedChild = _buildHorizontalSwipeWrapper( - child: wrappedChild, - ); - break; - case AvailableGestures.none: - break; - } - - return Container( - key: key, - margin: const EdgeInsets.symmetric(horizontal: 8.0), - child: wrappedChild, - ); - } - - Widget _buildVerticalSwipeWrapper({Widget child}) { - return SimpleGestureDetector( - child: child, - onVerticalSwipe: (direction) { - setState(() { - _calendarLogic.swipeCalendarFormat(direction == SwipeDirection.up); - }); - }, - swipeConfig: widget.simpleSwipeConfig, - ); - } - - Widget _buildHorizontalSwipeWrapper({Widget child}) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 350), - switchInCurve: Curves.decelerate, - transitionBuilder: (child, animation) { - return SlideTransition( - position: Tween(begin: Offset(_calendarLogic.dx, 0), end: Offset(0, 0)).animate(animation), - child: child, - ); - }, - layoutBuilder: (currentChild, _) => currentChild, - child: Dismissible( - key: ValueKey(_calendarLogic.pageId), - resizeDuration: null, - onDismissed: _onHorizontalSwipe, - direction: DismissDirection.horizontal, - child: child, - ), - ); - } - - Widget _buildTable() { - final daysInWeek = 7; - final children = [ - _buildDaysOfWeek(), - ]; - - int x = 0; - while (x < _calendarLogic.visibleDays.length) { - children.add(_buildTableRow(_calendarLogic.visibleDays.skip(x).take(daysInWeek).toList())); - x += daysInWeek; - } - - return Table( - // Makes this Table fill its parent horizontally - defaultColumnWidth: FractionColumnWidth(1.0 / daysInWeek), - children: children, - ); - } - - TableRow _buildDaysOfWeek() { - return TableRow( - children: _calendarLogic.visibleDays.take(7).map((date) { - return Center( - child: Text( - widget.daysOfWeekStyle.dowTextBuilder != null - ? widget.daysOfWeekStyle.dowTextBuilder(date, widget.locale) - : DateFormat.E(widget.locale).format(date), - style: _calendarLogic.isWeekend(date) - ? widget.daysOfWeekStyle.weekendStyle - : widget.daysOfWeekStyle.weekdayStyle, - ), - ); - }).toList(), - ); - } - - TableRow _buildTableRow(List days) { - return TableRow(children: days.map((date) => _buildTableCell(date)).toList()); - } - - // TableCell will have equal width and height - Widget _buildTableCell(DateTime date) { - return LayoutBuilder( - builder: (context, constraints) => ConstrainedBox( - constraints: BoxConstraints( - maxHeight: widget.rowHeight ?? constraints.maxWidth, - minHeight: widget.rowHeight ?? constraints.maxWidth, - ), - child: _buildCell(date), - ), - ); - } - - Widget _buildCell(DateTime date) { - if (!widget.calendarStyle.outsideDaysVisible && - _calendarLogic.isExtraDay(date) && - _calendarLogic.calendarFormat == CalendarFormat.month) { - return Container(); - } - - Widget content = _buildCellContent(date); - - final eventKey = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); - final holidayKey = widget.holidays.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); - final key = eventKey ?? holidayKey; - - if (key != null) { - final children = [content]; - final events = eventKey != null ? widget.events[eventKey].take(widget.calendarStyle.markersMaxAmount) : []; - final holidays = holidayKey != null ? widget.holidays[holidayKey] : []; - - if (!_isDayUnavailable(date)) { - if (widget.builders.markersBuilder != null) { - children.addAll( - widget.builders.markersBuilder( - context, - key, - events.toList(), - holidays, - ), - ); - } else { - children.add( - Positioned( - top: widget.calendarStyle.markersPositionTop, - bottom: widget.calendarStyle.markersPositionBottom, - left: widget.calendarStyle.markersPositionLeft, - right: widget.calendarStyle.markersPositionRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: events.map((event) => _buildMarker(eventKey, event)).toList(), - ), - ), - ); - } - } - - if (children.length > 1) { - content = Stack( - alignment: widget.calendarStyle.markersAlignment, - children: children, - overflow: widget.calendarStyle.canEventMarkersOverflow ? Overflow.visible : Overflow.clip, - ); - } - } - - return GestureDetector( - behavior: widget.dayHitTestBehavior, - onTap: () => _isDayUnavailable(date) ? _onUnavailableDaySelected() : _selectDate(date), - child: content, - ); - } - - Widget _buildCellContent(DateTime date) { - final eventKey = widget.events.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); - final holidayKey = widget.holidays.keys.firstWhere((it) => Utils.isSameDay(it, date), orElse: () => null); - - final tIsUnavailable = _isDayUnavailable(date); - final tIsSelected = _calendarLogic.isSelected(date); - final tIsToday = _calendarLogic.isToday(date); - final tIsOutside = _calendarLogic.isExtraDay(date); - final tIsHoliday = widget.holidays.containsKey(holidayKey); - final tIsWeekend = _calendarLogic.isWeekend(date); - - final isUnavailable = widget.builders.unavailableDayBuilder != null && tIsUnavailable; - final isSelected = widget.builders.selectedDayBuilder != null && tIsSelected; - final isToday = widget.builders.todayDayBuilder != null && tIsToday; - final isOutsideHoliday = widget.builders.outsideHolidayDayBuilder != null && tIsOutside && tIsHoliday; - final isHoliday = widget.builders.holidayDayBuilder != null && !tIsOutside && tIsHoliday; - final isOutsideWeekend = - widget.builders.outsideWeekendDayBuilder != null && tIsOutside && tIsWeekend && !tIsHoliday; - final isOutside = widget.builders.outsideDayBuilder != null && tIsOutside && !tIsWeekend && !tIsHoliday; - final isWeekend = widget.builders.weekendDayBuilder != null && !tIsOutside && tIsWeekend && !tIsHoliday; - - if (isUnavailable) { - return widget.builders.unavailableDayBuilder(context, date, widget.events[eventKey]); - } else if (isSelected && widget.calendarStyle.renderSelectedFirst) { - return widget.builders.selectedDayBuilder(context, date, widget.events[eventKey]); - } else if (isToday) { - return widget.builders.todayDayBuilder(context, date, widget.events[eventKey]); - } else if (isSelected) { - return widget.builders.selectedDayBuilder(context, date, widget.events[eventKey]); - } else if (isOutsideHoliday) { - return widget.builders.outsideHolidayDayBuilder(context, date, widget.events[eventKey]); - } else if (isHoliday) { - return widget.builders.holidayDayBuilder(context, date, widget.events[eventKey]); - } else if (isOutsideWeekend) { - return widget.builders.outsideWeekendDayBuilder(context, date, widget.events[eventKey]); - } else if (isOutside) { - return widget.builders.outsideDayBuilder(context, date, widget.events[eventKey]); - } else if (isWeekend) { - return widget.builders.weekendDayBuilder(context, date, widget.events[eventKey]); - } else if (widget.builders.dayBuilder != null) { - return widget.builders.dayBuilder(context, date, widget.events[eventKey]); - } else { - return CellWidget( - text: '${date.day}', - isUnavailable: tIsUnavailable, - isSelected: tIsSelected, - isToday: tIsToday, - isWeekend: tIsWeekend, - isOutsideMonth: tIsOutside, - isHoliday: tIsHoliday, - calendarStyle: widget.calendarStyle, - ); - } - } - - Widget _buildMarker(DateTime date, dynamic event) { - if (widget.builders.singleMarkerBuilder != null) { - return widget.builders.singleMarkerBuilder(context, date, event); - } else { - return Container( - width: 8.0, - height: 8.0, - margin: const EdgeInsets.symmetric(horizontal: 0.3), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: widget.calendarStyle.markersColor, - ), - ); - } - } -} +part 'src/calendar.dart'; +part 'src/calendar_controller.dart'; +part 'src/customization/calendar_builders.dart'; +part 'src/customization/calendar_style.dart'; +part 'src/customization/days_of_week_style.dart'; +part 'src/customization/header_style.dart'; +part 'src/widgets/cell_widget.dart'; +part 'src/widgets/custom_icon_button.dart';