From bdd6ba9932876375f098c68dd1ea7bcc9c32c46b Mon Sep 17 00:00:00 2001 From: Nicolas Haan Date: Sun, 21 Feb 2021 08:54:28 +0100 Subject: [PATCH 1/4] add deleteCalendar() function --- .../devicecalendar/CalendarDelegate.kt | 37 ++++++++++++++ .../devicecalendar/DeviceCalendarPlugin.kt | 5 ++ .../presentation/pages/calendar_events.dart | 50 ++++++++++++++++++- example/lib/presentation/pages/calendars.dart | 11 ++++ ios/Classes/SwiftDeviceCalendarPlugin.swift | 29 +++++++++++ lib/src/common/channel_constants.dart | 1 + lib/src/device_calendar.dart | 20 ++++++++ 7 files changed, 152 insertions(+), 1 deletion(-) diff --git a/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt b/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt index 46f80be4..2d62a0bf 100644 --- a/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt +++ b/android/src/main/kotlin/com/builttoroam/devicecalendar/CalendarDelegate.kt @@ -83,6 +83,7 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { private val CREATE_OR_UPDATE_EVENT_REQUEST_CODE = RETRIEVE_CALENDAR_REQUEST_CODE + 1 private val DELETE_EVENT_REQUEST_CODE = CREATE_OR_UPDATE_EVENT_REQUEST_CODE + 1 private val REQUEST_PERMISSIONS_REQUEST_CODE = DELETE_EVENT_REQUEST_CODE + 1 + private val DELETE_CALENDAR_REQUEST_CODE = REQUEST_PERMISSIONS_REQUEST_CODE + 1 private val PART_TEMPLATE = ";%s=" private val BYMONTHDAY_PART = "BYMONTHDAY" private val BYMONTH_PART = "BYMONTH" @@ -141,6 +142,9 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { REQUEST_PERMISSIONS_REQUEST_CODE -> { finishWithSuccess(permissionGranted, cachedValues.pendingChannelResult) } + DELETE_CALENDAR_REQUEST_CODE -> { + deleteCalendar(cachedValues.calendarId,cachedValues.pendingChannelResult) + } } return true @@ -236,6 +240,39 @@ class CalendarDelegate : PluginRegistry.RequestPermissionsResultListener { return null } + fun deleteCalendar(calendarId: String, pendingChannelResult: MethodChannel.Result, isInternalCall: Boolean = false): Calendar? { + if (isInternalCall || arePermissionsGranted()) { + val calendarIdNumber = calendarId.toLongOrNull() + if (calendarIdNumber == null) { + if (!isInternalCall) { + finishWithError(INVALID_ARGUMENT, CALENDAR_ID_INVALID_ARGUMENT_NOT_A_NUMBER_MESSAGE, pendingChannelResult) + } + return null + } + + val contentResolver: ContentResolver? = _context?.contentResolver + + val calendar = retrieveCalendar(calendarId,pendingChannelResult,true); + if(calendar != null) { + val calenderUriWithId = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calendarIdNumber) + val deleteSucceeded = contentResolver?.delete(calenderUriWithId, null, null) ?: 0 + finishWithSuccess(deleteSucceeded > 0, pendingChannelResult) + }else { + if (!isInternalCall) { + finishWithError(NOT_FOUND, "The calendar with the ID $calendarId could not be found", pendingChannelResult) + } + } + } else { + val parameters = CalendarMethodsParametersCacheModel( + pendingChannelResult = pendingChannelResult, + calendarDelegateMethodCode = DELETE_CALENDAR_REQUEST_CODE, + calendarId = calendarId) + requestPermissions(parameters) + } + + return null + } + fun createCalendar(calendarName: String, calendarColor: String?, localAccountName: String, pendingChannelResult: MethodChannel.Result) { val contentResolver: ContentResolver? = _context?.contentResolver diff --git a/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt b/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt index b4b20b36..d49ece49 100644 --- a/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt +++ b/android/src/main/kotlin/com/builttoroam/devicecalendar/DeviceCalendarPlugin.kt @@ -23,6 +23,7 @@ class DeviceCalendarPlugin() : MethodCallHandler { private val DELETE_EVENT_INSTANCE_METHOD = "deleteEventInstance" private val CREATE_OR_UPDATE_EVENT_METHOD = "createOrUpdateEvent" private val CREATE_CALENDAR_METHOD = "createCalendar" + private val DELETE_CALENDAR_METHOD = "deleteCalendar" // Method arguments private val CALENDAR_ID_ARGUMENT = "calendarId" @@ -130,6 +131,10 @@ class DeviceCalendarPlugin() : MethodCallHandler { _calendarDelegate.createCalendar(calendarName!!, calendarColor, localAccountName!!, result) } + DELETE_CALENDAR_METHOD -> { + val calendarId = call.argument(CALENDAR_ID_ARGUMENT) + _calendarDelegate.deleteCalendar(calendarId!!,result) + } else -> { result.notImplemented() } diff --git a/example/lib/presentation/pages/calendar_events.dart b/example/lib/presentation/pages/calendar_events.dart index f05c53a7..79824431 100644 --- a/example/lib/presentation/pages/calendar_events.dart +++ b/example/lib/presentation/pages/calendar_events.dart @@ -40,7 +40,9 @@ class _CalendarEventsPageState extends State { Widget build(BuildContext context) { return Scaffold( key: _scaffoldstate, - appBar: AppBar(title: Text('${_calendar.name} events')), + appBar: AppBar(title: Text('${_calendar.name} events'),actions: [ + _getDeleteButton() + ],), body: ((_calendarEvents?.isNotEmpty ?? false) || _isLoading) ? Stack( children: [ @@ -137,4 +139,50 @@ class _CalendarEventsPageState extends State { _isLoading = false; }); } + + Widget _getDeleteButton() { + return IconButton( + icon: Icon(Icons.delete), + onPressed: () async { + _showDeleteDialog(); + }); + } + + Future _showDeleteDialog() async { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Warning'), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text('This will delete this calendar'), + Text('Are you sure?'), + ], + ), + ), + actions: [ + TextButton( + child: Text('Delete!'), + onPressed: () async { + var returnValue = await _deviceCalendarPlugin.deleteCalendar(_calendar.id); + print("returnValue: ${returnValue.data}, ${returnValue.errors}"); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } } + + diff --git a/example/lib/presentation/pages/calendars.dart b/example/lib/presentation/pages/calendars.dart index 7870ec05..801cbf81 100644 --- a/example/lib/presentation/pages/calendars.dart +++ b/example/lib/presentation/pages/calendars.dart @@ -38,6 +38,9 @@ class _CalendarsPageState extends State { return Scaffold( appBar: AppBar( title: Text('Calendars'), + actions: [ + _getRefreshButton() + ], ), body: Column( children: [ @@ -136,4 +139,12 @@ class _CalendarsPageState extends State { print(e); } } + + Widget _getRefreshButton() { + return IconButton( + icon: Icon(Icons.refresh), + onPressed: () async { + _retrieveCalendars(); + }); + } } diff --git a/ios/Classes/SwiftDeviceCalendarPlugin.swift b/ios/Classes/SwiftDeviceCalendarPlugin.swift index c4c469af..b7398d6d 100644 --- a/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ b/ios/Classes/SwiftDeviceCalendarPlugin.swift @@ -87,6 +87,7 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { let retrieveSourcesMethod = "retrieveSources" let createOrUpdateEventMethod = "createOrUpdateEvent" let createCalendarMethod = "createCalendar" + let deleteCalendarMethod = "deleteCalendar" let deleteEventMethod = "deleteEvent" let deleteEventInstanceMethod = "deleteEventInstance" let calendarIdArgument = "calendarId" @@ -146,6 +147,8 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { deleteEvent(call, result) case createCalendarMethod: createCalendar(call, result) + case deleteCalendarMethod: + deleteCalendar(call, result) default: result(FlutterMethodNotImplemented) } @@ -209,6 +212,32 @@ public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { }, result: result) } + private func deleteCalendar(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + checkPermissionsThenExecute(permissionsGrantedAction: { + let arguments = call.arguments as! Dictionary + let calendarId = arguments[calendarIdArgument] as! String + + let ekCalendar = self.eventStore.calendar(withIdentifier: calendarId) + if ekCalendar == nil { + self.finishWithCalendarNotFoundError(result: result, calendarId: calendarId) + return + } + + if !(ekCalendar!.allowsContentModifications) { + self.finishWithCalendarReadOnlyError(result: result, calendarId: calendarId) + return + } + + do { + try self.eventStore.removeCalendar(ekCalendar!, commit: true) + result(true) + } catch { + self.eventStore.reset() + result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) + } + }, result: result) + } + private func getAccountType(_ sourceType: EKSourceType) -> String { switch (sourceType) { case .local: diff --git a/lib/src/common/channel_constants.dart b/lib/src/common/channel_constants.dart index 723ad303..344e8fc5 100644 --- a/lib/src/common/channel_constants.dart +++ b/lib/src/common/channel_constants.dart @@ -9,6 +9,7 @@ class ChannelConstants { static const String methodNameDeleteEventInstance = 'deleteEventInstance'; static const String methodNameCreateOrUpdateEvent = 'createOrUpdateEvent'; static const String methodNameCreateCalendar = 'createCalendar'; + static const String methodNameDeleteCalendar = 'deleteCalendar'; static const String parameterNameCalendarId = 'calendarId'; static const String parameterNameStartDate = 'startDate'; diff --git a/lib/src/device_calendar.dart b/lib/src/device_calendar.dart index cbac0c26..6be74daf 100644 --- a/lib/src/device_calendar.dart +++ b/lib/src/device_calendar.dart @@ -272,6 +272,26 @@ class DeviceCalendarPlugin { ); } + /// Deletes a calendar. + /// The `calendarId` parameter is the id of the calendar that plugin will try to delete the event from\/// + /// Returns a [Result] indicating if the instance of the calendar has (true) or has not (false) been deleted + Future> deleteCalendar( + String calendarId, + ) async { + return _invokeChannelMethod( + ChannelConstants.methodNameDeleteCalendar, + assertParameters: (result) { + _validateCalendarIdParameter( + result, + calendarId, + ); + }, + arguments: () => { + ChannelConstants.parameterNameCalendarId: calendarId, + }, + ); + } + Future> _invokeChannelMethod( String channelMethodName, { Function(Result) assertParameters, From 25bfa9726af5dd783fb47722b01637b113b94515 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Mon, 1 Mar 2021 18:11:00 +1100 Subject: [PATCH 2/4] Fixing build number for Android build --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7b548a5a..5620a606 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,6 +31,7 @@ stages: displayName: 'Flutter build - Android' inputs: target: 'aab' + buildNumber: $(BuildID) projectDirectory: 'example' - task: FlutterBuild@0 From d5bb03510e90208f0ebb8addf3b8904328917911 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Mon, 1 Mar 2021 18:14:42 +1100 Subject: [PATCH 3/4] Adding Build prefix --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5620a606..e608a2d7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,7 +31,7 @@ stages: displayName: 'Flutter build - Android' inputs: target: 'aab' - buildNumber: $(BuildID) + buildNumber: $(Build.BuildID) projectDirectory: 'example' - task: FlutterBuild@0 From 11ba2ff0cd637499a9a92fc5daff19315ac9d189 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Mon, 1 Mar 2021 18:28:48 +1100 Subject: [PATCH 4/4] Updating ndk version to match build agent --- example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 80892c9d..3d0f0ad3 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -17,7 +17,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 29 - ndkVersion '21.3.6528147' + ndkVersion '22.0.7026061' sourceSets { main.java.srcDirs += 'src/main/kotlin'