Add HIIT watch runner, rest-time setting, and HealthKit watch auto-launch
- 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
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user