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.
81 lines
3.2 KiB
Swift
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
|