Adjust rest time from the watch with the same 1-second stepper

A gear on the watch root opens a small Settings sheet whose stepper
edits the shared restSeconds default (10-180s, 1s steps). Edits ride a
new settingsUpdate message to the phone - debounced per stepper burst,
falling back to transferUserInfo when unreachable - which clamps the
value, writes the shared default, and re-echoes it to every device
through the application context. While an edit is pending on the watch,
an in-flight context's stale rest value is skipped so it can't yank the
stepper back mid-edit.
This commit is contained in:
2026-07-16 20:18:47 -04:00
parent 47a49cc356
commit 373f812968
8 changed files with 125 additions and 3 deletions
@@ -249,6 +249,15 @@ final class PhoneConnectivityBridge: NSObject {
liveRunState.apply(frame)
}
/// A setting edited on the watch. The phone owns the durable value: clamp it, write
/// the shared default (the same store Settings' `@AppStorage` reads), and re-push the
/// application context so every device converges on it.
private func applySettingsUpdate(restSeconds: Int) {
let clamped = min(max(restSeconds, 10), 180)
UserDefaults.standard.set(clamped, forKey: WCPayload.restSecondsKey)
pushAll()
}
/// Parse the (non-Sendable) WC dictionary in the nonisolated delegate context,
/// then hop to the MainActor with only Sendable values.
nonisolated private func route(_ dict: [String: Any]) {
@@ -259,6 +268,10 @@ final class PhoneConnectivityBridge: NSObject {
if let doc = WCPayload.decodeWorkoutUpdate(dict) {
Task { @MainActor in await self.syncEngine.ingestFromWatch(doc) }
}
case WCPayload.settingsUpdateType:
if let rest = WCPayload.decodeRestSeconds(dict) {
Task { @MainActor in self.applySettingsUpdate(restSeconds: rest) }
}
case WCPayload.liveProgressType:
if let frame = WCPayload.decodeLiveProgress(dict) {
Task { @MainActor in self.applyIncomingLive(frame) }