fix(sync): add foreground sync, remove manual sync button

- Add scenePhase observer to sync when app returns to foreground
- Remove misleading "Sync Schedules" button from Settings (it only
  reloaded local data, didn't actually sync from CloudKit)
- Fix GamesHistoryView to refresh list after deleting a visit

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-12 22:04:10 -06:00
parent 0524284ab8
commit ba3ea6daeb
4 changed files with 19 additions and 77 deletions

View File

@@ -18,7 +18,12 @@ struct GamesHistoryView: View {
}
}
.navigationTitle("Games Attended")
.sheet(item: $selectedVisit) { visit in
.sheet(item: $selectedVisit, onDismiss: {
// Refresh data after sheet closes (handles deletion case)
Task {
await viewModel?.loadGames()
}
}) { visit in
if let stadium = AppDataProvider.shared.stadium(for: visit.stadiumId) {
VisitDetailView(visit: visit, stadium: stadium)
}

View File

@@ -26,12 +26,6 @@ final class SettingsViewModel {
didSet { savePreferences() }
}
// MARK: - Sync State
private(set) var isSyncing = false
private(set) var lastSyncDate: Date?
private(set) var syncError: String?
// MARK: - App Info
let appVersion: String
@@ -57,9 +51,6 @@ final class SettingsViewModel {
let savedDrivingHours = defaults.integer(forKey: "maxDrivingHoursPerDay")
self.maxDrivingHoursPerDay = savedDrivingHours == 0 ? 8 : savedDrivingHours
// Last sync
self.lastSyncDate = defaults.object(forKey: "lastSyncDate") as? Date
// App info
self.appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
self.buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
@@ -67,19 +58,6 @@ final class SettingsViewModel {
// MARK: - Actions
func syncSchedules() async {
isSyncing = true
syncError = nil
// Trigger data reload from provider
await AppDataProvider.shared.loadInitialData()
lastSyncDate = Date()
UserDefaults.standard.set(lastSyncDate, forKey: "lastSyncDate")
isSyncing = false
}
func toggleSport(_ sport: Sport) {
if selectedSports.contains(sport) {
// Don't allow removing all sports

View File

@@ -21,9 +21,6 @@ struct SettingsView: View {
// Travel Preferences
travelSection
// Data Sync
dataSection
// About
aboutSection
@@ -149,57 +146,6 @@ struct SettingsView: View {
.listRowBackground(Theme.cardBackground(colorScheme))
}
// MARK: - Data Section
private var dataSection: some View {
Section {
Button {
Task {
await viewModel.syncSchedules()
}
} label: {
HStack {
Label("Sync Schedules", systemImage: "arrow.triangle.2.circlepath")
Spacer()
if viewModel.isSyncing {
ThemedSpinnerCompact(size: 18)
}
}
}
.disabled(viewModel.isSyncing)
if let lastSync = viewModel.lastSyncDate {
HStack {
Text("Last Sync")
Spacer()
Text(lastSync, style: .relative)
.foregroundStyle(.secondary)
}
}
if let error = viewModel.syncError {
HStack {
Image(systemName: "exclamationmark.triangle")
.foregroundStyle(.red)
Text(error)
.font(.caption)
.foregroundStyle(.red)
}
}
} header: {
Text("Data")
} footer: {
#if targetEnvironment(simulator)
Text("Using stub data (Simulator mode)")
#else
Text("Schedule data is synced from CloudKit.")
#endif
}
.listRowBackground(Theme.cardBackground(colorScheme))
}
// MARK: - About Section
private var aboutSection: some View {

View File

@@ -59,8 +59,10 @@ struct SportsTimeApp: App {
struct BootstrappedContentView: View {
let modelContainer: ModelContainer
@Environment(\.scenePhase) private var scenePhase
@State private var isBootstrapping = true
@State private var bootstrapError: Error?
@State private var hasCompletedInitialSync = false
var body: some View {
Group {
@@ -79,6 +81,14 @@ struct BootstrappedContentView: View {
.task {
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)
}
}
}
}
@MainActor
@@ -105,6 +115,9 @@ struct BootstrappedContentView: View {
// 5. Background: Try to refresh from CloudKit (non-blocking)
Task.detached(priority: .background) {
await self.performBackgroundSync(context: context)
await MainActor.run {
self.hasCompletedInitialSync = true
}
}
} catch {
bootstrapError = error