65 lines
1.6 KiB
Swift
65 lines
1.6 KiB
Swift
//
|
||
// SearchViewModel.swift
|
||
// furumi_macos
|
||
//
|
||
|
||
import Foundation
|
||
import Observation
|
||
|
||
@MainActor
|
||
@Observable
|
||
final class SearchViewModel {
|
||
private var service: CatalogService
|
||
private(set) var results: SearchResult?
|
||
private(set) var isSearching = false
|
||
private(set) var error: String?
|
||
|
||
var query: String = "" {
|
||
didSet { debounceSearch() }
|
||
}
|
||
|
||
private var pendingTask: Task<Void, Never>?
|
||
private let limit = 20
|
||
|
||
init(service: CatalogService) {
|
||
self.service = service
|
||
}
|
||
|
||
func reset(service: CatalogService) {
|
||
self.service = service
|
||
self.results = nil
|
||
self.isSearching = false
|
||
self.error = nil
|
||
self.query = ""
|
||
pendingTask?.cancel()
|
||
pendingTask = nil
|
||
}
|
||
|
||
private func debounceSearch() {
|
||
pendingTask?.cancel()
|
||
let q = query.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
guard !q.isEmpty else { results = nil; error = nil; return }
|
||
pendingTask = Task { [weak self] in
|
||
do {
|
||
try await Task.sleep(nanoseconds: 300_000_000) // 300ms
|
||
} catch {
|
||
return // cancelled — не вызываем поиск
|
||
}
|
||
await self?.performSearch(q)
|
||
}
|
||
}
|
||
|
||
private func performSearch(_ q: String) async {
|
||
guard !q.isEmpty else { return }
|
||
isSearching = true
|
||
error = nil
|
||
defer { isSearching = false }
|
||
do {
|
||
results = try await service.search(query: q, limit: limit)
|
||
} catch {
|
||
self.error = (error as NSError).localizedDescription
|
||
self.results = nil
|
||
}
|
||
}
|
||
}
|