From 718e135f200e398ca132d86fabd4a2b4d360cd48 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Thu, 23 Jan 2020 14:14:23 +1100 Subject: [PATCH 01/10] Delete instance added for Android + example app --- .../devicecalendar/CalendarDelegate.kt | 24 ++++++-- .../devicecalendar/DeviceCalendarPlugin.kt | 9 +++ .../example/lib/presentation/event_item.dart | 59 +++++++++++++++---- .../presentation/pages/calendar_event.dart | 38 ++++++++---- device_calendar/lib/src/device_calendar.dart | 32 +++++++++- 5 files changed, 132 insertions(+), 30 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index f0889a51..476ae300 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -74,6 +74,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val BYMONTHDAY_PART = "BYMONTHDAY" private val BYMONTH_PART = "BYMONTH" private val BYSETPOS_PART = "BYSETPOS" + private val INSTANCE_BEGIN = "begin" private val _cachedParametersMap: MutableMap = mutableMapOf() private var _registrar: Registrar? = null @@ -445,7 +446,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } - fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result) { + fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result, startDate: Long? = null, endDate: Long? = null) { if (arePermissionsGranted()) { val existingCal = retrieveCalendar(calendarId, pendingChannelResult, true) if (existingCal == null) { @@ -465,9 +466,24 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } val contentResolver: ContentResolver? = _context?.contentResolver - val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber) - val deleteSucceeded = contentResolver?.delete(eventsUriWithId, null, null) ?: 0 - finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) + if (startDate == null && endDate == null) { + val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber) + val deleteSucceeded = contentResolver?.delete(eventsUriWithId, null, null) ?: 0 + finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) + } + else { + val instanceCursor = CalendarContract.Instances.query(contentResolver, arrayOf(INSTANCE_BEGIN), startDate!!, endDate!!) + instanceCursor.moveToFirst() + + val values = ContentValues() + values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(0)) + values.put(Events.STATUS, Events.STATUS_CANCELED) + + val exceptionUriWithId = ContentUris.withAppendedId(Events.CONTENT_EXCEPTION_URI, eventIdNumber) + + val deleteSucceeded = contentResolver?.insert(exceptionUriWithId, values) + finishWithSuccess(deleteSucceeded != null, pendingChannelResult) + } } else { val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, DELETE_EVENT_REQUEST_CODE, calendarId) parameters.eventId = eventId diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 93f37c9e..02a965a3 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -23,6 +23,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { private val RETRIEVE_CALENDARS_METHOD = "retrieveCalendars" private val RETRIEVE_EVENTS_METHOD = "retrieveEvents" private val DELETE_EVENT_METHOD = "deleteEvent" + private val DELETE_EVENT_INSTANCE_METHOD = "deleteEventInstance" private val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent" // Method arguments @@ -107,6 +108,14 @@ class DeviceCalendarPlugin() : MethodCallHandler { _calendarDelegate.deleteEvent(calendarId!!, eventId!!, result) } + DELETE_EVENT_INSTANCE_METHOD -> { + val calendarId = call.argument(CALENDAR_ID_ARGUMENT) + val eventId = call.argument(EVENT_ID_ARGUMENT) + val startDate = call.argument(EVENT_START_DATE_ARGUMENT) + val endDate = call.argument(EVENT_END_DATE_ARGUMENT) + + _calendarDelegate.deleteEvent(calendarId!!, eventId!!, result, startDate, endDate) + } else -> { result.notImplemented() } diff --git a/device_calendar/example/lib/presentation/event_item.dart b/device_calendar/example/lib/presentation/event_item.dart index 72745e9f..be25f019 100644 --- a/device_calendar/example/lib/presentation/event_item.dart +++ b/device_calendar/example/lib/presentation/event_item.dart @@ -169,12 +169,12 @@ class EventItem extends StatelessWidget { IconButton( onPressed: () async { await showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + if (_calendarEvent.recurrenceRule == null) { return AlertDialog( - title: Text( - 'Are you sure you want to delete this event?'), + title: Text('Are you sure you want to delete this event?'), actions: [ FlatButton( onPressed: () { @@ -186,18 +186,51 @@ class EventItem extends StatelessWidget { onPressed: () async { Navigator.of(context).pop(); _onLoadingStarted(); - final deleteResult = - await _deviceCalendarPlugin.deleteEvent( - _calendarEvent.calendarId, - _calendarEvent.eventId); - _onDeleteFinished(deleteResult.isSuccess && - deleteResult.data); + final deleteResult = await _deviceCalendarPlugin.deleteEvent(_calendarEvent.calendarId, _calendarEvent.eventId); + _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); }, - child: Text('Ok'), + child: Text('Delete'), ), ], ); - }); + } + else { + return AlertDialog( + title: Text('Are you sure you want to delete this event?'), + actions: [ + FlatButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('Cancel'), + ), + FlatButton( + onPressed: () async { + Navigator.of(context).pop(); + _onLoadingStarted(); + final deleteResult = await _deviceCalendarPlugin.deleteEvent(_calendarEvent.calendarId, _calendarEvent.eventId); + _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); + }, + child: Text('Delete All'), + ), + FlatButton( + onPressed: () async { + Navigator.of(context).pop(); + _onLoadingStarted(); + final deleteResult = await _deviceCalendarPlugin.deleteEventInstance( + _calendarEvent.calendarId, + _calendarEvent.eventId, + _calendarEvent.start.millisecondsSinceEpoch, + _calendarEvent.end.millisecondsSinceEpoch); + _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); + }, + child: Text('Delete Instance'), + ), + ], + ); + } + } + ); }, icon: Icon(Icons.delete), ), diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index b52b373c..bd722443 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -591,17 +591,33 @@ class _CalendarEventPageState extends State { ), ), if (!_calendar.isReadOnly && (_event.eventId?.isNotEmpty ?? false)) ...[ - RaisedButton( - key: Key('deleteEventButton'), - textColor: Colors.white, - color: Colors.red, - child: Text('Delete'), - onPressed: () async { - await _deviceCalendarPlugin.deleteEvent( - _calendar.id, _event.eventId); - Navigator.pop(context, true); - }, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + RaisedButton( + key: Key('deleteEventButton'), + textColor: Colors.white, + color: Colors.red, + child: Text(_isRecurringEvent ? 'Delete All' : 'Delete'), + onPressed: () async { + await _deviceCalendarPlugin.deleteEvent(_calendar.id, _event.eventId); + Navigator.pop(context, true); + }, + ), + if (_isRecurringEvent) ...[ + RaisedButton( + key: Key('deleteInstnaceEventButton'), + textColor: Colors.white, + color: Colors.red, + child: Text('Delete Instance'), + onPressed: () async { + await _deviceCalendarPlugin.deleteEventInstance(_calendar.id, _event.eventId, _event.start.millisecondsSinceEpoch, _event.end.millisecondsSinceEpoch); + Navigator.pop(context, true); + }, + ), + ] + ] + ) ] ], ), diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index c67d11d1..e93d6345 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -131,9 +131,10 @@ class DeviceCalendarPlugin { return res; } - /// Deletes an event from a calendar. For a recurring event, this will delete all instances of it + /// Deletes an event from a calendar. For a recurring event, this will delete all instances of it.\ + /// To delete individual instance of a recurring event, please use [deleteEventInstance()] /// - /// The `calendarId` paramter is the id of the calendar that plugin will try to delete the event from + /// The `calendarId` parameter is the id of the calendar that plugin will try to delete the event from\ /// The `eventId` parameter is the id of the event that plugin will try to delete /// /// Returns a [Result] indicating if the event has (true) or has not (false) been deleted from the calendar @@ -156,6 +157,33 @@ class DeviceCalendarPlugin { return res; } + /// Deletes an instance of a recurring event from a calendar. This should be used for a recurring event only. + /// + /// The `calendarId` parameter is the id of the calendar that plugin will try to delete the event from\ + /// The `eventId` parameter is the id of the event that plugin will try to delete\ + /// The `startDate` parameter is the start date of the instance to delete\ + /// The `endDate` parameter is the end date of the instance to delete + /// + /// Returns a [Result] indicating if the instance of the event has (true) or has not (false) been deleted from the calendar + Future> deleteEventInstance(String calendarId, String eventId, int startDate, int endDate) async { + final res = Result(); + + if ((calendarId?.isEmpty ?? true) || (eventId?.isEmpty ?? true)) { + res.errorMessages.add( + '[${ErrorCodes.invalidArguments}] ${ErrorMessages.deleteEventInvalidArgumentsMessage}'); + return res; + } + + try { + res.data = await channel.invokeMethod('deleteEventInstance', + {'calendarId': calendarId, 'eventId': eventId, 'eventStartDate': startDate, 'eventEndDate': endDate}); + } catch (e) { + _parsePlatformExceptionAndUpdateResult(e, res); + } + + return res; + } + /// Creates or updates an event /// /// The `event` paramter specifies how event data should be saved into the calendar From d5f08ce344664465bef6d74c7f44b2a61c7f456b Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Thu, 23 Jan 2020 15:55:17 +1100 Subject: [PATCH 02/10] Delete instance added for iOS --- .../Classes/SwiftDeviceCalendarPlugin.swift | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 8d2899a7..5350f43b 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -75,6 +75,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let retrieveEventsMethod = "retrieveEvents" let createOrUpdateEventMethod = "createOrUpdateEvent" let deleteEventMethod = "deleteEvent" + let deleteEventInstanceMethod = "deleteEventInstance" let calendarIdArgument = "calendarId" let startDateArgument = "startDate" let endDateArgument = "endDate" @@ -123,6 +124,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { createOrUpdateEvent(call, result) case deleteEventMethod: deleteEvent(call, result) + case deleteEventInstanceMethod: + deleteEvent(call, result) default: result(FlutterMethodNotImplemented) } @@ -537,6 +540,9 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let arguments = call.arguments as! Dictionary let calendarId = arguments[calendarIdArgument] as! String let eventId = arguments[eventIdArgument] as! String + let startDateNumber = arguments[eventStartDateArgument] as? NSNumber + let endDateNumber = arguments[eventEndDateArgument] as? NSNumber + let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) if ekCalendar == nil { self.finishWithCalendarNotFoundError(result: result, calendarId: calendarId) @@ -548,18 +554,40 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { return } - let ekEvent = self.eventStore.event(withIdentifier: eventId) - if ekEvent == nil { - self.finishWithEventNotFoundError(result: result, eventId: eventId) - return + if (startDateNumber == nil && endDateNumber == nil) { + let ekEvent = self.eventStore.event(withIdentifier: eventId) + if ekEvent == nil { + self.finishWithEventNotFoundError(result: result, eventId: eventId) + return + } + + do { + try self.eventStore.remove(ekEvent!, span: .futureEvents) + result(true) + } catch { + self.eventStore.reset() + result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) + } } - - do { - try self.eventStore.remove(ekEvent!, span: .futureEvents) - result(true) - } catch { - self.eventStore.reset() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) + else { + let startDate = Date (timeIntervalSince1970: startDateNumber!.doubleValue / 1000.0) + let endDate = Date (timeIntervalSince1970: endDateNumber!.doubleValue / 1000.0) + + let predicate = self.eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil) + let ekEvent = self.eventStore.events(matching: predicate) as [EKEvent]? + + if ekEvent == nil || ekEvent?.count == 0 { + self.finishWithEventNotFoundError(result: result, eventId: eventId) + return + } + + do { + try self.eventStore.remove(ekEvent!.first!, span: .thisEvent, commit: true) + result(true) + } catch { + self.eventStore.reset() + result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) + } } }, result: result) } From 511c5b3b2d44c1371f355c81d8aa240c52d54d20 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Fri, 24 Jan 2020 15:50:29 +1100 Subject: [PATCH 03/10] Added a new dialog for deleting instances - [Android] delete instance and its following works but retrieving afterwards is broken --- .../devicecalendar/CalendarDelegate.kt | 47 ++++++++--- .../devicecalendar/DeviceCalendarPlugin.kt | 4 +- .../example/lib/presentation/event_item.dart | 41 ++------- .../presentation/pages/calendar_event.dart | 70 +++++++++------- .../presentation/pages/calendar_events.dart | 2 +- .../presentation/recurring_event_dialog.dart | 84 +++++++++++++++++++ device_calendar/lib/src/device_calendar.dart | 16 +++- 7 files changed, 185 insertions(+), 79 deletions(-) create mode 100644 device_calendar/example/lib/presentation/recurring_event_dialog.dart diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 476ae300..a9cd4bbc 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -75,6 +75,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val BYMONTH_PART = "BYMONTH" private val BYSETPOS_PART = "BYSETPOS" private val INSTANCE_BEGIN = "begin" + private val EVENT_ID = "event_id" private val _cachedParametersMap: MutableMap = mutableMapOf() private var _registrar: Registrar? = null @@ -446,7 +447,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } - fun deleteEvent(calendarId: String, eventId: String, pendingChannelResult: MethodChannel.Result, startDate: Long? = null, endDate: Long? = null) { + 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) if (existingCal == null) { @@ -466,23 +467,49 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } val contentResolver: ContentResolver? = _context?.contentResolver - if (startDate == null && endDate == null) { + if (startDate == null && endDate == null && followingInstances == null) { // Delete all instances val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber) val deleteSucceeded = contentResolver?.delete(eventsUriWithId, null, null) ?: 0 finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) } else { - val instanceCursor = CalendarContract.Instances.query(contentResolver, arrayOf(INSTANCE_BEGIN), startDate!!, endDate!!) - instanceCursor.moveToFirst() + val exceptionUriWithId = ContentUris.withAppendedId(Events.CONTENT_EXCEPTION_URI, eventIdNumber) - val values = ContentValues() - values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(0)) - values.put(Events.STATUS, Events.STATUS_CANCELED) + if (!followingInstances!!) { // Only this instance + val values = ContentValues() + val instanceCursor = CalendarContract.Instances.query(contentResolver, arrayOf(INSTANCE_BEGIN, EVENT_ID), startDate!!, endDate!!) - val exceptionUriWithId = ContentUris.withAppendedId(Events.CONTENT_EXCEPTION_URI, eventIdNumber) + for (x in 0 until instanceCursor.count) { + instanceCursor.moveToNext() + val foundEventID = instanceCursor.getLong(1) + + if (eventIdNumber == foundEventID) { + values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(0)) + values.put(Events.STATUS, Events.STATUS_CANCELED) + } + } + + val deleteSucceeded = contentResolver?.insert(exceptionUriWithId, values) + finishWithSuccess(deleteSucceeded != null, pendingChannelResult) + } + else { // This and following instances + val instanceCursor = CalendarContract.Instances.query(contentResolver, arrayOf(INSTANCE_BEGIN, EVENT_ID), startDate!!, 8640000000000000) // Max milliseconds: 8640000000000000 - val deleteSucceeded = contentResolver?.insert(exceptionUriWithId, values) - finishWithSuccess(deleteSucceeded != null, pendingChannelResult) + for (x in 0 until instanceCursor.count) { + instanceCursor.moveToNext() + val foundEventID = instanceCursor.getLong(1) + + if (eventIdNumber == foundEventID) { + val values = ContentValues() + values.put(Events.STATUS, Events.STATUS_CANCELED) + values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(0)) + + contentResolver?.insert(exceptionUriWithId, values) + } + } + + finishWithSuccess(true, pendingChannelResult) + } } } else { val parameters = CalendarMethodsParametersCacheModel(pendingChannelResult, DELETE_EVENT_REQUEST_CODE, calendarId) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index 02a965a3..711a1153 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -53,6 +53,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { private val ROLE_ARGUMENT = "role" private val REMINDERS_ARGUMENT = "reminders" private val MINUTES_ARGUMENT = "minutes" + private val FOLLOWING_INSTANCES = "followingInstances" private lateinit var _registrar: Registrar private lateinit var _calendarDelegate: CalendarDelegate @@ -113,8 +114,9 @@ class DeviceCalendarPlugin() : MethodCallHandler { val eventId = call.argument(EVENT_ID_ARGUMENT) val startDate = call.argument(EVENT_START_DATE_ARGUMENT) val endDate = call.argument(EVENT_END_DATE_ARGUMENT) + val followingInstances = call.argument(FOLLOWING_INSTANCES) - _calendarDelegate.deleteEvent(calendarId!!, eventId!!, result, startDate, endDate) + _calendarDelegate.deleteEvent(calendarId!!, eventId!!, result, startDate, endDate, followingInstances) } else -> { result.notImplemented() diff --git a/device_calendar/example/lib/presentation/event_item.dart b/device_calendar/example/lib/presentation/event_item.dart index be25f019..9b8ad524 100644 --- a/device_calendar/example/lib/presentation/event_item.dart +++ b/device_calendar/example/lib/presentation/event_item.dart @@ -2,6 +2,8 @@ import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'recurring_event_dialog.dart'; + class EventItem extends StatelessWidget { final Event _calendarEvent; final DeviceCalendarPlugin _deviceCalendarPlugin; @@ -168,7 +170,7 @@ class EventItem extends StatelessWidget { ), IconButton( onPressed: () async { - await showDialog( + await showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { @@ -195,38 +197,11 @@ class EventItem extends StatelessWidget { ); } else { - return AlertDialog( - title: Text('Are you sure you want to delete this event?'), - actions: [ - FlatButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text('Cancel'), - ), - FlatButton( - onPressed: () async { - Navigator.of(context).pop(); - _onLoadingStarted(); - final deleteResult = await _deviceCalendarPlugin.deleteEvent(_calendarEvent.calendarId, _calendarEvent.eventId); - _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); - }, - child: Text('Delete All'), - ), - FlatButton( - onPressed: () async { - Navigator.of(context).pop(); - _onLoadingStarted(); - final deleteResult = await _deviceCalendarPlugin.deleteEventInstance( - _calendarEvent.calendarId, - _calendarEvent.eventId, - _calendarEvent.start.millisecondsSinceEpoch, - _calendarEvent.end.millisecondsSinceEpoch); - _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); - }, - child: Text('Delete Instance'), - ), - ], + return RecurringEventDialog( + _deviceCalendarPlugin, + _calendarEvent, + _onLoadingStarted, + _onDeleteFinished ); } } diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index bd722443..d13535cb 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; import 'package:collection/collection.dart'; import '../date_time_picker.dart'; +import '../recurring_event_dialog.dart'; import 'event_reminders.dart'; enum RecurrenceRuleEndType { Indefinite, MaxOccurrences, SpecifiedEndDate } @@ -14,11 +15,14 @@ class CalendarEventPage extends StatefulWidget { final Calendar _calendar; final Event _event; - CalendarEventPage(this._calendar, [this._event]); + final VoidCallback _onLoadingStarted; + final Function(bool) _onDeleteFinished; + + CalendarEventPage(this._calendar, [this._event, this._onLoadingStarted, this._onDeleteFinished]); @override _CalendarEventPageState createState() { - return _CalendarEventPageState(_calendar, _event); + return _CalendarEventPageState(_calendar, _event, _onLoadingStarted, _onDeleteFinished); } } @@ -30,6 +34,9 @@ class _CalendarEventPageState extends State { Event _event; DeviceCalendarPlugin _deviceCalendarPlugin; + VoidCallback _onLoadingStarted; + Function(bool) _onDeleteFinished; + DateTime _startDate; TimeOfDay _startTime; @@ -56,7 +63,7 @@ class _CalendarEventPageState extends State { List _attendees = List(); List _reminders = List(); - _CalendarEventPageState(this._calendar, this._event) { + _CalendarEventPageState(this._calendar, this._event, this._onLoadingStarted, this._onDeleteFinished) { _deviceCalendarPlugin = DeviceCalendarPlugin(); _attendees = List(); @@ -591,33 +598,36 @@ class _CalendarEventPageState extends State { ), ), if (!_calendar.isReadOnly && (_event.eventId?.isNotEmpty ?? false)) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - RaisedButton( - key: Key('deleteEventButton'), - textColor: Colors.white, - color: Colors.red, - child: Text(_isRecurringEvent ? 'Delete All' : 'Delete'), - onPressed: () async { - await _deviceCalendarPlugin.deleteEvent(_calendar.id, _event.eventId); - Navigator.pop(context, true); - }, - ), - if (_isRecurringEvent) ...[ - RaisedButton( - key: Key('deleteInstnaceEventButton'), - textColor: Colors.white, - color: Colors.red, - child: Text('Delete Instance'), - onPressed: () async { - await _deviceCalendarPlugin.deleteEventInstance(_calendar.id, _event.eventId, _event.start.millisecondsSinceEpoch, _event.end.millisecondsSinceEpoch); - Navigator.pop(context, true); - }, - ), - ] - ] - ) + RaisedButton( + key: Key('deleteEventButton'), + textColor: Colors.white, + color: Colors.red, + child: Text('Delete'), + onPressed: () async { + var result = true; + if (!_isRecurringEvent) { + await _deviceCalendarPlugin.deleteEvent(_calendar.id, _event.eventId); + } + else { + result = await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return RecurringEventDialog( + _deviceCalendarPlugin, + _event, + _onLoadingStarted, + _onDeleteFinished, + ); + } + ); + } + + if (result) { + Navigator.pop(context, true); + } + }, + ), ] ], ), diff --git a/device_calendar/example/lib/presentation/pages/calendar_events.dart b/device_calendar/example/lib/presentation/pages/calendar_events.dart index 89ece36e..e15d7599 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_events.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_events.dart @@ -110,7 +110,7 @@ class _CalendarEventsPageState extends State { Future _onTapped(Event event) async { final refreshEvents = await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) { - return CalendarEventPage(_calendar, event); + return CalendarEventPage(_calendar, event, _onLoading, _onDeletedFinished); })); if (refreshEvents != null && refreshEvents) { await _retrieveCalendarEvents(); diff --git a/device_calendar/example/lib/presentation/recurring_event_dialog.dart b/device_calendar/example/lib/presentation/recurring_event_dialog.dart new file mode 100644 index 00000000..d994075b --- /dev/null +++ b/device_calendar/example/lib/presentation/recurring_event_dialog.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:device_calendar/device_calendar.dart'; + +class RecurringEventDialog extends StatefulWidget { + final DeviceCalendarPlugin _deviceCalendarPlugin; + final Event _calendarEvent; + + final VoidCallback _onLoadingStarted; + final Function(bool) _onDeleteFinished; + + RecurringEventDialog( + this._deviceCalendarPlugin, + this._calendarEvent, + this._onLoadingStarted, + this._onDeleteFinished, + {Key key}) : super(key: key); + + _RecurringEventDialogState createState() => + _RecurringEventDialogState(_deviceCalendarPlugin, _calendarEvent, onLoadingStarted: _onLoadingStarted, onDeleteFinished: _onDeleteFinished); +} + +class _RecurringEventDialogState extends State { + DeviceCalendarPlugin _deviceCalendarPlugin; + Event _calendarEvent; + VoidCallback _onLoadingStarted; + Function(bool) _onDeleteFinished; + + _RecurringEventDialogState(DeviceCalendarPlugin deviceCalendarPlugin, Event calendarEvent, {VoidCallback onLoadingStarted, Function(bool) onDeleteFinished}) { + _deviceCalendarPlugin = deviceCalendarPlugin; + _calendarEvent = calendarEvent; + _onLoadingStarted = onLoadingStarted; + _onDeleteFinished = onDeleteFinished; + } + + @override + Widget build(BuildContext context) { + return SimpleDialog( + title: Text('Are you sure you want to delete this event?'), + children: [ + SimpleDialogOption( + child: Text('This instance only'), + onPressed: () async { + Navigator.of(context).pop(true); + _onLoadingStarted(); + final deleteResult = await _deviceCalendarPlugin.deleteEventInstance( + _calendarEvent.calendarId, + _calendarEvent.eventId, + _calendarEvent.start.millisecondsSinceEpoch, + _calendarEvent.end.millisecondsSinceEpoch, + false); + _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); + }, + ), + SimpleDialogOption( + child: Text('This and following instances'), + onPressed: () async { + Navigator.of(context).pop(true); + _onLoadingStarted(); + final deleteResult = await _deviceCalendarPlugin.deleteEventInstance( + _calendarEvent.calendarId, + _calendarEvent.eventId, + _calendarEvent.start.millisecondsSinceEpoch, + _calendarEvent.end.millisecondsSinceEpoch, + true); + _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); + }, + ), + SimpleDialogOption( + child: Text('All instances'), + onPressed: () async { + Navigator.of(context).pop(true); + _onLoadingStarted(); + final deleteResult = await _deviceCalendarPlugin.deleteEvent(_calendarEvent.calendarId, _calendarEvent.eventId); + _onDeleteFinished(deleteResult.isSuccess && deleteResult.data); + }, + ), + SimpleDialogOption( + child: Text('Cancel'), + onPressed: () { Navigator.of(context).pop(false); }, + ) + ], + ); + } +} diff --git a/device_calendar/lib/src/device_calendar.dart b/device_calendar/lib/src/device_calendar.dart index e93d6345..4dff63d2 100644 --- a/device_calendar/lib/src/device_calendar.dart +++ b/device_calendar/lib/src/device_calendar.dart @@ -157,15 +157,17 @@ class DeviceCalendarPlugin { return res; } - /// Deletes an instance of a recurring event from a calendar. This should be used for a recurring event only. + /// Deletes an instance of a recurring event from a calendar. This should be used for a recurring event only.\ + /// If `startDate`, `endDate` or `deleteFollowingInstances` is not valid or null, then all instances of the event will be deleted. /// /// The `calendarId` parameter is the id of the calendar that plugin will try to delete the event from\ /// The `eventId` parameter is the id of the event that plugin will try to delete\ /// The `startDate` parameter is the start date of the instance to delete\ - /// The `endDate` parameter is the end date of the instance to delete + /// The `endDate` parameter is the end date of the instance to delete\ + /// The `deleteFollowingInstances` parameter will also delete the following instances if set to true /// /// Returns a [Result] indicating if the instance of the event has (true) or has not (false) been deleted from the calendar - Future> deleteEventInstance(String calendarId, String eventId, int startDate, int endDate) async { + Future> deleteEventInstance(String calendarId, String eventId, int startDate, int endDate, bool deleteFollowingInstances) async { final res = Result(); if ((calendarId?.isEmpty ?? true) || (eventId?.isEmpty ?? true)) { @@ -176,7 +178,13 @@ class DeviceCalendarPlugin { try { res.data = await channel.invokeMethod('deleteEventInstance', - {'calendarId': calendarId, 'eventId': eventId, 'eventStartDate': startDate, 'eventEndDate': endDate}); + { + 'calendarId': calendarId, + 'eventId': eventId, + 'eventStartDate': startDate, + 'eventEndDate': endDate, + 'followingInstances': deleteFollowingInstances + }); } catch (e) { _parsePlatformExceptionAndUpdateResult(e, res); } From faf4b3a831db358bc30f6664974bf964a3c81b29 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Fri, 24 Jan 2020 16:21:51 +1100 Subject: [PATCH 04/10] Recurrence delete functional for iOS --- .../Classes/SwiftDeviceCalendarPlugin.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift index 5350f43b..78fc4e8d 100644 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -102,6 +102,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let roleArgument = "role" let remindersArgument = "reminders" let minutesArgument = "minutes" + let followingInstancesArgument = "followingInstances" let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] public static func register(with registrar: FlutterPluginRegistrar) { @@ -542,6 +543,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let eventId = arguments[eventIdArgument] as! String let startDateNumber = arguments[eventStartDateArgument] as? NSNumber let endDateNumber = arguments[eventEndDateArgument] as? NSNumber + let followingInstances = arguments[followingInstancesArgument] as? Bool let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) if ekCalendar == nil { @@ -554,7 +556,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { return } - if (startDateNumber == nil && endDateNumber == nil) { + if (startDateNumber == nil && endDateNumber == nil && followingInstances == nil) { let ekEvent = self.eventStore.event(withIdentifier: eventId) if ekEvent == nil { self.finishWithEventNotFoundError(result: result, eventId: eventId) @@ -574,15 +576,23 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let endDate = Date (timeIntervalSince1970: endDateNumber!.doubleValue / 1000.0) let predicate = self.eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil) - let ekEvent = self.eventStore.events(matching: predicate) as [EKEvent]? + let foundEkEvents = self.eventStore.events(matching: predicate) as [EKEvent]? - if ekEvent == nil || ekEvent?.count == 0 { + if foundEkEvents == nil || foundEkEvents?.count == 0 { self.finishWithEventNotFoundError(result: result, eventId: eventId) return } + let ekEvent = foundEkEvents!.first(where: {$0.eventIdentifier == eventId}) + do { - try self.eventStore.remove(ekEvent!.first!, span: .thisEvent, commit: true) + if (!followingInstances!) { + try self.eventStore.remove(ekEvent!, span: .thisEvent, commit: true) + } + else { + try self.eventStore.remove(ekEvent!, span: .futureEvents, commit: true) + } + result(true) } catch { self.eventStore.reset() From 1c231e5b642f8af43488c267d00f1dca6accd600 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Tue, 28 Jan 2020 17:24:31 +1100 Subject: [PATCH 05/10] Delete multiple instances is functional for Android --- device_calendar/CHANGELOG.md | 1 + device_calendar/README.md | 4 +- .../devicecalendar/CalendarDelegate.kt | 63 ++++++++++++++----- .../devicecalendar/common/Constants.kt | 14 +++++ 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index 7812b58c..132981f5 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -11,6 +11,7 @@ * Added to retrieve colour for calendars. Thanks to [nadavfima](https://github.com/nadavfima) for the contribution and PR to add colour support for both Android and iOS * Added compatibility with a new Flutter plugin for Android. Thanks to the PR submitted by [RohitKumarMishra](https://github.com/RohitKumarMishra) * [Android] Fixed all day timezone issue [164](https://github.com/builttoroam/flutter_plugins/issues/164) +* Added support for deleting individual or multiple instances of a recurring event for issue [108](https://github.com/builttoroam/flutter_plugins/issues/108) ## 3.0.0+3 3rd February 2020 diff --git a/device_calendar/README.md b/device_calendar/README.md index 026b8ce1..676f24d7 100644 --- a/device_calendar/README.md +++ b/device_calendar/README.md @@ -11,7 +11,9 @@ A cross platform plugin for modifying calendars on the user's device. * Retrieve calendars on the user's device * Retrieve events associated with a calendar * Ability to add, update or delete events from a calendar -* Ability to set up recurring events (NOTE: deleting a recurring event will currently delete all instances of it) +* Ability to set up, edit or delete recurring events + * **NOTE**: Editing a recurring event will currently edit all instances of it + * **NOTE**: Deleting multiple instances in **Android** takes time to update, you'll see the changes after a few seconds * Ability to add, modify or remove attendees and receive if an attendee is an organiser for an event * Ability to setup reminders for an event diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index a9cd4bbc..4fb6dfe8 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -3,6 +3,7 @@ package com.builttoroam.devicecalendar import android.Manifest import android.annotation.SuppressLint import android.app.Activity +import android.app.Service import android.content.ContentResolver import android.content.ContentUris import android.content.ContentValues @@ -37,6 +38,12 @@ import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTIO import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_ID_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_RECURRING_RULE_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_PROJECTION_TITLE_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION +import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_ID_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_RRULE_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_LAST_DATE_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_BEGIN_INDEX +import com.builttoroam.devicecalendar.common.Constants.Companion.EVENT_INSTANCE_DELETION_END_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_MINUTES_INDEX import com.builttoroam.devicecalendar.common.Constants.Companion.REMINDER_PROJECTION import com.builttoroam.devicecalendar.common.DayOfWeek @@ -74,8 +81,6 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val BYMONTHDAY_PART = "BYMONTHDAY" private val BYMONTH_PART = "BYMONTH" private val BYSETPOS_PART = "BYSETPOS" - private val INSTANCE_BEGIN = "begin" - private val EVENT_ID = "event_id" private val _cachedParametersMap: MutableMap = mutableMapOf() private var _registrar: Registrar? = null @@ -473,18 +478,17 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) } else { - val exceptionUriWithId = ContentUris.withAppendedId(Events.CONTENT_EXCEPTION_URI, eventIdNumber) - if (!followingInstances!!) { // Only this instance + val exceptionUriWithId = ContentUris.withAppendedId(Events.CONTENT_EXCEPTION_URI, eventIdNumber) val values = ContentValues() - val instanceCursor = CalendarContract.Instances.query(contentResolver, arrayOf(INSTANCE_BEGIN, EVENT_ID), startDate!!, endDate!!) + val instanceCursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate!!, endDate!!) for (x in 0 until instanceCursor.count) { instanceCursor.moveToNext() - val foundEventID = instanceCursor.getLong(1) + val foundEventID = instanceCursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX) if (eventIdNumber == foundEventID) { - values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(0)) + values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(EVENT_INSTANCE_DELETION_BEGIN_INDEX)) values.put(Events.STATUS, Events.STATUS_CANCELED) } } @@ -493,22 +497,49 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { finishWithSuccess(deleteSucceeded != null, pendingChannelResult) } else { // This and following instances - val instanceCursor = CalendarContract.Instances.query(contentResolver, arrayOf(INSTANCE_BEGIN, EVENT_ID), startDate!!, 8640000000000000) // Max milliseconds: 8640000000000000 + val eventsUriWithId = ContentUris.withAppendedId(Events.CONTENT_URI, eventIdNumber) + val values = ContentValues() + val instanceCursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate!!, endDate!!) for (x in 0 until instanceCursor.count) { instanceCursor.moveToNext() - val foundEventID = instanceCursor.getLong(1) + val foundEventID = instanceCursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX) if (eventIdNumber == foundEventID) { - val values = ContentValues() - values.put(Events.STATUS, Events.STATUS_CANCELED) - values.put(Events.ORIGINAL_INSTANCE_TIME, instanceCursor.getLong(0)) - - contentResolver?.insert(exceptionUriWithId, values) + val newRule = org.dmfs.rfc5545.recur.RecurrenceRule(instanceCursor.getString(EVENT_INSTANCE_DELETION_RRULE_INDEX)) + val lastDate = instanceCursor.getLong(EVENT_INSTANCE_DELETION_LAST_DATE_INDEX) + + if (lastDate > 0 && newRule.count != null && newRule.count > 0) { // Update occurrence rule + val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate, lastDate) + for (y in 0 until cursor.count) { + cursor.moveToNext() + if (eventIdNumber == cursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)) { + newRule.count-- + } + } + cursor.close() + } + else { // Indefinite and specified date rule + val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, 946645200000, startDate - 1) // 946645200000 = 01/01/2000 + var lastRecurrenceDate = 0.toLong() + + for (y in 0 until cursor.count) { + cursor.moveToNext() + if (eventIdNumber == cursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)) { + lastRecurrenceDate = cursor.getLong(EVENT_INSTANCE_DELETION_END_INDEX) + } + } + + newRule.until = DateTime(lastRecurrenceDate) + cursor.close() + } + + values.put(Events.RRULE, newRule.toString()) + contentResolver?.update(eventsUriWithId, values, null, null) + instanceCursor.close() + finishWithSuccess(true, pendingChannelResult) } } - - finishWithSuccess(true, pendingChannelResult) } } } else { diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt index 2c574543..3c37ba0f 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/common/Constants.kt @@ -58,6 +58,20 @@ class Constants { CalendarContract.Events.CUSTOM_APP_URI ) + const val EVENT_INSTANCE_DELETION_ID_INDEX: Int = 0 + const val EVENT_INSTANCE_DELETION_RRULE_INDEX: Int = 1 + const val EVENT_INSTANCE_DELETION_LAST_DATE_INDEX: Int = 2 + const val EVENT_INSTANCE_DELETION_BEGIN_INDEX: Int = 3 + const val EVENT_INSTANCE_DELETION_END_INDEX: Int = 4 + + val EVENT_INSTANCE_DELETION: Array = arrayOf( + CalendarContract.Instances.EVENT_ID, + CalendarContract.Events.RRULE, + CalendarContract.Events.LAST_DATE, + CalendarContract.Instances.BEGIN, + CalendarContract.Instances.END + ) + const val ATTENDEE_ID_INDEX: Int = 0 const val ATTENDEE_EVENT_ID_INDEX: Int = 1 const val ATTENDEE_NAME_INDEX: Int = 2 From a2a200fbbf3cd481312060e71d823221b229576e Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Wed, 29 Jan 2020 15:34:40 +1100 Subject: [PATCH 06/10] Fixed to show snack bar message --- .../com/builttoroam/devicecalendar/CalendarDelegate.kt | 1 - .../example/lib/presentation/pages/calendar_events.dart | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 4fb6dfe8..d1af5de8 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -3,7 +3,6 @@ package com.builttoroam.devicecalendar import android.Manifest import android.annotation.SuppressLint import android.app.Activity -import android.app.Service import android.content.ContentResolver import android.content.ContentUris import android.content.ContentValues diff --git a/device_calendar/example/lib/presentation/pages/calendar_events.dart b/device_calendar/example/lib/presentation/pages/calendar_events.dart index e15d7599..e33fa171 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_events.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_events.dart @@ -19,7 +19,7 @@ class CalendarEventsPage extends StatefulWidget { class _CalendarEventsPageState extends State { final Calendar _calendar; - BuildContext _scaffoldContext; + final GlobalKey _scaffoldstate = GlobalKey(); DeviceCalendarPlugin _deviceCalendarPlugin; List _calendarEvents; @@ -38,6 +38,7 @@ class _CalendarEventsPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + key: _scaffoldstate, appBar: AppBar(title: Text('${_calendar.name} events')), body: (_calendarEvents?.isNotEmpty ?? false) ? Stack( @@ -96,7 +97,7 @@ class _CalendarEventsPageState extends State { if (deleteSucceeded) { await _retrieveCalendarEvents(); } else { - Scaffold.of(_scaffoldContext).showSnackBar(SnackBar( + _scaffoldstate.currentState.showSnackBar(SnackBar( content: Text('Oops, we ran into an issue deleting the event'), backgroundColor: Colors.red, duration: Duration(seconds: 5), From 6545f6325e412de3dac796cbf0ea8b484c06d428 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Thu, 5 Mar 2020 14:32:27 +1100 Subject: [PATCH 07/10] Remove callbacks for pages and use widget --- .../presentation/pages/calendar_event.dart | 22 ++++++------------- .../presentation/pages/calendar_events.dart | 9 +++++++- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index d13535cb..91f5ad20 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -14,15 +14,13 @@ enum RecurrenceRuleEndType { Indefinite, MaxOccurrences, SpecifiedEndDate } class CalendarEventPage extends StatefulWidget { final Calendar _calendar; final Event _event; + final RecurringEventDialog _recurringEventDialog; - final VoidCallback _onLoadingStarted; - final Function(bool) _onDeleteFinished; - - CalendarEventPage(this._calendar, [this._event, this._onLoadingStarted, this._onDeleteFinished]); + CalendarEventPage(this._calendar, [this._event, this._recurringEventDialog]); @override _CalendarEventPageState createState() { - return _CalendarEventPageState(_calendar, _event, _onLoadingStarted, _onDeleteFinished); + return _CalendarEventPageState(_calendar, _event, _recurringEventDialog); } } @@ -33,9 +31,7 @@ class _CalendarEventPageState extends State { Event _event; DeviceCalendarPlugin _deviceCalendarPlugin; - - VoidCallback _onLoadingStarted; - Function(bool) _onDeleteFinished; + RecurringEventDialog _recurringEventDialog; DateTime _startDate; TimeOfDay _startTime; @@ -63,8 +59,9 @@ class _CalendarEventPageState extends State { List _attendees = List(); List _reminders = List(); - _CalendarEventPageState(this._calendar, this._event, this._onLoadingStarted, this._onDeleteFinished) { + _CalendarEventPageState(this._calendar, this._event, this._recurringEventDialog) { _deviceCalendarPlugin = DeviceCalendarPlugin(); + _recurringEventDialog = this._recurringEventDialog; _attendees = List(); _reminders = List(); @@ -613,12 +610,7 @@ class _CalendarEventPageState extends State { context: context, barrierDismissible: false, builder: (BuildContext context) { - return RecurringEventDialog( - _deviceCalendarPlugin, - _event, - _onLoadingStarted, - _onDeleteFinished, - ); + return _recurringEventDialog; } ); } diff --git a/device_calendar/example/lib/presentation/pages/calendar_events.dart b/device_calendar/example/lib/presentation/pages/calendar_events.dart index e33fa171..3a52a75d 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_events.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_events.dart @@ -4,6 +4,7 @@ import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; import '../event_item.dart'; +import '../recurring_event_dialog.dart'; import 'calendar_event.dart'; class CalendarEventsPage extends StatefulWidget { @@ -111,7 +112,13 @@ class _CalendarEventsPageState extends State { Future _onTapped(Event event) async { final refreshEvents = await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) { - return CalendarEventPage(_calendar, event, _onLoading, _onDeletedFinished); + return CalendarEventPage(_calendar, event, + RecurringEventDialog( + _deviceCalendarPlugin, + event, + _onLoading, + _onDeletedFinished, + ),); })); if (refreshEvents != null && refreshEvents) { await _retrieveCalendarEvents(); From b920f2d7552fe9c855d4593d72335184762ee342 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Fri, 6 Mar 2020 10:39:42 +1100 Subject: [PATCH 08/10] Apply DayOfWeekGroup enum update --- .../example/lib/presentation/pages/calendar_event.dart | 6 ++---- device_calendar/lib/src/common/calendar_enums.dart | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 91f5ad20..98b735bd 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -747,8 +747,6 @@ class _CalendarEventPageState extends State { case DayOfWeekGroup.None: _daysOfWeek.clear(); break; - default: // DayOfWeekGroup.Custom, it shouldn't be changed - break; } } @@ -771,9 +769,9 @@ class _CalendarEventPageState extends State { else if (deepEquality(_daysOfWeek, DayOfWeekGroup.Alldays.getDays) && _dayOfWeekGroup != DayOfWeekGroup.Alldays) { _dayOfWeekGroup = DayOfWeekGroup.Alldays; } - // Otherwise, set as Custom + // Otherwise null else { - _dayOfWeekGroup = DayOfWeekGroup.Custom; + _dayOfWeekGroup = null; } } diff --git a/device_calendar/lib/src/common/calendar_enums.dart b/device_calendar/lib/src/common/calendar_enums.dart index f2266ab4..0c9313ef 100644 --- a/device_calendar/lib/src/common/calendar_enums.dart +++ b/device_calendar/lib/src/common/calendar_enums.dart @@ -12,8 +12,7 @@ enum DayOfWeekGroup { None, Weekday, Weekend, - Alldays, - Custom + Alldays } enum MonthOfYear { From 1d9e7f2d7b9f7542a0bdf5df79e87517042abf14 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Mon, 9 Mar 2020 11:04:45 +1100 Subject: [PATCH 09/10] Update CHANGELOG.md --- device_calendar/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/CHANGELOG.md b/device_calendar/CHANGELOG.md index 132981f5..1c484dee 100644 --- a/device_calendar/CHANGELOG.md +++ b/device_calendar/CHANGELOG.md @@ -7,7 +7,7 @@ * Updated property summaries for issues [121](https://github.com/builttoroam/flutter_plugins/issues/121) and [122](https://github.com/builttoroam/flutter_plugins/issues/122) * Updated example documentation for issue [119](https://github.com/builttoroam/flutter_plugins/issues/119) * Read-only calendars cannot be edited or deleted for the example app -* Added `DayOfWeekGroup` enum and an extension `getDays` to get corresponding dates of the enum values (**NOTE**: `DayOfWeekGroup.Custom.getDays` will return an empty list) +* Added `DayOfWeekGroup` enum and an extension `getDays` to get corresponding dates of the enum values * Added to retrieve colour for calendars. Thanks to [nadavfima](https://github.com/nadavfima) for the contribution and PR to add colour support for both Android and iOS * Added compatibility with a new Flutter plugin for Android. Thanks to the PR submitted by [RohitKumarMishra](https://github.com/RohitKumarMishra) * [Android] Fixed all day timezone issue [164](https://github.com/builttoroam/flutter_plugins/issues/164) From 48b0ceb8a64d593a0bfc6467dfa7364eab0deafb Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Wed, 18 Mar 2020 16:06:13 +1100 Subject: [PATCH 10/10] Update loops and search logic --- .../devicecalendar/CalendarDelegate.kt | 26 ++++++++++--------- .../presentation/pages/calendar_event.dart | 6 ++--- .../lib/src/common/calendar_enums.dart | 4 +-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index d1af5de8..07ec806c 100644 --- a/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/device_calendar/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -482,8 +482,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val values = ContentValues() val instanceCursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate!!, endDate!!) - for (x in 0 until instanceCursor.count) { - instanceCursor.moveToNext() + while (instanceCursor.moveToNext()) { val foundEventID = instanceCursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX) if (eventIdNumber == foundEventID) { @@ -493,6 +492,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { } val deleteSucceeded = contentResolver?.insert(exceptionUriWithId, values) + instanceCursor.close() finishWithSuccess(deleteSucceeded != null, pendingChannelResult) } else { // This and following instances @@ -500,8 +500,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { val values = ContentValues() val instanceCursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate!!, endDate!!) - for (x in 0 until instanceCursor.count) { - instanceCursor.moveToNext() + while (instanceCursor.moveToNext()) { val foundEventID = instanceCursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX) if (eventIdNumber == foundEventID) { @@ -510,8 +509,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { if (lastDate > 0 && newRule.count != null && newRule.count > 0) { // Update occurrence rule val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate, lastDate) - for (y in 0 until cursor.count) { - cursor.moveToNext() + while (cursor.moveToNext()) { if (eventIdNumber == cursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)) { newRule.count-- } @@ -519,26 +517,30 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { cursor.close() } else { // Indefinite and specified date rule - val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, 946645200000, startDate - 1) // 946645200000 = 01/01/2000 - var lastRecurrenceDate = 0.toLong() + val cursor = CalendarContract.Instances.query(contentResolver, EVENT_INSTANCE_DELETION, startDate - DateUtils.YEAR_IN_MILLIS, startDate - 1) + var lastRecurrenceDate: Long? = null - for (y in 0 until cursor.count) { - cursor.moveToNext() + while (cursor.moveToNext()) { if (eventIdNumber == cursor.getLong(EVENT_INSTANCE_DELETION_ID_INDEX)) { lastRecurrenceDate = cursor.getLong(EVENT_INSTANCE_DELETION_END_INDEX) } } - newRule.until = DateTime(lastRecurrenceDate) + if (lastRecurrenceDate != null) { + newRule.until = DateTime(lastRecurrenceDate) + } + else { + newRule.until = DateTime(startDate - 1) + } cursor.close() } values.put(Events.RRULE, newRule.toString()) contentResolver?.update(eventsUriWithId, values, null, null) - instanceCursor.close() finishWithSuccess(true, pendingChannelResult) } } + instanceCursor.close() } } } else { diff --git a/device_calendar/example/lib/presentation/pages/calendar_event.dart b/device_calendar/example/lib/presentation/pages/calendar_event.dart index 98b735bd..73c5caff 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_event.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_event.dart @@ -740,7 +740,7 @@ class _CalendarEventPageState extends State { switch (_dayOfWeekGroup) { case DayOfWeekGroup.Weekday: case DayOfWeekGroup.Weekend: - case DayOfWeekGroup.Alldays: + case DayOfWeekGroup.AllDays: _daysOfWeek.clear(); _daysOfWeek.addAll(days.where((a) => _daysOfWeek.every((b) => a != b))); break; @@ -766,8 +766,8 @@ class _CalendarEventPageState extends State { _dayOfWeekGroup = DayOfWeekGroup.Weekend; } // If _daysOfWeek contains all days - else if (deepEquality(_daysOfWeek, DayOfWeekGroup.Alldays.getDays) && _dayOfWeekGroup != DayOfWeekGroup.Alldays) { - _dayOfWeekGroup = DayOfWeekGroup.Alldays; + else if (deepEquality(_daysOfWeek, DayOfWeekGroup.AllDays.getDays) && _dayOfWeekGroup != DayOfWeekGroup.AllDays) { + _dayOfWeekGroup = DayOfWeekGroup.AllDays; } // Otherwise null else { diff --git a/device_calendar/lib/src/common/calendar_enums.dart b/device_calendar/lib/src/common/calendar_enums.dart index 0c9313ef..a7d4b16a 100644 --- a/device_calendar/lib/src/common/calendar_enums.dart +++ b/device_calendar/lib/src/common/calendar_enums.dart @@ -12,7 +12,7 @@ enum DayOfWeekGroup { None, Weekday, Weekend, - Alldays + AllDays } enum MonthOfYear { @@ -76,7 +76,7 @@ extension DaysOfWeekGroupExtension on DayOfWeekGroup { return [DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday]; case DayOfWeekGroup.Weekend: return [DayOfWeek.Saturday, DayOfWeek.Sunday]; - case DayOfWeekGroup.Alldays: + case DayOfWeekGroup.AllDays: return [DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday]; default: return [];