feat(sync): add background task system for nightly CloudKit sync

Add BGTaskScheduler-based background sync for keeping local data fresh:

- BackgroundSyncManager: New singleton managing background tasks
  - BGAppRefreshTask for periodic CloudKit sync (system-determined frequency)
  - BGProcessingTask for overnight sync + database cleanup (2 AM)
  - Auto-archives games older than 1 year during cleanup

- Info.plist: Added BGTaskSchedulerPermittedIdentifiers
  - com.sportstime.app.refresh (periodic sync)
  - com.sportstime.app.db-cleanup (overnight processing)

- SportsTimeApp: Integrated background task lifecycle
  - Register tasks in init() (required before app finishes launching)
  - Schedule tasks after bootstrap and when app enters background

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-13 18:36:35 -06:00
parent f180e5bfed
commit b99c25d8f6
3 changed files with 310 additions and 8 deletions

View File

@@ -7,6 +7,7 @@
import SwiftUI
import SwiftData
import BackgroundTasks
@main
struct SportsTimeApp: App {
@@ -14,6 +15,10 @@ struct SportsTimeApp: App {
private var transactionListener: Task<Void, Never>?
init() {
// Register background tasks BEFORE app finishes launching
// This must happen synchronously in init or applicationDidFinishLaunching
BackgroundSyncManager.shared.registerTasks()
// Start listening for transactions immediately
transactionListener = StoreManager.shared.listenForTransactions()
}
@@ -105,11 +110,19 @@ struct BootstrappedContentView: View {
await performBootstrap()
}
.onChange(of: scenePhase) { _, newPhase in
// Sync when app comes to foreground (but not on initial launch)
if newPhase == .active && hasCompletedInitialSync {
Task {
await performBackgroundSync(context: modelContainer.mainContext)
switch newPhase {
case .active:
// Sync when app comes to foreground (but not on initial launch)
if hasCompletedInitialSync {
Task {
await performBackgroundSync(context: modelContainer.mainContext)
}
}
case .background:
// Schedule background tasks when app goes to background
BackgroundSyncManager.shared.scheduleAllTasks()
default:
break
}
}
}
@@ -129,17 +142,23 @@ struct BootstrappedContentView: View {
// 2. Configure DataProvider with SwiftData context
AppDataProvider.shared.configure(with: context)
// 3. Load data from SwiftData into memory
// 3. Configure BackgroundSyncManager with model container
BackgroundSyncManager.shared.configure(with: modelContainer)
// 4. Load data from SwiftData into memory
await AppDataProvider.shared.loadInitialData()
// 4. Load store products and entitlements
// 5. Load store products and entitlements
await StoreManager.shared.loadProducts()
await StoreManager.shared.updateEntitlements()
// 5. App is now usable
// 6. App is now usable
isBootstrapping = false
// 6. Background: Try to refresh from CloudKit (non-blocking)
// 7. Schedule background tasks for future syncs
BackgroundSyncManager.shared.scheduleAllTasks()
// 8. Background: Try to refresh from CloudKit (non-blocking)
Task.detached(priority: .background) {
await self.performBackgroundSync(context: context)
await MainActor.run {