Files
workouts/Workouts Watch App/Views/Exercises/ExerciseProgressControlView.swift
2025-07-25 17:42:25 -04:00

141 lines
4.5 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ExerciseProgressControlView 2.swift
// Workouts
//
// Created by rzen on 7/23/25 at 9:15AM.
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
struct ExerciseProgressControlView: View {
let log: WorkoutLog
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@State private var exerciseStates: [ExerciseState] = []
@State private var currentStateIndex: Int = 0
@State private var elapsedSeconds: Int = 0
@State private var timer: Timer? = nil
@State private var previousStateIndex: Int = 0
var body: some View {
TabView(selection: $currentStateIndex) {
ForEach(Array(exerciseStates.enumerated()), id: \.element.id) { index, state in
if state.isIntro {
ExerciseIntroCard(log: log)
.tag(index)
} else if state.isSet {
ExerciseSetCard(set: state.setNumber ?? 0, elapsedSeconds: elapsedSeconds)
.tag(index)
} else if state.isRest {
ExerciseRestCard(elapsedSeconds: elapsedSeconds)
.tag(index)
} else if state.isDone {
ExerciseDoneCard(elapsedSeconds: elapsedSeconds, onComplete: completeExercise)
.tag(index)
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.onChange(of: currentStateIndex) { oldValue, newValue in
if oldValue != newValue {
elapsedSeconds = 0
moveToNextState()
}
}
.onAppear {
setupExerciseStates()
currentStateIndex = log.currentStateIndex ?? 0
startTimer()
}
.onDisappear {
stopTimer()
}
}
private func setupExerciseStates() {
var states: [ExerciseState] = []
states.append(.intro)
for i in 1...log.sets {
states.append(.set(number: i))
if i < log.sets {
states.append(.rest(afterSet: i))
}
}
states.append(.done)
exerciseStates = states
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
elapsedSeconds += 1
// Check if we need to provide haptic feedback during rest periods
if currentStateIndex >= 0 && currentStateIndex < exerciseStates.count {
let currentState = exerciseStates[currentStateIndex]
if currentState.isRest {
provideRestHapticFeedback()
} else if currentState.isDone && elapsedSeconds >= 10 {
// Auto-complete after 10 seconds on the DONE state
completeExercise()
}
}
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
}
private func moveToNextState() {
if currentStateIndex < exerciseStates.count - 1 {
elapsedSeconds = 0
withAnimation {
currentStateIndex += 1
log.currentStateIndex = currentStateIndex
log.elapsedSeconds = elapsedSeconds
log.status = .inProgress
try? modelContext.save()
}
} else {
// We've reached the end (DONE state)
completeExercise()
}
}
private func provideRestHapticFeedback() {
// Provide haptic feedback based on elapsed time
if elapsedSeconds % 60 == 0 && elapsedSeconds > 0 {
// Triple tap every 60 seconds
HapticFeedback.tripleTap()
} else if elapsedSeconds % 30 == 0 && elapsedSeconds > 0 {
// Double tap every 30 seconds
HapticFeedback.doubleTap()
} else if elapsedSeconds % 10 == 0 && elapsedSeconds > 0 {
// Single tap every 10 seconds
HapticFeedback.success()
}
}
private func completeExercise() {
// Update the workout log status to completed
log.status = .completed
// reset index in case we wish to re-run the exercise
log.currentStateIndex = 0
// Provide "tada" haptic feedback
HapticFeedback.tripleTap()
// Dismiss this view to return to WorkoutDetailView
dismiss()
}
}