Skip to content

Commit

Permalink
Read-only Calendar API (#46)
Browse files Browse the repository at this point in the history
* Calendar Read-Only API

* Return exceptions that match test case System.NotImplementedException --> Xamarin.Essentials.NotImplementedInReferenceAssemblyException (#45)
  • Loading branch information
ScottBTR committed Jan 22, 2020
1 parent cd4c817 commit a22d4ea
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Samples/Samples.iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
<string>1.0</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Access to your location is required for cool things to happen!</string>
<key>NSCalendarsUsageDescription</key>
<string>Access to view and create calendar events</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand Down
160 changes: 160 additions & 0 deletions Xamarin.Essentials/Calendar/Calendar.android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Android.Content;
using Android.Database;
using Android.Provider;
using Java.Security;

namespace Xamarin.Essentials
{
public static partial class Calendar
{
const string andCondition = "AND";

static async Task<IEnumerable<DeviceCalendar>> PlatformGetCalendarsAsync()
{
await Permissions.RequireAsync(PermissionType.CalendarRead);

var calendarsUri = CalendarContract.Calendars.ContentUri;
var calendarsProjection = new List<string>
{
CalendarContract.Calendars.InterfaceConsts.Id,
CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName
};
var queryConditions = $"{CalendarContract.Calendars.InterfaceConsts.Deleted} != 1";

using (var cur = Platform.AppContext.ApplicationContext.ContentResolver.Query(calendarsUri, calendarsProjection.ToArray(), queryConditions, null, null))
{
var calendars = new List<DeviceCalendar>();
while (cur.MoveToNext())
{
calendars.Add(new DeviceCalendar()
{
Id = cur.GetString(calendarsProjection.IndexOf(CalendarContract.Calendars.InterfaceConsts.Id)),
Name = cur.GetString(calendarsProjection.IndexOf(CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName)),
});
}
return calendars;
}
}

static async Task<IEnumerable<DeviceEvent>> PlatformGetEventsAsync(string calendarId = null, DateTimeOffset? startDate = null, DateTimeOffset? endDate = null)
{
await Permissions.RequireAsync(PermissionType.CalendarRead);

var eventsUri = CalendarContract.Events.ContentUri;
var eventsProjection = new List<string>
{
CalendarContract.Events.InterfaceConsts.Id,
CalendarContract.Events.InterfaceConsts.CalendarId,
CalendarContract.Events.InterfaceConsts.Title,
CalendarContract.Events.InterfaceConsts.AllDay,
CalendarContract.Events.InterfaceConsts.Dtstart,
CalendarContract.Events.InterfaceConsts.Dtend,
CalendarContract.Events.InterfaceConsts.Deleted
};
var calendarSpecificEvent = string.Empty;
var sDate = startDate ?? DateTimeOffset.Now.Add(defaultStartTimeFromNow);
var eDate = endDate ?? sDate.Add(defaultEndTimeFromStartTime);
if (!string.IsNullOrEmpty(calendarId))
{
calendarSpecificEvent = $"{CalendarContract.Events.InterfaceConsts.CalendarId}={calendarId} {andCondition} ";
}
calendarSpecificEvent += $"{CalendarContract.Events.InterfaceConsts.Dtend} >= {sDate.AddMilliseconds(sDate.Offset.TotalMilliseconds).ToUnixTimeMilliseconds()} {andCondition} ";
calendarSpecificEvent += $"{CalendarContract.Events.InterfaceConsts.Dtstart} <= {eDate.AddMilliseconds(sDate.Offset.TotalMilliseconds).ToUnixTimeMilliseconds()} {andCondition} ";
calendarSpecificEvent += $"{CalendarContract.Events.InterfaceConsts.Deleted} != 1";

using (var cur = Platform.AppContext.ApplicationContext.ContentResolver.Query(eventsUri, eventsProjection.ToArray(), calendarSpecificEvent, null, $"{CalendarContract.Events.InterfaceConsts.Dtstart} ASC"))
{
var events = new List<DeviceEvent>();
while (cur.MoveToNext())
{
events.Add(new DeviceEvent()
{
Id = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Id)),
CalendarId = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.CalendarId)),
Title = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Title)),
StartDate = DateTimeOffset.FromUnixTimeMilliseconds(cur.GetLong(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Dtstart))),
EndDate = cur.GetInt(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.AllDay)) == 0 ? (DateTimeOffset?)DateTimeOffset.FromUnixTimeMilliseconds(cur.GetLong(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Dtend))) : null
});
}
return events;
}
}

static async Task<DeviceEvent> PlatformGetEventByIdAsync(string eventId)
{
await Permissions.RequireAsync(PermissionType.CalendarRead);

var eventsUri = CalendarContract.Events.ContentUri;
var eventsProjection = new List<string>
{
CalendarContract.Events.InterfaceConsts.Id,
CalendarContract.Events.InterfaceConsts.CalendarId,
CalendarContract.Events.InterfaceConsts.Title,
CalendarContract.Events.InterfaceConsts.Description,
CalendarContract.Events.InterfaceConsts.EventLocation,
CalendarContract.Events.InterfaceConsts.AllDay,
CalendarContract.Events.InterfaceConsts.Dtstart,
CalendarContract.Events.InterfaceConsts.Dtend
};

// Android event ids are always integers
if (!int.TryParse(eventId, out var resultId))
{
throw new ArgumentException($"[Android]: No Event found for event Id {eventId}");
}

var calendarSpecificEvent = $"{CalendarContract.Events.InterfaceConsts.Id}={resultId}";
using (var cur = Platform.AppContext.ApplicationContext.ContentResolver.Query(eventsUri, eventsProjection.ToArray(), calendarSpecificEvent, null, null))
{
if (cur.Count > 0)
{
cur.MoveToNext();
var eventResult = new DeviceEvent
{
Id = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Id)),
CalendarId = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.CalendarId)),
Title = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Title)),
Description = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Description)),
Location = cur.GetString(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.EventLocation)),
StartDate = DateTimeOffset.FromUnixTimeMilliseconds(cur.GetLong(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Dtstart))),
EndDate = cur.GetInt(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.AllDay)) == 0 ? (DateTimeOffset?)DateTimeOffset.FromUnixTimeMilliseconds(cur.GetLong(eventsProjection.IndexOf(CalendarContract.Events.InterfaceConsts.Dtend))) : null,
Attendees = GetAttendeesForEvent(eventId)
};
return eventResult;
}
else
{
throw new ArgumentException($"[Android]: No Event found for event Id {eventId}");
}
}
}

static IEnumerable<DeviceEventAttendee> GetAttendeesForEvent(string eventId)
{
var attendeesUri = CalendarContract.Attendees.ContentUri;
var attendeesProjection = new List<string>
{
CalendarContract.Attendees.InterfaceConsts.EventId,
CalendarContract.Attendees.InterfaceConsts.AttendeeEmail,
CalendarContract.Attendees.InterfaceConsts.AttendeeName
};
var attendeeSpecificAttendees = $"{CalendarContract.Attendees.InterfaceConsts.EventId}={eventId}";
var cur = Platform.AppContext.ApplicationContext.ContentResolver.Query(attendeesUri, attendeesProjection.ToArray(), attendeeSpecificAttendees, null, null);
var attendees = new List<DeviceEventAttendee>();
while (cur.MoveToNext())
{
attendees.Add(new DeviceEventAttendee()
{
Name = cur.GetString(attendeesProjection.IndexOf(CalendarContract.Attendees.InterfaceConsts.AttendeeName)),
Email = cur.GetString(attendeesProjection.IndexOf(CalendarContract.Attendees.InterfaceConsts.AttendeeEmail)),
});
}
cur.Dispose();
return attendees;
}
}
}
114 changes: 114 additions & 0 deletions Xamarin.Essentials/Calendar/Calendar.ios.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EventKit;
using Foundation;

namespace Xamarin.Essentials
{
public static partial class Calendar
{
static async Task<IEnumerable<DeviceCalendar>> PlatformGetCalendarsAsync()
{
await Permissions.RequireAsync(PermissionType.CalendarRead);

EKCalendar[] calendars;
try
{
calendars = CalendarRequest.Instance.Calendars;
}
catch (NullReferenceException ex)
{
throw new Exception($"iOS: Unexpected null reference exception {ex.Message}");
}
var calendarList = (from calendar in calendars
select new DeviceCalendar
{
Id = calendar.CalendarIdentifier,
Name = calendar.Title
}).ToList();

return calendarList;
}

static async Task<IEnumerable<DeviceEvent>> PlatformGetEventsAsync(string calendarId = null, DateTimeOffset? startDate = null, DateTimeOffset? endDate = null)
{
await Permissions.RequireAsync(PermissionType.CalendarRead);

var startDateToConvert = startDate ?? DateTimeOffset.Now.Add(defaultStartTimeFromNow);
var endDateToConvert = endDate ?? startDateToConvert.Add(defaultEndTimeFromStartTime); // NOTE: 4 years is the maximum period that a iOS calendar events can search
var sDate = startDateToConvert.ToNSDate();
var eDate = endDateToConvert.ToNSDate();
EKCalendar[] calendars;
try
{
calendars = !string.IsNullOrWhiteSpace(calendarId)
? CalendarRequest.Instance.Calendars.Where(x => x.CalendarIdentifier == calendarId).ToArray()
: null;
}
catch (NullReferenceException ex)
{
throw new NullReferenceException($"iOS: Unexpected null reference exception {ex.Message}");
}

var query = CalendarRequest.Instance.PredicateForEvents(sDate, eDate, calendars);
var events = CalendarRequest.Instance.EventsMatching(query);

var eventList = (from e in events
select new DeviceEvent
{
Id = e.CalendarItemIdentifier,
CalendarId = e.Calendar.CalendarIdentifier,
Title = e.Title,
StartDate = e.StartDate.ToDateTimeOffset(),
EndDate = !e.AllDay ? (DateTimeOffset?)e.EndDate.ToDateTimeOffset() : null
})
.OrderBy(e => e.StartDate)
.ToList();

return eventList;
}

static async Task<DeviceEvent> PlatformGetEventByIdAsync(string eventId)
{
await Permissions.RequireAsync(PermissionType.CalendarRead);

EKEvent e;
try
{
e = CalendarRequest.Instance.GetCalendarItem(eventId) as EKEvent;
}
catch (NullReferenceException)
{
throw new NullReferenceException($"[iOS]: No Event found for event Id {eventId}");
}

return new DeviceEvent
{
Id = e.CalendarItemIdentifier,
CalendarId = e.Calendar.CalendarIdentifier,
Title = e.Title,
Description = e.Notes,
Location = e.Location,
StartDate = e.StartDate.ToDateTimeOffset(),
EndDate = !e.AllDay ? (DateTimeOffset?)e.EndDate.ToDateTimeOffset() : null,
Attendees = e.Attendees != null ? GetAttendeesForEvent(e.Attendees) : new List<DeviceEventAttendee>()
};
}

static IEnumerable<DeviceEventAttendee> GetAttendeesForEvent(IEnumerable<EKParticipant> inviteList)
{
var attendees = (from attendee in inviteList
select new DeviceEventAttendee
{
Name = attendee.Name,
Email = attendee.Name
})
.OrderBy(e => e.Name)
.ToList();

return attendees;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Xamarin.Essentials
{
public static partial class Calendar
{
static Task<IEnumerable<DeviceCalendar>> PlatformGetCalendarsAsync() => throw ExceptionUtils.NotSupportedOrImplementedException;

static Task<IEnumerable<DeviceEvent>> PlatformGetEventsAsync(string calendarId = null, DateTimeOffset? startDate = null, DateTimeOffset? endDate = null) => throw ExceptionUtils.NotSupportedOrImplementedException;

static Task<DeviceEvent> PlatformGetEventByIdAsync(string eventId) => throw ExceptionUtils.NotSupportedOrImplementedException;
}
}
19 changes: 19 additions & 0 deletions Xamarin.Essentials/Calendar/Calendar.shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Xamarin.Essentials
{
public static partial class Calendar
{
static TimeSpan defaultStartTimeFromNow = TimeSpan.Zero;

static TimeSpan defaultEndTimeFromStartTime = TimeSpan.FromDays(14);

public static Task<IEnumerable<DeviceCalendar>> GetCalendarsAsync() => PlatformGetCalendarsAsync();

public static Task<IEnumerable<DeviceEvent>> GetEventsAsync(string calendarId = null, DateTimeOffset? startDate = null, DateTimeOffset? endDate = null) => PlatformGetEventsAsync(calendarId, startDate, endDate);

public static Task<DeviceEvent> GetEventByIdAsync(string eventId) => PlatformGetEventByIdAsync(eventId);
}
}
Loading

0 comments on commit a22d4ea

Please sign in to comment.