/* ============================================================================== This file is part of the JUCE framework. Copyright (c) Raw Material Software Limited JUCE is an open source framework subject to commercial or open source licensing. By downloading, installing, or using the JUCE framework, or combining the JUCE framework with any other source code, object code, content or any other copyrightable work, you agree to the terms of the JUCE End User Licence Agreement, and all incorporated terms including the JUCE Privacy Policy and the JUCE Website Terms of Service, as applicable, which will bind you. If you do not agree to the terms of these agreements, we will not license the JUCE framework to you, and you must discontinue the installation or download process and cease use of the JUCE framework. JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ JUCE Privacy Policy: https://juce.com/juce-privacy-policy JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ Or: You may also use this code under the terms of the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.en.html THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { struct PushNotificationsDelegateDetails { //============================================================================== using Action = PushNotifications::Settings::Action; using Category = PushNotifications::Settings::Category; static void* actionToNSAction (const Action& a) { if (@available (iOS 10, *)) { if (a.style == Action::text) { return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier) title: juceStringToNS (a.title) options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2) textInputButtonTitle: juceStringToNS (a.textInputButtonText) textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)]; } return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier) title: juceStringToNS (a.title) options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)]; } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") auto action = [[UIMutableUserNotificationAction alloc] init]; action.identifier = juceStringToNS (a.identifier); action.title = juceStringToNS (a.title); action.behavior = a.style == Action::text ? UIUserNotificationActionBehaviorTextInput : UIUserNotificationActionBehaviorDefault; action.parameters = varToNSDictionary (a.parameters); action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground : UIUserNotificationActivationModeForeground; action.destructive = (bool) a.destructive; [action autorelease]; return action; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } static void* categoryToNSCategory (const Category& c) { if (@available (iOS 10, *)) { auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()]; for (const auto& a : c.actions) { auto* action = (UNNotificationAction*) actionToNSAction (a); [actions addObject: action]; } return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier) actions: actions intentIdentifiers: @[] options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0]; } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") auto category = [[UIMutableUserNotificationCategory alloc] init]; category.identifier = juceStringToNS (c.identifier); auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()]; for (const auto& a : c.actions) { auto* action = (UIUserNotificationAction*) actionToNSAction (a); [actions addObject: action]; } [category setActions: actions forContext: UIUserNotificationActionContextDefault]; [category setActions: actions forContext: UIUserNotificationActionContextMinimal]; [category autorelease]; return category; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } //============================================================================== JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") static UILocalNotification* juceNotificationToUILocalNotification (const PushNotifications::Notification& n) { auto notification = [[UILocalNotification alloc] init]; notification.alertTitle = juceStringToNS (n.title); notification.alertBody = juceStringToNS (n.body); notification.category = juceStringToNS (n.category); notification.applicationIconBadgeNumber = n.badgeNumber; auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec); notification.fireDate = [NSDate dateWithTimeIntervalSince1970: (double) triggerTime.toMilliseconds() / 1000.0]; notification.userInfo = varToNSDictionary (n.properties); auto soundToPlayString = n.soundToPlay.toString (true); if (soundToPlayString == "default_os_sound") notification.soundName = UILocalNotificationDefaultSoundName; else if (soundToPlayString.isNotEmpty()) notification.soundName = juceStringToNS (soundToPlayString); return notification; } JUCE_END_IGNORE_WARNINGS_GCC_LIKE static UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n) { // content auto content = [[UNMutableNotificationContent alloc] init]; content.title = juceStringToNS (n.title); content.subtitle = juceStringToNS (n.subtitle); content.threadIdentifier = juceStringToNS (n.groupId); content.body = juceStringToNS (n.body); content.categoryIdentifier = juceStringToNS (n.category); content.badge = [NSNumber numberWithInt: n.badgeNumber]; auto soundToPlayString = n.soundToPlay.toString (true); if (soundToPlayString == "default_os_sound") content.sound = [UNNotificationSound defaultSound]; else if (soundToPlayString.isNotEmpty()) content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)]; auto* propsDict = (NSMutableDictionary*) varToNSDictionary (n.properties); [propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")]; content.userInfo = propsDict; // trigger UNTimeIntervalNotificationTrigger* trigger = nil; if (std::abs (n.triggerIntervalSec) >= 0.001) { BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60; trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat]; } // request // each notification on iOS 10 needs to have an identifier, otherwise it will not show up jassert (n.identifier.isNotEmpty()); UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier) content: content trigger: trigger]; [content autorelease]; return request; } static String getUserResponseFromNSDictionary (NSDictionary* dictionary) { if (dictionary == nil || dictionary.count == 0) return {}; jassert (dictionary.count == 1); for (NSString* key in dictionary) { const auto keyString = nsStringToJuce (key); id value = dictionary[key]; if ([value isKindOfClass: [NSString class]]) return nsStringToJuce ((NSString*) value); } jassertfalse; return {}; } //============================================================================== static var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar) { DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject(); if (dictionaryVarObject == nullptr) return {}; const auto& properties = dictionaryVarObject->getProperties(); DynamicObject::Ptr propsVarObject = new DynamicObject(); for (int i = 0; i < properties.size(); ++i) { auto propertyName = properties.getName (i).toString(); if (propertyName == "aps") continue; propsVarObject->setProperty (propertyName, properties.getValueAt (i)); } return var (propsVarObject.get()); } //============================================================================== static double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t) { if (t != nil) { if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]]) { auto* trigger = (UNTimeIntervalNotificationTrigger*) t; return trigger.timeInterval; } else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]]) { auto* trigger = (UNCalendarNotificationTrigger*) t; NSDate* date = [trigger.dateComponents date]; NSDate* dateNow = [NSDate date]; return [dateNow timeIntervalSinceDate: date]; } } return 0.; } static PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r) { PushNotifications::Notification n; n.identifier = nsStringToJuce (r.identifier); n.title = nsStringToJuce (r.content.title); n.subtitle = nsStringToJuce (r.content.subtitle); n.body = nsStringToJuce (r.content.body); n.groupId = nsStringToJuce (r.content.threadIdentifier); n.category = nsStringToJuce (r.content.categoryIdentifier); n.badgeNumber = r.content.badge.intValue; auto userInfoVar = nsDictionaryToVar (r.content.userInfo); if (auto* object = userInfoVar.getDynamicObject()) { static const Identifier soundName ("com.juce.soundName"); n.soundToPlay = URL (object->getProperty (soundName).toString()); object->removeProperty (soundName); } n.properties = userInfoVar; n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger); n.repeat = r.trigger != nil && r.trigger.repeats; return n; } static PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n) { return unNotificationRequestToJuceNotification (n.request); } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") static PushNotifications::Notification uiLocalNotificationToJuceNotification (UILocalNotification* n) { PushNotifications::Notification notif; notif.title = nsStringToJuce (n.alertTitle); notif.body = nsStringToJuce (n.alertBody); if (n.fireDate != nil) { NSDate* dateNow = [NSDate date]; NSDate* fireDate = n.fireDate; notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: fireDate]; } notif.soundToPlay = URL (nsStringToJuce (n.soundName)); notif.badgeNumber = (int) n.applicationIconBadgeNumber; notif.category = nsStringToJuce (n.category); notif.properties = nsDictionaryToVar (n.userInfo); return notif; } static Action uiUserNotificationActionToAction (UIUserNotificationAction* a) { Action action; action.identifier = nsStringToJuce (a.identifier); action.title = nsStringToJuce (a.title); action.style = a.behavior == UIUserNotificationActionBehaviorTextInput ? Action::text : Action::button; action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground; action.destructive = a.destructive; action.parameters = nsDictionaryToVar (a.parameters); return action; } static Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c) { Category category; category.identifier = nsStringToJuce (c.identifier); for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault]) category.actions.add (uiUserNotificationActionToAction (a)); return category; } JUCE_END_IGNORE_WARNINGS_GCC_LIKE static Action unNotificationActionToAction (UNNotificationAction* a) { Action action; action.identifier = nsStringToJuce (a.identifier); action.title = nsStringToJuce (a.title); action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground); action.destructive = a.options & UNNotificationActionOptionDestructive; if ([a isKindOfClass: [UNTextInputNotificationAction class]]) { auto* textAction = (UNTextInputNotificationAction*)a; action.style = Action::text; action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle); action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder); } else { action.style = Action::button; } return action; } static Category unNotificationCategoryToCategory (UNNotificationCategory* c) { Category category; category.identifier = nsStringToJuce (c.identifier); category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction; for (UNNotificationAction* a in c.actions) category.actions.add (unNotificationActionToAction (a)); return category; } static PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary) { const var dictionaryVar = nsDictionaryToVar (dictionary); const var apsVar = dictionaryVar.getProperty ("aps", {}); if (! apsVar.isObject()) return {}; var alertVar = apsVar.getProperty ("alert", {}); const var titleVar = alertVar.getProperty ("title", {}); const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar; const var categoryVar = apsVar.getProperty ("category", {}); const var soundVar = apsVar.getProperty ("sound", {}); const var badgeVar = apsVar.getProperty ("badge", {}); const var threadIdVar = apsVar.getProperty ("thread-id", {}); PushNotifications::Notification notification; notification.title = titleVar .toString(); notification.body = bodyVar .toString(); notification.groupId = threadIdVar.toString(); notification.category = categoryVar.toString(); notification.soundToPlay = URL (soundVar.toString()); notification.badgeNumber = (int) badgeVar; notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar); return notification; } private: ~PushNotificationsDelegateDetails() = delete; }; //============================================================================== bool PushNotifications::Notification::isValid() const noexcept { if (@available (iOS 10, *)) return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty(); return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty(); } //============================================================================== struct PushNotifications::Pimpl { Pimpl (PushNotifications& p) : owner (p) { Class::setThis (delegate.get(), this); auto appDelegate = [[UIApplication sharedApplication] delegate]; JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegateToUse:)]) [appDelegate performSelector: @selector (setPushNotificationsDelegateToUse:) withObject: delegate.get()]; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse) { settings = settingsToUse; auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()]; if (@available (iOS 10, *)) { for (const auto& c : settings.categories) { auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c); [categories addObject: category]; } UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0 | (bool)settings.allowSound << 1 | (bool)settings.allowAlert << 2); [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories]; [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions completionHandler: ^(BOOL /*granted*/, NSError* /*error*/) { requestSettingsUsed(); }]; } else { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") for (const auto& c : settings.categories) { auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c); [categories addObject: category]; } UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0 | (bool)settings.allowSound << 1 | (bool)settings.allowAlert << 2); UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories]; [[UIApplication sharedApplication] registerUserNotificationSettings: s]; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } [[UIApplication sharedApplication] registerForRemoteNotifications]; } void requestSettingsUsed() { if (@available (iOS 10, *)) { [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler: ^(UNNotificationSettings* s) { [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler: ^(NSSet* categories) { settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled; settings.allowSound = s.soundSetting == UNNotificationSettingEnabled; settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled; for (UNNotificationCategory* c in categories) settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c)); owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); }); } ]; }]; } else { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings; settings.allowBadge = s.types & UIUserNotificationTypeBadge; settings.allowSound = s.types & UIUserNotificationTypeSound; settings.allowAlert = s.types & UIUserNotificationTypeAlert; for (UIUserNotificationCategory *c in s.categories) settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c)); owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); }); JUCE_END_IGNORE_WARNINGS_GCC_LIKE } } bool areNotificationsEnabled() const { return true; } void sendLocalNotification (const Notification& n) { if (@available (iOS 10, *)) { UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n); [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request withCompletionHandler: ^(NSError* error) { jassert (error == nil); if (error != nil) NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error); }]; } else { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n); [[UIApplication sharedApplication] scheduleLocalNotification: notification]; [notification release]; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } } void getDeliveredNotifications() const { if (@available (iOS 10, *)) { [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler: ^(NSArray* notifications) { Array notifs; for (UNNotification* n in notifications) notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n)); owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); }); }]; } else { // Not supported on this platform jassertfalse; owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); }); } } void removeAllDeliveredNotifications() { if (@available (iOS 10, *)) { [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications]; } else { // Not supported on this platform jassertfalse; } } void removeDeliveredNotification ([[maybe_unused]] const String& identifier) { if (@available (iOS 10, *)) { NSArray* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)]; [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers]; } else { // Not supported on this platform jassertfalse; } } void setupChannels ([[maybe_unused]] const Array& groups, [[maybe_unused]] const Array& channels) { } void getPendingLocalNotifications() const { if (@available (iOS 10, *)) { [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler: ^(NSArray* requests) { Array notifs; for (UNNotificationRequest* r : requests) notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r)); owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); }); } ]; } else { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") Array notifs; for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications) notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n)); owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); }); JUCE_END_IGNORE_WARNINGS_GCC_LIKE } } void removePendingLocalNotification (const String& identifier) { if (@available (iOS 10, *)) { NSArray* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)]; [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers]; } else { // Not supported on this platform jassertfalse; } } void removeAllPendingLocalNotifications() { if (@available (iOS 10, *)) { [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests]; } else { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") [[UIApplication sharedApplication] cancelAllLocalNotifications]; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } } String getDeviceToken() { // You need to call requestPermissionsWithSettings() first. jassert (initialised); return deviceToken; } void subscribeToTopic ([[maybe_unused]] const String& topic) {} void unsubscribeFromTopic ([[maybe_unused]] const String& topic) {} void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId, [[maybe_unused]] const String& collapseKey, [[maybe_unused]] const String& messageId, [[maybe_unused]] const String& messageType, [[maybe_unused]] int timeToLive, [[maybe_unused]] const StringPairArray& additionalData) { } private: //============================================================================== void registeredForRemoteNotifications (NSData* deviceTokenToUse) { deviceToken = [deviceTokenToUse]() -> String { auto length = deviceTokenToUse.length; if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes) { NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)]; for (NSUInteger i = 0; i < length; ++i) [hexString appendFormat:@"%02x", buffer[i]]; return nsStringToJuce ([hexString copy]); } return {}; }(); initialised = true; owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); }); } void failedToRegisterForRemoteNotifications ([[maybe_unused]] NSError* error) { deviceToken.clear(); } void didReceiveRemoteNotification (NSDictionary* userInfo) { auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo); owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); }); } void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo, void (^completionHandler)(UIBackgroundFetchResult result)) { didReceiveRemoteNotification (userInfo); completionHandler (UIBackgroundFetchResultNewData); } void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier, NSDictionary* userInfo, NSDictionary* responseInfo, void (^completionHandler)()) { auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo); auto actionString = nsStringToJuce (actionIdentifier); auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo); owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); }); completionHandler(); } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") void didRegisterUserNotificationSettings (UIUserNotificationSettings*) { requestSettingsUsed(); } void didReceiveLocalNotification (UILocalNotification* notification) { auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification); owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); }); } void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier, UILocalNotification* notification, void (^completionHandler)()) { handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, nil, completionHandler); } void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier, UILocalNotification* notification, NSDictionary* responseInfo, void (^completionHandler)()) { auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification); auto actionString = nsStringToJuce (actionIdentifier); auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo); owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); }); completionHandler(); } JUCE_END_IGNORE_WARNINGS_GCC_LIKE void willPresentNotificationWithCompletionHandler ([[maybe_unused]] UNNotification* notification, void (^completionHandler)(UNNotificationPresentationOptions options)) { NSUInteger options = NSUInteger ((int)settings.allowBadge << 0 | (int)settings.allowSound << 1 | (int)settings.allowAlert << 2); completionHandler (options); } void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response, void (^completionHandler)()) { const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]]; auto actionString = nsStringToJuce (response.actionIdentifier); if (actionString == "com.apple.UNNotificationDefaultActionIdentifier") actionString.clear(); else if (actionString == "com.apple.UNNotificationDismissActionIdentifier") actionString = "com.juce.NotificationDeleted"; auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification); String responseString; if ([response isKindOfClass: [UNTextInputNotificationResponse class]]) { UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response; responseString = nsStringToJuce (textResponse.userText); } owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); }); completionHandler(); } //============================================================================== struct Class final : public ObjCClass> { Class() : ObjCClass ("JucePushNotificationsDelegate_") { addIvar ("self"); addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data) { getThis (self).registeredForRemoteNotifications (data); }); addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, UIApplication*, NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }); addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }); addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo, void (^completionHandler)(UIBackgroundFetchResult result)) { getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler); }); addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, NSDictionary* userInfo, NSDictionary* responseInfo, void (^completionHandler)()) { getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler); }); JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") addMethod (@selector (application:didRegisterUserNotificationSettings:), [] (id self, SEL, UIApplication*, UIUserNotificationSettings* settingsToUse) { getThis (self).didRegisterUserNotificationSettings (settingsToUse); }); addMethod (@selector (application:didReceiveLocalNotification:), [] (id self, SEL, UIApplication*, UILocalNotification* notification) { getThis (self).didReceiveLocalNotification (notification); }); addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, void (^completionHandler)()) { getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler); }); addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, NSDictionary* responseInfo, void (^completionHandler)()) { getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler); }); JUCE_END_IGNORE_WARNINGS_GCC_LIKE addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotification* notification, void (^completionHandler)(UNNotificationPresentationOptions options)) { getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler); }); addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotificationResponse* response, void (^completionHandler)()) { getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler); }); registerClass(); } //============================================================================== static Pimpl& getThis (id self) { return *getIvar (self, "self"); } static void setThis (id self, Pimpl* d) { object_setInstanceVariable (self, "self", d); } }; //============================================================================== static Class& getClass() { static Class c; return c; } NSUniquePtr> delegate { [getClass().createInstance() init] }; PushNotifications& owner; bool initialised = false; String deviceToken; PushNotifications::Settings settings; }; } // namespace juce