DuplicateReviewViewModel.swift
121 lignes · 3909 octets
import Combine import Foundation // Port of ui/contact/DuplicateReviewViewModel.kt. @MainActor final class DuplicateReviewViewModel: ObservableObject { @Published private(set) var cluster: [CrmContactEntity] = [] @Published private(set) var selectedIds: Set<Int64> = [] @Published private(set) var merging = false @Published private(set) var mergeDoneKeepId: Int64? let contactId: Int64 private let repository: ContactRepository private var cancellables: Set<AnyCancellable> = [] init( contactId: Int64, repository: ContactRepository = ContactRepository( dao: Card2vcfDatabase.shared.crmContactDao, images: ContactImageStore() ) ) { self.contactId = contactId self.repository = repository NotificationCenter.default.publisher(for: .card2vcfDatabaseDidChange) .receive(on: RunLoop.main) .sink { [weak self] _ in Task { await self?.refresh() } } .store(in: &cancellables) } func refresh() async { do { let list = try await repository.fetchAll() let exclusions = try await repository.getExclusions() let clusters = DuplicateDetector.clusters(contacts: list, exclusions: exclusions) let memberIds = clusters[contactId] ?? [] cluster = list.filter { memberIds.contains($0.id) }.sorted { $0.id < $1.id } } catch { cluster = [] } } func toggleSelect(_ id: Int64) { guard id != contactId else { return } if selectedIds.contains(id) { selectedIds.remove(id) } else { selectedIds.insert(id) } } func deleteSelected() { let ids = selectedIds guard !ids.isEmpty else { return } Task { for id in ids { try? await repository.delete(id) } selectedIds = [] await refresh() } } func markSelectedDifferent() { let ids = selectedIds guard !ids.isEmpty else { return } Task { for id in ids { try? await repository.markDifferent(contactId, id) } selectedIds = [] await refresh() } } func startMerge() { merging = true } func cancelMerge() { merging = false } /// Sources = current contact + selection (or every other member when the /// selection is empty). func mergeSources(_ cluster: [CrmContactEntity]) -> [CrmContactEntity] { let others = cluster.filter { $0.id != contactId } let peers = selectedIds.isEmpty ? others : others.filter { selectedIds.contains($0.id) } let primary = cluster.first { $0.id == contactId } return (primary.map { [$0] } ?? []) + peers } func defaultChoices(_ sources: [CrmContactEntity]) -> MergeFieldChoices { let keep = sources.contains { $0.id == contactId } ? contactId : sources[0].id return MergeFieldChoices( fullNameFromId: keep, firstNameFromId: keep, lastNameFromId: keep, companyFromId: keep, jobTitleFromId: keep, websiteFromId: keep, addressFromId: keep, notesMode: .keepPrimary, cardImageFromId: sources.first { $0.cardImagePath?.nilIfBlank != nil }?.id, profileImageFromId: sources.first { $0.profileImagePath?.nilIfBlank != nil }?.id, keepId: keep ) } func confirmMerge(sources: [CrmContactEntity], choices: MergeFieldChoices) { guard sources.count >= 2 else { return } Task { try? await repository.mergeContacts(sources: sources, choices: choices) merging = false selectedIds = [] mergeDoneKeepId = choices.keepId } } }
GitRust