feat(settings): add dark/light/system appearance mode toggle

- Add AppearanceMode enum with system, light, and dark options
- Add AppearanceManager singleton to persist user preference
- Add appearance section in SettingsView with icon and description
- Apply preferredColorScheme at app root for immediate effect
- Include appearance mode in reset to defaults

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-14 13:08:38 -06:00
parent 3d4952e5ff
commit f7f1bbd87a
4 changed files with 116 additions and 0 deletions

View File

@@ -69,6 +69,65 @@ final class ThemeManager {
}
}
// MARK: - Appearance Mode
enum AppearanceMode: String, CaseIterable, Identifiable {
case system = "System"
case light = "Light"
case dark = "Dark"
var id: String { rawValue }
var displayName: String { rawValue }
var iconName: String {
switch self {
case .system: return "circle.lefthalf.filled"
case .light: return "sun.max.fill"
case .dark: return "moon.fill"
}
}
var description: String {
switch self {
case .system: return "Match device settings"
case .light: return "Always use light mode"
case .dark: return "Always use dark mode"
}
}
/// Returns the ColorScheme to apply, or nil for system default
var colorScheme: ColorScheme? {
switch self {
case .system: return nil
case .light: return .light
case .dark: return .dark
}
}
}
// MARK: - Appearance Manager
@Observable
final class AppearanceManager {
static let shared = AppearanceManager()
var currentMode: AppearanceMode {
didSet {
UserDefaults.standard.set(currentMode.rawValue, forKey: "appearanceMode")
}
}
private init() {
if let saved = UserDefaults.standard.string(forKey: "appearanceMode"),
let mode = AppearanceMode(rawValue: saved) {
self.currentMode = mode
} else {
self.currentMode = .system
}
}
}
// MARK: - Theme
enum Theme {