Add theme picker with 7 color schemes

- 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>
This commit is contained in:
Trey t
2026-01-08 09:49:04 -06:00
parent 5bbfd30a70
commit 4eca0c920c
3 changed files with 324 additions and 28 deletions

View File

@@ -12,6 +12,12 @@ final class SettingsViewModel {
// MARK: - User Preferences (persisted via UserDefaults)
var selectedTheme: AppTheme {
didSet {
ThemeManager.shared.currentTheme = selectedTheme
}
}
var selectedSports: Set<Sport> {
didSet { savePreferences() }
}
@@ -41,6 +47,9 @@ final class SettingsViewModel {
// 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) })
@@ -93,6 +102,7 @@ final class SettingsViewModel {
}
func resetToDefaults() {
selectedTheme = .teal
selectedSports = Set(Sport.supported)
maxDrivingHoursPerDay = 8
maxTripOptions = 10

View File

@@ -11,6 +11,9 @@ struct SettingsView: View {
var body: some View {
List {
// Theme Selection
themeSection
// Sports Preferences
sportsSection
@@ -37,6 +40,58 @@ struct SettingsView: View {
}
}
// MARK: - Theme Section
private var themeSection: some View {
Section {
ForEach(AppTheme.allCases) { theme in
Button {
withAnimation(.easeInOut(duration: 0.2)) {
viewModel.selectedTheme = theme
}
} label: {
HStack(spacing: 12) {
// Color preview circles
HStack(spacing: -6) {
ForEach(Array(theme.previewColors.enumerated()), id: \.offset) { _, color in
Circle()
.fill(color)
.frame(width: 24, height: 24)
.overlay(
Circle()
.stroke(Color.primary.opacity(0.1), lineWidth: 1)
)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(theme.displayName)
.font(.body)
.foregroundStyle(.primary)
Text(theme.description)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
if viewModel.selectedTheme == theme {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.font(.title3)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
} header: {
Text("Theme")
} footer: {
Text("Choose a color scheme for the app.")
}
}
// MARK: - Sports Section
private var sportsSection: some View {