From f1b19d5c1a5c5e24b04b2ffd899800579d199204 Mon Sep 17 00:00:00 2001 From: Brett Lim Date: Sat, 21 Mar 2020 10:41:45 +1100 Subject: [PATCH 01/15] Removed a redundant block (#194) --- .../example/lib/presentation/pages/calendar_add.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/device_calendar/example/lib/presentation/pages/calendar_add.dart b/device_calendar/example/lib/presentation/pages/calendar_add.dart index 16dc2e7c..96deb056 100644 --- a/device_calendar/example/lib/presentation/pages/calendar_add.dart +++ b/device_calendar/example/lib/presentation/pages/calendar_add.dart @@ -65,7 +65,7 @@ class _CalendarAddPageState extends State { ), ], ), - if (Platform.isAndroid) ...[ + if (Platform.isAndroid) TextFormField( decoration: const InputDecoration( labelText: 'Local Account Name', @@ -73,7 +73,6 @@ class _CalendarAddPageState extends State { ), onSaved: (String value) => _localAccountName = value, ), - ] ], ), ), From 21dca841be17e37e340f8510ace7d7dbbd988a3d Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Tue, 31 Mar 2020 19:04:06 +0300 Subject: [PATCH 02/15] change ios part - migration to Objective-c --- device_calendar/example/ios/Podfile | 1 + device_calendar/example/ios/Podfile.lock | 12 +- .../ios/Runner.xcodeproj/project.pbxproj | 18 +- .../example/ios/Runner/AppDelegate.h | 6 + .../example/ios/Runner/AppDelegate.m | 13 + .../example/ios/Runner/AppDelegate.swift | 13 - .../ios/Runner/Runner-Bridging-Header.h | 1 - device_calendar/example/ios/Runner/main.m | 9 + device_calendar/ios/Classes/Date+Utilities.h | 11 + device_calendar/ios/Classes/Date+Utilities.m | 10 + .../Classes/DeviceCalendarExtendedPlugin.h | 4 + .../Classes/DeviceCalendarExtendedPlugin.m | 8 + .../ios/Classes/DeviceCalendarPlugin.h | 5 +- .../ios/Classes/DeviceCalendarPlugin.m | 702 +++++++++++++++- .../ios/Classes/EKParticipant+Utilities.h | 12 + .../ios/Classes/EKParticipant+Utilities.m | 9 + .../Classes/SwiftDeviceCalendarPlugin.swift | 783 ------------------ .../ios/Classes/UIColor+Utilities.h | 7 + .../ios/Classes/UIColor+Utilities.m | 22 + device_calendar/ios/Classes/models/Attendee.h | 11 + device_calendar/ios/Classes/models/Attendee.m | 6 + device_calendar/ios/Classes/models/Calendar.h | 14 + device_calendar/ios/Classes/models/Calendar.m | 6 + .../ios/Classes/models/Department.h | 7 + .../ios/Classes/models/Department.m | 6 + device_calendar/ios/Classes/models/Event.h | 21 + device_calendar/ios/Classes/models/Event.m | 6 + .../ios/Classes/models/RecurrenceRule.h | 16 + .../ios/Classes/models/RecurrenceRule.m | 6 + device_calendar/ios/Classes/models/Reminder.h | 8 + device_calendar/ios/Classes/models/Reminder.m | 6 + device_calendar/ios/device_calendar.podspec | 3 +- 32 files changed, 952 insertions(+), 810 deletions(-) create mode 100644 device_calendar/example/ios/Runner/AppDelegate.h create mode 100644 device_calendar/example/ios/Runner/AppDelegate.m delete mode 100644 device_calendar/example/ios/Runner/AppDelegate.swift delete mode 100644 device_calendar/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 device_calendar/example/ios/Runner/main.m create mode 100644 device_calendar/ios/Classes/Date+Utilities.h create mode 100644 device_calendar/ios/Classes/Date+Utilities.m create mode 100644 device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h create mode 100644 device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m create mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.h create mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.m delete mode 100644 device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift create mode 100644 device_calendar/ios/Classes/UIColor+Utilities.h create mode 100644 device_calendar/ios/Classes/UIColor+Utilities.m create mode 100644 device_calendar/ios/Classes/models/Attendee.h create mode 100644 device_calendar/ios/Classes/models/Attendee.m create mode 100644 device_calendar/ios/Classes/models/Calendar.h create mode 100644 device_calendar/ios/Classes/models/Calendar.m create mode 100644 device_calendar/ios/Classes/models/Department.h create mode 100644 device_calendar/ios/Classes/models/Department.m create mode 100644 device_calendar/ios/Classes/models/Event.h create mode 100644 device_calendar/ios/Classes/models/Event.m create mode 100644 device_calendar/ios/Classes/models/RecurrenceRule.h create mode 100644 device_calendar/ios/Classes/models/RecurrenceRule.m create mode 100644 device_calendar/ios/Classes/models/Reminder.h create mode 100644 device_calendar/ios/Classes/models/Reminder.m diff --git a/device_calendar/example/ios/Podfile b/device_calendar/example/ios/Podfile index 2c347942..5394ab7e 100644 --- a/device_calendar/example/ios/Podfile +++ b/device_calendar/example/ios/Podfile @@ -48,6 +48,7 @@ target 'Runner' do } # Plugin Pods + pod 'JSONModel' plugin_pods = parse_KV_file('../.flutter-plugins') plugin_pods.map { |p| symlink = File.join('Pods', '.symlinks', 'plugins', p[:name]) diff --git a/device_calendar/example/ios/Podfile.lock b/device_calendar/example/ios/Podfile.lock index b81b0078..5951ee35 100644 --- a/device_calendar/example/ios/Podfile.lock +++ b/device_calendar/example/ios/Podfile.lock @@ -1,11 +1,18 @@ PODS: - device_calendar (0.0.1): - Flutter + - JSONModel - Flutter (1.0.0) + - JSONModel (1.8.0) DEPENDENCIES: - device_calendar (from `Pods/.symlinks/plugins/device_calendar/ios`) - Flutter (from `Pods/.symlinks/flutter/ios`) + - JSONModel + +SPEC REPOS: + trunk: + - JSONModel EXTERNAL SOURCES: device_calendar: @@ -14,9 +21,10 @@ EXTERNAL SOURCES: :path: Pods/.symlinks/flutter/ios SPEC CHECKSUMS: - device_calendar: 23b28a5f1ab3bf77e34542fb1167e1b8b29a98f5 + device_calendar: fb103d17cdd72b294a9c464b811e903566f04e67 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + JSONModel: 02ab723958366a3fd27da57ea2af2113658762e9 -PODFILE CHECKSUM: ea0518673586564c605fb6593d385c0e3708ff8d +PODFILE CHECKSUM: d833004bd9a416c1f9c3fb12831e1203a373fbfb COCOAPODS: 1.8.4 diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 6a2dbac0..001fe62c 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,11 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 0904E8AC2433323B00DFD5BE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0904E8AB2433323B00DFD5BE /* AppDelegate.m */; }; + 0904E8AF2433360500DFD5BE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0904E8AE2433360500DFD5BE /* main.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 763C5C3662C48FEF7F2B0120 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E098C60D243A71853922C094 /* Pods_Runner.framework */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -36,13 +37,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0904E8AB2433323B00DFD5BE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 0904E8AD2433325300DFD5BE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 0904E8AE2433360500DFD5BE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2AD784C0989B63BAA46EFDFD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -129,8 +131,9 @@ 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 0904E8AB2433323B00DFD5BE /* AppDelegate.m */, + 0904E8AD2433325300DFD5BE /* AppDelegate.h */, + 0904E8AE2433360500DFD5BE /* main.m */, ); path = Runner; sourceTree = ""; @@ -224,11 +227,13 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/JSONModel/JSONModel.framework", "${BUILT_PRODUCTS_DIR}/device_calendar/device_calendar.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JSONModel.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_calendar.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -289,8 +294,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 0904E8AF2433360500DFD5BE /* main.m in Sources */, + 0904E8AC2433323B00DFD5BE /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/device_calendar/example/ios/Runner/AppDelegate.h b/device_calendar/example/ios/Runner/AppDelegate.h new file mode 100644 index 00000000..36e21bbf --- /dev/null +++ b/device_calendar/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/device_calendar/example/ios/Runner/AppDelegate.m b/device_calendar/example/ios/Runner/AppDelegate.m new file mode 100644 index 00000000..f699984e --- /dev/null +++ b/device_calendar/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application +didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/device_calendar/example/ios/Runner/AppDelegate.swift b/device_calendar/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 70693e4a..00000000 --- a/device_calendar/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/device_calendar/example/ios/Runner/Runner-Bridging-Header.h b/device_calendar/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 7335fdf9..00000000 --- a/device_calendar/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/device_calendar/example/ios/Runner/main.m b/device_calendar/example/ios/Runner/main.m new file mode 100644 index 00000000..0341bdd0 --- /dev/null +++ b/device_calendar/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/device_calendar/ios/Classes/Date+Utilities.h b/device_calendar/ios/Classes/Date+Utilities.h new file mode 100644 index 00000000..35e3d1ab --- /dev/null +++ b/device_calendar/ios/Classes/Date+Utilities.h @@ -0,0 +1,11 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSDate (Utilities) + +@property (readonly) float millisecondsSinceEpoch; + +@end + +NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/Date+Utilities.m b/device_calendar/ios/Classes/Date+Utilities.m new file mode 100644 index 00000000..a264be75 --- /dev/null +++ b/device_calendar/ios/Classes/Date+Utilities.m @@ -0,0 +1,10 @@ +#import "Date+Utilities.h" + +@implementation NSDate (Utilities) + +- (float) millisecondsSinceEpoch +{ + return [self timeIntervalSince1970] * 1000.0; +} + +@end diff --git a/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h new file mode 100644 index 00000000..831b6bd7 --- /dev/null +++ b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface DeviceCalendarExtendedPlugin : NSObject +@end diff --git a/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m new file mode 100644 index 00000000..ac2a7534 --- /dev/null +++ b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m @@ -0,0 +1,8 @@ +#import "DeviceCalendarExtendedPlugin.h" +#import "DeviceCalendarPlugin.h" + +@implementation DeviceCalendarExtendedPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [DeviceCalendarPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.h b/device_calendar/ios/Classes/DeviceCalendarPlugin.h index 0d5ad0b9..60894a85 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.h +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.h @@ -1,4 +1,7 @@ #import +#import -@interface DeviceCalendarPlugin : NSObject +@interface DeviceCalendarPlugin: NSObject @end + + diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 774b46d3..9e762fa9 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -1,8 +1,704 @@ #import "DeviceCalendarPlugin.h" -#import +#import +#import +#import +#import +#import "models/Calendar.h" +#import "models/RecurrenceRule.h" +#import "models/Event.h" +#import "models/Attendee.h" +#import "models/Reminder.h" +#import "models/Department.h" +#import "Date+Utilities.h" +#import "UIColor+Utilities.h" +#import "EKParticipant+Utilities.h" +#import @implementation DeviceCalendarPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftDeviceCalendarPlugin registerWithRegistrar:registrar]; +NSString *streamName = @"calendarChangeEvent/stream"; +NSString *methodChannelName = @"plugins.builttoroam.com/device_calendar"; +NSString *notFoundErrorCode = @"404"; +NSString *notAllowed = @"405"; +NSString *genericError = @"500"; +NSString *unauthorizedErrorCode = @"401"; +NSString *unauthorizedErrorMessage = @"The user has not allowed this application to modify their calendar(s)"; +NSString *calendarNotFoundErrorMessageFormat = @"The calendar with the ID %@ could not be found"; +NSString *calendarReadOnlyErrorMessageFormat = @"Calendar with ID %@ is read-only"; +NSString *eventNotFoundErrorMessageFormat = @"The event with the ID %@ could not be found"; +NSString *requestPermissionsMethod = @"requestPermissions"; +NSString *hasPermissionsMethod = @"hasPermissions"; +NSString *retrieveSourcesMethod = @"retrieveSources"; +NSString *retrieveCalendarsMethod = @"retrieveCalendars"; +NSString *retrieveEventsMethod = @"retrieveEvents"; +NSString *createCalendarMethod = @"createCalendar"; +NSString *createOrUpdateEventMethod = @"createOrUpdateEvent"; +NSString *deleteEventMethod = @"deleteEvent"; +NSString *deleteEventInstanceMethod = @"deleteEventInstance"; +NSString *calendarIdArgument = @"calendarId"; +NSString *startDateArgument = @"startDate"; +NSString *endDateArgument = @"endDate"; +NSString *eventIdArgument = @"eventId"; +NSString *eventIdsArgument = @"eventIds"; +NSString *eventTitleArgument = @"eventTitle"; +NSString *eventDescriptionArgument = @"eventDescription"; +NSString *eventAllDayArgument = @"eventAllDay"; +NSString *eventStartDateArgument = @"eventStartDate"; +NSString *eventEndDateArgument = @"eventEndDate"; +NSString *eventLocationArgument = @"eventLocation"; +NSString *attendeesArgument = @"attendees"; +NSString *recurrenceRuleArgument = @"recurrenceRule"; +NSString *recurrenceFrequencyArgument = @"recurrenceFrequency"; +NSString *totalOccurrencesArgument = @"totalOccurrences"; +NSString *intervalArgument = @"interval"; +NSString *daysOfWeekArgument = @"daysOfWeek"; +NSString *daysOfMonthArgument = @"daysOfMonth"; +NSString *monthOfYearArgument = @"monthsOfYear"; +NSString *weeksOfYearArgument = @"weeksOfYear"; +NSString *weekOfMonthArgument = @"weekOfMonth"; +NSString *nameArgument = @"nameArgument"; +NSString *emailAddressArgument = @"emailAddress"; +NSString *roleArgument = @"role"; +NSString *remindersArgument = @"reminders"; +NSString *minutesArgument = @"minutes"; +NSString *followingInstancesArgument = @"followingInstances"; +NSString *calendarNameArgument = @"calendarName"; +NSString *calendarColorArgument = @"calendarColor"; +NSMutableArray *validFrequencyTypes; +EKEventStore *eventStore; + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel + methodChannelWithName: methodChannelName + binaryMessenger:[registrar messenger]]; + eventStore = [[EKEventStore alloc] init]; + validFrequencyTypes = [NSMutableArray new]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyDaily]]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyWeekly]]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyMonthly]]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyYearly]]; + DeviceCalendarPlugin* instance = [[DeviceCalendarPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *method = call.method; + if ([method isEqualToString: requestPermissionsMethod]) { + [self requestPermissions:nil result:result]; + return; + } + else if([method isEqualToString: hasPermissionsMethod]) { + [self hasPermissions:nil result:result]; + return; + } + else if ([method isEqualToString: retrieveCalendarsMethod]) { + [self retrieveCalendars:nil result:result]; + return; + } + else if ([method isEqualToString: retrieveEventsMethod]) { + [self retrieveEvents:call result:result]; + return; + } + else if ([method isEqualToString: createOrUpdateEventMethod]) { + [self createOrUpdateEvent:call result:result]; + return; + } + else if ([method isEqualToString: deleteEventMethod]) { + [self deleteEvent:call result:result]; + return; + } + else if ([method isEqualToString:deleteEventInstanceMethod]) { + [self deleteEvent:call result:result]; + return; + } + else if ([method isEqualToString:createCalendarMethod]) { + [self createCalendar:call result:result]; + return; + } + else + result(FlutterMethodNotImplemented); +} + +-(void)hasPermissions:(id)args result:(FlutterResult)result { + result([NSNumber numberWithBool:[self hasEventPermissions]]); +} + +-(void)createCalendar:(FlutterMethodCall *)call result:(FlutterResult)result { + NSDictionary *arguments = call.arguments; + EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:eventStore]; + calendar.title = [arguments valueForKey:calendarNameArgument]; + NSString *calendarColor = [arguments valueForKey:calendarColorArgument]; + NSMutableArray *localSources = [NSMutableArray new]; + NSError *error = nil; + if (calendarColor != nil) { + calendar.CGColor = [[self colorFromHexString:calendarColor] CGColor]; + }else { + calendar.CGColor = [[[UIColor alloc] initWithRed:255 green:0 blue:0 alpha:0] CGColor]; + } + for (EKSource *ekSource in eventStore.sources) { + if (ekSource.sourceType == EKSourceTypeLocal) { + [localSources addObject:ekSource]; + } + } + if ([localSources count] == 0) { + calendar.source = [localSources firstObject]; + [eventStore saveCalendar:calendar commit:YES error:&error]; + } else { + result([FlutterError errorWithCode:genericError message: @"Local calendar was not found." details:nil ]); + } + if (error == nil) { + result([calendar calendarIdentifier]); + } else { + [eventStore reset]; + result([FlutterError errorWithCode:genericError message: [error localizedDescription] details:nil ]); + } +} + +- (UIColor *)colorFromHexString:(NSString *)hexString { + unsigned rgbValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + [scanner setScanLocation:1]; // bypass '#' character + [scanner scanHexInt:&rgbValue]; + return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 24)/255.0 green:((rgbValue & 0xFF00) >> 16)/255.0 blue:((rgbValue & 0x0000ff00) >> 8)/255.0 alpha: (rgbValue & 0x000000ff) / 255]; +} + +-(void)retrieveCalendars:(id)args result:(FlutterResult)result{ + Department *department = [Department new]; + department.calendars = [NSMutableArray new]; + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSArray *ekcalendars = [eventStore calendarsForEntityType:EKEntityTypeEvent]; + EKCalendar *defaultCalendar = [eventStore defaultCalendarForNewEvents]; + for (EKCalendar* ekCalendar in ekcalendars) { + + Calendar *calendar = [Calendar new]; + calendar.id = ekCalendar.calendarIdentifier; + calendar.name = ekCalendar.title; + calendar.isReadOnly = !ekCalendar.allowsContentModifications; + calendar.isDefault = [defaultCalendar calendarIdentifier] == [ekCalendar calendarIdentifier]; + calendar.color = [[[[UIColor alloc] initWithCGColor:[ekCalendar CGColor]] rgb] intValue]; + calendar.accountName = [[ekCalendar source] title]; + calendar.accountType = [self getAccountType:[[ekCalendar source]sourceType]]; + [department.calendars addObject:calendar]; + } + [self encodeJsonAndFinish:department result:result]; + } result:result]; +} + +-(NSString*)getAccountType:(EKSourceType)sourceType { + if (sourceType == EKSourceTypeLocal) { + return @"Local"; + }else if(sourceType == EKSourceTypeExchange) { + return @"Exchange"; + }else if(sourceType == EKSourceTypeCalDAV) { + return @"CalDAV"; + }else if(sourceType == EKSourceTypeMobileMe) { + return @"MobileMe"; + }else if(sourceType == EKSourceTypeSubscribed) { + return @"Subscribed"; + }else if(sourceType == EKSourceTypeBirthdays) { + return @"Birthdays"; + } else { + return @"Unknown"; + } +} + +-(void)retrieveEvents:(FlutterMethodCall *)call result:(FlutterResult)result { + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSDictionary *arguments = call.arguments; + NSString *calendarId = [arguments valueForKey:calendarIdArgument]; + NSNumber *startDateMillisecondsSinceEpoch = [arguments valueForKey:startDateArgument]; + NSNumber *endDateMillisecondsSinceEpoch = [arguments valueForKey:endDateArgument]; + NSArray *_Nullable eventIds = [arguments valueForKey:eventIdsArgument]; + Department *department = [[Department alloc] init]; + department.events = [NSMutableArray new]; + NSMutableArray *events = [NSMutableArray new]; + BOOL specifiedStartEndDates = startDateMillisecondsSinceEpoch != nil && endDateMillisecondsSinceEpoch != nil; + if (specifiedStartEndDates) { + NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSince1970: [startDateMillisecondsSinceEpoch doubleValue] / 1000.0]; + NSDate *endDate = [[NSDate alloc] initWithTimeIntervalSince1970: [endDateMillisecondsSinceEpoch doubleValue] / 1000.0]; + EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + NSMutableArray *calendars = [NSMutableArray new]; + [calendars addObject:ekCalendar]; + NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:calendars]; + NSArray *ekEvents = [eventStore eventsMatchingPredicate:predicate]; + for (EKEvent* ekEvent in ekEvents) { + Event *event = [self createEventFromEkEvent:calendarId event:ekEvent]; + [events addObject:event]; + } + } + + if (eventIds == [NSNull null]) { + for (Event *event in events) { + [department.events addObject:event]; + } + [self encodeJsonAndFinish:department result:result]; + return; + } + if (specifiedStartEndDates) { + for (Event *event in events) { + if (event.calendarId == calendarId && [eventIds containsObject:event.eventId]) { + [department.events addObject:event]; + } + } + [self encodeJsonAndFinish:department result:result]; + return; + } + for (NSString *eventId in eventIds) { + EKEvent *ekEvent = [eventStore eventWithIdentifier:eventId]; + if (ekEvent != nil) { + continue; + } + Event *event = [self createEventFromEkEvent:calendarId event:ekEvent]; + [department.events addObject:event]; + } + [self encodeJsonAndFinish:department result:result]; + } result: result]; +} + +-(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent { + NSMutableArray *attendees = [NSMutableArray new]; + if ([ekEvent attendees] != nil) { + for(EKParticipant *ekParticipant in [ekEvent attendees]) { + Attendee *attendee = [self convertEkParticipantToAttendee:ekParticipant]; + if (attendee == nil) { + continue; + } + [attendees addObject:attendee]; + } + } + + NSMutableArray *reminders = [NSMutableArray new]; + if ([ekEvent alarms] != nil) { + for (EKAlarm *alarm in [ekEvent alarms]) { + Reminder *reminder = [Reminder new]; + NSUInteger minutes = -[alarm relativeOffset]/60; + reminder.minutes = [[[NSNumber alloc] initWithUnsignedInteger:minutes] integerValue]; + [reminders addObject:reminder]; + } + } + + RecurrenceRule *recurrenceRule = [self parseEKRecurrenceRules:ekEvent]; + Event *event = [Event new]; + event.eventId = [ekEvent eventIdentifier]; + event.calendarId = calendarId; + if (ekEvent.title == nil) { + event.title = @"New title"; + } else { + event.title = [ekEvent title]; + } + event.description = [ekEvent notes]; + event.start = [[[NSNumber alloc] initWithFloat:[[ekEvent startDate] millisecondsSinceEpoch]] integerValue]; + event.end = [[[NSNumber alloc] initWithFloat:[[ekEvent endDate] millisecondsSinceEpoch]] integerValue]; + event.allDay = [ekEvent isAllDay]; + event.attendees = attendees; + event.location = [ekEvent location]; + event.recurrenceRule = recurrenceRule; + event.organizer = [self convertEkParticipantToAttendee:[ekEvent organizer]]; + event.reminders = reminders; + return event; +} + +-(Attendee*)convertEkParticipantToAttendee: (EKParticipant *)ekParticipant { + if (ekParticipant == nil || [ekParticipant emailAddress] == nil) { + return nil; + } + Attendee *attendee = [Attendee new]; + attendee.name = [ekParticipant name]; + attendee.emailAddress = [ekParticipant emailAddress]; + attendee.role = [ekParticipant participantRole]; + return attendee; +} + +-(RecurrenceRule *)parseEKRecurrenceRules:(EKEvent *)ekEvent { + RecurrenceRule *recurrenceRule = [RecurrenceRule new]; + if ([ekEvent hasRecurrenceRules]) { + EKRecurrenceRule *ekRecurrenceRule = [[ekEvent recurrenceRules] firstObject]; + NSInteger frequency; + switch ([ekRecurrenceRule frequency]) { + case EKRecurrenceFrequencyDaily: + frequency = 0; + break; + case EKRecurrenceFrequencyWeekly: + frequency = 1; + break; + case EKRecurrenceFrequencyMonthly: + frequency = 2; + break; + case EKRecurrenceFrequencyYearly: + frequency = 3; + break; + default: + frequency = 0; + break; + } + + NSInteger totalOccurrences = 0; + NSInteger endDate; + if([[ekRecurrenceRule recurrenceEnd] occurrenceCount] != 0){ + totalOccurrences = [[ekRecurrenceRule recurrenceEnd] occurrenceCount]; + } + float endDateMs = -1; + endDateMs = [[[ekRecurrenceRule recurrenceEnd] endDate] millisecondsSinceEpoch]; + if (endDateMs != -1) { + endDate = [[[NSNumber alloc] initWithFloat:endDateMs] integerValue]; + } + + NSMutableArray *daysOfWeek = [NSMutableArray new]; + NSInteger weekOfMonth = [[[ekRecurrenceRule setPositions] firstObject] intValue]; + if ([ekRecurrenceRule daysOfTheWeek] != nil && [[ekRecurrenceRule daysOfTheWeek] count] != 0) { + daysOfWeek = [NSMutableArray new]; + for (EKRecurrenceDayOfWeek *dayOfWeek in [ekRecurrenceRule daysOfTheWeek]) { + [daysOfWeek addObject: [[NSNumber alloc] initWithInt:[dayOfWeek dayOfTheWeek] - 1]]; + if (weekOfMonth == 0) { + weekOfMonth = [dayOfWeek weekNumber]; + } + } + } + + NSInteger dayOfMonth = [[[ekRecurrenceRule daysOfTheMonth] firstObject] intValue]; + NSInteger monthOfYear = [[[ekRecurrenceRule monthsOfTheYear] firstObject] intValue]; + + if (ekRecurrenceRule.frequency == EKRecurrenceFrequencyYearly + && weekOfMonth == 0 && dayOfMonth == 0 && monthOfYear == 0) { + NSDateFormatter *dateFormater = [[NSDateFormatter alloc] init]; + dateFormater.dateFormat = @"d"; + dayOfMonth = [[dateFormater stringFromDate:ekEvent.startDate] intValue]; + dateFormater.dateFormat = @"M"; + monthOfYear = [[dateFormater stringFromDate:ekEvent.startDate] intValue]; + } + + recurrenceRule.recurrenceFrequency = frequency; + recurrenceRule.totalOccurrences = totalOccurrences; + recurrenceRule.interval = [ekRecurrenceRule interval]; + recurrenceRule.endDate = endDate; + recurrenceRule.daysOfWeek = daysOfWeek; + recurrenceRule.daysOfMonth = dayOfMonth; + recurrenceRule.monthsOfYear = monthOfYear; + recurrenceRule.weekOfMonth = weekOfMonth; + } + return recurrenceRule; +} + +-(NSArray*) createEKRecurrenceRules: (NSDictionary*)arguments { + NSDictionary *recurrenceRuleArguments = [arguments valueForKey:recurrenceRuleArgument]; + if (recurrenceRuleArguments) { + return nil; + } + + NSString *recurrenceFrequencyIndex = [recurrenceRuleArguments valueForKey:recurrenceFrequencyArgument]; + NSInteger totalOccurrences = [[recurrenceRuleArguments valueForKey:totalOccurrencesArgument] integerValue]; + NSInteger interval = -1; + interval = [[recurrenceRuleArguments valueForKey:intervalArgument] integerValue]; + NSInteger recurrenceInterval = 1; + NSNumber *endDate = [recurrenceRuleArguments valueForKey:endDateArgument]; + EKRecurrenceFrequency namedFrequency = [[validFrequencyTypes valueForKey:recurrenceFrequencyIndex] integerValue]; + EKRecurrenceEnd *recurrenceEnd; + + if (endDate != nil) { + recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithEndDate: [[NSDate alloc] initWithTimeIntervalSince1970: [endDate doubleValue]]]; + } else if (totalOccurrences > 0){ + recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithOccurrenceCount: totalOccurrences]; + } + if (interval != -1 && interval > 1) { + recurrenceInterval = interval; + } + + NSArray *daysOfTheWeekIndices = [recurrenceRuleArguments valueForKey:daysOfWeekArgument]; + NSMutableArray *daysOfWeek; + + if (daysOfWeek != nil && [daysOfTheWeekIndices count] == 0) { + daysOfWeek = [NSMutableArray new]; + for (NSNumber *dayOfWeekIndex in daysOfTheWeekIndices) { + NSNumber *weekOfMonth = [recurrenceRuleArguments valueForKey:weekOfMonthArgument]; + if (weekOfMonth != nil) { + if (namedFrequency == EKRecurrenceFrequencyYearly || [weekOfMonth intValue] == -1) { + EKRecurrenceDayOfWeek *dayOfTheWeek = [EKRecurrenceDayOfWeek dayOfWeek:[dayOfWeekIndex integerValue] + 1 weekNumber: [weekOfMonth intValue]]; + [daysOfWeek addObject: dayOfTheWeek]; + } + } + if ([daysOfWeek count] == 0) { + [daysOfWeek addObject:[EKRecurrenceDayOfWeek dayOfWeek:[dayOfWeekIndex integerValue] + 1]]; + } + } + } + NSMutableArray *dayOfMonthArray; + NSNumber *dayOfMonth = [recurrenceRuleArguments valueForKey:monthOfYearArgument]; + if (dayOfMonth != nil) { + dayOfMonthArray = [NSMutableArray new]; + [dayOfMonthArray addObject: dayOfMonth]; + } + NSMutableArray *monthOfYearArray; + NSNumber *monthOfYear = [recurrenceRuleArguments valueForKey:monthOfYearArgument]; + if (monthOfYear != nil) { + monthOfYearArray = [NSMutableArray new]; + [monthOfYearArray addObject: monthOfYear]; + } + NSMutableArray *weekOfMonthArray; + if (namedFrequency == EKRecurrenceFrequencyMonthly) { + NSNumber *weekOfMonth = [recurrenceRuleArguments valueForKey:weekOfMonthArgument]; + if (weekOfMonth != nil) { + weekOfMonthArray = [NSMutableArray init]; + [weekOfMonthArray addObject:weekOfMonth]; + } + } + NSMutableArray *ekRecurrenceRules = [NSMutableArray new]; + EKRecurrenceRule *ekRecurrenceRule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency: + namedFrequency + interval:recurrenceInterval + daysOfTheWeek:daysOfWeek + daysOfTheMonth:dayOfMonthArray + monthsOfTheYear:monthOfYearArray + weeksOfTheYear:nil + daysOfTheYear:nil + setPositions:weekOfMonthArray + end:recurrenceEnd + ]; + [ekRecurrenceRules addObject: ekRecurrenceRule]; + return ekRecurrenceRules; +} + +-(void)setAttendees: (NSDictionary *)arguments event:(EKEvent *)ekEvent{ + NSDictionary *attendeesArguments = [arguments valueForKey:attendeesArgument]; + if (attendeesArguments == nil) { + return; + } + + NSMutableArray *attendees = [NSMutableArray new]; + for (NSString *attendeeArguments in attendeesArguments) { + NSString *name = [attendeesArguments valueForKey:nameArgument]; + NSString *emailAddress = [attendeeArguments valueForKey:emailAddressArgument]; + NSNumber *role = [attendeeArguments valueForKey:roleArgument]; + if ([ekEvent attendees] != nil) { + NSArray *participants = [ekEvent attendees]; + EKParticipant *existingAttendee; + for(EKParticipant* participant in participants) { + if ([participant emailAddress] == emailAddress) { + existingAttendee = participant; + break; + } + } + if (existingAttendee != nil && [[ekEvent organizer] emailAddress] != [existingAttendee emailAddress]) { + [attendees addObject: existingAttendee]; + continue; + } + EKParticipant *attendee = [self createParticipant:emailAddress name:name role:role]; + if (attendee == nil) { + continue; + } + } + } + [ekEvent setValue:attendees forKey:@"attendees"]; +} + +-(NSArray*)createReminders: (NSDictionary *)arguments { + NSDictionary *remindersArguments = [arguments valueForKey:remindersArgument]; + if (remindersArguments == nil) { + return nil; + } + NSMutableArray *reminders = [NSMutableArray new]; + for (NSString *reminderArguments in remindersArguments) { + NSNumber *arg = [[NSNumber alloc] initWithInt: [reminderArguments valueForKey:minutesArgument]]; + double relativeOffset = 60 * (-[arg doubleValue]); + [reminders addObject:[EKAlarm alarmWithRelativeOffset:relativeOffset]]; + } + return reminders; +} + +-(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)result{ + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSDictionary *arguments = [call arguments]; + NSString *calendarId = [arguments valueForKey:calendarIdArgument]; + NSString *eventId = [arguments valueForKey:eventIdArgument]; + BOOL isAllDay = [arguments valueForKey:eventAllDayArgument]; + NSNumber *startDateMillisecondsSinceEpoch = [arguments valueForKey: eventStartDateArgument]; + NSNumber *endDateDateMillisecondsSinceEpoch = [arguments valueForKey: eventEndDateArgument]; + NSDate *startDate = [NSDate dateWithTimeIntervalSince1970: [startDateMillisecondsSinceEpoch doubleValue] / 1000.0 ]; + NSDate *endDate = [NSDate dateWithTimeIntervalSince1970: [endDateDateMillisecondsSinceEpoch doubleValue] / 1000.0 ]; + NSString *title = [arguments valueForKey: eventTitleArgument]; + NSString *description = [arguments valueForKey: eventDescriptionArgument]; + NSString *location = [arguments valueForKey: eventLocationArgument]; + EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + if (ekCalendar == nil) { + [self finishWithCalendarNotFoundError:calendarId result: result]; + return; + } + if (![ekCalendar allowsContentModifications]) { + [self finishWithCalendarReadOnlyError:calendarId result:result]; + return; + } + EKEvent *ekEvent; + if (eventId == [NSNull null]) { + ekEvent = [EKEvent eventWithEventStore:eventStore]; + }else { + ekEvent = [eventStore eventWithIdentifier:eventId]; + if (ekEvent == nil) { + [self finishWithEventNotFoundError: eventId result: result]; + return; + } + } + [ekEvent setTitle:title]; + [ekEvent setNotes:description]; + [ekEvent setAllDay:isAllDay]; + [ekEvent setStartDate:startDate]; + if (isAllDay) { + [ekEvent setEndDate:startDate]; + } else { + [ekEvent setEndDate:endDate]; + } + [ekEvent setCalendar:ekCalendar]; + [ekEvent setLocation:location]; + [ekEvent setRecurrenceRules: [self createEKRecurrenceRules: arguments]]; + [self setAttendees:arguments event:ekEvent]; + [ekEvent setAlarms: [self createReminders: arguments]]; + + NSError *error = nil; + [eventStore saveEvent:ekEvent span:EKSpanFutureEvents error:&error]; + if (error == nil) { + result([ekEvent eventIdentifier]); + } else { + [eventStore reset]; + result([FlutterError errorWithCode:genericError message: [error localizedDescription] details:nil ]); + } + + } result: result]; +} + +-(EKParticipant *)createParticipant:(NSString *)emailAddress name:(NSString *)name role:(NSNumber *)role { + Class ekAttendeeClass = NSClassFromString(@"EKAttendee"); + if ([ekAttendeeClass isSubclassOfClass:[NSObject class]]) { + NSObject *participant = [[ekAttendeeClass alloc] init]; + [participant setValue:emailAddress forKey:@"emailAddress"]; + [participant setValue:name forKey:@"displayName"]; + [participant setValue:role forKey:@"participantRole"]; + return (EKParticipant *)participant; + } + return nil; +} + +-(void)deleteEvent:(FlutterMethodCall *)call result:(FlutterResult)result { + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSDictionary *arguments = call.arguments; + NSString *calendarId = [arguments valueForKey:calendarIdArgument]; + NSString *eventId = [arguments valueForKey:eventIdArgument]; + NSNumber *startDateNumber = [arguments valueForKey:eventStartDateArgument]; + NSNumber *endDateNumber = [arguments valueForKey:eventEndDateArgument]; + BOOL followingInstances = [arguments valueForKey:followingInstancesArgument]; + EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + NSError *error = nil; + if (ekCalendar == nil) { + [self finishWithCalendarNotFoundError:calendarId result: result]; + return; + } + if (![ekCalendar allowsContentModifications]) { + [self finishWithCalendarReadOnlyError:calendarId result: result]; + return; + } + if (startDateNumber == nil && endDateNumber == nil && followingInstances == NO) { + EKEvent *ekEvent = [eventStore eventWithIdentifier: eventId]; + if (ekEvent == nil) { + [self finishWithEventNotFoundError:eventId result: result]; + return; + } + [eventStore removeEvent:ekEvent span:EKSpanFutureEvents error:&error]; + }else { + NSDate *startDate = [NSDate dateWithTimeIntervalSince1970: [startDateNumber doubleValue] / 1000.0]; + NSDate *endDate = [NSDate dateWithTimeIntervalSince1970: [endDateNumber doubleValue] / 1000.0]; + + NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil]; + NSArray *foundEkEvents = [eventStore eventsMatchingPredicate:predicate]; + if (foundEkEvents == nil || [foundEkEvents count] == 0) { + [self finishWithEventNotFoundError:eventId result:result]; + return; + } + NSMutableArray *ekEvents = [NSMutableArray new]; + for (EKEvent *event in foundEkEvents) { + if (event.eventIdentifier == eventId) { + [ekEvents addObject:event]; + break; + } + } + if (!followingInstances) { + [eventStore removeEvent:ekEvents.firstObject span:EKSpanThisEvent commit: YES error:&error]; + } else { + [eventStore removeEvent:ekEvents.firstObject span:EKSpanFutureEvents commit: YES error:&error]; + } + } + if (error == nil) { + result([NSNumber numberWithBool:YES]); + } else { + [eventStore reset]; + result([FlutterError errorWithCode:genericError message: [error localizedDescription] details:nil ]); + } + } result:result]; +} + +-(void)finishWithUnauthorizedError:(id)args result:(FlutterResult)result{ + result([FlutterError errorWithCode:unauthorizedErrorCode message:unauthorizedErrorMessage details:nil]); +} + +-(void)finishWithCalendarNotFoundError:(NSString *)calendarId result:(FlutterResult)result{ + NSString *errorMessage = [calendarNotFoundErrorMessageFormat stringByAppendingFormat: calendarId]; + result([FlutterError errorWithCode:notFoundErrorCode message:errorMessage details:nil]); +} + +-(void)finishWithCalendarReadOnlyError:(NSString *)calendarId result:(FlutterResult)result{ + NSString *errorMessage = [calendarReadOnlyErrorMessageFormat stringByAppendingFormat: calendarId]; + result([FlutterError errorWithCode:notAllowed message:errorMessage details:nil]); +} + +-(void)finishWithEventNotFoundError: (NSString *)eventId result:(FlutterResult)result { + NSString *errorMessage = [eventNotFoundErrorMessageFormat stringByAppendingFormat: eventId]; + result([FlutterError errorWithCode:notFoundErrorCode message:errorMessage details:nil]); +} + +-(void)encodeJsonAndFinish: (Department *)codable result:(FlutterResult)result { + NSMutableArray *resultArr = [NSMutableArray new]; + if ([codable.calendars count] > 0) { + for(Calendar *calendar in codable.calendars) { + [resultArr addObject:[calendar toJSONString]]; + } + } else if ([codable.events count] > 0) { + for(Event *event in codable.events) { + [resultArr addObject:[event toJSONString]]; + } + } + + NSString * arrayToString = [[resultArr valueForKey:@"description"] componentsJoinedByString:@","]; + NSString *resultStr = [[NSString alloc] initWithFormat:@"[%@]", arrayToString]; + result(resultStr); +} + +-(void)checkPermissionsThenExecute:(id)args permissionsGrantedAction:(void (^)(void))permissionsGrantedAction result:(FlutterResult)result { + if ([self hasEventPermissions]) { + permissionsGrantedAction(); + return; + } + [self finishWithUnauthorizedError:nil result:result]; +} + +-(void) selector: (FlutterResult *) result { + +} + +-(void)requestPermissions: completion:(void (^)(BOOL success))complet { + if ([self hasEventPermissions]) { + complet(YES); + } else { + [eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { + complet(granted); + }]; + } +} + +-(BOOL)hasEventPermissions { + EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType: EKEntityTypeEvent]; + return status == EKAuthorizationStatusAuthorized; +} + +-(void)requestPermissions:(id)args result:(FlutterResult)result { + if ([self hasEventPermissions]) + result([NSNumber numberWithBool:YES]); + [eventStore requestAccessToEntityType: EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { + result([NSNumber numberWithBool:granted]); + }]; } @end diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.h b/device_calendar/ios/Classes/EKParticipant+Utilities.h new file mode 100644 index 00000000..8adbc519 --- /dev/null +++ b/device_calendar/ios/Classes/EKParticipant+Utilities.h @@ -0,0 +1,12 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface EKParticipant (Utilities) + +@property (readonly) NSString *emailAddress; + +@end + +NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.m b/device_calendar/ios/Classes/EKParticipant+Utilities.m new file mode 100644 index 00000000..43dec69b --- /dev/null +++ b/device_calendar/ios/Classes/EKParticipant+Utilities.m @@ -0,0 +1,9 @@ +#import "EKParticipant+Utilities.h" + +@implementation EKParticipant (Utilities) + +- (NSString *) emailAddress +{ + return [self valueForKey:@"emailAddress"]; +} +@end diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift deleted file mode 100644 index d802ea4f..00000000 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ /dev/null @@ -1,783 +0,0 @@ -import Flutter -import UIKit -import EventKit - -extension Date { - var millisecondsSinceEpoch: Double { return self.timeIntervalSince1970 * 1000.0 } -} - -extension EKParticipant { - var emailAddress: String? { - return self.value(forKey: "emailAddress") as? String - } -} - -public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { - struct Calendar: Codable { - let id: String - let name: String - let isReadOnly: Bool - let isDefault: Bool - let color: Int - let accountName: String - let accountType: String - } - - struct Event: Codable { - let eventId: String - let calendarId: String - let title: String - let description: String? - let start: Int64 - let end: Int64 - let allDay: Bool - let attendees: [Attendee] - let location: String? - let url: String? - let recurrenceRule: RecurrenceRule? - let organizer: Attendee? - let reminders: [Reminder] - } - - struct RecurrenceRule: Codable { - let recurrenceFrequency: Int - let totalOccurrences: Int? - let interval: Int - let endDate: Int64? - let daysOfWeek: [Int]? - let dayOfMonth: Int? - let monthOfYear: Int? - let weekOfMonth: Int? - } - - struct Attendee: Codable { - let name: String? - let emailAddress: String - let role: Int - let attendanceStatus: Int - } - - struct Reminder: Codable { - let minutes: Int - } - - static let channelName = "plugins.builttoroam.com/device_calendar" - let notFoundErrorCode = "404" - let notAllowed = "405" - let genericError = "500" - let unauthorizedErrorCode = "401" - let unauthorizedErrorMessage = "The user has not allowed this application to modify their calendar(s)" - let calendarNotFoundErrorMessageFormat = "The calendar with the ID %@ could not be found" - let calendarReadOnlyErrorMessageFormat = "Calendar with ID %@ is read-only" - let eventNotFoundErrorMessageFormat = "The event with the ID %@ could not be found" - let eventStore = EKEventStore() - let requestPermissionsMethod = "requestPermissions" - let hasPermissionsMethod = "hasPermissions" - let retrieveCalendarsMethod = "retrieveCalendars" - let retrieveEventsMethod = "retrieveEvents" - let retrieveSourcesMethod = "retrieveSources" - let createOrUpdateEventMethod = "createOrUpdateEvent" - let createCalendarMethod = "createCalendar" - let deleteEventMethod = "deleteEvent" - let deleteEventInstanceMethod = "deleteEventInstance" - let calendarIdArgument = "calendarId" - let startDateArgument = "startDate" - let endDateArgument = "endDate" - let eventIdArgument = "eventId" - let eventIdsArgument = "eventIds" - let eventTitleArgument = "eventTitle" - let eventDescriptionArgument = "eventDescription" - let eventAllDayArgument = "eventAllDay" - let eventStartDateArgument = "eventStartDate" - let eventEndDateArgument = "eventEndDate" - let eventLocationArgument = "eventLocation" - let eventURLArgument = "eventURL" - let attendeesArgument = "attendees" - let recurrenceRuleArgument = "recurrenceRule" - let recurrenceFrequencyArgument = "recurrenceFrequency" - let totalOccurrencesArgument = "totalOccurrences" - let intervalArgument = "interval" - let daysOfWeekArgument = "daysOfWeek" - let dayOfMonthArgument = "dayOfMonth" - let monthOfYearArgument = "monthOfYear" - let weekOfMonthArgument = "weekOfMonth" - let nameArgument = "name" - let emailAddressArgument = "emailAddress" - let roleArgument = "role" - let remindersArgument = "reminders" - let minutesArgument = "minutes" - let followingInstancesArgument = "followingInstances" - let calendarNameArgument = "calendarName" - let calendarColorArgument = "calendarColor" - let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] - - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) - let instance = SwiftDeviceCalendarPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case requestPermissionsMethod: - requestPermissions(result) - case hasPermissionsMethod: - hasPermissions(result) - case retrieveCalendarsMethod: - retrieveCalendars(result) - case retrieveEventsMethod: - retrieveEvents(call, result) - case createOrUpdateEventMethod: - createOrUpdateEvent(call, result) - case deleteEventMethod: - deleteEvent(call, result) - case deleteEventInstanceMethod: - deleteEvent(call, result) - case createCalendarMethod: - createCalendar(call, result) - default: - result(FlutterMethodNotImplemented) - } - } - - private func hasPermissions(_ result: FlutterResult) { - let hasPermissions = hasEventPermissions() - result(hasPermissions) - } - - private func createCalendar(_ call: FlutterMethodCall, _ result: FlutterResult) { - let arguments = call.arguments as! Dictionary - let calendar = EKCalendar.init(for: EKEntityType.event, eventStore: eventStore) - do { - calendar.title = arguments[calendarNameArgument] as! String - let calendarColor = arguments[calendarColorArgument] as? String - - if (calendarColor != nil) { - calendar.cgColor = UIColor(hex: calendarColor!)?.cgColor - } - else { - calendar.cgColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0).cgColor // Red colour as a default - } - - let localSources = eventStore.sources.filter { $0.sourceType == .local } - - if (!localSources.isEmpty) { - calendar.source = localSources.first - - try eventStore.saveCalendar(calendar, commit: true) - result(calendar.calendarIdentifier) - } - else { - result(FlutterError(code: self.genericError, message: "Local calendar was not found.", details: nil)) - } - } - catch { - eventStore.reset() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) - } - } - - private func retrieveCalendars(_ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - let ekCalendars = self.eventStore.calendars(for: .event) - let defaultCalendar = self.eventStore.defaultCalendarForNewEvents - var calendars = [Calendar]() - for ekCalendar in ekCalendars { - let calendar = Calendar( - id: ekCalendar.calendarIdentifier, - name: ekCalendar.title, - isReadOnly: !ekCalendar.allowsContentModifications, - isDefault: defaultCalendar?.calendarIdentifier == ekCalendar.calendarIdentifier, - color: UIColor(cgColor: ekCalendar.cgColor).rgb()!, - accountName: ekCalendar.source.title, - accountType: getAccountType(ekCalendar.source.sourceType)) - calendars.append(calendar) - } - - self.encodeJsonAndFinish(codable: calendars, result: result) - }, result: result) - } - - private func getAccountType(_ sourceType: EKSourceType) -> String { - switch (sourceType) { - case .local: - return "Local"; - case .exchange: - return "Exchange"; - case .calDAV: - return "CalDAV"; - case .mobileMe: - return "MobileMe"; - case .subscribed: - return "Subscribed"; - case .birthdays: - return "Birthdays"; - default: - return "Unknown"; - } - } - - private func retrieveEvents(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - let arguments = call.arguments as! Dictionary - let calendarId = arguments[calendarIdArgument] as! String - let startDateMillisecondsSinceEpoch = arguments[startDateArgument] as? NSNumber - let endDateDateMillisecondsSinceEpoch = arguments[endDateArgument] as? NSNumber - let eventIds = arguments[eventIdsArgument] as? [String] - var events = [Event]() - let specifiedStartEndDates = startDateMillisecondsSinceEpoch != nil && endDateDateMillisecondsSinceEpoch != nil - if specifiedStartEndDates { - let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch!.doubleValue / 1000.0) - let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch!.doubleValue / 1000.0) - 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) - } - } - - if eventIds == nil { - self.encodeJsonAndFinish(codable: events, result: result) - return - } - - if specifiedStartEndDates { - events = events.filter({ (e) -> Bool in - e.calendarId == calendarId && eventIds!.contains(e.eventId) - }) - - self.encodeJsonAndFinish(codable: events, result: result) - return - } - - for eventId in eventIds! { - let ekEvent = self.eventStore.event(withIdentifier: eventId) - if ekEvent == nil { - continue - } - - let event = createEventFromEkEvent(calendarId: calendarId, ekEvent: ekEvent!) - events.append(event) - } - - self.encodeJsonAndFinish(codable: events, result: result) - }, result: result) - } - - private func createEventFromEkEvent(calendarId: String, ekEvent: EKEvent) -> Event { - var attendees = [Attendee]() - if ekEvent.attendees != nil { - for ekParticipant in ekEvent.attendees! { - let attendee = convertEkParticipantToAttendee(ekParticipant: ekParticipant) - if attendee == nil { - continue - } - - attendees.append(attendee!) - } - } - - var reminders = [Reminder]() - if ekEvent.alarms != nil { - for alarm in ekEvent.alarms! { - reminders.append(Reminder(minutes: Int(-alarm.relativeOffset / 60))) - } - } - - let recurrenceRule = parseEKRecurrenceRules(ekEvent) - let event = Event( - eventId: ekEvent.eventIdentifier, - calendarId: calendarId, - title: ekEvent.title ?? "New Event", - description: ekEvent.notes, - start: Int64(ekEvent.startDate.millisecondsSinceEpoch), - end: Int64(ekEvent.endDate.millisecondsSinceEpoch), - allDay: ekEvent.isAllDay, - attendees: attendees, - location: ekEvent.location, - url: ekEvent.url?.absoluteString, - recurrenceRule: recurrenceRule, - organizer: convertEkParticipantToAttendee(ekParticipant: ekEvent.organizer), - reminders: reminders - ) - return event - } - - private func convertEkParticipantToAttendee(ekParticipant: EKParticipant?) -> Attendee? { - if ekParticipant == nil || ekParticipant?.emailAddress == nil { - return nil - } - - let attendee = Attendee(name: ekParticipant!.name, emailAddress: ekParticipant!.emailAddress!, role: ekParticipant!.participantRole.rawValue, attendanceStatus: ekParticipant!.participantStatus.rawValue) - return attendee - } - - private func parseEKRecurrenceRules(_ ekEvent: EKEvent) -> RecurrenceRule? { - var recurrenceRule: RecurrenceRule? - if ekEvent.hasRecurrenceRules { - let ekRecurrenceRule = ekEvent.recurrenceRules![0] - var frequency: Int - switch ekRecurrenceRule.frequency { - case EKRecurrenceFrequency.daily: - frequency = 0 - case EKRecurrenceFrequency.weekly: - frequency = 1 - case EKRecurrenceFrequency.monthly: - frequency = 2 - case EKRecurrenceFrequency.yearly: - frequency = 3 - default: - frequency = 0 - } - - var totalOccurrences: Int? - var endDate: Int64? - if(ekRecurrenceRule.recurrenceEnd?.occurrenceCount != nil) { - totalOccurrences = ekRecurrenceRule.recurrenceEnd?.occurrenceCount - } - - let endDateMs = ekRecurrenceRule.recurrenceEnd?.endDate?.millisecondsSinceEpoch - if(endDateMs != nil) { - endDate = Int64(exactly: endDateMs!) - } - - var weekOfMonth = ekRecurrenceRule.setPositions?.first?.intValue - - var daysOfWeek: [Int]? - if ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty { - daysOfWeek = [] - for dayOfWeek in ekRecurrenceRule.daysOfTheWeek! { - daysOfWeek!.append(dayOfWeek.dayOfTheWeek.rawValue - 1) - - if weekOfMonth == nil { - weekOfMonth = dayOfWeek.weekNumber - } - } - } - - // For recurrence of nth day of nth month every year, no calendar parameters are given - // So we need to explicitly set them from event start date - var dayOfMonth = ekRecurrenceRule.daysOfTheMonth?.first?.intValue - var monthOfYear = ekRecurrenceRule.monthsOfTheYear?.first?.intValue - if (ekRecurrenceRule.frequency == EKRecurrenceFrequency.yearly - && weekOfMonth == nil && dayOfMonth == nil && monthOfYear == nil) { - let dateFormatter = DateFormatter() - - // Setting day of the month - dateFormatter.dateFormat = "d" - dayOfMonth = Int(dateFormatter.string(from: ekEvent.startDate)) - - // Setting month of the year - dateFormatter.dateFormat = "M" - monthOfYear = Int(dateFormatter.string(from: ekEvent.startDate)) - } - - recurrenceRule = RecurrenceRule( - recurrenceFrequency: frequency, - totalOccurrences: totalOccurrences, - interval: ekRecurrenceRule.interval, - endDate: endDate, - daysOfWeek: daysOfWeek, - dayOfMonth: dayOfMonth, - monthOfYear: monthOfYear, - weekOfMonth: weekOfMonth) - } - - return recurrenceRule - } - - private func createEKRecurrenceRules(_ arguments: [String : AnyObject]) -> [EKRecurrenceRule]?{ - let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary - if recurrenceRuleArguments == nil { - return nil - } - - let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger - let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger - let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger - var recurrenceInterval = 1 - let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber - let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] - - var recurrenceEnd:EKRecurrenceEnd? - if endDate != nil { - recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) - } else if(totalOccurrences != nil && totalOccurrences! > 0) { - recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) - } - - if interval != nil && interval! > 1 { - recurrenceInterval = interval! - } - - let daysOfWeekIndices = recurrenceRuleArguments![daysOfWeekArgument] as? [Int] - var daysOfWeek : [EKRecurrenceDayOfWeek]? - - if daysOfWeekIndices != nil && !daysOfWeekIndices!.isEmpty { - daysOfWeek = [] - for dayOfWeekIndex in daysOfWeekIndices! { - // Append week number to BYDAY for yearly or monthly with 'last' week number - if let weekOfMonth = recurrenceRuleArguments![weekOfMonthArgument] as? Int { - if namedFrequency == EKRecurrenceFrequency.yearly || weekOfMonth == -1 { - daysOfWeek!.append(EKRecurrenceDayOfWeek.init( - dayOfTheWeek: EKWeekday.init(rawValue: dayOfWeekIndex + 1)!, - weekNumber: weekOfMonth - )) - } - } - - if daysOfWeek?.isEmpty == true { - daysOfWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) - } - } - } - - var dayOfMonthArray : [NSNumber]? - if let dayOfMonth = recurrenceRuleArguments![dayOfMonthArgument] as? Int { - dayOfMonthArray = [] - dayOfMonthArray!.append(NSNumber(value: dayOfMonth)) - } - - var monthOfYearArray : [NSNumber]? - if let monthOfYear = recurrenceRuleArguments![monthOfYearArgument] as? Int { - monthOfYearArray = [] - monthOfYearArray!.append(NSNumber(value: monthOfYear)) - } - - // Append BYSETPOS only on monthly (but not last), yearly's week number (and last for monthly) appends to BYDAY - var weekOfMonthArray : [NSNumber]? - if namedFrequency == EKRecurrenceFrequency.monthly { - if let weekOfMonth = recurrenceRuleArguments![weekOfMonthArgument] as? Int { - if weekOfMonth != -1 { - weekOfMonthArray = [] - weekOfMonthArray!.append(NSNumber(value: weekOfMonth)) - } - } - } - - return [EKRecurrenceRule( - recurrenceWith: namedFrequency, - interval: recurrenceInterval, - daysOfTheWeek: daysOfWeek, - daysOfTheMonth: dayOfMonthArray, - monthsOfTheYear: monthOfYearArray, - weeksOfTheYear: nil, - daysOfTheYear: nil, - setPositions: weekOfMonthArray, - end: recurrenceEnd)] - } - - private func setAttendees(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { - let attendeesArguments = arguments[attendeesArgument] as? [Dictionary] - if attendeesArguments == nil { - return - } - - var attendees = [EKParticipant]() - for attendeeArguments in attendeesArguments! { - let name = attendeeArguments[nameArgument] as! String - let emailAddress = attendeeArguments[emailAddressArgument] as! String - let role = attendeeArguments[roleArgument] as! Int - - if (ekEvent!.attendees != nil) { - let existingAttendee = ekEvent!.attendees!.first { element in - return element.emailAddress == emailAddress - } - if existingAttendee != nil && ekEvent!.organizer?.emailAddress != existingAttendee?.emailAddress{ - attendees.append(existingAttendee!) - continue - } - } - - let attendee = createParticipant( - name: name, - emailAddress: emailAddress, - role: role) - - if (attendee == nil) { - continue - } - - attendees.append(attendee!) - } - - ekEvent!.setValue(attendees, forKey: "attendees") - } - - private func createReminders(_ arguments: [String : AnyObject]) -> [EKAlarm]?{ - let remindersArguments = arguments[remindersArgument] as? [Dictionary] - if remindersArguments == nil { - return nil - } - - var reminders = [EKAlarm]() - for reminderArguments in remindersArguments! { - let minutes = reminderArguments[minutesArgument] as! Int - reminders.append(EKAlarm.init(relativeOffset: 60 * Double(-minutes))) - } - - return reminders - } - - private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - let arguments = call.arguments as! Dictionary - let calendarId = arguments[calendarIdArgument] as! String - let eventId = arguments[eventIdArgument] as? String - let isAllDay = arguments[eventAllDayArgument] as! Bool - let startDateMillisecondsSinceEpoch = arguments[eventStartDateArgument] as! NSNumber - let endDateDateMillisecondsSinceEpoch = arguments[eventEndDateArgument] as! NSNumber - let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch.doubleValue / 1000.0) - let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch.doubleValue / 1000.0) - let title = arguments[self.eventTitleArgument] as! String - let description = arguments[self.eventDescriptionArgument] as? String - let location = arguments[self.eventLocationArgument] as? String - let url = arguments[self.eventURLArgument] 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 - } - - var ekEvent: EKEvent? - if eventId == nil { - ekEvent = EKEvent.init(eventStore: self.eventStore) - } else { - ekEvent = self.eventStore.event(withIdentifier: eventId!) - if(ekEvent == nil) { - self.finishWithEventNotFoundError(result: result, eventId: eventId!) - return - } - } - - ekEvent!.title = title - ekEvent!.notes = description - ekEvent!.isAllDay = isAllDay - ekEvent!.startDate = startDate - if (isAllDay) { ekEvent!.endDate = startDate } - else { ekEvent!.endDate = endDate } - ekEvent!.calendar = ekCalendar! - ekEvent!.location = location - - // Create and add URL object only when if the input string is not empty or nil - if let urlCheck = url, !urlCheck.isEmpty { - let iosUrl = URL(string: url ?? "") - ekEvent!.url = iosUrl - } - else { - ekEvent!.url = nil - } - - ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) - setAttendees(arguments, ekEvent) - ekEvent!.alarms = createReminders(arguments) - - do { - try self.eventStore.save(ekEvent!, span: .futureEvents) - result(ekEvent!.eventIdentifier) - } catch { - self.eventStore.reset() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) - } - }, result: result) - } - - private func createParticipant(name: String, emailAddress: String, role: Int) -> EKParticipant? { - let ekAttendeeClass: AnyClass? = NSClassFromString("EKAttendee") - if let type = ekAttendeeClass as? NSObject.Type { - let participant = type.init() - participant.setValue(name, forKey: "displayName") - participant.setValue(emailAddress, forKey: "emailAddress") - participant.setValue(role, forKey: "participantRole") - return participant as? EKParticipant - } - return nil - } - - private func deleteEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - 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 followingInstances = arguments[followingInstancesArgument] as? Bool - - 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 - } - - if (startDateNumber == nil && endDateNumber == nil && followingInstances == 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)) - } - } - 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 foundEkEvents = self.eventStore.events(matching: predicate) as [EKEvent]? - - if foundEkEvents == nil || foundEkEvents?.count == 0 { - self.finishWithEventNotFoundError(result: result, eventId: eventId) - return - } - - let ekEvent = foundEkEvents!.first(where: {$0.eventIdentifier == eventId}) - - do { - 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() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) - } - } - }, result: result) - } - - private func finishWithUnauthorizedError(result: @escaping FlutterResult) { - result(FlutterError(code:self.unauthorizedErrorCode, message: self.unauthorizedErrorMessage, details: nil)) - } - - private func finishWithCalendarNotFoundError(result: @escaping FlutterResult, calendarId: String) { - let errorMessage = String(format: self.calendarNotFoundErrorMessageFormat, calendarId) - result(FlutterError(code:self.notFoundErrorCode, message: errorMessage, details: nil)) - } - - private func finishWithCalendarReadOnlyError(result: @escaping FlutterResult, calendarId: String) { - let errorMessage = String(format: self.calendarReadOnlyErrorMessageFormat, calendarId) - result(FlutterError(code:self.notAllowed, message: errorMessage, details: nil)) - } - - private func finishWithEventNotFoundError(result: @escaping FlutterResult, eventId: String) { - let errorMessage = String(format: self.eventNotFoundErrorMessageFormat, eventId) - result(FlutterError(code:self.notFoundErrorCode, message: errorMessage, details: nil)) - } - - private func encodeJsonAndFinish(codable: T, result: @escaping FlutterResult) { - do { - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(codable) - let jsonString = String(data: jsonData, encoding: .utf8) - result(jsonString) - } catch { - result(FlutterError(code: genericError, message: error.localizedDescription, details: nil)) - } - } - - private func checkPermissionsThenExecute(permissionsGrantedAction: () -> Void, result: @escaping FlutterResult) { - if hasEventPermissions() { - permissionsGrantedAction() - return - } - self.finishWithUnauthorizedError(result: result) - } - - private func requestPermissions(completion: @escaping (Bool) -> Void) { - if hasEventPermissions() { - completion(true) - return - } - eventStore.requestAccess(to: .event, completion: { - (accessGranted: Bool, _: Error?) in - completion(accessGranted) - }) - } - - private func hasEventPermissions() -> Bool { - let status = EKEventStore.authorizationStatus(for: .event) - return status == EKAuthorizationStatus.authorized - } - - private func requestPermissions(_ result: @escaping FlutterResult) { - if hasEventPermissions() { - result(true) - } - eventStore.requestAccess(to: .event, completion: { - (accessGranted: Bool, _: Error?) in - result(accessGranted) - }) - } -} - -extension UIColor { - - func rgb() -> Int? { - var fRed : CGFloat = 0 - var fGreen : CGFloat = 0 - var fBlue : CGFloat = 0 - var fAlpha: CGFloat = 0 - if self.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha) { - let iRed = Int(fRed * 255.0) - let iGreen = Int(fGreen * 255.0) - let iBlue = Int(fBlue * 255.0) - let iAlpha = Int(fAlpha * 255.0) - - // (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). - let rgb = (iAlpha << 24) + (iRed << 16) + (iGreen << 8) + iBlue - return rgb - } else { - // Could not extract RGBA components: - return nil - } - } - - public convenience init?(hex: String) { - let r, g, b, a: CGFloat - - if hex.hasPrefix("0x") { - let start = hex.index(hex.startIndex, offsetBy: 2) - let hexColor = String(hex[start...]) - - if hexColor.count == 8 { - let scanner = Scanner(string: hexColor) - var hexNumber: UInt64 = 0 - - if scanner.scanHexInt64(&hexNumber) { - a = CGFloat((hexNumber & 0xff000000) >> 24) / 255 - r = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 - g = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 - b = CGFloat((hexNumber & 0x000000ff)) / 255 - - self.init(red: r, green: g, blue: b, alpha: a) - return - } - } - } - - return nil - } -} - diff --git a/device_calendar/ios/Classes/UIColor+Utilities.h b/device_calendar/ios/Classes/UIColor+Utilities.h new file mode 100644 index 00000000..af7e58c0 --- /dev/null +++ b/device_calendar/ios/Classes/UIColor+Utilities.h @@ -0,0 +1,7 @@ +#import + +NS_ASSUME_NONNULL_BEGIN +@interface UIColor (Utilities) +@property (readonly) NSNumber *rgb; +@end +NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/UIColor+Utilities.m b/device_calendar/ios/Classes/UIColor+Utilities.m new file mode 100644 index 00000000..23b36e82 --- /dev/null +++ b/device_calendar/ios/Classes/UIColor+Utilities.m @@ -0,0 +1,22 @@ +#import "UIColor+Utilities.h" + +@implementation UIColor (Utilities) +- (NSNumber*) rgb +{ + CGFloat* fRead = 0; + CGFloat* fGreen = 0; + CGFloat* fBlue = 0; + CGFloat* fAlpha = 0; + + if ([self getRed:fRead green:fGreen blue:fBlue alpha:fAlpha]) { + NSInteger iRed = (NSInteger)fRead*255.0; + NSInteger iGreen = (NSInteger)fGreen*255.0; + NSInteger iBlue = (NSInteger)fRead*255.0; + NSInteger iAlpha = (NSInteger)fRead*255.0; + NSNumber *rgb = [[NSNumber alloc] initWithLong:(iAlpha<<24)+ (iRed << 16) + (iGreen << 8) + iBlue]; + return rgb; + }else{ + return nil; + } +} +@end diff --git a/device_calendar/ios/Classes/models/Attendee.h b/device_calendar/ios/Classes/models/Attendee.h new file mode 100644 index 00000000..33c3454c --- /dev/null +++ b/device_calendar/ios/Classes/models/Attendee.h @@ -0,0 +1,11 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Attendee : JSONModel + +@property NSString *name; +@property NSString *emailAddress; +@property NSInteger role; +@property NSInteger attendanceStatus; + +@end diff --git a/device_calendar/ios/Classes/models/Attendee.m b/device_calendar/ios/Classes/models/Attendee.m new file mode 100644 index 00000000..449d8db0 --- /dev/null +++ b/device_calendar/ios/Classes/models/Attendee.m @@ -0,0 +1,6 @@ +#import +#import "Attendee.h" + +@implementation Attendee + +@end diff --git a/device_calendar/ios/Classes/models/Calendar.h b/device_calendar/ios/Classes/models/Calendar.h new file mode 100644 index 00000000..b45030c7 --- /dev/null +++ b/device_calendar/ios/Classes/models/Calendar.h @@ -0,0 +1,14 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Calendar : JSONModel + +@property NSString *id; +@property NSString *name; +@property BOOL isReadOnly; +@property BOOL isDefault; +@property NSInteger color; +@property NSString *accountName; +@property NSString *accountType; + +@end diff --git a/device_calendar/ios/Classes/models/Calendar.m b/device_calendar/ios/Classes/models/Calendar.m new file mode 100644 index 00000000..471dff57 --- /dev/null +++ b/device_calendar/ios/Classes/models/Calendar.m @@ -0,0 +1,6 @@ +#import +#import "Calendar.h" + +@implementation Calendar + +@end diff --git a/device_calendar/ios/Classes/models/Department.h b/device_calendar/ios/Classes/models/Department.h new file mode 100644 index 00000000..6725ce63 --- /dev/null +++ b/device_calendar/ios/Classes/models/Department.h @@ -0,0 +1,7 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Department : JSONModel +@property NSMutableArray *calendars; +@property NSMutableArray *events; +@end diff --git a/device_calendar/ios/Classes/models/Department.m b/device_calendar/ios/Classes/models/Department.m new file mode 100644 index 00000000..28077c1d --- /dev/null +++ b/device_calendar/ios/Classes/models/Department.m @@ -0,0 +1,6 @@ +#import +#import "Department.h" + +@implementation Department + +@end diff --git a/device_calendar/ios/Classes/models/Event.h b/device_calendar/ios/Classes/models/Event.h new file mode 100644 index 00000000..cfb3bd30 --- /dev/null +++ b/device_calendar/ios/Classes/models/Event.h @@ -0,0 +1,21 @@ +#import +#import "RecurrenceRule.h" +#import "Attendee.h" +#import "JSONModel/JSONModel.h" + +@interface Event : JSONModel + +@property NSString *eventId; +@property NSString *calendarId; +@property NSString *title; +@property NSString *description; +@property NSInteger start; +@property NSInteger end; +@property BOOL allDay; +@property NSArray *attendees; +@property NSString *location; +@property RecurrenceRule *recurrenceRule; +@property Attendee *organizer; +@property NSMutableArray *reminders; + +@end diff --git a/device_calendar/ios/Classes/models/Event.m b/device_calendar/ios/Classes/models/Event.m new file mode 100644 index 00000000..e3a6c221 --- /dev/null +++ b/device_calendar/ios/Classes/models/Event.m @@ -0,0 +1,6 @@ +#import +#import "Event.h" + +@implementation Event +@synthesize description; +@end diff --git a/device_calendar/ios/Classes/models/RecurrenceRule.h b/device_calendar/ios/Classes/models/RecurrenceRule.h new file mode 100644 index 00000000..b15cec63 --- /dev/null +++ b/device_calendar/ios/Classes/models/RecurrenceRule.h @@ -0,0 +1,16 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface RecurrenceRule : JSONModel + +@property NSInteger recurrenceFrequency; +@property NSInteger totalOccurrences; +@property NSInteger interval; +@property NSInteger endDate; +@property NSArray *daysOfWeek; +@property NSInteger daysOfMonth; +@property NSInteger monthsOfYear; +@property NSInteger weeksOfYear; +@property NSInteger weekOfMonth; + +@end diff --git a/device_calendar/ios/Classes/models/RecurrenceRule.m b/device_calendar/ios/Classes/models/RecurrenceRule.m new file mode 100644 index 00000000..fff63614 --- /dev/null +++ b/device_calendar/ios/Classes/models/RecurrenceRule.m @@ -0,0 +1,6 @@ +#import +#import "RecurrenceRule.h" + +@implementation RecurrenceRule + +@end diff --git a/device_calendar/ios/Classes/models/Reminder.h b/device_calendar/ios/Classes/models/Reminder.h new file mode 100644 index 00000000..481abbe8 --- /dev/null +++ b/device_calendar/ios/Classes/models/Reminder.h @@ -0,0 +1,8 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Reminder : JSONModel + +@property NSInteger minutes; + +@end diff --git a/device_calendar/ios/Classes/models/Reminder.m b/device_calendar/ios/Classes/models/Reminder.m new file mode 100644 index 00000000..3da8f974 --- /dev/null +++ b/device_calendar/ios/Classes/models/Reminder.m @@ -0,0 +1,6 @@ +#import +#import "Reminder.h" + +@implementation Reminder + +@end diff --git a/device_calendar/ios/device_calendar.podspec b/device_calendar/ios/device_calendar.podspec index 7b91dd81..7435a1e5 100644 --- a/device_calendar/ios/device_calendar.podspec +++ b/device_calendar/ios/device_calendar.podspec @@ -15,7 +15,8 @@ A new flutter plugin project. s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - + s.dependency 'JSONModel' + s.ios.deployment_target = '8.0' end From 73a339974a0a11c8e4fa557d478e1a48c0c1d1a1 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Mon, 6 Apr 2020 18:36:18 +0300 Subject: [PATCH 03/15] fix isAllDay is always true even when it is sent as false in dart --- device_calendar/ios/Classes/DeviceCalendarPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 9e762fa9..9a8e8567 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -508,7 +508,7 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu NSDictionary *arguments = [call arguments]; NSString *calendarId = [arguments valueForKey:calendarIdArgument]; NSString *eventId = [arguments valueForKey:eventIdArgument]; - BOOL isAllDay = [arguments valueForKey:eventAllDayArgument]; + BOOL isAllDay = [[arguments valueForKey:eventAllDayArgument] boolValue]; NSNumber *startDateMillisecondsSinceEpoch = [arguments valueForKey: eventStartDateArgument]; NSNumber *endDateDateMillisecondsSinceEpoch = [arguments valueForKey: eventEndDateArgument]; NSDate *startDate = [NSDate dateWithTimeIntervalSince1970: [startDateMillisecondsSinceEpoch doubleValue] / 1000.0 ]; From 8ec13d73d7043dbdcf14a6b7d11ce04232defa3a Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Thu, 16 Apr 2020 14:54:51 +0300 Subject: [PATCH 04/15] fix comments --- .../ios/Runner.xcodeproj/project.pbxproj | 28 ++++++++-- .../ios/Classes/DeviceCalendarPlugin.m | 55 +++++++++++-------- device_calendar/ios/Classes/models/Event.h | 4 +- .../ios/Classes/models/RecurrenceRule.h | 4 +- device_calendar/ios/Classes/models/Reminder.h | 2 +- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 001fe62c..cf205457 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = YCDSDGA3LX; + DevelopmentTeam = PT8YH28249; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,7 +448,16 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + "\"Flutter\"", + "-framework", + "\"JSONModel\"", + "-framework", + "\"device_calendar\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -465,7 +474,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -477,7 +486,16 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + "\"Flutter\"", + "-framework", + "\"JSONModel\"", + "-framework", + "\"device_calendar\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 9a8e8567..212ca6ce 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -63,6 +63,7 @@ @implementation DeviceCalendarPlugin NSString *followingInstancesArgument = @"followingInstances"; NSString *calendarNameArgument = @"calendarName"; NSString *calendarColorArgument = @"calendarColor"; +NSString *eventURLArgument = @"eventURL"; NSMutableArray *validFrequencyTypes; EKEventStore *eventStore; @@ -139,7 +140,7 @@ -(void)createCalendar:(FlutterMethodCall *)call result:(FlutterResult)result { [localSources addObject:ekSource]; } } - if ([localSources count] == 0) { + if ([localSources count]) { calendar.source = [localSources firstObject]; [eventStore saveCalendar:calendar commit:YES error:&error]; } else { @@ -270,12 +271,10 @@ -(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent if ([ekEvent alarms] != nil) { for (EKAlarm *alarm in [ekEvent alarms]) { Reminder *reminder = [Reminder new]; - NSUInteger minutes = -[alarm relativeOffset]/60; - reminder.minutes = [[[NSNumber alloc] initWithUnsignedInteger:minutes] integerValue]; + reminder.minutes = -[alarm relativeOffset]/60; [reminders addObject:reminder]; } } - RecurrenceRule *recurrenceRule = [self parseEKRecurrenceRules:ekEvent]; Event *event = [Event new]; event.eventId = [ekEvent eventIdentifier]; @@ -291,6 +290,7 @@ -(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent event.allDay = [ekEvent isAllDay]; event.attendees = attendees; event.location = [ekEvent location]; + event.url = [[ekEvent URL] absoluteString]; event.recurrenceRule = recurrenceRule; event.organizer = [self convertEkParticipantToAttendee:[ekEvent organizer]]; event.reminders = reminders; @@ -331,17 +331,15 @@ -(RecurrenceRule *)parseEKRecurrenceRules:(EKEvent *)ekEvent { break; } - NSInteger totalOccurrences = 0; - NSInteger endDate; + NSNumber *totalOccurrences; + NSNumber *endDate; if([[ekRecurrenceRule recurrenceEnd] occurrenceCount] != 0){ - totalOccurrences = [[ekRecurrenceRule recurrenceEnd] occurrenceCount]; + totalOccurrences = [[NSNumber alloc] initWithUnsignedInteger:[[ekRecurrenceRule recurrenceEnd] occurrenceCount]]; } - float endDateMs = -1; - endDateMs = [[[ekRecurrenceRule recurrenceEnd] endDate] millisecondsSinceEpoch]; - if (endDateMs != -1) { - endDate = [[[NSNumber alloc] initWithFloat:endDateMs] integerValue]; + float endDateMs = [[[ekRecurrenceRule recurrenceEnd] endDate] millisecondsSinceEpoch]; + if (endDateMs) { + endDate = [[NSNumber alloc] initWithFloat:endDateMs]; } - NSMutableArray *daysOfWeek = [NSMutableArray new]; NSInteger weekOfMonth = [[[ekRecurrenceRule setPositions] firstObject] intValue]; if ([ekRecurrenceRule daysOfTheWeek] != nil && [[ekRecurrenceRule daysOfTheWeek] count] != 0) { @@ -380,19 +378,18 @@ -(RecurrenceRule *)parseEKRecurrenceRules:(EKEvent *)ekEvent { -(NSArray*) createEKRecurrenceRules: (NSDictionary*)arguments { NSDictionary *recurrenceRuleArguments = [arguments valueForKey:recurrenceRuleArgument]; - if (recurrenceRuleArguments) { + if ([recurrenceRuleArguments isEqual:[NSNull null]]) { return nil; } - - NSString *recurrenceFrequencyIndex = [recurrenceRuleArguments valueForKey:recurrenceFrequencyArgument]; + NSNumber *recurrenceFrequencyIndex = [recurrenceRuleArguments valueForKey:recurrenceFrequencyArgument]; NSInteger totalOccurrences = [[recurrenceRuleArguments valueForKey:totalOccurrencesArgument] integerValue]; NSInteger interval = -1; interval = [[recurrenceRuleArguments valueForKey:intervalArgument] integerValue]; NSInteger recurrenceInterval = 1; NSNumber *endDate = [recurrenceRuleArguments valueForKey:endDateArgument]; - EKRecurrenceFrequency namedFrequency = [[validFrequencyTypes valueForKey:recurrenceFrequencyIndex] integerValue]; + EKRecurrenceFrequency namedFrequency = [[validFrequencyTypes objectAtIndex:[recurrenceFrequencyIndex intValue]] integerValue]; EKRecurrenceEnd *recurrenceEnd; - + if (endDate != nil) { recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithEndDate: [[NSDate alloc] initWithTimeIntervalSince1970: [endDate doubleValue]]]; } else if (totalOccurrences > 0){ @@ -496,9 +493,10 @@ -(NSArray*)createReminders: (NSDictionary *)arguments { } NSMutableArray *reminders = [NSMutableArray new]; for (NSString *reminderArguments in remindersArguments) { - NSNumber *arg = [[NSNumber alloc] initWithInt: [reminderArguments valueForKey:minutesArgument]]; - double relativeOffset = 60 * (-[arg doubleValue]); - [reminders addObject:[EKAlarm alarmWithRelativeOffset:relativeOffset]]; + NSNumber *arg = [reminderArguments valueForKey:minutesArgument]; + NSNumber *reminder = [NSNumber numberWithDouble:fabs([arg doubleValue])]; + NSNumber *relativeOffset = [[NSNumber alloc] initWithDouble: (0 - 60 * [reminder doubleValue])]; + [reminders addObject:[EKAlarm alarmWithRelativeOffset:[relativeOffset doubleValue]]]; } return reminders; } @@ -517,6 +515,8 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu NSString *description = [arguments valueForKey: eventDescriptionArgument]; NSString *location = [arguments valueForKey: eventLocationArgument]; EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + NSString *url = [arguments valueForKey:eventURLArgument]; + if (ekCalendar == nil) { [self finishWithCalendarNotFoundError:calendarId result: result]; return; @@ -526,9 +526,9 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu return; } EKEvent *ekEvent; - if (eventId == [NSNull null]) { + if ([eventId isEqual:[NSNull null]]) { ekEvent = [EKEvent eventWithEventStore:eventStore]; - }else { + } else { ekEvent = [eventStore eventWithIdentifier:eventId]; if (ekEvent == nil) { [self finishWithEventNotFoundError: eventId result: result]; @@ -546,10 +546,14 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu } [ekEvent setCalendar:ekCalendar]; [ekEvent setLocation:location]; + if (![url isEqual:[NSNull null]]) { + ekEvent.URL = [NSURL URLWithString: url]; + } else { + ekEvent.URL = [NSURL URLWithString: @""]; + } [ekEvent setRecurrenceRules: [self createEKRecurrenceRules: arguments]]; [self setAttendees:arguments event:ekEvent]; [ekEvent setAlarms: [self createReminders: arguments]]; - NSError *error = nil; [eventStore saveEvent:ekEvent span:EKSpanFutureEvents error:&error]; if (error == nil) { @@ -658,6 +662,11 @@ -(void)encodeJsonAndFinish: (Department *)codable result:(FlutterResult)result { } } else if ([codable.events count] > 0) { for(Event *event in codable.events) { + NSMutableArray *reminders = [NSMutableArray new]; + for (Reminder *remeinder in event.reminders) { + [reminders addObject:[remeinder toDictionary]]; + } + event.reminders = reminders; [resultArr addObject:[event toJSONString]]; } } diff --git a/device_calendar/ios/Classes/models/Event.h b/device_calendar/ios/Classes/models/Event.h index cfb3bd30..662f4cba 100644 --- a/device_calendar/ios/Classes/models/Event.h +++ b/device_calendar/ios/Classes/models/Event.h @@ -1,6 +1,7 @@ #import #import "RecurrenceRule.h" #import "Attendee.h" +#import "Reminder.h" #import "JSONModel/JSONModel.h" @interface Event : JSONModel @@ -16,6 +17,7 @@ @property NSString *location; @property RecurrenceRule *recurrenceRule; @property Attendee *organizer; -@property NSMutableArray *reminders; +@property NSArray *reminders; +@property NSString *url; @end diff --git a/device_calendar/ios/Classes/models/RecurrenceRule.h b/device_calendar/ios/Classes/models/RecurrenceRule.h index b15cec63..15cb8168 100644 --- a/device_calendar/ios/Classes/models/RecurrenceRule.h +++ b/device_calendar/ios/Classes/models/RecurrenceRule.h @@ -4,9 +4,9 @@ @interface RecurrenceRule : JSONModel @property NSInteger recurrenceFrequency; -@property NSInteger totalOccurrences; +@property NSNumber *totalOccurrences; @property NSInteger interval; -@property NSInteger endDate; +@property NSNumber *endDate; @property NSArray *daysOfWeek; @property NSInteger daysOfMonth; @property NSInteger monthsOfYear; diff --git a/device_calendar/ios/Classes/models/Reminder.h b/device_calendar/ios/Classes/models/Reminder.h index 481abbe8..5f3b2421 100644 --- a/device_calendar/ios/Classes/models/Reminder.h +++ b/device_calendar/ios/Classes/models/Reminder.h @@ -3,6 +3,6 @@ @interface Reminder : JSONModel -@property NSInteger minutes; +@property (nonatomic) double minutes; @end From 309405cccac2ffee19a785f0700833d16a305764 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Thu, 16 Apr 2020 14:58:44 +0300 Subject: [PATCH 05/15] remove dev acc --- .../ios/Runner.xcodeproj/project.pbxproj | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index cf205457..001fe62c 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = PT8YH28249; + DevelopmentTeam = YCDSDGA3LX; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,16 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"Flutter\"", - "-framework", - "\"JSONModel\"", - "-framework", - "\"device_calendar\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -474,7 +465,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -486,16 +477,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"Flutter\"", - "-framework", - "\"JSONModel\"", - "-framework", - "\"device_calendar\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; From 13c6800af2808feda872512b956df45aae01ea7e Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Tue, 26 May 2020 16:33:07 +0300 Subject: [PATCH 06/15] fix attendees --- .../ios/Runner.xcodeproj/project.pbxproj | 10 ++--- .../ios/Classes/DeviceCalendarPlugin.m | 40 ++++++++++++------- .../ios/Classes/EKParticipant+Utilities.h | 12 ------ .../ios/Classes/EKParticipant+Utilities.m | 9 ----- device_calendar/ios/Classes/models/Event.h | 2 +- 5 files changed, 31 insertions(+), 42 deletions(-) delete mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.h delete mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.m diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 001fe62c..d9000d07 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = YCDSDGA3LX; + DevelopmentTeam = PT8YH28249; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,7 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -465,7 +465,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -477,7 +477,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 212ca6ce..41b5adad 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -11,7 +11,6 @@ #import "models/Department.h" #import "Date+Utilities.h" #import "UIColor+Utilities.h" -#import "EKParticipant+Utilities.h" #import @implementation DeviceCalendarPlugin @@ -298,12 +297,12 @@ -(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent } -(Attendee*)convertEkParticipantToAttendee: (EKParticipant *)ekParticipant { - if (ekParticipant == nil || [ekParticipant emailAddress] == nil) { + if (ekParticipant == nil || [[ekParticipant URL] resourceSpecifier] == nil) { return nil; } Attendee *attendee = [Attendee new]; attendee.name = [ekParticipant name]; - attendee.emailAddress = [ekParticipant emailAddress]; + attendee.emailAddress = [[ekParticipant URL] resourceSpecifier]; attendee.role = [ekParticipant participantRole]; return attendee; } @@ -465,15 +464,15 @@ -(void)setAttendees: (NSDictionary *)arguments event:(EKEvent *)ekEvent{ NSString *emailAddress = [attendeeArguments valueForKey:emailAddressArgument]; NSNumber *role = [attendeeArguments valueForKey:roleArgument]; if ([ekEvent attendees] != nil) { - NSArray *participants = [ekEvent attendees]; + NSArray *participants = [ekEvent attendees]; EKParticipant *existingAttendee; for(EKParticipant* participant in participants) { - if ([participant emailAddress] == emailAddress) { + if ([[participant URL] resourceSpecifier] == emailAddress) { existingAttendee = participant; break; } } - if (existingAttendee != nil && [[ekEvent organizer] emailAddress] != [existingAttendee emailAddress]) { + if (existingAttendee != nil && [[[ekEvent organizer] URL] resourceSpecifier] != [[existingAttendee URL] resourceSpecifier]) { [attendees addObject: existingAttendee]; continue; } @@ -481,7 +480,16 @@ -(void)setAttendees: (NSDictionary *)arguments event:(EKEvent *)ekEvent{ if (attendee == nil) { continue; } + if (existingAttendee != nil && [[[ekEvent organizer] URL] resourceSpecifier] != [[existingAttendee URL] resourceSpecifier]) { + [attendees addObject: existingAttendee]; + continue; + } } + EKParticipant *attendee = [self createParticipant:emailAddress name:name role:role]; + if (attendee == nil) { + continue; + } + [attendees addObject: attendee]; } [ekEvent setValue:attendees forKey:@"attendees"]; } @@ -503,7 +511,7 @@ -(NSArray*)createReminders: (NSDictionary *)arguments { -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)result{ [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ - NSDictionary *arguments = [call arguments]; + NSDictionary *arguments = [call arguments]; NSString *calendarId = [arguments valueForKey:calendarIdArgument]; NSString *eventId = [arguments valueForKey:eventIdArgument]; BOOL isAllDay = [[arguments valueForKey:eventAllDayArgument] boolValue]; @@ -568,14 +576,11 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu -(EKParticipant *)createParticipant:(NSString *)emailAddress name:(NSString *)name role:(NSNumber *)role { Class ekAttendeeClass = NSClassFromString(@"EKAttendee"); - if ([ekAttendeeClass isSubclassOfClass:[NSObject class]]) { - NSObject *participant = [[ekAttendeeClass alloc] init]; - [participant setValue:emailAddress forKey:@"emailAddress"]; - [participant setValue:name forKey:@"displayName"]; - [participant setValue:role forKey:@"participantRole"]; - return (EKParticipant *)participant; - } - return nil; + id participant = [ekAttendeeClass new]; + [participant setValue:emailAddress forKey:@"emailAddress"]; + [participant setValue:name forKey:@"displayName"]; + [participant setValue:role forKey:@"participantRole"]; + return participant; } -(void)deleteEvent:(FlutterMethodCall *)call result:(FlutterResult)result { @@ -666,6 +671,11 @@ -(void)encodeJsonAndFinish: (Department *)codable result:(FlutterResult)result { for (Reminder *remeinder in event.reminders) { [reminders addObject:[remeinder toDictionary]]; } + NSMutableArray *attendees = [NSMutableArray new]; + for (Attendee *attendee in event.attendees) { + [attendees addObject:[attendee toDictionary]]; + } + event.attendees = attendees; event.reminders = reminders; [resultArr addObject:[event toJSONString]]; } diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.h b/device_calendar/ios/Classes/EKParticipant+Utilities.h deleted file mode 100644 index 8adbc519..00000000 --- a/device_calendar/ios/Classes/EKParticipant+Utilities.h +++ /dev/null @@ -1,12 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface EKParticipant (Utilities) - -@property (readonly) NSString *emailAddress; - -@end - -NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.m b/device_calendar/ios/Classes/EKParticipant+Utilities.m deleted file mode 100644 index 43dec69b..00000000 --- a/device_calendar/ios/Classes/EKParticipant+Utilities.m +++ /dev/null @@ -1,9 +0,0 @@ -#import "EKParticipant+Utilities.h" - -@implementation EKParticipant (Utilities) - -- (NSString *) emailAddress -{ - return [self valueForKey:@"emailAddress"]; -} -@end diff --git a/device_calendar/ios/Classes/models/Event.h b/device_calendar/ios/Classes/models/Event.h index 662f4cba..890cc0f8 100644 --- a/device_calendar/ios/Classes/models/Event.h +++ b/device_calendar/ios/Classes/models/Event.h @@ -13,7 +13,7 @@ @property NSInteger start; @property NSInteger end; @property BOOL allDay; -@property NSArray *attendees; +@property NSMutableArray *attendees; @property NSString *location; @property RecurrenceRule *recurrenceRule; @property Attendee *organizer; From c784757b0c692824d12aaa1327503b84e4716804 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Thu, 28 May 2020 15:37:31 +0300 Subject: [PATCH 07/15] change ios developer team --- .../example/ios/Runner.xcodeproj/project.pbxproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index d9000d07..001fe62c 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = PT8YH28249; + DevelopmentTeam = YCDSDGA3LX; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,7 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -465,7 +465,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -477,7 +477,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; From d376caa53467a32afdb16a05667542f669438659 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Tue, 31 Mar 2020 19:04:06 +0300 Subject: [PATCH 08/15] change ios part - migration to Objective-c --- device_calendar/example/ios/Podfile | 1 + device_calendar/example/ios/Podfile.lock | 12 +- .../ios/Runner.xcodeproj/project.pbxproj | 18 +- .../example/ios/Runner/AppDelegate.h | 6 + .../example/ios/Runner/AppDelegate.m | 13 + .../example/ios/Runner/AppDelegate.swift | 13 - .../ios/Runner/Runner-Bridging-Header.h | 1 - device_calendar/example/ios/Runner/main.m | 9 + device_calendar/ios/Classes/Date+Utilities.h | 11 + device_calendar/ios/Classes/Date+Utilities.m | 10 + .../Classes/DeviceCalendarExtendedPlugin.h | 4 + .../Classes/DeviceCalendarExtendedPlugin.m | 8 + .../ios/Classes/DeviceCalendarPlugin.h | 5 +- .../ios/Classes/DeviceCalendarPlugin.m | 702 +++++++++++++++- .../ios/Classes/EKParticipant+Utilities.h | 12 + .../ios/Classes/EKParticipant+Utilities.m | 9 + .../Classes/SwiftDeviceCalendarPlugin.swift | 783 ------------------ .../ios/Classes/UIColor+Utilities.h | 7 + .../ios/Classes/UIColor+Utilities.m | 22 + device_calendar/ios/Classes/models/Attendee.h | 11 + device_calendar/ios/Classes/models/Attendee.m | 6 + device_calendar/ios/Classes/models/Calendar.h | 14 + device_calendar/ios/Classes/models/Calendar.m | 6 + .../ios/Classes/models/Department.h | 7 + .../ios/Classes/models/Department.m | 6 + device_calendar/ios/Classes/models/Event.h | 21 + device_calendar/ios/Classes/models/Event.m | 6 + .../ios/Classes/models/RecurrenceRule.h | 16 + .../ios/Classes/models/RecurrenceRule.m | 6 + device_calendar/ios/Classes/models/Reminder.h | 8 + device_calendar/ios/Classes/models/Reminder.m | 6 + device_calendar/ios/device_calendar.podspec | 3 +- 32 files changed, 952 insertions(+), 810 deletions(-) create mode 100644 device_calendar/example/ios/Runner/AppDelegate.h create mode 100644 device_calendar/example/ios/Runner/AppDelegate.m delete mode 100644 device_calendar/example/ios/Runner/AppDelegate.swift delete mode 100644 device_calendar/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 device_calendar/example/ios/Runner/main.m create mode 100644 device_calendar/ios/Classes/Date+Utilities.h create mode 100644 device_calendar/ios/Classes/Date+Utilities.m create mode 100644 device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h create mode 100644 device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m create mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.h create mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.m delete mode 100644 device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift create mode 100644 device_calendar/ios/Classes/UIColor+Utilities.h create mode 100644 device_calendar/ios/Classes/UIColor+Utilities.m create mode 100644 device_calendar/ios/Classes/models/Attendee.h create mode 100644 device_calendar/ios/Classes/models/Attendee.m create mode 100644 device_calendar/ios/Classes/models/Calendar.h create mode 100644 device_calendar/ios/Classes/models/Calendar.m create mode 100644 device_calendar/ios/Classes/models/Department.h create mode 100644 device_calendar/ios/Classes/models/Department.m create mode 100644 device_calendar/ios/Classes/models/Event.h create mode 100644 device_calendar/ios/Classes/models/Event.m create mode 100644 device_calendar/ios/Classes/models/RecurrenceRule.h create mode 100644 device_calendar/ios/Classes/models/RecurrenceRule.m create mode 100644 device_calendar/ios/Classes/models/Reminder.h create mode 100644 device_calendar/ios/Classes/models/Reminder.m diff --git a/device_calendar/example/ios/Podfile b/device_calendar/example/ios/Podfile index 2c347942..5394ab7e 100644 --- a/device_calendar/example/ios/Podfile +++ b/device_calendar/example/ios/Podfile @@ -48,6 +48,7 @@ target 'Runner' do } # Plugin Pods + pod 'JSONModel' plugin_pods = parse_KV_file('../.flutter-plugins') plugin_pods.map { |p| symlink = File.join('Pods', '.symlinks', 'plugins', p[:name]) diff --git a/device_calendar/example/ios/Podfile.lock b/device_calendar/example/ios/Podfile.lock index b81b0078..5951ee35 100644 --- a/device_calendar/example/ios/Podfile.lock +++ b/device_calendar/example/ios/Podfile.lock @@ -1,11 +1,18 @@ PODS: - device_calendar (0.0.1): - Flutter + - JSONModel - Flutter (1.0.0) + - JSONModel (1.8.0) DEPENDENCIES: - device_calendar (from `Pods/.symlinks/plugins/device_calendar/ios`) - Flutter (from `Pods/.symlinks/flutter/ios`) + - JSONModel + +SPEC REPOS: + trunk: + - JSONModel EXTERNAL SOURCES: device_calendar: @@ -14,9 +21,10 @@ EXTERNAL SOURCES: :path: Pods/.symlinks/flutter/ios SPEC CHECKSUMS: - device_calendar: 23b28a5f1ab3bf77e34542fb1167e1b8b29a98f5 + device_calendar: fb103d17cdd72b294a9c464b811e903566f04e67 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + JSONModel: 02ab723958366a3fd27da57ea2af2113658762e9 -PODFILE CHECKSUM: ea0518673586564c605fb6593d385c0e3708ff8d +PODFILE CHECKSUM: d833004bd9a416c1f9c3fb12831e1203a373fbfb COCOAPODS: 1.8.4 diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 6a2dbac0..001fe62c 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,11 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 0904E8AC2433323B00DFD5BE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0904E8AB2433323B00DFD5BE /* AppDelegate.m */; }; + 0904E8AF2433360500DFD5BE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0904E8AE2433360500DFD5BE /* main.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 763C5C3662C48FEF7F2B0120 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E098C60D243A71853922C094 /* Pods_Runner.framework */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -36,13 +37,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0904E8AB2433323B00DFD5BE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 0904E8AD2433325300DFD5BE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 0904E8AE2433360500DFD5BE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2AD784C0989B63BAA46EFDFD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -129,8 +131,9 @@ 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 0904E8AB2433323B00DFD5BE /* AppDelegate.m */, + 0904E8AD2433325300DFD5BE /* AppDelegate.h */, + 0904E8AE2433360500DFD5BE /* main.m */, ); path = Runner; sourceTree = ""; @@ -224,11 +227,13 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/JSONModel/JSONModel.framework", "${BUILT_PRODUCTS_DIR}/device_calendar/device_calendar.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JSONModel.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_calendar.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -289,8 +294,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 0904E8AF2433360500DFD5BE /* main.m in Sources */, + 0904E8AC2433323B00DFD5BE /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/device_calendar/example/ios/Runner/AppDelegate.h b/device_calendar/example/ios/Runner/AppDelegate.h new file mode 100644 index 00000000..36e21bbf --- /dev/null +++ b/device_calendar/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/device_calendar/example/ios/Runner/AppDelegate.m b/device_calendar/example/ios/Runner/AppDelegate.m new file mode 100644 index 00000000..f699984e --- /dev/null +++ b/device_calendar/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application +didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/device_calendar/example/ios/Runner/AppDelegate.swift b/device_calendar/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 70693e4a..00000000 --- a/device_calendar/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/device_calendar/example/ios/Runner/Runner-Bridging-Header.h b/device_calendar/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 7335fdf9..00000000 --- a/device_calendar/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/device_calendar/example/ios/Runner/main.m b/device_calendar/example/ios/Runner/main.m new file mode 100644 index 00000000..0341bdd0 --- /dev/null +++ b/device_calendar/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/device_calendar/ios/Classes/Date+Utilities.h b/device_calendar/ios/Classes/Date+Utilities.h new file mode 100644 index 00000000..35e3d1ab --- /dev/null +++ b/device_calendar/ios/Classes/Date+Utilities.h @@ -0,0 +1,11 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSDate (Utilities) + +@property (readonly) float millisecondsSinceEpoch; + +@end + +NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/Date+Utilities.m b/device_calendar/ios/Classes/Date+Utilities.m new file mode 100644 index 00000000..a264be75 --- /dev/null +++ b/device_calendar/ios/Classes/Date+Utilities.m @@ -0,0 +1,10 @@ +#import "Date+Utilities.h" + +@implementation NSDate (Utilities) + +- (float) millisecondsSinceEpoch +{ + return [self timeIntervalSince1970] * 1000.0; +} + +@end diff --git a/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h new file mode 100644 index 00000000..831b6bd7 --- /dev/null +++ b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface DeviceCalendarExtendedPlugin : NSObject +@end diff --git a/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m new file mode 100644 index 00000000..ac2a7534 --- /dev/null +++ b/device_calendar/ios/Classes/DeviceCalendarExtendedPlugin.m @@ -0,0 +1,8 @@ +#import "DeviceCalendarExtendedPlugin.h" +#import "DeviceCalendarPlugin.h" + +@implementation DeviceCalendarExtendedPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [DeviceCalendarPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.h b/device_calendar/ios/Classes/DeviceCalendarPlugin.h index 0d5ad0b9..60894a85 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.h +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.h @@ -1,4 +1,7 @@ #import +#import -@interface DeviceCalendarPlugin : NSObject +@interface DeviceCalendarPlugin: NSObject @end + + diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 774b46d3..9e762fa9 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -1,8 +1,704 @@ #import "DeviceCalendarPlugin.h" -#import +#import +#import +#import +#import +#import "models/Calendar.h" +#import "models/RecurrenceRule.h" +#import "models/Event.h" +#import "models/Attendee.h" +#import "models/Reminder.h" +#import "models/Department.h" +#import "Date+Utilities.h" +#import "UIColor+Utilities.h" +#import "EKParticipant+Utilities.h" +#import @implementation DeviceCalendarPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftDeviceCalendarPlugin registerWithRegistrar:registrar]; +NSString *streamName = @"calendarChangeEvent/stream"; +NSString *methodChannelName = @"plugins.builttoroam.com/device_calendar"; +NSString *notFoundErrorCode = @"404"; +NSString *notAllowed = @"405"; +NSString *genericError = @"500"; +NSString *unauthorizedErrorCode = @"401"; +NSString *unauthorizedErrorMessage = @"The user has not allowed this application to modify their calendar(s)"; +NSString *calendarNotFoundErrorMessageFormat = @"The calendar with the ID %@ could not be found"; +NSString *calendarReadOnlyErrorMessageFormat = @"Calendar with ID %@ is read-only"; +NSString *eventNotFoundErrorMessageFormat = @"The event with the ID %@ could not be found"; +NSString *requestPermissionsMethod = @"requestPermissions"; +NSString *hasPermissionsMethod = @"hasPermissions"; +NSString *retrieveSourcesMethod = @"retrieveSources"; +NSString *retrieveCalendarsMethod = @"retrieveCalendars"; +NSString *retrieveEventsMethod = @"retrieveEvents"; +NSString *createCalendarMethod = @"createCalendar"; +NSString *createOrUpdateEventMethod = @"createOrUpdateEvent"; +NSString *deleteEventMethod = @"deleteEvent"; +NSString *deleteEventInstanceMethod = @"deleteEventInstance"; +NSString *calendarIdArgument = @"calendarId"; +NSString *startDateArgument = @"startDate"; +NSString *endDateArgument = @"endDate"; +NSString *eventIdArgument = @"eventId"; +NSString *eventIdsArgument = @"eventIds"; +NSString *eventTitleArgument = @"eventTitle"; +NSString *eventDescriptionArgument = @"eventDescription"; +NSString *eventAllDayArgument = @"eventAllDay"; +NSString *eventStartDateArgument = @"eventStartDate"; +NSString *eventEndDateArgument = @"eventEndDate"; +NSString *eventLocationArgument = @"eventLocation"; +NSString *attendeesArgument = @"attendees"; +NSString *recurrenceRuleArgument = @"recurrenceRule"; +NSString *recurrenceFrequencyArgument = @"recurrenceFrequency"; +NSString *totalOccurrencesArgument = @"totalOccurrences"; +NSString *intervalArgument = @"interval"; +NSString *daysOfWeekArgument = @"daysOfWeek"; +NSString *daysOfMonthArgument = @"daysOfMonth"; +NSString *monthOfYearArgument = @"monthsOfYear"; +NSString *weeksOfYearArgument = @"weeksOfYear"; +NSString *weekOfMonthArgument = @"weekOfMonth"; +NSString *nameArgument = @"nameArgument"; +NSString *emailAddressArgument = @"emailAddress"; +NSString *roleArgument = @"role"; +NSString *remindersArgument = @"reminders"; +NSString *minutesArgument = @"minutes"; +NSString *followingInstancesArgument = @"followingInstances"; +NSString *calendarNameArgument = @"calendarName"; +NSString *calendarColorArgument = @"calendarColor"; +NSMutableArray *validFrequencyTypes; +EKEventStore *eventStore; + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel + methodChannelWithName: methodChannelName + binaryMessenger:[registrar messenger]]; + eventStore = [[EKEventStore alloc] init]; + validFrequencyTypes = [NSMutableArray new]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyDaily]]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyWeekly]]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyMonthly]]; + [validFrequencyTypes addObject: [[NSNumber alloc] initWithInt:EKRecurrenceFrequencyYearly]]; + DeviceCalendarPlugin* instance = [[DeviceCalendarPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + NSString *method = call.method; + if ([method isEqualToString: requestPermissionsMethod]) { + [self requestPermissions:nil result:result]; + return; + } + else if([method isEqualToString: hasPermissionsMethod]) { + [self hasPermissions:nil result:result]; + return; + } + else if ([method isEqualToString: retrieveCalendarsMethod]) { + [self retrieveCalendars:nil result:result]; + return; + } + else if ([method isEqualToString: retrieveEventsMethod]) { + [self retrieveEvents:call result:result]; + return; + } + else if ([method isEqualToString: createOrUpdateEventMethod]) { + [self createOrUpdateEvent:call result:result]; + return; + } + else if ([method isEqualToString: deleteEventMethod]) { + [self deleteEvent:call result:result]; + return; + } + else if ([method isEqualToString:deleteEventInstanceMethod]) { + [self deleteEvent:call result:result]; + return; + } + else if ([method isEqualToString:createCalendarMethod]) { + [self createCalendar:call result:result]; + return; + } + else + result(FlutterMethodNotImplemented); +} + +-(void)hasPermissions:(id)args result:(FlutterResult)result { + result([NSNumber numberWithBool:[self hasEventPermissions]]); +} + +-(void)createCalendar:(FlutterMethodCall *)call result:(FlutterResult)result { + NSDictionary *arguments = call.arguments; + EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:eventStore]; + calendar.title = [arguments valueForKey:calendarNameArgument]; + NSString *calendarColor = [arguments valueForKey:calendarColorArgument]; + NSMutableArray *localSources = [NSMutableArray new]; + NSError *error = nil; + if (calendarColor != nil) { + calendar.CGColor = [[self colorFromHexString:calendarColor] CGColor]; + }else { + calendar.CGColor = [[[UIColor alloc] initWithRed:255 green:0 blue:0 alpha:0] CGColor]; + } + for (EKSource *ekSource in eventStore.sources) { + if (ekSource.sourceType == EKSourceTypeLocal) { + [localSources addObject:ekSource]; + } + } + if ([localSources count] == 0) { + calendar.source = [localSources firstObject]; + [eventStore saveCalendar:calendar commit:YES error:&error]; + } else { + result([FlutterError errorWithCode:genericError message: @"Local calendar was not found." details:nil ]); + } + if (error == nil) { + result([calendar calendarIdentifier]); + } else { + [eventStore reset]; + result([FlutterError errorWithCode:genericError message: [error localizedDescription] details:nil ]); + } +} + +- (UIColor *)colorFromHexString:(NSString *)hexString { + unsigned rgbValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + [scanner setScanLocation:1]; // bypass '#' character + [scanner scanHexInt:&rgbValue]; + return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 24)/255.0 green:((rgbValue & 0xFF00) >> 16)/255.0 blue:((rgbValue & 0x0000ff00) >> 8)/255.0 alpha: (rgbValue & 0x000000ff) / 255]; +} + +-(void)retrieveCalendars:(id)args result:(FlutterResult)result{ + Department *department = [Department new]; + department.calendars = [NSMutableArray new]; + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSArray *ekcalendars = [eventStore calendarsForEntityType:EKEntityTypeEvent]; + EKCalendar *defaultCalendar = [eventStore defaultCalendarForNewEvents]; + for (EKCalendar* ekCalendar in ekcalendars) { + + Calendar *calendar = [Calendar new]; + calendar.id = ekCalendar.calendarIdentifier; + calendar.name = ekCalendar.title; + calendar.isReadOnly = !ekCalendar.allowsContentModifications; + calendar.isDefault = [defaultCalendar calendarIdentifier] == [ekCalendar calendarIdentifier]; + calendar.color = [[[[UIColor alloc] initWithCGColor:[ekCalendar CGColor]] rgb] intValue]; + calendar.accountName = [[ekCalendar source] title]; + calendar.accountType = [self getAccountType:[[ekCalendar source]sourceType]]; + [department.calendars addObject:calendar]; + } + [self encodeJsonAndFinish:department result:result]; + } result:result]; +} + +-(NSString*)getAccountType:(EKSourceType)sourceType { + if (sourceType == EKSourceTypeLocal) { + return @"Local"; + }else if(sourceType == EKSourceTypeExchange) { + return @"Exchange"; + }else if(sourceType == EKSourceTypeCalDAV) { + return @"CalDAV"; + }else if(sourceType == EKSourceTypeMobileMe) { + return @"MobileMe"; + }else if(sourceType == EKSourceTypeSubscribed) { + return @"Subscribed"; + }else if(sourceType == EKSourceTypeBirthdays) { + return @"Birthdays"; + } else { + return @"Unknown"; + } +} + +-(void)retrieveEvents:(FlutterMethodCall *)call result:(FlutterResult)result { + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSDictionary *arguments = call.arguments; + NSString *calendarId = [arguments valueForKey:calendarIdArgument]; + NSNumber *startDateMillisecondsSinceEpoch = [arguments valueForKey:startDateArgument]; + NSNumber *endDateMillisecondsSinceEpoch = [arguments valueForKey:endDateArgument]; + NSArray *_Nullable eventIds = [arguments valueForKey:eventIdsArgument]; + Department *department = [[Department alloc] init]; + department.events = [NSMutableArray new]; + NSMutableArray *events = [NSMutableArray new]; + BOOL specifiedStartEndDates = startDateMillisecondsSinceEpoch != nil && endDateMillisecondsSinceEpoch != nil; + if (specifiedStartEndDates) { + NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSince1970: [startDateMillisecondsSinceEpoch doubleValue] / 1000.0]; + NSDate *endDate = [[NSDate alloc] initWithTimeIntervalSince1970: [endDateMillisecondsSinceEpoch doubleValue] / 1000.0]; + EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + NSMutableArray *calendars = [NSMutableArray new]; + [calendars addObject:ekCalendar]; + NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:calendars]; + NSArray *ekEvents = [eventStore eventsMatchingPredicate:predicate]; + for (EKEvent* ekEvent in ekEvents) { + Event *event = [self createEventFromEkEvent:calendarId event:ekEvent]; + [events addObject:event]; + } + } + + if (eventIds == [NSNull null]) { + for (Event *event in events) { + [department.events addObject:event]; + } + [self encodeJsonAndFinish:department result:result]; + return; + } + if (specifiedStartEndDates) { + for (Event *event in events) { + if (event.calendarId == calendarId && [eventIds containsObject:event.eventId]) { + [department.events addObject:event]; + } + } + [self encodeJsonAndFinish:department result:result]; + return; + } + for (NSString *eventId in eventIds) { + EKEvent *ekEvent = [eventStore eventWithIdentifier:eventId]; + if (ekEvent != nil) { + continue; + } + Event *event = [self createEventFromEkEvent:calendarId event:ekEvent]; + [department.events addObject:event]; + } + [self encodeJsonAndFinish:department result:result]; + } result: result]; +} + +-(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent { + NSMutableArray *attendees = [NSMutableArray new]; + if ([ekEvent attendees] != nil) { + for(EKParticipant *ekParticipant in [ekEvent attendees]) { + Attendee *attendee = [self convertEkParticipantToAttendee:ekParticipant]; + if (attendee == nil) { + continue; + } + [attendees addObject:attendee]; + } + } + + NSMutableArray *reminders = [NSMutableArray new]; + if ([ekEvent alarms] != nil) { + for (EKAlarm *alarm in [ekEvent alarms]) { + Reminder *reminder = [Reminder new]; + NSUInteger minutes = -[alarm relativeOffset]/60; + reminder.minutes = [[[NSNumber alloc] initWithUnsignedInteger:minutes] integerValue]; + [reminders addObject:reminder]; + } + } + + RecurrenceRule *recurrenceRule = [self parseEKRecurrenceRules:ekEvent]; + Event *event = [Event new]; + event.eventId = [ekEvent eventIdentifier]; + event.calendarId = calendarId; + if (ekEvent.title == nil) { + event.title = @"New title"; + } else { + event.title = [ekEvent title]; + } + event.description = [ekEvent notes]; + event.start = [[[NSNumber alloc] initWithFloat:[[ekEvent startDate] millisecondsSinceEpoch]] integerValue]; + event.end = [[[NSNumber alloc] initWithFloat:[[ekEvent endDate] millisecondsSinceEpoch]] integerValue]; + event.allDay = [ekEvent isAllDay]; + event.attendees = attendees; + event.location = [ekEvent location]; + event.recurrenceRule = recurrenceRule; + event.organizer = [self convertEkParticipantToAttendee:[ekEvent organizer]]; + event.reminders = reminders; + return event; +} + +-(Attendee*)convertEkParticipantToAttendee: (EKParticipant *)ekParticipant { + if (ekParticipant == nil || [ekParticipant emailAddress] == nil) { + return nil; + } + Attendee *attendee = [Attendee new]; + attendee.name = [ekParticipant name]; + attendee.emailAddress = [ekParticipant emailAddress]; + attendee.role = [ekParticipant participantRole]; + return attendee; +} + +-(RecurrenceRule *)parseEKRecurrenceRules:(EKEvent *)ekEvent { + RecurrenceRule *recurrenceRule = [RecurrenceRule new]; + if ([ekEvent hasRecurrenceRules]) { + EKRecurrenceRule *ekRecurrenceRule = [[ekEvent recurrenceRules] firstObject]; + NSInteger frequency; + switch ([ekRecurrenceRule frequency]) { + case EKRecurrenceFrequencyDaily: + frequency = 0; + break; + case EKRecurrenceFrequencyWeekly: + frequency = 1; + break; + case EKRecurrenceFrequencyMonthly: + frequency = 2; + break; + case EKRecurrenceFrequencyYearly: + frequency = 3; + break; + default: + frequency = 0; + break; + } + + NSInteger totalOccurrences = 0; + NSInteger endDate; + if([[ekRecurrenceRule recurrenceEnd] occurrenceCount] != 0){ + totalOccurrences = [[ekRecurrenceRule recurrenceEnd] occurrenceCount]; + } + float endDateMs = -1; + endDateMs = [[[ekRecurrenceRule recurrenceEnd] endDate] millisecondsSinceEpoch]; + if (endDateMs != -1) { + endDate = [[[NSNumber alloc] initWithFloat:endDateMs] integerValue]; + } + + NSMutableArray *daysOfWeek = [NSMutableArray new]; + NSInteger weekOfMonth = [[[ekRecurrenceRule setPositions] firstObject] intValue]; + if ([ekRecurrenceRule daysOfTheWeek] != nil && [[ekRecurrenceRule daysOfTheWeek] count] != 0) { + daysOfWeek = [NSMutableArray new]; + for (EKRecurrenceDayOfWeek *dayOfWeek in [ekRecurrenceRule daysOfTheWeek]) { + [daysOfWeek addObject: [[NSNumber alloc] initWithInt:[dayOfWeek dayOfTheWeek] - 1]]; + if (weekOfMonth == 0) { + weekOfMonth = [dayOfWeek weekNumber]; + } + } + } + + NSInteger dayOfMonth = [[[ekRecurrenceRule daysOfTheMonth] firstObject] intValue]; + NSInteger monthOfYear = [[[ekRecurrenceRule monthsOfTheYear] firstObject] intValue]; + + if (ekRecurrenceRule.frequency == EKRecurrenceFrequencyYearly + && weekOfMonth == 0 && dayOfMonth == 0 && monthOfYear == 0) { + NSDateFormatter *dateFormater = [[NSDateFormatter alloc] init]; + dateFormater.dateFormat = @"d"; + dayOfMonth = [[dateFormater stringFromDate:ekEvent.startDate] intValue]; + dateFormater.dateFormat = @"M"; + monthOfYear = [[dateFormater stringFromDate:ekEvent.startDate] intValue]; + } + + recurrenceRule.recurrenceFrequency = frequency; + recurrenceRule.totalOccurrences = totalOccurrences; + recurrenceRule.interval = [ekRecurrenceRule interval]; + recurrenceRule.endDate = endDate; + recurrenceRule.daysOfWeek = daysOfWeek; + recurrenceRule.daysOfMonth = dayOfMonth; + recurrenceRule.monthsOfYear = monthOfYear; + recurrenceRule.weekOfMonth = weekOfMonth; + } + return recurrenceRule; +} + +-(NSArray*) createEKRecurrenceRules: (NSDictionary*)arguments { + NSDictionary *recurrenceRuleArguments = [arguments valueForKey:recurrenceRuleArgument]; + if (recurrenceRuleArguments) { + return nil; + } + + NSString *recurrenceFrequencyIndex = [recurrenceRuleArguments valueForKey:recurrenceFrequencyArgument]; + NSInteger totalOccurrences = [[recurrenceRuleArguments valueForKey:totalOccurrencesArgument] integerValue]; + NSInteger interval = -1; + interval = [[recurrenceRuleArguments valueForKey:intervalArgument] integerValue]; + NSInteger recurrenceInterval = 1; + NSNumber *endDate = [recurrenceRuleArguments valueForKey:endDateArgument]; + EKRecurrenceFrequency namedFrequency = [[validFrequencyTypes valueForKey:recurrenceFrequencyIndex] integerValue]; + EKRecurrenceEnd *recurrenceEnd; + + if (endDate != nil) { + recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithEndDate: [[NSDate alloc] initWithTimeIntervalSince1970: [endDate doubleValue]]]; + } else if (totalOccurrences > 0){ + recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithOccurrenceCount: totalOccurrences]; + } + if (interval != -1 && interval > 1) { + recurrenceInterval = interval; + } + + NSArray *daysOfTheWeekIndices = [recurrenceRuleArguments valueForKey:daysOfWeekArgument]; + NSMutableArray *daysOfWeek; + + if (daysOfWeek != nil && [daysOfTheWeekIndices count] == 0) { + daysOfWeek = [NSMutableArray new]; + for (NSNumber *dayOfWeekIndex in daysOfTheWeekIndices) { + NSNumber *weekOfMonth = [recurrenceRuleArguments valueForKey:weekOfMonthArgument]; + if (weekOfMonth != nil) { + if (namedFrequency == EKRecurrenceFrequencyYearly || [weekOfMonth intValue] == -1) { + EKRecurrenceDayOfWeek *dayOfTheWeek = [EKRecurrenceDayOfWeek dayOfWeek:[dayOfWeekIndex integerValue] + 1 weekNumber: [weekOfMonth intValue]]; + [daysOfWeek addObject: dayOfTheWeek]; + } + } + if ([daysOfWeek count] == 0) { + [daysOfWeek addObject:[EKRecurrenceDayOfWeek dayOfWeek:[dayOfWeekIndex integerValue] + 1]]; + } + } + } + NSMutableArray *dayOfMonthArray; + NSNumber *dayOfMonth = [recurrenceRuleArguments valueForKey:monthOfYearArgument]; + if (dayOfMonth != nil) { + dayOfMonthArray = [NSMutableArray new]; + [dayOfMonthArray addObject: dayOfMonth]; + } + NSMutableArray *monthOfYearArray; + NSNumber *monthOfYear = [recurrenceRuleArguments valueForKey:monthOfYearArgument]; + if (monthOfYear != nil) { + monthOfYearArray = [NSMutableArray new]; + [monthOfYearArray addObject: monthOfYear]; + } + NSMutableArray *weekOfMonthArray; + if (namedFrequency == EKRecurrenceFrequencyMonthly) { + NSNumber *weekOfMonth = [recurrenceRuleArguments valueForKey:weekOfMonthArgument]; + if (weekOfMonth != nil) { + weekOfMonthArray = [NSMutableArray init]; + [weekOfMonthArray addObject:weekOfMonth]; + } + } + NSMutableArray *ekRecurrenceRules = [NSMutableArray new]; + EKRecurrenceRule *ekRecurrenceRule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency: + namedFrequency + interval:recurrenceInterval + daysOfTheWeek:daysOfWeek + daysOfTheMonth:dayOfMonthArray + monthsOfTheYear:monthOfYearArray + weeksOfTheYear:nil + daysOfTheYear:nil + setPositions:weekOfMonthArray + end:recurrenceEnd + ]; + [ekRecurrenceRules addObject: ekRecurrenceRule]; + return ekRecurrenceRules; +} + +-(void)setAttendees: (NSDictionary *)arguments event:(EKEvent *)ekEvent{ + NSDictionary *attendeesArguments = [arguments valueForKey:attendeesArgument]; + if (attendeesArguments == nil) { + return; + } + + NSMutableArray *attendees = [NSMutableArray new]; + for (NSString *attendeeArguments in attendeesArguments) { + NSString *name = [attendeesArguments valueForKey:nameArgument]; + NSString *emailAddress = [attendeeArguments valueForKey:emailAddressArgument]; + NSNumber *role = [attendeeArguments valueForKey:roleArgument]; + if ([ekEvent attendees] != nil) { + NSArray *participants = [ekEvent attendees]; + EKParticipant *existingAttendee; + for(EKParticipant* participant in participants) { + if ([participant emailAddress] == emailAddress) { + existingAttendee = participant; + break; + } + } + if (existingAttendee != nil && [[ekEvent organizer] emailAddress] != [existingAttendee emailAddress]) { + [attendees addObject: existingAttendee]; + continue; + } + EKParticipant *attendee = [self createParticipant:emailAddress name:name role:role]; + if (attendee == nil) { + continue; + } + } + } + [ekEvent setValue:attendees forKey:@"attendees"]; +} + +-(NSArray*)createReminders: (NSDictionary *)arguments { + NSDictionary *remindersArguments = [arguments valueForKey:remindersArgument]; + if (remindersArguments == nil) { + return nil; + } + NSMutableArray *reminders = [NSMutableArray new]; + for (NSString *reminderArguments in remindersArguments) { + NSNumber *arg = [[NSNumber alloc] initWithInt: [reminderArguments valueForKey:minutesArgument]]; + double relativeOffset = 60 * (-[arg doubleValue]); + [reminders addObject:[EKAlarm alarmWithRelativeOffset:relativeOffset]]; + } + return reminders; +} + +-(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)result{ + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSDictionary *arguments = [call arguments]; + NSString *calendarId = [arguments valueForKey:calendarIdArgument]; + NSString *eventId = [arguments valueForKey:eventIdArgument]; + BOOL isAllDay = [arguments valueForKey:eventAllDayArgument]; + NSNumber *startDateMillisecondsSinceEpoch = [arguments valueForKey: eventStartDateArgument]; + NSNumber *endDateDateMillisecondsSinceEpoch = [arguments valueForKey: eventEndDateArgument]; + NSDate *startDate = [NSDate dateWithTimeIntervalSince1970: [startDateMillisecondsSinceEpoch doubleValue] / 1000.0 ]; + NSDate *endDate = [NSDate dateWithTimeIntervalSince1970: [endDateDateMillisecondsSinceEpoch doubleValue] / 1000.0 ]; + NSString *title = [arguments valueForKey: eventTitleArgument]; + NSString *description = [arguments valueForKey: eventDescriptionArgument]; + NSString *location = [arguments valueForKey: eventLocationArgument]; + EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + if (ekCalendar == nil) { + [self finishWithCalendarNotFoundError:calendarId result: result]; + return; + } + if (![ekCalendar allowsContentModifications]) { + [self finishWithCalendarReadOnlyError:calendarId result:result]; + return; + } + EKEvent *ekEvent; + if (eventId == [NSNull null]) { + ekEvent = [EKEvent eventWithEventStore:eventStore]; + }else { + ekEvent = [eventStore eventWithIdentifier:eventId]; + if (ekEvent == nil) { + [self finishWithEventNotFoundError: eventId result: result]; + return; + } + } + [ekEvent setTitle:title]; + [ekEvent setNotes:description]; + [ekEvent setAllDay:isAllDay]; + [ekEvent setStartDate:startDate]; + if (isAllDay) { + [ekEvent setEndDate:startDate]; + } else { + [ekEvent setEndDate:endDate]; + } + [ekEvent setCalendar:ekCalendar]; + [ekEvent setLocation:location]; + [ekEvent setRecurrenceRules: [self createEKRecurrenceRules: arguments]]; + [self setAttendees:arguments event:ekEvent]; + [ekEvent setAlarms: [self createReminders: arguments]]; + + NSError *error = nil; + [eventStore saveEvent:ekEvent span:EKSpanFutureEvents error:&error]; + if (error == nil) { + result([ekEvent eventIdentifier]); + } else { + [eventStore reset]; + result([FlutterError errorWithCode:genericError message: [error localizedDescription] details:nil ]); + } + + } result: result]; +} + +-(EKParticipant *)createParticipant:(NSString *)emailAddress name:(NSString *)name role:(NSNumber *)role { + Class ekAttendeeClass = NSClassFromString(@"EKAttendee"); + if ([ekAttendeeClass isSubclassOfClass:[NSObject class]]) { + NSObject *participant = [[ekAttendeeClass alloc] init]; + [participant setValue:emailAddress forKey:@"emailAddress"]; + [participant setValue:name forKey:@"displayName"]; + [participant setValue:role forKey:@"participantRole"]; + return (EKParticipant *)participant; + } + return nil; +} + +-(void)deleteEvent:(FlutterMethodCall *)call result:(FlutterResult)result { + [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ + NSDictionary *arguments = call.arguments; + NSString *calendarId = [arguments valueForKey:calendarIdArgument]; + NSString *eventId = [arguments valueForKey:eventIdArgument]; + NSNumber *startDateNumber = [arguments valueForKey:eventStartDateArgument]; + NSNumber *endDateNumber = [arguments valueForKey:eventEndDateArgument]; + BOOL followingInstances = [arguments valueForKey:followingInstancesArgument]; + EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + NSError *error = nil; + if (ekCalendar == nil) { + [self finishWithCalendarNotFoundError:calendarId result: result]; + return; + } + if (![ekCalendar allowsContentModifications]) { + [self finishWithCalendarReadOnlyError:calendarId result: result]; + return; + } + if (startDateNumber == nil && endDateNumber == nil && followingInstances == NO) { + EKEvent *ekEvent = [eventStore eventWithIdentifier: eventId]; + if (ekEvent == nil) { + [self finishWithEventNotFoundError:eventId result: result]; + return; + } + [eventStore removeEvent:ekEvent span:EKSpanFutureEvents error:&error]; + }else { + NSDate *startDate = [NSDate dateWithTimeIntervalSince1970: [startDateNumber doubleValue] / 1000.0]; + NSDate *endDate = [NSDate dateWithTimeIntervalSince1970: [endDateNumber doubleValue] / 1000.0]; + + NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil]; + NSArray *foundEkEvents = [eventStore eventsMatchingPredicate:predicate]; + if (foundEkEvents == nil || [foundEkEvents count] == 0) { + [self finishWithEventNotFoundError:eventId result:result]; + return; + } + NSMutableArray *ekEvents = [NSMutableArray new]; + for (EKEvent *event in foundEkEvents) { + if (event.eventIdentifier == eventId) { + [ekEvents addObject:event]; + break; + } + } + if (!followingInstances) { + [eventStore removeEvent:ekEvents.firstObject span:EKSpanThisEvent commit: YES error:&error]; + } else { + [eventStore removeEvent:ekEvents.firstObject span:EKSpanFutureEvents commit: YES error:&error]; + } + } + if (error == nil) { + result([NSNumber numberWithBool:YES]); + } else { + [eventStore reset]; + result([FlutterError errorWithCode:genericError message: [error localizedDescription] details:nil ]); + } + } result:result]; +} + +-(void)finishWithUnauthorizedError:(id)args result:(FlutterResult)result{ + result([FlutterError errorWithCode:unauthorizedErrorCode message:unauthorizedErrorMessage details:nil]); +} + +-(void)finishWithCalendarNotFoundError:(NSString *)calendarId result:(FlutterResult)result{ + NSString *errorMessage = [calendarNotFoundErrorMessageFormat stringByAppendingFormat: calendarId]; + result([FlutterError errorWithCode:notFoundErrorCode message:errorMessage details:nil]); +} + +-(void)finishWithCalendarReadOnlyError:(NSString *)calendarId result:(FlutterResult)result{ + NSString *errorMessage = [calendarReadOnlyErrorMessageFormat stringByAppendingFormat: calendarId]; + result([FlutterError errorWithCode:notAllowed message:errorMessage details:nil]); +} + +-(void)finishWithEventNotFoundError: (NSString *)eventId result:(FlutterResult)result { + NSString *errorMessage = [eventNotFoundErrorMessageFormat stringByAppendingFormat: eventId]; + result([FlutterError errorWithCode:notFoundErrorCode message:errorMessage details:nil]); +} + +-(void)encodeJsonAndFinish: (Department *)codable result:(FlutterResult)result { + NSMutableArray *resultArr = [NSMutableArray new]; + if ([codable.calendars count] > 0) { + for(Calendar *calendar in codable.calendars) { + [resultArr addObject:[calendar toJSONString]]; + } + } else if ([codable.events count] > 0) { + for(Event *event in codable.events) { + [resultArr addObject:[event toJSONString]]; + } + } + + NSString * arrayToString = [[resultArr valueForKey:@"description"] componentsJoinedByString:@","]; + NSString *resultStr = [[NSString alloc] initWithFormat:@"[%@]", arrayToString]; + result(resultStr); +} + +-(void)checkPermissionsThenExecute:(id)args permissionsGrantedAction:(void (^)(void))permissionsGrantedAction result:(FlutterResult)result { + if ([self hasEventPermissions]) { + permissionsGrantedAction(); + return; + } + [self finishWithUnauthorizedError:nil result:result]; +} + +-(void) selector: (FlutterResult *) result { + +} + +-(void)requestPermissions: completion:(void (^)(BOOL success))complet { + if ([self hasEventPermissions]) { + complet(YES); + } else { + [eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { + complet(granted); + }]; + } +} + +-(BOOL)hasEventPermissions { + EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType: EKEntityTypeEvent]; + return status == EKAuthorizationStatusAuthorized; +} + +-(void)requestPermissions:(id)args result:(FlutterResult)result { + if ([self hasEventPermissions]) + result([NSNumber numberWithBool:YES]); + [eventStore requestAccessToEntityType: EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { + result([NSNumber numberWithBool:granted]); + }]; } @end diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.h b/device_calendar/ios/Classes/EKParticipant+Utilities.h new file mode 100644 index 00000000..8adbc519 --- /dev/null +++ b/device_calendar/ios/Classes/EKParticipant+Utilities.h @@ -0,0 +1,12 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface EKParticipant (Utilities) + +@property (readonly) NSString *emailAddress; + +@end + +NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.m b/device_calendar/ios/Classes/EKParticipant+Utilities.m new file mode 100644 index 00000000..43dec69b --- /dev/null +++ b/device_calendar/ios/Classes/EKParticipant+Utilities.m @@ -0,0 +1,9 @@ +#import "EKParticipant+Utilities.h" + +@implementation EKParticipant (Utilities) + +- (NSString *) emailAddress +{ + return [self valueForKey:@"emailAddress"]; +} +@end diff --git a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift b/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift deleted file mode 100644 index d802ea4f..00000000 --- a/device_calendar/ios/Classes/SwiftDeviceCalendarPlugin.swift +++ /dev/null @@ -1,783 +0,0 @@ -import Flutter -import UIKit -import EventKit - -extension Date { - var millisecondsSinceEpoch: Double { return self.timeIntervalSince1970 * 1000.0 } -} - -extension EKParticipant { - var emailAddress: String? { - return self.value(forKey: "emailAddress") as? String - } -} - -public class SwiftDeviceCalendarPlugin: NSObject, FlutterPlugin { - struct Calendar: Codable { - let id: String - let name: String - let isReadOnly: Bool - let isDefault: Bool - let color: Int - let accountName: String - let accountType: String - } - - struct Event: Codable { - let eventId: String - let calendarId: String - let title: String - let description: String? - let start: Int64 - let end: Int64 - let allDay: Bool - let attendees: [Attendee] - let location: String? - let url: String? - let recurrenceRule: RecurrenceRule? - let organizer: Attendee? - let reminders: [Reminder] - } - - struct RecurrenceRule: Codable { - let recurrenceFrequency: Int - let totalOccurrences: Int? - let interval: Int - let endDate: Int64? - let daysOfWeek: [Int]? - let dayOfMonth: Int? - let monthOfYear: Int? - let weekOfMonth: Int? - } - - struct Attendee: Codable { - let name: String? - let emailAddress: String - let role: Int - let attendanceStatus: Int - } - - struct Reminder: Codable { - let minutes: Int - } - - static let channelName = "plugins.builttoroam.com/device_calendar" - let notFoundErrorCode = "404" - let notAllowed = "405" - let genericError = "500" - let unauthorizedErrorCode = "401" - let unauthorizedErrorMessage = "The user has not allowed this application to modify their calendar(s)" - let calendarNotFoundErrorMessageFormat = "The calendar with the ID %@ could not be found" - let calendarReadOnlyErrorMessageFormat = "Calendar with ID %@ is read-only" - let eventNotFoundErrorMessageFormat = "The event with the ID %@ could not be found" - let eventStore = EKEventStore() - let requestPermissionsMethod = "requestPermissions" - let hasPermissionsMethod = "hasPermissions" - let retrieveCalendarsMethod = "retrieveCalendars" - let retrieveEventsMethod = "retrieveEvents" - let retrieveSourcesMethod = "retrieveSources" - let createOrUpdateEventMethod = "createOrUpdateEvent" - let createCalendarMethod = "createCalendar" - let deleteEventMethod = "deleteEvent" - let deleteEventInstanceMethod = "deleteEventInstance" - let calendarIdArgument = "calendarId" - let startDateArgument = "startDate" - let endDateArgument = "endDate" - let eventIdArgument = "eventId" - let eventIdsArgument = "eventIds" - let eventTitleArgument = "eventTitle" - let eventDescriptionArgument = "eventDescription" - let eventAllDayArgument = "eventAllDay" - let eventStartDateArgument = "eventStartDate" - let eventEndDateArgument = "eventEndDate" - let eventLocationArgument = "eventLocation" - let eventURLArgument = "eventURL" - let attendeesArgument = "attendees" - let recurrenceRuleArgument = "recurrenceRule" - let recurrenceFrequencyArgument = "recurrenceFrequency" - let totalOccurrencesArgument = "totalOccurrences" - let intervalArgument = "interval" - let daysOfWeekArgument = "daysOfWeek" - let dayOfMonthArgument = "dayOfMonth" - let monthOfYearArgument = "monthOfYear" - let weekOfMonthArgument = "weekOfMonth" - let nameArgument = "name" - let emailAddressArgument = "emailAddress" - let roleArgument = "role" - let remindersArgument = "reminders" - let minutesArgument = "minutes" - let followingInstancesArgument = "followingInstances" - let calendarNameArgument = "calendarName" - let calendarColorArgument = "calendarColor" - let validFrequencyTypes = [EKRecurrenceFrequency.daily, EKRecurrenceFrequency.weekly, EKRecurrenceFrequency.monthly, EKRecurrenceFrequency.yearly] - - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) - let instance = SwiftDeviceCalendarPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case requestPermissionsMethod: - requestPermissions(result) - case hasPermissionsMethod: - hasPermissions(result) - case retrieveCalendarsMethod: - retrieveCalendars(result) - case retrieveEventsMethod: - retrieveEvents(call, result) - case createOrUpdateEventMethod: - createOrUpdateEvent(call, result) - case deleteEventMethod: - deleteEvent(call, result) - case deleteEventInstanceMethod: - deleteEvent(call, result) - case createCalendarMethod: - createCalendar(call, result) - default: - result(FlutterMethodNotImplemented) - } - } - - private func hasPermissions(_ result: FlutterResult) { - let hasPermissions = hasEventPermissions() - result(hasPermissions) - } - - private func createCalendar(_ call: FlutterMethodCall, _ result: FlutterResult) { - let arguments = call.arguments as! Dictionary - let calendar = EKCalendar.init(for: EKEntityType.event, eventStore: eventStore) - do { - calendar.title = arguments[calendarNameArgument] as! String - let calendarColor = arguments[calendarColorArgument] as? String - - if (calendarColor != nil) { - calendar.cgColor = UIColor(hex: calendarColor!)?.cgColor - } - else { - calendar.cgColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0).cgColor // Red colour as a default - } - - let localSources = eventStore.sources.filter { $0.sourceType == .local } - - if (!localSources.isEmpty) { - calendar.source = localSources.first - - try eventStore.saveCalendar(calendar, commit: true) - result(calendar.calendarIdentifier) - } - else { - result(FlutterError(code: self.genericError, message: "Local calendar was not found.", details: nil)) - } - } - catch { - eventStore.reset() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) - } - } - - private func retrieveCalendars(_ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - let ekCalendars = self.eventStore.calendars(for: .event) - let defaultCalendar = self.eventStore.defaultCalendarForNewEvents - var calendars = [Calendar]() - for ekCalendar in ekCalendars { - let calendar = Calendar( - id: ekCalendar.calendarIdentifier, - name: ekCalendar.title, - isReadOnly: !ekCalendar.allowsContentModifications, - isDefault: defaultCalendar?.calendarIdentifier == ekCalendar.calendarIdentifier, - color: UIColor(cgColor: ekCalendar.cgColor).rgb()!, - accountName: ekCalendar.source.title, - accountType: getAccountType(ekCalendar.source.sourceType)) - calendars.append(calendar) - } - - self.encodeJsonAndFinish(codable: calendars, result: result) - }, result: result) - } - - private func getAccountType(_ sourceType: EKSourceType) -> String { - switch (sourceType) { - case .local: - return "Local"; - case .exchange: - return "Exchange"; - case .calDAV: - return "CalDAV"; - case .mobileMe: - return "MobileMe"; - case .subscribed: - return "Subscribed"; - case .birthdays: - return "Birthdays"; - default: - return "Unknown"; - } - } - - private func retrieveEvents(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - let arguments = call.arguments as! Dictionary - let calendarId = arguments[calendarIdArgument] as! String - let startDateMillisecondsSinceEpoch = arguments[startDateArgument] as? NSNumber - let endDateDateMillisecondsSinceEpoch = arguments[endDateArgument] as? NSNumber - let eventIds = arguments[eventIdsArgument] as? [String] - var events = [Event]() - let specifiedStartEndDates = startDateMillisecondsSinceEpoch != nil && endDateDateMillisecondsSinceEpoch != nil - if specifiedStartEndDates { - let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch!.doubleValue / 1000.0) - let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch!.doubleValue / 1000.0) - 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) - } - } - - if eventIds == nil { - self.encodeJsonAndFinish(codable: events, result: result) - return - } - - if specifiedStartEndDates { - events = events.filter({ (e) -> Bool in - e.calendarId == calendarId && eventIds!.contains(e.eventId) - }) - - self.encodeJsonAndFinish(codable: events, result: result) - return - } - - for eventId in eventIds! { - let ekEvent = self.eventStore.event(withIdentifier: eventId) - if ekEvent == nil { - continue - } - - let event = createEventFromEkEvent(calendarId: calendarId, ekEvent: ekEvent!) - events.append(event) - } - - self.encodeJsonAndFinish(codable: events, result: result) - }, result: result) - } - - private func createEventFromEkEvent(calendarId: String, ekEvent: EKEvent) -> Event { - var attendees = [Attendee]() - if ekEvent.attendees != nil { - for ekParticipant in ekEvent.attendees! { - let attendee = convertEkParticipantToAttendee(ekParticipant: ekParticipant) - if attendee == nil { - continue - } - - attendees.append(attendee!) - } - } - - var reminders = [Reminder]() - if ekEvent.alarms != nil { - for alarm in ekEvent.alarms! { - reminders.append(Reminder(minutes: Int(-alarm.relativeOffset / 60))) - } - } - - let recurrenceRule = parseEKRecurrenceRules(ekEvent) - let event = Event( - eventId: ekEvent.eventIdentifier, - calendarId: calendarId, - title: ekEvent.title ?? "New Event", - description: ekEvent.notes, - start: Int64(ekEvent.startDate.millisecondsSinceEpoch), - end: Int64(ekEvent.endDate.millisecondsSinceEpoch), - allDay: ekEvent.isAllDay, - attendees: attendees, - location: ekEvent.location, - url: ekEvent.url?.absoluteString, - recurrenceRule: recurrenceRule, - organizer: convertEkParticipantToAttendee(ekParticipant: ekEvent.organizer), - reminders: reminders - ) - return event - } - - private func convertEkParticipantToAttendee(ekParticipant: EKParticipant?) -> Attendee? { - if ekParticipant == nil || ekParticipant?.emailAddress == nil { - return nil - } - - let attendee = Attendee(name: ekParticipant!.name, emailAddress: ekParticipant!.emailAddress!, role: ekParticipant!.participantRole.rawValue, attendanceStatus: ekParticipant!.participantStatus.rawValue) - return attendee - } - - private func parseEKRecurrenceRules(_ ekEvent: EKEvent) -> RecurrenceRule? { - var recurrenceRule: RecurrenceRule? - if ekEvent.hasRecurrenceRules { - let ekRecurrenceRule = ekEvent.recurrenceRules![0] - var frequency: Int - switch ekRecurrenceRule.frequency { - case EKRecurrenceFrequency.daily: - frequency = 0 - case EKRecurrenceFrequency.weekly: - frequency = 1 - case EKRecurrenceFrequency.monthly: - frequency = 2 - case EKRecurrenceFrequency.yearly: - frequency = 3 - default: - frequency = 0 - } - - var totalOccurrences: Int? - var endDate: Int64? - if(ekRecurrenceRule.recurrenceEnd?.occurrenceCount != nil) { - totalOccurrences = ekRecurrenceRule.recurrenceEnd?.occurrenceCount - } - - let endDateMs = ekRecurrenceRule.recurrenceEnd?.endDate?.millisecondsSinceEpoch - if(endDateMs != nil) { - endDate = Int64(exactly: endDateMs!) - } - - var weekOfMonth = ekRecurrenceRule.setPositions?.first?.intValue - - var daysOfWeek: [Int]? - if ekRecurrenceRule.daysOfTheWeek != nil && !ekRecurrenceRule.daysOfTheWeek!.isEmpty { - daysOfWeek = [] - for dayOfWeek in ekRecurrenceRule.daysOfTheWeek! { - daysOfWeek!.append(dayOfWeek.dayOfTheWeek.rawValue - 1) - - if weekOfMonth == nil { - weekOfMonth = dayOfWeek.weekNumber - } - } - } - - // For recurrence of nth day of nth month every year, no calendar parameters are given - // So we need to explicitly set them from event start date - var dayOfMonth = ekRecurrenceRule.daysOfTheMonth?.first?.intValue - var monthOfYear = ekRecurrenceRule.monthsOfTheYear?.first?.intValue - if (ekRecurrenceRule.frequency == EKRecurrenceFrequency.yearly - && weekOfMonth == nil && dayOfMonth == nil && monthOfYear == nil) { - let dateFormatter = DateFormatter() - - // Setting day of the month - dateFormatter.dateFormat = "d" - dayOfMonth = Int(dateFormatter.string(from: ekEvent.startDate)) - - // Setting month of the year - dateFormatter.dateFormat = "M" - monthOfYear = Int(dateFormatter.string(from: ekEvent.startDate)) - } - - recurrenceRule = RecurrenceRule( - recurrenceFrequency: frequency, - totalOccurrences: totalOccurrences, - interval: ekRecurrenceRule.interval, - endDate: endDate, - daysOfWeek: daysOfWeek, - dayOfMonth: dayOfMonth, - monthOfYear: monthOfYear, - weekOfMonth: weekOfMonth) - } - - return recurrenceRule - } - - private func createEKRecurrenceRules(_ arguments: [String : AnyObject]) -> [EKRecurrenceRule]?{ - let recurrenceRuleArguments = arguments[recurrenceRuleArgument] as? Dictionary - if recurrenceRuleArguments == nil { - return nil - } - - let recurrenceFrequencyIndex = recurrenceRuleArguments![recurrenceFrequencyArgument] as? NSInteger - let totalOccurrences = recurrenceRuleArguments![totalOccurrencesArgument] as? NSInteger - let interval = recurrenceRuleArguments![intervalArgument] as? NSInteger - var recurrenceInterval = 1 - let endDate = recurrenceRuleArguments![endDateArgument] as? NSNumber - let namedFrequency = validFrequencyTypes[recurrenceFrequencyIndex!] - - var recurrenceEnd:EKRecurrenceEnd? - if endDate != nil { - recurrenceEnd = EKRecurrenceEnd(end: Date.init(timeIntervalSince1970: endDate!.doubleValue / 1000)) - } else if(totalOccurrences != nil && totalOccurrences! > 0) { - recurrenceEnd = EKRecurrenceEnd(occurrenceCount: totalOccurrences!) - } - - if interval != nil && interval! > 1 { - recurrenceInterval = interval! - } - - let daysOfWeekIndices = recurrenceRuleArguments![daysOfWeekArgument] as? [Int] - var daysOfWeek : [EKRecurrenceDayOfWeek]? - - if daysOfWeekIndices != nil && !daysOfWeekIndices!.isEmpty { - daysOfWeek = [] - for dayOfWeekIndex in daysOfWeekIndices! { - // Append week number to BYDAY for yearly or monthly with 'last' week number - if let weekOfMonth = recurrenceRuleArguments![weekOfMonthArgument] as? Int { - if namedFrequency == EKRecurrenceFrequency.yearly || weekOfMonth == -1 { - daysOfWeek!.append(EKRecurrenceDayOfWeek.init( - dayOfTheWeek: EKWeekday.init(rawValue: dayOfWeekIndex + 1)!, - weekNumber: weekOfMonth - )) - } - } - - if daysOfWeek?.isEmpty == true { - daysOfWeek!.append(EKRecurrenceDayOfWeek.init(EKWeekday.init(rawValue: dayOfWeekIndex + 1)!)) - } - } - } - - var dayOfMonthArray : [NSNumber]? - if let dayOfMonth = recurrenceRuleArguments![dayOfMonthArgument] as? Int { - dayOfMonthArray = [] - dayOfMonthArray!.append(NSNumber(value: dayOfMonth)) - } - - var monthOfYearArray : [NSNumber]? - if let monthOfYear = recurrenceRuleArguments![monthOfYearArgument] as? Int { - monthOfYearArray = [] - monthOfYearArray!.append(NSNumber(value: monthOfYear)) - } - - // Append BYSETPOS only on monthly (but not last), yearly's week number (and last for monthly) appends to BYDAY - var weekOfMonthArray : [NSNumber]? - if namedFrequency == EKRecurrenceFrequency.monthly { - if let weekOfMonth = recurrenceRuleArguments![weekOfMonthArgument] as? Int { - if weekOfMonth != -1 { - weekOfMonthArray = [] - weekOfMonthArray!.append(NSNumber(value: weekOfMonth)) - } - } - } - - return [EKRecurrenceRule( - recurrenceWith: namedFrequency, - interval: recurrenceInterval, - daysOfTheWeek: daysOfWeek, - daysOfTheMonth: dayOfMonthArray, - monthsOfTheYear: monthOfYearArray, - weeksOfTheYear: nil, - daysOfTheYear: nil, - setPositions: weekOfMonthArray, - end: recurrenceEnd)] - } - - private func setAttendees(_ arguments: [String : AnyObject], _ ekEvent: EKEvent?) { - let attendeesArguments = arguments[attendeesArgument] as? [Dictionary] - if attendeesArguments == nil { - return - } - - var attendees = [EKParticipant]() - for attendeeArguments in attendeesArguments! { - let name = attendeeArguments[nameArgument] as! String - let emailAddress = attendeeArguments[emailAddressArgument] as! String - let role = attendeeArguments[roleArgument] as! Int - - if (ekEvent!.attendees != nil) { - let existingAttendee = ekEvent!.attendees!.first { element in - return element.emailAddress == emailAddress - } - if existingAttendee != nil && ekEvent!.organizer?.emailAddress != existingAttendee?.emailAddress{ - attendees.append(existingAttendee!) - continue - } - } - - let attendee = createParticipant( - name: name, - emailAddress: emailAddress, - role: role) - - if (attendee == nil) { - continue - } - - attendees.append(attendee!) - } - - ekEvent!.setValue(attendees, forKey: "attendees") - } - - private func createReminders(_ arguments: [String : AnyObject]) -> [EKAlarm]?{ - let remindersArguments = arguments[remindersArgument] as? [Dictionary] - if remindersArguments == nil { - return nil - } - - var reminders = [EKAlarm]() - for reminderArguments in remindersArguments! { - let minutes = reminderArguments[minutesArgument] as! Int - reminders.append(EKAlarm.init(relativeOffset: 60 * Double(-minutes))) - } - - return reminders - } - - private func createOrUpdateEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - let arguments = call.arguments as! Dictionary - let calendarId = arguments[calendarIdArgument] as! String - let eventId = arguments[eventIdArgument] as? String - let isAllDay = arguments[eventAllDayArgument] as! Bool - let startDateMillisecondsSinceEpoch = arguments[eventStartDateArgument] as! NSNumber - let endDateDateMillisecondsSinceEpoch = arguments[eventEndDateArgument] as! NSNumber - let startDate = Date (timeIntervalSince1970: startDateMillisecondsSinceEpoch.doubleValue / 1000.0) - let endDate = Date (timeIntervalSince1970: endDateDateMillisecondsSinceEpoch.doubleValue / 1000.0) - let title = arguments[self.eventTitleArgument] as! String - let description = arguments[self.eventDescriptionArgument] as? String - let location = arguments[self.eventLocationArgument] as? String - let url = arguments[self.eventURLArgument] 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 - } - - var ekEvent: EKEvent? - if eventId == nil { - ekEvent = EKEvent.init(eventStore: self.eventStore) - } else { - ekEvent = self.eventStore.event(withIdentifier: eventId!) - if(ekEvent == nil) { - self.finishWithEventNotFoundError(result: result, eventId: eventId!) - return - } - } - - ekEvent!.title = title - ekEvent!.notes = description - ekEvent!.isAllDay = isAllDay - ekEvent!.startDate = startDate - if (isAllDay) { ekEvent!.endDate = startDate } - else { ekEvent!.endDate = endDate } - ekEvent!.calendar = ekCalendar! - ekEvent!.location = location - - // Create and add URL object only when if the input string is not empty or nil - if let urlCheck = url, !urlCheck.isEmpty { - let iosUrl = URL(string: url ?? "") - ekEvent!.url = iosUrl - } - else { - ekEvent!.url = nil - } - - ekEvent!.recurrenceRules = createEKRecurrenceRules(arguments) - setAttendees(arguments, ekEvent) - ekEvent!.alarms = createReminders(arguments) - - do { - try self.eventStore.save(ekEvent!, span: .futureEvents) - result(ekEvent!.eventIdentifier) - } catch { - self.eventStore.reset() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) - } - }, result: result) - } - - private func createParticipant(name: String, emailAddress: String, role: Int) -> EKParticipant? { - let ekAttendeeClass: AnyClass? = NSClassFromString("EKAttendee") - if let type = ekAttendeeClass as? NSObject.Type { - let participant = type.init() - participant.setValue(name, forKey: "displayName") - participant.setValue(emailAddress, forKey: "emailAddress") - participant.setValue(role, forKey: "participantRole") - return participant as? EKParticipant - } - return nil - } - - private func deleteEvent(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - checkPermissionsThenExecute(permissionsGrantedAction: { - 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 followingInstances = arguments[followingInstancesArgument] as? Bool - - 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 - } - - if (startDateNumber == nil && endDateNumber == nil && followingInstances == 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)) - } - } - 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 foundEkEvents = self.eventStore.events(matching: predicate) as [EKEvent]? - - if foundEkEvents == nil || foundEkEvents?.count == 0 { - self.finishWithEventNotFoundError(result: result, eventId: eventId) - return - } - - let ekEvent = foundEkEvents!.first(where: {$0.eventIdentifier == eventId}) - - do { - 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() - result(FlutterError(code: self.genericError, message: error.localizedDescription, details: nil)) - } - } - }, result: result) - } - - private func finishWithUnauthorizedError(result: @escaping FlutterResult) { - result(FlutterError(code:self.unauthorizedErrorCode, message: self.unauthorizedErrorMessage, details: nil)) - } - - private func finishWithCalendarNotFoundError(result: @escaping FlutterResult, calendarId: String) { - let errorMessage = String(format: self.calendarNotFoundErrorMessageFormat, calendarId) - result(FlutterError(code:self.notFoundErrorCode, message: errorMessage, details: nil)) - } - - private func finishWithCalendarReadOnlyError(result: @escaping FlutterResult, calendarId: String) { - let errorMessage = String(format: self.calendarReadOnlyErrorMessageFormat, calendarId) - result(FlutterError(code:self.notAllowed, message: errorMessage, details: nil)) - } - - private func finishWithEventNotFoundError(result: @escaping FlutterResult, eventId: String) { - let errorMessage = String(format: self.eventNotFoundErrorMessageFormat, eventId) - result(FlutterError(code:self.notFoundErrorCode, message: errorMessage, details: nil)) - } - - private func encodeJsonAndFinish(codable: T, result: @escaping FlutterResult) { - do { - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(codable) - let jsonString = String(data: jsonData, encoding: .utf8) - result(jsonString) - } catch { - result(FlutterError(code: genericError, message: error.localizedDescription, details: nil)) - } - } - - private func checkPermissionsThenExecute(permissionsGrantedAction: () -> Void, result: @escaping FlutterResult) { - if hasEventPermissions() { - permissionsGrantedAction() - return - } - self.finishWithUnauthorizedError(result: result) - } - - private func requestPermissions(completion: @escaping (Bool) -> Void) { - if hasEventPermissions() { - completion(true) - return - } - eventStore.requestAccess(to: .event, completion: { - (accessGranted: Bool, _: Error?) in - completion(accessGranted) - }) - } - - private func hasEventPermissions() -> Bool { - let status = EKEventStore.authorizationStatus(for: .event) - return status == EKAuthorizationStatus.authorized - } - - private func requestPermissions(_ result: @escaping FlutterResult) { - if hasEventPermissions() { - result(true) - } - eventStore.requestAccess(to: .event, completion: { - (accessGranted: Bool, _: Error?) in - result(accessGranted) - }) - } -} - -extension UIColor { - - func rgb() -> Int? { - var fRed : CGFloat = 0 - var fGreen : CGFloat = 0 - var fBlue : CGFloat = 0 - var fAlpha: CGFloat = 0 - if self.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha) { - let iRed = Int(fRed * 255.0) - let iGreen = Int(fGreen * 255.0) - let iBlue = Int(fBlue * 255.0) - let iAlpha = Int(fAlpha * 255.0) - - // (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). - let rgb = (iAlpha << 24) + (iRed << 16) + (iGreen << 8) + iBlue - return rgb - } else { - // Could not extract RGBA components: - return nil - } - } - - public convenience init?(hex: String) { - let r, g, b, a: CGFloat - - if hex.hasPrefix("0x") { - let start = hex.index(hex.startIndex, offsetBy: 2) - let hexColor = String(hex[start...]) - - if hexColor.count == 8 { - let scanner = Scanner(string: hexColor) - var hexNumber: UInt64 = 0 - - if scanner.scanHexInt64(&hexNumber) { - a = CGFloat((hexNumber & 0xff000000) >> 24) / 255 - r = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 - g = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 - b = CGFloat((hexNumber & 0x000000ff)) / 255 - - self.init(red: r, green: g, blue: b, alpha: a) - return - } - } - } - - return nil - } -} - diff --git a/device_calendar/ios/Classes/UIColor+Utilities.h b/device_calendar/ios/Classes/UIColor+Utilities.h new file mode 100644 index 00000000..af7e58c0 --- /dev/null +++ b/device_calendar/ios/Classes/UIColor+Utilities.h @@ -0,0 +1,7 @@ +#import + +NS_ASSUME_NONNULL_BEGIN +@interface UIColor (Utilities) +@property (readonly) NSNumber *rgb; +@end +NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/UIColor+Utilities.m b/device_calendar/ios/Classes/UIColor+Utilities.m new file mode 100644 index 00000000..23b36e82 --- /dev/null +++ b/device_calendar/ios/Classes/UIColor+Utilities.m @@ -0,0 +1,22 @@ +#import "UIColor+Utilities.h" + +@implementation UIColor (Utilities) +- (NSNumber*) rgb +{ + CGFloat* fRead = 0; + CGFloat* fGreen = 0; + CGFloat* fBlue = 0; + CGFloat* fAlpha = 0; + + if ([self getRed:fRead green:fGreen blue:fBlue alpha:fAlpha]) { + NSInteger iRed = (NSInteger)fRead*255.0; + NSInteger iGreen = (NSInteger)fGreen*255.0; + NSInteger iBlue = (NSInteger)fRead*255.0; + NSInteger iAlpha = (NSInteger)fRead*255.0; + NSNumber *rgb = [[NSNumber alloc] initWithLong:(iAlpha<<24)+ (iRed << 16) + (iGreen << 8) + iBlue]; + return rgb; + }else{ + return nil; + } +} +@end diff --git a/device_calendar/ios/Classes/models/Attendee.h b/device_calendar/ios/Classes/models/Attendee.h new file mode 100644 index 00000000..33c3454c --- /dev/null +++ b/device_calendar/ios/Classes/models/Attendee.h @@ -0,0 +1,11 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Attendee : JSONModel + +@property NSString *name; +@property NSString *emailAddress; +@property NSInteger role; +@property NSInteger attendanceStatus; + +@end diff --git a/device_calendar/ios/Classes/models/Attendee.m b/device_calendar/ios/Classes/models/Attendee.m new file mode 100644 index 00000000..449d8db0 --- /dev/null +++ b/device_calendar/ios/Classes/models/Attendee.m @@ -0,0 +1,6 @@ +#import +#import "Attendee.h" + +@implementation Attendee + +@end diff --git a/device_calendar/ios/Classes/models/Calendar.h b/device_calendar/ios/Classes/models/Calendar.h new file mode 100644 index 00000000..b45030c7 --- /dev/null +++ b/device_calendar/ios/Classes/models/Calendar.h @@ -0,0 +1,14 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Calendar : JSONModel + +@property NSString *id; +@property NSString *name; +@property BOOL isReadOnly; +@property BOOL isDefault; +@property NSInteger color; +@property NSString *accountName; +@property NSString *accountType; + +@end diff --git a/device_calendar/ios/Classes/models/Calendar.m b/device_calendar/ios/Classes/models/Calendar.m new file mode 100644 index 00000000..471dff57 --- /dev/null +++ b/device_calendar/ios/Classes/models/Calendar.m @@ -0,0 +1,6 @@ +#import +#import "Calendar.h" + +@implementation Calendar + +@end diff --git a/device_calendar/ios/Classes/models/Department.h b/device_calendar/ios/Classes/models/Department.h new file mode 100644 index 00000000..6725ce63 --- /dev/null +++ b/device_calendar/ios/Classes/models/Department.h @@ -0,0 +1,7 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Department : JSONModel +@property NSMutableArray *calendars; +@property NSMutableArray *events; +@end diff --git a/device_calendar/ios/Classes/models/Department.m b/device_calendar/ios/Classes/models/Department.m new file mode 100644 index 00000000..28077c1d --- /dev/null +++ b/device_calendar/ios/Classes/models/Department.m @@ -0,0 +1,6 @@ +#import +#import "Department.h" + +@implementation Department + +@end diff --git a/device_calendar/ios/Classes/models/Event.h b/device_calendar/ios/Classes/models/Event.h new file mode 100644 index 00000000..cfb3bd30 --- /dev/null +++ b/device_calendar/ios/Classes/models/Event.h @@ -0,0 +1,21 @@ +#import +#import "RecurrenceRule.h" +#import "Attendee.h" +#import "JSONModel/JSONModel.h" + +@interface Event : JSONModel + +@property NSString *eventId; +@property NSString *calendarId; +@property NSString *title; +@property NSString *description; +@property NSInteger start; +@property NSInteger end; +@property BOOL allDay; +@property NSArray *attendees; +@property NSString *location; +@property RecurrenceRule *recurrenceRule; +@property Attendee *organizer; +@property NSMutableArray *reminders; + +@end diff --git a/device_calendar/ios/Classes/models/Event.m b/device_calendar/ios/Classes/models/Event.m new file mode 100644 index 00000000..e3a6c221 --- /dev/null +++ b/device_calendar/ios/Classes/models/Event.m @@ -0,0 +1,6 @@ +#import +#import "Event.h" + +@implementation Event +@synthesize description; +@end diff --git a/device_calendar/ios/Classes/models/RecurrenceRule.h b/device_calendar/ios/Classes/models/RecurrenceRule.h new file mode 100644 index 00000000..b15cec63 --- /dev/null +++ b/device_calendar/ios/Classes/models/RecurrenceRule.h @@ -0,0 +1,16 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface RecurrenceRule : JSONModel + +@property NSInteger recurrenceFrequency; +@property NSInteger totalOccurrences; +@property NSInteger interval; +@property NSInteger endDate; +@property NSArray *daysOfWeek; +@property NSInteger daysOfMonth; +@property NSInteger monthsOfYear; +@property NSInteger weeksOfYear; +@property NSInteger weekOfMonth; + +@end diff --git a/device_calendar/ios/Classes/models/RecurrenceRule.m b/device_calendar/ios/Classes/models/RecurrenceRule.m new file mode 100644 index 00000000..fff63614 --- /dev/null +++ b/device_calendar/ios/Classes/models/RecurrenceRule.m @@ -0,0 +1,6 @@ +#import +#import "RecurrenceRule.h" + +@implementation RecurrenceRule + +@end diff --git a/device_calendar/ios/Classes/models/Reminder.h b/device_calendar/ios/Classes/models/Reminder.h new file mode 100644 index 00000000..481abbe8 --- /dev/null +++ b/device_calendar/ios/Classes/models/Reminder.h @@ -0,0 +1,8 @@ +#import +#import "JSONModel/JSONModel.h" + +@interface Reminder : JSONModel + +@property NSInteger minutes; + +@end diff --git a/device_calendar/ios/Classes/models/Reminder.m b/device_calendar/ios/Classes/models/Reminder.m new file mode 100644 index 00000000..3da8f974 --- /dev/null +++ b/device_calendar/ios/Classes/models/Reminder.m @@ -0,0 +1,6 @@ +#import +#import "Reminder.h" + +@implementation Reminder + +@end diff --git a/device_calendar/ios/device_calendar.podspec b/device_calendar/ios/device_calendar.podspec index 7b91dd81..7435a1e5 100644 --- a/device_calendar/ios/device_calendar.podspec +++ b/device_calendar/ios/device_calendar.podspec @@ -15,7 +15,8 @@ A new flutter plugin project. s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - + s.dependency 'JSONModel' + s.ios.deployment_target = '8.0' end From 0a886577e28b8c781f8055f47c35607dacc2e8ef Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Mon, 6 Apr 2020 18:36:18 +0300 Subject: [PATCH 09/15] fix isAllDay is always true even when it is sent as false in dart --- device_calendar/ios/Classes/DeviceCalendarPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 9e762fa9..9a8e8567 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -508,7 +508,7 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu NSDictionary *arguments = [call arguments]; NSString *calendarId = [arguments valueForKey:calendarIdArgument]; NSString *eventId = [arguments valueForKey:eventIdArgument]; - BOOL isAllDay = [arguments valueForKey:eventAllDayArgument]; + BOOL isAllDay = [[arguments valueForKey:eventAllDayArgument] boolValue]; NSNumber *startDateMillisecondsSinceEpoch = [arguments valueForKey: eventStartDateArgument]; NSNumber *endDateDateMillisecondsSinceEpoch = [arguments valueForKey: eventEndDateArgument]; NSDate *startDate = [NSDate dateWithTimeIntervalSince1970: [startDateMillisecondsSinceEpoch doubleValue] / 1000.0 ]; From addc5055d349308c903237a79e19e596030f8279 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Thu, 16 Apr 2020 14:54:51 +0300 Subject: [PATCH 10/15] fix comments --- .../ios/Runner.xcodeproj/project.pbxproj | 28 ++++++++-- .../ios/Classes/DeviceCalendarPlugin.m | 55 +++++++++++-------- device_calendar/ios/Classes/models/Event.h | 4 +- .../ios/Classes/models/RecurrenceRule.h | 4 +- device_calendar/ios/Classes/models/Reminder.h | 2 +- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 001fe62c..cf205457 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = YCDSDGA3LX; + DevelopmentTeam = PT8YH28249; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,7 +448,16 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + "\"Flutter\"", + "-framework", + "\"JSONModel\"", + "-framework", + "\"device_calendar\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -465,7 +474,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -477,7 +486,16 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + "\"Flutter\"", + "-framework", + "\"JSONModel\"", + "-framework", + "\"device_calendar\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 9a8e8567..212ca6ce 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -63,6 +63,7 @@ @implementation DeviceCalendarPlugin NSString *followingInstancesArgument = @"followingInstances"; NSString *calendarNameArgument = @"calendarName"; NSString *calendarColorArgument = @"calendarColor"; +NSString *eventURLArgument = @"eventURL"; NSMutableArray *validFrequencyTypes; EKEventStore *eventStore; @@ -139,7 +140,7 @@ -(void)createCalendar:(FlutterMethodCall *)call result:(FlutterResult)result { [localSources addObject:ekSource]; } } - if ([localSources count] == 0) { + if ([localSources count]) { calendar.source = [localSources firstObject]; [eventStore saveCalendar:calendar commit:YES error:&error]; } else { @@ -270,12 +271,10 @@ -(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent if ([ekEvent alarms] != nil) { for (EKAlarm *alarm in [ekEvent alarms]) { Reminder *reminder = [Reminder new]; - NSUInteger minutes = -[alarm relativeOffset]/60; - reminder.minutes = [[[NSNumber alloc] initWithUnsignedInteger:minutes] integerValue]; + reminder.minutes = -[alarm relativeOffset]/60; [reminders addObject:reminder]; } } - RecurrenceRule *recurrenceRule = [self parseEKRecurrenceRules:ekEvent]; Event *event = [Event new]; event.eventId = [ekEvent eventIdentifier]; @@ -291,6 +290,7 @@ -(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent event.allDay = [ekEvent isAllDay]; event.attendees = attendees; event.location = [ekEvent location]; + event.url = [[ekEvent URL] absoluteString]; event.recurrenceRule = recurrenceRule; event.organizer = [self convertEkParticipantToAttendee:[ekEvent organizer]]; event.reminders = reminders; @@ -331,17 +331,15 @@ -(RecurrenceRule *)parseEKRecurrenceRules:(EKEvent *)ekEvent { break; } - NSInteger totalOccurrences = 0; - NSInteger endDate; + NSNumber *totalOccurrences; + NSNumber *endDate; if([[ekRecurrenceRule recurrenceEnd] occurrenceCount] != 0){ - totalOccurrences = [[ekRecurrenceRule recurrenceEnd] occurrenceCount]; + totalOccurrences = [[NSNumber alloc] initWithUnsignedInteger:[[ekRecurrenceRule recurrenceEnd] occurrenceCount]]; } - float endDateMs = -1; - endDateMs = [[[ekRecurrenceRule recurrenceEnd] endDate] millisecondsSinceEpoch]; - if (endDateMs != -1) { - endDate = [[[NSNumber alloc] initWithFloat:endDateMs] integerValue]; + float endDateMs = [[[ekRecurrenceRule recurrenceEnd] endDate] millisecondsSinceEpoch]; + if (endDateMs) { + endDate = [[NSNumber alloc] initWithFloat:endDateMs]; } - NSMutableArray *daysOfWeek = [NSMutableArray new]; NSInteger weekOfMonth = [[[ekRecurrenceRule setPositions] firstObject] intValue]; if ([ekRecurrenceRule daysOfTheWeek] != nil && [[ekRecurrenceRule daysOfTheWeek] count] != 0) { @@ -380,19 +378,18 @@ -(RecurrenceRule *)parseEKRecurrenceRules:(EKEvent *)ekEvent { -(NSArray*) createEKRecurrenceRules: (NSDictionary*)arguments { NSDictionary *recurrenceRuleArguments = [arguments valueForKey:recurrenceRuleArgument]; - if (recurrenceRuleArguments) { + if ([recurrenceRuleArguments isEqual:[NSNull null]]) { return nil; } - - NSString *recurrenceFrequencyIndex = [recurrenceRuleArguments valueForKey:recurrenceFrequencyArgument]; + NSNumber *recurrenceFrequencyIndex = [recurrenceRuleArguments valueForKey:recurrenceFrequencyArgument]; NSInteger totalOccurrences = [[recurrenceRuleArguments valueForKey:totalOccurrencesArgument] integerValue]; NSInteger interval = -1; interval = [[recurrenceRuleArguments valueForKey:intervalArgument] integerValue]; NSInteger recurrenceInterval = 1; NSNumber *endDate = [recurrenceRuleArguments valueForKey:endDateArgument]; - EKRecurrenceFrequency namedFrequency = [[validFrequencyTypes valueForKey:recurrenceFrequencyIndex] integerValue]; + EKRecurrenceFrequency namedFrequency = [[validFrequencyTypes objectAtIndex:[recurrenceFrequencyIndex intValue]] integerValue]; EKRecurrenceEnd *recurrenceEnd; - + if (endDate != nil) { recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithEndDate: [[NSDate alloc] initWithTimeIntervalSince1970: [endDate doubleValue]]]; } else if (totalOccurrences > 0){ @@ -496,9 +493,10 @@ -(NSArray*)createReminders: (NSDictionary *)arguments { } NSMutableArray *reminders = [NSMutableArray new]; for (NSString *reminderArguments in remindersArguments) { - NSNumber *arg = [[NSNumber alloc] initWithInt: [reminderArguments valueForKey:minutesArgument]]; - double relativeOffset = 60 * (-[arg doubleValue]); - [reminders addObject:[EKAlarm alarmWithRelativeOffset:relativeOffset]]; + NSNumber *arg = [reminderArguments valueForKey:minutesArgument]; + NSNumber *reminder = [NSNumber numberWithDouble:fabs([arg doubleValue])]; + NSNumber *relativeOffset = [[NSNumber alloc] initWithDouble: (0 - 60 * [reminder doubleValue])]; + [reminders addObject:[EKAlarm alarmWithRelativeOffset:[relativeOffset doubleValue]]]; } return reminders; } @@ -517,6 +515,8 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu NSString *description = [arguments valueForKey: eventDescriptionArgument]; NSString *location = [arguments valueForKey: eventLocationArgument]; EKCalendar *ekCalendar = [eventStore calendarWithIdentifier: calendarId]; + NSString *url = [arguments valueForKey:eventURLArgument]; + if (ekCalendar == nil) { [self finishWithCalendarNotFoundError:calendarId result: result]; return; @@ -526,9 +526,9 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu return; } EKEvent *ekEvent; - if (eventId == [NSNull null]) { + if ([eventId isEqual:[NSNull null]]) { ekEvent = [EKEvent eventWithEventStore:eventStore]; - }else { + } else { ekEvent = [eventStore eventWithIdentifier:eventId]; if (ekEvent == nil) { [self finishWithEventNotFoundError: eventId result: result]; @@ -546,10 +546,14 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu } [ekEvent setCalendar:ekCalendar]; [ekEvent setLocation:location]; + if (![url isEqual:[NSNull null]]) { + ekEvent.URL = [NSURL URLWithString: url]; + } else { + ekEvent.URL = [NSURL URLWithString: @""]; + } [ekEvent setRecurrenceRules: [self createEKRecurrenceRules: arguments]]; [self setAttendees:arguments event:ekEvent]; [ekEvent setAlarms: [self createReminders: arguments]]; - NSError *error = nil; [eventStore saveEvent:ekEvent span:EKSpanFutureEvents error:&error]; if (error == nil) { @@ -658,6 +662,11 @@ -(void)encodeJsonAndFinish: (Department *)codable result:(FlutterResult)result { } } else if ([codable.events count] > 0) { for(Event *event in codable.events) { + NSMutableArray *reminders = [NSMutableArray new]; + for (Reminder *remeinder in event.reminders) { + [reminders addObject:[remeinder toDictionary]]; + } + event.reminders = reminders; [resultArr addObject:[event toJSONString]]; } } diff --git a/device_calendar/ios/Classes/models/Event.h b/device_calendar/ios/Classes/models/Event.h index cfb3bd30..662f4cba 100644 --- a/device_calendar/ios/Classes/models/Event.h +++ b/device_calendar/ios/Classes/models/Event.h @@ -1,6 +1,7 @@ #import #import "RecurrenceRule.h" #import "Attendee.h" +#import "Reminder.h" #import "JSONModel/JSONModel.h" @interface Event : JSONModel @@ -16,6 +17,7 @@ @property NSString *location; @property RecurrenceRule *recurrenceRule; @property Attendee *organizer; -@property NSMutableArray *reminders; +@property NSArray *reminders; +@property NSString *url; @end diff --git a/device_calendar/ios/Classes/models/RecurrenceRule.h b/device_calendar/ios/Classes/models/RecurrenceRule.h index b15cec63..15cb8168 100644 --- a/device_calendar/ios/Classes/models/RecurrenceRule.h +++ b/device_calendar/ios/Classes/models/RecurrenceRule.h @@ -4,9 +4,9 @@ @interface RecurrenceRule : JSONModel @property NSInteger recurrenceFrequency; -@property NSInteger totalOccurrences; +@property NSNumber *totalOccurrences; @property NSInteger interval; -@property NSInteger endDate; +@property NSNumber *endDate; @property NSArray *daysOfWeek; @property NSInteger daysOfMonth; @property NSInteger monthsOfYear; diff --git a/device_calendar/ios/Classes/models/Reminder.h b/device_calendar/ios/Classes/models/Reminder.h index 481abbe8..5f3b2421 100644 --- a/device_calendar/ios/Classes/models/Reminder.h +++ b/device_calendar/ios/Classes/models/Reminder.h @@ -3,6 +3,6 @@ @interface Reminder : JSONModel -@property NSInteger minutes; +@property (nonatomic) double minutes; @end From 2d6f6d98295c0851a0f508c8a06311d02d677523 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Thu, 16 Apr 2020 14:58:44 +0300 Subject: [PATCH 11/15] remove dev acc --- .../ios/Runner.xcodeproj/project.pbxproj | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index cf205457..001fe62c 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = PT8YH28249; + DevelopmentTeam = YCDSDGA3LX; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,16 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"Flutter\"", - "-framework", - "\"JSONModel\"", - "-framework", - "\"device_calendar\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -474,7 +465,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -486,16 +477,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"Flutter\"", - "-framework", - "\"JSONModel\"", - "-framework", - "\"device_calendar\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; From dba28b196285a6eb79fd65385a6e94eef4aa9b5e Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Tue, 26 May 2020 16:33:07 +0300 Subject: [PATCH 12/15] fix attendees --- .../ios/Runner.xcodeproj/project.pbxproj | 10 ++--- .../ios/Classes/DeviceCalendarPlugin.m | 40 ++++++++++++------- .../ios/Classes/EKParticipant+Utilities.h | 12 ------ .../ios/Classes/EKParticipant+Utilities.m | 9 ----- device_calendar/ios/Classes/models/Event.h | 2 +- 5 files changed, 31 insertions(+), 42 deletions(-) delete mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.h delete mode 100644 device_calendar/ios/Classes/EKParticipant+Utilities.m diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 001fe62c..d9000d07 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = YCDSDGA3LX; + DevelopmentTeam = PT8YH28249; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,7 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -465,7 +465,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YCDSDGA3LX; + DEVELOPMENT_TEAM = PT8YH28249; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -477,7 +477,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; diff --git a/device_calendar/ios/Classes/DeviceCalendarPlugin.m b/device_calendar/ios/Classes/DeviceCalendarPlugin.m index 212ca6ce..41b5adad 100644 --- a/device_calendar/ios/Classes/DeviceCalendarPlugin.m +++ b/device_calendar/ios/Classes/DeviceCalendarPlugin.m @@ -11,7 +11,6 @@ #import "models/Department.h" #import "Date+Utilities.h" #import "UIColor+Utilities.h" -#import "EKParticipant+Utilities.h" #import @implementation DeviceCalendarPlugin @@ -298,12 +297,12 @@ -(Event*)createEventFromEkEvent: (NSString *)calendarId event:(EKEvent *)ekEvent } -(Attendee*)convertEkParticipantToAttendee: (EKParticipant *)ekParticipant { - if (ekParticipant == nil || [ekParticipant emailAddress] == nil) { + if (ekParticipant == nil || [[ekParticipant URL] resourceSpecifier] == nil) { return nil; } Attendee *attendee = [Attendee new]; attendee.name = [ekParticipant name]; - attendee.emailAddress = [ekParticipant emailAddress]; + attendee.emailAddress = [[ekParticipant URL] resourceSpecifier]; attendee.role = [ekParticipant participantRole]; return attendee; } @@ -465,15 +464,15 @@ -(void)setAttendees: (NSDictionary *)arguments event:(EKEvent *)ekEvent{ NSString *emailAddress = [attendeeArguments valueForKey:emailAddressArgument]; NSNumber *role = [attendeeArguments valueForKey:roleArgument]; if ([ekEvent attendees] != nil) { - NSArray *participants = [ekEvent attendees]; + NSArray *participants = [ekEvent attendees]; EKParticipant *existingAttendee; for(EKParticipant* participant in participants) { - if ([participant emailAddress] == emailAddress) { + if ([[participant URL] resourceSpecifier] == emailAddress) { existingAttendee = participant; break; } } - if (existingAttendee != nil && [[ekEvent organizer] emailAddress] != [existingAttendee emailAddress]) { + if (existingAttendee != nil && [[[ekEvent organizer] URL] resourceSpecifier] != [[existingAttendee URL] resourceSpecifier]) { [attendees addObject: existingAttendee]; continue; } @@ -481,7 +480,16 @@ -(void)setAttendees: (NSDictionary *)arguments event:(EKEvent *)ekEvent{ if (attendee == nil) { continue; } + if (existingAttendee != nil && [[[ekEvent organizer] URL] resourceSpecifier] != [[existingAttendee URL] resourceSpecifier]) { + [attendees addObject: existingAttendee]; + continue; + } } + EKParticipant *attendee = [self createParticipant:emailAddress name:name role:role]; + if (attendee == nil) { + continue; + } + [attendees addObject: attendee]; } [ekEvent setValue:attendees forKey:@"attendees"]; } @@ -503,7 +511,7 @@ -(NSArray*)createReminders: (NSDictionary *)arguments { -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)result{ [self checkPermissionsThenExecute:nil permissionsGrantedAction:^{ - NSDictionary *arguments = [call arguments]; + NSDictionary *arguments = [call arguments]; NSString *calendarId = [arguments valueForKey:calendarIdArgument]; NSString *eventId = [arguments valueForKey:eventIdArgument]; BOOL isAllDay = [[arguments valueForKey:eventAllDayArgument] boolValue]; @@ -568,14 +576,11 @@ -(void)createOrUpdateEvent: (FlutterMethodCall *)call result:(FlutterResult)resu -(EKParticipant *)createParticipant:(NSString *)emailAddress name:(NSString *)name role:(NSNumber *)role { Class ekAttendeeClass = NSClassFromString(@"EKAttendee"); - if ([ekAttendeeClass isSubclassOfClass:[NSObject class]]) { - NSObject *participant = [[ekAttendeeClass alloc] init]; - [participant setValue:emailAddress forKey:@"emailAddress"]; - [participant setValue:name forKey:@"displayName"]; - [participant setValue:role forKey:@"participantRole"]; - return (EKParticipant *)participant; - } - return nil; + id participant = [ekAttendeeClass new]; + [participant setValue:emailAddress forKey:@"emailAddress"]; + [participant setValue:name forKey:@"displayName"]; + [participant setValue:role forKey:@"participantRole"]; + return participant; } -(void)deleteEvent:(FlutterMethodCall *)call result:(FlutterResult)result { @@ -666,6 +671,11 @@ -(void)encodeJsonAndFinish: (Department *)codable result:(FlutterResult)result { for (Reminder *remeinder in event.reminders) { [reminders addObject:[remeinder toDictionary]]; } + NSMutableArray *attendees = [NSMutableArray new]; + for (Attendee *attendee in event.attendees) { + [attendees addObject:[attendee toDictionary]]; + } + event.attendees = attendees; event.reminders = reminders; [resultArr addObject:[event toJSONString]]; } diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.h b/device_calendar/ios/Classes/EKParticipant+Utilities.h deleted file mode 100644 index 8adbc519..00000000 --- a/device_calendar/ios/Classes/EKParticipant+Utilities.h +++ /dev/null @@ -1,12 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface EKParticipant (Utilities) - -@property (readonly) NSString *emailAddress; - -@end - -NS_ASSUME_NONNULL_END diff --git a/device_calendar/ios/Classes/EKParticipant+Utilities.m b/device_calendar/ios/Classes/EKParticipant+Utilities.m deleted file mode 100644 index 43dec69b..00000000 --- a/device_calendar/ios/Classes/EKParticipant+Utilities.m +++ /dev/null @@ -1,9 +0,0 @@ -#import "EKParticipant+Utilities.h" - -@implementation EKParticipant (Utilities) - -- (NSString *) emailAddress -{ - return [self valueForKey:@"emailAddress"]; -} -@end diff --git a/device_calendar/ios/Classes/models/Event.h b/device_calendar/ios/Classes/models/Event.h index 662f4cba..890cc0f8 100644 --- a/device_calendar/ios/Classes/models/Event.h +++ b/device_calendar/ios/Classes/models/Event.h @@ -13,7 +13,7 @@ @property NSInteger start; @property NSInteger end; @property BOOL allDay; -@property NSArray *attendees; +@property NSMutableArray *attendees; @property NSString *location; @property RecurrenceRule *recurrenceRule; @property Attendee *organizer; From c71728866ee068e3b531d34d693d5f1c46b3b555 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Thu, 28 May 2020 15:37:31 +0300 Subject: [PATCH 13/15] change ios developer team --- .../example/ios/Runner.xcodeproj/project.pbxproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj index d9000d07..001fe62c 100644 --- a/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/device_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -181,7 +181,7 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = PT8YH28249; + DevelopmentTeam = YCDSDGA3LX; LastSwiftMigration = 1130; }; }; @@ -436,7 +436,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,7 +448,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -465,7 +465,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = PT8YH28249; + DEVELOPMENT_TEAM = YCDSDGA3LX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -477,7 +477,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExampleTest; + PRODUCT_BUNDLE_IDENTIFIER = com.builttoroam.deviceCalendarExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; From 8524df81d7a902cc458f3878ab3753db6b141ac4 Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Sun, 5 Jul 2020 23:47:07 +0300 Subject: [PATCH 14/15] refactoring code --- .../xcshareddata/WorkspaceSettings.xcsettings | 8 -------- ios/Classes/DeviceCalendarPlugin.m | 2 ++ ios/Classes/models/Availability.h | 8 ++++---- 3 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 device_calendar/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/device_calendar/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/device_calendar/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 949b6789..00000000 --- a/device_calendar/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - BuildSystemType - Original - - diff --git a/ios/Classes/DeviceCalendarPlugin.m b/ios/Classes/DeviceCalendarPlugin.m index 733f52a2..657c610c 100644 --- a/ios/Classes/DeviceCalendarPlugin.m +++ b/ios/Classes/DeviceCalendarPlugin.m @@ -308,6 +308,8 @@ -(NSString*) convertEkEventAvailability: (EKEventAvailability)ekEventAvailabilit return @"TENTATIVE"; } else if (ekEventAvailability == EKEventAvailabilityUnavailable) { return @"UNAVAILABLE"; + } else { + return nil; } } diff --git a/ios/Classes/models/Availability.h b/ios/Classes/models/Availability.h index bb99a57e..f7503744 100644 --- a/ios/Classes/models/Availability.h +++ b/ios/Classes/models/Availability.h @@ -2,10 +2,10 @@ #import "JSONModel/JSONModel.h" typedef enum{ - BUSY = @"BUSY", - FREE = @"FREE", - TENTATIVE = @"TENTATIVE", - UNAVAILABLE = @"UNAVAILABLE"; + BUSY = 1, + FREE = 2, + TENTATIVE = 3, + UNAVAILABLE = 4 } Availability; From 2ba59d71684c3fd44d481d75f2d493cf361c741e Mon Sep 17 00:00:00 2001 From: Chernikovich Andrey Date: Sun, 5 Jul 2020 23:55:18 +0300 Subject: [PATCH 15/15] refactoring code --- ios/Classes/DeviceCalendarPlugin.m | 1 - ios/Classes/models/Availability.h | 11 ----------- ios/Classes/models/Availability.m | 6 ------ ios/Classes/models/Event.h | 1 - 4 files changed, 19 deletions(-) delete mode 100644 ios/Classes/models/Availability.h delete mode 100644 ios/Classes/models/Availability.m diff --git a/ios/Classes/DeviceCalendarPlugin.m b/ios/Classes/DeviceCalendarPlugin.m index 657c610c..c8a10867 100644 --- a/ios/Classes/DeviceCalendarPlugin.m +++ b/ios/Classes/DeviceCalendarPlugin.m @@ -12,7 +12,6 @@ #import "Date+Utilities.h" #import "UIColor+Utilities.h" #import -#import "Availability.h" @implementation DeviceCalendarPlugin NSString *streamName = @"calendarChangeEvent/stream"; diff --git a/ios/Classes/models/Availability.h b/ios/Classes/models/Availability.h deleted file mode 100644 index f7503744..00000000 --- a/ios/Classes/models/Availability.h +++ /dev/null @@ -1,11 +0,0 @@ -#import -#import "JSONModel/JSONModel.h" - -typedef enum{ - BUSY = 1, - FREE = 2, - TENTATIVE = 3, - UNAVAILABLE = 4 -} Availability; - - diff --git a/ios/Classes/models/Availability.m b/ios/Classes/models/Availability.m deleted file mode 100644 index 7c99edf0..00000000 --- a/ios/Classes/models/Availability.m +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import "Availability.h" - -@implementation Availability - -@end diff --git a/ios/Classes/models/Event.h b/ios/Classes/models/Event.h index 27a2fc2d..fc467312 100644 --- a/ios/Classes/models/Event.h +++ b/ios/Classes/models/Event.h @@ -3,7 +3,6 @@ #import "Attendee.h" #import "Reminder.h" #import "JSONModel/JSONModel.h" -#import "Availability.h" @interface Event : JSONModel