AutoCat/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift

235 lines
6.4 KiB
Swift

//
// HistoryViewModel.swift
// AutoCat
//
// Created by Selim Mustafaev on 15.01.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
import AutoCatCore
enum CheckType {
case normal
case update
case record(VehicleEventDto?)
}
@MainActor
@Observable
final class HistoryViewModel: ACHudContainer {
let apiService: ApiServiceProtocol
let storageService: StorageServiceProtocol
let vehicleService: VehicleServiceProtocol
var hud: ACHud?
var vehicles: [VehicleDto] = []
var vehiclesFiltered: [VehicleDto] = []
var vehicleSections: [DateSection<VehicleDto>] = []
var vehicleToOpen: VehicleDto?
var selectedVehicleId: VehicleDto.ID?
var selectedVehicle: VehicleDto? {
guard let id = selectedVehicleId else {
return nil
}
if let vehicle = vehicles.first(where: { $0.id == id }) {
return vehicle
} else {
return nil
}
}
var searchText: String = "" {
didSet {
if searchText != oldValue {
applyFilters()
}
}
}
var filter: HistoryFilter = .all
var vehiclesArchive: VehiclesArchive {
VehiclesArchive(vehiles: vehiclesFiltered)
}
var vehiclesCount: Int {
vehicleSections.reduce(0, { $0 + $1.elements.count })
}
var dbFileURL: URL?
init(apiService: ApiServiceProtocol,
storageService: StorageServiceProtocol,
vehicleService: VehicleServiceProtocol) {
self.apiService = apiService
self.storageService = storageService
self.vehicleService = vehicleService
}
func onAppear() async {
guard vehicles.isEmpty else {
return
}
async let loadTask: () = loadVehicles()
async let dbUrlTask = storageService.dbFileURL
await loadTask
dbFileURL = await dbUrlTask
}
func loadVehicles() async {
vehicles = await storageService.loadVehicles()
applyFilters()
}
func applyFilters() {
vehiclesFiltered = filterType(vehicles)
vehiclesFiltered = filterSearch(vehiclesFiltered)
vehicleSections = vehiclesFiltered.groupedByDate(type: .updatedDate)
}
func filterSearch(_ vehicles: [VehicleDto]) -> [VehicleDto] {
guard !searchText.isEmpty else {
return vehicles
}
let text = searchText.uppercased()
let regex = try? Regex(text)
return vehicles.filter { vehicle in
let number = vehicle.getNumber()
if let regex {
return number.contains(regex)
} else {
return number.contains(text)
}
}
}
func filterType(_ vehicles: [VehicleDto]) -> [VehicleDto] {
guard filter != .all else {
return vehicles
}
return vehicles.filter { vehicle in
switch filter {
case .unrecognized: vehicle.unrecognized
case .outdated: vehicle.outdated
case .notSynced: vehicle.needSync
case .all: true
}
}
}
func checkVehicle(
number: String,
numberType: VehicleNumberType = .plateNumber,
checkType: CheckType,
trackLocation: Bool = true
) async {
do {
hud = .progress
let (vehicle, errors) = switch checkType {
case .normal: try await vehicleService.check(number: number, numberType: numberType, trackLocation: trackLocation)
case .update: try await vehicleService.updateHistory(number: number)
case .record(let event): try await vehicleService.checkRecord(number: number, event: event)
}
await loadVehicles()
if errors.isEmpty {
hud = nil
if !vehicle.unrecognized {
openVehicleDetail(vehicle, numberType: numberType)
}
} else {
showErrors(errors)
}
} catch {
hud = .error(error)
}
}
func openVehicleDetail(_ vehicle: VehicleDto, numberType: VehicleNumberType) {
if Device.isIPhone {
switch numberType {
case .plateNumber:
Router.shared.openLocalReport(vehicle: vehicle)
case .vin:
Router.shared.openSharedReport(vehicle: vehicle)
}
} else {
selectedVehicleId = vehicle.id
}
}
func checkNewNumber(_ number: String, numberType: VehicleNumberType, enableLocation: Bool) async {
await checkVehicle(
number: number,
numberType: numberType,
checkType: .normal,
trackLocation: enableLocation
)
}
func deleteVehicle(_ vehicle: VehicleDto) async {
await wrapWithToast(showProgress: false) { [weak self] in
guard let self else { throw GenericError.somethingWentWrong }
try await storageService.deleteVehicle(number: vehicle.getNumber())
await loadVehicles()
}
}
func updateVehicle(_ vehicle: VehicleDto) async {
await checkVehicle(number: vehicle.getNumber(), checkType: .update)
}
func checkRecord(number: String, event: VehicleEventDto?) async {
await checkVehicle(number: number, checkType: .record(event))
}
func onVehicleChanged(_ vehicle: VehicleDto) {
Task {
await loadVehicles()
}
}
func onOpenUrl(_ url: URL) {
guard let param = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first,
let token = param.value,
let jwt = JWT<NumberPayload>(string: token)
else {
return
}
Task {
await openReport(number: jwt.payload.plateNumber)
}
}
func openReport(number: String) async {
await wrapWithToast { [weak self] in
guard let self else { return }
let vehicle = try await apiService.getReport(for: number)
if Device.isIPhone {
Router.shared.openSharedReport(vehicle: vehicle)
} else {
vehicleToOpen = vehicle
}
}
}
}