diff --git a/packages/expo-calendar/CHANGELOG.md b/packages/expo-calendar/CHANGELOG.md index 6a294c6841ff4..a784c8ade6d68 100644 --- a/packages/expo-calendar/CHANGELOG.md +++ b/packages/expo-calendar/CHANGELOG.md @@ -7,3 +7,5 @@ ### 🎉 New features ### 🐛 Bug fixes + +- Fixed `Calendar.getCalendarsAsync` requiring not needed permissions on iOS. ([#7928](https://github.com/expo/expo/pull/7928) by [@lukmccall](https://github.com/lukmccall)) diff --git a/packages/expo-calendar/ios/EXCalendar/EXCalendar.m b/packages/expo-calendar/ios/EXCalendar/EXCalendar.m index 65b3c89d1f20d..cbc778b6bc27a 100644 --- a/packages/expo-calendar/ios/EXCalendar/EXCalendar.m +++ b/packages/expo-calendar/ios/EXCalendar/EXCalendar.m @@ -18,11 +18,21 @@ @interface EXCalendar () @property (nonatomic, strong) EKEventStore *eventStore; @property (nonatomic) BOOL isAccessToEventStoreGranted; @property (nonatomic, weak) id permissionsManager; +@property (nonatomic) EKEntityMask permittedEntities; @end @implementation EXCalendar +- (instancetype)init +{ + if (self = [super init]) { + _permittedEntities = 0; + } + + return self; +} + UM_EXPORT_MODULE(ExpoCalendar); - (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry @@ -42,6 +52,7 @@ - (EKEventStore *)eventStore { if (!_eventStore) { _eventStore = [[EKEventStore alloc] init]; + [self _initializePermittedEntities]; } return _eventStore; } @@ -54,18 +65,26 @@ - (EKEventStore *)eventStore resolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject) { - if (![self _checkCalendarPermissions:reject]) { - return; - } - NSArray *calendars; if (!typeString) { + if(![self _checkCalendarPermissions:reject] || ![self _checkRemindersPermissions:reject]) { + return; + } + NSArray *eventCalendars = [self.eventStore calendarsForEntityType:EKEntityTypeEvent]; NSArray *reminderCalendars = [self.eventStore calendarsForEntityType:EKEntityTypeReminder]; calendars = [eventCalendars arrayByAddingObjectsFromArray:reminderCalendars]; } else if ([typeString isEqualToString:@"event"]) { + if (![self _checkCalendarPermissions:reject]) { + return; + } + calendars = [self.eventStore calendarsForEntityType:EKEntityTypeEvent]; } else if ([typeString isEqualToString:@"reminder"]) { + if (![self _checkRemindersPermissions:reject]) { + return; + } + calendars = [self.eventStore calendarsForEntityType:EKEntityTypeReminder]; } else { reject(@"E_INVALID_CALENDAR_ENTITY_TYPE", @@ -86,6 +105,10 @@ - (EKEventStore *)eventStore getDefaultCalendarAsync:(UMPromiseResolveBlock)resolve rejector:(UMPromiseRejectBlock)reject) { + if (![self _checkCalendarPermissions:reject]) { + return; + } + EKCalendar *defaultCalendar = [self.eventStore defaultCalendarForNewEvents]; if (defaultCalendar == nil) { return reject(@"E_CALENDARS_NOT_FOUND", @"Could not find default calendars", nil); @@ -956,24 +979,74 @@ - (EKEventAvailability)_availabilityConstant:(NSString *)string return EKEventAvailabilityNotSupported; } -- (BOOL)_checkPermissions:(Class)permissionsRequester reject:(UMPromiseRejectBlock)reject +- (BOOL)_checkPermissions:(EKEntityType)entity reject:(UMPromiseRejectBlock)reject { - if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXCalendarPermissionRequester class]]) { - NSString *errorMessage = [NSString stringWithFormat:@"%@ permission is required to do this operation.", [[permissionsRequester permissionType] uppercaseString]]; - reject(@"E_MISSING_PERMISSION", errorMessage, nil); + if (_eventStore && _permittedEntities & entity) { + return YES; + } + + Class requesterClass; + switch (entity) { + case EKEntityTypeEvent: + requesterClass = [EXCalendarPermissionRequester class]; + break; + case EKEntityTypeReminder: + requesterClass = [EXRemindersPermissionRequester class]; + break; + default: { + NSString *errorMessage = [NSString stringWithFormat:@"Unknown entity: %lu.", (unsigned long)entity]; + reject(@"ERR_UNKNOWN_ENTITY", errorMessage, nil); + return NO; + } + } + + if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:requesterClass]) { + NSString *errorMessage = [NSString stringWithFormat:@"%@ permission is required to do this operation.", [[requesterClass permissionType] uppercaseString]]; + reject(@"ERR_MISSING_PERMISSION", errorMessage, nil); return NO; } + + [self _resetEventStoreIfPermissionsWasChanged:1 << entity]; return YES; } - (BOOL)_checkCalendarPermissions:(UMPromiseRejectBlock)reject { - return [self _checkPermissions:[EXCalendarPermissionRequester class] reject:reject]; + return [self _checkPermissions:EKEntityTypeEvent reject:reject]; } - (BOOL)_checkRemindersPermissions:(UMPromiseRejectBlock)reject { - return [self _checkPermissions:[EXRemindersPermissionRequester class] reject:reject]; + return [self _checkPermissions:EKEntityTypeReminder reject:reject]; } +- (void)_initializePermittedEntities +{ + EKEntityMask permittedEntities = 0; + if ([_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXCalendarPermissionRequester class]]) { + permittedEntities |= EKEntityMaskEvent; + } + + if ([_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXRemindersPermissionRequester class]]) { + permittedEntities |= EKEntityMaskReminder; + } + + _permittedEntities = permittedEntities; +} + +// During the construction, EKEventStore checks permissions and loads data to which has access. +// If the user granted only partial permission and called a Calendar function, we get as a result an EKEventStore object only contains one type of entity. +// However, in the future user can add permission, so we need to reset EKEventStore to get all available data. +- (void)_resetEventStoreIfPermissionsWasChanged:(EKEntityMask)newEntity +{ + if (!_eventStore) { + return; + } + + if ((_permittedEntities & newEntity) == newEntity) { + return; + } + [self.eventStore reset]; // We can safely reset the store, cause all changes are committed immediately. + _permittedEntities |= newEntity; +} @end