The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

977 lines
42KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. struct PushNotificationsDelegateDetails
  21. {
  22. //==============================================================================
  23. using Action = PushNotifications::Settings::Action;
  24. using Category = PushNotifications::Settings::Category;
  25. static void* actionToNSAction (const Action& a)
  26. {
  27. if (@available (iOS 10, *))
  28. {
  29. if (a.style == Action::text)
  30. {
  31. return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
  32. title: juceStringToNS (a.title)
  33. options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)
  34. textInputButtonTitle: juceStringToNS (a.textInputButtonText)
  35. textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)];
  36. }
  37. return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
  38. title: juceStringToNS (a.title)
  39. options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)];
  40. }
  41. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  42. auto action = [[UIMutableUserNotificationAction alloc] init];
  43. action.identifier = juceStringToNS (a.identifier);
  44. action.title = juceStringToNS (a.title);
  45. action.behavior = a.style == Action::text ? UIUserNotificationActionBehaviorTextInput
  46. : UIUserNotificationActionBehaviorDefault;
  47. action.parameters = varObjectToNSDictionary (a.parameters);
  48. action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground
  49. : UIUserNotificationActivationModeForeground;
  50. action.destructive = (bool) a.destructive;
  51. [action autorelease];
  52. return action;
  53. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  54. }
  55. static void* categoryToNSCategory (const Category& c)
  56. {
  57. if (@available (iOS 10, *))
  58. {
  59. auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
  60. for (const auto& a : c.actions)
  61. {
  62. auto* action = (UNNotificationAction*) actionToNSAction (a);
  63. [actions addObject: action];
  64. }
  65. return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier)
  66. actions: actions
  67. intentIdentifiers: @[]
  68. options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0];
  69. }
  70. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  71. auto category = [[UIMutableUserNotificationCategory alloc] init];
  72. category.identifier = juceStringToNS (c.identifier);
  73. auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
  74. for (const auto& a : c.actions)
  75. {
  76. auto* action = (UIUserNotificationAction*) actionToNSAction (a);
  77. [actions addObject: action];
  78. }
  79. [category setActions: actions forContext: UIUserNotificationActionContextDefault];
  80. [category setActions: actions forContext: UIUserNotificationActionContextMinimal];
  81. [category autorelease];
  82. return category;
  83. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  84. }
  85. //==============================================================================
  86. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  87. static UILocalNotification* juceNotificationToUILocalNotification (const PushNotifications::Notification& n)
  88. {
  89. auto notification = [[UILocalNotification alloc] init];
  90. notification.alertTitle = juceStringToNS (n.title);
  91. notification.alertBody = juceStringToNS (n.body);
  92. notification.category = juceStringToNS (n.category);
  93. notification.applicationIconBadgeNumber = n.badgeNumber;
  94. auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
  95. notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
  96. notification.userInfo = varObjectToNSDictionary (n.properties);
  97. auto soundToPlayString = n.soundToPlay.toString (true);
  98. if (soundToPlayString == "default_os_sound")
  99. notification.soundName = UILocalNotificationDefaultSoundName;
  100. else if (soundToPlayString.isNotEmpty())
  101. notification.soundName = juceStringToNS (soundToPlayString);
  102. return notification;
  103. }
  104. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  105. static UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n)
  106. {
  107. // content
  108. auto content = [[UNMutableNotificationContent alloc] init];
  109. content.title = juceStringToNS (n.title);
  110. content.subtitle = juceStringToNS (n.subtitle);
  111. content.threadIdentifier = juceStringToNS (n.groupId);
  112. content.body = juceStringToNS (n.body);
  113. content.categoryIdentifier = juceStringToNS (n.category);
  114. content.badge = [NSNumber numberWithInt: n.badgeNumber];
  115. auto soundToPlayString = n.soundToPlay.toString (true);
  116. if (soundToPlayString == "default_os_sound")
  117. content.sound = [UNNotificationSound defaultSound];
  118. else if (soundToPlayString.isNotEmpty())
  119. content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)];
  120. auto* propsDict = (NSMutableDictionary*) varObjectToNSDictionary (n.properties);
  121. [propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")];
  122. content.userInfo = propsDict;
  123. // trigger
  124. UNTimeIntervalNotificationTrigger* trigger = nil;
  125. if (std::abs (n.triggerIntervalSec) >= 0.001)
  126. {
  127. BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60;
  128. trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat];
  129. }
  130. // request
  131. // each notification on iOS 10 needs to have an identifier, otherwise it will not show up
  132. jassert (n.identifier.isNotEmpty());
  133. UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier)
  134. content: content
  135. trigger: trigger];
  136. [content autorelease];
  137. return request;
  138. }
  139. static String getUserResponseFromNSDictionary (NSDictionary* dictionary)
  140. {
  141. if (dictionary == nil || dictionary.count == 0)
  142. return {};
  143. jassert (dictionary.count == 1);
  144. for (NSString* key in dictionary)
  145. {
  146. const auto keyString = nsStringToJuce (key);
  147. id value = dictionary[key];
  148. if ([value isKindOfClass: [NSString class]])
  149. return nsStringToJuce ((NSString*) value);
  150. }
  151. jassertfalse;
  152. return {};
  153. }
  154. //==============================================================================
  155. static var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
  156. {
  157. DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject();
  158. if (dictionaryVarObject == nullptr)
  159. return {};
  160. const auto& properties = dictionaryVarObject->getProperties();
  161. DynamicObject::Ptr propsVarObject = new DynamicObject();
  162. for (int i = 0; i < properties.size(); ++i)
  163. {
  164. auto propertyName = properties.getName (i).toString();
  165. if (propertyName == "aps")
  166. continue;
  167. propsVarObject->setProperty (propertyName, properties.getValueAt (i));
  168. }
  169. return var (propsVarObject.get());
  170. }
  171. //==============================================================================
  172. static double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t)
  173. {
  174. if (t != nil)
  175. {
  176. if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]])
  177. {
  178. auto* trigger = (UNTimeIntervalNotificationTrigger*) t;
  179. return trigger.timeInterval;
  180. }
  181. else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]])
  182. {
  183. auto* trigger = (UNCalendarNotificationTrigger*) t;
  184. NSDate* date = [trigger.dateComponents date];
  185. NSDate* dateNow = [NSDate date];
  186. return [dateNow timeIntervalSinceDate: date];
  187. }
  188. }
  189. return 0.;
  190. }
  191. static PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r)
  192. {
  193. PushNotifications::Notification n;
  194. n.identifier = nsStringToJuce (r.identifier);
  195. n.title = nsStringToJuce (r.content.title);
  196. n.subtitle = nsStringToJuce (r.content.subtitle);
  197. n.body = nsStringToJuce (r.content.body);
  198. n.groupId = nsStringToJuce (r.content.threadIdentifier);
  199. n.category = nsStringToJuce (r.content.categoryIdentifier);
  200. n.badgeNumber = r.content.badge.intValue;
  201. auto userInfoVar = nsDictionaryToVar (r.content.userInfo);
  202. if (auto* object = userInfoVar.getDynamicObject())
  203. {
  204. static const Identifier soundName ("com.juce.soundName");
  205. n.soundToPlay = URL (object->getProperty (soundName).toString());
  206. object->removeProperty (soundName);
  207. }
  208. n.properties = userInfoVar;
  209. n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger);
  210. n.repeat = r.trigger != nil && r.trigger.repeats;
  211. return n;
  212. }
  213. static PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n)
  214. {
  215. return unNotificationRequestToJuceNotification (n.request);
  216. }
  217. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  218. static PushNotifications::Notification uiLocalNotificationToJuceNotification (UILocalNotification* n)
  219. {
  220. PushNotifications::Notification notif;
  221. notif.title = nsStringToJuce (n.alertTitle);
  222. notif.body = nsStringToJuce (n.alertBody);
  223. if (n.fireDate != nil)
  224. {
  225. NSDate* dateNow = [NSDate date];
  226. NSDate* fireDate = n.fireDate;
  227. notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: fireDate];
  228. }
  229. notif.soundToPlay = URL (nsStringToJuce (n.soundName));
  230. notif.badgeNumber = (int) n.applicationIconBadgeNumber;
  231. notif.category = nsStringToJuce (n.category);
  232. notif.properties = nsDictionaryToVar (n.userInfo);
  233. return notif;
  234. }
  235. static Action uiUserNotificationActionToAction (UIUserNotificationAction* a)
  236. {
  237. Action action;
  238. action.identifier = nsStringToJuce (a.identifier);
  239. action.title = nsStringToJuce (a.title);
  240. action.style = a.behavior == UIUserNotificationActionBehaviorTextInput
  241. ? Action::text
  242. : Action::button;
  243. action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground;
  244. action.destructive = a.destructive;
  245. action.parameters = nsDictionaryToVar (a.parameters);
  246. return action;
  247. }
  248. static Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c)
  249. {
  250. Category category;
  251. category.identifier = nsStringToJuce (c.identifier);
  252. for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault])
  253. category.actions.add (uiUserNotificationActionToAction (a));
  254. return category;
  255. }
  256. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  257. static Action unNotificationActionToAction (UNNotificationAction* a)
  258. {
  259. Action action;
  260. action.identifier = nsStringToJuce (a.identifier);
  261. action.title = nsStringToJuce (a.title);
  262. action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground);
  263. action.destructive = a.options & UNNotificationActionOptionDestructive;
  264. if ([a isKindOfClass: [UNTextInputNotificationAction class]])
  265. {
  266. auto* textAction = (UNTextInputNotificationAction*)a;
  267. action.style = Action::text;
  268. action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle);
  269. action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder);
  270. }
  271. else
  272. {
  273. action.style = Action::button;
  274. }
  275. return action;
  276. }
  277. static Category unNotificationCategoryToCategory (UNNotificationCategory* c)
  278. {
  279. Category category;
  280. category.identifier = nsStringToJuce (c.identifier);
  281. category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction;
  282. for (UNNotificationAction* a in c.actions)
  283. category.actions.add (unNotificationActionToAction (a));
  284. return category;
  285. }
  286. static PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
  287. {
  288. const var dictionaryVar = nsDictionaryToVar (dictionary);
  289. const var apsVar = dictionaryVar.getProperty ("aps", {});
  290. if (! apsVar.isObject())
  291. return {};
  292. var alertVar = apsVar.getProperty ("alert", {});
  293. const var titleVar = alertVar.getProperty ("title", {});
  294. const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
  295. const var categoryVar = apsVar.getProperty ("category", {});
  296. const var soundVar = apsVar.getProperty ("sound", {});
  297. const var badgeVar = apsVar.getProperty ("badge", {});
  298. const var threadIdVar = apsVar.getProperty ("thread-id", {});
  299. PushNotifications::Notification notification;
  300. notification.title = titleVar .toString();
  301. notification.body = bodyVar .toString();
  302. notification.groupId = threadIdVar.toString();
  303. notification.category = categoryVar.toString();
  304. notification.soundToPlay = URL (soundVar.toString());
  305. notification.badgeNumber = (int) badgeVar;
  306. notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
  307. return notification;
  308. }
  309. private:
  310. ~PushNotificationsDelegateDetails() = delete;
  311. };
  312. //==============================================================================
  313. struct PushNotificationsDelegate
  314. {
  315. PushNotificationsDelegate()
  316. {
  317. Class::setThis (delegate.get(), this);
  318. auto appDelegate = [[UIApplication sharedApplication] delegate];
  319. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  320. if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegateToUse:)])
  321. [appDelegate performSelector: @selector (setPushNotificationsDelegateToUse:) withObject: delegate.get()];
  322. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  323. }
  324. virtual ~PushNotificationsDelegate() = default;
  325. virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
  326. virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
  327. virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
  328. virtual void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  329. void (^completionHandler)(UIBackgroundFetchResult result)) = 0;
  330. virtual void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  331. NSDictionary* userInfo,
  332. NSDictionary* responseInfo,
  333. void (^completionHandler)()) = 0;
  334. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  335. virtual void didRegisterUserNotificationSettings (UIUserNotificationSettings* notificationSettings) = 0;
  336. virtual void didReceiveLocalNotification (UILocalNotification* notification) = 0;
  337. virtual void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  338. UILocalNotification* notification,
  339. void (^completionHandler)()) = 0;
  340. virtual void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  341. UILocalNotification* notification,
  342. NSDictionary* responseInfo,
  343. void (^completionHandler)()) = 0;
  344. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  345. virtual void willPresentNotificationWithCompletionHandler (UNNotification* notification,
  346. void (^completionHandler)(UNNotificationPresentationOptions options)) = 0;
  347. virtual void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  348. void (^completionHandler)()) = 0;
  349. protected:
  350. NSUniquePtr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate { [getClass().createInstance() init] };
  351. private:
  352. //==============================================================================
  353. struct Class : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
  354. {
  355. Class() : ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
  356. {
  357. addIvar<PushNotificationsDelegate*> ("self");
  358. addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* deviceToken)
  359. {
  360. getThis (self).registeredForRemoteNotifications (deviceToken);
  361. });
  362. addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, UIApplication*, NSError* error)
  363. {
  364. getThis (self).failedToRegisterForRemoteNotifications (error);
  365. });
  366. addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo)
  367. {
  368. getThis (self).didReceiveRemoteNotification (userInfo);
  369. });
  370. addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo, void (^completionHandler)(UIBackgroundFetchResult result))
  371. {
  372. getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler);
  373. });
  374. addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, NSDictionary* userInfo, NSDictionary* responseInfo, void (^completionHandler)())
  375. {
  376. getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler);
  377. });
  378. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  379. addMethod (@selector (application:didRegisterUserNotificationSettings:), [] (id self, SEL, UIApplication*, UIUserNotificationSettings* settings)
  380. {
  381. getThis (self).didRegisterUserNotificationSettings (settings);
  382. });
  383. addMethod (@selector (application:didReceiveLocalNotification:), [] (id self, SEL, UIApplication*, UILocalNotification* notification)
  384. {
  385. getThis (self).didReceiveLocalNotification (notification);
  386. });
  387. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, void (^completionHandler)())
  388. {
  389. getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler);
  390. });
  391. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, NSDictionary* responseInfo, void (^completionHandler)())
  392. {
  393. getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler);
  394. });
  395. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  396. addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotification* notification, void (^completionHandler)(UNNotificationPresentationOptions options))
  397. {
  398. getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler);
  399. });
  400. addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotificationResponse* response, void (^completionHandler)())
  401. {
  402. getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler);
  403. });
  404. registerClass();
  405. }
  406. //==============================================================================
  407. static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
  408. static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
  409. };
  410. //==============================================================================
  411. static Class& getClass()
  412. {
  413. static Class c;
  414. return c;
  415. }
  416. };
  417. //==============================================================================
  418. bool PushNotifications::Notification::isValid() const noexcept
  419. {
  420. if (@available (iOS 10, *))
  421. return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
  422. return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();
  423. }
  424. //==============================================================================
  425. struct PushNotifications::Pimpl : private PushNotificationsDelegate
  426. {
  427. Pimpl (PushNotifications& p)
  428. : owner (p)
  429. {
  430. }
  431. void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
  432. {
  433. settings = settingsToUse;
  434. auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
  435. if (@available (iOS 10, *))
  436. {
  437. for (const auto& c : settings.categories)
  438. {
  439. auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
  440. [categories addObject: category];
  441. }
  442. UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
  443. | (bool)settings.allowSound << 1
  444. | (bool)settings.allowAlert << 2);
  445. [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
  446. [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
  447. completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
  448. {
  449. requestSettingsUsed();
  450. }];
  451. }
  452. else
  453. {
  454. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  455. for (const auto& c : settings.categories)
  456. {
  457. auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
  458. [categories addObject: category];
  459. }
  460. UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0
  461. | (bool)settings.allowSound << 1
  462. | (bool)settings.allowAlert << 2);
  463. UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories];
  464. [[UIApplication sharedApplication] registerUserNotificationSettings: s];
  465. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  466. }
  467. [[UIApplication sharedApplication] registerForRemoteNotifications];
  468. }
  469. void requestSettingsUsed()
  470. {
  471. if (@available (iOS 10, *))
  472. {
  473. [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
  474. ^(UNNotificationSettings* s)
  475. {
  476. [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
  477. ^(NSSet<UNNotificationCategory*>* categories)
  478. {
  479. settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
  480. settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
  481. settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;
  482. for (UNNotificationCategory* c in categories)
  483. settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));
  484. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  485. }
  486. ];
  487. }];
  488. }
  489. else
  490. {
  491. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  492. UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings;
  493. settings.allowBadge = s.types & UIUserNotificationTypeBadge;
  494. settings.allowSound = s.types & UIUserNotificationTypeSound;
  495. settings.allowAlert = s.types & UIUserNotificationTypeAlert;
  496. for (UIUserNotificationCategory *c in s.categories)
  497. settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c));
  498. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  499. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  500. }
  501. }
  502. bool areNotificationsEnabled() const { return true; }
  503. void sendLocalNotification (const Notification& n)
  504. {
  505. if (@available (iOS 10, *))
  506. {
  507. UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
  508. [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
  509. withCompletionHandler: ^(NSError* error)
  510. {
  511. jassert (error == nil);
  512. if (error != nil)
  513. NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
  514. }];
  515. }
  516. else
  517. {
  518. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  519. auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);
  520. [[UIApplication sharedApplication] scheduleLocalNotification: notification];
  521. [notification release];
  522. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  523. }
  524. }
  525. void getDeliveredNotifications() const
  526. {
  527. if (@available (iOS 10, *))
  528. {
  529. [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
  530. ^(NSArray<UNNotification*>* notifications)
  531. {
  532. Array<PushNotifications::Notification> notifs;
  533. for (UNNotification* n in notifications)
  534. notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));
  535. owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
  536. }];
  537. }
  538. else
  539. {
  540. // Not supported on this platform
  541. jassertfalse;
  542. owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
  543. }
  544. }
  545. void removeAllDeliveredNotifications()
  546. {
  547. if (@available (iOS 10, *))
  548. {
  549. [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
  550. }
  551. else
  552. {
  553. // Not supported on this platform
  554. jassertfalse;
  555. }
  556. }
  557. void removeDeliveredNotification (const String& identifier)
  558. {
  559. if (@available (iOS 10, *))
  560. {
  561. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  562. [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
  563. }
  564. else
  565. {
  566. ignoreUnused (identifier);
  567. // Not supported on this platform
  568. jassertfalse;
  569. }
  570. }
  571. void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
  572. {
  573. ignoreUnused (groups, channels);
  574. }
  575. void getPendingLocalNotifications() const
  576. {
  577. if (@available (iOS 10, *))
  578. {
  579. [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
  580. ^(NSArray<UNNotificationRequest*>* requests)
  581. {
  582. Array<PushNotifications::Notification> notifs;
  583. for (UNNotificationRequest* r : requests)
  584. notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));
  585. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  586. }
  587. ];
  588. }
  589. else
  590. {
  591. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  592. Array<PushNotifications::Notification> notifs;
  593. for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
  594. notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));
  595. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  596. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  597. }
  598. }
  599. void removePendingLocalNotification (const String& identifier)
  600. {
  601. if (@available (iOS 10, *))
  602. {
  603. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  604. [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
  605. }
  606. else
  607. {
  608. // Not supported on this platform
  609. jassertfalse;
  610. }
  611. }
  612. void removeAllPendingLocalNotifications()
  613. {
  614. if (@available (iOS 10, *))
  615. {
  616. [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
  617. }
  618. else
  619. {
  620. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  621. [[UIApplication sharedApplication] cancelAllLocalNotifications];
  622. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  623. }
  624. }
  625. String getDeviceToken()
  626. {
  627. // You need to call requestPermissionsWithSettings() first.
  628. jassert (initialised);
  629. return deviceToken;
  630. }
  631. //==============================================================================
  632. //PushNotificationsDelegate
  633. void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
  634. {
  635. deviceToken = [deviceTokenToUse]() -> String
  636. {
  637. auto length = deviceTokenToUse.length;
  638. if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
  639. {
  640. NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
  641. for (NSUInteger i = 0; i < length; ++i)
  642. [hexString appendFormat:@"%02x", buffer[i]];
  643. return nsStringToJuce ([hexString copy]);
  644. }
  645. return {};
  646. }();
  647. initialised = true;
  648. owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
  649. }
  650. void failedToRegisterForRemoteNotifications (NSError* error) override
  651. {
  652. ignoreUnused (error);
  653. deviceToken.clear();
  654. }
  655. void didReceiveRemoteNotification (NSDictionary* userInfo) override
  656. {
  657. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  658. owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
  659. }
  660. void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  661. void (^completionHandler)(UIBackgroundFetchResult result)) override
  662. {
  663. didReceiveRemoteNotification (userInfo);
  664. completionHandler (UIBackgroundFetchResultNewData);
  665. }
  666. void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  667. NSDictionary* userInfo,
  668. NSDictionary* responseInfo,
  669. void (^completionHandler)()) override
  670. {
  671. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  672. auto actionString = nsStringToJuce (actionIdentifier);
  673. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  674. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); });
  675. completionHandler();
  676. }
  677. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  678. void didRegisterUserNotificationSettings (UIUserNotificationSettings*) override
  679. {
  680. requestSettingsUsed();
  681. }
  682. void didReceiveLocalNotification (UILocalNotification* notification) override
  683. {
  684. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  685. owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
  686. }
  687. void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  688. UILocalNotification* notification,
  689. void (^completionHandler)()) override
  690. {
  691. handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
  692. notification,
  693. nil,
  694. completionHandler);
  695. }
  696. void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  697. UILocalNotification* notification,
  698. NSDictionary* responseInfo,
  699. void (^completionHandler)()) override
  700. {
  701. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  702. auto actionString = nsStringToJuce (actionIdentifier);
  703. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  704. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); });
  705. completionHandler();
  706. }
  707. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  708. void willPresentNotificationWithCompletionHandler (UNNotification* notification,
  709. void (^completionHandler)(UNNotificationPresentationOptions options)) override
  710. {
  711. NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
  712. | (int)settings.allowSound << 1
  713. | (int)settings.allowAlert << 2);
  714. ignoreUnused (notification);
  715. completionHandler (options);
  716. }
  717. void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  718. void (^completionHandler)()) override
  719. {
  720. const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];
  721. auto actionString = nsStringToJuce (response.actionIdentifier);
  722. if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
  723. actionString.clear();
  724. else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
  725. actionString = "com.juce.NotificationDeleted";
  726. auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);
  727. String responseString;
  728. if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
  729. {
  730. UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
  731. responseString = nsStringToJuce (textResponse.userText);
  732. }
  733. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); });
  734. completionHandler();
  735. }
  736. void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
  737. void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
  738. void sendUpstreamMessage (const String& serverSenderId,
  739. const String& collapseKey,
  740. const String& messageId,
  741. const String& messageType,
  742. int timeToLive,
  743. const StringPairArray& additionalData)
  744. {
  745. ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
  746. ignoreUnused (timeToLive, additionalData);
  747. }
  748. private:
  749. PushNotifications& owner;
  750. bool initialised = false;
  751. String deviceToken;
  752. PushNotifications::Settings settings;
  753. };
  754. } // namespace juce