From 9cf6e7822fec1aaf4e20e345a68da415f3c11e2e Mon Sep 17 00:00:00 2001 From: Scott Young <57081063+ScottBTR@users.noreply.github.com> Date: Tue, 29 Sep 2020 08:30:44 +1000 Subject: [PATCH] Write support for calendars * Squash Merge (#72) * #67 Use int.TryParse instead of Convert.ToInt32 * #59 rename extensions to more accurately represent their purpose * #61 replace usages of .Count == 0 with !.Any() * #62 rename cursor to something more meaningful * #66 Remove not needed Deleted flag from create+update event Also don't try to set up a null event. * #68 - Avoid abbreviations where possible * whoops, missed a change. * Changes adhere to PR feedback * PR Feedback - Replace strings with const values for ease of readability and to remove confusion around + numbers * PR Feedback allow recurrence rules to be removed for existing events. * #87 ios: delete calendar event instance * #82 drop down works for setting check boxes, but need to work out other way. * Undo permission testing code * Meeting PR Feedback * Make CalendarList readonly * Rename TimeSpanExtensions more accurately --> remove unnessacary extension RoundToNearestMinutes * Started Converting to bit flag * Update to get checkboxes functioning using bitflag * Alter according to PR requirements * CalendarDayOfWeek enum/bitflag working with UI and each platform * #89 Added Ability to SetEventRecurrenceEndDate which allows stopping event recurrences going from a specific date (end recurrence rule from date) * Remove blank line causing error. * Alter Recurrences TotalOccurrences to be null so that end date dictates the rule changes * Alter strings to use consts for consistency/readability * Altered picker to be 4 checkboxes (with custom no longer being an option) Also merged my two enums into one using condition values to determine results. * Alter as per PR feedback * Remove un-necessary using statement * Altered to PR feedback * Altered to PR feedback * Convert RecurrenceDays to readonly property * Alter ForLoop to use bitflag enum checks rather than relying on int bit logic override * Remove IsNone, IsWeekday, IsWeekly, IsAllDays --> Converter * Alter IsDaily || Is Weekly || IsMonthlyOrYearly || IsYearly --> RecurrenceTypeBoolConverter * Reoganize file and remove redundancies * missed merge change. * Resolve conflicts. * Prevent android from breaking onappearing for calendarpage Co-authored-by: Nick Randolph --- .../Properties/AndroidManifest.xml | 3 +- .../DeviceTests.Shared/Calendar_Tests.cs | 390 +++++++++ .../Properties/AndroidManifest.xml | 2 +- .../AttendeeRequiredColorConverter.cs | 32 + .../RecurrenceDayToBoolConverter.cs | 33 + .../RecurrenceEndTypeToBoolConverter.cs | 32 + .../Converters/RecurrenceRuleTextConverter.cs | 86 ++ .../RecurrenceTypeToBoolConverter.cs | 45 + .../RecurrenceUntilTypeToBoolConverter.cs | 32 + .../Converters/ReminderTextConverter.cs | 19 + .../Converters/StartWidthDisplayConverter.cs | 20 + Samples/Samples/View/CalendarAddPage.xaml | 31 + Samples/Samples/View/CalendarAddPage.xaml.cs | 10 + .../Samples/View/CalendarEventAddPage.xaml | 323 +++++++ .../Samples/View/CalendarEventAddPage.xaml.cs | 15 + .../View/CalendarEventAttendeeAddPage.xaml | 66 ++ .../View/CalendarEventAttendeeAddPage.xaml.cs | 15 + Samples/Samples/View/CalendarEventPage.xaml | 254 +++++- .../Samples/View/CalendarEventPage.xaml.cs | 133 +++ Samples/Samples/View/CalendarPage.xaml | 231 +++-- Samples/Samples/View/CalendarPage.xaml.cs | 42 +- .../Samples/ViewModel/CalendarAddViewModel.cs | 50 ++ .../CalendarEventAddAttendeeViewModel.cs | 99 +++ .../ViewModel/CalendarEventAddViewModel.cs | 615 ++++++++++++++ .../Samples/ViewModel/CalendarViewModel.cs | 202 +++-- .../Calendars/CalendarRequest.uwp.cs | 25 + .../Calendars/Calendars.android.cs | 789 +++++++++++++++--- Xamarin.Essentials/Calendars/Calendars.ios.cs | 536 ++++++++++++ ...alendars.netstandard.tvos.watchos.tizen.cs | 28 +- .../Calendars/Calendars.shared.cs | 49 +- Xamarin.Essentials/Calendars/Calendars.uwp.cs | 560 +++++++++++-- .../Permissions/Permissions.ios.cs | 14 + Xamarin.Essentials/Types/Calendar.shared.cs | 159 +++- .../CalendarExtensions.ios.cs | 14 + .../NumberExtensions.shared.cs | 21 + 35 files changed, 4540 insertions(+), 435 deletions(-) create mode 100644 Samples/Samples/Converters/AttendeeRequiredColorConverter.cs create mode 100644 Samples/Samples/Converters/RecurrenceDayToBoolConverter.cs create mode 100644 Samples/Samples/Converters/RecurrenceEndTypeToBoolConverter.cs create mode 100644 Samples/Samples/Converters/RecurrenceRuleTextConverter.cs create mode 100644 Samples/Samples/Converters/RecurrenceTypeToBoolConverter.cs create mode 100644 Samples/Samples/Converters/RecurrenceUntilTypeToBoolConverter.cs create mode 100644 Samples/Samples/Converters/ReminderTextConverter.cs create mode 100644 Samples/Samples/Converters/StartWidthDisplayConverter.cs create mode 100644 Samples/Samples/View/CalendarAddPage.xaml create mode 100644 Samples/Samples/View/CalendarAddPage.xaml.cs create mode 100644 Samples/Samples/View/CalendarEventAddPage.xaml create mode 100644 Samples/Samples/View/CalendarEventAddPage.xaml.cs create mode 100644 Samples/Samples/View/CalendarEventAttendeeAddPage.xaml create mode 100644 Samples/Samples/View/CalendarEventAttendeeAddPage.xaml.cs create mode 100644 Samples/Samples/ViewModel/CalendarAddViewModel.cs create mode 100644 Samples/Samples/ViewModel/CalendarEventAddAttendeeViewModel.cs create mode 100644 Samples/Samples/ViewModel/CalendarEventAddViewModel.cs create mode 100644 Xamarin.Essentials/Calendars/CalendarRequest.uwp.cs create mode 100644 Xamarin.Essentials/Calendars/Calendars.ios.cs create mode 100644 Xamarin.Essentials/Types/PlatformExtensions/CalendarExtensions.ios.cs create mode 100644 Xamarin.Essentials/Types/PlatformExtensions/NumberExtensions.shared.cs diff --git a/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml b/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml index 8c38e08a0..61b1737c9 100644 --- a/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml +++ b/DeviceTests/DeviceTests.Android/Properties/AndroidManifest.xml @@ -5,7 +5,8 @@ - + + diff --git a/DeviceTests/DeviceTests.Shared/Calendar_Tests.cs b/DeviceTests/DeviceTests.Shared/Calendar_Tests.cs index c60511927..54671ca8d 100644 --- a/DeviceTests/DeviceTests.Shared/Calendar_Tests.cs +++ b/DeviceTests/DeviceTests.Shared/Calendar_Tests.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Xamarin.Essentials; using Xunit; @@ -99,5 +102,392 @@ public Task Get_Event_By_Bad_Text_Id(string eventId) await Assert.ThrowsAsync(() => Calendars.GetEventAsync(eventId)); }); } + + [Fact] + public Task Full_Calendar_Edit_Test() + { + return Utils.OnMainThread(async () => + { + // Create Calendar + var calendars = await Calendars.GetCalendarsAsync(); + var calendar = calendars.FirstOrDefault(x => x.Name == "Test_Calendar"); + var calendarId = string.Empty; + if (calendar == null) + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + calendarId = await Calendars.CreateCalendar(newCalendar); + } + else + { + calendarId = calendar.Id; + } + + var startDate = TimeZoneInfo.ConvertTime(new DateTimeOffset(2019, 4, 1, 10, 30, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var events = await Calendars.GetEventsAsync(calendarId, startDate, startDate.AddHours(10)); + var newEvent = events.FirstOrDefault(x => x.Title == "Test_Event"); + var eventId = string.Empty; + if (newEvent == null) + { + newEvent = new CalendarEvent() + { + Title = "Test_Event", + CalendarId = calendarId, + StartDate = startDate, + EndDate = startDate.AddHours(10) + }; + eventId = await Calendars.CreateCalendarEvent(newEvent); + } + else + { + eventId = newEvent.Id; + } + Assert.NotEmpty(eventId); + var createdEvent = await Calendars.GetEventByIdAsync(eventId); + newEvent.Id = createdEvent.Id; + newEvent.Attendees = createdEvent.Attendees; + + Assert.Equal(newEvent.Id, createdEvent.Id); + Assert.Equal(newEvent.CalendarId, createdEvent.CalendarId); + Assert.Equal(newEvent.Title, createdEvent.Title); + Assert.Equal(string.Empty, createdEvent.Description); + Assert.Equal(string.Empty, createdEvent.Location); + Assert.Equal(string.Empty, createdEvent.Url); + Assert.Equal(newEvent.AllDay, createdEvent.AllDay); + Assert.Equal(newEvent.StartDate, createdEvent.StartDate); + Assert.Equal(newEvent.Duration, createdEvent.Duration); + Assert.Equal(newEvent.EndDate, createdEvent.EndDate); + Assert.Equal(newEvent.Attendees, createdEvent.Attendees); + Assert.Equal(newEvent.Reminders, createdEvent.Reminders); + Assert.Equal(newEvent.RecurrancePattern, createdEvent.RecurrancePattern); + + createdEvent.RecurrancePattern = new RecurrenceRule() + { + Frequency = RecurrenceFrequency.YearlyOnDay, + Interval = 1, + WeekOfMonth = IterationOffset.Second, + DaysOfTheWeek = new List() { CalendarDayOfWeek.Thursday }, + MonthOfTheYear = MonthOfYear.April, + TotalOccurrences = 4 + }; + createdEvent.AllDay = true; + + var updateSuccessful = await Calendars.UpdateCalendarEvent(createdEvent); + var updatedEvent = await Calendars.GetEventByIdAsync(createdEvent.Id); + + // Updated Successfuly + Assert.True(updateSuccessful); + Assert.Equal(createdEvent.Id, updatedEvent.Id); + Assert.Equal(createdEvent.CalendarId, updatedEvent.CalendarId); + Assert.Equal(createdEvent.Title, updatedEvent.Title); + Assert.Equal(createdEvent.Description, updatedEvent.Description); + Assert.Equal(createdEvent.Location, updatedEvent.Location); + Assert.Equal(createdEvent.Url, updatedEvent.Url); + Assert.Equal(createdEvent.AllDay, updatedEvent.AllDay); + Assert.NotEqual(createdEvent.StartDate, updatedEvent.StartDate); + Assert.Equal(createdEvent.Attendees, updatedEvent.Attendees); + Assert.Equal(createdEvent.Reminders, updatedEvent.Reminders); + + var attendeeToAddAndRemove = new CalendarEventAttendee() { Email = "fake@email.com", Name = "Fake Email", Type = AttendeeType.Resource }; + + // Added Attendee to event successfully + var attendeeAddedSuccessfully = await Calendars.AddAttendeeToEvent(attendeeToAddAndRemove, updatedEvent.Id); + Assert.True(attendeeAddedSuccessfully); + + // Verify Attendee added to event + updatedEvent = await Calendars.GetEventByIdAsync(createdEvent.Id); + var expectedAttendeeCount = createdEvent.Attendees != null ? createdEvent.Attendees.Count() + 1 : 1; + Assert.Equal(updatedEvent.Attendees.Count(), expectedAttendeeCount); + + // Remove Attendee from event + var removedAttendeeSuccessfully = await Calendars.RemoveAttendeeFromEvent(attendeeToAddAndRemove, updatedEvent.Id); + Assert.True(removedAttendeeSuccessfully); + + var dateOfSecondOccurence = TimeZoneInfo.ConvertTime(new DateTimeOffset(2020, 4, 9, 0, 0, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var eventInstance = await Calendars.GetEventInstanceByIdAsync(updatedEvent.Id, dateOfSecondOccurence); + + // Retrieve instance of event + Assert.Equal(eventInstance.Id, updatedEvent.Id); + Assert.Equal(eventInstance.StartDate.Date, dateOfSecondOccurence.Date); + + // Delete instance of event + var canDeleteInstance = await Calendars.DeleteCalendarEventInstanceByDate(eventInstance.Id, calendarId, eventInstance.StartDate); + Assert.True(canDeleteInstance); + + // Get whole event + var eventStillExists = await Calendars.GetEventByIdAsync(eventInstance.Id); + Assert.NotNull(eventStillExists); + + // Delete whole event + var deleteEvent = await Calendars.DeleteCalendarEventById(eventInstance.Id, calendarId); + Assert.True(deleteEvent); + }); + } + + [Fact] + public Task Basic_Calendar_Creation() + { + return Utils.OnMainThread(async () => + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + var calendarId = await Calendars.CreateCalendar(newCalendar); + Assert.NotEmpty(calendarId); + }); + } + + [Fact] + public Task Basic_Calendar_Event_Creation() + { + return Utils.OnMainThread(async () => + { + var calendars = await Calendars.GetCalendarsAsync(); + var calendar = calendars.FirstOrDefault(x => x.Name == "Test_Calendar"); + var calendarId = string.Empty; + if (calendar == null) + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + calendarId = await Calendars.CreateCalendar(newCalendar); + } + else + { + calendarId = calendar.Id; + } + + var startDate = TimeZoneInfo.ConvertTime(new DateTimeOffset(2019, 4, 1, 10, 30, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var newEvent = new CalendarEvent() + { + Title = "Test_Event", + CalendarId = calendarId, + StartDate = startDate, + EndDate = startDate.AddHours(10) + }; + var eventId = await Calendars.CreateCalendarEvent(newEvent); + Assert.NotEmpty(eventId); + }); + } + + [Fact] + public Task Basic_Calendar_Event_Attendee_Add() + { + return Utils.OnMainThread(async () => + { + var calendars = await Calendars.GetCalendarsAsync(); + var calendar = calendars.FirstOrDefault(x => x.Name == "Test_Calendar"); + var calendarId = string.Empty; + if (calendar == null) + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + calendarId = await Calendars.CreateCalendar(newCalendar); + } + else + { + calendarId = calendar.Id; + } + + var startDate = TimeZoneInfo.ConvertTime(new DateTimeOffset(2019, 4, 1, 10, 30, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var events = await Calendars.GetEventsAsync(calendarId, startDate, startDate.AddHours(10)); + var newEvent = events.FirstOrDefault(x => x.Title == "Test_Event"); + var eventId = string.Empty; + if (newEvent == null) + { + newEvent = new CalendarEvent() + { + Title = "Test_Event", + CalendarId = calendarId, + StartDate = startDate, + EndDate = startDate.AddHours(10) + }; + eventId = await Calendars.CreateCalendarEvent(newEvent); + } + else + { + eventId = newEvent.Id; + } + var attendeeToAdd = new CalendarEventAttendee() { Email = "fake@email.com", Name = "Fake Out", Type = AttendeeType.Required }; + Assert.True(await Calendars.AddAttendeeToEvent(attendeeToAdd, eventId)); + + newEvent = await Calendars.GetEventByIdAsync(eventId); + var attendee = newEvent.Attendees.FirstOrDefault(x => x.Email == "fake@email.com"); + + Assert.Equal(attendee.Email, attendeeToAdd.Email); + Assert.Equal(attendee.Name, attendeeToAdd.Name); + Assert.Equal(attendee.IsOrganizer, attendeeToAdd.IsOrganizer); + Assert.Equal(attendee.Type, attendeeToAdd.Type); + }); + } + + [Fact] + public Task Basic_Calendar_Event_Attendee_Remove() + { + return Utils.OnMainThread(async () => + { + var calendars = await Calendars.GetCalendarsAsync(); + var calendar = calendars.FirstOrDefault(x => x.Name == "Test_Calendar"); + var calendarId = string.Empty; + if (calendar == null) + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + calendarId = await Calendars.CreateCalendar(newCalendar); + } + else + { + calendarId = calendar.Id; + } + + var startDate = TimeZoneInfo.ConvertTime(new DateTimeOffset(2019, 4, 1, 10, 30, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var events = await Calendars.GetEventsAsync(calendarId, startDate, startDate.AddHours(10)); + var newEvent = events.FirstOrDefault(x => x.Title == "Test_Event"); + var eventId = string.Empty; + if (newEvent == null) + { + newEvent = new CalendarEvent() + { + Title = "Test_Event", + CalendarId = calendarId, + StartDate = startDate, + EndDate = startDate.AddHours(10) + }; + eventId = await Calendars.CreateCalendarEvent(newEvent); + } + else + { + eventId = newEvent.Id; + } + + var attendeeCount = 0; + CalendarEventAttendee attendeeToAdd = null; + CalendarEventAttendee attendee = null; + if (newEvent.Attendees != null) + { + if (newEvent.Attendees.Count() > 0) + { + attendeeToAdd = newEvent.Attendees.FirstOrDefault(x => x.Email == "fake@email.com"); + if (attendeeToAdd != null) + { + attendeeCount = newEvent.Attendees.Count(); + attendee = attendeeToAdd; + } + else + { + attendeeToAdd = new CalendarEventAttendee() { Email = "fake@email.com", Name = "Fake Out", Type = AttendeeType.Required }; + Assert.True(await Calendars.AddAttendeeToEvent(attendeeToAdd, eventId)); + newEvent = await Calendars.GetEventByIdAsync(eventId); + attendeeCount = newEvent.Attendees.Count(); + attendee = newEvent.Attendees.FirstOrDefault(x => x.Email == "fake@email.com"); + + Assert.Equal(attendee.Email, attendeeToAdd.Email); + Assert.Equal(attendee.Name, attendeeToAdd.Name); + Assert.Equal(attendee.IsOrganizer, attendeeToAdd.IsOrganizer); + Assert.Equal(attendee.Type, attendeeToAdd.Type); + } + } + } + else + { + attendeeToAdd = new CalendarEventAttendee() { Email = "fake@email.com", Name = "Fake Out", Type = AttendeeType.Required }; + Assert.True(await Calendars.AddAttendeeToEvent(attendeeToAdd, eventId)); + newEvent = await Calendars.GetEventByIdAsync(eventId); + attendeeCount = newEvent.Attendees.Count(); + attendee = newEvent.Attendees.FirstOrDefault(x => x.Email == "fake@email.com"); + + Assert.Equal(attendee.Email, attendeeToAdd.Email); + Assert.Equal(attendee.Name, attendeeToAdd.Name); + Assert.Equal(attendee.IsOrganizer, attendeeToAdd.IsOrganizer); + Assert.Equal(attendee.Type, attendeeToAdd.Type); + } + Assert.True(await Calendars.RemoveAttendeeFromEvent(attendee, eventId)); + newEvent = await Calendars.GetEventByIdAsync(eventId); + var newAttendeeCount = newEvent.Attendees.Count(); + + Assert.Equal(attendeeCount - 1, newAttendeeCount); + }); + } + + [Fact] + public Task Basic_Calendar_Event_Update() + { + return Utils.OnMainThread(async () => + { + var calendars = await Calendars.GetCalendarsAsync(); + var calendar = calendars.FirstOrDefault(x => x.Name == "Test_Calendar"); + var calendarId = string.Empty; + if (calendar == null) + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + calendarId = await Calendars.CreateCalendar(newCalendar); + } + else + { + calendarId = calendar.Id; + } + + var startDate = TimeZoneInfo.ConvertTime(new DateTimeOffset(2019, 4, 1, 10, 30, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var events = await Calendars.GetEventsAsync(calendarId, startDate, startDate.AddHours(10)); + var newEvent = events.FirstOrDefault(x => x.Title == "Test_Event"); + if (newEvent == null) + { + newEvent = new CalendarEvent() + { + Title = "Test_Event", + CalendarId = calendarId, + StartDate = startDate, + EndDate = startDate.AddHours(10) + }; + var eventId = await Calendars.CreateCalendarEvent(newEvent); + newEvent = await Calendars.GetEventByIdAsync(eventId); + } + else + { + newEvent = await Calendars.GetEventByIdAsync(newEvent.Id); + } + + newEvent.AllDay = true; + + var result = await Calendars.UpdateCalendarEvent(newEvent); + Assert.True(result); + }); + } + + [Fact] + public Task Basic_Calendar_Event_Deletion() + { + return Utils.OnMainThread(async () => + { + var calendars = await Calendars.GetCalendarsAsync(); + var calendar = calendars.FirstOrDefault(x => x.Name == "Test_Calendar"); + var calendarId = string.Empty; + if (calendar == null) + { + var newCalendar = new Calendar() { Name = "Test_Calendar" }; + calendarId = await Calendars.CreateCalendar(newCalendar); + } + else + { + calendarId = calendar.Id; + } + + var startDate = TimeZoneInfo.ConvertTime(new DateTimeOffset(2019, 4, 1, 10, 30, 0, TimeZoneInfo.Local.BaseUtcOffset), TimeZoneInfo.Local); + var events = await Calendars.GetEventsAsync(calendarId, startDate, startDate.AddHours(10)); + var newEvent = events.FirstOrDefault(x => x.Title == "Test_Event"); + var eventId = string.Empty; + if (newEvent == null) + { + newEvent = new CalendarEvent() + { + Title = "Test_Event", + CalendarId = calendarId, + StartDate = startDate, + EndDate = startDate.AddHours(10) + }; + eventId = await Calendars.CreateCalendarEvent(newEvent); + } + else + { + eventId = newEvent.Id; + } + var result = await Calendars.DeleteCalendarEventById(eventId, calendarId); + + Assert.True(result); + }); + } } } diff --git a/Samples/Samples.Android/Properties/AndroidManifest.xml b/Samples/Samples.Android/Properties/AndroidManifest.xml index b5127354e..9966edeff 100644 --- a/Samples/Samples.Android/Properties/AndroidManifest.xml +++ b/Samples/Samples.Android/Properties/AndroidManifest.xml @@ -12,7 +12,7 @@ - + diff --git a/Samples/Samples/Converters/AttendeeRequiredColorConverter.cs b/Samples/Samples/Converters/AttendeeRequiredColorConverter.cs new file mode 100644 index 000000000..2031479b8 --- /dev/null +++ b/Samples/Samples/Converters/AttendeeRequiredColorConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class AttendeeRequiredColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!(value is AttendeeType attendeeType)) + { + return Color.PaleVioletRed; + } + + switch (attendeeType) + { + case AttendeeType.Required: + return Color.LightGoldenrodYellow; + case AttendeeType.Resource: + return Color.PaleGreen; + default: + return Color.LightGray; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); + } +} diff --git a/Samples/Samples/Converters/RecurrenceDayToBoolConverter.cs b/Samples/Samples/Converters/RecurrenceDayToBoolConverter.cs new file mode 100644 index 000000000..8e53f2548 --- /dev/null +++ b/Samples/Samples/Converters/RecurrenceDayToBoolConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using Samples.ViewModel; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class RecurrenceDayToBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is CalendarDayOfWeek recurrenceDays) || !(parameter is CalendarDayOfWeek expectedResult)) + { + return false; + } + + return recurrenceDays == expectedResult; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is bool switchValue) || !(parameter is CalendarDayOfWeek expectedResult) || switchValue == false) + { + return null; + } + + return expectedResult; + } + } +} diff --git a/Samples/Samples/Converters/RecurrenceEndTypeToBoolConverter.cs b/Samples/Samples/Converters/RecurrenceEndTypeToBoolConverter.cs new file mode 100644 index 000000000..abb1611e3 --- /dev/null +++ b/Samples/Samples/Converters/RecurrenceEndTypeToBoolConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using Samples.ViewModel; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class RecurrenceEndTypeToBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is string type)) + { + return false; + } + switch (type) + { + case RecurrenceEndType.AfterOccurences: + return true; + case RecurrenceEndType.UntilEndDate: + case RecurrenceEndType.Indefinitely: + default: + return false; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Samples/Samples/Converters/RecurrenceRuleTextConverter.cs b/Samples/Samples/Converters/RecurrenceRuleTextConverter.cs new file mode 100644 index 000000000..140069843 --- /dev/null +++ b/Samples/Samples/Converters/RecurrenceRuleTextConverter.cs @@ -0,0 +1,86 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class RecurrenceRuleTextConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is RecurrenceRule rule)) + return null; + + var toReturn = $"Occurs "; + + if (rule.Interval > 0) + { + if (rule.Interval == 1) + { + toReturn += $"Every "; + } + else + { + toReturn += $"Every {((int)rule.Interval).ToOrdinal()} "; + } + switch (rule.Frequency) + { + case RecurrenceFrequency.Daily: + toReturn += "Day "; + break; + case RecurrenceFrequency.Weekly: + toReturn += "Week "; + break; + case RecurrenceFrequency.Monthly: + case RecurrenceFrequency.MonthlyOnDay: + toReturn += "Month "; + break; + case RecurrenceFrequency.Yearly: + case RecurrenceFrequency.YearlyOnDay: + toReturn += "Year "; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + if (rule.WeekOfMonth != null && (rule.Frequency == RecurrenceFrequency.MonthlyOnDay || rule.Frequency == RecurrenceFrequency.YearlyOnDay)) + { + toReturn += $"on the {rule.WeekOfMonth} "; + if (rule.DaysOfTheWeek?.Count > 0) + { + toReturn += $"["; + toReturn = rule.DaysOfTheWeek.Aggregate(toReturn, (current, d) => current + $"{d}, "); + toReturn = toReturn.Substring(0, toReturn.Length - 2) + "] "; + } + if (rule.Frequency == RecurrenceFrequency.YearlyOnDay) + { + toReturn += $"in {rule.MonthOfTheYear.ToString()} "; + } + } + else if (rule.DaysOfTheWeek?.Count > 0) + { + toReturn += $"On: ["; + toReturn = rule.DaysOfTheWeek.Aggregate(toReturn, (current, d) => current + $"{d}, "); + toReturn = toReturn.Substring(0, toReturn.Length - 2) + "] "; + } + + if (rule.TotalOccurrences > 0) + { + toReturn += $"For the next {rule.TotalOccurrences} occurrences "; + } + + if (rule.EndDate.HasValue) + { + toReturn += $"Until {rule.EndDate.Value.DateTime.ToShortDateString()} "; + } + + return toReturn; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); + } +} diff --git a/Samples/Samples/Converters/RecurrenceTypeToBoolConverter.cs b/Samples/Samples/Converters/RecurrenceTypeToBoolConverter.cs new file mode 100644 index 000000000..04350fe96 --- /dev/null +++ b/Samples/Samples/Converters/RecurrenceTypeToBoolConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using Samples.ViewModel; +using Xamarin.Essentials; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class RecurrenceTypeToBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is RecurrenceFrequency recurrenceType) || !(parameter is RecurrenceFrequency expectedResult)) + { + return false; + } + + switch (expectedResult) + { + case RecurrenceFrequency.Monthly: + return recurrenceType == RecurrenceFrequency.MonthlyOnDay || + recurrenceType == RecurrenceFrequency.Monthly || + recurrenceType == RecurrenceFrequency.YearlyOnDay || + recurrenceType == RecurrenceFrequency.Yearly; + case RecurrenceFrequency.Yearly: + return recurrenceType == RecurrenceFrequency.Yearly || + recurrenceType == RecurrenceFrequency.YearlyOnDay; + default: + return recurrenceType == expectedResult; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is bool switchValue) || !(parameter is RecurrenceFrequency expectedResult) || switchValue == false) + { + return false; + } + + return expectedResult; + } + } +} diff --git a/Samples/Samples/Converters/RecurrenceUntilTypeToBoolConverter.cs b/Samples/Samples/Converters/RecurrenceUntilTypeToBoolConverter.cs new file mode 100644 index 000000000..acb1fff4b --- /dev/null +++ b/Samples/Samples/Converters/RecurrenceUntilTypeToBoolConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using Samples.ViewModel; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class RecurrenceUntilTypeToBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is string type)) + { + return false; + } + switch (type) + { + case RecurrenceEndType.UntilEndDate: + return true; + case RecurrenceEndType.Indefinitely: + case RecurrenceEndType.AfterOccurences: + default: + return false; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Samples/Samples/Converters/ReminderTextConverter.cs b/Samples/Samples/Converters/ReminderTextConverter.cs new file mode 100644 index 000000000..57b7cb0c5 --- /dev/null +++ b/Samples/Samples/Converters/ReminderTextConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class ReminderTextConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is int minutes)) + return null; + + return minutes > 0 ? $"{minutes} minutes prior" : "No Reminders"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); + } +} diff --git a/Samples/Samples/Converters/StartWidthDisplayConverter.cs b/Samples/Samples/Converters/StartWidthDisplayConverter.cs new file mode 100644 index 000000000..1c2f3a92e --- /dev/null +++ b/Samples/Samples/Converters/StartWidthDisplayConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using Xamarin.Forms; + +namespace Samples.Converters +{ + public class StartWidthDisplayConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || !(value is bool b)) + { + return 1; + } + return b ? 3 : 1; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); + } +} diff --git a/Samples/Samples/View/CalendarAddPage.xaml b/Samples/Samples/View/CalendarAddPage.xaml new file mode 100644 index 000000000..0fab192ce --- /dev/null +++ b/Samples/Samples/View/CalendarAddPage.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + +