Files
workouts/Workouts/Views/Common/SyncStatusBanner.swift
T
rzen c05e83cff7 Queue document writes durably and surface sync trouble in the UI
iCloud Drive writes now flow through a persistent WriteBacklog sidecar
(drained with backoff, flushed on backgrounding, wiped with the cache on
account change), so a save can never be lost to a transient coordinator
error. A status banner on the workout list surfaces stuck syncing.
Also: the split picker gains a Recent section with day labels, split
rows fold SplitItem into SplitListView, and list rows dim the multiply
sign in sets-by-reps.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
2026-07-08 12:48:12 -04:00

67 lines
2.5 KiB
Swift

//
// SyncStatusBanner.swift
// Workouts
//
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
/// Surfaces the write queue's banner tiers at the bottom of the main screen.
/// Tier 0 (fresh writes, silent retries) shows nothing; tier 1 is a calm
/// "changes pending" capsule that clears itself on success; tier 2 is a
/// prominent fault that opens Diagnostics.
struct SyncStatusBanner: View {
@Environment(SyncEngine.self) private var sync
@State private var showingDiagnostics = false
var body: some View {
Group {
switch sync.writeQueueState {
case .idle, .pending:
EmptyView()
case .retrying:
HStack(spacing: 8) {
ProgressView()
.controlSize(.small)
Text("Changes pending — retrying…")
.font(.footnote)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(.regularMaterial, in: Capsule())
.padding(.bottom, 6)
case .fault(let message):
Button {
showingDiagnostics = true
} label: {
HStack(alignment: .firstTextBaseline, spacing: 10) {
Image(systemName: "exclamationmark.icloud.fill")
.foregroundStyle(.white)
VStack(alignment: .leading, spacing: 2) {
Text(message)
.font(.footnote.weight(.semibold))
.foregroundStyle(.white)
.multilineTextAlignment(.leading)
Text("Your changes are safe on this iPhone. Tap for diagnostics.")
.font(.caption2)
.foregroundStyle(.white.opacity(0.85))
}
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(.red.gradient, in: RoundedRectangle(cornerRadius: 14))
}
.buttonStyle(.plain)
.padding(.horizontal, 16)
.padding(.bottom, 6)
.sheet(isPresented: $showingDiagnostics) {
NavigationStack { DiagnosticsView() }
}
}
}
.animation(.default, value: sync.writeQueueState)
}
}