AutoCat/AutoCat/Screens/SearchScreen/SearchViewModel.swift

193 lines
5.3 KiB
Swift

//
// SearchViewModel.swift
// AutoCat
//
// Created by Selim Mustafaev on 17.02.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import AutoCatCore
import SwiftUI
import Combine
@MainActor
@Observable
final class SearchViewModel: ACHudContainer {
let apiService: ApiServiceProtocol
let vehicleService: VehicleServiceProtocol
var coordinator: SearchCoordinator?
var hud: ACHud?
@ObservationIgnored
var vehicles: [VehicleDto] = []
var vehicleSections: [DateSection<VehicleDto>] = []
var filter = Filter()
var pageToken: String?
var hasMoreData: Bool = true
var vehiclesCount: Int = 0
var searchText: String = "" {
didSet {
if searchText != oldValue {
filter.searchString = searchText
searchTask = Task { [filter] in
do {
try await Task.sleep(for: .milliseconds(500))
await reloadData(with: filter)
} catch {}
}
}
}
}
@ObservationIgnored
@AutoCancellable
var searchTask: Task<Void, Never>?
var vehiclesArchive: VehiclesArchive {
VehiclesArchive(apiService: apiService, filter: filter)
}
init(apiService: ApiServiceProtocol,
vehicleService: VehicleServiceProtocol) {
self.apiService = apiService
self.vehicleService = vehicleService
}
func onAppear() async {
guard vehicles.isEmpty else {
return
}
resetData()
await wrapWithToast { [weak self] in
guard let self else { return }
try await loadSearchResults(filter: filter)
}
}
func resetData() {
vehicles = []
vehicleSections = []
pageToken = nil
}
func loadSearchResults(filter: Filter) async throws {
let response = try await apiService.getVehicles(with: filter, pageToken: pageToken, pageSize: 20)
if response.items.isEmpty {
hasMoreData = false
} else {
hasMoreData = response.pageToken != nil
vehicles += response.items
pageToken = response.pageToken
vehiclesCount = response.count ?? 0
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
}
}
func loadMoreData() async {
do {
try await loadSearchResults(filter: filter)
} catch {
if !error.isCanceled {
hasMoreData = false
hud = .error(error)
}
}
}
func reloadData() async {
await reloadData(with: filter)
}
func reloadData(with filter: Filter) async {
resetData()
do {
try await loadSearchResults(filter: filter)
} catch {
if !error.isCanceled {
hasMoreData = false
hud = .error(error)
}
}
}
func openReport(vehicle: VehicleDto) async {
guard let updatedVehicle = await coordinator?.openReport(vehicle: vehicle) else {
return
}
if let index = vehicles.firstIndex(where: { $0.number == updatedVehicle.number }) {
vehicles[index] = updatedVehicle
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
}
}
func openFilterDetail() async {
guard let updatedFilter = await coordinator?.openFilterDetail(filter: filter) else {
return
}
filter = updatedFilter
resetData()
await wrapWithToast { [weak self] in
guard let self else { return }
try await loadSearchResults(filter: filter)
}
}
func showOnMap() {
coordinator?.showOnMap(filter: filter)
}
func exportSearchResults() async {
await wrapWithToast { [weak self] in
guard let self else {
return
}
let resp = try await apiService.getVehicles(with: filter, pageToken: nil, pageSize: 0)
let newLine = "\r\n"
var csvString = VehicleDto.csvHeader + newLine
for vehicle in resp.items {
csvString.append(vehicle.csvLine)
csvString.append(newLine)
}
let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv")
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
coordinator?.export(url: tmpUrl)
}
}
func updateVehicle(_ vehicle: VehicleDto) async {
do {
hud = .progress
let (updatedVehicle, errors) = try await vehicleService.updateSearch(number: vehicle.number)
if !errors.isEmpty {
showErrors(errors)
return
}
if let index = vehicles.firstIndex(where: { $0.number == updatedVehicle.number }) {
vehicles[index] = updatedVehicle
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
}
hud = nil
} catch {
hud = .error(error)
}
}
}