diff --git a/.gitignore b/.gitignore index d4946796..b133fccd 100644 --- a/.gitignore +++ b/.gitignore @@ -558,7 +558,7 @@ ios/Pods ios/Podfile.lock ios/NotifeeCore.framework ios/NotifeeCore.xcworkspace - +ios/build ### Flutter ### # Flutter/Dart/Pub related @@ -616,4 +616,4 @@ flutter_export_environment.sh #tests_react_native -/tests_react_native/playgrounds \ No newline at end of file +/tests_react_native/playgrounds diff --git a/ios/NotifeeCore.xcodeproj/project.pbxproj b/ios/NotifeeCore.xcodeproj/project.pbxproj index ec5dbdee..61401b60 100644 --- a/ios/NotifeeCore.xcodeproj/project.pbxproj +++ b/ios/NotifeeCore.xcodeproj/project.pbxproj @@ -406,7 +406,7 @@ PRODUCT_BUNDLE_IDENTIFIER = app.notifee.core.NotifeeCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4,6"; }; name = Debug; }; @@ -428,7 +428,7 @@ PRODUCT_BUNDLE_IDENTIFIER = app.notifee.core.NotifeeCore; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4,6"; }; name = Release; }; diff --git a/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m b/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m index f90a1f59..82707f37 100644 --- a/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m +++ b/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m @@ -5,7 +5,13 @@ // Copyright © 2020 Invertase. All rights reserved. // -#import +#include +#if TARGET_OS_IPHONE + @import UIKit; +#else + @import AppKit; +#endif + #import "Private/NotifeeCore+NSNotificationCenter.h" #import "Private/NotifeeCore+UNUserNotificationCenter.h" @@ -27,17 +33,34 @@ - (void)observe { NotifeeCoreNSNotificationCenter *strongSelf = weakSelf; // Application // ObjC -> Initialize other delegates & observers +#if TARGET_OS_IPHONE +#if !TARGET_OS_WATCH [[NSNotificationCenter defaultCenter] addObserver:strongSelf selector:@selector(application_onDidFinishLaunchingNotification:) name:UIApplicationDidFinishLaunchingNotification object:nil]; +#endif + [[NSNotificationCenter defaultCenter] + addObserver:strongSelf + selector:@selector(messaging_didReceiveRemoteNotification:) + name:@"RNFBMessagingDidReceiveRemoteNotification" + object:nil]; + }); +#endif +#if !TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] + addObserver:strongSelf + selector:@selector(application_onDidFinishLaunchingNotification:) + name:NSApplicationDidFinishLaunchingNotification + object:nil]; [[NSNotificationCenter defaultCenter] addObserver:strongSelf selector:@selector(messaging_didReceiveRemoteNotification:) name:@"RNFBMessagingDidReceiveRemoteNotification" object:nil]; }); +#endif } // start observing immediately on class load - specifically for diff --git a/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m b/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m index 55a816e4..186e0c85 100644 --- a/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +++ b/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m @@ -70,8 +70,10 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options))completionHandler { - NSDictionary *notifeeNotification = - notification.request.content.userInfo[kNotifeeUserInfoNotification]; + NSDictionary *notifeeNotification = nil; +#if !TARGET_OS_TV + notifeeNotification = notification.request.content.userInfo[kNotifeeUserInfoNotification]; +#endif // we only care about notifications created through notifee if (notifeeNotification != nil) { @@ -92,10 +94,22 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center } if (alert) { - presentationOptions |= UNNotificationPresentationOptionAlert; + if (@available(ios 14, macOS 11, macCatalyst 14, tvOS 14, watchOS 7, *)) { + presentationOptions |= UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner; + } else { +// Guarding with '@available' does not remove deprecation warning unfortunately. Pragma it is +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + presentationOptions |= UNNotificationPresentationOptionAlert; +#pragma clang diagnostic pop + } } - NSDictionary *notifeeTrigger = notification.request.content.userInfo[kNotifeeUserInfoTrigger]; + NSDictionary *notifeeTrigger = nil; +#if !TARGET_OS_TV + notifeeTrigger = notification.request.content.userInfo[kNotifeeUserInfoTrigger]; +#endif + if (notifeeTrigger != nil) { // post DELIVERED event [[NotifeeCoreDelegateHolder instance] didReceiveNotifeeCoreEvent:@{ @@ -118,6 +132,7 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center // The method will be called when the user responded to the notification by opening the application, // dismissing the notification or choosing a UNNotificationAction. The delegate must be set before // the application returns from application:didFinishLaunchingWithOptions:. +#if !TARGET_OS_TV - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { @@ -188,12 +203,19 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center withCompletionHandler:completionHandler]; } } +#endif +#if !TARGET_OS_TV && !TARGET_OS_WATCH - (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification { if (_originalDelegate != nil && originalUNCDelegateRespondsTo.openSettingsForNotification) { - [_originalDelegate userNotificationCenter:center openSettingsForNotification:notification]; + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, *)) { + [_originalDelegate userNotificationCenter:center openSettingsForNotification:notification]; + } else { + // Fallback on earlier versions + } } } +#endif @end diff --git a/ios/NotifeeCore/NotifeeCore.m b/ios/NotifeeCore/NotifeeCore.m index 439bb5e6..49e11c06 100644 --- a/ios/NotifeeCore/NotifeeCore.m +++ b/ios/NotifeeCore/NotifeeCore.m @@ -8,7 +8,13 @@ #import "Public/NotifeeCore.h" -#import +#include +#if TARGET_OS_IPHONE + @import UIKit; +#else + @import AppKit; +#endif + #import "Private/NotifeeCore+UNUserNotificationCenter.h" #import "Private/NotifeeCoreDelegateHolder.h" #import "Private/NotifeeCoreUtil.h" @@ -31,8 +37,11 @@ + (void)cancelNotification:(NSString *)notificationId withNotificationType: (NSI UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; // cancel displayed notification if (notificationType == NotifeeCoreNotificationTypeDisplayed || - notificationType == NotifeeCoreNotificationTypeAll) + notificationType == NotifeeCoreNotificationTypeAll) { +#if !TARGET_OS_TV [center removeDeliveredNotificationsWithIdentifiers:@[ notificationId ]]; +#endif + } // cancel trigger notification if (notificationType == NotifeeCoreNotificationTypeTrigger || @@ -52,8 +61,11 @@ + (void)cancelAllNotifications:(NSInteger)notificationType withBlock:(notifeeMet // cancel displayed notifications if (notificationType == NotifeeCoreNotificationTypeDisplayed || - notificationType == NotifeeCoreNotificationTypeAll) + notificationType == NotifeeCoreNotificationTypeAll) { +#if !TARGET_OS_TV [center removeAllDeliveredNotifications]; +#endif + } // cancel trigger notifications if (notificationType == NotifeeCoreNotificationTypeTrigger || @@ -159,6 +171,10 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif NSDictionary *iosDict = notification[@"ios"]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + // badgeCount - nil is an acceptable value so no need to check key existence + content.badge = iosDict[@"badgeCount"]; + +#if !TARGET_OS_TV // title if (notification[@"title"] != nil) { content.title = notification[@"title"]; @@ -185,29 +201,30 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif content.userInfo = userInfo; - // badgeCount - nil is an acceptable value so no need to check key existence - content.badge = iosDict[@"badgeCount"]; - // categoryId if (iosDict[@"categoryId"] != nil) { content.categoryIdentifier = iosDict[@"categoryId"]; } +#if TARGET_OS_IPHONE // launchImageName if (iosDict[@"launchImageName"] != nil) { content.launchImageName = iosDict[@"launchImageName"]; } +#endif // critical, criticalVolume, sound if (iosDict[@"critical"] != nil) { UNNotificationSound *notificationSound; BOOL criticalSound = [iosDict[@"critical"] boolValue]; NSNumber *criticalSoundVolume = iosDict[@"criticalVolume"]; - NSString *soundName = iosDict[@"sound"] != nil ? iosDict[@"sound"] : @"default"; +#if !TARGET_OS_WATCH + NSString *soundName = iosDict[@"sound"] != nil ? iosDict[@"sound"] : @"default"; if ([soundName isEqualToString:@"default"]) { +#endif if (criticalSound) { - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, watchOS 5, *)) { if (criticalSoundVolume != nil) { notificationSound = [UNNotificationSound defaultCriticalSoundWithAudioVolume:[criticalSoundVolume floatValue]]; @@ -220,9 +237,10 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif } else { notificationSound = [UNNotificationSound defaultSound]; } +#if !TARGET_OS_WATCH } else { if (criticalSound) { - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, watchOS 5, *)) { if (criticalSoundVolume != nil) { notificationSound = [UNNotificationSound criticalSoundNamed:soundName @@ -237,16 +255,21 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif notificationSound = [UNNotificationSound soundNamed:soundName]; } } +#endif content.sound = notificationSound; } else if (iosDict[@"sound"] != nil) { UNNotificationSound *notificationSound; - NSString *soundName = iosDict[@"sound"]; +#if !TARGET_OS_WATCH + NSString *soundName = iosDict[@"sound"]; if ([soundName isEqualToString:@"default"]) { +#endif notificationSound = [UNNotificationSound defaultSound]; +#if !TARGET_OS_WATCH } else { notificationSound = [UNNotificationSound soundNamed:soundName]; } +#endif content.sound = notificationSound; @@ -257,7 +280,8 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif content.threadIdentifier = iosDict[@"threadId"]; } - if (@available(iOS 12.0, *)) { +#if !TARGET_OS_WATCH + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, *)) { // summaryArgument if (iosDict[@"summaryArgument"] != nil) { content.summaryArgument = iosDict[@"summaryArgument"]; @@ -268,8 +292,9 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif content.summaryArgumentCount = [iosDict[@"summaryArgumentCount"] unsignedIntValue]; } } +#endif - if (@available(iOS 13.0, *)) { + if (@available(iOS 13.0, macOS 10.15, macCatalyst 13, tvOS 13, watchOS 6, *)) { // targetContentId if (iosDict[@"targetContentId"] != nil) { content.targetContentIdentifier = iosDict[@"targetContentId"]; @@ -281,10 +306,12 @@ + (UNMutableNotificationContent *)buildNotificationContent:(NSDictionary *)notif content.attachments = [NotifeeCoreUtil notificationAttachmentsFromDictionaryArray:iosDict[@"attachments"]]; } +#endif return content; } +#if !TARGET_OS_TV + (void)getNotificationCategories:(notifeeMethodNSArrayBlock)block { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center getNotificationCategoriesWithCompletionHandler:^( @@ -295,10 +322,13 @@ + (void)getNotificationCategories:(notifeeMethodNSArrayBlock)block { NSMutableDictionary *categoryDictionary = [NSMutableDictionary dictionary]; categoryDictionary[@"id"] = notificationCategory.identifier; +#if TARGET_OS_IPHONE categoryDictionary[@"allowInCarPlay"] = @(((notificationCategory.options & UNNotificationCategoryOptionAllowInCarPlay) != 0)); +#endif - if (@available(iOS 11.0, *)) { +#if !TARGET_OS_WATCH + if (@available(iOS 11.0, macOS 10.14, macCatalyst 13, *)) { categoryDictionary[@"hiddenPreviewsShowTitle"] = @(((notificationCategory.options & UNNotificationCategoryOptionHiddenPreviewsShowTitle) != 0)); @@ -310,22 +340,26 @@ + (void)getNotificationCategories:(notifeeMethodNSArrayBlock)block { notificationCategory.hiddenPreviewsBodyPlaceholder; } } else { +#endif categoryDictionary[@"hiddenPreviewsShowTitle"] = @(NO); categoryDictionary[@"hiddenPreviewsShowSubtitle"] = @(NO); +#if !TARGET_OS_WATCH } - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, *)) { if (notificationCategory.categorySummaryFormat != nil) { categoryDictionary[@"summaryFormat"] = notificationCategory.categorySummaryFormat; } } +#endif - if (@available(iOS 13.0, *)) { + categoryDictionary[@"allowAnnouncement"] = @(NO); +#if TARGET_OS_IPHONE + if (@available(iOS 13.0, macCatalyst 13, watchOS 6, *)) { categoryDictionary[@"allowAnnouncement"] = @( ((notificationCategory.options & UNNotificationCategoryOptionAllowAnnouncement) != 0)); - } else { - categoryDictionary[@"allowAnnouncement"] = @(NO); } +#endif categoryDictionary[@"actions"] = [NotifeeCoreUtil notificationActionsToDictionaryArray:notificationCategory.actions]; @@ -353,8 +387,6 @@ + (void)setNotificationCategories:(NSArray *)categories UNNotificationCategory *category; NSString *id = categoryDictionary[@"id"]; - NSString *summaryFormat = categoryDictionary[@"summaryFormat"]; - NSString *bodyPlaceHolder = categoryDictionary[@"hiddenPreviewsBodyPlaceholder"]; NSArray *actions = [NotifeeCoreUtil notificationActionsFromDictionaryArray:categoryDictionary[@"actions"]]; @@ -363,11 +395,14 @@ + (void)setNotificationCategories:(NSArray *)categories UNNotificationCategoryOptions options = UNNotificationCategoryOptionCustomDismissAction; +#if TARGET_OS_IPHONE if ([categoryDictionary[@"allowInCarPlay"] isEqual:@(YES)]) { options |= UNNotificationCategoryOptionAllowInCarPlay; } +#endif - if (@available(iOS 11.0, *)) { +#if !TARGET_OS_WATCH + if (@available(iOS 11.0, macOS 10.14, macCatalyst 13, *)) { if ([categoryDictionary[@"hiddenPreviewsShowTitle"] isEqual:@(YES)]) { options |= UNNotificationCategoryOptionHiddenPreviewsShowTitle; } @@ -376,14 +411,20 @@ + (void)setNotificationCategories:(NSArray *)categories options |= UNNotificationCategoryOptionHiddenPreviewsShowSubtitle; } } +#endif - if (@available(iOS 13.0, *)) { +#if TARGET_OS_IPHONE + if (@available(iOS 13.0, macCatalyst 13, watchOS 6, *)) { if ([categoryDictionary[@"allowAnnouncement"] isEqual:@(YES)]) { options |= UNNotificationCategoryOptionAllowAnnouncement; } } +#endif - if (@available(iOS 12.0, *)) { +#if !TARGET_OS_WATCH + NSString *summaryFormat = categoryDictionary[@"summaryFormat"]; + NSString *bodyPlaceHolder = categoryDictionary[@"hiddenPreviewsBodyPlaceholder"]; + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, *)) { category = [UNNotificationCategory categoryWithIdentifier:id actions:actions intentIdentifiers:intentIdentifiers @@ -397,11 +438,14 @@ + (void)setNotificationCategories:(NSArray *)categories hiddenPreviewsBodyPlaceholder:bodyPlaceHolder options:options]; } else { +#endif category = [UNNotificationCategory categoryWithIdentifier:id actions:actions intentIdentifiers:intentIdentifiers options:options]; +#if !TARGET_OS_WATCH } +#endif [UNNotificationCategories addObject:category]; } @@ -410,6 +454,7 @@ + (void)setNotificationCategories:(NSArray *)categories [center setNotificationCategories:UNNotificationCategories]; block(nil); } +#endif /** * Request UNAuthorizationOptions for user notifications. @@ -443,23 +488,25 @@ + (void)requestPermission:(NSDictionary *)permissions } if ([permissions[@"provisional"] isEqual:@(YES)]) { - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, tvOS 12, watchOS 5, *)) { options |= UNAuthorizationOptionProvisional; } } +#if !TARGET_OS_TV && TARGET_OS_IPHONE if ([permissions[@"announcement"] isEqual:@(YES)]) { - if (@available(iOS 13.0, *)) { + if (@available(iOS 13.0, macCatalyst 13, watchOS 6, *)) { options |= UNAuthorizationOptionAnnouncement; } } +#endif if ([permissions[@"carPlay"] isEqual:@(YES)]) { options |= UNAuthorizationOptionCarPlay; } if ([permissions[@"criticalAlert"] isEqual:@(YES)]) { - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, tvOS 12, watchOS 5, *)) { options |= UNAuthorizationOptionCriticalAlert; } } @@ -498,14 +545,15 @@ + (void)getNotificationSettings:(notifeeMethodNSDictionaryBlock)block { authorizedStatus = @1; } - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, tvOS 12, watchOS 5, *)) { if (settings.authorizationStatus == UNAuthorizationStatusProvisional) { authorizedStatus = @2; } } +#if !TARGET_OS_TV && !TARGET_OS_WATCH NSNumber *showPreviews = @-1; - if (@available(iOS 11.0, *)) { + if (@available(iOS 11.0, macOS 10.14, macCatalyst 13, *)) { if (settings.showPreviewsSetting == UNShowPreviewsSettingNever) { showPreviews = @0; } else if (settings.showPreviewsSetting == UNShowPreviewsSettingAlways) { @@ -515,21 +563,22 @@ + (void)getNotificationSettings:(notifeeMethodNSDictionaryBlock)block { } } - if (@available(iOS 13.0, *)) { + settingsDictionary[@"announcement"] = @-1; +#if TARGET_OS_IPHONE + if (@available(iOS 13.0, macCatalyst 13, watchOS 6, *)) { settingsDictionary[@"announcement"] = [NotifeeCoreUtil numberForUNNotificationSetting:settings.announcementSetting]; - } else { - settingsDictionary[@"announcement"] = @-1; } +#endif - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, watchOS 5, *)) { settingsDictionary[@"criticalAlert"] = [NotifeeCoreUtil numberForUNNotificationSetting:settings.criticalAlertSetting]; } else { settingsDictionary[@"criticalAlert"] = @-1; } - if (@available(iOS 12.0, *)) { + if (@available(iOS 12.0, macOS 10.14, macCatalyst 13, tvOS 12, watchOS 5, *)) { settingsDictionary[@"inAppNotificationSettings"] = settings.providesAppNotificationSettings ? @1 : @0; } else { @@ -544,12 +593,15 @@ + (void)getNotificationSettings:(notifeeMethodNSDictionaryBlock)block { [NotifeeCoreUtil numberForUNNotificationSetting:settings.badgeSetting]; settingsDictionary[@"sound"] = [NotifeeCoreUtil numberForUNNotificationSetting:settings.soundSetting]; +#if TARGET_OS_IPHONE settingsDictionary[@"carPlay"] = [NotifeeCoreUtil numberForUNNotificationSetting:settings.carPlaySetting]; +#endif settingsDictionary[@"lockScreen"] = [NotifeeCoreUtil numberForUNNotificationSetting:settings.lockScreenSetting]; settingsDictionary[@"notificationCenter"] = [NotifeeCoreUtil numberForUNNotificationSetting:settings.notificationCenterSetting]; +#endif block(nil, settingsDictionary); }]; } @@ -559,22 +611,46 @@ + (void)getInitialNotification:(notifeeMethodNSDictionaryBlock)block { } + (void)setBadgeCount:(NSInteger)count withBlock:(notifeeMethodVoidBlock)block { +#if !TARGET_OS_WATCH && TARGET_OS_IPHONE [[UIApplication sharedApplication] setApplicationIconBadgeNumber:count]; +#endif +#if !TARGET_OS_IPHONE + NSDockTile *tile = [[NSApplication sharedApplication] dockTile]; + [tile setBadgeLabel:[NSString stringWithFormat:@"%d", (int)count]]; + block(nil); +#endif block(nil); } + (void)getBadgeCount:(notifeeMethodNSIntegerBlock)block { +#if !TARGET_OS_WATCH && TARGET_OS_IPHONE block(nil, [UIApplication sharedApplication].applicationIconBadgeNumber); +#endif +#if !TARGET_OS_IPHONE + NSDockTile *tile = [[NSApplication sharedApplication] dockTile]; + NSString *currentCountString = [tile badgeLabel]; + NSInteger currentCount = [currentCountString integerValue]; // value is zero if not a valid integer: serves as reasonable init + block(nil, currentCount); +#endif } + (void)incrementBadgeCount:(NSInteger)incrementBy withBlock:(notifeeMethodVoidBlock)block { +#if !TARGET_OS_WATCH && TARGET_OS_IPHONE NSInteger currentCount = [UIApplication sharedApplication].applicationIconBadgeNumber; NSInteger newCount = currentCount + incrementBy; [[UIApplication sharedApplication] setApplicationIconBadgeNumber:newCount]; +#endif +#if !TARGET_OS_IPHONE + NSDockTile *tile = [[NSApplication sharedApplication] dockTile]; + NSString *currentCountString = [tile badgeLabel]; + NSInteger currentCount = [currentCountString integerValue]; // value is zero if not a valid integer: serves as reasonable init + [tile setBadgeLabel:[NSString stringWithFormat:@"%d", (int)(currentCount + 1)]]; +#endif block(nil); } + (void)decrementBadgeCount:(NSInteger)decrementBy withBlock:(notifeeMethodVoidBlock)block { +#if !TARGET_OS_WATCH && TARGET_OS_IPHONE NSInteger currentCount = [UIApplication sharedApplication].applicationIconBadgeNumber; NSInteger newCount = currentCount - decrementBy; @@ -583,6 +659,18 @@ + (void)decrementBadgeCount:(NSInteger)decrementBy withBlock:(notifeeMethodVoidB } [[UIApplication sharedApplication] setApplicationIconBadgeNumber:newCount]; +#endif +#if !TARGET_OS_IPHONE + NSDockTile *tile = [[NSApplication sharedApplication] dockTile]; + NSString *currentCountString = [tile badgeLabel]; + NSInteger currentCount = [currentCountString integerValue]; // value is zero if not a valid integer: serves as reasonable init + NSInteger newCount = currentCount - decrementBy; + if (newCount < 0) { + newCount = 0; + } + + [tile setBadgeLabel:[NSString stringWithFormat:@"%d", (int)newCount]]; +#endif block(nil); } diff --git a/ios/NotifeeCore/NotifeeCoreUtil.m b/ios/NotifeeCore/NotifeeCoreUtil.m index 6b585afd..1cc7c32b 100644 --- a/ios/NotifeeCore/NotifeeCoreUtil.m +++ b/ios/NotifeeCore/NotifeeCoreUtil.m @@ -6,6 +6,8 @@ // Copyright © 2020 Invertase. All rights reserved. // +#include + #import "Private/NotifeeCoreUtil.h" #include @@ -26,6 +28,7 @@ + (NSNumber *)numberForUNNotificationSetting:(UNNotificationSetting)setting { return asNumber; } +#if !TARGET_OS_TV + (NSMutableArray *)notificationActionsToDictionaryArray: (NSArray *)notificationActions { NSMutableArray *notificationActionDicts = [[NSMutableArray alloc] init]; @@ -177,6 +180,7 @@ + (UNNotificationAttachment *)attachmentFromDictionary:(NSDictionary *)attachmen NSLog(@"NotifeeCore: Unable to resolve url for attachment: %@", attachmentDict); return nil; } +#endif /* * Downloads a media file, syncronously to the NSCachesDirectory @@ -236,6 +240,7 @@ + (NSURL *)downloadMediaSynchronously:(NSString *)urlString { } } +#if !TARGET_OS_TV /** * Returns a NSDictionary representation of options related to the attached file * @@ -269,6 +274,7 @@ + (NSDictionary *)attachmentOptionsFromDictionary:(NSDictionary *)optionsDict { return options; } +#endif /** * Returns an UNNotificationTrigger from NSDictionary representing a trigger @@ -374,16 +380,33 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger (NSArray *)identifiers { NSMutableArray *intentIdentifiers = [[NSMutableArray alloc] init]; +#if !TARGET_OS_TV && TARGET_OS_IPHONE for (NSString *identifier in identifiers) { - if ([identifier isEqualToString:INStartAudioCallIntentIdentifier]) { - // IOSIntentIdentifier.START_AUDIO_CALL - [intentIdentifiers addObject:@0]; - } else if ([identifier isEqualToString:INStartVideoCallIntentIdentifier]) { - // IOSIntentIdentifier.START_VIDEO_CALL - [intentIdentifiers addObject:@1]; - } else if ([identifier isEqualToString:INSearchCallHistoryIntentIdentifier]) { + + // Handle deprecation of separate audio and video symbols / new single "call" symbol + if (@available(ios 13, macOS 13, watchOS 6, *)) { + if ([identifier isEqualToString:INStartCallIntentIdentifier]) { + // IOSIntentIdentifier.START_CALL + [intentIdentifiers addObject:@0]; + } + } else { +// Guarding with '@available' does not remove deprecation warning unfortunately. Pragma it is +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if ([identifier isEqualToString:INStartAudioCallIntentIdentifier]) { + // IOSIntentIdentifier.START_AUDIO_CALL + [intentIdentifiers addObject:@0]; + } else if ([identifier isEqualToString:INStartVideoCallIntentIdentifier]) { + // IOSIntentIdentifier.START_VIDEO_CALL + [intentIdentifiers addObject:@1]; + } +#pragma clang diagnostic pop + } + + if ([identifier isEqualToString:INSearchCallHistoryIntentIdentifier]) { // IOSIntentIdentifier.SEARCH_CALL_HISTORY [intentIdentifiers addObject:@2]; +#if !TARGET_OS_WATCH } else if ([identifier isEqualToString:INSetAudioSourceInCarIntentIdentifier]) { // IOSIntentIdentifier.SET_AUDIO_SOURCE_IN_CAR [intentIdentifiers addObject:@3]; @@ -402,6 +425,7 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger } else if ([identifier isEqualToString:INSaveProfileInCarIntentIdentifier]) { // IOSIntentIdentifier.SAVE_PROFILE_IN_CAR [intentIdentifiers addObject:@8]; +#endif } else if ([identifier isEqualToString:INStartWorkoutIntentIdentifier]) { // IOSIntentIdentifier.START_WORKOUT [intentIdentifiers addObject:@9]; @@ -417,18 +441,22 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger } else if ([identifier isEqualToString:INResumeWorkoutIntentIdentifier]) { // IOSIntentIdentifier.RESUME_WORKOUT [intentIdentifiers addObject:@13]; +#if !TARGET_OS_WATCH } else if ([identifier isEqualToString:INSetRadioStationIntentIdentifier]) { // IOSIntentIdentifier.SET_RADIO_STATION [intentIdentifiers addObject:@14]; +#endif } else if ([identifier isEqualToString:INSendMessageIntentIdentifier]) { // IOSIntentIdentifier.SEND_MESSAGE [intentIdentifiers addObject:@15]; } else if ([identifier isEqualToString:INSearchForMessagesIntentIdentifier]) { // IOSIntentIdentifier.SEARCH_FOR_MESSAGES [intentIdentifiers addObject:@16]; +#if !TARGET_OS_WATCH } else if ([identifier isEqualToString:INSetMessageAttributeIntentIdentifier]) { // IOSIntentIdentifier.SET_MESSAGE_ATTRIBUTE [intentIdentifiers addObject:@17]; +#endif } else if ([identifier isEqualToString:INSendPaymentIntentIdentifier]) { // IOSIntentIdentifier.SEND_PAYMENT [intentIdentifiers addObject:@18]; @@ -452,6 +480,7 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger [intentIdentifiers addObject:@24]; } } +#endif return intentIdentifiers; } @@ -460,16 +489,33 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger (NSArray *)identifiers { NSMutableArray *intentIdentifiers = [[NSMutableArray alloc] init]; +#if !TARGET_OS_TV && TARGET_OS_IPHONE for (NSNumber *identifier in identifiers) { - if ([identifier isEqualToNumber:@0]) { - // IOSIntentIdentifier.START_AUDIO_CALL - [intentIdentifiers addObject:INStartAudioCallIntentIdentifier]; - } else if ([identifier isEqualToNumber:@1]) { - // IOSIntentIdentifier.START_VIDEO_CALL - [intentIdentifiers addObject:INStartVideoCallIntentIdentifier]; - } else if ([identifier isEqualToNumber:@2]) { + + // Handle deprecation of separate audio and video symbols / new single "call" symbol + if (@available(ios 13, macOS 13, watchOS 6, *)) { + if ([identifier isEqualToNumber:@0]) { + // IOSIntentIdentifier.START_CALL + [intentIdentifiers addObject:INStartCallIntentIdentifier]; + } + } else { +// Guarding with '@available' does not remove deprecation warning unfortunately. Pragma it is +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if ([identifier isEqualToNumber:@0]) { + // IOSIntentIdentifier.START_AUDIO_CALL + [intentIdentifiers addObject:INStartAudioCallIntentIdentifier]; + } else if ([identifier isEqualToNumber:@1]) { + // IOSIntentIdentifier.START_VIDEO_CALL + [intentIdentifiers addObject:INStartVideoCallIntentIdentifier]; + } +#pragma clang diagnostic pop + } + + if ([identifier isEqualToNumber:@2]) { // IOSIntentIdentifier.SEARCH_CALL_HISTORY [intentIdentifiers addObject:INSearchCallHistoryIntentIdentifier]; +#if !TARGET_OS_WATCH } else if ([identifier isEqualToNumber:@3]) { // IOSIntentIdentifier.SET_AUDIO_SOURCE_IN_CAR [intentIdentifiers addObject:INSetAudioSourceInCarIntentIdentifier]; @@ -488,6 +534,7 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger } else if ([identifier isEqualToNumber:@8]) { // IOSIntentIdentifier.SAVE_PROFILE_IN_CAR [intentIdentifiers addObject:INSaveProfileInCarIntentIdentifier]; +#endif } else if ([identifier isEqualToNumber:@9]) { // IOSIntentIdentifier.START_WORKOUT [intentIdentifiers addObject:INStartWorkoutIntentIdentifier]; @@ -503,18 +550,22 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger } else if ([identifier isEqualToNumber:@13]) { // IOSIntentIdentifier.RESUME_WORKOUT [intentIdentifiers addObject:INResumeWorkoutIntentIdentifier]; +#if !TARGET_OS_WATCH } else if ([identifier isEqualToNumber:@14]) { // IOSIntentIdentifier.SET_RADIO_STATION [intentIdentifiers addObject:INSetRadioStationIntentIdentifier]; +#endif } else if ([identifier isEqualToNumber:@15]) { // IOSIntentIdentifier.SEND_MESSAGE [intentIdentifiers addObject:INSendMessageIntentIdentifier]; } else if ([identifier isEqualToNumber:@16]) { // IOSIntentIdentifier.SEARCH_FOR_MESSAGES [intentIdentifiers addObject:INSearchForMessagesIntentIdentifier]; +#if !TARGET_OS_WATCH } else if ([identifier isEqualToNumber:@17]) { // IOSIntentIdentifier.SET_MESSAGE_ATTRIBUTE [intentIdentifiers addObject:INSetMessageAttributeIntentIdentifier]; +#endif } else if ([identifier isEqualToNumber:@18]) { // IOSIntentIdentifier.SEND_PAYMENT [intentIdentifiers addObject:INSendPaymentIntentIdentifier]; @@ -538,6 +589,7 @@ + (UNNotificationTrigger *)intervalTriggerFromDictionary:(NSDictionary *)trigger [intentIdentifiers addObject:INGetRideStatusIntentIdentifier]; } } +#endif return intentIdentifiers; } diff --git a/ios/NotifeeCore/Private/NotifeeCoreUtil.h b/ios/NotifeeCore/Private/NotifeeCoreUtil.h index 5d933768..c11195a5 100644 --- a/ios/NotifeeCore/Private/NotifeeCoreUtil.h +++ b/ios/NotifeeCore/Private/NotifeeCoreUtil.h @@ -29,20 +29,22 @@ typedef NS_ENUM(NSInteger, NotifeeCoreTriggerType) { // Enum representing repeat frequency for TimestampTrigger typedef NS_ENUM(NSInteger, NotifeeCoreRepeatFrequency) { - NotifeeCoreRepeatFrequencyHourly = 0, - NotifeeCoreRepeatFrequencyDaily = 1, - NotifeeCoreRepeatFrequencyWeekly = 2 + NotifeeCoreRepeatFrequencyHourly = 0, + NotifeeCoreRepeatFrequencyDaily = 1, + NotifeeCoreRepeatFrequencyWeekly = 2 }; @interface NotifeeCoreUtil : NSObject + (NSNumber *)numberForUNNotificationSetting:(UNNotificationSetting)setting; +#if !TARGET_OS_TV + (NSMutableArray *)notificationActionsToDictionaryArray:(NSArray *)notificationActions; + (NSMutableArray *)notificationActionsFromDictionaryArray:(NSArray *)actionDictionaries; + (NSMutableArray *)notificationAttachmentsFromDictionaryArray:(NSArray *)attachmentDictionaries; +#endif + (NSMutableArray *)intentIdentifiersFromStringArray:(NSArray *)identifiers; diff --git a/ios/NotifeeCore/Public/NotifeeCore.h b/ios/NotifeeCore/Public/NotifeeCore.h index 4e8e7af7..5f663809 100644 --- a/ios/NotifeeCore/Public/NotifeeCore.h +++ b/ios/NotifeeCore/Public/NotifeeCore.h @@ -45,29 +45,31 @@ typedef NS_ENUM(NSInteger, NotifeeCoreEventType) { @protocol NotifeeCoreDelegate @optional -- (void) didReceiveNotifeeCoreEvent:(NSDictionary *_Nonnull)event; +- (void)didReceiveNotifeeCoreEvent:(NSDictionary *_Nonnull)event; @end @interface NotifeeCore : NSObject -+ (void)setCoreDelegate:(id )coreDelegate; ++ (void)setCoreDelegate:(id)coreDelegate; -+ (void)cancelNotification:(NSString *)notificationId withNotificationType: (NSInteger)notificationType withBlock:(notifeeMethodVoidBlock)block; ++ (void)cancelNotification:(NSString *)notificationId withNotificationType:(NSInteger)notificationType withBlock:(notifeeMethodVoidBlock)block; + (void)cancelAllNotifications:(NSInteger)notificationType withBlock:(notifeeMethodVoidBlock)block; + (void)displayNotification:(NSDictionary *)notification withBlock:(notifeeMethodVoidBlock)block; -+ (void)createTriggerNotification:(NSDictionary *)notification withTrigger: (NSDictionary *)trigger withBlock:(notifeeMethodVoidBlock)block; ++ (void)createTriggerNotification:(NSDictionary *)notification withTrigger:(NSDictionary *)trigger withBlock:(notifeeMethodVoidBlock)block; + (void)getTriggerNotificationIds:(notifeeMethodNSArrayBlock)block; + (void)requestPermission:(NSDictionary *)permissions withBlock:(notifeeMethodNSDictionaryBlock)block; +#if !TARGET_OS_TV + (void)getNotificationCategories:(notifeeMethodNSArrayBlock)block; + (void)setNotificationCategories:(NSArray *)categories withBlock:(notifeeMethodVoidBlock)block; +#endif + (void)getNotificationSettings:(notifeeMethodNSDictionaryBlock)block; diff --git a/ios/build_xcframework.sh b/ios/build_xcframework.sh index 3b4bb790..aadef369 100755 --- a/ios/build_xcframework.sh +++ b/ios/build_xcframework.sh @@ -6,9 +6,14 @@ FRAMEWORK_FOLDER_NAME="Notifee_XCFramework" FRAMEWORK_NAME="NotifeeCore" FRAMEWORK_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/${FRAMEWORK_NAME}.xcframework" BUILD_SCHEME="NotifeeCore" -SIMULATOR_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/simulator.xcarchive" +IOS_SIMULATOR_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/iOS_simulator.xcarchive" IOS_DEVICE_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/iOS.xcarchive" CATALYST_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/catalyst.xcarchive" +TVOS_SIMULATOR_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/tvOS_simulator.xcarchive" +TVOS_DEVICE_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/tvOS.xcarchive" +WATCHOS_SIMULATOR_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/watchOS_simulator.xcarchive" +WATCHOS_DEVICE_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/watchOS.xcarchive" +MACOS_DEVICE_ARCHIVE_PATH="${WORKING_DIR}/${FRAMEWORK_FOLDER_NAME}/macOS.xcarchive" OUTPUT_FOLDER=${WORKING_DIR}/../packages/react-native/ios/ rm -rf "${WORKING_DIR:?}/${FRAMEWORK_FOLDER_NAME}" @@ -18,7 +23,7 @@ echo "Created ${FRAMEWORK_FOLDER_NAME}" echo "Archiving for iOS Simulator" xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ - -destination="iOS Simulator" -archivePath "${SIMULATOR_ARCHIVE_PATH}" \ + -destination="iOS Simulator" -archivePath "${IOS_SIMULATOR_ARCHIVE_PATH}" \ -sdk iphonesimulator SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k echo "Archiving for iOS" @@ -27,22 +32,67 @@ xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_ -sdk iphoneos SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k # Do not be tempted to use "-sdk macosx" instead of the "destination" argument. It switches you to AppKit from UIKit +# Do not be tempted to use "-sdk iphoneos" either, or you end up with the incorrect fat framework binary file # https://developer.apple.com/forums/thread/120542?answerId=374124022#374124022 echo "Archiving for Mac Catalyst" xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ - -destination='platform=macOS,arch=x86_64,variant=Mac Catalyst' -archivePath "${CATALYST_ARCHIVE_PATH}" \ + -destination='platform=macOS,variant=Mac Catalyst' -archivePath "${CATALYST_ARCHIVE_PATH}" \ SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k +echo "Archiving for tvOS Simulator" +xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ + -destination="tvOS Simulator" -archivePath "${TVOS_SIMULATOR_ARCHIVE_PATH}" \ + -sdk appletvsimulator SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k + +echo "Archiving for tvOS" +xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ + -destination="tvOS" -archivePath "${TVOS_DEVICE_ARCHIVE_PATH}" \ + -sdk appletvos SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k + +echo "Archiving for watchOS Simulator" +xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ + -destination="watchOS Simulator" -archivePath "${WATCHOS_SIMULATOR_ARCHIVE_PATH}" \ + -sdk watchsimulator SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k + +echo "Archiving for watchOS" +xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ + -destination="watchOS" -archivePath "${WATCHOS_DEVICE_ARCHIVE_PATH}" \ + -sdk watchos SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty -k + +echo "Archiving for macOS" +\rm -fr ./build +xcodebuild archive -workspace "./${FRAMEWORK_NAME}.xcworkspace" -scheme ${BUILD_SCHEME} -configuration Release \ + -destination="platform=macOS" -archivePath "${MACOS_DEVICE_ARCHIVE_PATH}" \ + -sdk macosx -SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + -derivedDataPath ./build | xcpretty -k + +# macOS builds for some reason drop things in the DerivedData path, but do not copy them correctly to the framework. +mkdir -p "${MACOS_DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" +# This copies and flattens (removes the 'Versions/Current' symlink path components) from the archive output +# That is similar to the macOS Catalyst build flattening, but we do that post xcframework creation, here we do it before +cp -r ."/build/Build/Intermediates.noindex/ArchiveIntermediates/${FRAMEWORK_NAME}/IntermediateBuildFilesPath/UninstalledProducts/macosx/${FRAMEWORK_NAME}.framework/Versions/Current/" \ + "${MACOS_DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" + echo "Packaging archives into ${FRAMEWORK_NAME}.xcframework bundle" xcodebuild -create-xcframework \ - -framework "${SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ + -framework "${IOS_SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ -framework "${IOS_DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ -framework "${CATALYST_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ - -output "${FRAMEWORK_PATH}" | xcpretty + -framework "${TVOS_SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ + -framework "${TVOS_DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ + -framework "${WATCHOS_SIMULATOR_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ + -framework "${WATCHOS_DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ + -framework "${MACOS_DEVICE_ARCHIVE_PATH}/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \ + -output "${FRAMEWORK_PATH}" # | xcpretty -rm -rf "${SIMULATOR_ARCHIVE_PATH}" -rm -rf "${IOS_DEVICE_ARCHIVE_PATH}" -rm -rf "${CATALYST_ARCHIVE_PATH}" +# rm -rf "${IOS_SIMULATOR_ARCHIVE_PATH}" +# rm -rf "${IOS_DEVICE_ARCHIVE_PATH}" +# rm -rf "${CATALYST_ARCHIVE_PATH}" +# rm -rf "${TVOS_SIMULATOR_ARCHIVE_PATH}" +# rm -rf "${TVOS_DEVICE_ARCHIVE_PATH}" +# rm -rf "${WATCHOS_SIMULATOR_ARCHIVE_PATH}" +# rm -rf "${WATCHOS_DEVICE_ARCHIVE_PATH}" +# rm -rf "${MACOS_DEVICE_ARCHIVE_PATH}" # Catalyst framework directory structure flattening step 1: # - Copy the framework in a specific way: de-referencing symbolic links on purpose