SPB Git

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%
4.0 KB · 136 lines swift
Raw Blame History
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