8f69497b24
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
27 lines
760 B
Swift
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() }
|
|
}
|
|
}
|
|
}
|
|
}
|