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 Nov 5, 2024
1 parent 982747f commit a9ebc1d
Show file tree
Hide file tree
Showing 7 changed files with 541 additions and 7 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
289 changes: 289 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 _occurrenceController = 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();
_occurrenceController.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,8 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
));
} else {
_endDate = date.withoutTime;
_recurrenceEndDate = _endDate;
updateWeekdaysSelection();
}

if (mounted) {
Expand Down Expand Up @@ -247,6 +257,245 @@ 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) {
setState(
() => _selectedFrequency = value,
);
},
),
Text(
"Weekly",
style: TextStyle(
color: AppColors.black,
fontSize: 17,
),
),
],
),
Row(
children: [
Radio<RepeatFrequency>(
value: RepeatFrequency.monthly,
groupValue: _selectedFrequency,
onChanged: (value) {
setState(
() => _selectedFrequency = value,
);
},
),
Text(
"Monthly",
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: _recurrenceEndDate,
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) => _recurrenceEndDate = date ?? _recurrenceEndDate,
type: DateTimeSelectionType.date,
),
if (_selectedRecurrenceEnd == RecurrenceEnd.after)
TextFormField(
controller: _occurrenceController,
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 +542,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(_occurrenceController.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,12 +596,15 @@ class _AddOrEditEventFormState extends State<AddOrEditEventForm> {
_endTime = event.endTime ?? _endTime;
_titleController.text = event.title;
_descriptionController.text = event.description ?? '';
_selectedFrequency = event.recurrenceSettings?.frequency;
// TODO(Shubham): Update recurrence end date
}

void _resetForm() {
_form.currentState?.reset();
_startDate = DateTime.now().withoutTime;
_endDate = DateTime.now().withoutTime;
// TODO(Shubham): Update recurrence end date
_startTime = null;
_endTime = null;
_color = Colors.blue;
Expand Down
Loading

0 comments on commit a9ebc1d

Please sign in to comment.