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:
134
SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift
Normal file
134
SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// ScheduleViewModel.swift
|
||||
// SportsTime
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class ScheduleViewModel {
|
||||
|
||||
// MARK: - Filter State
|
||||
|
||||
var selectedSports: Set<Sport> = Set(Sport.supported)
|
||||
var startDate: Date = Date()
|
||||
var endDate: Date = Calendar.current.date(byAdding: .day, value: 14, to: Date()) ?? Date()
|
||||
var searchText: String = ""
|
||||
|
||||
// MARK: - Data State
|
||||
|
||||
private(set) var games: [RichGame] = []
|
||||
private(set) var isLoading = false
|
||||
private(set) var error: Error?
|
||||
private(set) var errorMessage: String?
|
||||
|
||||
private let dataProvider = AppDataProvider.shared
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
var filteredGames: [RichGame] {
|
||||
guard !searchText.isEmpty else { return games }
|
||||
|
||||
let query = searchText.lowercased()
|
||||
return games.filter { game in
|
||||
game.homeTeam.name.lowercased().contains(query) ||
|
||||
game.homeTeam.city.lowercased().contains(query) ||
|
||||
game.awayTeam.name.lowercased().contains(query) ||
|
||||
game.awayTeam.city.lowercased().contains(query) ||
|
||||
game.stadium.name.lowercased().contains(query) ||
|
||||
game.stadium.city.lowercased().contains(query)
|
||||
}
|
||||
}
|
||||
|
||||
var gamesByDate: [(date: Date, games: [RichGame])] {
|
||||
let calendar = Calendar.current
|
||||
let grouped = Dictionary(grouping: filteredGames) { game in
|
||||
calendar.startOfDay(for: game.game.dateTime)
|
||||
}
|
||||
return grouped.sorted { $0.key < $1.key }.map { ($0.key, $0.value) }
|
||||
}
|
||||
|
||||
var hasFilters: Bool {
|
||||
selectedSports.count < Sport.supported.count || !searchText.isEmpty
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
func loadGames() async {
|
||||
guard !selectedSports.isEmpty else {
|
||||
games = []
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
error = nil
|
||||
errorMessage = nil
|
||||
|
||||
do {
|
||||
// Load initial data if needed
|
||||
if dataProvider.teams.isEmpty {
|
||||
await dataProvider.loadInitialData()
|
||||
}
|
||||
|
||||
// Check if data provider had an error
|
||||
if let providerError = dataProvider.errorMessage {
|
||||
self.errorMessage = providerError
|
||||
self.error = dataProvider.error
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
games = try await dataProvider.fetchRichGames(
|
||||
sports: selectedSports,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
)
|
||||
} catch let cloudKitError as CloudKitError {
|
||||
self.error = cloudKitError
|
||||
self.errorMessage = cloudKitError.errorDescription
|
||||
print("CloudKit error loading games: \(cloudKitError.errorDescription ?? "Unknown")")
|
||||
} catch {
|
||||
self.error = error
|
||||
self.errorMessage = error.localizedDescription
|
||||
print("Failed to load games: \(error)")
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func clearError() {
|
||||
error = nil
|
||||
errorMessage = nil
|
||||
}
|
||||
|
||||
func toggleSport(_ sport: Sport) {
|
||||
if selectedSports.contains(sport) {
|
||||
selectedSports.remove(sport)
|
||||
} else {
|
||||
selectedSports.insert(sport)
|
||||
}
|
||||
Task {
|
||||
await loadGames()
|
||||
}
|
||||
}
|
||||
|
||||
func resetFilters() {
|
||||
selectedSports = Set(Sport.supported)
|
||||
searchText = ""
|
||||
startDate = Date()
|
||||
endDate = Calendar.current.date(byAdding: .day, value: 14, to: Date()) ?? Date()
|
||||
Task {
|
||||
await loadGames()
|
||||
}
|
||||
}
|
||||
|
||||
func updateDateRange(start: Date, end: Date) {
|
||||
startDate = start
|
||||
endDate = end
|
||||
Task {
|
||||
await loadGames()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user