public static async Task <Calendar?> GetCalendarAsync() { if (!await RequestPermissionsAsync()) { return(null); } IList <Calendar> calendars = await GetAllCalendarsAsync(); Calendar?defaultCalendar = calendars.SingleOrDefault(c => c.ExternalID == SettingsRepository.Settings.DefaultCalendarId); if (defaultCalendar != null) { return(defaultCalendar); } if (calendars.Count == 1) { return(calendars.First()); } string targetCalendarName = await Shell.Current.DisplayActionSheet(LN.ChooseCalendar, LN.Cancel, null, calendars.Select(c => c.Name).ToArray()); if (targetCalendarName == null || targetCalendarName == LN.Cancel) { return(null); } Calendar selectedCalendar = calendars.First(c => c.Name == targetCalendarName); return(selectedCalendar); }
/// <summary> /// Gets a single calendar by platform-specific ID. /// </summary> /// <param name="externalId">Platform-specific calendar identifier</param> /// <returns>The corresponding calendar, or null if not found</returns> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public Task <Calendar> GetCalendarByIdAsync(string externalId) { long calendarId = -1; if (!long.TryParse(externalId, out calendarId)) { return(null); } return(Task.Run <Calendar>(() => { Calendar calendar = null; var cursor = Query( ContentUris.WithAppendedId(_calendarsUri, calendarId), _calendarsProjection); try { if (cursor.MoveToFirst()) { calendar = GetCalendar(cursor); } } catch (Java.Lang.Exception ex) { throw new PlatformException(ex.Message, ex); } finally { cursor.Close(); } return calendar; })); }
public static async Task <IList <Calendar> > GetAllCalendars() { if (!await RequestPermissions()) { return(null); } // Getting Calendar list IList <Calendar> calendars = await CrossCalendars.Current.GetCalendarsAsync(); calendars = calendars .Where(c => c.Name.ToLower() == c.AccountName.ToLower() || c.AccountName.ToLower() == CustomCalendarName.ToLower()) .ToList(); // Getting our custom calendar Calendar customCalendar = calendars.FirstOrDefault(c => c.AccountName.ToLower() == CustomCalendarName.ToLower()); if (customCalendar == null) { customCalendar = new Calendar { Name = CustomCalendarName, Color = "#56a5de" }; await CrossCalendars.Current.AddOrUpdateCalendarAsync(customCalendar); calendars.Add(customCalendar); } else if (calendars.Count(c => c.AccountName == customCalendar.AccountName) > 1) { MessagingCenter.Send(Application.Current, MessageTypes.ExceptionOccurred, new IndexOutOfRangeException($"There are {calendars.Count(c => c.AccountName == customCalendar.AccountName)} calendars with AccountName {customCalendar.AccountName}")); } return(calendars); }
public static async Task <bool> AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (!await RequestPermissionsAsync()) { return(false); } Analytics.TrackEvent("Add to calendar");
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Plugin.Calendars.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <IList <CalendarEvent> > GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } var eventsUriBuilder = CalendarContract.Instances.ContentUri.BuildUpon(); // Note that this is slightly different from the GetEventById projection // due to the Instances API vs. Event API (specifically, IDs and start/end times) // string[] eventsProjection = { CalendarContract.Events.InterfaceConsts.Title, CalendarContract.Events.InterfaceConsts.Description, CalendarContract.Instances.Begin, CalendarContract.Instances.End, CalendarContract.Events.InterfaceConsts.AllDay, CalendarContract.Events.InterfaceConsts.EventLocation, CalendarContract.Instances.EventId }; ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(start)); ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(end)); var eventsUri = eventsUriBuilder.Build(); return(await Task.Run(() => { var cursor = Query(eventsUri, eventsProjection, string.Format("{0} = {1}", CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID), null, CalendarContract.Events.InterfaceConsts.Dtstart + " ASC"); var events = IterateCursor(cursor, () => { bool allDay = cursor.GetBoolean(CalendarContract.Events.InterfaceConsts.AllDay); var calendarEvent = new CalendarEvent { Name = cursor.GetString(CalendarContract.Events.InterfaceConsts.Title), ExternalID = cursor.GetString(CalendarContract.Instances.EventId), Description = cursor.GetString(CalendarContract.Events.InterfaceConsts.Description), Start = cursor.GetDateTime(CalendarContract.Instances.Begin, allDay), End = cursor.GetDateTime(CalendarContract.Instances.End, allDay), Location = cursor.GetString(CalendarContract.Events.InterfaceConsts.EventLocation), AllDay = allDay }; calendarEvent.Reminders = GetEventReminders(calendarEvent.ExternalID); return calendarEvent; }); return events; }).ConfigureAwait(false)); }
/// <summary> /// Creates a new calendar with the specified name and optional color. /// (just a convenience wrapper around AddOrUpdateCalendarAsync) /// </summary> /// <param name="api">ICalendars instance to extend</param> /// <param name="calendarName">Calendar name</param> /// <param name="color">Preferred color, or null to accept default</param> /// <returns>The created calendar</returns> public static async Task<Calendar> CreateCalendarAsync(this ICalendars api, string calendarName, string color = null) { var calendar = new Calendar { Name = calendarName, Color = color, CanEditCalendar = true, CanEditEvents = true }; await api.AddOrUpdateCalendarAsync(calendar).ConfigureAwait(false); return calendar; }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <bool> DeleteCalendarAsync(Calendar calendar) { var existing = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existing == null) { return(false); } else if (!existing.CanEditCalendar) { throw new ArgumentException("Cannot delete calendar (probably because it's non-local)", "calendar"); } return(await Task.Run(() => Delete(_calendarsUri, long.Parse(calendar.ExternalID))).ConfigureAwait(false)); }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="System.InvalidOperationException">Editing recurring events is not supported</exception> /// <exception cref="Plugin.Calendars.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { long existingId = -1; // Even though a Calendar was passed-in, we get this to both verify // that the calendar exists and to make sure we have accurate permissions // (rather than trusting the permissions that were passed to us...) // var existingCal = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCal == null) { return(false); } else if (!existingCal.CanEditEvents) { throw new ArgumentException("Cannot delete event from readonly calendar", "calendar"); } if (long.TryParse(calendarEvent.ExternalID, out existingId)) { return(await Task.Run <bool>(() => { if (IsEventRecurring(calendarEvent.ExternalID)) { throw new InvalidOperationException("Editing recurring events is not supported"); } var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { var eventsUri = CalendarContract.Events.ContentUri; return Delete(eventsUri, existingId); } return false; }).ConfigureAwait(false)); } return(false); }
public static async Task <IList <Calendar> > GetAllCalendarsAsync() { if (!await RequestPermissionsAsync()) { return(new List <Calendar>()); } // Getting Calendar list IList <Calendar> calendars = await CrossCalendars.Current.GetCalendarsAsync(); calendars = calendars .Where(c => string.Equals(c.Name, c.AccountName, StringComparison.OrdinalIgnoreCase) || string.Equals(c.AccountName, CustomCalendarName, StringComparison.OrdinalIgnoreCase)) .ToList(); // Getting our custom calendar Calendar?customCalendar = calendars.FirstOrDefault(c => string.Equals(c.AccountName, CustomCalendarName, StringComparison.OrdinalIgnoreCase)); if (customCalendar == null) { customCalendar = new Calendar { Name = CustomCalendarName, Color = "#56a5de" }; await CrossCalendars.Current.AddOrUpdateCalendarAsync(customCalendar); calendars.Add(customCalendar); } else if (calendars.Count(c => c.AccountName == customCalendar.AccountName) > 1) { ExceptionService.LogException(new IndexOutOfRangeException($"There are {calendars.Count(c => c.AccountName == customCalendar.AccountName)} calendars with AccountName {customCalendar.AccountName}")); } return(calendars); }
public async void Calendars_AddOrUpdateEvents_StartAfterEndThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddDays(-1) }; var calendar = new Calendar { Name = _calendarName }; await _service.AddOrUpdateCalendarAsync(calendar); Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
public async Task Calendars_AddOrUpdateEvents_CopiesEventsBetweenCalendars() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }; var calendarSource = new Calendar { Name = _calendarName }; var calendarTarget = new Calendar { Name = _calendarName + " copy destination" }; await _service.AddOrUpdateCalendarAsync(calendarSource); await _service.AddOrUpdateCalendarAsync(calendarTarget); await _service.AddOrUpdateEventAsync(calendarSource, calendarEvent); var sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); await _service.AddOrUpdateEventAsync(calendarTarget, calendarEvent); var targetEvents = await _service.GetEventsAsync(calendarTarget, DateTime.Today, DateTime.Today.AddDays(30)); // Requery source events, just to be extra sure sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); // Make sure the events are the same... CollectionAssert.AreEqual((ICollection)sourceEvents, (ICollection)targetEvents, _eventComparer); // ...except for their IDs! (i.e., they are actually unique copies) CollectionAssert.AreNotEqual(sourceEvents.Select(e => e.ExternalID).ToList(), targetEvents.Select(e => e.ExternalID).ToList()); }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID) || string.IsNullOrEmpty(calendarEvent.ExternalID)) { return false; } await EnsureInitializedAsync().ConfigureAwait(false); bool deleted = false; //var appCalendar = await _localApptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); var appCalendar = await GetLocalCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { // Verify that event actually exists on calendar // var appt = await appCalendar.GetAppointmentAsync(calendarEvent.ExternalID).ConfigureAwait(false); // The second check is because AppointmentCalendar.GetAppointmentAsync will apparently still // return events if they are associated with a different calendar... // if (appt != null && appt.CalendarId == appCalendar.LocalId) { // Sometimes DeleteAppointmentAsync throws UnauthorizedException if the event doesn't exist? // And sometimes it just fails silently? // Well, hopefully the above check will help avoid either case... // await appCalendar.DeleteAppointmentAsync(calendarEvent.ExternalID).ConfigureAwait(false); deleted = true; } } else { // Check for calendar from non-local appt store // If we get it from there, then error that it's not writeable // else, it just doesn't exist, so return false appCalendar = await _apptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { throw new ArgumentException("Cannot delete event from readonly calendar", "calendar"); } } return deleted; }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteCalendarAsync(Calendar calendar) { if (string.IsNullOrEmpty(calendar.ExternalID)) { return false; } await RequestCalendarAccess().ConfigureAwait(false); var deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { return false; } NSError error = null; if (!_eventStore.RemoveCalendar(deviceCalendar, true, out error)) { // Without this, the eventStore may act like the remove succeeded. // (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, "calendar", new NSErrorException(error)); } return true; }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task<bool> DeleteCalendarAsync(Calendar calendar) { throw new NotSupportedException(); }
public async void Calendars_AddOrUpdateEvents_UpdatesEvents() { // TODO: Test description var originalEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob", Description = "Bob's event", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }, new CalendarEvent { Name = "Steve", Description = "Steve's event", Start = DateTime.Today.AddDays(7), End = DateTime.Today.AddDays(8), AllDay = true }, new CalendarEvent { Name = "Wheeee", Description = "Fun times", Start = DateTime.Today.AddDays(13), End = DateTime.Today.AddDays(15), AllDay = true } }; var editedEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob (edited)", Description = "Bob's edited event", Start = DateTime.Today.AddDays(5).AddHours(-2), End = DateTime.Today.AddDays(5).AddHours(1), AllDay = false }, new CalendarEvent { Name = "Steve (edited)", Description = "Steve's edited event", Start = DateTime.Today.AddDays(6), End = DateTime.Today.AddDays(7).AddHours(-1), AllDay = false }, new CalendarEvent { Name = "Yay (edited)", Description = "Edited fun times", Start = DateTime.Today.AddDays(12), End = DateTime.Today.AddDays(13), AllDay = true } }; var calendar = new Calendar { Name = _calendarName }; var queryStartDate = DateTime.Today; var queryEndDate = queryStartDate.AddDays(30); await _service.AddOrUpdateCalendarAsync(calendar); //await _service.AddOrUpdateEventsAsync(calendar, originalEvents); foreach (var cev in originalEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var eventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.That(eventResults, Is.EqualTo(originalEvents).Using<CalendarEvent>(_eventComparer)); for (int i = 0; i < eventResults.Count; i++) { editedEvents.ElementAt(i).ExternalID = eventResults.ElementAt(i).ExternalID; } //await _service.AddOrUpdateEventsAsync(calendar, editedEvents); foreach (var cev in editedEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var editedEventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.That(editedEventResults, Is.EqualTo(editedEvents).Using<CalendarEvent>(_eventComparer)); }
public async void Calendars_AddOrUpdateEvent_UnspecifiedCalendarThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddHours(1) }; var calendar = new Calendar { Name = _calendarName }; Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
static async Task<Calendar> GetOrCreateEvolveCalendarAsync() { var id = Settings.Current.EvolveCalendarId; if (!string.IsNullOrWhiteSpace(id)) { try { var calendar = await CrossCalendars.Current.GetCalendarByIdAsync(id); if(calendar != null) return calendar; } catch(Exception ex) { Debug.WriteLine("Unable to get calendar.. odd as we created it already: " + ex); } } //if for some reason the calendar does not exist then simply create a enw one. if (Device.OS == TargetPlatform.Android) { //On android it is really hard to delete a calendar made by an app, so just add to default calendar. try { var calendars = await CrossCalendars.Current.GetCalendarsAsync(); foreach (var calendar in calendars) { //find first calendar we can add stuff to if (!calendar.CanEditEvents) continue; Settings.Current.EvolveCalendarId = calendar.ExternalID; return calendar; } } catch (Exception ex) { Debug.WriteLine("Unable to get calendars.. " + ex); } } else { //try to find evolve app if already uninstalled for some reason try { var calendars = await CrossCalendars.Current.GetCalendarsAsync(); foreach(var calendar in calendars) { //find first calendar we can add stuff to if(calendar.CanEditEvents && calendar.Name == "Xamarin Evolve") { Settings.Current.EvolveCalendarId = calendar.ExternalID; return calendar; } } } catch(Exception ex) { Debug.WriteLine("Unable to get calendars.. " + ex); } } var evolveCalendar = new Calendar(); evolveCalendar.Color = "#7635EB"; evolveCalendar.Name = "Xamarin Evolve"; try { await CrossCalendars.Current.AddOrUpdateCalendarAsync(evolveCalendar); Settings.Current.EvolveCalendarId = evolveCalendar.ExternalID; return evolveCalendar; } catch(Exception ex) { Debug.WriteLine("Unable to create calendar.. " + ex); } return null; }
public async void Calendars_GetEvents_NonexistentCalendarThrows() { var calendar = new Calendar { Name = "Bob", ExternalID = "42" }; Assert.IsTrue(await _service.GetEventsAsync(calendar, DateTime.Today, DateTime.Today.AddDays(30)).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
/// <summary> /// Add new event to a calendar or update an existing event. /// If a new event was added, the ExternalID property will be set on the CalendarEvent object, /// to support future queries/updates. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="System.InvalidOperationException">Editing recurring events is not supported</exception> /// <exception cref="Plugin.Calendars.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { // Verify calendar exists (Android actually allows using a nonexistent calendar ID...) // var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } } // Validate times if (calendarEvent.End < calendarEvent.Start) { throw new ArgumentException("End time may not precede start time", "calendarEvent"); } bool updateExisting = false; long existingId = -1; CalendarEvent existingEvent = null; await Task.Run(() => { if (long.TryParse(calendarEvent.ExternalID, out existingId)) { if (IsEventRecurring(calendarEvent.ExternalID)) { throw new InvalidOperationException("Editing recurring events is not supported"); } var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { updateExisting = true; existingEvent = GetEventById(calendarEvent.ExternalID); } } var eventValues = new ContentValues(); bool allDay = calendarEvent.AllDay; var start = allDay ? DateTime.SpecifyKind(calendarEvent.Start.Date, DateTimeKind.Utc) : calendarEvent.Start; var end = allDay ? DateTime.SpecifyKind(calendarEvent.End.Date, DateTimeKind.Utc) : calendarEvent.End; eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID); eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, calendarEvent.Name); eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, calendarEvent.Description); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, DateConversions.GetDateAsAndroidMS(start)); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, DateConversions.GetDateAsAndroidMS(end)); eventValues.Put(CalendarContract.Events.InterfaceConsts.AllDay, allDay); eventValues.Put(CalendarContract.Events.InterfaceConsts.EventLocation, calendarEvent.Location ?? string.Empty); // If we're updating an existing event, don't mess with the existing // time zone (since we don't support explicitly setting it yet). // *Unless* we're toggling the "all day" setting // (because that would mean the time zone is UTC rather than local...). // if (!updateExisting || allDay != existingEvent?.AllDay) { eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone, allDay ? Java.Util.TimeZone.GetTimeZone("UTC")?.ID ?? string.Empty : Java.Util.TimeZone.Default.ID); } if (!updateExisting) { calendarEvent.ExternalID = Insert(_eventsUri, eventValues); } else { Update(_eventsUri, existingId, eventValues); } }).ConfigureAwait(false); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { throw new NotSupportedException(); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task AddOrUpdateCalendarAsync(Calendar calendar) { throw new NotSupportedException(); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { throw new NotSupportedException(); }
public async void Calendars_AddOrUpdateEvents_AddsEvents() { var events = new List<CalendarEvent> { new CalendarEvent { Name = "Bob", Description = "Bob's event", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }, new CalendarEvent { Name = "Steve", Description = "Steve's event", Start = DateTime.Today.AddDays(7), End = DateTime.Today.AddDays(8), AllDay = true }, new CalendarEvent { Name = "Wheeee", Description = "Fun times", Start = DateTime.Today.AddDays(13), End = DateTime.Today.AddDays(15), AllDay = true } }; var calendar = new Calendar { Name = _calendarName }; await _service.AddOrUpdateCalendarAsync(calendar); //await _service.AddOrUpdateEventsAsync(calendar, events); foreach (var cev in events) { await _service.AddOrUpdateEventAsync(calendar, cev); } var eventResults = await _service.GetEventsAsync(calendar, DateTime.Today, DateTime.Today.AddDays(30)); Assert.That(eventResults, Is.EqualTo(events).Using<CalendarEvent>(_eventComparer)); // Extra check that DateTime.Kinds are local Assert.AreEqual(DateTimeKind.Local, eventResults.Select(e => e.Start.Kind).Distinct().Single()); Assert.AreEqual(DateTimeKind.Local, eventResults.Select(e => e.End.Kind).Distinct().Single()); }
/// <summary> /// Creates a new calendar or updates the name and color of an existing one. /// </summary> /// <param name="calendar">The calendar to create/update</param> /// <exception cref="System.ArgumentException">Calendar does not exist on device or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateCalendarAsync(Calendar calendar) { bool updateExisting = false; long existingId = -1; if (long.TryParse(calendar.ExternalID, out existingId)) { var existingCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCalendar != null) { if (!existingCalendar.CanEditCalendar) { throw new ArgumentException("Destination calendar is not writeable"); } updateExisting = true; } else { throw new ArgumentException("Specified calendar does not exist on device", "calendar"); } } var values = new ContentValues(); values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName, calendar.Name); values.Put(CalendarContract.Calendars.Name, calendar.Name); // Unlike iOS/WinPhone, Android does not automatically assign a color for us, // so we use our own default of blue. // int colorInt = unchecked ((int)0xFF0000FF); if (!string.IsNullOrEmpty(calendar.Color)) { int.TryParse(calendar.Color.Trim('#'), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out colorInt); } values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarColor, colorInt); if (!updateExisting) { values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarAccessLevel, (int)CalendarAccess.AccessOwner); 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); } await Task.Run(() => { if (updateExisting) { Update(_calendarsUri, existingId, values); } else { var uri = _calendarsUri.BuildUpon() .AppendQueryParameter(CalendarContract.CallerIsSyncadapter, "true") .AppendQueryParameter(CalendarContract.Calendars.InterfaceConsts.AccountName, AccountName) .AppendQueryParameter(CalendarContract.Calendars.InterfaceConsts.AccountType, CalendarContract.AccountTypeLocal) .Build(); calendar.ExternalID = Insert(uri, values); calendar.CanEditCalendar = true; calendar.CanEditEvents = true; calendar.Color = "#" + colorInt.ToString("x8"); } }).ConfigureAwait(false); }
public async void Calendars_AddOrUpdateEvents_StartAfterEndThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddDays(-1) }; var calendar = new Calendar { Name = _calendarName }; await _service.AddOrUpdateCalendarAsync(calendar); Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); // Ensure that calendar still has no events Assert.That(await _service.GetEventsAsync(calendar, DateTime.Today.AddMonths(-1), DateTime.Today.AddMonths(1)), Is.Empty, "Calendar has event, even after throwing"); }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { await EnsureInitializedAsync().ConfigureAwait(false); AppointmentCalendar deviceCalendar = null; try { deviceCalendar = await _apptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); } catch (ArgumentException ex) { throw new ArgumentException("Specified calendar not found on device", ex); } // Not all properties are populated by default // var options = new FindAppointmentsOptions { IncludeHidden = false }; options.FetchProperties.Add(AppointmentProperties.Subject); options.FetchProperties.Add(AppointmentProperties.Details); 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(); return events; }
public async void Calendars_AddOrUpdateEvent_NonexistentCalendarThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddHours(1) }; var calendar = new Calendar { Name = _calendarName }; // Create/delete calendar so we have a valid ID for a nonexistent calendar // await _service.AddOrUpdateCalendarAsync(calendar); await _service.DeleteCalendarAsync(calendar); Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
/// <summary> /// Creates a new calendar or updates the name and color of an existing one. /// </summary> /// <param name="calendar">The calendar to create/update</param> /// <exception cref="System.ArgumentException">Calendar does not exist on device or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateCalendarAsync(Calendar calendar) { await RequestCalendarAccess().ConfigureAwait(false); EKCalendar deviceCalendar = null; if (!string.IsNullOrEmpty(calendar.ExternalID)) { deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar does not exist on device", "calendar"); } } if (deviceCalendar == null) { deviceCalendar = CreateEKCalendar(calendar.Name, calendar.Color); calendar.ExternalID = deviceCalendar.CalendarIdentifier; // Update color in case iOS assigned one if(deviceCalendar?.CGColor != null) calendar.Color = ColorConversion.ToHexColor(deviceCalendar.CGColor); } else { deviceCalendar.Title = calendar.Name; if (!string.IsNullOrEmpty(calendar.Color)) { deviceCalendar.CGColor = ColorConversion.ToCGColor(calendar.Color); } NSError error = null; if (!_eventStore.SaveCalendar(deviceCalendar, true, out error)) { // Without this, the eventStore will continue to return the "updated" // 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(); if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.CalendarIsImmutable) { throw new ArgumentException(error.LocalizedDescription, new NSErrorException(error)); } else { throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); } } } }
public async void Calendars_AddOrUpdateEvents_CopiesEventsBetweenCalendars() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }; var calendarSource = new Calendar { Name = _calendarName }; var calendarTarget = new Calendar { Name = _calendarName + " copy destination" }; await _service.AddOrUpdateCalendarAsync(calendarSource); await _service.AddOrUpdateCalendarAsync(calendarTarget); await _service.AddOrUpdateEventAsync(calendarSource, calendarEvent); var sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); //await _service.AddOrUpdateEventsAsync(calendarTarget, sourceEvents); foreach (var cev in sourceEvents) { await _service.AddOrUpdateEventAsync(calendarTarget, cev); } var targetEvents = await _service.GetEventsAsync(calendarTarget, DateTime.Today, DateTime.Today.AddDays(30)); // Requery source events, just to be extra sure sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); // Make sure the events are the same... Assert.That(targetEvents, Is.EqualTo(sourceEvents).Using<CalendarEvent>(_eventComparer)); // ...except for their IDs! (i.e., they are actually unique copies) Assert.That(targetEvents.Select(e => e.ExternalID).ToList(), Is.Not.EqualTo(sourceEvents.Select(e => e.ExternalID).ToList())); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent cev) { throw new NotSupportedException(); }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } var eventsUriBuilder = CalendarContract.Instances.ContentUri.BuildUpon(); // Note that this is slightly different from the GetEventById projection // due to the Instances API vs. Event API (specifically, IDs) // string[] eventsProjection = { // CalendarContract.Events.InterfaceConsts.Id, CalendarContract.Events.InterfaceConsts.Title, CalendarContract.Events.InterfaceConsts.Description, CalendarContract.Events.InterfaceConsts.Dtstart, CalendarContract.Events.InterfaceConsts.Dtend, CalendarContract.Events.InterfaceConsts.AllDay, CalendarContract.Events.InterfaceConsts.EventLocation, CalendarContract.Instances.EventId }; ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(start)); ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(end)); var eventsUri = eventsUriBuilder.Build(); var events = new List<CalendarEvent>(); await Task.Run(() => { var cursor = Query(eventsUri, eventsProjection, string.Format("{0} = {1}", CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID), null, CalendarContract.Events.InterfaceConsts.Dtstart + " ASC"); try { if (cursor.MoveToFirst()) { do { events.Add(new CalendarEvent { Name = cursor.GetString(CalendarContract.Events.InterfaceConsts.Title), ExternalID = cursor.GetString(CalendarContract.Instances.EventId), 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()); } } catch (Java.Lang.Exception ex) { throw new PlatformException(ex.Message, ex); } finally { cursor.Close(); } }); return events; }
/// <summary> /// Creates a new calendar or updates the name and color of an existing one. /// </summary> /// <param name="calendar">The calendar to create/update</param> /// <exception cref="System.ArgumentException">Calendar does not exist on device or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateCalendarAsync(Calendar calendar) { bool updateExisting = false; long existingId = -1; if (long.TryParse(calendar.ExternalID, out existingId)) { var existingCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCalendar != null) { if (!existingCalendar.CanEditCalendar) { throw new ArgumentException("Destination calendar is not writeable"); } updateExisting = true; } else { throw new ArgumentException("Specified calendar does not exist on device", "calendar"); } } var values = new ContentValues(); values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName, calendar.Name); values.Put(CalendarContract.Calendars.Name, calendar.Name); // Unlike iOS/WinPhone, Android does not automatically assign a color for us, // so we use our own default of blue. // int colorInt = unchecked((int)0xFF0000FF); if (!string.IsNullOrEmpty(calendar.Color)) { int.TryParse(calendar.Color.Trim('#'), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out colorInt); } values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarColor, colorInt); if (!updateExisting) { values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarAccessLevel, (int)CalendarAccess.AccessOwner); 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); } await Task.Run(() => { if (updateExisting) { Update(_calendarsUri, existingId, values); } else { var uri = _calendarsUri.BuildUpon() .AppendQueryParameter(CalendarContract.CallerIsSyncadapter, "true") .AppendQueryParameter(CalendarContract.Calendars.InterfaceConsts.AccountName, AccountName) .AppendQueryParameter(CalendarContract.Calendars.InterfaceConsts.AccountType, CalendarContract.AccountTypeLocal) .Build(); calendar.ExternalID = Insert(uri, values); calendar.CanEditCalendar = true; calendar.CanEditEvents = true; calendar.Color = "#" + colorInt.ToString("x8"); } }).ConfigureAwait(false); }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <IList <CalendarEvent> > GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } var eventsUriBuilder = CalendarContract.Instances.ContentUri.BuildUpon(); // Note that this is slightly different from the GetEventById projection // due to the Instances API vs. Event API (specifically, IDs) // string[] eventsProjection = { // CalendarContract.Events.InterfaceConsts.Id, CalendarContract.Events.InterfaceConsts.Title, CalendarContract.Events.InterfaceConsts.Description, CalendarContract.Events.InterfaceConsts.Dtstart, CalendarContract.Events.InterfaceConsts.Dtend, CalendarContract.Events.InterfaceConsts.AllDay, CalendarContract.Events.InterfaceConsts.EventLocation, CalendarContract.Instances.EventId }; ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(start)); ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(end)); var eventsUri = eventsUriBuilder.Build(); var events = new List <CalendarEvent>(); await Task.Run(() => { var cursor = Query(eventsUri, eventsProjection, string.Format("{0} = {1}", CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID), null, CalendarContract.Events.InterfaceConsts.Dtstart + " ASC"); try { if (cursor.MoveToFirst()) { do { events.Add(new CalendarEvent { Name = cursor.GetString(CalendarContract.Events.InterfaceConsts.Title), ExternalID = cursor.GetString(CalendarContract.Instances.EventId), 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()); } } catch (Java.Lang.Exception ex) { throw new PlatformException(ex.Message, ex); } finally { cursor.Close(); } }); return(events); }
/// <summary> /// Add new event to a calendar or update an existing event. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { // Verify calendar exists (Android actually allows using a nonexistent calendar ID...) // var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } } // Validate times if (calendarEvent.End < calendarEvent.Start) { throw new ArgumentException("End time may not precede start time", "calendarEvent"); } bool updateExisting = false; long existingId = -1; await Task.Run(() => { if (long.TryParse(calendarEvent.ExternalID, out existingId)) { var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { updateExisting = true; } } var eventValues = new ContentValues(); eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID); eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, calendarEvent.Name); eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, calendarEvent.Description); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, DateConversions.GetDateAsAndroidMS(calendarEvent.Start)); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, 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); if (!updateExisting) { calendarEvent.ExternalID = Insert(_eventsUri, eventValues); } else { Update(_eventsUri, existingId, eventValues); } }); }
/// <summary> /// Add new event to a calendar or update an existing event. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { // Verify calendar exists (Android actually allows using a nonexistent calendar ID...) // var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } } // Validate times if (calendarEvent.End < calendarEvent.Start) { throw new ArgumentException("End time may not precede start time", "calendarEvent"); } bool updateExisting = false; long existingId = -1; await Task.Run(() => { if (long.TryParse(calendarEvent.ExternalID, out existingId)) { var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { updateExisting = true; } } var eventValues = new ContentValues(); eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID); eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, calendarEvent.Name); eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, calendarEvent.Description); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, DateConversions.GetDateAsAndroidMS(calendarEvent.Start)); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, 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); if (!updateExisting) { calendarEvent.ExternalID = Insert(_eventsUri, eventValues); } else { Update(_eventsUri, existingId, eventValues); } }); }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteCalendarAsync(Calendar calendar) { var existing = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existing == null) { return false; } else if (!existing.CanEditCalendar) { throw new ArgumentException("Cannot delete calendar (probably because it's non-local)", "calendar"); } return await Task.Run(() => Delete(_calendarsUri, long.Parse(calendar.ExternalID))).ConfigureAwait(false); }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { await RequestCalendarAccess().ConfigureAwait(false); var deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } var query = _eventStore.PredicateForEvents(start.ToNSDate(), end.ToNSDate(), new EKCalendar[] { deviceCalendar }); var events = await Task.Run(() => { var iosEvents = _eventStore.EventsMatching(query); return iosEvents == null ? new List<CalendarEvent>() : iosEvents.Select(e => e.ToCalendarEvent()).ToList(); }).ConfigureAwait(false); return events; }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { long existingId = -1; // Even though a Calendar was passed-in, we get this to both verify // that the calendar exists and to make sure we have accurate permissions // (rather than trusting the permissions that were passed to us...) // var existingCal = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCal == null) { return false; } else if (!existingCal.CanEditEvents) { throw new ArgumentException("Cannot delete event from readonly calendar", "calendar"); } if (long.TryParse(calendarEvent.ExternalID, out existingId)) { return await Task.Run<bool>(() => { var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { var eventsUri = CalendarContract.Events.ContentUri; return Delete(eventsUri, existingId); } return false; }); } return false; }
/// <summary> /// Add new event to a calendar or update an existing event. /// Throws if Calendar ID is empty, calendar does not exist, or calendar is read-only. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { await RequestCalendarAccess().ConfigureAwait(false); EKCalendar deviceCalendar = null; if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } } EKEvent iosEvent = null; // If Event already corresponds to an existing EKEvent in the target // Calendar, then edit that instead of creating a new one. // if (!string.IsNullOrEmpty(calendarEvent.ExternalID)) { var existingEvent = _eventStore.EventFromIdentifier(calendarEvent.ExternalID); if (existingEvent.Calendar.CalendarIdentifier == deviceCalendar.CalendarIdentifier) { iosEvent = existingEvent; } } if (iosEvent == null) { iosEvent = EKEvent.FromStore(_eventStore); } 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 // assumes that the event consumes two full days. // (whereas WinPhone/Android consider that one day, and thus so do we) // iosEvent.EndDate = calendarEvent.AllDay ? calendarEvent.End.AddMilliseconds(-1).ToNSDate() : calendarEvent.End.ToNSDate(); iosEvent.Calendar = deviceCalendar; NSError error = null; if (!_eventStore.SaveEvent(iosEvent, 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(); // Technically, probably any ekerrordomain error would be an ArgumentException? // - but we don't necessarily know *which* argument (at least not without the code) // - for now, just focusing on the start > end scenario and translating the rest to PlatformException. Can always add more later. if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.DatesInverted) { throw new ArgumentException(error.LocalizedDescription, new NSErrorException(error)); } else { throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); } } calendarEvent.ExternalID = iosEvent.EventIdentifier; }
public async Task Calendars_AddOrUpdateEvents_UpdatesEvents() { var originalEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob", Description = "Bob's event", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }, new CalendarEvent { Name = "Steve", Description = "Steve's event", Start = DateTime.Today.AddDays(7), End = DateTime.Today.AddDays(8), AllDay = true }, new CalendarEvent { Name = "Wheeee", Description = "Fun times", Start = DateTime.Today.AddDays(13), End = DateTime.Today.AddDays(15), AllDay = true } }; var editedEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob (edited)", Description = "Bob's edited event", Start = DateTime.Today.AddDays(5).AddHours(-2), End = DateTime.Today.AddDays(5).AddHours(1), AllDay = false }, new CalendarEvent { Name = "Steve (edited)", Description = "Steve's edited event", Start = DateTime.Today.AddDays(6), End = DateTime.Today.AddDays(7).AddHours(-1), AllDay = false }, new CalendarEvent { Name = "Yay (edited)", Description = "Edited fun times", Start = DateTime.Today.AddDays(12), End = DateTime.Today.AddDays(13), AllDay = true } }; var calendar = new Calendar { Name = _calendarName }; var queryStartDate = DateTime.Today; var queryEndDate = queryStartDate.AddDays(30); await _service.AddOrUpdateCalendarAsync(calendar); foreach (var cev in originalEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var eventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.IsNotNull(eventResults); CollectionAssert.AreEqual((ICollection)originalEvents, (ICollection)eventResults, _eventComparer); for (int i = 0; i < eventResults.Count; i++) { editedEvents.ElementAt(i).ExternalID = eventResults.ElementAt(i).ExternalID; } foreach (var cev in editedEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var editedEventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.IsNotNull(editedEventResults); CollectionAssert.AreEqual((ICollection)editedEvents, (ICollection)editedEventResults, _eventComparer); }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID) || string.IsNullOrEmpty(calendarEvent.ExternalID)) { return false; } await RequestCalendarAccess().ConfigureAwait(false); var deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { return false; } var iosEvent = _eventStore.EventFromIdentifier(calendarEvent.ExternalID); if (iosEvent == null || iosEvent.Calendar.CalendarIdentifier != deviceCalendar.CalendarIdentifier) { return false; } NSError error = null; if (!_eventStore.RemoveEvent(iosEvent, EKSpan.ThisEvent, true, out error)) { // Without this, the eventStore may act like the remove succeeded. // (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(); if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.CalendarReadOnly) { throw new ArgumentException(error.LocalizedDescription, "calendar", new NSErrorException(error)); } else { throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); } } return true; }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteCalendarAsync(Calendar calendar) { if (string.IsNullOrEmpty(calendar.ExternalID)) { return false; } await EnsureInitializedAsync().ConfigureAwait(false); bool deleted = false; //var appCalendar = await _localApptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); var appCalendar = await GetLocalCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { // Perform the delete and return true await appCalendar.DeleteAsync().ConfigureAwait(false); deleted = true; } else { // Check for calendar from non-local appt store // If we get it from there, then error that it's not writeable // else, it just doesn't exist, so return false appCalendar = await _apptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { throw new ArgumentException("Cannot delete read-only calendar", "calendar"); } } return deleted; }