Javier Cicchelli 9bcdaa697b [Setup] Basic project structure (#1)
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
2023-04-08 18:37:13 +00:00

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)
}
}
}