TripOptionCard improvements: - Replace horizontal route with vertical layout (start → end with arrow) - Remove rank badges (1, 2, 3, etc.) - Split stats into two rows: cities/miles and sports with game counts - Clear selection when navigating back from detail view Settings cleanup: - Remove unused settings (preferred game time, playoff games, notifications) - Convert remaining settings to sliders Planning fixes: - Fix multi-day driving calculation in canTransition - Remove over-restrictive trip rejection in TravelEstimator - Clear games cache when sport selection changes UI polish: - RoutePreviewStrip shows all cities (abbreviated) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
110 lines
2.9 KiB
Swift
110 lines
2.9 KiB
Swift
//
|
|
// SettingsViewModel.swift
|
|
// SportsTime
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class SettingsViewModel {
|
|
|
|
// MARK: - User Preferences (persisted via UserDefaults)
|
|
|
|
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
|
|
|
|
// 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() {
|
|
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")
|
|
}
|
|
}
|