- Teal: Cool cyan and teal tones (default) - Orbit: Bold navy and orange - Retro: Classic columbia blue - Clutch: Championship red and gold - Monochrome: Clean grayscale aesthetic - Sunset: Warm oranges and purples - Midnight: Deep blues and gold Features: - Theme selection persisted via UserDefaults - ThemeManager singleton for app-wide theme state - All Theme colors now dynamically switch based on selection - Settings UI shows color preview circles for each theme 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
120 lines
3.2 KiB
Swift
120 lines
3.2 KiB
Swift
//
|
|
// SettingsViewModel.swift
|
|
// SportsTime
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class SettingsViewModel {
|
|
|
|
// MARK: - User Preferences (persisted via UserDefaults)
|
|
|
|
var selectedTheme: AppTheme {
|
|
didSet {
|
|
ThemeManager.shared.currentTheme = selectedTheme
|
|
}
|
|
}
|
|
|
|
var selectedSports: Set<Sport> {
|
|
didSet { savePreferences() }
|
|
}
|
|
|
|
var maxDrivingHoursPerDay: Int {
|
|
didSet { savePreferences() }
|
|
}
|
|
|
|
var maxTripOptions: Int {
|
|
didSet { savePreferences() }
|
|
}
|
|
|
|
// MARK: - Sync State
|
|
|
|
private(set) var isSyncing = false
|
|
private(set) var lastSyncDate: Date?
|
|
private(set) var syncError: String?
|
|
|
|
// MARK: - App Info
|
|
|
|
let appVersion: String
|
|
let buildNumber: String
|
|
|
|
// MARK: - Initialization
|
|
|
|
init() {
|
|
// Load from UserDefaults using local variables first
|
|
let defaults = UserDefaults.standard
|
|
|
|
// Theme
|
|
self.selectedTheme = ThemeManager.shared.currentTheme
|
|
|
|
// Selected sports
|
|
if let sportStrings = defaults.stringArray(forKey: "selectedSports") {
|
|
self.selectedSports = Set(sportStrings.compactMap { Sport(rawValue: $0) })
|
|
} else {
|
|
self.selectedSports = Set(Sport.supported)
|
|
}
|
|
|
|
// Travel preferences
|
|
let savedDrivingHours = defaults.integer(forKey: "maxDrivingHoursPerDay")
|
|
self.maxDrivingHoursPerDay = savedDrivingHours == 0 ? 8 : savedDrivingHours
|
|
|
|
let savedMaxTripOptions = defaults.integer(forKey: "maxTripOptions")
|
|
self.maxTripOptions = savedMaxTripOptions == 0 ? 10 : savedMaxTripOptions
|
|
|
|
// Last sync
|
|
self.lastSyncDate = defaults.object(forKey: "lastSyncDate") as? Date
|
|
|
|
// App info
|
|
self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
|
|
self.buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
func syncSchedules() async {
|
|
isSyncing = true
|
|
syncError = nil
|
|
|
|
do {
|
|
// Trigger data reload from provider
|
|
await AppDataProvider.shared.loadInitialData()
|
|
|
|
lastSyncDate = Date()
|
|
UserDefaults.standard.set(lastSyncDate, forKey: "lastSyncDate")
|
|
} catch {
|
|
syncError = error.localizedDescription
|
|
}
|
|
|
|
isSyncing = false
|
|
}
|
|
|
|
func toggleSport(_ sport: Sport) {
|
|
if selectedSports.contains(sport) {
|
|
// Don't allow removing all sports
|
|
guard selectedSports.count > 1 else { return }
|
|
selectedSports.remove(sport)
|
|
} else {
|
|
selectedSports.insert(sport)
|
|
}
|
|
}
|
|
|
|
func resetToDefaults() {
|
|
selectedTheme = .teal
|
|
selectedSports = Set(Sport.supported)
|
|
maxDrivingHoursPerDay = 8
|
|
maxTripOptions = 10
|
|
}
|
|
|
|
// MARK: - Persistence
|
|
|
|
private func savePreferences() {
|
|
let defaults = UserDefaults.standard
|
|
defaults.set(selectedSports.map(\.rawValue), forKey: "selectedSports")
|
|
defaults.set(maxDrivingHoursPerDay, forKey: "maxDrivingHoursPerDay")
|
|
defaults.set(maxTripOptions, forKey: "maxTripOptions")
|
|
}
|
|
}
|