// Copyright © 2020 Metabolist. All rights reserved. import Combine import Foundation import Mastodon 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 var cancellables = Set() public init(environment: AppEnvironment) throws { self.environment = environment allIdentitiesService = try AllIdentitiesService(environment: environment) userNotificationService = UserNotificationService(environment: environment) allIdentitiesService.immediateMostRecentlyUsedIdentityIdPublisher() .replaceError(with: nil) .assign(to: &$mostRecentlyUsedIdentityId) identitySelected(id: mostRecentlyUsedIdentityId, immediate: true) allIdentitiesService.identitiesCreated .sink { [weak self] in self?.identitySelected(id: $0) } .store(in: &cancellables) userNotificationService.events .sink { [weak self] in self?.handle(event: $0) } .store(in: &cancellables) } } public extension RootViewModel { func identitySelected(id: Identity.Id?) { identitySelected(id: id, immediate: false) } func deleteIdentity(id: Identity.Id) { allIdentitiesService.deleteIdentity(id: id) .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) } func addIdentityViewModel() -> AddIdentityViewModel { AddIdentityViewModel( allIdentitiesService: allIdentitiesService, instanceURLService: InstanceURLService(environment: environment)) } func newStatusViewModel( identityContext: IdentityContext, inReplyTo: StatusViewModel? = nil, redraft: Status? = nil) -> NewStatusViewModel { NewStatusViewModel( allIdentitiesService: allIdentitiesService, identityContext: identityContext, environment: environment, inReplyTo: inReplyTo, redraft: redraft, extensionContext: nil) } } private extension RootViewModel { func identitySelected(id: Identity.Id?, immediate: Bool) { navigationViewModel?.presentingSecondaryNavigation = false guard let id = id, let identityService = try? allIdentitiesService.identityService(id: id) else { navigationViewModel = nil return } let identityPublisher = identityService.identityPublisher(immediate: immediate) .catch { [weak self] _ -> Empty in DispatchQueue.main.async { if self?.navigationViewModel?.identityContext.identity.id == id { self?.identitySelected(id: self?.mostRecentlyUsedIdentityId, immediate: false) } } return Empty() } .share() identityPublisher .first() .map { [weak self] in guard let self = self else { return nil } let identityContext = IdentityContext( identity: $0, publisher: identityPublisher.eraseToAnyPublisher(), service: identityService, environment: self.environment) identityContext.service.updateLastUse() .sink { _ in } receiveValue: { _ in } .store(in: &self.cancellables) if identityContext.identity.authenticated, !identityContext.identity.pending, let registerForRemoteNotifications = self.registerForRemoteNotifications { self.userNotificationService.isAuthorized(request: true) .filter { $0 } .zip(registerForRemoteNotifications()) .filter { identityContext.identity.lastRegisteredDeviceToken != $1 } .map { ($1, identityContext.identity.pushSubscriptionAlerts) } .flatMap(identityContext.service.createPushSubscription(deviceToken:alerts:)) .sink { _ in } receiveValue: { _ in } .store(in: &self.cancellables) } return NavigationViewModel(identityContext: identityContext) } .assign(to: &$navigationViewModel) } func handle(event: UserNotificationService.Event) { 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 } }