From 55f94d65853bb4bf93d8961c72d0e8cd9d7b6a30 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Sat, 13 Jan 2024 00:10:42 -0800 Subject: [PATCH 1/5] [CI] Install the latest version of packages in the brewfile (#2024) --- .ado/Brewfile.lock.json | 62 ---------------------------- .ado/templates/apple-tools-setup.yml | 6 ++- 2 files changed, 5 insertions(+), 63 deletions(-) delete mode 100644 .ado/Brewfile.lock.json diff --git a/.ado/Brewfile.lock.json b/.ado/Brewfile.lock.json deleted file mode 100644 index 208260f35b2c02..00000000000000 --- a/.ado/Brewfile.lock.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "entries": { - "brew": { - "xcbeautify": { - "version": "1.0.0", - "bottle": { - "rebuild": 0, - "root_url": "https://ghcr.io/v2/homebrew/core", - "files": { - "arm64_sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:c8f49160c9c1df0f01ebc1a4530289fb0b4d69093bf6a1a501f5647844c1437b", - "sha256": "c8f49160c9c1df0f01ebc1a4530289fb0b4d69093bf6a1a501f5647844c1437b" - }, - "arm64_ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:bacf88c0dd3a4132e5cf01dc9af94d69678e8adc9faadc0b55eca53dfeb4edd6", - "sha256": "bacf88c0dd3a4132e5cf01dc9af94d69678e8adc9faadc0b55eca53dfeb4edd6" - }, - "arm64_monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:6a46df2ced30543c8ec0fb7ea53a1ffd45f69db7ed9b59a5ca5fbc6885606f9c", - "sha256": "6a46df2ced30543c8ec0fb7ea53a1ffd45f69db7ed9b59a5ca5fbc6885606f9c" - }, - "sonoma": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:400ad7359e6c260156e1982dc2d0227034c3294b95db899555c2c3375651417c", - "sha256": "400ad7359e6c260156e1982dc2d0227034c3294b95db899555c2c3375651417c" - }, - "ventura": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:2d7d8323275c3d5f808434c84808a59c4d8eabb128053f05630f298b592d5bfe", - "sha256": "2d7d8323275c3d5f808434c84808a59c4d8eabb128053f05630f298b592d5bfe" - }, - "monterey": { - "cellar": ":any_skip_relocation", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:4688cfc59364db9efa881f2cb4c99f2304149c0f69e2bc4164cbae5d525ca7ed", - "sha256": "4688cfc59364db9efa881f2cb4c99f2304149c0f69e2bc4164cbae5d525ca7ed" - }, - "x86_64_linux": { - "cellar": "/home/linuxbrew/.linuxbrew/Cellar", - "url": "https://ghcr.io/v2/homebrew/core/xcbeautify/blobs/sha256:4f4ed4b6e2367b3ac361e4916186778de7aef1b6a2dd772e4609437f05ec3f3f", - "sha256": "4f4ed4b6e2367b3ac361e4916186778de7aef1b6a2dd772e4609437f05ec3f3f" - } - } - } - } - } - }, - "system": { - "macos": { - "sonoma": { - "HOMEBREW_VERSION": "4.1.15", - "HOMEBREW_PREFIX": "/opt/homebrew", - "Homebrew/homebrew-core": "api", - "CLT": "15.0.0.0.1.1694021235", - "Xcode": "15.0", - "macOS": "14.0" - } - } - } -} diff --git a/.ado/templates/apple-tools-setup.yml b/.ado/templates/apple-tools-setup.yml index 5dc0ac3319ffe1..9b826746d2814b 100644 --- a/.ado/templates/apple-tools-setup.yml +++ b/.ado/templates/apple-tools-setup.yml @@ -3,8 +3,12 @@ steps: inputs: versionSpec: '18.x' - - script: 'brew bundle --file .ado/Brewfile' + - task: CmdLine@2 displayName: 'brew bundle' + inputs: + script: | + brew bundle --file .ado/Brewfile + cat .ado/Brewfile.lock.json - ${{ if ne(parameters.xcode_version, '')}}: - template: apple-xcode-select.yml From 6128427a04c3a121e7f3bfc9bd5be6587737fbc6 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Sun, 14 Jan 2024 14:22:08 -0800 Subject: [PATCH 2/5] [Cherry Pick] Refactor RCTPushNotificationManager to use `UNNotification` (#2031) --- .../NativePushNotificationManagerIOS.js | 38 +- .../RCTPushNotificationManager.h | 3 - .../RCTPushNotificationManager.mm | 425 ++++++++---------- packages/rn-tester/RNTester/AppDelegate.mm | 9 - 4 files changed, 214 insertions(+), 261 deletions(-) diff --git a/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js b/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js index 102ce795e77a04..0df32a8b3792ea 100644 --- a/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js +++ b/packages/react-native/Libraries/PushNotificationIOS/NativePushNotificationManagerIOS.js @@ -20,17 +20,45 @@ type Permissions = {| type Notification = {| +alertTitle?: ?string, - // Actual type: string | number - +fireDate?: ?number, +alertBody?: ?string, - +alertAction?: ?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 + * accessing already created notifications using getScheduledLocalNotifications + * or getDeliveredNotifications. + */ +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.h b/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index fc912b4bef5cb8..f6fdb8c4e7e660 100644 --- a/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -16,9 +16,6 @@ typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result); #endif // [macOS] #if !TARGET_OS_UIKITFORMAC -#if !TARGET_OS_OSX // [macOS] -+ (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; -#endif // [macOS] + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; + (void)didReceiveRemoteNotification:(NSDictionary *)notification; #if !TARGET_OS_OSX // [macOS] diff --git a/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm b/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm index 7d575e2fe01de7..c0c08ab746c853 100644 --- a/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm +++ b/packages/react-native/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm @@ -25,88 +25,66 @@ 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) -#if !TARGET_OS_OSX // [macOS] -+ (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 notification; + + return content; } -#else // [macOS -+ (NSUserNotification *)NSUserNotification:(id)json + ++ (NSDictionary *)NSDictionaryForNotification: + (JS::NativePushNotificationManagerIOS::Notification &)notification { - NSDictionary *details = [self NSDictionary:json]; - BOOL isSilent = [RCTConvert BOOL:details[@"isSilent"]]; - NSUserNotification *notification = [NSUserNotification new]; - notification.deliveryDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; - notification.informativeText = [RCTConvert NSString:details[@"alertBody"]]; - NSString *title = [RCTConvert NSString:details[@"alertTitle"]]; - if (title) { - notification.title = title; - } - NSString *actionButtonTitle = [RCTConvert NSString:details[@"alertAction"]]; - if (actionButtonTitle) { - notification.actionButtonTitle = actionButtonTitle; + // 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()); } - notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; - - NSCalendarUnit calendarUnit = [RCTConvert NSCalendarUnit:details[@"repeatInterval"]]; - if (calendarUnit > 0) { - NSDateComponents *dateComponents = [NSDateComponents new]; - [dateComponents setValue:1 forComponent:calendarUnit]; - notification.deliveryRepeatInterval = dateComponents; + if (notification.fireDate()) { + notificationDict[@"fireDate"] = @(*notification.fireDate()); } - if (!isSilent) { - notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: NSUserNotificationDefaultSoundName; + if (notification.applicationIconBadgeNumber()) { + notificationDict[@"applicationIconBadgeNumber"] = @(*notification.applicationIconBadgeNumber()); } - - NSString *identifier = [RCTConvert NSString:details[@"identifier"]]; - if (identifier == nil) { - identifier = [[NSUUID UUID] UUIDString]; + if (notification.isSilent()) { + notificationDict[@"isSilent"] = @(*notification.isSilent()); + if ([notificationDict[@"isSilent"] isEqualToNumber:@(NO)]) { + notificationDict[@"soundName"] = notification.soundName(); + } } - notification.identifier = identifier; - return notification; + return notificationDict; } -#endif // macOS] + +@end #if !TARGET_OS_OSX // [macOS] +@implementation RCTConvert (UIBackgroundFetchResult) + RCT_ENUM_CONVERTER( UIBackgroundFetchResult, (@{ @@ -116,9 +94,9 @@ + (NSUserNotification *)NSUserNotification:(id)json }), UIBackgroundFetchResultNoData, integerValue) -#endif // [macOS] @end +#endif // [macOS] #else @interface RCTPushNotificationManager () @end @@ -126,8 +104,10 @@ @interface RCTPushNotificationManager () @implementation RCTPushNotificationManager -#if !TARGET_OS_UIKITFORMAC && !TARGET_OS_OSX // [macOS] +#if !TARGET_OS_UIKITFORMAC +#if !TARGET_OS_OSX // [macOS] +/** DEPRECATED. UILocalNotification was deprecated in iOS 10. Please don't add new callsites. */ static NSDictionary *RCTFormatLocalNotification(UILocalNotification *notification) { NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; @@ -146,33 +126,7 @@ @implementation RCTPushNotificationManager formattedLocalNotification[@"remote"] = @NO; return formattedLocalNotification; } - -static NSDictionary *RCTFormatUNNotification(UNNotification *notification) -{ - NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary]; - UNNotificationContent *content = notification.request.content; - - formattedNotification[@"identifier"] = notification.request.identifier; - - if (notification.date) { - NSDateFormatter *formatter = [NSDateFormatter new]; - [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; - NSString *dateString = [formatter stringFromDate:notification.date]; - formattedNotification[@"date"] = dateString; - } - - formattedNotification[@"title"] = RCTNullIfNil(content.title); - formattedNotification[@"body"] = RCTNullIfNil(content.body); - formattedNotification[@"category"] = RCTNullIfNil(content.categoryIdentifier); - formattedNotification[@"thread-id"] = RCTNullIfNil(content.threadIdentifier); - formattedNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); - - return formattedNotification; -} - -#endif // TARGET_OS_UIKITFORMAC -#if TARGET_OS_OSX // [macOS - +#else // [macOS static NSDictionary *RCTFormatUserNotification(NSUserNotification *notification) { NSMutableDictionary *formattedUserNotification = [NSMutableDictionary dictionary]; @@ -192,6 +146,60 @@ @implementation RCTPushNotificationManager } #endif // macOS] +/** For delivered notifications */ +static NSDictionary *RCTFormatUNNotification(UNNotification *notification) +{ + NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; + if (notification.date) { + formattedLocalNotification[@"fireDate"] = RCTFormatNotificationDateFromNSDate(notification.date); + } + [formattedLocalNotification addEntriesFromDictionary:RCTFormatUNNotificationContent(notification.request.content)]; + return formattedLocalNotification; +} + +/** For scheduled notification requests */ +static NSDictionary *RCTFormatUNNotificationRequest(UNNotificationRequest *request) +{ + NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; + if (request.trigger) { + NSDate *triggerDate = nil; + if ([request.trigger isKindOfClass:[UNTimeIntervalNotificationTrigger class]]) { + triggerDate = [(UNTimeIntervalNotificationTrigger *)request.trigger nextTriggerDate]; + } else if ([request.trigger isKindOfClass:[UNCalendarNotificationTrigger class]]) { + triggerDate = [(UNCalendarNotificationTrigger *)request.trigger nextTriggerDate]; + } + + if (triggerDate) { + formattedLocalNotification[@"fireDate"] = RCTFormatNotificationDateFromNSDate(triggerDate); + } + } + [formattedLocalNotification addEntriesFromDictionary:RCTFormatUNNotificationContent(request.content)]; + return formattedLocalNotification; +} + +static NSDictionary *RCTFormatUNNotificationContent(UNNotificationContent *content) +{ + // Note: soundName is not set because this can't be read from UNNotificationSound. + // Note: alertAction is no longer relevant with UNNotification + NSMutableDictionary *formattedLocalNotification = [NSMutableDictionary dictionary]; + formattedLocalNotification[@"alertTitle"] = RCTNullIfNil(content.title); + formattedLocalNotification[@"alertBody"] = RCTNullIfNil(content.body); + formattedLocalNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); + formattedLocalNotification[@"category"] = content.categoryIdentifier; + formattedLocalNotification[@"applicationIconBadgeNumber"] = content.badge; + formattedLocalNotification[@"remote"] = @NO; + return formattedLocalNotification; +} + +static NSString *RCTFormatNotificationDateFromNSDate(NSDate *date) +{ + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; + return [formatter stringFromDate:date]; +} + +#endif // TARGET_OS_UIKITFORMAC + RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue @@ -235,12 +243,6 @@ - (void)stopObserving ]; } -#if !TARGET_OS_OSX // [macOS] -+ (void)didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings -{ -} -#endif // [macOS] - + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSMutableString *hexString = [NSMutableString string]; @@ -278,16 +280,16 @@ + (void)didReceiveRemoteNotification:(NSDictionary *)notification object:self userInfo:userInfo]; } +#endif // [macOS] +#if !TARGET_OS_OSX // [macOS] + (void)didReceiveLocalNotification:(UILocalNotification *)notification { [[NSNotificationCenter defaultCenter] postNotificationName:kLocalNotificationReceived object:self userInfo:RCTFormatLocalNotification(notification)]; } - -#else // [macOS - +#else // [macOS + (void)didReceiveUserNotification:(NSUserNotification *)notification { NSString *notificationName = notification.isRemote ? RCTRemoteNotificationReceived : kLocalNotificationReceived; @@ -296,7 +298,6 @@ + (void)didReceiveUserNotification:(NSUserNotification *)notification object:self userInfo:userInfo]; } - #endif // macOS] - (void)handleLocalNotificationReceived:(NSNotification *)notification @@ -306,15 +307,13 @@ - (void)handleLocalNotificationReceived:(NSNotification *)notification - (void)handleRemoteNotificationReceived:(NSNotification *)notification { +#if !TARGET_OS_OSX // [macOS] NSMutableDictionary *remoteNotification = [NSMutableDictionary dictionaryWithDictionary:notification.userInfo[@"notification"]]; -#if !TARGET_OS_OSX // [macOS] RCTRemoteNotificationCallback completionHandler = notification.userInfo[@"completionHandler"]; -#endif // [macOS] NSString *notificationId = [[NSUUID UUID] UUIDString]; remoteNotification[@"notificationId"] = notificationId; remoteNotification[@"remote"] = @YES; -#if !TARGET_OS_OSX // [macOS] if (completionHandler) { if (!self.remoteNotificationCallbacks) { // Lazy initialization @@ -322,9 +321,9 @@ - (void)handleRemoteNotificationReceived:(NSNotification *)notification } self.remoteNotificationCallbacks[notificationId] = completionHandler; } -#endif // [macOS] [self sendEventWithName:@"remoteNotificationReceived" body:remoteNotification]; +#endif // [macOS] } - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification @@ -343,9 +342,9 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification [self sendEventWithName:@"remoteNotificationRegistrationError" body:errorDetails]; } -#if !TARGET_OS_OSX // [macOS] RCT_EXPORT_METHOD(onFinishRemoteNotification : (NSString *)notificationId fetchResult : (NSString *)fetchResult) { +#if !TARGET_OS_OSX // [macOS] UIBackgroundFetchResult result = [RCTConvert UIBackgroundFetchResult:fetchResult]; RCTRemoteNotificationCallback completionHandler = self.remoteNotificationCallbacks[notificationId]; if (!completionHandler) { @@ -354,8 +353,8 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification } completionHandler(result); [self.remoteNotificationCallbacks removeObjectForKey:notificationId]; -} #endif // [macOS] +} /** * Update the application icon badge number on the home screen @@ -401,33 +400,19 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification // Add a listener to make sure that startObserving has been called [self addListener:@"remoteNotificationsRegistered"]; -#if !TARGET_OS_OSX // [macOS - UIUserNotificationType types = UIUserNotificationTypeNone; + UNAuthorizationOptions options = UNAuthorizationOptionNone; if (permissions.alert()) { - types |= UIUserNotificationTypeAlert; + options |= UNAuthorizationOptionAlert; } if (permissions.badge()) { - types |= UIUserNotificationTypeBadge; + options |= UNAuthorizationOptionBadge; } if (permissions.sound()) { - types |= UIUserNotificationTypeSound; + options |= UNAuthorizationOptionSound; } -#else - NSRemoteNotificationType types = NSRemoteNotificationTypeNone; - - if (permissions.alert()) { - types |= NSRemoteNotificationTypeAlert; - } - if (permissions.badge()) { - types |= NSRemoteNotificationTypeBadge; - } - if (permissions.sound()) { - types |= NSRemoteNotificationTypeSound; - } -#endif // macOS] [UNUserNotificationCenter.currentNotificationCenter - requestAuthorizationWithOptions:types + requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError *_Nullable error) { if (error != NULL) { reject(@"-1", @"Error - Push authorization request failed.", error); @@ -496,108 +481,87 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification }; } -#if !TARGET_OS_OSX // [macOS] 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]]; -} -#else // [macOS -RCT_EXPORT_METHOD(presentLocalNotification:(NSUserNotification *)notification) -{ - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + 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]; } -#endif // macOS] -#if !TARGET_OS_OSX // [macOS] 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]]; -} -#else // [macOS -RCT_EXPORT_METHOD(scheduleLocalNotification:(NSUserNotification *)notification) -{ - [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification:notification]; + + UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:[[NSUUID UUID] UUIDString] + content:content + trigger:trigger]; + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:request withCompletionHandler:nil]; } -#endif // macOS] RCT_EXPORT_METHOD(cancelAllLocalNotifications) { -#if !TARGET_OS_OSX // [macOS] - [RCTSharedApplication() cancelAllLocalNotifications]; -#else // [macOS - for (NSUserNotification *notif in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications) { - [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification:notif]; - } -#endif // macOS] + [[UNUserNotificationCenter currentNotificationCenter] + getPendingNotificationRequestsWithCompletionHandler:^(NSArray *requests) { + NSMutableArray *notificationIdentifiersToCancel = [NSMutableArray new]; + for (UNNotificationRequest *request in requests) { + [notificationIdentifiersToCancel addObject:request.identifier]; + } + [[UNUserNotificationCenter currentNotificationCenter] + removePendingNotificationRequestsWithIdentifiers:notificationIdentifiersToCancel]; + }]; } RCT_EXPORT_METHOD(cancelLocalNotifications : (NSDictionary *)userInfo) { -#if !TARGET_OS_OSX // [macOS] - for (UILocalNotification *notification in RCTSharedApplication().scheduledLocalNotifications) { -#else // [macOS - for (NSUserNotification *notification in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications) { -#endif // macOS] - __block BOOL matchesAll = YES; - NSDictionary *notificationInfo = notification.userInfo; - // Note: we do this with a loop instead of just `isEqualToDictionary:` - // because we only require that all specified userInfo values match the - // notificationInfo values - notificationInfo may contain additional values - // which we don't care about. - [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { - if (![notificationInfo[key] isEqual:obj]) { - matchesAll = NO; - *stop = YES; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getPendingNotificationRequestsWithCompletionHandler:^(NSArray *_Nonnull requests) { + NSMutableArray *notificationIdentifiersToCancel = [NSMutableArray new]; + for (UNNotificationRequest *request in requests) { + NSDictionary *notificationInfo = request.content.userInfo; + // Note: we do this with a loop instead of just `isEqualToDictionary:` + // because we only require that all specified userInfo values match the + // notificationInfo values - notificationInfo may contain additional values + // which we don't care about. + __block BOOL shouldCancel = YES; + [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if (![notificationInfo[key] isEqual:obj]) { + shouldCancel = NO; + *stop = YES; + } + }]; + + if (shouldCancel) { + [notificationIdentifiersToCancel addObject:request.identifier]; } - }]; -#if !TARGET_OS_OSX // [macOS] - if (matchesAll) { - [RCTSharedApplication() cancelLocalNotification:notification]; - } -#else // [macOS - if ([notification.identifier isEqualToString:userInfo[@"identifier"]] || matchesAll) { - [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification:notification]; } -#endif // macOS] - } + + [center removePendingNotificationRequestsWithIdentifiers:notificationIdentifiersToCancel]; + }]; } RCT_EXPORT_METHOD(getInitialNotification @@ -631,50 +595,30 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification RCT_EXPORT_METHOD(getScheduledLocalNotifications : (RCTResponseSenderBlock)callback) { -#if !TARGET_OS_OSX // [macOS] - NSArray *scheduledLocalNotifications = RCTSharedApplication().scheduledLocalNotifications; -#endif // [macOS] - NSMutableArray *formattedScheduledLocalNotifications = [NSMutableArray new]; -#if !TARGET_OS_OSX // [macOS] - for (UILocalNotification *notification in scheduledLocalNotifications) { - [formattedScheduledLocalNotifications addObject:RCTFormatLocalNotification(notification)]; - } -#else // [macOS - for (NSUserNotification *notification in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications) { - [formattedScheduledLocalNotifications addObject:RCTFormatUserNotification(notification)]; - } -#endif // macOS] - callback(@[ formattedScheduledLocalNotifications ]); + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getPendingNotificationRequestsWithCompletionHandler:^(NSArray *_Nonnull requests) { + NSMutableArray *formattedScheduledLocalNotifications = [NSMutableArray new]; + for (UNNotificationRequest *request in requests) { + [formattedScheduledLocalNotifications addObject:RCTFormatUNNotificationRequest(request)]; + } + callback(@[ formattedScheduledLocalNotifications ]); + }]; } RCT_EXPORT_METHOD(removeAllDeliveredNotifications) { -#if !TARGET_OS_OSX // [macOS] UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center removeAllDeliveredNotifications]; -#else // [macOS - [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; -#endif // macOS] } RCT_EXPORT_METHOD(removeDeliveredNotifications : (NSArray *)identifiers) { -#if !TARGET_OS_OSX // [macOS] UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center removeDeliveredNotificationsWithIdentifiers:identifiers]; -#else // [macOS - NSArray *notificationsToRemove = [[NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSUserNotification* evaluatedObject, NSDictionary * _Nullable bindings) { - return [identifiers containsObject:evaluatedObject.identifier]; - }]]; - for (NSUserNotification *notification in notificationsToRemove) { - [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification:notification]; - } -#endif // macOS] } RCT_EXPORT_METHOD(getDeliveredNotifications : (RCTResponseSenderBlock)callback) { -#if !TARGET_OS_OSX // [macOS] UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getDeliveredNotificationsWithCompletionHandler:^(NSArray *_Nonnull notifications) { NSMutableArray *formattedNotifications = [NSMutableArray new]; @@ -684,13 +628,6 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification } callback(@[ formattedNotifications ]); }]; -#else // [macOS - NSMutableArray *formattedNotifications = [NSMutableArray new]; - for (NSUserNotification *notification in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications) { - [formattedNotifications addObject:RCTFormatUserNotification(notification)]; - } - callback(@[formattedNotifications]); -#endif // macOS] } RCT_EXPORT_METHOD(getAuthorizationStatus : (RCTResponseSenderBlock)callback) diff --git a/packages/rn-tester/RNTester/AppDelegate.mm b/packages/rn-tester/RNTester/AppDelegate.mm index 0ec5f98a0d9f46..4ab8f7a5a0a80d 100644 --- a/packages/rn-tester/RNTester/AppDelegate.mm +++ b/packages/rn-tester/RNTester/AppDelegate.mm @@ -102,15 +102,6 @@ - (void)loadSourceForBridge:(RCTBridge *)bridge #if !TARGET_OS_TV && !TARGET_OS_UIKITFORMAC -#if !TARGET_OS_OSX // [macOS] -// Required to register for notifications -- (void)application:(__unused UIApplication *)application - didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings -{ - [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings]; -} -#endif // [macOS] - // Required for the remoteNotificationsRegistered event. - (void)application:(__unused RCTUIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken From 01efe835195c06f2152499dcb463bb37e4701a21 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Sun, 14 Jan 2024 22:36:12 -0800 Subject: [PATCH 3/5] [Cherry Pick] Remove some usages of APIs deprecated in iOS & removed in visionOS (#2034) --- .../Libraries/Text/Text/RCTTextView.mm | 25 ++++++++++++++--- packages/react-native/React/Base/RCTUtils.m | 26 +++++++++++------- .../React/CoreModules/RCTAlertController.mm | 24 +---------------- .../React/CoreModules/RCTDevLoadingView.mm | 16 +++++------ .../Text/RCTParagraphComponentView.mm | 27 ++++++++++++++++++- .../View/RCTViewComponentView.mm | 2 +- .../react-native/React/UIUtils/RCTUIUtils.m | 3 +-- .../react-native/React/Views/RCTViewManager.m | 2 +- .../FBSnapshotTestController.m | 15 ++++++----- .../FBSnapshotTestCase/UIImage+Compare.m | 3 +-- 10 files changed, 84 insertions(+), 59 deletions(-) diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.mm b/packages/react-native/Libraries/Text/Text/RCTTextView.mm index 4e5afc435ec77d..3f47a3a23b8787 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.mm @@ -37,11 +37,18 @@ - (BOOL)canBecomeKeyView @end -@interface RCTTextView () -@end +#endif // macOS] + +#if !TARGET_OS_OSX // [macOS] +@interface RCTTextView () +@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); +#else // [macOS +@interface RCTTextView () #endif // macOS] +@end + #import @implementation RCTTextView { @@ -351,6 +358,10 @@ - (void)enableContextMenu { _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; + if (@available(iOS 16.0, *)) { + _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; + [self addInteraction:_editMenuInteraction]; + } [self addGestureRecognizer:_longPressGestureRecognizer]; } @@ -362,8 +373,16 @@ - (void)disableContextMenu - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture { - // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) #if !TARGET_OS_UIKITFORMAC + if (@available(iOS 16.0, *)) { + CGPoint location = [gesture locationInView:self]; + UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:location]; + if (_editMenuInteraction) { + [_editMenuInteraction presentEditMenuWithConfiguration:config]; + } + return; + } + // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) UIMenuController *menuController = [UIMenuController sharedMenuController]; if (menuController.isMenuVisible) { diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m index 923d6a277ad0d4..648bceacfd841b 100644 --- a/packages/react-native/React/Base/RCTUtils.m +++ b/packages/react-native/React/Base/RCTUtils.m @@ -305,14 +305,14 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp void RCTComputeScreenScale(void) { dispatch_once(&onceTokenScreenScale, ^{ - screenScale = [UIScreen mainScreen].scale; + screenScale = [UITraitCollection currentTraitCollection].displayScale; }); } CGFloat RCTScreenScale(void) { RCTUnsafeExecuteOnMainQueueOnceSync(&onceTokenScreenScale, ^{ - screenScale = [UIScreen mainScreen].scale; + screenScale = [UITraitCollection currentTraitCollection].displayScale; }); return screenScale; @@ -600,12 +600,20 @@ BOOL RCTRunningInAppExtension(void) return nil; } - // TODO: replace with a more robust solution - for (UIWindow *window in RCTSharedApplication().windows) { - if (window.keyWindow) { - return window; + for (UIScene *scene in RCTSharedApplication().connectedScenes) { + if (scene.activationState != UISceneActivationStateForegroundActive || + ![scene isKindOfClass:[UIWindowScene class]]) { + continue; + } + UIWindowScene *windowScene = (UIWindowScene *)scene; + + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow) { + return window; + } } } + return nil; #else // [macOS return [NSApp keyWindow]; @@ -634,12 +642,10 @@ BOOL RCTForceTouchAvailable(void) static BOOL forceSupported; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - forceSupported = - [UITraitCollection class] && [UITraitCollection instancesRespondToSelector:@selector(forceTouchCapability)]; + forceSupported = [UITraitCollection currentTraitCollection].forceTouchCapability == UIForceTouchCapabilityAvailable; }); - return forceSupported && - (RCTKeyWindow() ?: [UIView new]).traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable; + return forceSupported; } #endif // [macOS] diff --git a/packages/react-native/React/CoreModules/RCTAlertController.mm b/packages/react-native/React/CoreModules/RCTAlertController.mm index 698d7d1ae7856e..7999a937e697bd 100644 --- a/packages/react-native/React/CoreModules/RCTAlertController.mm +++ b/packages/react-native/React/CoreModules/RCTAlertController.mm @@ -23,17 +23,7 @@ @implementation RCTAlertController - (UIWindow *)alertWindow { if (_alertWindow == nil) { - _alertWindow = [self getUIWindowFromScene]; - - if (_alertWindow == nil) { - UIWindow *keyWindow = RCTSharedApplication().keyWindow; - if (keyWindow) { - _alertWindow = [[UIWindow alloc] initWithFrame:keyWindow.bounds]; - } else { - // keyWindow is nil, so we cannot create and initialize _alertWindow - NSLog(@"Unable to create alert window: keyWindow is nil"); - } - } + _alertWindow = [[UIWindow alloc] initWithWindowScene:RCTKeyWindow().windowScene]; if (_alertWindow) { _alertWindow.rootViewController = [UIViewController new]; @@ -67,18 +57,6 @@ - (void)hide _alertWindow = nil; } - -- (UIWindow *)getUIWindowFromScene -{ - for (UIScene *scene in RCTSharedApplication().connectedScenes) { - if (scene.activationState == UISceneActivationStateForegroundActive && - [scene isKindOfClass:[UIWindowScene class]]) { - return [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene]; - } - } - - return nil; -} #endif // [macOS] @end diff --git a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm index 3e2dcd83436b0e..5895b756cea3ce 100644 --- a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm +++ b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm @@ -120,15 +120,15 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo dispatch_async(dispatch_get_main_queue(), ^{ self->_showDate = [NSDate date]; + if (!self->_window && !RCTRunningInTestEnvironment()) { #if !TARGET_OS_OSX // [macOS] - CGSize screenSize = [UIScreen mainScreen].bounds.size; + UIWindow *window = RCTKeyWindow(); + CGFloat windowWidth = window.bounds.size.width; - UIWindow *window = RCTSharedApplication().keyWindow; - self->_window = - [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, window.safeAreaInsets.top + 10)]; - self->_label = - [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, screenSize.width, 20)]; + self->_window = [[UIWindow alloc] initWithWindowScene:window.windowScene]; + self->_window.frame = CGRectMake(0, 0, windowWidth, window.safeAreaInsets.top + 10); + self->_label = [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, windowWidth, 20)]; [self->_window addSubview:self->_label]; self->_window.windowLevel = UIWindowLevelStatusBar + 1; @@ -164,12 +164,10 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo self->_window.backgroundColor = backgroundColor; self->_window.hidden = NO; - - UIWindowScene *scene = (UIWindowScene *)RCTSharedApplication().connectedScenes.anyObject; - self->_window.windowScene = scene; #else // [macOS self->_label.stringValue = message; self->_label.textColor = color; + self->_label.backgroundColor = backgroundColor; [self->_window orderFront:nil]; #endif // macOS] diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index e2db81977263a3..8a20e5326707b0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -27,6 +27,14 @@ using namespace facebook::react; +#if !TARGET_OS_OSX // [macOS] +@interface RCTParagraphComponentView () + +@property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); + +@end +#endif // [macOS] + @implementation RCTParagraphComponentView { ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; @@ -223,19 +231,36 @@ - (void)enableContextMenu { _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; + + if (@available(iOS 16.0, *)) { + _editMenuInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; + [self addInteraction:_editMenuInteraction]; + } [self addGestureRecognizer:_longPressGestureRecognizer]; } - (void)disableContextMenu { [self removeGestureRecognizer:_longPressGestureRecognizer]; + if (@available(iOS 16.0, *)) { + [self removeInteraction:_editMenuInteraction]; + _editMenuInteraction = nil; + } _longPressGestureRecognizer = nil; } - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture { - // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) #if !TARGET_OS_UIKITFORMAC + if (@available(iOS 16.0, *)) { + CGPoint location = [gesture locationInView:self]; + UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil sourcePoint:location]; + if (_editMenuInteraction) { + [_editMenuInteraction presentEditMenuWithConfiguration:config]; + } + return; + } + // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) UIMenuController *menuController = [UIMenuController sharedMenuController]; if (menuController.isMenuVisible) { diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index a382fe0da93e1b..d1340d92eda18d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -263,7 +263,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (oldViewProps.shouldRasterize != newViewProps.shouldRasterize) { self.layer.shouldRasterize = newViewProps.shouldRasterize; #if !TARGET_OS_OSX // [macOS] - self.layer.rasterizationScale = newViewProps.shouldRasterize ? [UIScreen mainScreen].scale : 1.0; + self.layer.rasterizationScale = newViewProps.shouldRasterize ? self.traitCollection.displayScale : 1.0; #else // [macOS self.layer.rasterizationScale = 1.0; #endif // macOS] diff --git a/packages/react-native/React/UIUtils/RCTUIUtils.m b/packages/react-native/React/UIUtils/RCTUIUtils.m index 8d6ebc0ea5c2f4..c5fe036be9f593 100644 --- a/packages/react-native/React/UIUtils/RCTUIUtils.m +++ b/packages/react-native/React/UIUtils/RCTUIUtils.m @@ -15,8 +15,7 @@ RCTDimensions RCTGetDimensions(CGFloat fontScale) UIScreen *mainScreen = UIScreen.mainScreen; CGSize screenSize = mainScreen.bounds.size; - UIView *mainWindow; - mainWindow = RCTKeyWindow(); + UIView *mainWindow = RCTKeyWindow(); // We fallback to screen size if a key window is not found. CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize; #else // [macOS diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index 4063fbbc2908e9..53c82b94775669 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -277,7 +277,7 @@ - (RCTShadowView *)shadowView { view.layer.shouldRasterize = json ? [RCTConvert BOOL:json] : defaultView.layer.shouldRasterize; view.layer.rasterizationScale = - view.layer.shouldRasterize ? [UIScreen mainScreen].scale : defaultView.layer.rasterizationScale; + view.layer.shouldRasterize ? view.traitCollection.displayScale : defaultView.layer.rasterizationScale; } #endif // [macOS] diff --git a/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m b/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m index 01db01bfa2112e..ec4cd1cb835797 100644 --- a/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m +++ b/packages/rn-tester/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m @@ -238,14 +238,15 @@ - (NSString *)_fileNameForSelector:(SEL)selector if (0 < identifier.length) { fileName = [fileName stringByAppendingFormat:@"_%@", identifier]; } - CGFloat scale; // [macOS -#if !TARGET_OS_OSX - scale = [[UIScreen mainScreen] scale]; -#else - scale = [[NSScreen mainScreen] backingScaleFactor]; -#endif - if (scale > 1.0) { // macOS] +#if !TARGET_OS_OSX // [macOS] + UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection]; + if (currentTraitCollection.displayScale > 1.0) { + fileName = [fileName stringByAppendingFormat:@"@%.fx", currentTraitCollection.displayScale]; +#else // [macOS + CGFloat scale = [[NSScreen mainScreen] backingScaleFactor]; + if (scale > 1.0) { fileName = [fileName stringByAppendingFormat:@"@%.fx", scale]; +#endif // macOS] } fileName = [fileName stringByAppendingPathExtension:@"png"]; return fileName; diff --git a/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m b/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m index 964efed23099cb..9aa90fa83c6e0d 100644 --- a/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m +++ b/packages/rn-tester/RCTTest/FBSnapshotTestCase/UIImage+Compare.m @@ -45,14 +45,13 @@ - (BOOL)compareWithImage:(UIImage *)image (CGBitmapInfo)kCGImageAlphaPremultipliedLast); #if !TARGET_OS_OSX // [macOS] - CGFloat scaleFactor = [UIScreen mainScreen].scale; + CGFloat scaleFactor = [UITraitCollection currentTraitCollection].displayScale; #else // [macOS // The compareWithImage: method is used for integration test snapshot image comparison. // The _snapshotView: method that creates snapshot images that are *not* scaled for the screen. // By not using the screen scale factor in this method the test results are machine independent. CGFloat scaleFactor = 1; #endif // macOS] - CGContextScaleCTM(referenceImageContext, scaleFactor, scaleFactor); CGContextScaleCTM(imageContext, scaleFactor, scaleFactor); From 2121f8cca6f7c7911bc956d7eed2c39f26dd36be Mon Sep 17 00:00:00 2001 From: Gabriel Donadel Dall'Agnol Date: Fri, 19 Jan 2024 15:40:39 -0300 Subject: [PATCH 4/5] [react-native] Fix Hermes version mismatch (#2014) * [react-native] Fix Hermes version mismatch * Address PR comments * Update packages/react-native/sdks/hermes-engine/hermes-utils.rb Co-authored-by: Saad Najmi --------- Co-authored-by: Saad Najmi --- .../sdks/hermes-engine/hermes-engine.podspec | 5 +++- .../sdks/hermes-engine/hermes-utils.rb | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec index 769d8ce0d60afc..a4a617f5208c3d 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-engine.podspec +++ b/packages/react-native/sdks/hermes-engine/hermes-engine.podspec @@ -10,7 +10,10 @@ react_native_path = File.join(__dir__, "..", "..") # package.json package = JSON.parse(File.read(File.join(react_native_path, "package.json"))) -version = package['version'] +# [macOS +rn_version = package['version'] +version = findLastestVersionWithArtifact(rn_version) || rn_version +# macOS] source_type = hermes_source_type(version, react_native_path) source = podspec_source(source_type, version, react_native_path) diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index 8c2f4f6c665a83..7b404d10bebf71 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -5,6 +5,7 @@ require 'net/http' require 'rexml/document' +require 'open3' # [macOS] HERMES_GITHUB_URL = "https://github.com/facebook/hermes.git" @@ -233,6 +234,28 @@ def resolve_url_redirects(url) return (`curl -Ls -o /dev/null -w %{url_effective} \"#{url}\"`) end +# [macOS react-native-macos does not publish macos specific hermes artifacts +# so we attempt to find the latest patch version of the iOS artifacts and use that +def findLastestVersionWithArtifact(version) + versionWithoutPatch = version.match(/^(\d+\.\d+)/) + xml_data, = Open3.capture3("curl -s https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/maven-metadata.xml") + + metadata = REXML::Document.new(xml_data) + versions = metadata.elements.to_a('//metadata/versioning/versions/version') + + # Extract version numbers and sort them + filtered_versions = versions.select { |version| version.text.match?(/^#{versionWithoutPatch}\.\d+$/) } + if filtered_versions.empty? + return + end + + version_numbers = filtered_versions.map { |version| version.text } + sorted_versions = version_numbers.sort_by { |v| Gem::Version.new(v) } + + return sorted_versions.last +end +# macOS] + # This function checks that Hermes artifact exists. # As of now it should check it on the Maven repo. # From 4448130bcfd9b132bd7fa6e056a87ab32416d3e0 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 19 Jan 2024 16:18:15 -0800 Subject: [PATCH 5/5] fix(macOS): Don't use iOS deployment target for macOS (#2042) * Remove stale pbxproj entries * Fix macos version typos in ruby scripts --- packages/react-native/scripts/cocoapods/utils.rb | 2 +- packages/react-native/scripts/react_native_pods.rb | 6 +----- packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index 3bb673935de4c5..63496f4cd5ec29 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -292,7 +292,7 @@ def self.updateOSDeploymentTarget(installer) end target_installation_result.native_target.build_configurations.each do |config| config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = Helpers::Constants.min_ios_version_supported - config.build_settings["MACOSX_DEPLOYMENT_TARGET"] = Helpers::Constants.min_ios_version_supported # [macOS] + config.build_settings["MACOSX_DEPLOYMENT_TARGET"] = Helpers::Constants.min_macos_version_supported # [macOS] end end end diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 625b3ff3ae14de..eaf4edc2147cc6 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -47,15 +47,11 @@ def min_macos_version_supported end # macOS] -def min_supported_versions -return { :ios => min_ios_version_supported, :osx => min_macos_version_supported } # [macOS] -end - # This function returns the min supported OS versions supported by React Native # By using this function, you won't have to manually change your Podfile # when we change the minimum version supported by the framework. def min_supported_versions - return { :ios => min_ios_version_supported, :osx => '10.15'} # [macOS] + return { :ios => min_ios_version_supported, :osx => min_macos_version_supported} # [macOS] end # This function prepares the project for React Native, before processing diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj index 347c5f110d162f..6cef6a4fcfd71a 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ AC73FCE829B1316D0003586F /* RNTesterIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7C1241922BEC44B00DA25C0 /* RNTesterIntegrationTests.m */; }; AC73FCE929B131700003586F /* RCTLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB215E22B2F3EC005AC45F /* RCTLoggingTests.m */; }; AC73FCEB29B131770003586F /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB215F22B2F3EC005AC45F /* RCTUIManagerScenarioTests.m */; }; - AC73FCEC29B1317A0003586F /* BuildFile in Sources */ = {isa = PBXBuildFile; }; AC73FCED29B1317D0003586F /* RNTesterTestModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB215D22B2F3EC005AC45F /* RNTesterTestModule.m */; }; AC73FCEE29B131870003586F /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB20C622B2BAA5005AC45F /* RCTAllocationTests.m */; }; AC73FCEF29B1318B0003586F /* RCTAnimationUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB20B222B2BAA4005AC45F /* RCTAnimationUtilsTests.m */; }; @@ -58,7 +57,6 @@ ACDBF0D529B2BF0A00EEBD9E /* RNTesterUnitTestsBundle.js in Resources */ = {isa = PBXBuildFile; fileRef = E7DB20B322B2BAA4005AC45F /* RNTesterUnitTestsBundle.js */; }; BE29745FA46255D106CB15ED /* libPods-RNTester.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 77955ECD532E128A8C49AC35 /* libPods-RNTester.a */; }; CD10C7A5290BD4EB0033E1ED /* RCTEventEmitterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CD10C7A4290BD4EB0033E1ED /* RCTEventEmitterTests.m */; }; - DD6FAB12C0152128DD2DA1BE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; E62F11832A5C6580000BF1C8 /* FlexibleSizeExampleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.mm */; }; E62F11842A5C6584000BF1C8 /* UpdatePropertiesExampleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.mm */; }; E7C1241A22BEC44B00DA25C0 /* RNTesterIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7C1241922BEC44B00DA25C0 /* RNTesterIntegrationTests.m */; }; @@ -242,7 +240,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DD6FAB12C0152128DD2DA1BE /* BuildFile in Frameworks */, BE29745FA46255D106CB15ED /* libPods-RNTester.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1049,7 +1046,6 @@ AC73FCE929B131700003586F /* RCTLoggingTests.m in Sources */, AC73FCEB29B131770003586F /* RCTUIManagerScenarioTests.m in Sources */, AC73FCED29B1317D0003586F /* RNTesterTestModule.m in Sources */, - AC73FCEC29B1317A0003586F /* BuildFile in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };