- Remove all print statements from planning engine, data providers, and PDF generation - Fix deprecated CLGeocoder usage with MKLocalSearch for iOS 26 - Fix Swift 6 actor isolation by converting PDFGenerator/ExportService to @MainActor - Add @retroactive to CLLocationCoordinate2D protocol conformances - Fix unused variable warnings in GameDAGRouter and scenario planners - Remove unreachable catch blocks in SettingsViewModel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
127 lines
4.0 KiB
Swift
127 lines
4.0 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
|
|
#else
|
|
self.provider = CloudKitDataProvider()
|
|
self.isUsingStubData = false
|
|
#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) })
|
|
} catch let cloudKitError as CloudKitError {
|
|
self.error = cloudKitError
|
|
self.errorMessage = cloudKitError.errorDescription
|
|
} catch {
|
|
self.error = error
|
|
self.errorMessage = error.localizedDescription
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|