metatext/ViewModels/Sources/ViewModels/View Models/AddIdentityViewModel.swift
2021-01-28 17:14:34 -08:00

116 lines
4 KiB
Swift

// Copyright © 2020 Metabolist. All rights reserved.
import Combine
import Foundation
import Mastodon
import ServiceLayer
public enum AddIdentityError: Error {
case unableToConnectToInstance
case instanceNotSupported
}
public final class AddIdentityViewModel: ObservableObject {
@Published public var urlFieldText = ""
@Published public var alertItem: AlertItem?
@Published public private(set) var loading = false
@Published public private(set) var url: URL?
@Published public private(set) var instance: Instance?
@Published public private(set) var isPublicTimelineAvailable = false
private let allIdentitiesService: AllIdentitiesService
private let instanceURLService: InstanceURLService
private var cancellables = Set<AnyCancellable>()
init(allIdentitiesService: AllIdentitiesService, instanceURLService: InstanceURLService) {
self.allIdentitiesService = allIdentitiesService
self.instanceURLService = instanceURLService
let url = $urlFieldText
.throttle(for: .seconds(Self.textFieldThrottleInterval), scheduler: DispatchQueue.global(), latest: true)
.removeDuplicates()
.flatMap {
instanceURLService.url(text: $0).publisher
.map { $0 as URL? }
.replaceError(with: nil)
}
.share()
url.receive(on: DispatchQueue.main).assign(to: &$url)
url.flatMap { url -> AnyPublisher<Instance?, Never> in
guard let url = url else {
return Just(nil).eraseToAnyPublisher()
}
return instanceURLService.instance(url: url)
.map { $0 as Instance? }
.replaceError(with: nil)
.eraseToAnyPublisher()
}
.receive(on: DispatchQueue.main)
.assign(to: &$instance)
url.flatMap { url -> AnyPublisher<Bool, Never> in
guard let url = url else {
return Just(false).eraseToAnyPublisher()
}
return instanceURLService.isPublicTimelineAvailable(url: url)
.replaceError(with: false)
.eraseToAnyPublisher()
}
.receive(on: DispatchQueue.main)
.assign(to: &$isPublicTimelineAvailable)
}
}
public extension AddIdentityViewModel {
func logInTapped() {
addIdentity(kind: .authentication)
}
func browseTapped() {
addIdentity(kind: .browsing)
}
func registrationViewModel(instance: Instance, url: URL) -> RegistrationViewModel {
RegistrationViewModel(instance: instance, url: url, allIdentitiesService: allIdentitiesService)
}
}
private extension AddIdentityViewModel {
private static let textFieldThrottleInterval: TimeInterval = 0.5
func addIdentity(kind: AllIdentitiesService.IdentityCreation) {
instanceURLService.url(text: urlFieldText).publisher
.map { ($0, kind) }
.flatMap(allIdentitiesService.createIdentity(url:kind:))
.receive(on: DispatchQueue.main)
.handleEvents(receiveSubscription: { [weak self] _ in self?.loading = true })
.sink { [weak self] in
guard let self = self else { return }
self.loading = false
if case let .failure(error) = $0 {
if case AuthenticationError.canceled = error {
return
}
let displayedError: Error
if case InstanceURLError.instanceNotSupported = error {
displayedError = AddIdentityError.instanceNotSupported
} else if error is URLError {
displayedError = AddIdentityError.unableToConnectToInstance
} else {
displayedError = error
}
self.alertItem = AlertItem(error: displayedError)
}
} receiveValue: { _ in }
.store(in: &cancellables)
}
}