Skip to content

Commit 23bb82e

Browse files
author
Shubham Jitiya
committed
✨ Handle daily & weekly reoccurrence events
1 parent 982747f commit 23bb82e

File tree

8 files changed

+234
-13
lines changed

8 files changed

+234
-13
lines changed

example/lib/constants.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import 'app_colors.dart';
55
class AppConstants {
66
AppConstants._();
77

8+
static final List<String> weekTitles = ["M", "T", "W", "T", "F", "S", "S"];
9+
810
static OutlineInputBorder inputBorder = OutlineInputBorder(
911
borderRadius: BorderRadius.circular(7),
1012
borderSide: BorderSide(

example/lib/main.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ List<CalendarEventData> _events = [
4141
description: "Today is project meeting.",
4242
startTime: DateTime(_now.year, _now.month, _now.day, 18, 30),
4343
endTime: DateTime(_now.year, _now.month, _now.day, 22),
44+
recurrenceSettings: RecurrenceSettings(
45+
_now.add(Duration(days: 1)),
46+
frequency: RepeatFrequency.daily,
47+
),
4448
),
4549
CalendarEventData(
4650
date: _now.add(Duration(days: 1)),
4751
startTime: DateTime(_now.year, _now.month, _now.day, 18),
4852
endTime: DateTime(_now.year, _now.month, _now.day, 19),
53+
recurrenceSettings: RecurrenceSettings(
54+
_now.add(Duration(days: 1)),
55+
frequency: RepeatFrequency.weekly,
56+
),
4957
title: "Wedding anniversary",
5058
description: "Attend uncle's wedding anniversary.",
5159
),

example/lib/widgets/add_event_form.dart

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
2828

2929
DateTime? _startTime;
3030
DateTime? _endTime;
31+
RepeatFrequency? _selectedFrequency;
32+
List<bool> _selectedDays = List.filled(7, false);
3133

3234
Color _color = Colors.blue;
3335

@@ -43,6 +45,7 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
4345
super.initState();
4446

4547
_setDefaults();
48+
_setInitialWeekday();
4649
}
4750

4851
@override
@@ -102,7 +105,9 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
102105
}
103106

104107
_startDate = date.withoutTime;
105-
108+
// Reset weekday from new start date
109+
_selectedDays.fillRange(0, _selectedDays.length, false);
110+
_selectedDays[date.weekday - 1] = true;
106111
if (mounted) {
107112
setState(() {});
108113
}
@@ -247,6 +252,103 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
247252
hintText: "Event Description",
248253
),
249254
),
255+
Align(
256+
alignment: Alignment.centerLeft,
257+
child: Text(
258+
"Repeat",
259+
style: TextStyle(
260+
color: AppColors.black,
261+
fontWeight: FontWeight.w500,
262+
fontSize: 17,
263+
),
264+
),
265+
),
266+
Column(
267+
crossAxisAlignment: CrossAxisAlignment.start,
268+
children: [
269+
Row(
270+
children: [
271+
Radio<RepeatFrequency>(
272+
value: RepeatFrequency.doNotRepeat,
273+
groupValue: _selectedFrequency,
274+
onChanged: (value) {
275+
setState(
276+
() => _selectedFrequency = value,
277+
);
278+
},
279+
),
280+
Text(
281+
"Do not repeat",
282+
style: TextStyle(
283+
color: AppColors.black,
284+
fontSize: 17,
285+
),
286+
),
287+
],
288+
),
289+
Row(
290+
children: [
291+
Radio<RepeatFrequency>(
292+
value: RepeatFrequency.daily,
293+
groupValue: _selectedFrequency,
294+
onChanged: (value) {
295+
setState(
296+
() => _selectedFrequency = value,
297+
);
298+
},
299+
),
300+
Text(
301+
"Daily",
302+
style: TextStyle(
303+
color: AppColors.black,
304+
fontSize: 17,
305+
),
306+
)
307+
],
308+
),
309+
Row(
310+
children: [
311+
Radio<RepeatFrequency>(
312+
value: RepeatFrequency.weekly,
313+
groupValue: _selectedFrequency,
314+
onChanged: (value) {
315+
setState(
316+
() => _selectedFrequency = value,
317+
);
318+
},
319+
),
320+
Text(
321+
"Weekly",
322+
style: TextStyle(
323+
color: AppColors.black,
324+
fontSize: 17,
325+
),
326+
),
327+
],
328+
)
329+
],
330+
),
331+
if (_selectedFrequency == RepeatFrequency.weekly) ...[
332+
Wrap(
333+
children: List<Widget>.generate(AppConstants.weekTitles.length,
334+
(int index) {
335+
return ChoiceChip(
336+
label: Text(AppConstants.weekTitles[index]),
337+
showCheckmark: false,
338+
selected: _selectedDays[index],
339+
onSelected: (bool selected) {
340+
setState(() {
341+
_selectedDays[index] = selected;
342+
if (!_selectedDays.contains(true)) {
343+
_selectedDays[_startDate.weekday - 1] = true;
344+
}
345+
});
346+
},
347+
shape: CircleBorder(),
348+
);
349+
}).toList(),
350+
),
351+
],
250352
SizedBox(
251353
height: 15.0,
252354
),
@@ -286,19 +388,40 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
286388
_form.currentState?.save();
287389

288390
final event = CalendarEventData(
289-
date: _startDate,
290-
endTime: _endTime,
291-
startTime: _startTime,
292-
endDate: _endDate,
293-
color: _color,
294-
title: _titleController.text.trim(),
295-
description: _descriptionController.text.trim(),
296-
);
391+
date: _startDate,
392+
endTime: _endTime,
393+
startTime: _startTime,
394+
endDate: _endDate,
395+
color: _color,
396+
title: _titleController.text.trim(),
397+
description: _descriptionController.text.trim(),
398+
recurrenceSettings: RecurrenceSettings(
399+
_startDate,
400+
frequency: _selectedFrequency ?? RepeatFrequency.daily,
401+
weekdays: _selectedIndexes,
402+
));
297403

298404
widget.onEventAdd?.call(event);
299405
_resetForm();
300406
}
301407

408+
/// Get list of weekdays in indices from the selected days
409+
List<int> get _selectedIndexes {
410+
List<int> selectedIndexes = [];
411+
for (int i = 0; i < _selectedDays.length; i++) {
412+
if (_selectedDays[i] == true) {
413+
selectedIndexes.add(i);
414+
}
415+
}
416+
return selectedIndexes;
417+
}
418+
419+
/// Set initial selected week to start date
420+
void _setInitialWeekday() {
421+
final currentWeekday = DateTime.now().weekday - 1;
422+
_selectedDays[currentWeekday] = true;
423+
}
424+
302425
void _setDefaults() {
303426
if (widget.event == null) return;
304427

lib/src/calendar_event_data.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class CalendarEventData<T extends Object?> {
4444
/// Define style of description.
4545
final TextStyle? descriptionStyle;
4646

47+
/// Define reoccurrence settings
48+
final RecurrenceSettings? recurrenceSettings;
49+
4750
/// {@macro calendar_event_data_doc}
4851
CalendarEventData({
4952
required this.title,
@@ -55,6 +58,7 @@ class CalendarEventData<T extends Object?> {
5558
this.endTime,
5659
this.titleStyle,
5760
this.descriptionStyle,
61+
this.recurrenceSettings,
5862
DateTime? endDate,
5963
}) : _endDate = endDate?.withoutTime,
6064
date = date.withoutTime;
@@ -119,6 +123,7 @@ class CalendarEventData<T extends Object?> {
119123
"title": title,
120124
"description": description,
121125
"endDate": endDate,
126+
"recurrenceSettings": recurrenceSettings,
122127
};
123128

124129
/// Returns new object of [CalendarEventData] with the updated values defined

lib/src/enumerations.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,12 @@ enum LineStyle {
4848
/// Dashed line
4949
dashed,
5050
}
51+
52+
/// Defines reoccurrence of event: Daily, weekly, monthly or yearly
53+
enum RepeatFrequency {
54+
doNotRepeat,
55+
daily,
56+
weekly,
57+
monthly,
58+
yearly,
59+
}

lib/src/event_controller.dart

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:collection';
77
import 'package:flutter/material.dart';
88

99
import 'calendar_event_data.dart';
10+
import 'enumerations.dart';
1011
import 'extensions.dart';
1112
import 'typedefs.dart';
1213

@@ -137,9 +138,46 @@ class EventController<T extends Object?> extends ChangeNotifier {
137138
{bool includeFullDayEvents = true}) {
138139
//ignore: deprecated_member_use_from_same_package
139140
if (_eventFilter != null) return _eventFilter!.call(date, this.events);
140-
141-
return _calendarData.getEventsOnDay(date.withoutTime,
141+
final events = _calendarData.getEventsOnDay(date.withoutTime,
142142
includeFullDayEvents: includeFullDayEvents);
143+
events.addAll(getRepeatedEvents(date));
144+
return events;
145+
}
146+
147+
/// Filters list of repeated events to show in the cell for given date
148+
/// from all the repeated events.
149+
/// Event reoccurrence will only show after today's date and event's day.
150+
List<CalendarEventData<T>> getRepeatedEvents(DateTime date) {
151+
if (!date.isAfter(DateTime.now())) {
152+
return [];
153+
}
154+
final repeatedEvents = _calendarData.repeatedEvents;
155+
List<CalendarEventData<T>> events = [];
156+
for (final event in repeatedEvents) {
157+
if (!date.isAfter(event.date)) {
158+
continue;
159+
}
160+
switch (event.recurrenceSettings!.frequency) {
161+
case RepeatFrequency.daily:
162+
events.add(event);
163+
break;
164+
case RepeatFrequency.weekly:
165+
if (event.recurrenceSettings!.weekdays.contains(date.weekday - 1)) {
166+
events.add(event);
167+
}
168+
break;
169+
case RepeatFrequency.monthly:
170+
// TODO: Handle this case.
171+
break;
172+
case RepeatFrequency.yearly:
173+
// TODO: Handle this case.
174+
break;
175+
case RepeatFrequency.doNotRepeat:
176+
// TODO: Handle this case.
177+
break;
178+
}
179+
}
180+
return events;
143181
}
144182

145183
/// Returns full day events on given day.
@@ -179,6 +217,10 @@ class CalendarData<T extends Object?> {
179217
/// available in this list as global itemList of all events).
180218
final _eventList = <CalendarEventData<T>>[];
181219

220+
/// If recurrence settings exist then get all the repeated events
221+
List<CalendarEventData<T>> get repeatedEvents =>
222+
_eventList.where((event) => event.recurrenceSettings != null).toList();
223+
182224
UnmodifiableListView<CalendarEventData<T>> get events =>
183225
UnmodifiableListView(_eventList);
184226

@@ -249,7 +291,6 @@ class CalendarData<T extends Object?> {
249291

250292
// TODO: improve this...
251293
if (_eventList.contains(event)) return;
252-
253294
if (event.isFullDayEvent) {
254295
addFullDayEvent(event);
255296
} else if (event.isRangingEvent) {
@@ -329,7 +370,6 @@ class CalendarData<T extends Object?> {
329370
if (includeFullDayEvents) {
330371
events.addAll(getFullDayEvent(date));
331372
}
332-
333373
return events;
334374
}
335375

lib/src/modals.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,36 @@ class LiveTimeIndicatorSettings {
8383
showBullet: false,
8484
);
8585
}
86+
87+
/// Set `frequency = RepeatFrequency.daily` to repeat every day after current date & event day.
88+
/// Set `frequency = RepeatFrequency.weekly` & provide list of weekdays to repeat on.
89+
/// [startDate]: Defines start date of repeating events.
90+
/// [endDate]: Defines end date of repeating events.
91+
/// [interval]: Defines repetition of event after given [interval] in days.
92+
/// [frequency]: Defines repeat daily, weekly, monthly or yearly.
93+
/// [weekdays]: Contains list of weekdays to repeat on.
94+
/// By default weekday of event is considered if not provided.
95+
class RecurrenceSettings {
96+
final DateTime startDate;
97+
final DateTime? endDate;
98+
final int? interval;
99+
final RepeatFrequency frequency;
100+
final List<int> weekdays;
101+
102+
RecurrenceSettings(
103+
this.startDate, {
104+
this.endDate,
105+
this.interval,
106+
this.frequency = RepeatFrequency.weekly,
107+
List<int>? weekdays,
108+
}) : weekdays = weekdays ?? [startDate.weekday];
109+
110+
@override
111+
String toString() {
112+
return "start date: ${startDate}, "
113+
"end date: ${endDate}, "
114+
"interval: ${interval}, "
115+
"frequency: ${frequency} "
116+
"weekdays: ${weekdays.toString()}";
117+
}
118+
}

lib/src/month_view/month_view.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ class _MonthPageBuilder<T> extends StatelessWidget {
699699
shrinkWrap: true,
700700
itemBuilder: (context, index) {
701701
final events = controller.getEventsOnDay(monthDays[index]);
702+
702703
return GestureDetector(
703704
onTap: () => onCellTap?.call(events, monthDays[index]),
704705
onLongPress: () => onDateLongPress?.call(monthDays[index]),

0 commit comments

Comments
 (0)