d5915a9552
- Redesign the watch app into an active-workout runner: a root gate shows the in-progress workout's exercises or prompts to start one on iPhone, and each exercise runs as a horizontally-paged HIIT cycle (count-up work, count-down rest with final-three-second haptics + auto-advance, One More / Done on the last set). Replaces the old history list. - Add a configurable rest-between-sets duration in iPhone Settings (default 45s), synced to the watch over WatchConnectivity. - Launch the watch app into the session when a workout starts on the phone via HealthKit (startWatchApp); the watch runs an HKWorkoutSession for foreground runtime and ends it when the workout finishes. Adds the HealthKit entitlement + Health usage strings on both targets and WKBackgroundModes on the watch. Claude-Session: https://claude.ai/code/session_018gg69MaUetDNzWzBXisfMV
59 lines
2.0 KiB
Swift
59 lines
2.0 KiB
Swift
//
|
|
// ActiveWorkoutGateView.swift
|
|
// Workouts Watch App
|
|
//
|
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
|
|
/// Root of the watch app. The watch only runs the workout that's currently active
|
|
/// on the phone — there's no history browsing here. The "active" workout is the most
|
|
/// recent cached one that isn't finished (`notStarted` or `inProgress`); a workout is
|
|
/// created on the phone as `notStarted` the moment a split is picked, and flips to
|
|
/// `completed` once every exercise is done, at which point we fall back to the gate.
|
|
struct ActiveWorkoutGateView: View {
|
|
@Environment(WatchConnectivityBridge.self) private var bridge
|
|
@Environment(WorkoutSessionManager.self) private var sessionManager
|
|
|
|
@Query(sort: \Workout.start, order: .reverse) private var workouts: [Workout]
|
|
|
|
private var activeWorkout: Workout? {
|
|
workouts.first { $0.status == .inProgress || $0.status == .notStarted }
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
if let workout = activeWorkout {
|
|
WorkoutLogListView(workout: workout)
|
|
} else {
|
|
emptyState
|
|
}
|
|
}
|
|
.task {
|
|
// Nothing to run yet — pull fresh state in case the phone just started one.
|
|
if activeWorkout == nil { bridge.requestSync() }
|
|
}
|
|
.onChange(of: activeWorkout == nil) { _, noActiveWorkout in
|
|
// The workout finished (or was cleared) — release the HealthKit session that
|
|
// was keeping the launched app alive.
|
|
if noActiveWorkout { sessionManager.end() }
|
|
}
|
|
}
|
|
|
|
private var emptyState: some View {
|
|
ContentUnavailableView {
|
|
Label("Start a Workout", systemImage: "iphone")
|
|
} description: {
|
|
Text("Begin a workout on your iPhone to run it here.")
|
|
} actions: {
|
|
Button {
|
|
bridge.requestSync()
|
|
} label: {
|
|
Label("Refresh", systemImage: "arrow.triangle.2.circlepath")
|
|
}
|
|
}
|
|
}
|
|
}
|