diff --git a/Notification Service Extension/NotificationService.swift b/Notification Service Extension/NotificationService.swift index 1e7826c..41337a4 100644 --- a/Notification Service Extension/NotificationService.swift +++ b/Notification Service Extension/NotificationService.swift @@ -39,6 +39,7 @@ final class NotificationService: UNNotificationServiceExtension { return } + bestAttemptContent.userInfo[PushNotificationParsingService.pushNotificationUserInfoKey] = decryptedJSON bestAttemptContent.title = pushNotification.title bestAttemptContent.body = XMLUnescaper(string: pushNotification.body).unescape() diff --git a/ServiceLayer/Sources/ServiceLayer/Services/PushNotificationParsingService.swift b/ServiceLayer/Sources/ServiceLayer/Services/PushNotificationParsingService.swift index a11a749..12beadc 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/PushNotificationParsingService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/PushNotificationParsingService.swift @@ -19,6 +19,9 @@ public struct PushNotificationParsingService { } public extension PushNotificationParsingService { + static let identityIdUserInfoKey = "i" + static let pushNotificationUserInfoKey = "com.metabolist.metatext.push-notification-user-info-key" + func extractAndDecrypt(userInfo: [AnyHashable: Any]) throws -> (Data, Identity.Id) { guard let identityIdString = userInfo[Self.identityIdUserInfoKey] as? String, let identityId = Identity.Id(uuidString: identityIdString), @@ -48,7 +51,6 @@ public extension PushNotificationParsingService { } private extension PushNotificationParsingService { - static let identityIdUserInfoKey = "i" static let encryptedMessageUserInfoKey = "m" static let saltUserInfoKey = "s" static let serverPublicKeyUserInfoKey = "k" diff --git a/System/MetatextApp.swift b/System/MetatextApp.swift index 1254c6b..dd09cc3 100644 --- a/System/MetatextApp.swift +++ b/System/MetatextApp.swift @@ -9,28 +9,27 @@ import ViewModels @main struct MetatextApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate - private let environment = AppEnvironment.live( - userNotificationCenter: .current(), - reduceMotion: { UIAccessibility.isReduceMotionEnabled }) + // swiftlint:disable:next force_try + private let viewModel = try! RootViewModel(environment: Self.environment) init() { try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default) - try? ImageCacheConfiguration(environment: environment).configure() + try? ImageCacheConfiguration(environment: Self.environment).configure() } var body: some Scene { - WindowGroup { - RootView( - // swiftlint:disable force_try - viewModel: try! RootViewModel( - environment: environment, - registerForRemoteNotifications: appDelegate.registerForRemoteNotifications)) - // swiftlint:enable force_try + viewModel.registerForRemoteNotifications = appDelegate.registerForRemoteNotifications + + return WindowGroup { + RootView(viewModel: viewModel) } } } private extension MetatextApp { + static let environment = AppEnvironment.live( + userNotificationCenter: .current(), + reduceMotion: { UIAccessibility.isReduceMotionEnabled }) static let imageCacheName = "Images" static let imageCacheDirectoryURL = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: AppEnvironment.appGroup)? diff --git a/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift b/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift index cc50053..0ae45fb 100644 --- a/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift +++ b/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift @@ -70,7 +70,7 @@ public extension Instance { } public extension RootViewModel { - static let preview = try! RootViewModel(environment: environment) { Empty().eraseToAnyPublisher() } + static let preview = try! RootViewModel(environment: environment) } public extension IdentityContext { diff --git a/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift b/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift index a9e94c4..83df7d7 100644 --- a/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift @@ -7,20 +7,30 @@ import ServiceLayer public final class RootViewModel: ObservableObject { @Published public private(set) var navigationViewModel: NavigationViewModel? + public var registerForRemoteNotifications: (() -> AnyPublisher)? { + didSet { + guard let registerForRemoteNotifications = registerForRemoteNotifications else { return } + + userNotificationService.isAuthorized(request: false) + .filter { $0 } + .zip(registerForRemoteNotifications()) + .map { $1 } + .flatMap(allIdentitiesService.updatePushSubscriptions(deviceToken:)) + .sink { _ in } receiveValue: { _ in } + .store(in: &cancellables) + } + } @Published private var mostRecentlyUsedIdentityId: Identity.Id? private let environment: AppEnvironment private let allIdentitiesService: AllIdentitiesService private let userNotificationService: UserNotificationService - private let registerForRemoteNotifications: () -> AnyPublisher private var cancellables = Set() - public init(environment: AppEnvironment, - registerForRemoteNotifications: @escaping () -> AnyPublisher) throws { + public init(environment: AppEnvironment) throws { self.environment = environment allIdentitiesService = try AllIdentitiesService(environment: environment) userNotificationService = UserNotificationService(environment: environment) - self.registerForRemoteNotifications = registerForRemoteNotifications allIdentitiesService.immediateMostRecentlyUsedIdentityIdPublisher() .replaceError(with: nil) @@ -32,14 +42,6 @@ public final class RootViewModel: ObservableObject { .sink { [weak self] in self?.identitySelected(id: $0) } .store(in: &cancellables) - userNotificationService.isAuthorized(request: false) - .filter { $0 } - .zip(registerForRemoteNotifications()) - .map { $1 } - .flatMap(allIdentitiesService.updatePushSubscriptions(deviceToken:)) - .sink { _ in } receiveValue: { _ in } - .store(in: &cancellables) - userNotificationService.events .sink { [weak self] in self?.handle(event: $0) } .store(in: &cancellables) @@ -116,10 +118,12 @@ private extension RootViewModel { .sink { _ in } receiveValue: { _ in } .store(in: &self.cancellables) - if identityContext.identity.authenticated && !identityContext.identity.pending { + if identityContext.identity.authenticated, + !identityContext.identity.pending, + let registerForRemoteNotifications = self.registerForRemoteNotifications { self.userNotificationService.isAuthorized(request: true) .filter { $0 } - .zip(self.registerForRemoteNotifications()) + .zip(registerForRemoteNotifications()) .filter { identityContext.identity.lastRegisteredDeviceToken != $1 } .map { ($1, identityContext.identity.pushSubscriptionAlerts) } .flatMap(identityContext.service.createPushSubscription(deviceToken:alerts:)) @@ -136,8 +140,23 @@ private extension RootViewModel { switch event { case let .willPresentNotification(_, completionHandler): completionHandler(.banner) + case let .didReceiveResponse(response, completionHandler): + let userInfo = response.notification.request.content.userInfo + + if let identityIdString = userInfo[PushNotificationParsingService.identityIdUserInfoKey] as? String, + let identityId = Identity.Id(uuidString: identityIdString), + let pushNotificationJSON = userInfo[PushNotificationParsingService.pushNotificationUserInfoKey] as? Data, + let pushNotification = try? MastodonDecoder().decode(PushNotification.self, from: pushNotificationJSON) { + handle(pushNotification: pushNotification, identityId: identityId) + } + + completionHandler() default: break } } + + func handle(pushNotification: PushNotification, identityId: Identity.Id) { + // TODO + } }