// // ExerciseProgressControlView 2.swift // Workouts // // Created by rzen on 7/23/25 at 9:15 AM. // // 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 @State private var hapticCounter: 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, afterSet: state.afterSet ?? 0) .tag(index) } else if state.isDone { ExerciseDoneCard(elapsedSeconds: elapsedSeconds, onComplete: completeExercise, onOneMoreSet: addOneMoreSet) .tag(index) } } } .tabViewStyle(.page(indexDisplayMode: .never)) .gesture( DragGesture() .onEnded { value in // Detect swipe left when on the Done card (last index) if currentStateIndex == exerciseStates.count - 1 && value.translation.width < -50 { // User swiped left from Done card - add one more set addOneMoreSet() } } ) .onChange(of: currentStateIndex) { oldValue, newValue in if oldValue != newValue { elapsedSeconds = 0 hapticCounter = 0 // Reset haptic pattern when changing phases // Update the log's current state but don't auto-advance log.currentStateIndex = currentStateIndex log.elapsedSeconds = elapsedSeconds try? modelContext.save() // Update status based on current state if currentStateIndex > 0 && currentStateIndex < exerciseStates.count - 1 { log.status = .inProgress } } } .onAppear { setupExerciseStates() currentStateIndex = log.currentStateIndex ?? 0 startTimer() } .onChange(of: log.sets) { oldValue, newValue in // Reconstruct exercise states if sets count changed if oldValue != newValue { setupExerciseStates() } } .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 if currentStateIndex >= 0 && currentStateIndex < exerciseStates.count { let currentState = exerciseStates[currentStateIndex] if currentState.isRest || currentState.isSet { provideHapticFeedback() } 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 provideHapticFeedback() { // Provide haptic feedback every 15 seconds in a cycling pattern: 1 → 2 → 3 → long if elapsedSeconds % 15 == 0 && elapsedSeconds > 0 { hapticCounter += 1 switch hapticCounter % 4 { case 1: // First 15 seconds: single tap HapticFeedback.click() case 2: // Second 15 seconds: double tap HapticFeedback.doubleTap() case 3: // Third 15 seconds: triple tap HapticFeedback.tripleTap() case 0: // Fourth 15 seconds: long tap, then reset pattern HapticFeedback.longTap() default: break } } } private func addOneMoreSet() { // Increment total sets log.sets += 1 // Reconstruct exercise states (will trigger onChange) setupExerciseStates() // Calculate the state index for the additional set // States: intro(0) → set1(1) → rest1(2) → ... → setN(2N-1) → done(2N) // For the additional set, we want to go to setN which is at index 2N-1 let additionalSetStateIndex = (log.sets * 2) - 1 log.status = .inProgress log.currentStateIndex = additionalSetStateIndex log.elapsedSeconds = 0 elapsedSeconds = 0 hapticCounter = 0 // Update the current state index for the TabView currentStateIndex = additionalSetStateIndex try? modelContext.save() } 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() } }