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.

920 lines
39KB

  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 = varToNSDictionary (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: (double) triggerTime.toMilliseconds() / 1000.0];
  96. notification.userInfo = varToNSDictionary (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*) varToNSDictionary (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. bool PushNotifications::Notification::isValid() const noexcept
  314. {
  315. if (@available (iOS 10, *))
  316. return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
  317. return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();
  318. }
  319. //==============================================================================
  320. struct PushNotifications::Pimpl
  321. {
  322. Pimpl (PushNotifications& p)
  323. : owner (p)
  324. {
  325. Class::setThis (delegate.get(), this);
  326. auto appDelegate = [[UIApplication sharedApplication] delegate];
  327. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  328. if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegateToUse:)])
  329. [appDelegate performSelector: @selector (setPushNotificationsDelegateToUse:) withObject: delegate.get()];
  330. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  331. }
  332. void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
  333. {
  334. settings = settingsToUse;
  335. auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
  336. if (@available (iOS 10, *))
  337. {
  338. for (const auto& c : settings.categories)
  339. {
  340. auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
  341. [categories addObject: category];
  342. }
  343. UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
  344. | (bool)settings.allowSound << 1
  345. | (bool)settings.allowAlert << 2);
  346. [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
  347. [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
  348. completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
  349. {
  350. requestSettingsUsed();
  351. }];
  352. }
  353. else
  354. {
  355. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  356. for (const auto& c : settings.categories)
  357. {
  358. auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
  359. [categories addObject: category];
  360. }
  361. UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0
  362. | (bool)settings.allowSound << 1
  363. | (bool)settings.allowAlert << 2);
  364. UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories];
  365. [[UIApplication sharedApplication] registerUserNotificationSettings: s];
  366. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  367. }
  368. [[UIApplication sharedApplication] registerForRemoteNotifications];
  369. }
  370. void requestSettingsUsed()
  371. {
  372. if (@available (iOS 10, *))
  373. {
  374. [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
  375. ^(UNNotificationSettings* s)
  376. {
  377. [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
  378. ^(NSSet<UNNotificationCategory*>* categories)
  379. {
  380. settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
  381. settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
  382. settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;
  383. for (UNNotificationCategory* c in categories)
  384. settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));
  385. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  386. }
  387. ];
  388. }];
  389. }
  390. else
  391. {
  392. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  393. UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings;
  394. settings.allowBadge = s.types & UIUserNotificationTypeBadge;
  395. settings.allowSound = s.types & UIUserNotificationTypeSound;
  396. settings.allowAlert = s.types & UIUserNotificationTypeAlert;
  397. for (UIUserNotificationCategory *c in s.categories)
  398. settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c));
  399. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  400. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  401. }
  402. }
  403. bool areNotificationsEnabled() const { return true; }
  404. void sendLocalNotification (const Notification& n)
  405. {
  406. if (@available (iOS 10, *))
  407. {
  408. UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
  409. [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
  410. withCompletionHandler: ^(NSError* error)
  411. {
  412. jassert (error == nil);
  413. if (error != nil)
  414. NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
  415. }];
  416. }
  417. else
  418. {
  419. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  420. auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);
  421. [[UIApplication sharedApplication] scheduleLocalNotification: notification];
  422. [notification release];
  423. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  424. }
  425. }
  426. void getDeliveredNotifications() const
  427. {
  428. if (@available (iOS 10, *))
  429. {
  430. [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
  431. ^(NSArray<UNNotification*>* notifications)
  432. {
  433. Array<PushNotifications::Notification> notifs;
  434. for (UNNotification* n in notifications)
  435. notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));
  436. owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
  437. }];
  438. }
  439. else
  440. {
  441. // Not supported on this platform
  442. jassertfalse;
  443. owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
  444. }
  445. }
  446. void removeAllDeliveredNotifications()
  447. {
  448. if (@available (iOS 10, *))
  449. {
  450. [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
  451. }
  452. else
  453. {
  454. // Not supported on this platform
  455. jassertfalse;
  456. }
  457. }
  458. void removeDeliveredNotification ([[maybe_unused]] const String& identifier)
  459. {
  460. if (@available (iOS 10, *))
  461. {
  462. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  463. [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
  464. }
  465. else
  466. {
  467. // Not supported on this platform
  468. jassertfalse;
  469. }
  470. }
  471. void setupChannels ([[maybe_unused]] const Array<ChannelGroup>& groups, [[maybe_unused]] const Array<Channel>& channels)
  472. {
  473. }
  474. void getPendingLocalNotifications() const
  475. {
  476. if (@available (iOS 10, *))
  477. {
  478. [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
  479. ^(NSArray<UNNotificationRequest*>* requests)
  480. {
  481. Array<PushNotifications::Notification> notifs;
  482. for (UNNotificationRequest* r : requests)
  483. notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));
  484. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  485. }
  486. ];
  487. }
  488. else
  489. {
  490. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  491. Array<PushNotifications::Notification> notifs;
  492. for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
  493. notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));
  494. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  495. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  496. }
  497. }
  498. void removePendingLocalNotification (const String& identifier)
  499. {
  500. if (@available (iOS 10, *))
  501. {
  502. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  503. [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
  504. }
  505. else
  506. {
  507. // Not supported on this platform
  508. jassertfalse;
  509. }
  510. }
  511. void removeAllPendingLocalNotifications()
  512. {
  513. if (@available (iOS 10, *))
  514. {
  515. [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
  516. }
  517. else
  518. {
  519. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  520. [[UIApplication sharedApplication] cancelAllLocalNotifications];
  521. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  522. }
  523. }
  524. String getDeviceToken()
  525. {
  526. // You need to call requestPermissionsWithSettings() first.
  527. jassert (initialised);
  528. return deviceToken;
  529. }
  530. void subscribeToTopic ([[maybe_unused]] const String& topic) {}
  531. void unsubscribeFromTopic ([[maybe_unused]] const String& topic) {}
  532. void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
  533. [[maybe_unused]] const String& collapseKey,
  534. [[maybe_unused]] const String& messageId,
  535. [[maybe_unused]] const String& messageType,
  536. [[maybe_unused]] int timeToLive,
  537. [[maybe_unused]] const StringPairArray& additionalData)
  538. {
  539. }
  540. private:
  541. //==============================================================================
  542. void registeredForRemoteNotifications (NSData* deviceTokenToUse)
  543. {
  544. deviceToken = [deviceTokenToUse]() -> String
  545. {
  546. auto length = deviceTokenToUse.length;
  547. if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
  548. {
  549. NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
  550. for (NSUInteger i = 0; i < length; ++i)
  551. [hexString appendFormat:@"%02x", buffer[i]];
  552. return nsStringToJuce ([hexString copy]);
  553. }
  554. return {};
  555. }();
  556. initialised = true;
  557. owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
  558. }
  559. void failedToRegisterForRemoteNotifications ([[maybe_unused]] NSError* error)
  560. {
  561. deviceToken.clear();
  562. }
  563. void didReceiveRemoteNotification (NSDictionary* userInfo)
  564. {
  565. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  566. owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
  567. }
  568. void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  569. void (^completionHandler)(UIBackgroundFetchResult result))
  570. {
  571. didReceiveRemoteNotification (userInfo);
  572. completionHandler (UIBackgroundFetchResultNewData);
  573. }
  574. void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  575. NSDictionary* userInfo,
  576. NSDictionary* responseInfo,
  577. void (^completionHandler)())
  578. {
  579. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  580. auto actionString = nsStringToJuce (actionIdentifier);
  581. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  582. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); });
  583. completionHandler();
  584. }
  585. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  586. void didRegisterUserNotificationSettings (UIUserNotificationSettings*)
  587. {
  588. requestSettingsUsed();
  589. }
  590. void didReceiveLocalNotification (UILocalNotification* notification)
  591. {
  592. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  593. owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
  594. }
  595. void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  596. UILocalNotification* notification,
  597. void (^completionHandler)())
  598. {
  599. handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
  600. notification,
  601. nil,
  602. completionHandler);
  603. }
  604. void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  605. UILocalNotification* notification,
  606. NSDictionary* responseInfo,
  607. void (^completionHandler)())
  608. {
  609. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  610. auto actionString = nsStringToJuce (actionIdentifier);
  611. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  612. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); });
  613. completionHandler();
  614. }
  615. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  616. void willPresentNotificationWithCompletionHandler ([[maybe_unused]] UNNotification* notification,
  617. void (^completionHandler)(UNNotificationPresentationOptions options))
  618. {
  619. NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
  620. | (int)settings.allowSound << 1
  621. | (int)settings.allowAlert << 2);
  622. completionHandler (options);
  623. }
  624. void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  625. void (^completionHandler)())
  626. {
  627. const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];
  628. auto actionString = nsStringToJuce (response.actionIdentifier);
  629. if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
  630. actionString.clear();
  631. else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
  632. actionString = "com.juce.NotificationDeleted";
  633. auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);
  634. String responseString;
  635. if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
  636. {
  637. UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
  638. responseString = nsStringToJuce (textResponse.userText);
  639. }
  640. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); });
  641. completionHandler();
  642. }
  643. //==============================================================================
  644. struct Class final : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
  645. {
  646. Class()
  647. : ObjCClass ("JucePushNotificationsDelegate_")
  648. {
  649. addIvar<Pimpl*> ("self");
  650. addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data)
  651. {
  652. getThis (self).registeredForRemoteNotifications (data);
  653. });
  654. addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, UIApplication*, NSError* error)
  655. {
  656. getThis (self).failedToRegisterForRemoteNotifications (error);
  657. });
  658. addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo)
  659. {
  660. getThis (self).didReceiveRemoteNotification (userInfo);
  661. });
  662. addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo, void (^completionHandler)(UIBackgroundFetchResult result))
  663. {
  664. getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler);
  665. });
  666. addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, NSDictionary* userInfo, NSDictionary* responseInfo, void (^completionHandler)())
  667. {
  668. getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler);
  669. });
  670. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  671. addMethod (@selector (application:didRegisterUserNotificationSettings:), [] (id self, SEL, UIApplication*, UIUserNotificationSettings* settingsToUse)
  672. {
  673. getThis (self).didRegisterUserNotificationSettings (settingsToUse);
  674. });
  675. addMethod (@selector (application:didReceiveLocalNotification:), [] (id self, SEL, UIApplication*, UILocalNotification* notification)
  676. {
  677. getThis (self).didReceiveLocalNotification (notification);
  678. });
  679. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, void (^completionHandler)())
  680. {
  681. getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler);
  682. });
  683. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, NSDictionary* responseInfo, void (^completionHandler)())
  684. {
  685. getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler);
  686. });
  687. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  688. addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotification* notification, void (^completionHandler)(UNNotificationPresentationOptions options))
  689. {
  690. getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler);
  691. });
  692. addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotificationResponse* response, void (^completionHandler)())
  693. {
  694. getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler);
  695. });
  696. registerClass();
  697. }
  698. //==============================================================================
  699. static Pimpl& getThis (id self) { return *getIvar<Pimpl*> (self, "self"); }
  700. static void setThis (id self, Pimpl* d) { object_setInstanceVariable (self, "self", d); }
  701. };
  702. //==============================================================================
  703. static Class& getClass()
  704. {
  705. static Class c;
  706. return c;
  707. }
  708. NSUniquePtr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate { [getClass().createInstance() init] };
  709. PushNotifications& owner;
  710. bool initialised = false;
  711. String deviceToken;
  712. PushNotifications::Settings settings;
  713. };
  714. } // namespace juce