- Three-scenario planning engine (A: date range, B: selected games, C: directional routes) - GeographicRouteExplorer with anchor game support for route exploration - Shared ItineraryBuilder for travel segment calculation - TravelEstimator for driving time/distance estimation - SwiftUI views for trip creation and detail display - CloudKit integration for schedule data - Python scraping scripts for sports schedules 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
155 lines
4.3 KiB
Swift
155 lines
4.3 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 preferredGameTime: PreferredGameTime {
|
|
didSet { savePreferences() }
|
|
}
|
|
|
|
var includePlayoffGames: Bool {
|
|
didSet { savePreferences() }
|
|
}
|
|
|
|
var notificationsEnabled: Bool {
|
|
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 - use local variable to avoid self access before init complete
|
|
let savedDrivingHours = defaults.integer(forKey: "maxDrivingHoursPerDay")
|
|
self.maxDrivingHoursPerDay = savedDrivingHours == 0 ? 8 : savedDrivingHours
|
|
|
|
if let timeRaw = defaults.string(forKey: "preferredGameTime"),
|
|
let time = PreferredGameTime(rawValue: timeRaw) {
|
|
self.preferredGameTime = time
|
|
} else {
|
|
self.preferredGameTime = .evening
|
|
}
|
|
|
|
self.includePlayoffGames = defaults.object(forKey: "includePlayoffGames") as? Bool ?? true
|
|
self.notificationsEnabled = defaults.object(forKey: "notificationsEnabled") as? Bool ?? true
|
|
|
|
// 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
|
|
preferredGameTime = .evening
|
|
includePlayoffGames = true
|
|
notificationsEnabled = true
|
|
}
|
|
|
|
// MARK: - Persistence
|
|
|
|
private func savePreferences() {
|
|
let defaults = UserDefaults.standard
|
|
defaults.set(selectedSports.map(\.rawValue), forKey: "selectedSports")
|
|
defaults.set(maxDrivingHoursPerDay, forKey: "maxDrivingHoursPerDay")
|
|
defaults.set(preferredGameTime.rawValue, forKey: "preferredGameTime")
|
|
defaults.set(includePlayoffGames, forKey: "includePlayoffGames")
|
|
defaults.set(notificationsEnabled, forKey: "notificationsEnabled")
|
|
}
|
|
}
|
|
|
|
// MARK: - Supporting Types
|
|
|
|
enum PreferredGameTime: String, CaseIterable, Identifiable {
|
|
case any = "any"
|
|
case afternoon = "afternoon"
|
|
case evening = "evening"
|
|
|
|
var id: String { rawValue }
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .any: return "Any Time"
|
|
case .afternoon: return "Afternoon"
|
|
case .evening: return "Evening"
|
|
}
|
|
}
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .any: return "No preference"
|
|
case .afternoon: return "1 PM - 5 PM"
|
|
case .evening: return "6 PM - 10 PM"
|
|
}
|
|
}
|
|
}
|