// Auteur : Simon-Pierre Boucher — contact@spboucher.ai // FavoritesStore.swift — favoris et collections UNIFIÉS (tous univers dans une // même collection : « Déménagement à Val-d'Or » peut contenir un 4½, une auto // et un resto). Persistance JSON locale (consultation hors ligne). // Repris de l'app iOS KA, sans haptique. import Foundation import SwiftUI @MainActor final class FavoritesStore: ObservableObject { static let shared = FavoritesStore() struct FavCollection: Identifiable, Codable, Hashable { var id: UUID = UUID() var name: String var items: [KAItem] = [] } @Published private(set) var collections: [FavCollection] = [] { didSet { save() } } private var fileURL: URL { let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent("KA", isDirectory: true) try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) return dir.appendingPathComponent("ka-favoris.json") } init() { if let data = try? Data(contentsOf: fileURL), let saved = try? JSONDecoder().decode([FavCollection].self, from: data) { collections = saved } if collections.isEmpty { collections = [FavCollection(name: "Mes favoris")] } } private func save() { if let data = try? JSONEncoder().encode(collections) { try? data.write(to: fileURL, options: .atomic) } } // MARK: API var allItems: [KAItem] { collections.flatMap(\.items) } var count: Int { collections.reduce(0) { $0 + $1.items.count } } func isFavorite(_ item: KAItem) -> Bool { collections.contains { $0.items.contains { $0.id == item.id } } } func toggle(_ item: KAItem, in collectionID: UUID? = nil) { if isFavorite(item) { for i in collections.indices { collections[i].items.removeAll { $0.id == item.id } } } else { let idx = collections.firstIndex { $0.id == collectionID } ?? 0 collections[idx].items.insert(item, at: 0) } } func addCollection(_ name: String) { let trimmed = name.trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { return } collections.append(FavCollection(name: trimmed)) } func removeCollection(_ id: UUID) { guard collections.count > 1 else { return } collections.removeAll { $0.id == id } } func move(_ item: KAItem, to collectionID: UUID) { for i in collections.indices { collections[i].items.removeAll { $0.id == item.id } } if let idx = collections.firstIndex(where: { $0.id == collectionID }) { collections[idx].items.insert(item, at: 0) } } }