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:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user