diff --git a/targets/watch/Localizable.xcstrings b/targets/watch/Localizable.xcstrings index 915af61..8597650 100644 --- a/targets/watch/Localizable.xcstrings +++ b/targets/watch/Localizable.xcstrings @@ -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", diff --git a/targets/watch/Views/ActiveWorkoutView.swift b/targets/watch/Views/ActiveWorkoutView.swift index 64ae54f..2df19f6 100644 --- a/targets/watch/Views/ActiveWorkoutView.swift +++ b/targets/watch/Views/ActiveWorkoutView.swift @@ -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() + } + } +} diff --git a/targets/watch/WorkoutManager.swift b/targets/watch/WorkoutManager.swift index 00da603..445403a 100644 --- a/targets/watch/WorkoutManager.swift +++ b/targets/watch/WorkoutManager.swift @@ -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) }