Files
workouts/Workouts/Screenshots/ScreenshotRootView.swift
T
rzen aa23d7baa7 Add a live status panel between the run screen's halves
Replaces the small target-HR pill on the iPhone run screen with a
glanceable big-digit strip between the timer flow and the figure —
heart rate (band-tinted with the cue arrow when the run carries a
target), HR zone, active calories, and the workout stopwatch. A
horizontal band in portrait, a vertical column in landscape (the
inverse of the half-and-half split, so it always sits between them).

The watch now rides the running calorie total and the HR zone (1-5,
computed watch-side where max HR is known) along with each live HR
sample; absent keys keep older builds wire-compatible both ways.
LiveRunState holds all three under the shared staleness expiry. The
screenshot rig seeds believable values so the run capture shows the
panel populated.
2026-07-16 18:54:39 -04:00

81 lines
3.2 KiB
Swift

#if DEBUG
import SwiftUI
import SwiftData
/// DEBUG-only root used for App Store screenshot capture. Bypasses the iCloud gate and
/// renders one fully-formed screen (chosen by `--screen`) against the seeded cache, so
/// captures are deterministic with no UI automation. Never compiled into release.
struct ScreenshotRootView: View {
let services: AppServices
private var activeWorkout: Workout? {
let context = services.container.mainContext
var descriptor = FetchDescriptor<Workout>(sortBy: [SortDescriptor(\.start, order: .reverse)])
descriptor.fetchLimit = 25
let workouts = (try? context.fetch(descriptor)) ?? []
return workouts.first { $0.status == .inProgress } ?? workouts.first
}
var body: some View {
content
.environment(services)
.environment(services.syncEngine)
.environment(services.liveRunState)
.modelContainer(services.container)
}
@ViewBuilder
private var content: some View {
// The full tab shell and the Progress tab need no particular workout,
// so they're handled before the active-workout unwrap.
if ScreenshotSeed.screen(default: "workouts") == "today" {
ContentView()
} else if ScreenshotSeed.screen(default: "workouts") == "progress" {
ProgressTabView()
} else if ScreenshotSeed.screen(default: "workouts") == "progress-bottom" {
ProgressTabView(initialAnchor: .bottom)
} else if ScreenshotSeed.screen(default: "workouts") == "exercise-trends" {
NavigationStack { ExerciseTrendsListView() }
} else if let workout = activeWorkout {
switch ScreenshotSeed.screen(default: "workouts") {
case "exercise":
let logID = WorkoutDocument(from: workout).logs.first { $0.exerciseName == "Seated Row" }?.id
NavigationStack { ExerciseView(workout: workout, logID: logID ?? "") }
case "run":
ScreenshotRunView(workout: workout)
// Believable watch-fed values so the status panel isn't placeholders.
.onAppear { services.liveRunState.applyLiveSample(bpm: 128, kcal: 236, zone: 3) }
case "settings":
SettingsView()
default:
NavigationStack { WorkoutLogListView(workout: workout) }
}
} else {
Color(.systemBackground)
}
}
}
/// The paged run flow for the in-progress exercise, showing the animated form guide
/// (with machine props) — the screenshot works off its own local document copy, so
/// nothing persists.
private struct ScreenshotRunView: View {
@State private var doc: WorkoutDocument
private let logID: String
init(workout: Workout) {
let document = WorkoutDocument(from: workout)
_doc = State(initialValue: document)
logID = document.logs.first { $0.exerciseName == "Seated Row" }?.id
?? document.logs.first?.id ?? ""
}
var body: some View {
NavigationStack {
ExerciseProgressView(doc: $doc, logID: logID, onChange: {},
onLive: { _ in }, onLiveEnded: {}, incomingFrame: nil)
}
}
}
#endif