Skip to content

Commit

Permalink
feat: Fixes issue #378: ✨Added support for daily & weekly reoccurrenc…
Browse files Browse the repository at this point in the history
…e event in month view
  • Loading branch information
shubham-jitiya-simform committed Oct 28, 2024
1 parent 982747f commit c560837
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 13 deletions.
2 changes: 2 additions & 0 deletions example/lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'app_colors.dart';
class AppConstants {
AppConstants._();

static final List<String> weekTitles = ["M", "T", "W", "T", "F", "S", "S"];

static OutlineInputBorder inputBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(7),
borderSide: BorderSide(
Expand Down
8 changes: 8 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,19 @@ List<CalendarEventData> _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.",
),
Expand Down
142 changes: 133 additions & 9 deletions example/lib/widgets/add_event_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {

DateTime? _startTime;
DateTime? _endTime;
List<bool> _selectedDays = List.filled(7, false);
RepeatFrequency? _selectedFrequency = RepeatFrequency.doNotRepeat;

Color _color = Colors.blue;

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

_setDefaults();
_setInitialWeekday();
}

@override
Expand Down Expand Up @@ -102,7 +105,9 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
}

_startDate = date.withoutTime;

// Reset weekday from new start date
_selectedDays.fillRange(0, _selectedDays.length, false);
_selectedDays[date.weekday - 1] = true;
if (mounted) {
setState(() {});
}
Expand Down Expand Up @@ -247,6 +252,103 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
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<RepeatFrequency>(
value: RepeatFrequency.doNotRepeat,
groupValue: _selectedFrequency,
onChanged: (value) {
setState(
() => _selectedFrequency = value,
);
},
),
Text(
"Do not repeat",
style: TextStyle(
color: AppColors.black,
fontSize: 17,
),
),
],
),
Row(
children: [
Radio<RepeatFrequency>(
value: RepeatFrequency.daily,
groupValue: _selectedFrequency,
onChanged: (value) {
setState(
() => _selectedFrequency = value,
);
},
),
Text(
"Daily",
style: TextStyle(
color: AppColors.black,
fontSize: 17,
),
)
],
),
Row(
children: [
Radio<RepeatFrequency>(
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<Widget>.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,
),
Expand Down Expand Up @@ -286,19 +388,40 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
_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<int> get _selectedIndexes {
List<int> 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;

Expand All @@ -310,6 +433,7 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
_endTime = event.endTime ?? _endTime;
_titleController.text = event.title;
_descriptionController.text = event.description ?? '';
_selectedFrequency = event.recurrenceSettings?.frequency;
}

void _resetForm() {
Expand Down
5 changes: 5 additions & 0 deletions lib/src/calendar_event_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class CalendarEventData<T extends Object?> {
/// Define style of description.
final TextStyle? descriptionStyle;

/// Define reoccurrence settings
final RecurrenceSettings? recurrenceSettings;

/// {@macro calendar_event_data_doc}
CalendarEventData({
required this.title,
Expand All @@ -55,6 +58,7 @@ class CalendarEventData<T extends Object?> {
this.endTime,
this.titleStyle,
this.descriptionStyle,
this.recurrenceSettings,
DateTime? endDate,
}) : _endDate = endDate?.withoutTime,
date = date.withoutTime;
Expand Down Expand Up @@ -119,6 +123,7 @@ class CalendarEventData<T extends Object?> {
"title": title,
"description": description,
"endDate": endDate,
"recurrenceSettings": recurrenceSettings,
};

/// Returns new object of [CalendarEventData] with the updated values defined
Expand Down
9 changes: 9 additions & 0 deletions lib/src/enumerations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
48 changes: 44 additions & 4 deletions lib/src/event_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -137,9 +138,46 @@ class EventController<T extends Object?> 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<CalendarEventData<T>> getRepeatedEvents(DateTime date) {
if (!date.isAfter(DateTime.now())) {
return [];
}
final repeatedEvents = _calendarData.repeatedEvents;
List<CalendarEventData<T>> 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.
Expand Down Expand Up @@ -179,6 +217,10 @@ class CalendarData<T extends Object?> {
/// available in this list as global itemList of all events).
final _eventList = <CalendarEventData<T>>[];

/// If recurrence settings exist then get all the repeated events
List<CalendarEventData<T>> get repeatedEvents =>
_eventList.where((event) => event.recurrenceSettings != null).toList();

UnmodifiableListView<CalendarEventData<T>> get events =>
UnmodifiableListView(_eventList);

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

// TODO: improve this...
if (_eventList.contains(event)) return;

if (event.isFullDayEvent) {
addFullDayEvent(event);
} else if (event.isRangingEvent) {
Expand Down Expand Up @@ -329,7 +370,6 @@ class CalendarData<T extends Object?> {
if (includeFullDayEvents) {
events.addAll(getFullDayEvent(date));
}

return events;
}

Expand Down
33 changes: 33 additions & 0 deletions lib/src/modals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> weekdays;

RecurrenceSettings(
this.startDate, {
this.endDate,
this.interval,
this.frequency = RepeatFrequency.weekly,
List<int>? weekdays,
}) : weekdays = weekdays ?? [startDate.weekday];

@override
String toString() {
return "start date: ${startDate}, "
"end date: ${endDate}, "
"interval: ${interval}, "
"frequency: ${frequency} "
"weekdays: ${weekdays.toString()}";
}
}
1 change: 1 addition & 0 deletions lib/src/month_view/month_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ class _MonthPageBuilder<T> 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]),
Expand Down

0 comments on commit c560837

Please sign in to comment.