Files
workouts/Workouts/ContentView.swift
T
rzen 8f69497b24 Make the live run two-way: drive from either device
The propped-up iPhone now runs the real ExerciseProgressView for a live
watch workout instead of a read-only mirror, and the live-run channel is
symmetric — either device can drive the flow and the other follows.

Each page transition is classified human / auto / remote: only human
transitions (swipe, Start, One More, swipe-back reset) are broadcast and
recorded by the actor; auto-advances (rest / timed-work countdown) record
locally but aren't sent, since both devices reach them independently off
the shared wall-clock anchors; an applied remote frame jumps the page
without re-recording or re-broadcasting. That rule is also what stops an
echo loop.

- PhoneConnectivityBridge gains sendLiveProgress/sendLiveEnded (the
  missing phone->watch direction); WatchConnectivityBridge receives
  frames into an observable liveIncoming via a new didReceiveMessage
  route. Both share one increasing per-run version sequence so the
  stale-frame guard works across the two devices' counters.
- Both ExerciseProgressViews gain an incomingFrame input + applyIncoming
  (syncing setCount for a remote One More); the iPhone one gains the
  liveSnapshot/broadcast machinery the watch already had.
- New LiveRunCoverView wraps the real driver for the cover (resolves the
  workout, persists via SyncEngine, wires the live channel + close);
  ContentView presents it; LiveProgressMirrorView is removed.

Claude-Session: https://claude.ai/code/session_01SCv7zvGFcKy47KSTnTLxRe
2026-06-20 22:11:05 -04:00

27 lines
760 B
Swift

//
// ContentView.swift
// Workouts
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
struct ContentView: View {
@Environment(LiveRunState.self) private var liveRun
var body: some View {
WorkoutLogsView()
// Prop the phone up and it runs the live Apple Watch workout two-way: drive from
// either device and the other follows.
.fullScreenCover(isPresented: Binding(
get: { liveRun.presentable != nil },
set: { presenting in if !presenting { liveRun.mute() } }
)) {
if let frame = liveRun.presentable {
LiveRunCoverView(frame: frame) { liveRun.mute() }
}
}
}
}