251 lines
8.6 KiB
Swift
251 lines
8.6 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) {
|
|
if workoutManager.isWaterLockEnabled {
|
|
HStack(spacing: 4) {
|
|
Image(systemName: "drop.fill")
|
|
.foregroundColor(.cyan)
|
|
.font(.caption)
|
|
Text("Water Lock")
|
|
.font(.caption)
|
|
.foregroundColor(.cyan)
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
}
|