Skip to content
Snippets Groups Projects
SearchRightViewModel.swift 3.40 KiB
import Shared
import Combine
import XXModels
import Foundation
import Permissions
import Integration
import DependencyInjection

enum ScanningStatus: Equatable {
    case reading
    case processing
    case success
    case failed(ScanningError)
}

enum ScanningError: Equatable {
    case requestOpened
    case unknown(String)
    case cameraPermission
    case alreadyFriends(String)
}

final class SearchRightViewModel {
    @Dependency var session: SessionType
    @Dependency var permissions: PermissionHandling

    var foundPublisher: AnyPublisher<Contact, Never> {
        foundSubject.eraseToAnyPublisher()
    }

    var cameraSemaphorePublisher: AnyPublisher<Bool, Never> {
        cameraSemaphoreSubject.eraseToAnyPublisher()
    }

    var statusPublisher: AnyPublisher<ScanningStatus, Never> {
        statusSubject.eraseToAnyPublisher()
    }

    private let foundSubject = PassthroughSubject<Contact, Never>()
    private let cameraSemaphoreSubject = PassthroughSubject<Bool, Never>()
    private(set) var statusSubject = CurrentValueSubject<ScanningStatus, Never>(.reading)

    func viewWillAppear() {
        permissions.requestCamera { [weak self] granted in
            guard let self = self else { return }

            if granted {
                self.statusSubject.value = .reading
                self.cameraSemaphoreSubject.send(true)
            } else {
                self.statusSubject.send(.failed(.cameraPermission))
            }
        }
    }

    func viewWillDisappear() {
        cameraSemaphoreSubject.send(false)
    }

    func didScan(data: Data) {
        /// We need to be accepting new readings in order
        /// to process what just got scanned.
        ///
        guard statusSubject.value == .reading else { return }
        statusSubject.send(.processing)

        /// Whatever got scanned, needs to have id and username
        /// otherwise is just noise or an unknown qr code
        ///
        guard let userId = session.getId(from: data),
              let username = try? session.extract(fact: .username, from: data) else {
            let errorTitle = Localized.Scan.Error.invalid
            statusSubject.send(.failed(.unknown(errorTitle)))
            return
        }

        /// Make sure we are not processing a contact
        /// that we already have
        ///
        if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first {
            /// Show error accordingly to the auth status
            ///
            if alreadyContact.authStatus == .friend {
                statusSubject.send(.failed(.alreadyFriends(username)))
            } else if [.requested, .verified].contains(alreadyContact.authStatus) {
                statusSubject.send(.failed(.requestOpened))
            } else {
                let generalErrorTitle = Localized.Scan.Error.general
                statusSubject.send(.failed(.unknown(generalErrorTitle)))
            }

            return
        }

        statusSubject.send(.success)
        cameraSemaphoreSubject.send(false)

        foundSubject.send(.init(
            id: userId,
            marshaled: data,
            username: username,
            email: try? session.extract(fact: .email, from: data),
            phone: try? session.extract(fact: .phone, from: data),
            nickname: nil,
            photo: nil,
            authStatus: .stranger,
            isRecent: false,
            createdAt: Date()
        ))
    }
}