Files
SwimBuddy/targets/watch/Views/ActiveWorkoutView.swift
T

240 lines
8.2 KiB
Swift

import SwiftUI
struct ActiveWorkoutView: View {
@ObservedObject var workoutManager: WorkoutManager
var body: some View {
TabView {
if workoutManager.isResting {
RestPage(workoutManager: workoutManager)
} else {
ActivePage(workoutManager: workoutManager)
}
WorkoutOverviewPage(workoutManager: workoutManager)
ControlsPage(workoutManager: workoutManager)
}
.tabViewStyle(.verticalPage)
}
}
// MARK: - Page 1: Active / Rest
struct ActivePage: View {
@ObservedObject var workoutManager: WorkoutManager
var body: some View {
VStack(spacing: 6) {
if let workout = workoutManager.selectedWorkout,
workoutManager.currentSetIndex < workout.sets.count {
let currentSet = workout.sets[workoutManager.currentSetIndex]
HStack {
Text("Set \(workoutManager.currentSetIndex + 1)/\(workout.sets.count)")
.font(.caption2)
.foregroundColor(.secondary)
Text("·")
.foregroundColor(.secondary)
Text("Rep \(workoutManager.currentRep + 1)/\(currentSet.repCount)")
.font(.caption2)
.foregroundColor(.secondary)
}
Text(currentSet.stroke)
.font(.headline)
.foregroundColor(.cyan)
Text("\(Int(workoutManager.currentSetDistanceSwum))/\(Int(workoutManager.currentSetTotalDistance))m")
.font(.system(size: 36, weight: .bold, design: .rounded))
.foregroundColor(.cyan)
}
HStack(spacing: 4) {
Image(systemName: "figure.pool.swim")
.font(.caption)
.foregroundColor(.cyan)
Text("\(Int(workoutManager.totalDistanceSwum))m total")
.font(.caption)
.foregroundColor(.secondary)
}
Text(formatTime(workoutManager.elapsedSeconds))
.font(.system(.title3, design: .monospaced))
HStack(spacing: 12) {
HStack(spacing: 4) {
Image(systemName: "flame.fill")
.foregroundColor(.orange)
.font(.caption)
Text("\(Int(workoutManager.activeCalories))")
.font(.caption)
}
if workoutManager.heartRate > 0 {
HStack(spacing: 4) {
Image(systemName: "heart.fill")
.foregroundColor(.red)
.font(.caption)
Text("\(Int(workoutManager.heartRate))")
.font(.caption)
}
}
}
if workoutManager.lapDetected {
Text("Lap detected!")
.font(.caption)
.foregroundColor(.green)
.transition(.opacity)
}
}
}
private func formatTime(_ seconds: TimeInterval) -> String {
let mins = Int(seconds) / 60
let secs = Int(seconds) % 60
return String(format: "%d:%02d", mins, secs)
}
}
struct RestPage: View {
@ObservedObject var workoutManager: WorkoutManager
var body: some View {
VStack(spacing: 12) {
Text("Rest")
.font(.headline)
.foregroundColor(.secondary)
Text(formatTime(workoutManager.restTimeRemaining))
.font(.system(size: 44, weight: .bold, design: .monospaced))
.foregroundColor(.cyan)
if let workout = workoutManager.selectedWorkout,
workoutManager.currentSetIndex + 1 < workout.sets.count {
let nextSet = workout.sets[workoutManager.currentSetIndex + 1]
Text("Next: \(nextSet.stroke) \(nextSet.distancePerRep)m")
.font(.caption)
.foregroundColor(.secondary)
}
HStack(spacing: 12) {
HStack(spacing: 4) {
Image(systemName: "figure.pool.swim")
.font(.caption)
.foregroundColor(.cyan)
Text("\(Int(workoutManager.totalDistanceSwum))m")
.font(.caption)
}
HStack(spacing: 4) {
Image(systemName: "flame.fill")
.foregroundColor(.orange)
.font(.caption)
Text("\(Int(workoutManager.activeCalories))kcal")
.font(.caption)
}
}
.foregroundColor(.secondary)
}
}
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 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()
}
}
}