Initial commit: SportsTime trip planning app
- 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>
This commit is contained in:
154
SportsTime/Features/Settings/ViewModels/SettingsViewModel.swift
Normal file
154
SportsTime/Features/Settings/ViewModels/SettingsViewModel.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user