metatext-app-ios-iphone-ipad/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift

205 lines
8.4 KiB
Swift
Raw Normal View History

2020-08-03 17:20:51 +02:00
// Copyright © 2020 Metabolist. All rights reserved.
import Combine
2020-09-05 04:31:43 +02:00
import Foundation
2021-01-11 23:45:30 +01:00
import Mastodon
2020-08-31 20:57:02 +02:00
import ServiceLayer
2020-08-03 17:20:51 +02:00
2020-09-01 09:33:49 +02:00
public final class RootViewModel: ObservableObject {
2020-09-10 01:00:10 +02:00
@Published public private(set) var navigationViewModel: NavigationViewModel?
2020-08-09 07:37:04 +02:00
2020-10-06 00:50:05 +02:00
@Published private var mostRecentlyUsedIdentityId: Identity.Id?
2020-09-09 08:17:35 +02:00
private let environment: AppEnvironment
2021-03-05 03:13:30 +01:00
private let registerForRemoteNotifications: () -> AnyPublisher<Data, Error>
2020-08-26 23:35:06 +02:00
private let allIdentitiesService: AllIdentitiesService
private let userNotificationService: UserNotificationService
2020-08-03 17:20:51 +02:00
private var cancellables = Set<AnyCancellable>()
2021-03-05 03:13:30 +01:00
public init(environment: AppEnvironment,
registerForRemoteNotifications: @escaping () -> AnyPublisher<Data, Error>) throws {
2020-09-09 08:17:35 +02:00
self.environment = environment
2021-03-05 03:13:30 +01:00
self.registerForRemoteNotifications = registerForRemoteNotifications
2020-09-01 09:33:49 +02:00
allIdentitiesService = try AllIdentitiesService(environment: environment)
userNotificationService = UserNotificationService(environment: environment)
2020-08-09 07:37:04 +02:00
2020-10-06 22:44:22 +02:00
allIdentitiesService.immediateMostRecentlyUsedIdentityIdPublisher()
2020-09-09 14:05:43 +02:00
.replaceError(with: nil)
2020-10-06 00:50:05 +02:00
.assign(to: &$mostRecentlyUsedIdentityId)
2020-08-26 10:40:54 +02:00
2021-02-04 21:09:05 +01:00
identitySelected(id: mostRecentlyUsedIdentityId, immediate: true, notify: false)
2020-08-12 09:24:39 +02:00
2020-09-13 02:50:22 +02:00
allIdentitiesService.identitiesCreated
.sink { [weak self] in self?.identitySelected(id: $0) }
.store(in: &cancellables)
2021-03-05 03:13:30 +01:00
userNotificationService.isAuthorized(request: false)
.filter { $0 }
.zip(registerForRemoteNotifications())
.map { $1 }
.flatMap(allIdentitiesService.updatePushSubscriptions(deviceToken:))
.sink { _ in } receiveValue: { _ in }
.store(in: &cancellables)
2021-01-29 05:59:06 +01:00
userNotificationService.events
.sink { [weak self] in self?.handle(event: $0) }
.store(in: &cancellables)
2020-08-03 17:20:51 +02:00
}
}
2020-09-01 09:33:49 +02:00
public extension RootViewModel {
2020-10-06 00:50:05 +02:00
func identitySelected(id: Identity.Id?) {
2021-02-04 21:09:05 +01:00
identitySelected(id: id, immediate: false, notify: false)
2020-08-12 09:24:39 +02:00
}
2020-10-06 00:50:05 +02:00
func deleteIdentity(id: Identity.Id) {
2020-09-09 07:40:49 +02:00
allIdentitiesService.deleteIdentity(id: id)
2020-08-14 03:59:17 +02:00
.sink { _ in } receiveValue: { _ in }
2020-08-09 07:37:04 +02:00
.store(in: &cancellables)
}
func addIdentityViewModel() -> AddIdentityViewModel {
2020-09-09 08:17:35 +02:00
AddIdentityViewModel(
allIdentitiesService: allIdentitiesService,
2020-09-10 06:51:31 +02:00
instanceURLService: InstanceURLService(environment: environment))
2020-08-03 17:20:51 +02:00
}
2020-12-10 03:44:06 +01:00
2021-01-11 23:45:30 +01:00
func newStatusViewModel(
2021-01-26 01:06:35 +01:00
identityContext: IdentityContext,
2021-03-22 00:23:41 +01:00
identity: Identity? = nil,
2021-01-11 23:45:30 +01:00
inReplyTo: StatusViewModel? = nil,
2021-03-02 01:53:36 +01:00
redraft: Status? = nil,
directMessageTo: AccountViewModel? = nil) -> NewStatusViewModel {
2020-12-10 03:44:06 +01:00
NewStatusViewModel(
allIdentitiesService: allIdentitiesService,
2021-01-26 01:06:35 +01:00
identityContext: identityContext,
2021-01-10 06:56:15 +01:00
environment: environment,
2021-03-22 00:23:41 +01:00
identity: identity,
2021-01-11 23:45:30 +01:00
inReplyTo: inReplyTo,
2021-01-17 08:14:17 +01:00
redraft: redraft,
2021-03-02 01:53:36 +01:00
directMessageTo: directMessageTo,
2021-01-17 08:14:17 +01:00
extensionContext: nil)
2020-12-10 03:44:06 +01:00
}
2020-08-03 17:20:51 +02:00
}
2020-09-09 14:05:43 +02:00
private extension RootViewModel {
2021-02-04 21:09:05 +01:00
static let identityChangeNotificationUserInfoKey =
"com.metabolist.metatext.identity-change-notification-user-info-key"
static let removeIdentityChangeNotificationAfter = DispatchTimeInterval.seconds(10)
// swiftlint:disable:next function_body_length
func identitySelected(id: Identity.Id?, immediate: Bool, notify: Bool) {
2020-09-10 00:48:56 +02:00
navigationViewModel?.presentingSecondaryNavigation = false
2020-09-09 14:05:43 +02:00
guard
let id = id,
let identityService = try? allIdentitiesService.identityService(id: id) else {
2020-09-10 00:48:56 +02:00
navigationViewModel = nil
2020-09-09 14:05:43 +02:00
return
}
2020-10-06 22:44:22 +02:00
let identityPublisher = identityService.identityPublisher(immediate: immediate)
2020-09-09 14:05:43 +02:00
.catch { [weak self] _ -> Empty<Identity, Never> in
DispatchQueue.main.async {
2021-01-28 20:16:23 +01:00
if self?.navigationViewModel?.identityContext.identity.id == id {
2021-02-04 21:09:05 +01:00
self?.identitySelected(id: self?.mostRecentlyUsedIdentityId,
immediate: false,
notify: true)
2021-01-28 20:16:23 +01:00
}
2020-09-09 14:05:43 +02:00
}
return Empty()
}
.share()
2021-01-28 03:46:19 +01:00
identityPublisher
.first()
2020-09-10 00:48:56 +02:00
.map { [weak self] in
2020-10-15 09:44:01 +02:00
guard let self = self else { return nil }
2021-01-26 01:06:35 +01:00
let identityContext = IdentityContext(
2020-09-09 22:21:04 +02:00
identity: $0,
2020-10-06 22:44:22 +02:00
publisher: identityPublisher.eraseToAnyPublisher(),
2020-10-15 09:44:01 +02:00
service: identityService,
environment: self.environment)
2021-01-26 01:06:35 +01:00
identityContext.service.updateLastUse()
2020-10-15 09:44:01 +02:00
.sink { _ in } receiveValue: { _ in }
.store(in: &self.cancellables)
2021-02-04 09:30:37 +01:00
if identityContext.identity.authenticated,
2021-03-05 03:13:30 +01:00
!identityContext.identity.pending {
2021-01-29 05:59:06 +01:00
self.userNotificationService.isAuthorized(request: true)
.filter { $0 }
2021-03-05 03:13:30 +01:00
.zip(self.registerForRemoteNotifications())
2021-01-29 05:59:06 +01:00
.filter { identityContext.identity.lastRegisteredDeviceToken != $1 }
.map { ($1, identityContext.identity.pushSubscriptionAlerts) }
.flatMap(identityContext.service.createPushSubscription(deviceToken:alerts:))
.sink { _ in } receiveValue: { _ in }
.store(in: &self.cancellables)
}
2020-09-10 00:48:56 +02:00
2021-02-04 21:09:05 +01:00
if notify {
self.notifyIdentityChange(identityContext: identityContext)
}
2022-11-08 23:04:34 +01:00
return NavigationViewModel(identityContext: identityContext, environment: self.environment)
2020-09-09 22:21:04 +02:00
}
2021-01-28 03:46:19 +01:00
.assign(to: &$navigationViewModel)
2020-09-09 14:05:43 +02:00
}
2021-01-29 05:59:06 +01:00
func handle(event: UserNotificationService.Event) {
switch event {
2021-02-04 21:09:05 +01:00
case let .willPresentNotification(notification, completionHandler):
2021-01-29 05:59:06 +01:00
completionHandler(.banner)
2021-02-04 21:09:05 +01:00
if notification.request.content.userInfo[Self.identityChangeNotificationUserInfoKey] as? Bool == true {
DispatchQueue.main.asyncAfter(deadline: .now() + Self.removeIdentityChangeNotificationAfter) {
self.userNotificationService.removeDeliveredNotifications(
withIdentifiers: [notification.request.identifier])
}
}
2021-02-04 09:30:37 +01:00
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()
2021-01-29 05:59:06 +01:00
default:
break
}
}
2021-02-04 09:30:37 +01:00
func handle(pushNotification: PushNotification, identityId: Identity.Id) {
2021-02-04 21:09:05 +01:00
if identityId != navigationViewModel?.identityContext.identity.id {
identitySelected(id: identityId, immediate: false, notify: true)
}
2021-02-04 22:33:29 +01:00
$navigationViewModel.first { $0?.identityContext.identity.id == identityId }
// Ensure views are set up if switching accounts
.delay(for: .milliseconds(1), scheduler: DispatchQueue.main)
.sink { $0?.navigate(pushNotification: pushNotification) }
.store(in: &cancellables)
2021-02-04 21:09:05 +01:00
}
func notifyIdentityChange(identityContext: IdentityContext) {
let content = UserNotificationService.MutableContent()
content.body = String.localizedStringWithFormat(
NSLocalizedString("notification.signed-in-as-%@", comment: ""),
identityContext.identity.handle)
content.userInfo[Self.identityChangeNotificationUserInfoKey] = true
let request = UserNotificationService.Request(identifier: UUID().uuidString, content: content, trigger: nil)
userNotificationService.add(request: request)
.sink { _ in } receiveValue: { _ in }
.store(in: &cancellables)
2021-02-04 09:30:37 +01:00
}
2020-09-09 14:05:43 +02:00
}