Skip to content

Commit 140468e

Browse files
YBTopaz8jfversluis
andauthored
Added Full Support for Reminders. (#55)
* Added Full Support for Reminders. - Users Can Add/Update/Remove a reminder from event - Should be robust to handle errors - Tried to decouple so that we can later expose those methods for granular dev control. * Add Reminders * Implement Windows & minor fixes * Minor improvements * This SHOULD solve issues with null calendar titles being null * Added support for Multiple reminders in a day - Added a RemindersInMinutes Property to ease the use of minutes directly. * Should be good now. * Added Missing Code parts. Fixed the demo and it now shows full CRUD with reminders I had all my projects mixed up - sorry! * Applied suggested changes/ Should Hopefully Align to the PR rules? * Update .gitignore * Fix all the things --------- Co-authored-by: Gerald Versluis <[email protected]> Co-authored-by: Gerald Versluis <[email protected]>
1 parent 8cfd365 commit 140468e

File tree

13 files changed

+472
-110
lines changed

13 files changed

+472
-110
lines changed

samples/Plugin.Maui.CalendarStore.Sample/AddEventsPage.xaml

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
33
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
44
xmlns:local="clr-namespace:Plugin.Maui.CalendarStore.Sample"
5+
xmlns:calendar="clr-namespace:Plugin.Maui.CalendarStore;assembly=Plugin.Maui.CalendarStore"
56
x:Class="Plugin.Maui.CalendarStore.Sample.AddEventsPage"
67
x:DataType="local:AddEventsPage"
78
Title="Add Event">
@@ -12,9 +13,9 @@
1213
</ContentPage.Resources>
1314

1415
<ScrollView>
15-
<Grid Margin="20" HorizontalOptions="Center"
16-
ColumnDefinitions="100, 300"
17-
RowDefinitions="50,50,100,50,50,50,50,50,50,50,*"
16+
<Grid Margin="20" HorizontalOptions="Fill"
17+
ColumnDefinitions="100, *"
18+
RowDefinitions="50,50,100,50,50,50,50,50,50,50,50,150,*"
1819
RowSpacing="5" ColumnSpacing="10">
1920

2021
<Label Grid.Column="0" Grid.Row="0"
@@ -24,66 +25,107 @@
2425
ItemsSource="{Binding Calendars}"
2526
ItemDisplayBinding="{Binding Name}"
2627
SelectedItem="{Binding SelectedCalendar}"
27-
HorizontalOptions="FillAndExpand"
28-
VerticalOptions="CenterAndExpand"
28+
HorizontalOptions="Fill"
29+
VerticalOptions="Center"
2930
IsEnabled="{Binding IsCreateAction}"/>
3031

3132
<Label Grid.Column="0" Grid.Row="1"
3233
Text="Event Title:" />
3334
<Entry Grid.Column="1" Grid.Row="1"
3435
Text="{Binding EventTitle}"
35-
HorizontalOptions="FillAndExpand"
36-
VerticalOptions="CenterAndExpand" />
36+
HorizontalOptions="Fill"
37+
VerticalOptions="Center" />
3738

3839
<Label Grid.Column="0" Grid.Row="2"
3940
Text="Event Description:" />
4041
<Editor Grid.Column="1" Grid.Row="2"
4142
Text="{Binding EventDescription}"
42-
HorizontalOptions="FillAndExpand"
43-
VerticalOptions="FillAndExpand" />
43+
HorizontalOptions="Fill"
44+
VerticalOptions="Fill" />
4445

4546
<Label Grid.Column="0" Grid.Row="3"
4647
Text="Event Location:" />
4748
<Entry Grid.Column="1" Grid.Row="3"
4849
Text="{Binding EventLocation}"
49-
HorizontalOptions="FillAndExpand"
50-
VerticalOptions="CenterAndExpand" />
50+
HorizontalOptions="Fill"
51+
VerticalOptions="Center" />
5152

5253
<Label Grid.Column="0" Grid.Row="4"
5354
Text="Start Date:" />
5455
<DatePicker Grid.Column="1" Grid.Row="4"
5556
Date="{Binding EventStartDate}"
56-
HorizontalOptions="FillAndExpand"
57-
VerticalOptions="CenterAndExpand" />
57+
HorizontalOptions="Fill"
58+
VerticalOptions="Center" />
5859

5960
<Label Grid.Column="0" Grid.Row="5"
6061
Text="Start Time:" />
6162
<TimePicker Grid.Column="1" Grid.Row="5"
6263
Time="{Binding EventStartTime}"
63-
HorizontalOptions="FillAndExpand"
64-
VerticalOptions="CenterAndExpand" />
64+
HorizontalOptions="Fill"
65+
VerticalOptions="Center" />
6566

6667
<Label Grid.Column="0" Grid.Row="6"
6768
Text="End Date:" />
6869
<DatePicker Grid.Column="1" Grid.Row="6"
6970
Date="{Binding EventEndDate}"
70-
HorizontalOptions="FillAndExpand"
71-
VerticalOptions="CenterAndExpand" />
71+
HorizontalOptions="Fill"
72+
VerticalOptions="Center" />
7273

7374
<Label Grid.Column="0" Grid.Row="7"
7475
Text="End Time:" />
7576
<TimePicker Grid.Column="1" Grid.Row="7"
7677
Time="{Binding EventEndTime}"
77-
HorizontalOptions="FillAndExpand"
78-
VerticalOptions="CenterAndExpand" />
78+
HorizontalOptions="Fill"
79+
VerticalOptions="Center" />
7980

80-
<Label Grid.Column="0" Grid.Row="8"
81-
Text="All day:" />
82-
<CheckBox Grid.Column="1" Grid.Row="8"
83-
IsChecked="{Binding EventIsAllDay}"
84-
HorizontalOptions="Start" />
81+
<HorizontalStackLayout Grid.Column="0" Grid.Row="8" HorizontalOptions="Fill" x:Name="IsAllDaySection">
82+
<Label Text="All day:" />
8583

86-
<Button Grid.Column="0" Grid.Row="9" Grid.ColumnSpan="2"
84+
<CheckBox IsChecked="{Binding EventIsAllDay}"
85+
HorizontalOptions="Start" />
86+
</HorizontalStackLayout>
87+
88+
<HorizontalStackLayout Grid.Column="0" Grid.Row="9">
89+
<Label Text="Reminder:" />
90+
</HorizontalStackLayout>
91+
92+
<HorizontalStackLayout Grid.Column="1" Grid.Row="9" >
93+
<TimePicker x:Name="ReminderTime" />
94+
95+
<Button Text="Add" x:Name="AddReminderBtn"
96+
Clicked="AddReminderBtn_Clicked"
97+
IsVisible="True"/>
98+
</HorizontalStackLayout>
99+
100+
<Button
101+
Grid.ColumnSpan="2"
102+
Grid.Row="10"
103+
Text="Clear reminders"
104+
Clicked="ClearRemindersButton_Clicked" />
105+
106+
<CollectionView
107+
Grid.ColumnSpan="2"
108+
Grid.Column="0"
109+
Grid.Row="11"
110+
x:Name="RemindersColView"
111+
ItemsSource="{Binding EventReminders}"
112+
EmptyView="No Reminders">
113+
<CollectionView.ItemTemplate>
114+
<DataTemplate x:DataType="calendar:Reminder">
115+
<HorizontalStackLayout HorizontalOptions="Center" Spacing="5" Margin="5">
116+
117+
<Label Text="{Binding DateTime, StringFormat='{0:ddd, dd MMM yyyy} at {0:hh:mm:ss}'}"/>
118+
119+
<Button Text="X" FontSize="18"
120+
Clicked="DltReminderBtn_Clicked" x:Name="DltReminderBtn"
121+
CommandParameter="{Binding .}"/>
122+
</HorizontalStackLayout>
123+
124+
</DataTemplate>
125+
</CollectionView.ItemTemplate>
126+
</CollectionView>
127+
128+
<Button Grid.Column="0" Grid.Row="12" Grid.ColumnSpan="2"
87129
Text="Save"
88130
VerticalOptions="Center"
89131
HorizontalOptions="Center"

samples/Plugin.Maui.CalendarStore.Sample/AddEventsPage.xaml.cs

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ public partial class AddEventsPage : ContentPage
88
readonly ICalendarStore calendarStore;
99

1010
public bool IsCreateAction => eventToUpdate is null;
11-
12-
public ObservableCollection<Calendar> Calendars { get; set; } = new();
11+
public ObservableCollection<Calendar> Calendars { get; set; } = [];
1312
public Calendar? SelectedCalendar { get; set; }
1413
public string EventTitle { get; set; } = string.Empty;
1514
public string EventDescription { get; set; } = string.Empty;
@@ -18,7 +17,9 @@ public partial class AddEventsPage : ContentPage
1817
public TimeSpan EventStartTime { get; set; } = DateTime.Now.TimeOfDay;
1918
public DateTime EventEndDate { get; set; } = DateTime.Now;
2019
public TimeSpan EventEndTime { get; set; } = DateTime.Now.TimeOfDay.Add(TimeSpan.FromHours(1));
20+
public ObservableCollection<Reminder> EventReminders { get; set; } = [];
2121
public bool EventIsAllDay { get; set; }
22+
public bool EventHasReminder { get; set; }
2223

2324
public AddEventsPage(ICalendarStore calendarStore, CalendarEvent? eventToUpdate)
2425
{
@@ -52,10 +53,15 @@ protected override async void OnNavigatedTo(NavigatedToEventArgs args)
5253
EventEndDate = eventToUpdate.EndDate.LocalDateTime;
5354
EventEndTime = eventToUpdate.EndDate.LocalDateTime.TimeOfDay;
5455
EventIsAllDay = eventToUpdate.IsAllDay;
55-
56+
EventReminders = new ObservableCollection<Reminder>(eventToUpdate.Reminders);
5657
SelectedCalendar = Calendars
5758
.Where(c => c.Id.Equals(eventToUpdate.CalendarId)).Single();
5859

60+
if (EventHasReminder)
61+
{
62+
IsAllDaySection.IsVisible = false;
63+
}
64+
5965
OnPropertyChanged(nameof(EventTitle));
6066
OnPropertyChanged(nameof(EventDescription));
6167
OnPropertyChanged(nameof(EventLocation));
@@ -66,6 +72,8 @@ protected override async void OnNavigatedTo(NavigatedToEventArgs args)
6672
OnPropertyChanged(nameof(EventIsAllDay));
6773
OnPropertyChanged(nameof(SelectedCalendar));
6874
OnPropertyChanged(nameof(IsCreateAction));
75+
OnPropertyChanged(nameof(EventHasReminder));
76+
OnPropertyChanged(nameof(EventReminders));
6977

7078
Title = "Edit Event";
7179
}
@@ -101,32 +109,59 @@ async void Save_Clicked(object sender, EventArgs e)
101109
if (eventToUpdate is not null)
102110
{
103111
await calendarStore.UpdateEvent(eventToUpdate.Id, EventTitle, EventDescription,
104-
EventLocation, startDateTime, startEndDateTime, EventIsAllDay);
112+
EventLocation, startDateTime, startEndDateTime, EventIsAllDay, EventReminders.ToArray());
105113

106-
await DisplayAlert("Event saved", $"The event has been successfully updated!", "OK");
114+
await DisplayAlert("Event saved", "The event has been successfully updated!", "OK");
107115
}
108116
else
109117
{
110-
// We could also not use this overload and just provide EventIsAllDay as a parameter
111-
if (EventIsAllDay)
112-
{
113-
savedEventId = await calendarStore.CreateAllDayEvent(SelectedCalendar.Id, EventTitle,
114-
EventDescription, EventLocation, startDateTime, startEndDateTime);
115-
}
116-
else
117-
{
118-
savedEventId = await calendarStore.CreateEvent(SelectedCalendar.Id, EventTitle,
119-
EventDescription, EventLocation, startDateTime, startEndDateTime);
120-
}
118+
savedEventId = await calendarStore.CreateEvent(SelectedCalendar.Id, EventTitle,
119+
EventDescription, EventLocation, startDateTime, startEndDateTime, EventIsAllDay, EventReminders.ToArray());
120+
121121

122122
await DisplayAlert("Event saved", $"The event has been successfully saved with ID: {savedEventId}!", "OK");
123123
}
124124

125125
await Shell.Current.Navigation.PopAsync();
126126
}
127-
catch(Exception ex)
127+
catch (Exception ex)
128128
{
129129
await DisplayAlert("Error", ex.Message, "OK");
130130
}
131131
}
132+
133+
public void AddReminderBtn_Clicked(object sender, EventArgs e)
134+
{
135+
if (OperatingSystem.IsWindows() && EventReminders.Count == 1)
136+
{
137+
DisplayAlert("Error", "Windows only supports 1 reminder per event.", "OK");
138+
return;
139+
}
140+
141+
if (EventStartTime.CompareTo(ReminderTime.Time) <= 0)
142+
{
143+
DisplayAlert("Error", "Reminder time has to be earlier than event start time.", "OK");
144+
return;
145+
}
146+
147+
var reminderDateTime = new DateTime(EventStartDate.Year, EventStartDate.Month, EventStartDate.Day,
148+
ReminderTime.Time.Hours, ReminderTime.Time.Minutes, ReminderTime.Time.Seconds);
149+
150+
Reminder newReminder = new(reminderDateTime);
151+
152+
EventReminders.Add(newReminder);
153+
}
154+
155+
public void DltReminderBtn_Clicked(object sender, EventArgs e)
156+
{
157+
var send = (Button)sender;
158+
var _reminder = (Reminder)send.BindingContext;
159+
EventReminders.Remove(_reminder);
160+
OnPropertyChanged(nameof(EventReminders));
161+
}
162+
163+
void ClearRemindersButton_Clicked(object sender, EventArgs e)
164+
{
165+
EventReminders.Clear();
166+
}
132167
}

samples/Plugin.Maui.CalendarStore.Sample/EventsPage.xaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
<VerticalStackLayout BindableLayout.ItemsSource="{Binding Events}" Spacing="5">
1919
<BindableLayout.ItemTemplate>
2020
<DataTemplate x:DataType="calendar:CalendarEvent">
21-
<Grid ColumnDefinitions="*, 100">
22-
<VerticalStackLayout Grid.Column="0">
21+
<Grid ColumnDefinitions="*,100" RowDefinitions="Auto,*">
22+
<VerticalStackLayout Grid.Column="0" Grid.Row="0">
2323
<Label Text="{Binding Title, StringFormat='Title: {0}'}" FontSize="25" />
2424
<Label Text="{Binding Id, StringFormat='ID: {0}'}" />
2525
<Label Text="{Binding CalendarId, StringFormat='Calendar ID: {0}'}" />
@@ -28,9 +28,10 @@
2828
<Label Text="{Binding StartDate, StringFormat='Start Date: {0}'}" />
2929
<Label Text="{Binding EndDate, StringFormat='End Date: {0}'}" />
3030
<Label Text="{Binding Duration, StringFormat='Duration: {0}'}" />
31+
<Label Text="{Binding Reminders.Count, StringFormat='Reminders: {0}'}" />
3132
</VerticalStackLayout>
32-
33-
<VerticalStackLayout Grid.Column="1" Spacing="5">
33+
34+
<VerticalStackLayout Grid.Column="2" Spacing="5" Grid.Row="0">
3435
<Button Text="Update"
3536
Clicked="Update_Clicked"
3637
VerticalOptions="Center"/>

samples/Plugin.Maui.CalendarStore.Sample/EventsPage.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async void Delete_Clicked(object sender, EventArgs e)
7878

7979
async Task LoadEvents()
8080
{
81-
var events = await CalendarStore.Default
81+
IEnumerable<CalendarEvent>? events = await CalendarStore.Default
8282
.GetEvents(startDate: DateTimeOffset.Now.AddDays(-7),
8383
endDate: DateTimeOffset.Now.AddDays(7));
8484

src/Plugin.Maui.CalendarStore/Calendar.shared.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ public class Calendar
1515
/// <param name="color">The color associated with this calendar.</param>
1616
/// <param name="isReadOnly">Indicates whether this calendar is read-only.</param>
1717
public Calendar(string id, string name, Color color, bool isReadOnly)
18-
{
19-
Id = id;
20-
Name = name;
18+
{
19+
Id = id;
20+
Name = name;
2121
Color = color;
2222
IsReadOnly = isReadOnly;
23-
}
23+
}
2424

2525
/// <summary>
2626
/// Gets unique identifier for this calendar.

src/Plugin.Maui.CalendarStore/CalendarEvent.shared.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public class CalendarEvent
1212
/// <param name="calendarId">The unique identifier for the calendar this event is part of.</param>
1313
/// <param name="title">The title for this event.</param>
1414
public CalendarEvent(string id, string calendarId, string title)
15-
{
16-
Id = id;
17-
CalendarId = calendarId;
18-
Title = title;
19-
}
15+
{
16+
Id = id;
17+
CalendarId = calendarId;
18+
Title = title;
19+
}
2020

2121
/// <summary>
2222
/// Gets the unique identifier for this event.
@@ -46,26 +46,33 @@ public CalendarEvent(string id, string calendarId, string title)
4646
/// <summary>
4747
/// Gets whether this event is marked as an all-day event.
4848
/// </summary>
49-
public bool IsAllDay { get; internal set; }
49+
public bool IsAllDay { get; internal set; }
5050

5151
/// <summary>
5252
/// Gets the start date and time for this event.
5353
/// </summary>
54-
public DateTimeOffset StartDate { get; internal set; }
54+
public DateTimeOffset StartDate { get; internal set; }
5555

5656
/// <summary>
5757
/// Gets the end date and time for this event.
5858
/// </summary>
59-
public DateTimeOffset EndDate { get; internal set; }
59+
public DateTimeOffset EndDate { get; internal set; }
6060

6161
/// <summary>
6262
/// Gets the total duration for this event.
6363
/// </summary>
64-
public TimeSpan Duration => EndDate - StartDate;
64+
public TimeSpan Duration => EndDate - StartDate;
65+
66+
/// <summary>
67+
/// Gets the list of reminders for this event.
68+
/// </summary>
69+
/// <remarks>
70+
/// On Windows only 1 reminder is supported. Therefore on Windows, this collection will always only contain 1 item.
71+
/// </remarks>
72+
public List<Reminder> Reminders { get; internal set; } = [];
6573

6674
/// <summary>
6775
/// Gets the list of attendees for this event.
6876
/// </summary>
69-
public IEnumerable<CalendarEventAttendee> Attendees { get; internal set; }
70-
= new List<CalendarEventAttendee>();
77+
public IEnumerable<CalendarEventAttendee> Attendees { get; internal set; } = [];
7178
}

0 commit comments

Comments
 (0)