Skip to content
Snippets Groups Projects
LaunchViewModel.swift 7.45 KiB
Newer Older
Ahmed Shehata's avatar
Ahmed Shehata committed
import HUD
import Shared
import Models
Bruno Muniz's avatar
Bruno Muniz committed
import SwiftCSV
Ahmed Shehata's avatar
Ahmed Shehata committed
import Combine
import Defaults
import XXModels
Bruno Muniz's avatar
Bruno Muniz committed
import Keychain
Ahmed Shehata's avatar
Ahmed Shehata committed
import Foundation
import Integration
import Permissions
import DropboxFeature
import VersionChecking
import CombineSchedulers
import DependencyInjection

struct Update {
    let content: String
    let urlString: String
    let positiveActionTitle: String
    let negativeActionTitle: String?
    let actionStyle: CapsuleButtonStyle
}

enum LaunchRoute {
    case chats
    case update(Update)
    case onboarding(String)
}

final class LaunchViewModel {
    @Dependency private var network: XXNetworking
    @Dependency private var versionChecker: VersionChecker
    @Dependency private var dropboxService: DropboxInterface
Bruno Muniz's avatar
Bruno Muniz committed
    @Dependency private var keychainHandler: KeychainHandling
Ahmed Shehata's avatar
Ahmed Shehata committed
    @Dependency private var permissionHandler: PermissionHandling

    @KeyObject(.username, defaultValue: nil) var username: String?
    @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool

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

    var routePublisher: AnyPublisher<LaunchRoute, Never> {
        routeSubject.eraseToAnyPublisher()
    }

Bruno Muniz's avatar
Bruno Muniz committed
    var mainScheduler: AnySchedulerOf<DispatchQueue> = {
        DispatchQueue.main.eraseToAnyScheduler()
    }()

Ahmed Shehata's avatar
Ahmed Shehata committed
    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = {
        DispatchQueue.global().eraseToAnyScheduler()
    }()

    var getSession: (String) throws -> SessionType = Session.init

    private var cancellables = Set<AnyCancellable>()
    private let routeSubject = PassthroughSubject<LaunchRoute, Never>()
    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)

    func viewDidAppear() {
Bruno Muniz's avatar
Bruno Muniz committed
        mainScheduler.schedule(after: .init(.now() + 1)) { [weak self] in
Ahmed Shehata's avatar
Ahmed Shehata committed
            guard let self = self else { return }

Bruno Muniz's avatar
Bruno Muniz committed
            self.hudSubject.send(.on)
Ahmed Shehata's avatar
Ahmed Shehata committed

Bruno Muniz's avatar
Bruno Muniz committed
            self.versionChecker().sink { [unowned self] in
                switch $0 {
                case .upToDate:
                    self.versionApproved()
                case .failure(let error):
                    self.versionFailed(error: error)
                case .updateRequired(let info):
                    self.versionUpdateRequired(info)
                case .updateRecommended(let info):
                    self.versionUpdateRecommended(info)
Ahmed Shehata's avatar
Ahmed Shehata committed
                }
Bruno Muniz's avatar
Bruno Muniz committed
            }.store(in: &self.cancellables)
        }
    }
Ahmed Shehata's avatar
Ahmed Shehata committed

Bruno Muniz's avatar
Bruno Muniz committed
    func versionApproved() {
        Task {
            do {
                network.writeLogs()
Bruno Muniz's avatar
Bruno Muniz committed
                let _ = try await fetchBannedList()
Ahmed Shehata's avatar
Ahmed Shehata committed

Bruno Muniz's avatar
Bruno Muniz committed
                network.updateNDF { [weak self] in
Ahmed Shehata's avatar
Ahmed Shehata committed
                    guard let self = self else { return }

Bruno Muniz's avatar
Bruno Muniz committed
                    switch $0 {
                    case .success(let ndf):
                        self.network.updateErrors()

                        guard self.network.hasClient else {
                            self.hudSubject.send(.none)
                            self.routeSubject.send(.onboarding(ndf))
                            self.dropboxService.unlink()
                            try? self.keychainHandler.clear()
                            return
                        }

                        guard self.username != nil else {
                            self.network.purgeFiles()
                            self.hudSubject.send(.none)
                            self.routeSubject.send(.onboarding(ndf))
                            self.dropboxService.unlink()
                            try? self.keychainHandler.clear()
                            return
                        }

                        self.backgroundScheduler.schedule { [weak self] in
                            guard let self = self else { return }

                            do {
                                let session = try self.getSession(ndf)
                                DependencyInjection.Container.shared.register(session as SessionType)
                                self.hudSubject.send(.none)
                                self.checkBiometrics()
                            } catch {
                                self.hudSubject.send(.error(HUDError(with: error)))
                            }
                        }
                    case .failure(let error):
Ahmed Shehata's avatar
Ahmed Shehata committed
                        self.hudSubject.send(.error(HUDError(with: error)))
                    }
                }
Bruno Muniz's avatar
Bruno Muniz committed
            } catch {
Ahmed Shehata's avatar
Ahmed Shehata committed
                self.hudSubject.send(.error(HUDError(with: error)))
            }
        }
    }

    func getContactWith(userId: Data) -> Contact? {
        guard let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
              let contact = try? session.dbManager.fetchContacts(.init(id: [userId])).first else {
            return nil
        }

        return contact
Ahmed Shehata's avatar
Ahmed Shehata committed
    }

    func getGroupInfoWith(groupId: Data) -> GroupInfo? {
        guard let session: SessionType = try? DependencyInjection.Container.shared.resolve(),
              let info = try? session.dbManager.fetchGroupInfos(.init(groupId: groupId)).first else {
            return nil
        }

        return info
Ahmed Shehata's avatar
Ahmed Shehata committed
    }

    private func versionFailed(error: Error) {
        let title = Localized.Launch.Version.failed
        let content = error.localizedDescription
        let hudError = HUDError(content: content, title: title, dismissable: false)

        hudSubject.send(.error(hudError))
    }

    private func versionUpdateRequired(_ info: DappVersionInformation) {
        hudSubject.send(.none)

        let model = Update(
            content: info.minimumMessage,
            urlString: info.appUrl,
            positiveActionTitle: Localized.Launch.Version.Required.positive,
            negativeActionTitle: nil,
            actionStyle: .brandColored
        )

        routeSubject.send(.update(model))
    }

    private func versionUpdateRecommended(_ info: DappVersionInformation) {
        hudSubject.send(.none)

        let model = Update(
            content: Localized.Launch.Version.Recommended.title,
            urlString: info.appUrl,
            positiveActionTitle: Localized.Launch.Version.Recommended.positive,
            negativeActionTitle: Localized.Launch.Version.Recommended.negative,
            actionStyle: .simplestColoredRed
        )

        routeSubject.send(.update(model))
    }

    private func checkBiometrics() {
        if permissionHandler.isBiometricsAvailable && isBiometricsOn {
            permissionHandler.requestBiometrics { [weak self] in
                guard let self = self else { return }

Ahmed Shehata's avatar
Ahmed Shehata committed
                switch $0 {
                case .success(let granted):
                    guard granted else { return }
                    self.routeSubject.send(.chats)
Ahmed Shehata's avatar
Ahmed Shehata committed

                case .failure(let error):
                    self.hudSubject.send(.error(HUDError(with: error)))
Ahmed Shehata's avatar
Ahmed Shehata committed
                }
            }
        } else {
            self.routeSubject.send(.chats)
Ahmed Shehata's avatar
Ahmed Shehata committed
        }
    }
Bruno Muniz's avatar
Bruno Muniz committed

    private func fetchBannedList() async throws -> Data {
Bruno Muniz's avatar
Bruno Muniz committed
        let url = URL(string: "https://elixxir-bins.s3.us-west-1.amazonaws.com/client/bannedUsers/banned.csv")
Bruno Muniz's avatar
Bruno Muniz committed
        return try await withCheckedThrowingContinuation { continuation in
Bruno Muniz's avatar
Bruno Muniz committed
            URLSession.shared.dataTask(with: url!) { data, _, error in
Bruno Muniz's avatar
Bruno Muniz committed
                if let error = error {
                    return continuation.resume(throwing: error)
                }

                guard let data = data else { fatalError("?") }
                return continuation.resume(returning: data)
            }.resume()
        }
    }
Ahmed Shehata's avatar
Ahmed Shehata committed
}