feat(watch): refactor active workout into paged TabView with overview and controls
This commit is contained in:
@@ -415,6 +415,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"End Workout" : {
|
||||
|
||||
},
|
||||
"kcal" : {
|
||||
"extractionState" : "manual",
|
||||
@@ -838,6 +841,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pause" : {
|
||||
|
||||
},
|
||||
"Recent" : {
|
||||
"extractionState" : "manual",
|
||||
@@ -966,6 +972,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rest %@" : {
|
||||
|
||||
},
|
||||
"Resume" : {
|
||||
|
||||
},
|
||||
"Saved Workouts" : {
|
||||
"extractionState" : "manual",
|
||||
|
||||
@@ -4,18 +4,22 @@ struct ActiveWorkoutView: View {
|
||||
@ObservedObject var workoutManager: WorkoutManager
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
TabView {
|
||||
if workoutManager.isResting {
|
||||
RestView(workoutManager: workoutManager)
|
||||
RestPage(workoutManager: workoutManager)
|
||||
} else {
|
||||
SwimView(workoutManager: workoutManager)
|
||||
ActivePage(workoutManager: workoutManager)
|
||||
}
|
||||
WorkoutOverviewPage(workoutManager: workoutManager)
|
||||
ControlsPage(workoutManager: workoutManager)
|
||||
}
|
||||
.padding()
|
||||
.tabViewStyle(.verticalPage)
|
||||
}
|
||||
}
|
||||
|
||||
struct SwimView: View {
|
||||
// MARK: - Page 1: Active / Rest
|
||||
|
||||
struct ActivePage: View {
|
||||
@ObservedObject var workoutManager: WorkoutManager
|
||||
|
||||
var body: some View {
|
||||
@@ -82,24 +86,6 @@ struct SwimView: View {
|
||||
.foregroundColor(.green)
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button {
|
||||
workoutManager.completeLap()
|
||||
} label: {
|
||||
Text("Complete Lap")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.cyan)
|
||||
}
|
||||
|
||||
Button("End") {
|
||||
workoutManager.endWorkout()
|
||||
}
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +96,7 @@ struct SwimView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct RestView: View {
|
||||
struct RestPage: View {
|
||||
@ObservedObject var workoutManager: WorkoutManager
|
||||
|
||||
var body: some View {
|
||||
@@ -141,8 +127,8 @@ struct RestView: View {
|
||||
}
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "flame.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.orange)
|
||||
.font(.caption)
|
||||
Text("\(Int(workoutManager.activeCalories))kcal")
|
||||
.font(.caption)
|
||||
}
|
||||
@@ -157,3 +143,97 @@ struct RestView: View {
|
||||
return String(format: "%d:%02d", mins, secs)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page 2: Workout Overview
|
||||
|
||||
struct WorkoutOverviewPage: View {
|
||||
@ObservedObject var workoutManager: WorkoutManager
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
if workoutManager.isResting {
|
||||
HStack {
|
||||
Image(systemName: "pause.circle.fill")
|
||||
.foregroundColor(.orange)
|
||||
.font(.caption)
|
||||
Text("Rest \(formatTime(workoutManager.restTimeRemaining))")
|
||||
.font(.caption)
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
}
|
||||
|
||||
if let workout = workoutManager.selectedWorkout {
|
||||
ForEach(Array(workout.sets.enumerated()), id: \.offset) { i, set in
|
||||
HStack {
|
||||
Circle()
|
||||
.fill(i < workoutManager.currentSetIndex ? .green :
|
||||
i == workoutManager.currentSetIndex ? .cyan : .gray.opacity(0.4))
|
||||
.frame(width: 8, height: 8)
|
||||
Text("\(i + 1). \(set.stroke)")
|
||||
.font(.caption)
|
||||
.foregroundColor(i <= workoutManager.currentSetIndex ? .primary : .secondary)
|
||||
Spacer()
|
||||
Text("\(set.repCount)x\(set.distancePerRep)m")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
private func formatTime(_ seconds: TimeInterval) -> String {
|
||||
let mins = Int(seconds) / 60
|
||||
let secs = Int(seconds) % 60
|
||||
return String(format: "%d:%02d", mins, secs)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page 3: Controls
|
||||
|
||||
struct ControlsPage: View {
|
||||
@ObservedObject var workoutManager: WorkoutManager
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
if workoutManager.isPaused {
|
||||
workoutManager.resumeWorkout()
|
||||
} else {
|
||||
workoutManager.pauseWorkout()
|
||||
}
|
||||
} label: {
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: workoutManager.isPaused ? "play.fill" : "pause.fill")
|
||||
.font(.title2)
|
||||
Text(workoutManager.isPaused ? "Resume" : "Pause")
|
||||
.font(.caption)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.orange)
|
||||
|
||||
Button {
|
||||
workoutManager.endWorkout()
|
||||
} label: {
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: "stop.fill")
|
||||
.font(.title2)
|
||||
Text("End Workout")
|
||||
.font(.caption)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.red)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate {
|
||||
private var stationarySince: Date?
|
||||
@Published var lapDetected = false
|
||||
@Published var healthKitAuthorized = false
|
||||
@Published var isPaused = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
@@ -170,6 +171,7 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate {
|
||||
stopLapDetection()
|
||||
stopHeartRateQuery()
|
||||
stopCalorieQuery()
|
||||
isPaused = true
|
||||
WKInterfaceDevice.current().play(.notification)
|
||||
}
|
||||
|
||||
@@ -178,6 +180,7 @@ class WorkoutManager: NSObject, ObservableObject, HKLiveWorkoutBuilderDelegate {
|
||||
startLapDetection()
|
||||
startHeartRateQuery()
|
||||
startCalorieQuery()
|
||||
isPaused = false
|
||||
WKInterfaceDevice.current().play(.start)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user