spb/zyquo-cloud Public MIT
Native macOS AI chat client for 12 cloud providers — your keys, every cloud model, one beautiful chat.
Swift 97.4%
Shell 1.7%
Makefile 1%
1//2// AppearanceStore.swift3// Zyquo Cloud4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// User appearance preferences: theme mode, accent choice, chat font size.9// Persisted to settings.json via PersistenceService.10//1112import SwiftUI1314/// Accent color choices offered in Settings → Appearance. Sky-indigo is the15/// flagship default; alternates keep the same subtle-tint relationship.16enum AccentChoice: String, Codable, CaseIterable, Identifiable {17 case skyIndigo18 case graphite19 case teal20 case amber21 case rose2223 var id: String { rawValue }2425 var displayName: String {26 switch self {27 case .skyIndigo: return "Sky Indigo"28 case .graphite: return "Graphite"29 case .teal: return "Teal"30 case .amber: return "Amber"31 case .rose: return "Rose"32 }33 }3435 /// (light, dark) accent hex pair.36 var accentHex: (UInt32, UInt32) {37 switch self {38 case .skyIndigo: return (0x4E6AF0, 0x6D84F5)39 case .graphite: return (0x5B6270, 0x8A93A6)40 case .teal: return (0x1D9A8F, 0x3BB5AA)41 case .amber: return (0xC77D1D, 0xDD9B45)42 case .rose: return (0xC94F7C, 0xDE7099)43 }44 }4546 /// (light, dark) subtle-tint hex pair (selected rows, user bubbles).47 var subtleHex: (UInt32, UInt32) {48 switch self {49 case .skyIndigo: return (0xEBEFFD, 0x28304C)50 case .graphite: return (0xEEF0F4, 0x2A2E3A)51 case .teal: return (0xE6F6F4, 0x1F3A38)52 case .amber: return (0xFBF1E3, 0x3D3222)53 case .rose: return (0xFAEAF1, 0x3D2733)54 }55 }56}5758enum ThemeMode: String, Codable, CaseIterable, Identifiable {59 case system, light, dark60 var id: String { rawValue }6162 var displayName: String {63 switch self {64 case .system: return "System"65 case .light: return "Light"66 case .dark: return "Dark"67 }68 }6970 var colorScheme: ColorScheme? {71 switch self {72 case .system: return nil73 case .light: return .light74 case .dark: return .dark75 }76 }77}7879/// Observable appearance preferences, persisted as part of app settings.80@MainActor81final class AppearanceStore: ObservableObject {82 struct Stored: Codable {83 var themeMode: ThemeMode = .system84 var accent: AccentChoice = .skyIndigo85 var chatFontSize: Double = 13.586 }8788 static let fileName = "appearance.json"8990 @Published var themeMode: ThemeMode { didSet { save() } }91 @Published var accent: AccentChoice { didSet { save() } }92 /// Chat body font size, clamped to the spec's 12–18pt range.93 @Published var chatFontSize: Double {94 didSet {95 let clamped = min(18, max(12, chatFontSize))96 if clamped != chatFontSize { chatFontSize = clamped }97 save()98 }99 }100101 private let persistence: PersistenceService102103 init(persistence: PersistenceService = .shared) {104 self.persistence = persistence105 let stored = persistence.load(Stored.self, from: Self.fileName) ?? Stored()106 themeMode = stored.themeMode107 accent = stored.accent108 chatFontSize = stored.chatFontSize109 }110111 /// Current accent color resolved for the active appearance.112 var accentColor: Color {113 let (light, dark) = accent.accentHex114 return Color(nsColor: NSColor(name: nil) { appearance in115 let hex = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? dark : light116 return NSColor(hex: hex)117 })118 }119120 /// Current subtle accent tint resolved for the active appearance.121 var accentSubtleColor: Color {122 let (light, dark) = accent.subtleHex123 return Color(nsColor: NSColor(name: nil) { appearance in124 let hex = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? dark : light125 return NSColor(hex: hex)126 })127 }128129 private func save() {130 persistence.save(131 Stored(themeMode: themeMode, accent: accent, chatFontSize: chatFontSize),132 to: Self.fileName133 )134 }135}136