Skip to content
Snippets Groups Projects
SearchRightViewModel.swift 4.04 KiB
Newer Older
import Shared
import Combine
import XXModels
import XXClient
import Foundation
import ReportingFeature
Bruno Muniz's avatar
Bruno Muniz committed
import XXMessengerClient
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 database: Database
    @Dependency var permissions: PermissionHandling
    @Dependency var reportingStatus: ReportingStatus
Bruno Muniz's avatar
Bruno Muniz committed
    var foundPublisher: AnyPublisher<XXModels.Contact, Never> {
        foundSubject.eraseToAnyPublisher()
    }

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

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

Bruno Muniz's avatar
Bruno Muniz committed
    private let foundSubject = PassthroughSubject<XXModels.Contact, Never>()
    private let cameraSemaphoreSubject = PassthroughSubject<Bool, Never>()
    private(set) var statusSubject = CurrentValueSubject<ScanningStatus, Never>(.reading)

Bruno Muniz's avatar
Bruno Muniz committed
    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
        ///
Bruno Muniz's avatar
Bruno Muniz committed
        let user = XXClient.Contact.live(data)
Bruno Muniz's avatar
Bruno Muniz committed
        guard
            let uid = try? user.getId(),
            let facts = try? user.getFacts(),
            let username = facts.first(where: { $0.type == .username })?.value
Bruno Muniz's avatar
Bruno Muniz committed
        else {
            let errorTitle = Localized.Scan.Error.invalid
            statusSubject.send(.failed(.unknown(errorTitle)))
            return
        }

        let email = facts.first { $0.type == .email }?.value
        let phone = facts.first { $0.type == .phone }?.value
        /// Make sure we are not processing a contact
        /// that we already have
        ///
Bruno Muniz's avatar
Bruno Muniz committed
        if let alreadyContact = try? database.fetchContacts(.init(id: [uid])).first {
            if alreadyContact.isBlocked, reportingStatus.isEnabled() {
                statusSubject.send(.failed(.unknown("You previously blocked this user.")))
                return
            }

            if alreadyContact.isBanned, reportingStatus.isEnabled() {
                statusSubject.send(.failed(.unknown("This user was banned.")))
                return
            }

            /// 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(
Bruno Muniz's avatar
Bruno Muniz committed
            id: uid,
            marshaled: data,
            username: username,
            email: email,
            phone: phone,
            nickname: nil,
            photo: nil,
            authStatus: .stranger,
            isRecent: false,
            createdAt: Date()
        ))
    }
}