// // AppearanceStore.swift // Zyquo Router // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // User appearance preferences: theme mode, accent choice, font size. // Persisted to appearance.json via PersistenceService. Family design with // the Router's accent set: cyan (default) + graphite, sky, emerald, violet, // copper. // import SwiftUI /// Accent color choices in Settings → Appearance. enum AccentChoice: String, Codable, CaseIterable, Identifiable { case cyan case graphite case sky case emerald case violet case copper var id: String { rawValue } var displayName: String { switch self { case .cyan: return "Signal Cyan" case .graphite: return "Graphite" case .sky: return "Sky" case .emerald: return "Emerald" case .violet: return "Violet" case .copper: return "Copper" } } /// (light, dark) accent hex pair. var accentHex: (UInt32, UInt32) { switch self { case .cyan: return (0x0891B2, 0x22B8D4) case .graphite: return (0x374151, 0x9AA4B2) case .sky: return (0x2E7CD6, 0x5A9BE3) case .emerald: return (0x1E9E6A, 0x3CBA87) case .violet: return (0x7451D8, 0x9273E4) case .copper: return (0xB2622B, 0xC97F4B) } } /// (light, dark) subtle-tint hex pair (selected rows, active tints). var subtleHex: (UInt32, UInt32) { switch self { case .cyan: return (0xE5F5F9, 0x143A44) case .graphite: return (0xEEF1F4, 0x2A2F36) case .sky: return (0xE8F1FC, 0x1B3350) case .emerald: return (0xE6F6EF, 0x173B2C) case .violet: return (0xF0EBFB, 0x2C2347) case .copper: return (0xF9EFE6, 0x3D2C1D) } } } enum ThemeMode: String, Codable, CaseIterable, Identifiable { case system, light, dark var id: String { rawValue } var displayName: String { switch self { case .system: return "System" case .light: return "Light" case .dark: return "Dark" } } var colorScheme: ColorScheme? { switch self { case .system: return nil case .light: return .light case .dark: return .dark } } } /// Observable appearance preferences, persisted with the app settings. @MainActor final class AppearanceStore: ObservableObject { struct Stored: Codable { var themeMode: ThemeMode = .system var accent: AccentChoice = .cyan var fontSize: Double = 13 } static let fileName = "appearance.json" @Published var themeMode: ThemeMode { didSet { save() } } @Published var accent: AccentChoice { didSet { save() } } /// Base body font size, clamped to 12–16pt. @Published var fontSize: Double { didSet { let clamped = min(16, max(12, fontSize)) if clamped != fontSize { fontSize = clamped } save() } } private let persistence: PersistenceService init(persistence: PersistenceService = .shared) { self.persistence = persistence let stored = persistence.load(Stored.self, from: Self.fileName) ?? Stored() themeMode = stored.themeMode accent = stored.accent fontSize = stored.fontSize } /// Current accent color resolved for the active appearance. var accentColor: Color { let (light, dark) = accent.accentHex return ZyquoColor.dynamic(light: light, dark: dark) } /// Current subtle accent tint resolved for the active appearance. var accentSubtleColor: Color { let (light, dark) = accent.subtleHex return ZyquoColor.dynamic(light: light, dark: dark) } private func save() { persistence.save( Stored(themeMode: themeMode, accent: accent, fontSize: fontSize), to: Self.fileName ) } }