From 23bb82e68023957298203f9aeee2af54f1e7d582 Mon Sep 17 00:00:00 2001 From: Shubham Jitiya Date: Wed, 23 Oct 2024 16:39:34 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Handle=20daily=20&=20weekly=20reocc?= =?UTF-8?q?urrence=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/constants.dart | 2 + example/lib/main.dart | 8 ++ example/lib/widgets/add_event_form.dart | 141 ++++++++++++++++++++++-- lib/src/calendar_event_data.dart | 5 + lib/src/enumerations.dart | 9 ++ lib/src/event_controller.dart | 48 +++++++- lib/src/modals.dart | 33 ++++++ lib/src/month_view/month_view.dart | 1 + 8 files changed, 234 insertions(+), 13 deletions(-) diff --git a/example/lib/constants.dart b/example/lib/constants.dart index 36ec8416..8828c9d3 100644 --- a/example/lib/constants.dart +++ b/example/lib/constants.dart @@ -5,6 +5,8 @@ import 'app_colors.dart'; class AppConstants { AppConstants._(); + static final List weekTitles = ["M", "T", "W", "T", "F", "S", "S"]; + static OutlineInputBorder inputBorder = OutlineInputBorder( borderRadius: BorderRadius.circular(7), borderSide: BorderSide( diff --git a/example/lib/main.dart b/example/lib/main.dart index 146e23af..2507f596 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -41,11 +41,19 @@ List _events = [ description: "Today is project meeting.", startTime: DateTime(_now.year, _now.month, _now.day, 18, 30), endTime: DateTime(_now.year, _now.month, _now.day, 22), + recurrenceSettings: RecurrenceSettings( + _now.add(Duration(days: 1)), + frequency: RepeatFrequency.daily, + ), ), CalendarEventData( date: _now.add(Duration(days: 1)), startTime: DateTime(_now.year, _now.month, _now.day, 18), endTime: DateTime(_now.year, _now.month, _now.day, 19), + recurrenceSettings: RecurrenceSettings( + _now.add(Duration(days: 1)), + frequency: RepeatFrequency.weekly, + ), title: "Wedding anniversary", description: "Attend uncle's wedding anniversary.", ), diff --git a/example/lib/widgets/add_event_form.dart b/example/lib/widgets/add_event_form.dart index 40b18718..64753328 100644 --- a/example/lib/widgets/add_event_form.dart +++ b/example/lib/widgets/add_event_form.dart @@ -28,6 +28,8 @@ class _AddOrEditEventFormState extends State { DateTime? _startTime; DateTime? _endTime; + RepeatFrequency? _selectedFrequency; + List _selectedDays = List.filled(7, false); Color _color = Colors.blue; @@ -43,6 +45,7 @@ class _AddOrEditEventFormState extends State { super.initState(); _setDefaults(); + _setInitialWeekday(); } @override @@ -102,7 +105,9 @@ class _AddOrEditEventFormState extends State { } _startDate = date.withoutTime; - + // Reset weekday from new start date + _selectedDays.fillRange(0, _selectedDays.length, false); + _selectedDays[date.weekday - 1] = true; if (mounted) { setState(() {}); } @@ -247,6 +252,103 @@ class _AddOrEditEventFormState extends State { hintText: "Event Description", ), ), + Align( + alignment: Alignment.centerLeft, + child: Text( + "Repeat", + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w500, + fontSize: 17, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Radio( + value: RepeatFrequency.doNotRepeat, + groupValue: _selectedFrequency, + onChanged: (value) { + setState( + () => _selectedFrequency = value, + ); + }, + ), + Text( + "Do not repeat", + style: TextStyle( + color: AppColors.black, + fontSize: 17, + ), + ), + ], + ), + Row( + children: [ + Radio( + value: RepeatFrequency.daily, + groupValue: _selectedFrequency, + onChanged: (value) { + setState( + () => _selectedFrequency = value, + ); + }, + ), + Text( + "Daily", + style: TextStyle( + color: AppColors.black, + fontSize: 17, + ), + ) + ], + ), + Row( + children: [ + Radio( + value: RepeatFrequency.weekly, + groupValue: _selectedFrequency, + onChanged: (value) { + setState( + () => _selectedFrequency = value, + ); + }, + ), + Text( + "Weekly", + style: TextStyle( + color: AppColors.black, + fontSize: 17, + ), + ), + ], + ) + ], + ), + if (_selectedFrequency == RepeatFrequency.weekly) ...[ + Wrap( + children: List.generate(AppConstants.weekTitles.length, + (int index) { + return ChoiceChip( + label: Text(AppConstants.weekTitles[index]), + showCheckmark: false, + selected: _selectedDays[index], + onSelected: (bool selected) { + setState(() { + _selectedDays[index] = selected; + if (!_selectedDays.contains(true)) { + _selectedDays[_startDate.weekday - 1] = true; + } + }); + }, + shape: CircleBorder(), + ); + }).toList(), + ), + ], SizedBox( height: 15.0, ), @@ -286,19 +388,40 @@ class _AddOrEditEventFormState extends State { _form.currentState?.save(); final event = CalendarEventData( - date: _startDate, - endTime: _endTime, - startTime: _startTime, - endDate: _endDate, - color: _color, - title: _titleController.text.trim(), - description: _descriptionController.text.trim(), - ); + date: _startDate, + endTime: _endTime, + startTime: _startTime, + endDate: _endDate, + color: _color, + title: _titleController.text.trim(), + description: _descriptionController.text.trim(), + recurrenceSettings: RecurrenceSettings( + _startDate, + frequency: _selectedFrequency ?? RepeatFrequency.daily, + weekdays: _selectedIndexes, + )); widget.onEventAdd?.call(event); _resetForm(); } + /// Get list of weekdays in indices from the selected days + List get _selectedIndexes { + List selectedIndexes = []; + for (int i = 0; i < _selectedDays.length; i++) { + if (_selectedDays[i] == true) { + selectedIndexes.add(i); + } + } + return selectedIndexes; + } + + /// Set initial selected week to start date + void _setInitialWeekday() { + final currentWeekday = DateTime.now().weekday - 1; + _selectedDays[currentWeekday] = true; + } + void _setDefaults() { if (widget.event == null) return; diff --git a/lib/src/calendar_event_data.dart b/lib/src/calendar_event_data.dart index e6b053fd..19f6986a 100644 --- a/lib/src/calendar_event_data.dart +++ b/lib/src/calendar_event_data.dart @@ -44,6 +44,9 @@ class CalendarEventData { /// Define style of description. final TextStyle? descriptionStyle; + /// Define reoccurrence settings + final RecurrenceSettings? recurrenceSettings; + /// {@macro calendar_event_data_doc} CalendarEventData({ required this.title, @@ -55,6 +58,7 @@ class CalendarEventData { this.endTime, this.titleStyle, this.descriptionStyle, + this.recurrenceSettings, DateTime? endDate, }) : _endDate = endDate?.withoutTime, date = date.withoutTime; @@ -119,6 +123,7 @@ class CalendarEventData { "title": title, "description": description, "endDate": endDate, + "recurrenceSettings": recurrenceSettings, }; /// Returns new object of [CalendarEventData] with the updated values defined diff --git a/lib/src/enumerations.dart b/lib/src/enumerations.dart index 5659e4e9..e10b8d22 100644 --- a/lib/src/enumerations.dart +++ b/lib/src/enumerations.dart @@ -48,3 +48,12 @@ enum LineStyle { /// Dashed line dashed, } + +/// Defines reoccurrence of event: Daily, weekly, monthly or yearly +enum RepeatFrequency { + doNotRepeat, + daily, + weekly, + monthly, + yearly, +} diff --git a/lib/src/event_controller.dart b/lib/src/event_controller.dart index 1a131a95..190b0f69 100644 --- a/lib/src/event_controller.dart +++ b/lib/src/event_controller.dart @@ -7,6 +7,7 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'calendar_event_data.dart'; +import 'enumerations.dart'; import 'extensions.dart'; import 'typedefs.dart'; @@ -137,9 +138,46 @@ class EventController extends ChangeNotifier { {bool includeFullDayEvents = true}) { //ignore: deprecated_member_use_from_same_package if (_eventFilter != null) return _eventFilter!.call(date, this.events); - - return _calendarData.getEventsOnDay(date.withoutTime, + final events = _calendarData.getEventsOnDay(date.withoutTime, includeFullDayEvents: includeFullDayEvents); + events.addAll(getRepeatedEvents(date)); + return events; + } + + /// Filters list of repeated events to show in the cell for given date + /// from all the repeated events. + /// Event reoccurrence will only show after today's date and event's day. + List> getRepeatedEvents(DateTime date) { + if (!date.isAfter(DateTime.now())) { + return []; + } + final repeatedEvents = _calendarData.repeatedEvents; + List> events = []; + for (final event in repeatedEvents) { + if (!date.isAfter(event.date)) { + continue; + } + switch (event.recurrenceSettings!.frequency) { + case RepeatFrequency.daily: + events.add(event); + break; + case RepeatFrequency.weekly: + if (event.recurrenceSettings!.weekdays.contains(date.weekday - 1)) { + events.add(event); + } + break; + case RepeatFrequency.monthly: + // TODO: Handle this case. + break; + case RepeatFrequency.yearly: + // TODO: Handle this case. + break; + case RepeatFrequency.doNotRepeat: + // TODO: Handle this case. + break; + } + } + return events; } /// Returns full day events on given day. @@ -179,6 +217,10 @@ class CalendarData { /// available in this list as global itemList of all events). final _eventList = >[]; + /// If recurrence settings exist then get all the repeated events + List> get repeatedEvents => + _eventList.where((event) => event.recurrenceSettings != null).toList(); + UnmodifiableListView> get events => UnmodifiableListView(_eventList); @@ -249,7 +291,6 @@ class CalendarData { // TODO: improve this... if (_eventList.contains(event)) return; - if (event.isFullDayEvent) { addFullDayEvent(event); } else if (event.isRangingEvent) { @@ -329,7 +370,6 @@ class CalendarData { if (includeFullDayEvents) { events.addAll(getFullDayEvent(date)); } - return events; } diff --git a/lib/src/modals.dart b/lib/src/modals.dart index 5fc80acb..f527dc47 100644 --- a/lib/src/modals.dart +++ b/lib/src/modals.dart @@ -83,3 +83,36 @@ class LiveTimeIndicatorSettings { showBullet: false, ); } + +/// Set `frequency = RepeatFrequency.daily` to repeat every day after current date & event day. +/// Set `frequency = RepeatFrequency.weekly` & provide list of weekdays to repeat on. +/// [startDate]: Defines start date of repeating events. +/// [endDate]: Defines end date of repeating events. +/// [interval]: Defines repetition of event after given [interval] in days. +/// [frequency]: Defines repeat daily, weekly, monthly or yearly. +/// [weekdays]: Contains list of weekdays to repeat on. +/// By default weekday of event is considered if not provided. +class RecurrenceSettings { + final DateTime startDate; + final DateTime? endDate; + final int? interval; + final RepeatFrequency frequency; + final List weekdays; + + RecurrenceSettings( + this.startDate, { + this.endDate, + this.interval, + this.frequency = RepeatFrequency.weekly, + List? weekdays, + }) : weekdays = weekdays ?? [startDate.weekday]; + + @override + String toString() { + return "start date: ${startDate}, " + "end date: ${endDate}, " + "interval: ${interval}, " + "frequency: ${frequency} " + "weekdays: ${weekdays.toString()}"; + } +} diff --git a/lib/src/month_view/month_view.dart b/lib/src/month_view/month_view.dart index 51690910..bc082b59 100644 --- a/lib/src/month_view/month_view.dart +++ b/lib/src/month_view/month_view.dart @@ -699,6 +699,7 @@ class _MonthPageBuilder extends StatelessWidget { shrinkWrap: true, itemBuilder: (context, index) { final events = controller.getEventsOnDay(monthDays[index]); + return GestureDetector( onTap: () => onCellTap?.call(events, monthDays[index]), onLongPress: () => onDateLongPress?.call(monthDays[index]),