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