diff --git a/.gitignore b/.gitignore index 2f72fc4..3fb8294 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ build/ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +*.lock.json + *_i.c *_p.c *.ilk diff --git a/Calendars.sln b/Calendars.sln index 5c63dde..65e44ee 100644 --- a/Calendars.sln +++ b/Calendars.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Calendars.Plugin", "Calendars\Calendars.Plugin\Calendars.Plugin.csproj", "{A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}" EndProject @@ -104,6 +104,7 @@ Global {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|ARM.ActiveCfg = Debug|Any CPU {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|iPhone.Build.0 = Debug|Any CPU {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {A6FCEF44-D2BA-42C7-B3CB-13667BCD7B54}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -148,6 +149,7 @@ Global {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|ARM.ActiveCfg = Debug|Any CPU {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|iPhone.Build.0 = Debug|Any CPU {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {6EDB0588-FFC5-4EF5-8A99-9E241D0F878D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -191,6 +193,7 @@ Global {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|ARM.ActiveCfg = Debug|Any CPU {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|iPhone.Build.0 = Debug|Any CPU {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {D7F3AA16-8EF1-4924-9E0A-DAD4AB46B2EB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -233,6 +236,7 @@ Global {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|ARM.ActiveCfg = Debug|Any CPU {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|iPhone.Build.0 = Debug|Any CPU {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {2882AEEB-D4CD-4EB9-8A6C-6653B33681F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -273,6 +277,7 @@ Global {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Debug|iPhone.ActiveCfg = Debug|Any CPU {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Debug|x64.ActiveCfg = Debug|Any CPU {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Debug|x86.ActiveCfg = Debug|Any CPU {56A56F17-7DE1-4CA1-9617-BF32E971AC84}.Release|Android.ActiveCfg = Release|Any CPU @@ -717,6 +722,8 @@ Global {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|iPhone.ActiveCfg = Debug|Any CPU {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|x64.ActiveCfg = Debug|Any CPU {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Debug|x86.ActiveCfg = Debug|Any CPU {E6284EB0-BDB0-4D38-AFC4-ECDC7BFF6D6E}.Release|Android.ActiveCfg = Release|Any CPU diff --git a/Calendars/Calendars.Plugin.Abstractions/CalendarEvent.cs b/Calendars/Calendars.Plugin.Abstractions/CalendarEvent.cs index 0271f17..f7549c7 100644 --- a/Calendars/Calendars.Plugin.Abstractions/CalendarEvent.cs +++ b/Calendars/Calendars.Plugin.Abstractions/CalendarEvent.cs @@ -26,6 +26,11 @@ public class CalendarEvent /// public DateTime End { get; set; } + /// + /// Gets or sets the location of the event + /// + public string Location { get; set; } + /// /// Whether or not this is an "all-day" event. /// @@ -42,6 +47,7 @@ public class CalendarEvent /// /// This ID will be the same for each instance of a recurring event. public string ExternalID { get; set; } + /// /// Simple ToString helper, to assist with debugging. @@ -49,7 +55,7 @@ public class CalendarEvent /// public override string ToString() { - return "Name=" + Name + ", AllDay=" + AllDay + ", Start=" + Start + ", End=" + End; + return "Name=" + Name + ", AllDay=" + AllDay + ", Start=" + Start + ", End=" + End + ", Location=" + Location; } } } diff --git a/Calendars/Calendars.Plugin.Abstractions/CalendarEventReminder.cs b/Calendars/Calendars.Plugin.Abstractions/CalendarEventReminder.cs new file mode 100644 index 0000000..890e12e --- /dev/null +++ b/Calendars/Calendars.Plugin.Abstractions/CalendarEventReminder.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Calendars.Plugin.Abstractions +{ + /// + /// Calendar reminder that happens before the event such as an alert + /// + public class CalendarEventReminder + { + + /// + /// Amount of time to set the reminder before the start of an event. + /// Default is 15 minutes + /// + public TimeSpan TimeBefore { get; set; } = TimeSpan.FromMinutes(15); + /// + /// Type of reminder to display + /// + public CalendarReminderMethod Method { get; set; } = CalendarReminderMethod.Default; + + } + + /// + /// Types of methods of the reminder + /// + public enum CalendarReminderMethod + { + /// + /// Use system default + /// + Default, + /// + /// Pop up alert + /// + Alert, + /// + /// Send an email + /// + Email, + /// + /// Send an sms + /// + Sms + } +} diff --git a/Calendars/Calendars.Plugin.Abstractions/Calendars.Plugin.Abstractions.csproj b/Calendars/Calendars.Plugin.Abstractions/Calendars.Plugin.Abstractions.csproj index 469518f..75bc3e0 100644 --- a/Calendars/Calendars.Plugin.Abstractions/Calendars.Plugin.Abstractions.csproj +++ b/Calendars/Calendars.Plugin.Abstractions/Calendars.Plugin.Abstractions.csproj @@ -40,6 +40,7 @@ + diff --git a/Calendars/Calendars.Plugin.Abstractions/ICalendars.cs b/Calendars/Calendars.Plugin.Abstractions/ICalendars.cs index faf68b5..94d38bf 100644 --- a/Calendars/Calendars.Plugin.Abstractions/ICalendars.cs +++ b/Calendars/Calendars.Plugin.Abstractions/ICalendars.cs @@ -88,5 +88,15 @@ public interface ICalendars /// Calendar access denied /// Unexpected platform-specific error Task DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent); + + /// + /// Adds a reminder to the specified calendar event + /// + /// Event to add + /// Reminder to add + /// If successful + /// If calendar event is not created or not valid + /// Unexpected platform-specific error + Task AddEventReminderAsync(CalendarEvent calendarEvent, CalendarEventReminder reminder); } } diff --git a/Calendars/Calendars.Plugin.Android/CalendarsImplementation.cs b/Calendars/Calendars.Plugin.Android/CalendarsImplementation.cs index a3b70b3..ff4d996 100644 --- a/Calendars/Calendars.Plugin.Android/CalendarsImplementation.cs +++ b/Calendars/Calendars.Plugin.Android/CalendarsImplementation.cs @@ -177,6 +177,7 @@ public async Task> GetEventsAsync(Calendar calendar, DateTi CalendarContract.Events.InterfaceConsts.Dtstart, CalendarContract.Events.InterfaceConsts.Dtend, CalendarContract.Events.InterfaceConsts.AllDay, + CalendarContract.Events.InterfaceConsts.EventLocation, CalendarContract.Instances.EventId }; @@ -204,6 +205,7 @@ await Task.Run(() => Description = cursor.GetString(CalendarContract.Events.InterfaceConsts.Description), Start = cursor.GetDateTime(CalendarContract.Events.InterfaceConsts.Dtstart), End = cursor.GetDateTime(CalendarContract.Events.InterfaceConsts.Dtend), + Location = cursor.GetString(CalendarContract.Events.InterfaceConsts.EventLocation), AllDay = cursor.GetBoolean(CalendarContract.Events.InterfaceConsts.AllDay) }); } while (cursor.MoveToNext()); @@ -241,6 +243,7 @@ public Task GetEventByIdAsync(string externalId) CalendarContract.Events.InterfaceConsts.Description, CalendarContract.Events.InterfaceConsts.Dtstart, CalendarContract.Events.InterfaceConsts.Dtend, + CalendarContract.Events.InterfaceConsts.EventLocation, CalendarContract.Events.InterfaceConsts.AllDay }; @@ -262,6 +265,7 @@ public Task GetEventByIdAsync(string externalId) Description = cursor.GetString(CalendarContract.Events.InterfaceConsts.Description), Start = cursor.GetDateTime(CalendarContract.Events.InterfaceConsts.Dtstart), End = cursor.GetDateTime(CalendarContract.Events.InterfaceConsts.Dtend), + Location = cursor.GetString(CalendarContract.Events.InterfaceConsts.EventLocation), AllDay = cursor.GetBoolean(CalendarContract.Events.InterfaceConsts.AllDay) }; } @@ -333,6 +337,7 @@ public async Task AddOrUpdateCalendarAsync(Calendar calendar) values.Put(CalendarContract.Calendars.InterfaceConsts.AccountName, AccountName); values.Put(CalendarContract.Calendars.InterfaceConsts.OwnerAccount, OwnerAccount); values.Put(CalendarContract.Calendars.InterfaceConsts.Visible, true); + values.Put(CalendarContract.Calendars.InterfaceConsts.SyncEvents, true); values.Put(CalendarContract.Calendars.InterfaceConsts.AccountType, CalendarContract.AccountTypeLocal); } @@ -421,6 +426,8 @@ await Task.Run(() => DateConversions.GetDateAsAndroidMS(calendarEvent.End)); eventValues.Put(CalendarContract.Events.InterfaceConsts.AllDay, calendarEvent.AllDay); + eventValues.Put(CalendarContract.Events.InterfaceConsts.EventLocation, + calendarEvent.Location ?? string.Empty); eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone, Java.Util.TimeZone.Default.ID); @@ -435,7 +442,60 @@ await Task.Run(() => } }); } + + /// + /// Adds an event reminder to specified calendar event + /// + /// Event to add the reminder to + /// The reminder + /// Success or failure + /// If calendar event is not created or not valid + /// Unexpected platform-specific error + public async Task AddEventReminderAsync(CalendarEvent calendarEvent, CalendarEventReminder reminder) + { + if (string.IsNullOrEmpty(calendarEvent.ExternalID)) + { + throw new ArgumentException("Missing calendar event identifier", "calendarEvent"); + } + // Verify calendar event exists + var existingAppt = await GetEventByIdAsync(calendarEvent.ExternalID).ConfigureAwait(false); + + if (existingAppt == null) + { + throw new ArgumentException("Specified calendar event not found on device"); + } + return await Task.Run(() => + { + var reminderValues = new ContentValues(); + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Minutes, reminder?.TimeBefore.TotalMinutes ?? 15); + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.EventId, calendarEvent.ExternalID); + switch(reminder.Method) + { + case CalendarReminderMethod.Alert: + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Method, (int)RemindersMethod.Alert); + break; + case CalendarReminderMethod.Default: + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Method, (int)RemindersMethod.Default); + break; + case CalendarReminderMethod.Email: + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Method, (int)RemindersMethod.Email); + break; + case CalendarReminderMethod.Sms: + reminderValues.Put(CalendarContract.Reminders.InterfaceConsts.Method, (int)RemindersMethod.Sms); + break; + + } + var uri = CalendarContract.Reminders.ContentUri; + Insert(uri, reminderValues); + + + return true; + }); + + } + + /// /// Removes a calendar and all its events from the system. /// @@ -638,4 +698,4 @@ private static Exception TranslateException(Java.Lang.Exception ex) #endregion } -} \ No newline at end of file +} diff --git a/Calendars/Calendars.Plugin.WindowsPhoneSL81/AppointmentExtensions.cs b/Calendars/Calendars.Plugin.WindowsPhoneSL81/AppointmentExtensions.cs index dd66bee..c670d1e 100644 --- a/Calendars/Calendars.Plugin.WindowsPhoneSL81/AppointmentExtensions.cs +++ b/Calendars/Calendars.Plugin.WindowsPhoneSL81/AppointmentExtensions.cs @@ -27,6 +27,7 @@ public static CalendarEvent ToCalendarEvent(this Appointment appt) Start = appt.StartTime.LocalDateTime, End = appt.StartTime.Add(appt.Duration).LocalDateTime, AllDay = appt.AllDay, + Location = appt.Location, ExternalID = appt.LocalId }; } diff --git a/Calendars/Calendars.Plugin.WindowsPhoneSL81/CalendarsImplementation.cs b/Calendars/Calendars.Plugin.WindowsPhoneSL81/CalendarsImplementation.cs index afa8e00..848793b 100644 --- a/Calendars/Calendars.Plugin.WindowsPhoneSL81/CalendarsImplementation.cs +++ b/Calendars/Calendars.Plugin.WindowsPhoneSL81/CalendarsImplementation.cs @@ -111,6 +111,7 @@ public async Task> GetEventsAsync(Calendar calendar, DateTi options.FetchProperties.Add(AppointmentProperties.StartTime); options.FetchProperties.Add(AppointmentProperties.Duration); options.FetchProperties.Add(AppointmentProperties.AllDay); + options.FetchProperties.Add(AppointmentProperties.Location); var appointments = await deviceCalendar.FindAppointmentsAsync(start, end - start, options).ConfigureAwait(false); var events = appointments.Select(a => a.ToCalendarEvent()).ToList(); @@ -227,12 +228,52 @@ public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calenda appt.StartTime = calendarEvent.Start; appt.Duration = calendarEvent.End - calendarEvent.Start; appt.AllDay = calendarEvent.AllDay; + appt.Location = calendarEvent.Location ?? string.Empty; await appCalendar.SaveAppointmentAsync(appt); calendarEvent.ExternalID = appt.LocalId; } + /// + /// Adds an event reminder to specified calendar event + /// + /// Event to add the reminder to + /// The reminder + /// Success or failure + /// If calendar event is not created or not valid + /// Unexpected platform-specific error + public async Task AddEventReminderAsync(CalendarEvent calendarEvent, CalendarEventReminder reminder) + { + if (string.IsNullOrEmpty(calendarEvent.ExternalID)) + { + throw new ArgumentException("Missing calendar event identifier", "calendarEvent"); + } + + + var existingAppt = await _localApptStore.GetAppointmentAsync(calendarEvent.ExternalID); + + + if (existingAppt == null) + { + throw new ArgumentException("Specified calendar event not found on device"); + } + + + var appCalendar = await _localApptStore.GetAppointmentCalendarAsync(existingAppt.CalendarId); + + if(appCalendar == null) + { + throw new ArgumentException("Event does not have a valid calendar."); + } + + existingAppt.Reminder = reminder?.TimeBefore ?? TimeSpan.FromMinutes(15); + + await appCalendar.SaveAppointmentAsync(existingAppt); + + return true; + } + /// /// Removes a calendar and all its events from the system. /// @@ -335,9 +376,9 @@ public async Task DeleteEventAsync(Calendar calendar, CalendarEvent calend return deleted; } - #endregion +#endregion - #region Private Methods +#region Private Methods private async Task EnsureInitializedAsync() { @@ -422,4 +463,4 @@ private async Task GetLocalCalendarAsync(string id) #endregion } -} \ No newline at end of file +} diff --git a/Calendars/Calendars.Plugin.WindowsStore/CalendarsImplementation.cs b/Calendars/Calendars.Plugin.WindowsStore/CalendarsImplementation.cs index af80a0c..0091acb 100644 --- a/Calendars/Calendars.Plugin.WindowsStore/CalendarsImplementation.cs +++ b/Calendars/Calendars.Plugin.WindowsStore/CalendarsImplementation.cs @@ -72,7 +72,15 @@ public Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent /// /// Not supported for Windows Store apps /// - public Task DeleteCalendarAsync(Calendar calendar) + public Task AddEventReminderAsync(CalendarEvent calendarEvent, CalendarEventReminder reminder) + { + throw new NotSupportedException(); + } + + /// + /// Not supported for Windows Store apps + /// + public Task DeleteCalendarAsync(Calendar calendar) { throw new NotSupportedException(); } diff --git a/Calendars/Calendars.Plugin.iOSUnified/CalendarsImplementation.cs b/Calendars/Calendars.Plugin.iOSUnified/CalendarsImplementation.cs index b6ec551..6961904 100644 --- a/Calendars/Calendars.Plugin.iOSUnified/CalendarsImplementation.cs +++ b/Calendars/Calendars.Plugin.iOSUnified/CalendarsImplementation.cs @@ -34,6 +34,7 @@ public class CalendarsImplementation : ICalendars private EKEventStore _eventStore; private bool? _hasCalendarAccess; + private double defaultTimeBefore; #endregion @@ -45,6 +46,8 @@ public class CalendarsImplementation : ICalendars public CalendarsImplementation() { _eventStore = new EKEventStore(); + //iOS stores in negative seconds before the event + defaultTimeBefore = -TimeSpan.FromMinutes(15).TotalSeconds; } #endregion @@ -168,7 +171,8 @@ public async Task AddOrUpdateCalendarAsync(Calendar calendar) calendar.ExternalID = deviceCalendar.CalendarIdentifier; // Update color in case iOS assigned one - calendar.Color = ColorConversion.ToHexColor(deviceCalendar.CGColor); + if(deviceCalendar?.CGColor != null) + calendar.Color = ColorConversion.ToHexColor(deviceCalendar.CGColor); } else { @@ -253,6 +257,7 @@ public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calenda iosEvent.Title = calendarEvent.Name; iosEvent.Notes = calendarEvent.Description; iosEvent.AllDay = calendarEvent.AllDay; + iosEvent.Location = calendarEvent.Location ?? string.Empty; iosEvent.StartDate = calendarEvent.Start.ToNSDate(); // If set to AllDay and given an EndDate of 12am the next day, EventKit @@ -289,6 +294,52 @@ public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calenda calendarEvent.ExternalID = iosEvent.EventIdentifier; } + /// + /// Adds an event reminder to specified calendar event + /// + /// Event to add the reminder to + /// The reminder + /// Success or failure + /// If calendar event is not created or not valid + /// Unexpected platform-specific error + public Task AddEventReminderAsync(CalendarEvent calendarEvent, CalendarEventReminder reminder) + { + if (string.IsNullOrEmpty(calendarEvent.ExternalID)) + { + throw new ArgumentException("Missing calendar event identifier", "calendarEvent"); + } + + //Grab current event + var existingEvent = _eventStore.EventFromIdentifier(calendarEvent.ExternalID); + + if (existingEvent == null) + { + throw new ArgumentException("Specified calendar event not found on device"); + } + + // + var seconds = -reminder?.TimeBefore.TotalSeconds ?? defaultTimeBefore; + var alarm = EKAlarm.FromTimeInterval(seconds); + + existingEvent.AddAlarm(alarm); + NSError error = null; + if (!_eventStore.SaveEvent(existingEvent, EKSpan.ThisEvent, out error)) + { + // Without this, the eventStore will continue to return the "updated" + // event even though the save failed! + // (this obviously also resets any other changes, but since we own the eventStore + // we can be pretty confident that won't be an issue) + // + _eventStore.Reset(); + + + throw new ArgumentException(error.LocalizedDescription, "reminder", new NSErrorException(error)); + } + + + return Task.FromResult(true); + } + /// /// Removes a calendar and all its events from the system. /// @@ -410,32 +461,76 @@ private async Task RequestCalendarAccess() return _hasCalendarAccess.Value; } - private EKCalendar CreateEKCalendar(string calendarName, string color = null) + + EKCalendar SaveEKCalendar(EKSource source, string calendarName, string color = null) { var calendar = EKCalendar.Create(EKEntityType.Event, _eventStore); - calendar.Source = _eventStore.Sources.First(source => source.SourceType == EKSourceType.Local); + + //Setup calendar to be inserted calendar.Title = calendarName; + NSError error = null; if (!string.IsNullOrEmpty(color)) { calendar.CGColor = ColorConversion.ToCGColor(color); } + calendar.Source = source; + + if (_eventStore.SaveCalendar(calendar, true, out error)) + { + return calendar; + } + + _eventStore.Reset(); + + return null; + + } + + private EKCalendar CreateEKCalendar(string calendarName, string color = null) + { + NSError error = null; - if (!_eventStore.SaveCalendar(calendar, true, out error)) + //first attempt to find any and all iCloud sources + var iCloudSources = _eventStore.Sources.Where(s => s.SourceType == EKSourceType.CalDav && s.Title.Equals("icloud", StringComparison.InvariantCultureIgnoreCase)); + foreach (var source in iCloudSources) { - // Without this, the eventStore may return the new calendar even though the save failed. - // (this obviously also resets any other changes, but since we own the eventStore - // we can be pretty confident that won't be an issue) - // - _eventStore.Reset(); + + //Ensure that the calendar is enabled + if (source.GetCalendars(EKEntityType.Event).Count > 0) + { + var cal = SaveEKCalendar(source, calendarName, color); + if (cal != null) + return cal; + } + } - throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); + //other sources that we didn't try before that are caldav + var otherSources = _eventStore.Sources.Where(s => s.SourceType == EKSourceType.CalDav && !s.Title.Equals("icloud", StringComparison.InvariantCultureIgnoreCase)); + foreach (var source in otherSources) + { + var cal = SaveEKCalendar(source, calendarName, color); + if (cal != null) + return cal; + } + + + //finally attempt just local sources + var localSources = _eventStore.Sources.Where(s => s.SourceType == EKSourceType.Local); + foreach (var source in localSources) + { + var cal = SaveEKCalendar(source, calendarName, color); + if (cal != null) + return cal; } + + - return calendar; + throw new ArgumentException("No active calendar sources available to create calendar on."); } + #endregion } -} \ No newline at end of file +} diff --git a/Calendars/Calendars.Plugin.iOSUnified/EKEventExtensions.cs b/Calendars/Calendars.Plugin.iOSUnified/EKEventExtensions.cs index 03d873f..d4e9f95 100644 --- a/Calendars/Calendars.Plugin.iOSUnified/EKEventExtensions.cs +++ b/Calendars/Calendars.Plugin.iOSUnified/EKEventExtensions.cs @@ -31,6 +31,7 @@ public static CalendarEvent ToCalendarEvent(this EKEvent ekEvent) // End = ekEvent.EndDate.ToDateTime().AddSeconds(ekEvent.AllDay ? 1 : 0), AllDay = ekEvent.AllDay, + Location = ekEvent.Location, ExternalID = ekEvent.EventIdentifier }; }