Files
Sportstime/SportsTime/Core/Services/DataProvider.swift
Trey t 9088b46563 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>
2026-01-07 00:46:40 -06:00

133 lines
4.3 KiB
Swift

//
// DataProvider.swift
// SportsTime
//
import Foundation
import Combine
/// Protocol defining data operations for teams, stadiums, and games
protocol DataProvider: Sendable {
func fetchTeams(for sport: Sport) async throws -> [Team]
func fetchAllTeams() async throws -> [Team]
func fetchStadiums() async throws -> [Stadium]
func fetchGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game]
func fetchGame(by id: UUID) async throws -> Game?
// Resolved data (with team/stadium references)
func fetchRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame]
}
/// Environment-aware data provider that switches between stub and CloudKit
@MainActor
final class AppDataProvider: ObservableObject {
static let shared = AppDataProvider()
private let provider: any DataProvider
@Published private(set) var teams: [Team] = []
@Published private(set) var stadiums: [Stadium] = []
@Published private(set) var isLoading = false
@Published private(set) var error: Error?
@Published private(set) var errorMessage: String?
@Published private(set) var isUsingStubData: Bool
private var teamsById: [UUID: Team] = [:]
private var stadiumsById: [UUID: Stadium] = [:]
private init() {
#if targetEnvironment(simulator)
self.provider = StubDataProvider()
self.isUsingStubData = true
print("📱 Using StubDataProvider (Simulator)")
#else
self.provider = CloudKitDataProvider()
self.isUsingStubData = false
print("☁️ Using CloudKitDataProvider (Device)")
#endif
}
// MARK: - Data Loading
func loadInitialData() async {
isLoading = true
error = nil
errorMessage = nil
do {
async let teamsTask = provider.fetchAllTeams()
async let stadiumsTask = provider.fetchStadiums()
let (loadedTeams, loadedStadiums) = try await (teamsTask, stadiumsTask)
self.teams = loadedTeams
self.stadiums = loadedStadiums
// Build lookup dictionaries
self.teamsById = Dictionary(uniqueKeysWithValues: loadedTeams.map { ($0.id, $0) })
self.stadiumsById = Dictionary(uniqueKeysWithValues: loadedStadiums.map { ($0.id, $0) })
print("✅ Loaded \(teams.count) teams, \(stadiums.count) stadiums")
} catch let cloudKitError as CloudKitError {
self.error = cloudKitError
self.errorMessage = cloudKitError.errorDescription
print("❌ CloudKit error: \(cloudKitError.errorDescription ?? "Unknown")")
} catch {
self.error = error
self.errorMessage = error.localizedDescription
print("❌ Failed to load data: \(error)")
}
isLoading = false
}
func clearError() {
error = nil
errorMessage = nil
}
func retry() async {
await loadInitialData()
}
// MARK: - Data Access
func team(for id: UUID) -> Team? {
teamsById[id]
}
func stadium(for id: UUID) -> Stadium? {
stadiumsById[id]
}
func teams(for sport: Sport) -> [Team] {
teams.filter { $0.sport == sport }
}
func fetchGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game] {
try await provider.fetchGames(sports: sports, startDate: startDate, endDate: endDate)
}
func fetchRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
let games = try await provider.fetchGames(sports: sports, startDate: startDate, endDate: endDate)
return games.compactMap { game in
guard let homeTeam = teamsById[game.homeTeamId],
let awayTeam = teamsById[game.awayTeamId],
let stadium = stadiumsById[game.stadiumId] else {
return nil
}
return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
}
}
func richGame(from game: Game) -> RichGame? {
guard let homeTeam = teamsById[game.homeTeamId],
let awayTeam = teamsById[game.awayTeamId],
let stadium = stadiumsById[game.stadiumId] else {
return nil
}
return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
}
}