chore: commit all pending changes
This commit is contained in:
@@ -12,9 +12,11 @@ struct SettingsView: View {
|
||||
@State private var showResetConfirmation = false
|
||||
@State private var showPaywall = false
|
||||
@State private var showOnboardingPaywall = false
|
||||
@State private var showSyncLogs = false
|
||||
@State private var isSyncActionInProgress = false
|
||||
@State private var syncActionMessage: String?
|
||||
#if DEBUG
|
||||
@State private var selectedSyncStatus: EntitySyncStatus?
|
||||
@State private var showSyncLogs = false
|
||||
@State private var exporter = DebugShareExporter()
|
||||
@State private var showExportProgress = false
|
||||
#endif
|
||||
@@ -39,6 +41,9 @@ struct SettingsView: View {
|
||||
// Travel Preferences
|
||||
travelSection
|
||||
|
||||
// Data Sync
|
||||
syncHealthSection
|
||||
|
||||
// Privacy
|
||||
privacySection
|
||||
|
||||
@@ -72,6 +77,17 @@ struct SettingsView: View {
|
||||
.sheet(isPresented: $showOnboardingPaywall) {
|
||||
OnboardingPaywallView(isPresented: $showOnboardingPaywall)
|
||||
}
|
||||
.sheet(isPresented: $showSyncLogs) {
|
||||
SyncLogViewerSheet()
|
||||
}
|
||||
.alert("Sync Status", isPresented: Binding(
|
||||
get: { syncActionMessage != nil },
|
||||
set: { if !$0 { syncActionMessage = nil } }
|
||||
)) {
|
||||
Button("OK", role: .cancel) { syncActionMessage = nil }
|
||||
} message: {
|
||||
Text(syncActionMessage ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Appearance Section
|
||||
@@ -370,6 +386,97 @@ struct SettingsView: View {
|
||||
.listRowBackground(Theme.cardBackground(colorScheme))
|
||||
}
|
||||
|
||||
// MARK: - Sync Health Section
|
||||
|
||||
private var syncHealthSection: some View {
|
||||
Section {
|
||||
let syncState = SyncState.current(in: modelContext)
|
||||
|
||||
if syncState.syncInProgress || isSyncActionInProgress {
|
||||
HStack {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
Text("Sync in progress...")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
if let lastSync = syncState.lastSuccessfulSync {
|
||||
HStack {
|
||||
Label("Last Successful Sync", systemImage: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Spacer()
|
||||
Text(lastSync.formatted(date: .abbreviated, time: .shortened))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
Label("Last Successful Sync", systemImage: "clock.arrow.circlepath")
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text("Never")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
if let lastError = syncState.lastSyncError, !lastError.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Label("Last Sync Warning", systemImage: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.orange)
|
||||
.font(.subheadline)
|
||||
Text(lastError)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
if !syncState.syncEnabled {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Label("Sync Paused", systemImage: "pause.circle.fill")
|
||||
.foregroundStyle(.orange)
|
||||
.font(.subheadline)
|
||||
if let reason = syncState.syncPausedReason {
|
||||
Text(reason)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Task {
|
||||
let syncService = CanonicalSyncService()
|
||||
await syncService.resumeSync(context: modelContext)
|
||||
syncActionMessage = "Sync has been re-enabled."
|
||||
}
|
||||
} label: {
|
||||
Label("Re-enable Sync", systemImage: "play.circle")
|
||||
}
|
||||
.disabled(isSyncActionInProgress)
|
||||
}
|
||||
|
||||
Button {
|
||||
triggerManualSync()
|
||||
} label: {
|
||||
Label("Sync Now", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
.disabled(isSyncActionInProgress)
|
||||
|
||||
Button {
|
||||
showSyncLogs = true
|
||||
} label: {
|
||||
Label("View Sync Logs", systemImage: "doc.text.magnifyingglass")
|
||||
}
|
||||
} header: {
|
||||
Text("Data Sync")
|
||||
} footer: {
|
||||
Text("SportsTime loads bundled data first, then refreshes from CloudKit.")
|
||||
}
|
||||
.listRowBackground(Theme.cardBackground(colorScheme))
|
||||
}
|
||||
|
||||
// MARK: - Debug Section
|
||||
|
||||
#if DEBUG
|
||||
@@ -548,9 +655,6 @@ struct SettingsView: View {
|
||||
.sheet(item: $selectedSyncStatus) { status in
|
||||
SyncStatusDetailSheet(status: status)
|
||||
}
|
||||
.sheet(isPresented: $showSyncLogs) {
|
||||
SyncLogViewerSheet()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -633,6 +737,23 @@ struct SettingsView: View {
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func triggerManualSync() {
|
||||
guard !isSyncActionInProgress else { return }
|
||||
|
||||
isSyncActionInProgress = true
|
||||
|
||||
Task {
|
||||
defer { isSyncActionInProgress = false }
|
||||
|
||||
do {
|
||||
let result = try await BackgroundSyncManager.shared.triggerManualSync()
|
||||
syncActionMessage = "Sync complete. Updated \(result.totalUpdated) records."
|
||||
} catch {
|
||||
syncActionMessage = "Sync failed: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sportColor(for sport: Sport) -> Color {
|
||||
sport.themeColor
|
||||
}
|
||||
@@ -775,6 +896,8 @@ private struct DetailRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// Sheet to view sync logs
|
||||
struct SyncLogViewerSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@@ -837,5 +960,3 @@ struct SyncLogViewerSheet: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user