Skip to content
Snippets Groups Projects
SearchLeftViewModel.swift 5.43 KiB
Newer Older
import HUD
import UIKit
Bruno Muniz's avatar
Bruno Muniz committed
import Shared
import Combine
import XXModels
import Countries
import Integration
import DependencyInjection

typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>

struct SearchLeftViewState {
    var input = ""
    var snapshot: SearchSnapshot?
    var country: Country = .fromMyPhone()
    var item: SearchSegmentedControl.Item = .username
final class SearchLeftViewModel {
    @Dependency var session: SessionType
    @Dependency var networkMonitor: NetworkMonitoring

    var hudPublisher: AnyPublisher<HUDStatus, Never> {
        hudSubject.eraseToAnyPublisher()
    }

    var successPublisher: AnyPublisher<Contact, Never> {
        successSubject.eraseToAnyPublisher()
    }

    var statePublisher: AnyPublisher<SearchLeftViewState, Never> {
        stateSubject.eraseToAnyPublisher()
    }

    private var invitation: String?
    private var searchCancellables = Set<AnyCancellable>()
    private let successSubject = PassthroughSubject<Contact, Never>()
    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
    private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
    private var networkCancellable = Set<AnyCancellable>()

    init(_ invitation: String? = nil) {
        self.invitation = invitation
    }

    func viewDidAppear() {
        if let pendingInvitation = invitation {
            invitation = nil
            stateSubject.value.input = pendingInvitation
            hudSubject.send(.onAction(Localized.Ud.Search.cancel))

Bruno Muniz's avatar
Bruno Muniz committed
            networkCancellable.removeAll()

            networkMonitor.statusPublisher
                .first { $0 == .available }
                .eraseToAnyPublisher()
                .flatMap { _ in self.session.waitForNodes(timeout: 5) }
                .sink {
                    if case .failure(let error) = $0 {
                        self.hudSubject.send(.error(.init(with: error)))
                    }
                } receiveValue: {
                    self.didStartSearching()
                }.store(in: &networkCancellable)

    func didEnterInput(_ string: String) {
        stateSubject.value.input = string
    }

    func didPick(country: Country) {
        stateSubject.value.country = country
    }

    func didSelectItem(_ item: SearchSegmentedControl.Item) {
        stateSubject.value.item = item
    }

    func didTapCancelSearch() {
        searchCancellables.forEach { $0.cancel() }
        searchCancellables.removeAll()
        hudSubject.send(.none)
    }

    func didStartSearching() {
Bruno Muniz's avatar
Bruno Muniz committed
        guard stateSubject.value.input.isEmpty == false else { return }

Bruno Muniz's avatar
Bruno Muniz committed
        hudSubject.send(.onAction(Localized.Ud.Search.cancel))
        var content = stateSubject.value.input
        let prefix = stateSubject.value.item.written.first!.uppercased()

        if stateSubject.value.item == .phone {
            content += stateSubject.value.country.code
        }

        session.search(fact: "\(prefix)\(content)")
            .sink { [unowned self] in
                if case .failure(let error) = $0 {
                    self.appendToLocalSearch(nil)
                    self.hudSubject.send(.error(.init(with: error)))
                }
            } receiveValue: { contact in
                self.hudSubject.send(.none)
                self.appendToLocalSearch(contact)
            }.store(in: &searchCancellables)
Bruno Muniz's avatar
Bruno Muniz committed
    func didTapResend(contact: Contact) {
        hudSubject.send(.on)

        do {
            try self.session.retryRequest(contact)
            hudSubject.send(.none)
        } catch {
            hudSubject.send(.error(.init(with: error)))
        }
    }

    func didTapRequest(contact: Contact) {
Bruno Muniz's avatar
Bruno Muniz committed
        hudSubject.send(.on)
        var contact = contact
        contact.nickname = contact.username

        do {
            try self.session.add(contact)
            hudSubject.send(.none)
            successSubject.send(contact)
        } catch {
            hudSubject.send(.error(.init(with: error)))
        }
    }

    func didSet(nickname: String, for contact: Contact) {
        if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first {
            contact.nickname = nickname
            _ = try? session.dbManager.saveContact(contact)
        }
    }

    private func appendToLocalSearch(_ user: Contact?) {
        var snapshot = SearchSnapshot()

        if var user = user {
            if let contact = try? session.dbManager.fetchContacts(.init(id: [user.id], isBlocked: false, isBanned: false)).first {
                user.authStatus = contact.authStatus
            }

            if user.authStatus != .friend {
                snapshot.appendSections([.stranger])
                snapshot.appendItems([.stranger(user)], toSection: .stranger)
            }
        let localsQuery = Contact.Query(text: stateSubject.value.input, authStatus: [.friend])
Bruno Muniz's avatar
Bruno Muniz committed
        if let locals = try? session.dbManager.fetchContacts(localsQuery),
           let localsWithoutMe = removeMyself(from: locals),
           localsWithoutMe.isEmpty == false {
            snapshot.appendSections([.connections])
Bruno Muniz's avatar
Bruno Muniz committed
            snapshot.appendItems(
                localsWithoutMe.map(SearchItem.connection),
                toSection: .connections
            )
        }

        stateSubject.value.snapshot = snapshot
    }
Bruno Muniz's avatar
Bruno Muniz committed

    private func removeMyself(from collection: [Contact]) -> [Contact]? {
        collection.filter { $0.id != session.myId }
    }