From a1299938a9fae9cc5b7a2dd49ab6dcb82001d5ef Mon Sep 17 00:00:00 2001 From: Ingrid Wang Date: Fri, 20 Oct 2023 13:22:55 -0700 Subject: [PATCH] Migrate notification scheduling to UserNotifications (#41039) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41039 ## Changelog: [iOS][Breaking] - repeatInterval is deprecated in PushNotificationIOS. Use fireDate and the new fireIntervalSeconds. Reviewed By: philIip Differential Revision: D50277316 fbshipit-source-id: ddcc2d2fc9d89d2bacac296848109e98c95c0107 --- .../NativePushNotificationManagerIOS.js | 30 +++- .../RCTPushNotificationManager.mm | 141 +++++++++--------- 2 files changed, 99 insertions(+), 72 deletions(-) diff --git a/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js b/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js index 6d83970816aec8..0df32a8b3792ea 100644 --- a/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js +++ b/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js @@ -20,14 +20,34 @@ type Permissions = {| type Notification = {| +alertTitle?: ?string, - // Actual type: string | number - +fireDate?: ?number, +alertBody?: ?string, +userInfo?: ?Object, + /** + * Identifier for the notification category. See the [Apple documentation](https://developer.apple.com/documentation/usernotifications/declaring_your_actionable_notification_types) + * for more details. + */ +category?: ?string, - // Actual type: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' - +repeatInterval?: ?string, + /** + * Actual type: string | number + * + * Schedule notifications using EITHER `fireDate` or `fireIntervalSeconds`. + * If both are specified, `fireDate` takes precedence. + * If you use `presentLocalNotification`, both will be ignored + * and the notification will be shown immediately. + */ + +fireDate?: ?number, + /** + * Seconds from now to display the notification. + * + * Schedule notifications using EITHER `fireDate` or `fireIntervalSeconds`. + * If both are specified, `fireDate` takes precedence. + * If you use `presentLocalNotification`, both will be ignored + * and the notification will be shown immediately. + */ + +fireIntervalSeconds?: ?number, + /** Badge count to display on the app icon. */ +applicationIconBadgeNumber?: ?number, + /** Whether to silence the notification sound. */ +isSilent?: ?boolean, /** * Custom notification sound to play. Write-only: soundName will be null when @@ -37,6 +57,8 @@ type Notification = {| +soundName?: ?string, /** DEPRECATED. This was used for iOS's legacy UILocalNotification. */ +alertAction?: ?string, + /** DEPRECATED. Use `fireDate` or `fireIntervalSeconds` instead. */ + +repeatInterval?: ?string, |}; export interface Spec extends TurboModule { diff --git a/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm b/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm index eb6e631a50f052..d0c38c91e5ac5c 100644 --- a/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm +++ b/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm @@ -25,48 +25,59 @@ static NSString *const kErrorUnableToRequestPermissions = @"E_UNABLE_TO_REQUEST_PERMISSIONS"; #if !TARGET_OS_UIKITFORMAC -@implementation RCTConvert (NSCalendarUnit) - -RCT_ENUM_CONVERTER( - NSCalendarUnit, - (@{ - @"year" : @(NSCalendarUnitYear), - @"month" : @(NSCalendarUnitMonth), - @"week" : @(NSCalendarUnitWeekOfYear), - @"day" : @(NSCalendarUnitDay), - @"hour" : @(NSCalendarUnitHour), - @"minute" : @(NSCalendarUnitMinute) - }), - 0, - integerValue) - -@end @interface RCTPushNotificationManager () @property (nonatomic, strong) NSMutableDictionary *remoteNotificationCallbacks; @end -@implementation RCTConvert (UILocalNotification) +@implementation RCTConvert (UNNotificationContent) -+ (UILocalNotification *)UILocalNotification:(id)json ++ (UNNotificationContent *)UNNotificationContent:(id)json { NSDictionary *details = [self NSDictionary:json]; BOOL isSilent = [RCTConvert BOOL:details[@"isSilent"]]; - UILocalNotification *notification = [UILocalNotification new]; - notification.alertTitle = [RCTConvert NSString:details[@"alertTitle"]]; - notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; - notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; - notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; - notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; - notification.category = [RCTConvert NSString:details[@"category"]]; - notification.repeatInterval = [RCTConvert NSCalendarUnit:details[@"repeatInterval"]]; + UNMutableNotificationContent *content = [UNMutableNotificationContent new]; + content.title = [RCTConvert NSString:details[@"alertTitle"]]; + content.body = [RCTConvert NSString:details[@"alertBody"]]; + content.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; + content.categoryIdentifier = [RCTConvert NSString:details[@"category"]]; if (details[@"applicationIconBadgeNumber"]) { - notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"applicationIconBadgeNumber"]]; + content.badge = [RCTConvert NSNumber:details[@"applicationIconBadgeNumber"]]; } if (!isSilent) { - notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; + NSString *soundName = [RCTConvert NSString:details[@"soundName"]]; + content.sound = + soundName ? [UNNotificationSound soundNamed:details[@"soundName"]] : [UNNotificationSound defaultSound]; + } + + return content; +} + ++ (NSDictionary *)NSDictionaryForNotification: + (JS::NativePushNotificationManagerIOS::Notification &)notification +{ + // Note: alertAction is not set, as it is no longer relevant with UNNotification + NSMutableDictionary *notificationDict = [NSMutableDictionary new]; + notificationDict[@"alertTitle"] = notification.alertTitle(); + notificationDict[@"alertBody"] = notification.alertBody(); + notificationDict[@"userInfo"] = notification.userInfo(); + notificationDict[@"category"] = notification.category(); + if (notification.fireIntervalSeconds()) { + notificationDict[@"fireIntervalSeconds"] = @(*notification.fireIntervalSeconds()); } - return notification; + if (notification.fireDate()) { + notificationDict[@"fireDate"] = @(*notification.fireDate()); + } + if (notification.applicationIconBadgeNumber()) { + notificationDict[@"applicationIconBadgeNumber"] = @(*notification.applicationIconBadgeNumber()); + } + if (notification.isSilent()) { + notificationDict[@"isSilent"] = @(*notification.isSilent()); + if ([notificationDict[@"isSilent"] isEqualToNumber:@(NO)]) { + notificationDict[@"soundName"] = notification.soundName(); + } + } + return notificationDict; } @end @@ -420,50 +431,44 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification RCT_EXPORT_METHOD(presentLocalNotification : (JS::NativePushNotificationManagerIOS::Notification &)notification) { - NSMutableDictionary *notificationDict = [NSMutableDictionary new]; - notificationDict[@"alertTitle"] = notification.alertTitle(); - notificationDict[@"alertBody"] = notification.alertBody(); - notificationDict[@"alertAction"] = notification.alertAction(); - notificationDict[@"userInfo"] = notification.userInfo(); - notificationDict[@"category"] = notification.category(); - notificationDict[@"repeatInterval"] = notification.repeatInterval(); - if (notification.fireDate()) { - notificationDict[@"fireDate"] = @(*notification.fireDate()); - } - if (notification.applicationIconBadgeNumber()) { - notificationDict[@"applicationIconBadgeNumber"] = @(*notification.applicationIconBadgeNumber()); - } - if (notification.isSilent()) { - notificationDict[@"isSilent"] = @(*notification.isSilent()); - if ([notificationDict[@"isSilent"] isEqualToNumber:@(NO)]) { - notificationDict[@"soundName"] = notification.soundName(); - } - } - [RCTSharedApplication() presentLocalNotificationNow:[RCTConvert UILocalNotification:notificationDict]]; + NSDictionary *notificationDict = [RCTConvert NSDictionaryForNotification:notification]; + UNNotificationContent *content = [RCTConvert UNNotificationContent:notificationDict]; + UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.1 + repeats:NO]; + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:[[NSUUID UUID] UUIDString] + content:content + trigger:trigger]; + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:request withCompletionHandler:nil]; } RCT_EXPORT_METHOD(scheduleLocalNotification : (JS::NativePushNotificationManagerIOS::Notification &)notification) { - NSMutableDictionary *notificationDict = [NSMutableDictionary new]; - notificationDict[@"alertTitle"] = notification.alertTitle(); - notificationDict[@"alertBody"] = notification.alertBody(); - notificationDict[@"alertAction"] = notification.alertAction(); - notificationDict[@"userInfo"] = notification.userInfo(); - notificationDict[@"category"] = notification.category(); - notificationDict[@"repeatInterval"] = notification.repeatInterval(); - if (notification.fireDate()) { - notificationDict[@"fireDate"] = @(*notification.fireDate()); - } - if (notification.applicationIconBadgeNumber()) { - notificationDict[@"applicationIconBadgeNumber"] = @(*notification.applicationIconBadgeNumber()); - } - if (notification.isSilent()) { - notificationDict[@"isSilent"] = @(*notification.isSilent()); - if ([notificationDict[@"isSilent"] isEqualToNumber:@(NO)]) { - notificationDict[@"soundName"] = notification.soundName(); - } + NSDictionary *notificationDict = [RCTConvert NSDictionaryForNotification:notification]; + UNNotificationContent *content = [RCTConvert UNNotificationContent:notificationDict]; + + UNNotificationTrigger *trigger = nil; + if (notificationDict[@"fireDate"]) { + NSDate *fireDate = [RCTConvert NSDate:notificationDict[@"fireDate"]] ?: [NSDate date]; + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSDateComponents *components = + [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | + NSCalendarUnitMinute | NSCalendarUnitSecond) + fromDate:fireDate]; + trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:NO]; + } else if (notificationDict[@"fireIntervalSeconds"]) { + trigger = [UNTimeIntervalNotificationTrigger + triggerWithTimeInterval:[notificationDict[@"fireIntervalSeconds"] doubleValue] + repeats:NO]; } - [RCTSharedApplication() scheduleLocalNotification:[RCTConvert UILocalNotification:notificationDict]]; + + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:[[NSUUID UUID] UUIDString] + content:content + trigger:trigger]; + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:request withCompletionHandler:nil]; } RCT_EXPORT_METHOD(cancelAllLocalNotifications)