feat(sync): add CloudKit sync for dynamic sports
- Add CKSport model to parse CloudKit Sport records - Add fetchSportsForSync() to CloudKitService for delta fetching - Add syncSports() and mergeSport() to CanonicalSyncService - Update DataProvider with dynamicSports support and allSports computed property - Update MockAppDataProvider with matching dynamic sports support - Add comprehensive documentation for adding new sports The app can now sync sport definitions from CloudKit, enabling new sports to be added without app updates. Sports are fetched, merged into SwiftData, and exposed via AppDataProvider.allSports alongside built-in Sport enum cases. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ final class AppDataProvider: ObservableObject {
|
||||
|
||||
@Published private(set) var teams: [Team] = []
|
||||
@Published private(set) var stadiums: [Stadium] = []
|
||||
@Published private(set) var dynamicSports: [DynamicSport] = []
|
||||
@Published private(set) var isLoading = false
|
||||
@Published private(set) var error: Error?
|
||||
@Published private(set) var errorMessage: String?
|
||||
@@ -24,6 +25,7 @@ final class AppDataProvider: ObservableObject {
|
||||
// Lookup dictionaries - keyed by canonical ID (String)
|
||||
private var teamsById: [String: Team] = [:]
|
||||
private var stadiumsById: [String: Stadium] = [:]
|
||||
private var dynamicSportsById: [String: DynamicSport] = [:]
|
||||
|
||||
private var modelContext: ModelContext?
|
||||
|
||||
@@ -80,10 +82,27 @@ final class AppDataProvider: ObservableObject {
|
||||
teamLookup[team.id] = team
|
||||
}
|
||||
|
||||
// Fetch canonical sports from SwiftData
|
||||
let sportDescriptor = FetchDescriptor<CanonicalSport>(
|
||||
predicate: #Predicate { $0.isActive == true }
|
||||
)
|
||||
let canonicalSports = try context.fetch(sportDescriptor)
|
||||
|
||||
// Convert to domain models
|
||||
var loadedSports: [DynamicSport] = []
|
||||
var sportLookup: [String: DynamicSport] = [:]
|
||||
for canonical in canonicalSports {
|
||||
let sport = canonical.toDomain()
|
||||
loadedSports.append(sport)
|
||||
sportLookup[sport.id] = sport
|
||||
}
|
||||
|
||||
self.teams = loadedTeams
|
||||
self.stadiums = loadedStadiums
|
||||
self.dynamicSports = loadedSports
|
||||
self.teamsById = teamLookup
|
||||
self.stadiumsById = stadiumLookup
|
||||
self.dynamicSportsById = sportLookup
|
||||
|
||||
} catch {
|
||||
self.error = error
|
||||
@@ -116,6 +135,17 @@ final class AppDataProvider: ObservableObject {
|
||||
teams.filter { $0.sport == sport }
|
||||
}
|
||||
|
||||
func dynamicSport(for id: String) -> DynamicSport? {
|
||||
dynamicSportsById[id]
|
||||
}
|
||||
|
||||
/// All sports: built-in Sport enum cases + CloudKit-defined DynamicSports
|
||||
var allSports: [any AnySport] {
|
||||
let builtIn: [any AnySport] = Sport.allCases
|
||||
let dynamic: [any AnySport] = dynamicSports
|
||||
return builtIn + dynamic
|
||||
}
|
||||
|
||||
// MARK: - Game Fetching
|
||||
|
||||
/// Filter games from SwiftData within date range
|
||||
|
||||
Reference in New Issue
Block a user