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
58 lines
2.0 KiB
Swift
58 lines
2.0 KiB
Swift
//
|
|
// WorkoutsApp.swift
|
|
// Workouts
|
|
//
|
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
import UIKit
|
|
|
|
@main
|
|
struct WorkoutsApp: App {
|
|
@State private var services = AppServices()
|
|
@Environment(\.scenePhase) private var scenePhase
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
#if DEBUG
|
|
if ScreenshotSeed.isActive {
|
|
ScreenshotRootView(services: services)
|
|
} else {
|
|
root
|
|
}
|
|
#else
|
|
root
|
|
#endif
|
|
}
|
|
// Keep the screen lit for the whole app, not just while logging — the phone is
|
|
// often propped up across the room during a workout. iOS clears this flag when
|
|
// the app is backgrounded, so re-assert it each time the scene becomes active.
|
|
.onChange(of: scenePhase, initial: true) { _, phase in
|
|
UIApplication.shared.isIdleTimerDisabled = (phase == .active)
|
|
// Last chance before suspension: push any queued document writes out
|
|
// now rather than waiting out a retry backoff we may not live to see.
|
|
if phase == .background {
|
|
Task { await services.syncEngine.flushPendingWrites() }
|
|
}
|
|
}
|
|
}
|
|
|
|
private var root: some View {
|
|
RootGateView()
|
|
.environment(services)
|
|
.environment(services.syncEngine)
|
|
.environment(services.liveRunState)
|
|
.modelContainer(services.container)
|
|
.task { await services.bootstrap() }
|
|
// Tap a shared ".workoutsbackup" file (Files, AirDrop, share sheet) to
|
|
// restore it. The controller stops the sync observer for the restore and
|
|
// rebuilds the cache afterward (see AppBackupConfiguration).
|
|
.onOpenURL { url in
|
|
guard url.pathExtension == "workoutsbackup" else { return }
|
|
Task { try? await services.backupController.restoreBackup(from: url) }
|
|
}
|
|
}
|
|
}
|