From f780e54171d988309314935379a1ea6c46bd4358 Mon Sep 17 00:00:00 2001 From: Krystof Celba Date: Sat, 3 Feb 2018 20:26:04 +0100 Subject: [PATCH] Add support for notification actions on iOS --- index.d.ts | 41 +++++++++++++ index.js | 26 ++++++++ ios/RNFIRMessaging.m | 142 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 206 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index fce43d2..ac9fa5a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -25,6 +25,26 @@ declare module "react-native-fcm" { const Local = "local_notification"; } + export enum NotificationCategoryOption { + CustomDismissAction = 'UNNotificationCategoryOptionCustomDismissAction', + AllowInCarPlay = 'UNNotificationCategoryOptionAllowInCarPlay', + PreviewsShowTitle = 'UNNotificationCategoryOptionHiddenPreviewsShowTitle', + PreviewsShowSubtitle = 'UNNotificationCategoryOptionHiddenPreviewsShowSubtitle', + None = 'UNNotificationCategoryOptionNone' + } + + export enum NotificationActionOption { + AuthenticationRequired = 'UNNotificationActionOptionAuthenticationRequired', + Destructive = 'UNNotificationActionOptionDestructive', + Foreground = 'UNNotificationActionOptionForeground', + None = 'UNNotificationActionOptionNone' + } + + export enum NotificationActionType { + Default = 'UNNotificationActionTypeDefault', + TextInput = 'UNNotificationActionTypeTextInput', + } + export interface Notification { collapse_key: string; opened_from_tray: boolean; @@ -44,6 +64,8 @@ declare module "react-native-fcm" { }; local_notification?: boolean; _notificationType: string; + _actionIdentifier?: string; + _userText?: string; finish(type?: string): void; [key: string]: any; } @@ -83,6 +105,23 @@ declare module "react-native-fcm" { remove(): void; } + export interface NotificationAction { + type: NotificationActionType; + id: string; + title?: string; + textInputButtonTitle?: string; + textInputPlaceholder?: string; + options: NotificationActionOption | NotificationActionOption[]; + } + + export interface NotificationCategory { + id: string; + actions: NotificationAction[]; + intentIdentifiers: string[]; + hiddenPreviewsBodyPlaceholder?: string; + options?: NotificationCategoryOption | NotificationCategoryOption[]; + } + export class FCM { static requestPermissions(): Promise; static getFCMToken(): Promise; @@ -109,6 +148,8 @@ declare module "react-native-fcm" { static enableDirectChannel(): void static isDirectChannelEstablished(): Promise static getAPNSToken(): Promise + + static setNotificationCategories(categories: NotificationCategory[]): void; } export default FCM; diff --git a/index.js b/index.js index dd0b3cb..74a3bb8 100644 --- a/index.js +++ b/index.js @@ -26,6 +26,26 @@ export const NotificationType = { Local: 'local_notification' }; +export const NotificationCategoryOption = { + CustomDismissAction: 'UNNotificationCategoryOptionCustomDismissAction', + AllowInCarPlay: 'UNNotificationCategoryOptionAllowInCarPlay', + PreviewsShowTitle: 'UNNotificationCategoryOptionHiddenPreviewsShowTitle', + PreviewsShowSubtitle: 'UNNotificationCategoryOptionHiddenPreviewsShowSubtitle', + None: 'UNNotificationCategoryOptionNone' +}; + +export const NotificationActionOption = { + AuthenticationRequired: 'UNNotificationActionOptionAuthenticationRequired', + Destructive: 'UNNotificationActionOptionDestructive', + Foreground: 'UNNotificationActionOptionForeground', + None: 'UNNotificationActionOptionNone', +}; + +export const NotificationActionType = { + Default: 'UNNotificationActionTypeDefault', + TextInput: 'UNNotificationActionTypeTextInput', +}; + const RNFIRMessaging = NativeModules.RNFIRMessaging; const FCM = {}; @@ -174,4 +194,10 @@ FCM.send = (senderId, payload) => { RNFIRMessaging.send(senderId, payload); }; +FCM.setNotificationCategories = (categories) => { + RNFIRMessaging.setNotificationCategories(categories); +} + export default FCM; + +export {}; diff --git a/ios/RNFIRMessaging.m b/ios/RNFIRMessaging.m index 8bc4ae3..e572405 100644 --- a/ios/RNFIRMessaging.m +++ b/ios/RNFIRMessaging.m @@ -131,6 +131,113 @@ RCT_ENUM_CONVERTER(UNNotificationPresentationOptions, (@{ @end +@implementation RCTConvert (UNNotificationAction) + +typedef NS_ENUM(NSUInteger, UNNotificationActionType) { + UNNotificationActionTypeDefault, + UNNotificationActionTypeTextInput +}; + ++ (UNNotificationAction *) UNNotificationAction:(id)json { + NSDictionary *details = [self NSDictionary:json]; + + NSString *identifier = [RCTConvert NSString: details[@"id"]]; + NSString *title = [RCTConvert NSString: details[@"title"]]; + UNNotificationActionOptions options = [RCTConvert UNNotificationActionOptions: details[@"options"]]; + UNNotificationActionType type = [RCTConvert UNNotificationActionType:details[@"type"]]; + + if (type == UNNotificationActionTypeTextInput) { + NSString *textInputButtonTitle = [RCTConvert NSString: details[@"textInputButtonTitle"]]; + NSString *textInputPlaceholder = [RCTConvert NSString: details[@"textInputPlaceholder"]]; + + return [UNTextInputNotificationAction actionWithIdentifier:identifier title:title options:options textInputButtonTitle:textInputButtonTitle textInputPlaceholder:textInputPlaceholder]; + } + + return [UNNotificationAction actionWithIdentifier:identifier + title:title + options:options]; + +} + +RCT_ENUM_CONVERTER(UNNotificationActionType, (@{ + @"UNNotificationActionTypeDefault": @(UNNotificationActionTypeDefault), + @"UNNotificationActionTypeTextInput": @(UNNotificationActionTypeTextInput), + }), UNNotificationActionTypeDefault, integerValue) + + +RCT_MULTI_ENUM_CONVERTER(UNNotificationActionOptions, (@{ + @"UNNotificationActionOptionAuthenticationRequired": @(UNNotificationActionOptionAuthenticationRequired), + @"UNNotificationActionOptionDestructive": @(UNNotificationActionOptionDestructive), + @"UNNotificationActionOptionForeground": @(UNNotificationActionOptionForeground), + @"UNNotificationActionOptionNone": @(UNNotificationActionOptionNone), + }), UNNotificationActionOptionNone, integerValue) + + +@end + +@implementation RCTConvert (UNNotificationCategory) + + ++ (UNNotificationCategory *) UNNotificationCategory:(id)json { + NSDictionary *details = [self NSDictionary:json]; + + NSString *identifier = [RCTConvert NSString: details[@"id"]]; + + NSMutableArray *actions = [[NSMutableArray alloc] init]; + for (NSDictionary *actionDict in details[@"actions"]) { + [actions addObject:[RCTConvert UNNotificationAction:actionDict]]; + } + + NSArray *intentIdentifiers = [RCTConvert NSStringArray:details[@"intentIdentifiers"]]; + NSString *hiddenPreviewsBodyPlaceholder = [RCTConvert NSString:details[@"hiddenPreviewsBodyPlaceholder"]]; + UNNotificationCategoryOptions options = [RCTConvert UNNotificationCategoryOptions: details[@"options"]]; + + if (hiddenPreviewsBodyPlaceholder) { + if (@available(iOS 11.0, *)) { + return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers hiddenPreviewsBodyPlaceholder:hiddenPreviewsBodyPlaceholder options:options]; + } + } + + return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers options:options]; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + +RCT_MULTI_ENUM_CONVERTER(UNNotificationCategoryOptions, (@{ + @"UNNotificationCategoryOptionNone": @(UNNotificationCategoryOptionNone), + @"UNNotificationCategoryOptionCustomDismissAction": @(UNNotificationCategoryOptionCustomDismissAction), + @"UNNotificationCategoryOptionAllowInCarPlay": @(UNNotificationCategoryOptionAllowInCarPlay), + @"UNNotificationCategoryOptionHiddenPreviewsShowTitle": @(UNNotificationCategoryOptionHiddenPreviewsShowTitle), + @"UNNotificationCategoryOptionHiddenPreviewsShowSubtitle": @(UNNotificationCategoryOptionHiddenPreviewsShowSubtitle), + }), UNNotificationCategoryOptionNone, integerValue) + +#pragma clang diagnostic pop + + +@end + +@interface RNFIRMessagingHelper : NSObject + +@property (nonatomic, retain) NSDictionary *lastNotificationResponse; + ++ (nonnull instancetype) sharedInstance; + +@end + +@implementation RNFIRMessagingHelper + ++ (nonnull instancetype)sharedInstance { + static RNFIRMessagingHelper *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [self new]; + }); + return sharedInstance; +} + +@end + @interface RNFIRMessaging () @property (nonatomic, strong) NSMutableDictionary *notificationCallbacks; @end @@ -144,7 +251,7 @@ RCT_EXPORT_MODULE(); } + (BOOL)requiresMainQueueSetup { - return YES; + return YES; } + (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull RCTRemoteNotificationCallback)completionHandler { @@ -169,7 +276,15 @@ RCT_EXPORT_MODULE(); if (response.actionIdentifier) { [data setValue:response.actionIdentifier forKey:@"_actionIdentifier"]; } - [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:@{@"data": data, @"completionHandler": completionHandler}]; + + if ([response isKindOfClass:UNTextInputNotificationResponse.class]) { + [data setValue:[(UNTextInputNotificationResponse *)response userText] forKey:@"_userText"]; + } + + NSDictionary *userInfo = @{@"data": data, @"completionHandler": completionHandler}; + [RNFIRMessagingHelper sharedInstance].lastNotificationResponse = userInfo; + + [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:userInfo]; } + (void)willPresentNotification:(UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler @@ -210,6 +325,12 @@ RCT_EXPORT_MODULE(); return self; } +-(void)startObserving { + if([RNFIRMessagingHelper sharedInstance].lastNotificationResponse) { + [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:[RNFIRMessagingHelper sharedInstance].lastNotificationResponse]; + } +} + RCT_EXPORT_METHOD(enableDirectChannel) { [[FIRMessaging messaging] setShouldEstablishDirectChannel:@YES]; @@ -416,6 +537,20 @@ RCT_EXPORT_METHOD(getScheduledLocalNotifications:(RCTPromiseResolveBlock)resolve } } +RCT_EXPORT_METHOD(setNotificationCategories:(NSArray *)categories) +{ + if([UNUserNotificationCenter currentNotificationCenter] != nil) { + NSMutableSet *categoriesSet = [[NSMutableSet alloc] init]; + + for(NSDictionary *categoryDict in categories) { + UNNotificationCategory *category = [RCTConvert UNNotificationCategory:categoryDict]; + [categoriesSet addObject:category]; + } + + [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categoriesSet]; + } +} + RCT_EXPORT_METHOD(setBadgeNumber: (NSInteger) number) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -492,7 +627,8 @@ RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId){ } [self sendEventWithName:FCMNotificationReceived body:data]; - + + [RNFIRMessagingHelper sharedInstance].lastNotificationResponse = nil; } - (void)sendDataMessageFailure:(NSNotification *)notification -- 2.26.2