Skip to content

Commit

Permalink
Change attendance status
Browse files Browse the repository at this point in the history
(cherry picked from commit 87710e0)
  • Loading branch information
scorpionate authored and thomassth committed Dec 21, 2021
1 parent 33f97e7 commit bf5a0bc
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJEC
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_ID_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_IS_PRIMARY_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_OLDER_API
import com.builttoroam.devicecalendar.common.Constants.Companion.CALENDAR_PROJECTION_OWNER_ACCOUNT_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_BEGIN_INDEX
import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_END_INDEX
Expand Down Expand Up @@ -349,7 +350,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
events.add(event)
}
for (event in events) {
val attendees = retrieveAttendees(event.eventId!!, contentResolver)
val attendees = retrieveAttendees(calendar, event.eventId!!, contentResolver)
event.organizer = attendees.firstOrNull { it.isOrganizer != null && it.isOrganizer }
event.attendees = attendees
event.reminders = retrieveReminders(event.eventId!!, contentResolver)
Expand Down Expand Up @@ -405,7 +406,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
} else {
job = GlobalScope.launch(Dispatchers.IO + exceptionHandler) {
contentResolver?.update(ContentUris.withAppendedId(Events.CONTENT_URI, eventId), values, null, null)
val existingAttendees = retrieveAttendees(eventId.toString(), contentResolver)
val existingAttendees = retrieveAttendees(calendar, eventId.toString(), contentResolver)
val attendeesToDelete = if (event.attendees.isNotEmpty()) existingAttendees.filter { existingAttendee -> event.attendees.all { it.emailAddress != existingAttendee.emailAddress } } else existingAttendees
for (attendeeToDelete in attendeesToDelete) {
deleteAttendee(eventId, attendeeToDelete, contentResolver)
Expand All @@ -415,6 +416,18 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
insertAttendees(attendeesToInsert, eventId, contentResolver)
deleteExistingReminders(contentResolver, eventId)
insertReminders(event.reminders, eventId, contentResolver!!)

val existingSelfAttendee = existingAttendees.firstOrNull {
it.emailAddress == calendar.ownerAccount
}
val newSelfAttendee = event.attendees.firstOrNull {
it.emailAddress == calendar.ownerAccount
}
if (existingSelfAttendee != null && newSelfAttendee != null &&
newSelfAttendee.attendanceStatus != null &&
existingSelfAttendee.attendanceStatus != newSelfAttendee.attendanceStatus) {
updateAttendeeStatus(eventId, newSelfAttendee, contentResolver)
}
}
}
job.invokeOnCompletion {
Expand Down Expand Up @@ -555,6 +568,14 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {

}

private fun updateAttendeeStatus(eventId: Long, attendee: Attendee, contentResolver: ContentResolver?) {
val selection = "(" + CalendarContract.Attendees.EVENT_ID + " = ?) AND (" + CalendarContract.Attendees.ATTENDEE_EMAIL + " = ?)"
val selectionArgs = arrayOf(eventId.toString() + "", attendee.emailAddress)
val values = ContentValues()
values.put(CalendarContract.Attendees.ATTENDEE_STATUS, attendee.attendanceStatus)
contentResolver?.update(CalendarContract.Attendees.CONTENT_URI, values, selection, selectionArgs)
}

fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result, startDate: Long? = null, endDate: Long? = null, followingInstances: Boolean? = null) {
if (arePermissionsGranted()) {
val existingCal = retrieveCalendar(calendarId, pendingChannelResult, true)
Expand Down Expand Up @@ -682,8 +703,17 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
val calendarColor = cursor.getInt(CALENDAR_PROJECTION_COLOR_INDEX)
val accountName = cursor.getString(CALENDAR_PROJECTION_ACCOUNT_NAME_INDEX)
val accountType = cursor.getString(CALENDAR_PROJECTION_ACCOUNT_TYPE_INDEX)
val ownerAccount = cursor.getString(CALENDAR_PROJECTION_OWNER_ACCOUNT_INDEX)

val calendar = Calendar(
calId.toString(),
displayName,
calendarColor,
accountName,
accountType,
ownerAccount
)

val calendar = Calendar(calId.toString(), displayName, calendarColor, accountName, accountType)
calendar.isReadOnly = isCalendarReadOnly(accessLevel)
if (atLeastAPI(17)) {
val isPrimary = cursor.getString(CALENDAR_PROJECTION_IS_PRIMARY_INDEX)
Expand Down Expand Up @@ -794,17 +824,21 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
}?.firstOrNull()
}

private fun parseAttendeeRow(cursor: Cursor?): Attendee? {
private fun parseAttendeeRow(calendar: Calendar, cursor: Cursor?): Attendee? {
if (cursor == null) {
return null
}

val emailAddress = cursor.getString(ATTENDEE_EMAIL_INDEX)

return Attendee(
cursor.getString(ATTENDEE_EMAIL_INDEX),
cursor.getString(ATTENDEE_NAME_INDEX),
cursor.getInt(ATTENDEE_TYPE_INDEX),
cursor.getInt(ATTENDEE_STATUS_INDEX),
cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER)
emailAddress,
cursor.getString(ATTENDEE_NAME_INDEX),
cursor.getInt(ATTENDEE_TYPE_INDEX),
cursor.getInt(ATTENDEE_STATUS_INDEX),
cursor.getInt(ATTENDEE_RELATIONSHIP_INDEX) == CalendarContract.Attendees.RELATIONSHIP_ORGANIZER,
emailAddress == calendar.ownerAccount
)
}

private fun parseReminderRow(cursor: Cursor?): Reminder? {
Expand All @@ -827,14 +861,14 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener {
}

@SuppressLint("MissingPermission")
private fun retrieveAttendees(eventId: String, contentResolver: ContentResolver?): MutableList<Attendee> {
private fun retrieveAttendees(calendar: Calendar, eventId: String, contentResolver: ContentResolver?): MutableList<Attendee> {
val attendees: MutableList<Attendee> = mutableListOf()
val attendeesQuery = "(${CalendarContract.Attendees.EVENT_ID} = ${eventId})"
val attendeesCursor = contentResolver?.query(CalendarContract.Attendees.CONTENT_URI, ATTENDEE_PROJECTION, attendeesQuery, null, null)
attendeesCursor.use { cursor ->
if (cursor?.moveToFirst() == true) {
do {
val attendee = parseAttendeeRow(attendeesCursor) ?: continue
val attendee = parseAttendeeRow(calendar, attendeesCursor) ?: continue
attendees.add(attendee)
} while (cursor.moveToNext())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class DeviceCalendarPlugin() : FlutterPlugin, MethodCallHandler, ActivityAware {
private val CALENDAR_COLOR_ARGUMENT = "calendarColor"
private val LOCAL_ACCOUNT_NAME_ARGUMENT = "localAccountName"
private val EVENT_AVAILABILITY_ARGUMENT = "availability"
private val ATTENDANCE_STATUS_ARGUMENT = "attendanceStatus"

private lateinit var _calendarDelegate: CalendarDelegate

Expand Down Expand Up @@ -190,6 +191,7 @@ class DeviceCalendarPlugin() : FlutterPlugin, MethodCallHandler, ActivityAware {
attendeeArgs[EMAIL_ADDRESS_ARGUMENT] as String,
attendeeArgs[NAME_ARGUMENT] as String?,
attendeeArgs[ROLE_ARGUMENT] as Int,
attendeeArgs[ATTENDANCE_STATUS_ARGUMENT] as Int?,
null, null))
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package com.builttoroam.devicecalendar.models

class Attendee(val emailAddress: String, val name: String?, val role: Int, val attendanceStatus: Int?, val isOrganizer: Boolean?)
class Attendee(val emailAddress: String, val name: String?, val role: Int, val attendanceStatus: Int?, val isOrganizer: Boolean?, val isCurrentUser: Boolean?)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.builttoroam.devicecalendar.models

class Calendar(val id: String, val name: String, val color : Int, val accountName: String, val accountType: String) {
class Calendar(val id: String, val name: String, val color : Int, val accountName: String, val accountType: String, val ownerAccount: String) {
var isReadOnly: Boolean = false
var isDefault: Boolean = false
}
97 changes: 86 additions & 11 deletions ios/Classes/SwiftDeviceCalendarPlugin.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import EventKit
import EventKitUI
import Flutter
import Foundation
import UIKit
import EventKit

extension Date {
var millisecondsSinceEpoch: Double { return self.timeIntervalSince1970 * 1000.0 }
Expand All @@ -12,7 +14,7 @@ extension EKParticipant {
}
}

public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin, EKEventViewDelegate, UINavigationControllerDelegate {
struct Calendar: Codable {
let id: String
let name: String
Expand Down Expand Up @@ -57,6 +59,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
let emailAddress: String
let role: Int
let attendanceStatus: Int
let isCurrentUser: Bool
}

struct Reminder: Codable {
Expand Down Expand Up @@ -90,6 +93,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
let deleteCalendarMethod = "deleteCalendar"
let deleteEventMethod = "deleteEvent"
let deleteEventInstanceMethod = "deleteEventInstance"
let showEventModalMethod = "showiOSEventModal"
let calendarIdArgument = "calendarId"
let startDateArgument = "startDate"
let endDateArgument = "endDate"
Expand Down Expand Up @@ -121,8 +125,11 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
let calendarNameArgument = "calendarName"
let calendarColorArgument = "calendarColor"
let availabilityArgument = "availability"
let attendanceStatusArgument = "attendanceStatus"
let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly]

var flutterResult : FlutterResult?

public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger())
let instance = SwiftDeviceCalendarPlugin()
Expand All @@ -149,6 +156,9 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
createCalendar(call, result)
case deleteCalendarMethod:
deleteCalendar(call, result)
case showEventModalMethod:
self.flutterResult = result
showEventModal(call, result)
default:
result(FlutterMethodNotImplemented)
}
Expand Down Expand Up @@ -287,14 +297,17 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
if specifiedStartEndDates {
let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch!.doubleValue / 1000.0)
let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch!.doubleValue / 1000.0)

if let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) {
let predicate = self.eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: [ekCalendar])
let ekEvents = self.eventStore.events(matching: predicate)
for ekEvent in ekEvents {
let event = createEventFromEkEvent(calendarId: calendarId, ekEvent: ekEvent)
events.append(event)
}
let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId)
if ekCalendar != nil {
let predicate = self.eventStore.predicateForEvents(
withStart: startDate,
end: endDate,
calendars: [ekCalendar!])
let ekEvents = self.eventStore.events(matching: predicate)
for ekEvent in ekEvents {
let event = createEventFromEkEvent(calendarId: calendarId, ekEvent: ekEvent)
events.append(event)
}
}
}

Expand Down Expand Up @@ -373,7 +386,14 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
return nil
}

let attendee = Attendee(name: ekParticipant!.name, emailAddress: ekParticipant!.emailAddress!, role: ekParticipant!.participantRole.rawValue, attendanceStatus: ekParticipant!.participantStatus.rawValue)
let attendee = Attendee(
name: ekParticipant!.name,
emailAddress: ekParticipant!.emailAddress!,
role: ekParticipant!.participantRole.rawValue,
attendanceStatus: ekParticipant!.participantStatus.rawValue,
isCurrentUser: ekParticipant!.isCurrentUser
)

return attendee
}

Expand Down Expand Up @@ -771,6 +791,61 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin {
}
}, result: result)
}

private func showEventModal(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
checkPermissionsThenExecute(permissionsGrantedAction: {
let arguments = call.arguments as! Dictionary<String, AnyObject>
let eventId = arguments[eventIdArgument] as! String
let event = self.eventStore.event(withIdentifier: eventId)

if event != nil {
let eventController = EKEventViewController()
eventController.event = event!
eventController.delegate = self
eventController.allowsEditing = true
eventController.allowsCalendarPreview = true

let flutterViewController = getTopMostViewController()
let navigationController = UINavigationController(rootViewController: eventController)

navigationController.toolbar.isTranslucent = false
navigationController.toolbar.tintColor = .blue
navigationController.toolbar.backgroundColor = .white

flutterViewController.present(navigationController, animated: true, completion: nil)


} else {
result(FlutterError(code: self.genericError, message: self.eventNotFoundErrorMessageFormat, details: nil))
}
}, result: result)
}

public func eventViewController(_ controller: EKEventViewController, didCompleteWith action: EKEventViewAction) {
controller.dismiss(animated: true, completion: nil)

if flutterResult != nil {
switch action {
case .done:
flutterResult!(nil)
case .responded:
flutterResult!(nil)
case .deleted:
flutterResult!(nil)
@unknown default:
flutterResult!(nil)
}
}
}

private func getTopMostViewController() -> UIViewController {
var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
while ((topController?.presentedViewController) != nil) {
topController = topController?.presentedViewController
}

return topController!
}

private func finishWithUnauthorizedError(result: @escaping FlutterResult) {
result(FlutterError(code:self.unauthorizedErrorCode, message: self.unauthorizedErrorMessage, details: nil))
Expand Down
1 change: 1 addition & 0 deletions lib/src/common/channel_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ChannelConstants {
static const String methodNameCreateOrUpdateEvent = 'createOrUpdateEvent';
static const String methodNameCreateCalendar = 'createCalendar';
static const String methodNameDeleteCalendar = 'deleteCalendar';
static const String methodNameShowiOSEventModal = 'showiOSEventModal';

static const String parameterNameCalendarId = 'calendarId';
static const String parameterNameStartDate = 'startDate';
Expand Down
21 changes: 19 additions & 2 deletions lib/src/device_calendar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sprintf/sprintf.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart';

import 'common/channel_constants.dart';
Expand All @@ -14,8 +15,6 @@ import 'models/event.dart';
import 'models/result.dart';
import 'models/retrieve_events_params.dart';

import 'package:timezone/data/latest.dart' as tz;

/// Provides functionality for working with device calendar(s)
class DeviceCalendarPlugin {
static const MethodChannel channel =
Expand Down Expand Up @@ -309,6 +308,24 @@ class DeviceCalendarPlugin {
);
}

/// Displays a native iOS view [EKEventViewController]
/// https://developer.apple.com/documentation/eventkitui/ekeventviewcontroller
///
/// Allows to change the event's attendance status
/// Works only on iOS
/// Returns after dismissing EKEventViewController's dialog
Future<Result<void>> showiOSEventModal(
String eventId,
) {
return _invokeChannelMethod(
ChannelConstants.methodNameShowiOSEventModal,
arguments: () => <String, String>{
ChannelConstants.parameterNameEventId: eventId,
},
);
}


Future<Result<T>> _invokeChannelMethod<T>(
String channelMethodName, {
Function(Result<T>)? assertParameters,
Expand Down
Loading

0 comments on commit bf5a0bc

Please sign in to comment.