@@ -45,6 +45,15 @@ final class PushNotificationsManager: PushNotesManager {
4545 /// Mutable reference to `inactiveNotifications`
4646 private let inactiveNotificationsSubject = PassthroughSubject < PushNotification , Never > ( )
4747
48+ /// An observable that emits values when a local notification is received.
49+ ///
50+ var localNotificationUserResponses : AnyPublisher < UNNotificationResponse , Never > {
51+ localNotificationResponsesSubject. eraseToAnyPublisher ( )
52+ }
53+
54+ /// Mutable reference to `localNotificationResponses`.
55+ private let localNotificationResponsesSubject = PassthroughSubject < UNNotificationResponse , Never > ( )
56+
4857 /// Returns the current Application's State
4958 ///
5059 private var applicationState : UIApplication . State {
@@ -220,48 +229,72 @@ extension PushNotificationsManager {
220229 unregisterForRemoteNotifications ( )
221230 }
222231
223-
224- /// Handles a Remote Push Notification Payload. On completion the `completionHandler` will be executed.
232+ /// Handles a Notification while in Foreground Mode. Currently, only remote notifications are handled in the foreground.
225233 ///
226- func handleNotification( _ userInfo: [ AnyHashable : Any ] ,
227- onBadgeUpdateCompletion: @escaping ( ) -> Void ,
228- completionHandler: @escaping ( UIBackgroundFetchResult ) -> Void ) {
229- DDLogVerbose ( " 📱 Push Notification Received: \n \( userInfo) \n " )
230-
231- // Badge: Update
232- if let typeString = userInfo. string ( forKey: APNSKey . type) ,
233- let type = Note . Kind ( rawValue: typeString) ,
234- let siteID = siteID,
235- let notificationSiteID = userInfo [ APNSKey . siteID] as? Int64 {
236- incrementNotificationCount ( siteID: notificationSiteID, type: type, incrementCount: 1 ) { [ weak self] in
237- self ? . loadNotificationCountAndUpdateApplicationBadgeNumberAndPostNotifications ( siteID: siteID, type: type)
238- onBadgeUpdateCompletion ( )
239- }
234+ /// - Parameters:
235+ /// - userInfo: The Notification's Payload
236+ /// - completionHandler: A callback, to be executed on completion
237+ ///
238+ /// - Returns: True when handled. False otherwise
239+ ///
240+ @MainActor
241+ func handleNotificationInTheForeground( _ notification: UNNotification ) async -> UNNotificationPresentationOptions {
242+ let content = notification. request. content
243+ guard applicationState == . active, content. isRemoteNotification else {
244+ // Local notifications are currently not handled when the app is in the foreground.
245+ return UNNotificationPresentationOptions ( rawValue: 0 )
240246 }
241247
242- // Badge: Reset
243- guard userInfo. string ( forKey: APNSKey . type) != PushType . badgeReset else {
244- return
248+ handleRemoteNotificationInAllAppStates ( content. userInfo)
249+
250+ if let foregroundNotification = PushNotification . from ( userInfo: content. userInfo) {
251+ configuration. application
252+ . presentInAppNotification ( title: foregroundNotification. title,
253+ subtitle: foregroundNotification. subtitle,
254+ message: foregroundNotification. message,
255+ actionTitle: Localization . viewInAppNotification) { [ weak self] in
256+ guard let self = self else { return }
257+ self . presentDetails ( for: foregroundNotification)
258+ self . foregroundNotificationsToViewSubject. send ( foregroundNotification)
259+ ServiceLocator . analytics. track ( . viewInAppPushNotificationPressed,
260+ withProperties: [ AnalyticKey . type: foregroundNotification. kind. rawValue] )
261+ }
262+
263+ foregroundNotificationsSubject. send ( foregroundNotification)
245264 }
246265
247- // Analytics
248- trackNotification ( with: userInfo)
266+ _ = await synchronizeNotifications ( )
267+ return UNNotificationPresentationOptions ( rawValue: 0 )
268+ }
249269
250- // Handling!
251- let handlers = [
252- handleSupportNotification,
253- handleForegroundNotification,
254- handleInactiveNotification,
255- handleBackgroundNotification
256- ]
257-
258- for handler in handlers {
259- if handler ( userInfo, completionHandler) {
260- break
261- }
270+ @MainActor
271+ func handleUserResponseToNotification( _ response: UNNotificationResponse ) async {
272+ // Remote notification response is handled separately.
273+ if let notification = PushNotification . from ( userInfo: response. notification. request. content. userInfo) {
274+ handleRemoteNotificationInAllAppStates ( response. notification. request. content. userInfo)
275+ await handleInactiveRemoteNotification ( notification: notification)
276+ } else {
277+ localNotificationResponsesSubject. send ( response)
262278 }
263279 }
264280
281+ /// Handles a remote notification while the app is in the background.
282+ ///
283+ /// - Parameter userInfo: The notification's payload.
284+ /// - Returns: Whether there is any data fetched in the background.
285+ @MainActor
286+ func handleRemoteNotificationInTheBackground( userInfo: [ AnyHashable : Any ] ) async -> UIBackgroundFetchResult {
287+ guard applicationState == . background, // Proceeds only if the app is in background.
288+ let _ = userInfo [ APNSKey . identifier] // Ensures that we are only processing a remote notification.
289+ else {
290+ return . noData
291+ }
292+
293+ handleRemoteNotificationInAllAppStates ( userInfo)
294+
295+ return await synchronizeNotifications ( )
296+ }
297+
265298 func requestLocalNotification( _ notification: LocalNotification , trigger: UNNotificationTrigger ? ) {
266299 Task {
267300 // TODO: 7318 - tech debt - replace `UNUserNotificationCenter.current()` with
@@ -371,103 +404,62 @@ private extension PushNotificationsManager {
371404 ///
372405 /// - Returns: True when handled. False otherwise
373406 ///
374- func handleSupportNotification( _ userInfo: [ AnyHashable : Any ] , completionHandler: @escaping ( UIBackgroundFetchResult ) -> Void ) -> Bool {
375-
407+ func handleSupportNotification( _ userInfo: [ AnyHashable : Any ] ) -> Bool {
376408 guard userInfo. string ( forKey: APNSKey . type) == PushType . zendesk else {
377- return false
409+ return false
378410 }
379411
380- self . configuration. supportManager. pushNotificationReceived ( )
412+ configuration. supportManager. pushNotificationReceived ( )
381413
382414 trackNotification ( with: userInfo)
383415
384416 if applicationState == . inactive {
385- self . configuration. supportManager. displaySupportRequest ( using: userInfo)
417+ configuration. supportManager. displaySupportRequest ( using: userInfo)
386418 }
387-
388- completionHandler ( . newData)
389-
390419 return true
391420 }
392421
393-
394- /// Handles a Notification while in Foreground Mode
395- ///
396- /// - Parameters:
397- /// - userInfo: The Notification's Payload
398- /// - completionHandler: A callback, to be executed on completion
399- ///
400- /// - Returns: True when handled. False otherwise
422+ /// Handles a Remote Push Notification Payload regardless of the application state.
401423 ///
402- func handleForegroundNotification( _ userInfo: [ AnyHashable : Any ] , completionHandler: @escaping ( UIBackgroundFetchResult ) -> Void ) -> Bool {
403- guard applicationState == . active, let _ = userInfo [ APNSKey . identifier] else {
404- return false
405- }
424+ func handleRemoteNotificationInAllAppStates( _ userInfo: [ AnyHashable : Any ] ) {
425+ DDLogVerbose ( " 📱 Push Notification Received: \n \( userInfo) \n " )
406426
407- if let foregroundNotification = PushNotification . from ( userInfo: userInfo) {
408- configuration. application
409- . presentInAppNotification ( title: foregroundNotification. title,
410- subtitle: foregroundNotification. subtitle,
411- message: foregroundNotification. message,
412- actionTitle: Localization . viewInAppNotification) { [ weak self] in
413- guard let self = self else { return }
414- self . presentDetails ( for: foregroundNotification)
415- self . foregroundNotificationsToViewSubject. send ( foregroundNotification)
416- ServiceLocator . analytics. track ( . viewInAppPushNotificationPressed, withProperties: [ AnalyticKey . type: foregroundNotification. kind. rawValue] )
417- }
427+ // Badge: Update
428+ if let typeString = userInfo. string ( forKey: APNSKey . type) ,
429+ let type = Note . Kind ( rawValue: typeString) ,
430+ let siteID = siteID,
431+ let notificationSiteID = userInfo [ APNSKey . siteID] as? Int64 {
432+ incrementNotificationCount ( siteID: notificationSiteID, type: type, incrementCount: 1 ) { [ weak self] in
433+ self ? . loadNotificationCountAndUpdateApplicationBadgeNumberAndPostNotifications ( siteID: siteID, type: type)
434+ }
435+ }
418436
419- foregroundNotificationsSubject. send ( foregroundNotification)
437+ // Badge: Reset
438+ guard userInfo. string ( forKey: APNSKey . type) != PushType . badgeReset else {
439+ return
420440 }
421441
422- synchronizeNotifications ( completionHandler: completionHandler)
442+ // Analytics
443+ trackNotification ( with: userInfo)
423444
424- return true
445+ // Handles support notification in different app states.
446+ // Note: support notifications are currently not working - https://github.com/woocommerce/woocommerce-ios/issues/3776
447+ _ = handleSupportNotification ( userInfo)
425448 }
426449
427-
428- /// Handles a Notification while in Inactive Mode
429- ///
430- /// - Parameters:
431- /// - userInfo: The Notification's Payload
432- /// - completionHandler: A callback, to be executed on completion
433- ///
434- /// - Returns: True when handled. False otherwise
450+ /// Handles a remote notification while the app is inactive.
435451 ///
436- func handleInactiveNotification( _ userInfo: [ AnyHashable : Any ] , completionHandler: ( UIBackgroundFetchResult ) -> Void ) -> Bool {
452+ /// - Parameter notification: Push notification content from a remote notification.
453+ func handleInactiveRemoteNotification( notification: PushNotification ) async {
437454 guard applicationState == . inactive else {
438- return false
455+ return
439456 }
440457
441- DDLogVerbose ( " 📱 Handling Notification in Inactive State " )
442-
443- if let notification = PushNotification . from ( userInfo: userInfo) {
444- presentDetails ( for: notification)
445-
446- inactiveNotificationsSubject. send ( notification)
447- }
458+ DDLogVerbose ( " 📱 Handling Remote Notification in Inactive State " )
448459
449- completionHandler ( . newData )
460+ presentDetails ( for : notification )
450461
451- return true
452- }
453-
454-
455- /// Handles a Notification while in Background Mode
456- ///
457- /// - Parameters:
458- /// - userInfo: The Notification's Payload
459- /// - completionHandler: A callback, to be executed on completion
460- ///
461- /// - Returns: True when handled. False otherwise
462- ///
463- func handleBackgroundNotification( _ userInfo: [ AnyHashable : Any ] , completionHandler: @escaping ( UIBackgroundFetchResult ) -> Void ) -> Bool {
464- guard applicationState == . background, let _ = userInfo [ APNSKey . identifier] else {
465- return false
466- }
467-
468- synchronizeNotifications ( completionHandler: completionHandler)
469-
470- return true
462+ inactiveNotificationsSubject. send ( notification)
471463 }
472464}
473465
@@ -579,16 +571,19 @@ private extension PushNotificationsManager {
579571
580572 /// Synchronizes all of the Notifications. On success this method will always signal `.newData`, and `.noData` on error.
581573 ///
582- func synchronizeNotifications( completionHandler: @escaping ( UIBackgroundFetchResult ) -> Void ) {
583- let action = NotificationAction . synchronizeNotifications { error in
584- DDLogInfo ( " 📱 Finished Synchronizing Notifications! " )
574+ @MainActor
575+ func synchronizeNotifications( ) async -> UIBackgroundFetchResult {
576+ await withCheckedContinuation { continuation in
577+ let action = NotificationAction . synchronizeNotifications { error in
578+ DDLogInfo ( " 📱 Finished Synchronizing Notifications! " )
585579
586- let result = ( error == nil ) ? UIBackgroundFetchResult . newData : . noData
587- completionHandler ( result)
588- }
580+ let result = ( error == nil ) ? UIBackgroundFetchResult . newData : . noData
581+ continuation . resume ( returning : result)
582+ }
589583
590- DDLogInfo ( " 📱 Synchronizing Notifications in \( applicationState. description) State... " )
591- configuration. storesManager. dispatch ( action)
584+ DDLogInfo ( " 📱 Synchronizing Notifications in \( applicationState. description) State... " )
585+ configuration. storesManager. dispatch ( action)
586+ }
592587 }
593588}
594589
@@ -609,6 +604,14 @@ private extension PushNotification {
609604 }
610605}
611606
607+ // MARK: - UNNotificationContent Extension
608+
609+ private extension UNNotificationContent {
610+ var isRemoteNotification : Bool {
611+ userInfo [ APNSKey . identifier] != nil
612+ }
613+ }
614+
612615// MARK: - App Icon Badge Number
613616
614617enum AppIconBadgeNumber {
0 commit comments