fix(schedule): use start of day for date range queries
- Fix startDate to use Calendar.startOfDay instead of Date() to include games earlier in the current day - Add SyncLogger for file-based sync logging viewable in Settings - Add "View Sync Logs" button in Settings debug section - Add diagnostics and NBA game logging to ScheduleViewModel - Add dropped game logging to DataProvider.filterRichGames - Use SyncLogger in SportsTimeApp and CloudKitService for sync operations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,7 @@ struct BootstrappedContentView: View {
|
||||
|
||||
@MainActor
|
||||
private func performBootstrap() async {
|
||||
print("🚀 [BOOT] Starting app bootstrap...")
|
||||
isBootstrapping = true
|
||||
bootstrapError = nil
|
||||
|
||||
@@ -159,34 +160,44 @@ struct BootstrappedContentView: View {
|
||||
|
||||
do {
|
||||
// 1. Bootstrap from bundled JSON if first launch (no data exists)
|
||||
print("🚀 [BOOT] Step 1: Checking if bootstrap needed...")
|
||||
try await bootstrapService.bootstrapIfNeeded(context: context)
|
||||
|
||||
// 2. Configure DataProvider with SwiftData context
|
||||
print("🚀 [BOOT] Step 2: Configuring DataProvider...")
|
||||
AppDataProvider.shared.configure(with: context)
|
||||
|
||||
// 3. Configure BackgroundSyncManager with model container
|
||||
print("🚀 [BOOT] Step 3: Configuring BackgroundSyncManager...")
|
||||
BackgroundSyncManager.shared.configure(with: modelContainer)
|
||||
|
||||
// 4. Load data from SwiftData into memory
|
||||
print("🚀 [BOOT] Step 4: Loading initial data from SwiftData...")
|
||||
await AppDataProvider.shared.loadInitialData()
|
||||
print("🚀 [BOOT] Loaded \(AppDataProvider.shared.teams.count) teams")
|
||||
print("🚀 [BOOT] Loaded \(AppDataProvider.shared.stadiums.count) stadiums")
|
||||
|
||||
// 5. Load store products and entitlements
|
||||
print("🚀 [BOOT] Step 5: Loading store products...")
|
||||
await StoreManager.shared.loadProducts()
|
||||
await StoreManager.shared.updateEntitlements()
|
||||
|
||||
// 6. Start network monitoring and wire up sync callback
|
||||
print("🚀 [BOOT] Step 6: Starting network monitoring...")
|
||||
NetworkMonitor.shared.onSyncNeeded = {
|
||||
await BackgroundSyncManager.shared.triggerSyncFromNetworkRestoration()
|
||||
}
|
||||
NetworkMonitor.shared.startMonitoring()
|
||||
|
||||
// 7. App is now usable
|
||||
print("🚀 [BOOT] Step 7: Bootstrap complete - app ready")
|
||||
isBootstrapping = false
|
||||
|
||||
// 8. Schedule background tasks for future syncs
|
||||
BackgroundSyncManager.shared.scheduleAllTasks()
|
||||
|
||||
// 9. Background: Try to refresh from CloudKit (non-blocking)
|
||||
print("🚀 [BOOT] Step 9: Starting background CloudKit sync...")
|
||||
Task.detached(priority: .background) {
|
||||
await self.performBackgroundSync(context: context)
|
||||
await MainActor.run {
|
||||
@@ -194,6 +205,7 @@ struct BootstrappedContentView: View {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("❌ [BOOT] Bootstrap failed: \(error.localizedDescription)")
|
||||
bootstrapError = error
|
||||
isBootstrapping = false
|
||||
}
|
||||
@@ -201,22 +213,46 @@ struct BootstrappedContentView: View {
|
||||
|
||||
@MainActor
|
||||
private func performBackgroundSync(context: ModelContext) async {
|
||||
let log = SyncLogger.shared
|
||||
log.log("🔄 [SYNC] Starting background sync...")
|
||||
|
||||
// Reset stale syncInProgress flag (in case app was killed mid-sync)
|
||||
let syncState = SyncState.current(in: context)
|
||||
if syncState.syncInProgress {
|
||||
log.log("⚠️ [SYNC] Resetting stale syncInProgress flag")
|
||||
syncState.syncInProgress = false
|
||||
try? context.save()
|
||||
}
|
||||
|
||||
let syncService = CanonicalSyncService()
|
||||
|
||||
do {
|
||||
let result = try await syncService.syncAll(context: context)
|
||||
|
||||
log.log("🔄 [SYNC] Sync completed in \(String(format: "%.2f", result.duration))s")
|
||||
log.log("🔄 [SYNC] Stadiums: \(result.stadiumsUpdated)")
|
||||
log.log("🔄 [SYNC] Teams: \(result.teamsUpdated)")
|
||||
log.log("🔄 [SYNC] Games: \(result.gamesUpdated)")
|
||||
log.log("🔄 [SYNC] League Structures: \(result.leagueStructuresUpdated)")
|
||||
log.log("🔄 [SYNC] Team Aliases: \(result.teamAliasesUpdated)")
|
||||
log.log("🔄 [SYNC] Stadium Aliases: \(result.stadiumAliasesUpdated)")
|
||||
log.log("🔄 [SYNC] Sports: \(result.sportsUpdated)")
|
||||
log.log("🔄 [SYNC] Skipped (incompatible): \(result.skippedIncompatible)")
|
||||
log.log("🔄 [SYNC] Skipped (older): \(result.skippedOlder)")
|
||||
log.log("🔄 [SYNC] Total updated: \(result.totalUpdated)")
|
||||
|
||||
// If any data was updated, reload the DataProvider
|
||||
if !result.isEmpty {
|
||||
log.log("🔄 [SYNC] Reloading DataProvider...")
|
||||
await AppDataProvider.shared.loadInitialData()
|
||||
print("CloudKit sync completed: \(result.totalUpdated) items updated")
|
||||
log.log("🔄 [SYNC] DataProvider reloaded. Teams: \(AppDataProvider.shared.teams.count), Stadiums: \(AppDataProvider.shared.stadiums.count)")
|
||||
} else {
|
||||
log.log("🔄 [SYNC] No updates - skipping DataProvider reload")
|
||||
}
|
||||
} catch CanonicalSyncService.SyncError.cloudKitUnavailable {
|
||||
// Offline or CloudKit not available - silently continue with local data
|
||||
print("CloudKit unavailable, using local data")
|
||||
log.log("❌ [SYNC] CloudKit unavailable - using local data only")
|
||||
} catch {
|
||||
// Other sync errors - log but don't interrupt user
|
||||
print("Background sync error: \(error.localizedDescription)")
|
||||
log.log("❌ [SYNC] Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user