CarnetViewModel.swift
91 lignes · 3111 octets
import Combine import Foundation // Port of ui/carnet/CarnetViewModel.kt. Kotlin observed Room flows; on iOS the // DAOs are fetch-based, so the view model refreshes on demand and on the // database-change notification posted by the persistence layer. @MainActor final class CarnetViewModel: ObservableObject { @Published private(set) var sort: ContactSort = .lastName @Published private(set) var query: String = "" @Published private(set) var sections: [AlphabetIndex.Section] = [] @Published private(set) var duplicateCountById: [Int64: Int] = [:] /// Sync badge per contact id (blue = queued, red = failed). @Published private(set) var badges: [Int64: ContactSyncBadge] = [:] let repository: ContactRepository private let syncOpDao: SyncOpDao? private var cancellables: Set<AnyCancellable> = [] private var refreshTask: Task<Void, Never>? init( repository: ContactRepository = ContactRepository( dao: Card2vcfDatabase.shared.crmContactDao, images: ContactImageStore() ), syncOpDao: SyncOpDao? = Card2vcfDatabase.shared.syncOpDao ) { self.repository = repository self.syncOpDao = syncOpDao NotificationCenter.default.publisher(for: .card2vcfDatabaseDidChange) .receive(on: RunLoop.main) .sink { [weak self] _ in self?.scheduleRefresh() } .store(in: &cancellables) } var isEmpty: Bool { sections.isEmpty || sections.allSatisfy { $0.contacts.isEmpty } } var presentLetters: Set<Character> { Set(sections.map { $0.letter }) } func setQuery(_ value: String) { query = value scheduleRefresh() } func setSort(_ value: ContactSort) { sort = value scheduleRefresh() } func scheduleRefresh() { refreshTask?.cancel() refreshTask = Task { await refresh() } } func refresh() async { do { let all = try await repository.fetchAll() let list: [CrmContactEntity] if query.isBlank { list = all } else { list = try await repository.search(query) } let exclusions = (try? await repository.getExclusions()) ?? [] let clusters = DuplicateDetector.clusters(contacts: all, exclusions: exclusions) var ops: [SyncOpEntity] = [] if let syncOpDao { ops = (try? await syncOpDao.listAll()) ?? [] } guard !Task.isCancelled else { return } sections = AlphabetIndex.group(contacts: list, sort: sort) duplicateCountById = Dictionary( uniqueKeysWithValues: all.map { ($0.id, DuplicateDetector.duplicateCount($0.id, clusters: clusters)) } ) badges = ContactSyncBadges.compute(contacts: all, ops: ops) } catch { guard !Task.isCancelled else { return } sections = [] duplicateCountById = [:] badges = [:] } } }
GitRust