1// Copyright 2018-present 650 Industries. All rights reserved. 2 3#import <EXNotifications/EXNotificationCategoriesModule.h> 4#import <EXNotifications/EXNotificationCenterDelegate.h> 5 6@implementation EXNotificationCategoriesModule 7 8EX_EXPORT_MODULE(ExpoNotificationCategoriesModule); 9 10# pragma mark - Exported methods 11 12EX_EXPORT_METHOD_AS(getNotificationCategoriesAsync, 13 getNotificationCategoriesAsyncWithResolver:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject) 14{ 15 [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> *categories) { 16 NSMutableArray* existingCategories = [NSMutableArray new]; 17 for (UNNotificationCategory *category in categories) { 18 [existingCategories addObject:[self serializeCategory:category]]; 19 } 20 resolve(existingCategories); 21 }]; 22} 23 24EX_EXPORT_METHOD_AS(setNotificationCategoryAsync, 25 setNotificationCategoryWithCategoryId:(NSString *)categoryId 26 actions:(NSArray *)actions 27 options:(NSDictionary *)options 28 resolve:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject) 29{ 30 UNNotificationCategory *newCategory = [EXNotificationCategoriesModule createCategoryWithId:categoryId 31 actions:actions 32 options:options]; 33 34 [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> *categories) { 35 NSMutableSet<UNNotificationCategory *> *newCategories = [categories mutableCopy] ?: [[NSMutableSet alloc] init]; 36 for (UNNotificationCategory *category in newCategories) { 37 if ([category.identifier isEqualToString:newCategory.identifier]) { 38 [newCategories removeObject:category]; 39 break; 40 } 41 } 42 [newCategories addObject:newCategory]; 43 [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:newCategories]; 44 resolve([self serializeCategory:newCategory]); 45 }]; 46} 47 48EX_EXPORT_METHOD_AS(deleteNotificationCategoryAsync, 49 deleteNotificationCategoryWithCategoryId:(NSString *)categoryId 50 resolve:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject) 51{ 52 [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> *categories) { 53 BOOL didDelete = NO; 54 NSMutableSet<UNNotificationCategory *> *newCategories = [categories mutableCopy]; 55 for (UNNotificationCategory *category in newCategories) { 56 if ([category.identifier isEqualToString:categoryId]) { 57 [newCategories removeObject:category]; 58 didDelete = YES; 59 break; 60 } 61 } 62 [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:newCategories]; 63 resolve(@(didDelete)); 64 }]; 65} 66 67# pragma mark- Internal 68 69+ (UNNotificationCategory *)createCategoryWithId:(NSString*)categoryId 70 actions:(NSArray *)actions 71 options:(NSDictionary *)options 72{ 73 NSArray<NSString *> *intentIdentifiers = options[@"intentIdentifiers"]; 74 NSString *previewPlaceholder = options[@"previewPlaceholder"]; 75 NSString *categorySummaryFormat = options[@"categorySummaryFormat"]; 76 77 NSMutableArray<UNNotificationAction *> *actionsArray = [[NSMutableArray alloc] init]; 78 for (NSDictionary<NSString *, id> *actionParams in actions) { 79 [actionsArray addObject:[self parseNotificationActionFromParams:actionParams]]; 80 } 81 UNNotificationCategoryOptions categoryOptions = [self parseNotificationCategoryOptionsFromParams: options]; 82 UNNotificationCategory *newCategory; 83 if (@available(iOS 12, *)) { 84 newCategory = [UNNotificationCategory categoryWithIdentifier:categoryId 85 actions:actionsArray 86 intentIdentifiers:intentIdentifiers 87 hiddenPreviewsBodyPlaceholder:previewPlaceholder 88 categorySummaryFormat:categorySummaryFormat 89 options:categoryOptions]; 90 } else if (@available(iOS 11, *)) { 91 newCategory = [UNNotificationCategory categoryWithIdentifier:categoryId 92 actions:actionsArray 93 intentIdentifiers:intentIdentifiers 94 hiddenPreviewsBodyPlaceholder:previewPlaceholder 95 options:categoryOptions]; 96 } else { 97 newCategory = [UNNotificationCategory categoryWithIdentifier:categoryId 98 actions:actionsArray 99 intentIdentifiers:intentIdentifiers 100 options:categoryOptions]; 101 } 102 return newCategory; 103} 104 105+ (UNNotificationAction *)parseNotificationActionFromParams:(NSDictionary *)params 106{ 107 NSString *identifier = params[@"identifier"]; 108 NSString *buttonTitle = params[@"buttonTitle"]; 109 UNNotificationActionOptions options = UNNotificationActionOptionNone; 110 111 if (params[@"options"][@"opensAppToForeground"] == nil || [params[@"options"][@"opensAppToForeground"] boolValue]) { 112 options += UNNotificationActionOptionForeground; 113 } 114 if ([params[@"options"][@"isDestructive"] boolValue]) { 115 options += UNNotificationActionOptionDestructive; 116 } 117 if ([params[@"options"][@"isAuthenticationRequired"] boolValue]) { 118 options += UNNotificationActionOptionAuthenticationRequired; 119 } 120 121 if ([params[@"textInput"] isKindOfClass:[NSDictionary class]]) { 122 return [UNTextInputNotificationAction actionWithIdentifier:identifier 123 title:buttonTitle 124 options:options 125 textInputButtonTitle:params[@"textInput"][@"submitButtonTitle"] 126 textInputPlaceholder:params[@"textInput"][@"placeholder"]]; 127 } 128 129 return [UNNotificationAction actionWithIdentifier:identifier title:buttonTitle options:options]; 130} 131 132+ (UNNotificationCategoryOptions )parseNotificationCategoryOptionsFromParams:(NSDictionary *)params 133{ 134 UNNotificationCategoryOptions options = UNNotificationCategoryOptionNone; 135 if ([params[@"customDismissAction"] boolValue]) { 136 options += UNNotificationCategoryOptionCustomDismissAction; 137 } 138 if ([params[@"allowInCarPlay"] boolValue]) { 139 options += UNNotificationCategoryOptionAllowInCarPlay; 140 } 141 if (@available(iOS 11, *)) { 142 if ([params[@"showTitle"] boolValue]) { 143 options += UNNotificationCategoryOptionHiddenPreviewsShowTitle; 144 } 145 if ([params[@"showSubtitle"] boolValue]) { 146 options += UNNotificationCategoryOptionHiddenPreviewsShowSubtitle; 147 } 148 } 149 if (@available(iOS 13, *)) { 150 if ([params[@"allowAnnouncement"] boolValue]) { 151 options += UNNotificationCategoryOptionAllowAnnouncement; 152 } 153 } 154 155 return options; 156} 157 158- (NSMutableDictionary *)serializeCategory:(UNNotificationCategory *)category 159{ 160 NSMutableDictionary* serializedCategory = [NSMutableDictionary dictionary]; 161 serializedCategory[@"identifier"] = category.identifier; 162 serializedCategory[@"actions"] = [self serializeActions: category.actions]; 163 serializedCategory[@"options"] = [self serializeCategoryOptions: category]; 164 return serializedCategory; 165} 166 167- (NSMutableDictionary *)serializeCategoryOptions:(UNNotificationCategory *)category 168{ 169 NSMutableDictionary* serializedOptions = [NSMutableDictionary dictionary]; 170 serializedOptions[@"intentIdentifiers"] = category.intentIdentifiers; 171 serializedOptions[@"customDismissAction"] = [NSNumber numberWithBool:((category.options & UNNotificationCategoryOptionCustomDismissAction) != 0)]; 172 serializedOptions[@"allowInCarPlay"] = [NSNumber numberWithBool:((category.options & UNNotificationCategoryOptionAllowInCarPlay) != 0)]; 173 if (@available(iOS 11, *)) { 174 serializedOptions[@"previewPlaceholder"] = category.hiddenPreviewsBodyPlaceholder; 175 serializedOptions[@"showTitle"] = [NSNumber numberWithBool:((category.options & UNNotificationCategoryOptionHiddenPreviewsShowTitle) != 0)]; 176 serializedOptions[@"showSubtitle"] = [NSNumber numberWithBool:((category.options & UNNotificationCategoryOptionHiddenPreviewsShowSubtitle) != 0)]; 177 } 178 if (@available(iOS 12, *)) { 179 serializedOptions[@"categorySummaryFormat"] = category.categorySummaryFormat; 180 } 181 if (@available(iOS 13, *)) { 182 serializedOptions[@"allowAnnouncement"] = [NSNumber numberWithBool:((category.options & UNNotificationActionOptionAuthenticationRequired) != 0)]; 183 } 184 return serializedOptions; 185} 186 187- (NSMutableArray *)serializeActions:(NSArray<UNNotificationAction *>*)actions 188{ 189 NSMutableArray* serializedActions = [NSMutableArray new]; 190 for (NSUInteger i = 0; i < [actions count]; i++) 191 { 192 NSMutableDictionary *actionDictionary = [NSMutableDictionary dictionary]; 193 actionDictionary[@"buttonTitle"] = actions[i].title; 194 actionDictionary[@"identifier"] = actions[i].identifier; 195 actionDictionary[@"options"] = [self serializeActionOptions:actions[i].options]; 196 if ([actions[i] isKindOfClass:[UNTextInputNotificationAction class]]) { 197 UNTextInputNotificationAction *textInputAction = (UNTextInputNotificationAction *)actions[i]; 198 NSMutableDictionary *textInputOptions = [NSMutableDictionary dictionary]; 199 textInputOptions[@"placeholder"] = textInputAction.textInputPlaceholder; 200 textInputOptions[@"submitButtonTitle"] = textInputAction.textInputButtonTitle; 201 actionDictionary[@"textInput"] = textInputOptions; 202 } 203 [serializedActions addObject:actionDictionary]; 204 } 205 return serializedActions; 206} 207 208- (NSMutableDictionary *)serializeActionOptions:(NSUInteger)options 209{ 210 NSMutableDictionary* serializedOptions = [NSMutableDictionary dictionary]; 211 serializedOptions[@"opensAppToForeground"] = [NSNumber numberWithBool:((options & UNNotificationActionOptionForeground) != 0)]; 212 serializedOptions[@"isDestructive"] = [NSNumber numberWithBool:((options & UNNotificationActionOptionDestructive) != 0)]; 213 serializedOptions[@"isAuthenticationRequired"] = [NSNumber numberWithBool:((options & UNNotificationActionOptionAuthenticationRequired) != 0)]; 214 return serializedOptions; 215} 216 217@end 218