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