This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended. To summarise this work: - [x] created a new **Xcode** project; - [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project; - [x] created the `Locations` app and also, its `Libraries` package; - [x] created the `Shared` package to share dependencies between the apps; - [x] added a `Makefile` file and implemented some **environment** and **help** commands. Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Reviewed-on: rock-n-code/deep-linking-assignment#1
106 lines
5.9 KiB
Swift
106 lines
5.9 KiB
Swift
import UserNotifications
|
|
import WMF
|
|
|
|
class NotificationService: UNNotificationServiceExtension {
|
|
|
|
var contentHandler: ((UNNotificationContent) -> Void)?
|
|
var bestAttemptContent: UNMutableNotificationContent?
|
|
|
|
private lazy var apiController: RemoteNotificationsAPIController = {
|
|
let configuration = Configuration.current
|
|
let session = Session(configuration: configuration)
|
|
let controller = RemoteNotificationsAPIController(session: session, configuration: configuration)
|
|
return controller
|
|
}()
|
|
private let sharedCache = SharedContainerCache<PushNotificationsCache>.init(fileName: SharedContainerCacheCommonNames.pushNotificationsCache, defaultCache: { PushNotificationsCache(settings: .default, notifications: []) })
|
|
|
|
private let fallbackPushContent = WMFLocalizedString("notifications-push-fallback-body-text", value: "New activity on Wikipedia", comment: "Fallback body content of a push notification whose content cannot be determined. Could be either due multiple notifications represented or errors.")
|
|
|
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
|
self.contentHandler = contentHandler
|
|
|
|
guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
|
|
contentHandler(request.content)
|
|
return
|
|
}
|
|
|
|
self.bestAttemptContent = bestAttemptContent
|
|
|
|
guard bestAttemptContent.body == EchoModelVersion.current else {
|
|
bestAttemptContent.body = fallbackPushContent
|
|
contentHandler(bestAttemptContent)
|
|
return
|
|
}
|
|
|
|
let cache = sharedCache.loadCache()
|
|
let project = WikimediaProject.wikipedia(cache.settings.primaryLanguageCode, cache.settings.primaryLocalizedName, nil)
|
|
|
|
let fallbackPushContent = self.fallbackPushContent
|
|
|
|
apiController.getUnreadPushNotifications(from: project) { [weak self] fetchedNotifications, error in
|
|
|
|
DispatchQueue.main.async {
|
|
guard let self = self,
|
|
error == nil else {
|
|
bestAttemptContent.body = fallbackPushContent
|
|
contentHandler(bestAttemptContent)
|
|
return
|
|
}
|
|
|
|
let finalNotifications = NotificationServiceHelper.determineNotificationsToDisplayAndCache(fetchedNotifications: fetchedNotifications, cachedNotifications: cache.notifications)
|
|
let finalNotificationsToDisplay = finalNotifications.notificationsToDisplay
|
|
let finalNotificationsToCache = finalNotifications.notificationsToCache
|
|
|
|
var newCache = cache
|
|
newCache.notifications = finalNotificationsToCache
|
|
self.sharedCache.saveCache(newCache)
|
|
|
|
// specific handling for talk page types (New messages title, bundled body)
|
|
if let talkPageContent = NotificationServiceHelper.talkPageContent(for: finalNotificationsToDisplay) {
|
|
bestAttemptContent.subtitle = talkPageContent.subtitle
|
|
bestAttemptContent.body = talkPageContent.body
|
|
} else if finalNotificationsToDisplay.count == 1,
|
|
let pushContentText = finalNotificationsToDisplay.first?.pushContentText {
|
|
bestAttemptContent.body = pushContentText
|
|
} else {
|
|
bestAttemptContent.body = fallbackPushContent
|
|
}
|
|
|
|
// Assigning interruption level and relevance score only available starting on iOS 15
|
|
if #available(iOS 15.0, *) {
|
|
if finalNotifications.notificationsToDisplay.count == 1, let notification = finalNotifications.notificationsToDisplay.first {
|
|
let priority = RemoteNotification.typeFrom(notification: notification).priority
|
|
bestAttemptContent.interruptionLevel = priority.interruptionLevel
|
|
bestAttemptContent.relevanceScore = priority.relevanceScore
|
|
} else {
|
|
if NotificationServiceHelper.allNotificationsAreForSameTalkPage(notifications: finalNotificationsToDisplay) {
|
|
bestAttemptContent.interruptionLevel = RemoteNotificationType.mentionInTalkPage.priority.interruptionLevel
|
|
bestAttemptContent.relevanceScore = RemoteNotificationType.mentionInTalkPage.priority.relevanceScore
|
|
} else {
|
|
bestAttemptContent.interruptionLevel = RemoteNotificationType.bulkPriority.interruptionLevel
|
|
bestAttemptContent.relevanceScore = RemoteNotificationType.bulkPriority.relevanceScore
|
|
}
|
|
}
|
|
}
|
|
|
|
let displayContentIdentifiers = finalNotificationsToDisplay.compactMap { PushNotificationContentIdentifier(key: $0.key, date: $0.date) }
|
|
PushNotificationContentIdentifier.save(displayContentIdentifiers, to: &bestAttemptContent.userInfo)
|
|
|
|
bestAttemptContent.badge = NSNumber(value: newCache.currentUnreadCount + finalNotificationsToDisplay.count)
|
|
|
|
contentHandler(bestAttemptContent)
|
|
}
|
|
}
|
|
}
|
|
|
|
override func serviceExtensionTimeWillExpire() {
|
|
// Called just before the extension will be terminated by the system.
|
|
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
|
if let contentHandler = contentHandler,
|
|
let bestAttemptContent = bestAttemptContent {
|
|
bestAttemptContent.body = fallbackPushContent
|
|
contentHandler(bestAttemptContent)
|
|
}
|
|
}
|
|
}
|