// // ExerciseProgressControlView.swift // Workouts // // Created by rzen on 7/20/25 at 7:19 PM. // // Copyright 2025 Rouslan Zenetl. All Rights Reserved. // import SwiftUI import SwiftData enum ExerciseState: Identifiable { case detail case set(number: Int) case rest(afterSet: Int) case done var id: String { switch self { case .detail: return "detail" case .set(let number): return "set_\(number)" case .rest(let afterSet): return "rest_\(afterSet)" case .done: return "done" } } var isRest: Bool { if case .rest = self { return true } return false } var isSet: Bool { if case .set = self { return true } return false } var isDone: Bool { if case .done = self { return true } return false } var isDetail: Bool { if case .detail = self { return true } return false } } 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.isDetail { ExerciseDetailView(log: log, onStart: { moveToNextState() }) .tag(index) } else { ExerciseStateView( title: state.isRest ? "Resting..." : state.isDone ? "Done" : "Set \(currentStateIndex)", isRest: state.isRest, isDone: state.isDone, elapsedSeconds: elapsedSeconds, onComplete: { // }) // ExerciseStateView( //// state: state, // // elapsedSeconds: elapsedSeconds, // onComplete: { // moveToNextState() // } // ) .tag(index) } } } .tabViewStyle(.page(indexDisplayMode: .never)) .onChange(of: currentStateIndex) { oldValue, newValue in if oldValue != newValue { // Reset timer when user swipes to a new state elapsedSeconds = 0 } } .onAppear { setupExerciseStates() startTimer() } .onDisappear { stopTimer() } } private func setupExerciseStates() { var states: [ExerciseState] = [] // Add the detail view as the first state states.append(.detail) // Create alternating set and rest states based on the log's set count for i in 1...log.sets { states.append(.set(number: i)) // Add rest after each set except the last one if i < log.sets { states.append(.rest(afterSet: i)) } } // Add the final DONE state 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 let currentState = exerciseStates[safe: 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 { withAnimation { currentStateIndex += 1 elapsedSeconds = 0 } } 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 // Provide "tada" haptic feedback HapticFeedback.tripleTap() // Dismiss this view to return to WorkoutDetailView dismiss() } } struct ExerciseStateView: View { // let state: ExerciseState let title: String let isRest: Bool let isDone: Bool let elapsedSeconds: Int let onComplete: () -> Void var body: some View { VStack(spacing: 20) { // Title based on state Text(title) .font(.title) // Timer display Text(timeFormatted) .font(.system(size: 48, weight: .semibold, design: .monospaced)) .foregroundStyle(isRest ? .orange : .accentColor) // Only show Done button and countdown for the final state if isDone { // Done button Button(action: onComplete) { Text("Done in \(10 - elapsedSeconds)s") .font(.headline) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .tint(.green) .padding(.horizontal) } } .padding() } // private var stateTitle: String { // switch state { // case .set(let number): // return "Set \(number)" // case .rest: // return "Resting..." // case .done: // return "Exercise Complete" // case .detail: // return "Swipe to Start" // } // } // private var buttonTitle: String { // switch state { // case .set: // return "Complete Set" // case .rest: // return "Start Next Set" // case .done: // return "DONE" // } // } // private var buttonColor: Color { // switch state { // case .set: // return .accentColor // case .rest: // return .orange // case .done: // return .green // } // } private var timeFormatted: String { let minutes = elapsedSeconds / 60 let seconds = elapsedSeconds % 60 return String(format: "%02d:%02d", minutes, seconds) } } // Detail view shown as the first item in the exercise progress carousel struct ExerciseDetailView: View { let log: WorkoutLog let onStart: () -> Void var body: some View { VStack(alignment: .center, spacing: 16) { Text(log.exerciseName) .font(.title) .lineLimit(1) .minimumScaleFactor(0.5) .layoutPriority(1) HStack(alignment: .bottom) { Text("\(log.weight)") Text("lbs") .fontWeight(.light) .padding([.trailing], 10) Text("\(log.sets)") Text("×") .fontWeight(.light) Text("\(log.reps)") } .font(.title3) .lineLimit(1) .minimumScaleFactor(0.5) .layoutPriority(1) Text(log.status?.name ?? "Not Started") .foregroundStyle(Color.accentColor) // Spacer() // // Button(action: onStart) { // Text("Start Exercise") // .font(.headline) // .frame(maxWidth: .infinity) // } // .buttonStyle(.borderedProminent) // .tint(.accentColor) } .padding() } } // Helper extension to safely access array elements extension Array { subscript(safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } } //#Preview { // let container = AppContainer.preview // let workout = Workout(start: Date(), end: nil, split: nil) // let log = WorkoutLog(workout: workout, exerciseName: "Bench Press", date: Date(), sets: 3, reps: 10, weight: 135) // // ExerciseProgressControlView(log: log) // .modelContainer(container) //}