Skip to content

Commit

Permalink
feat: Fixes issue #378: ✨Added support for daily & weekly recurrence …
Browse files Browse the repository at this point in the history
…event in month view
  • Loading branch information
shubham-jitiya-simform committed Oct 29, 2024
1 parent 982747f commit 6b65712
Show file tree
Hide file tree
Showing 7 changed files with 479 additions and 5 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
267 changes: 267 additions & 0 deletions example/lib/widgets/add_event_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ class AddOrEditEventForm extends StatefulWidget {
class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
late DateTime _startDate = DateTime.now().withoutTime;
late DateTime _endDate = DateTime.now().withoutTime;
late DateTime _recurrenceEndDate = DateTime.now().withoutTime;

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

Color _color = Colors.blue;

final _form = GlobalKey<FormState>();

late final _descriptionController = TextEditingController();
late final _recurrenceController = TextEditingController();
late final _titleController = TextEditingController();
late final _titleNode = FocusNode();
late final _descriptionNode = FocusNode();
Expand All @@ -43,6 +48,7 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
super.initState();

_setDefaults();
_setInitialWeekday();
}

@override
Expand All @@ -52,6 +58,7 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {

_descriptionController.dispose();
_titleController.dispose();
_recurrenceController.dispose();

super.dispose();
}
Expand Down Expand Up @@ -102,6 +109,7 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
}

_startDate = date.withoutTime;
updateWeekdaysSelection();

if (mounted) {
setState(() {});
Expand Down Expand Up @@ -137,6 +145,7 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
));
} else {
_endDate = date.withoutTime;
updateWeekdaysSelection();
}

if (mounted) {
Expand Down Expand Up @@ -247,6 +256,226 @@ 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,
),
),
),
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) {
// Range of events
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),
if (_selectedFrequency != RepeatFrequency.doNotRepeat)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Reoccurrence ends on: ",
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.w500,
fontSize: 17,
),
),
Row(
children: [
Radio<RecurrenceEnd>(
value: RecurrenceEnd.never,
groupValue: _selectedRecurrenceEnd,
onChanged: (value) => setState(
() => _selectedRecurrenceEnd = value,
),
),
Text(
"Never",
style: TextStyle(
color: AppColors.black,
fontSize: 17,
),
),
],
),
Row(
children: [
Radio<RecurrenceEnd>(
value: RecurrenceEnd.on,
groupValue: _selectedRecurrenceEnd,
onChanged: (value) => setState(
() => _selectedRecurrenceEnd = value,
),
),
Text(
"On",
style: TextStyle(
color: AppColors.black,
fontSize: 17,
),
)
],
),
Row(
children: [
Radio<RecurrenceEnd>(
value: RecurrenceEnd.after,
groupValue: _selectedRecurrenceEnd,
onChanged: (value) => setState(
() => _selectedRecurrenceEnd = value,
),
),
Text(
"After",
style: TextStyle(
color: AppColors.black,
fontSize: 17,
),
),
],
),
],
),
SizedBox(height: 15),
if (_selectedRecurrenceEnd == RecurrenceEnd.on)
DateTimeSelectorFormField(
initialDateTime: _endDate,
decoration: AppConstants.inputDecoration.copyWith(
labelText: "Ends on",
),
onSelect: (date) {
if (date.withoutTime.isBefore(_endDate.withoutTime)) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Recurrence ends after end date'),
));
} else {
_recurrenceEndDate = date.withoutTime;
}

if (mounted) {
setState(() {});
}
},
validator: (value) {
if (value == null || value == "") {
return "Please select end date.";
}

// TODO(Shubham): Add validation of end occurrence >= end date
return null;
},
textStyle: TextStyle(
color: AppColors.black,
fontSize: 17.0,
),
onSave: (date) => _endDate = date ?? _endDate,
type: DateTimeSelectionType.date,
),
if (_selectedRecurrenceEnd == RecurrenceEnd.after)
TextFormField(
controller: _recurrenceController,
style: TextStyle(
color: AppColors.black,
fontSize: 17.0,
),
keyboardType: TextInputType.number,
minLines: 1,
maxLength: 3,
validator: (value) {
if (value == null || value.trim() == '') {
return "Please specify occurrences";
}

return null;
},
decoration: AppConstants.inputDecoration.copyWith(
hintText: "30",
suffixText: 'Occurrences',
counterText: '',
),
),
SizedBox(
height: 15.0,
),
Expand Down Expand Up @@ -293,12 +522,49 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
color: _color,
title: _titleController.text.trim(),
description: _descriptionController.text.trim(),
recurrenceSettings: RecurrenceSettings(
_startDate,
endDate: _recurrenceEndDate,
frequency: _selectedFrequency ?? RepeatFrequency.daily,
weekdays: _selectedIndexes,
interval: int.tryParse(_recurrenceController.text),
recurrenceEndOn: _selectedRecurrenceEnd ?? RecurrenceEnd.never,
),
);

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;
}

void updateWeekdaysSelection() {
_selectedDays.fillRange(0, _selectedDays.length, false);
if (_endDate.difference(_startDate).inDays >= 7) {
_selectedDays.fillRange(0, _selectedDays.length, true);
}
DateTime current = _startDate;
while (current.isBefore(_endDate) || current.isAtSameMomentAs(_endDate)) {
_selectedDays[current.weekday - 1] = true;
current = current.add(Duration(days: 1));
}
}

/// 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 +576,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
19 changes: 19 additions & 0 deletions lib/src/enumerations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,22 @@ enum LineStyle {
/// Dashed line
dashed,
}

/// Defines reoccurrence of event: Daily, weekly, monthly or yearly
enum RepeatFrequency {
doNotRepeat,
daily,
weekly,
monthly,
yearly,
}

/// Defines reoccurrence event ends on:
/// `never` to repeat without any end date specified,
/// `on` to repeat till date specified
/// `after` repeat till defined number of occurrence.
enum RecurrenceEnd {
never,
on,
after,
}
Loading

0 comments on commit 6b65712

Please sign in to comment.