// // ExerciseView.swift // Workouts // // Created by rzen on 7/18/25 at 5:44 PM. // // Copyright 2025 Rouslan Zenetl. All Rights Reserved. // import SwiftUI import CoreData import Charts struct ExerciseView: View { @Environment(\.managedObjectContext) private var viewContext @Environment(\.dismiss) private var dismiss @ObservedObject var workoutLog: WorkoutLog @State private var progress: Int = 0 @State private var showingPlanEdit = false @State private var showingNotesEdit = false let notStartedColor = Color.white let completedColor = Color.green var body: some View { Form { // MARK: - Progress Section Section(header: Text("Progress")) { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: Int(workoutLog.sets)), spacing: 4) { ForEach(1...max(1, Int(workoutLog.sets)), id: \.self) { index in ZStack { let completed = index <= progress let color = completed ? completedColor : notStartedColor RoundedRectangle(cornerRadius: 8) .fill( LinearGradient( gradient: Gradient(colors: [color, color.darker(by: 0.2)]), startPoint: .topLeading, endPoint: .bottomTrailing ) ) .aspectRatio(0.618, contentMode: .fit) .shadow(radius: 2) Text("\(index)") .font(.title) .fontWeight(.bold) .foregroundColor(.primary) .colorInvert() } .onTapGesture { let totalSets = Int(workoutLog.sets) let isLastTile = index == totalSets let wasAlreadyAtThisProgress = progress == index withAnimation(.easeInOut(duration: 0.2)) { if wasAlreadyAtThisProgress { progress = 0 } else { progress = index } } updateLogStatus() // If tapping the last tile to complete, go back to list if isLastTile && !wasAlreadyAtThisProgress { dismiss() } } } } } // MARK: - Plan Section (Read-only with Edit button) Section { PlanTilesView(workoutLog: workoutLog) } header: { HStack { Text("Plan") Spacer() Button("Edit") { showingPlanEdit = true } .font(.subheadline) .textCase(.none) } } // MARK: - Notes Section (Read-only with Edit button) Section { if let notes = workoutLog.notes, !notes.isEmpty { Text(notes) .foregroundColor(.primary) } else { Text("No notes") .foregroundColor(.secondary) .italic() } } header: { HStack { Text("Notes") Spacer() Button("Edit") { showingNotesEdit = true } .font(.subheadline) .textCase(.none) } } // MARK: - Progress Tracking Chart Section(header: Text("Progress Tracking")) { WeightProgressionChartView(exerciseName: workoutLog.exerciseName) } } .navigationTitle(workoutLog.exerciseName) .sheet(isPresented: $showingPlanEdit) { PlanEditView(workoutLog: workoutLog) } .sheet(isPresented: $showingNotesEdit) { NotesEditView(workoutLog: workoutLog) } .onAppear { progress = Int(workoutLog.currentStateIndex) } } private func updateLogStatus() { workoutLog.currentStateIndex = Int32(progress) if progress >= Int(workoutLog.sets) { workoutLog.status = .completed workoutLog.completed = true } else if progress > 0 { workoutLog.status = .inProgress workoutLog.completed = false } else { workoutLog.status = .notStarted workoutLog.completed = false } updateWorkoutStatus() saveChanges() } private func updateWorkoutStatus() { guard let workout = workoutLog.workout else { return } let logs = workout.logsArray let allCompleted = logs.allSatisfy { $0.status == .completed } let anyInProgress = logs.contains { $0.status == .inProgress } let allNotStarted = logs.allSatisfy { $0.status == .notStarted } if allCompleted { workout.status = .completed workout.end = Date() } else if anyInProgress || !allNotStarted { workout.status = .inProgress } else { workout.status = .notStarted } } private func saveChanges() { try? viewContext.save() } } // MARK: - Plan Tiles View struct PlanTilesView: View { @ObservedObject var workoutLog: WorkoutLog var body: some View { if workoutLog.loadTypeEnum == .duration { // Duration layout: Sets | Duration HStack(spacing: 0) { PlanTile(label: "Sets", value: "\(workoutLog.sets)") PlanTile(label: "Duration", value: formattedDuration) } } else { // Weight layout: Sets | Reps | Weight HStack(spacing: 0) { PlanTile(label: "Sets", value: "\(workoutLog.sets)") PlanTile(label: "Reps", value: "\(workoutLog.reps)") PlanTile(label: "Weight", value: "\(workoutLog.weight) lbs") } } } private var formattedDuration: String { let mins = workoutLog.durationMinutes let secs = workoutLog.durationSeconds if mins > 0 && secs > 0 { return "\(mins)m \(secs)s" } else if mins > 0 { return "\(mins) min" } else if secs > 0 { return "\(secs) sec" } else { return "0 sec" } } } struct PlanTile: View { let label: String let value: String var body: some View { VStack(spacing: 4) { Text(label) .font(.caption) .foregroundColor(.secondary) Text(value) .font(.title2) .fontWeight(.semibold) .foregroundColor(.primary) } .frame(maxWidth: .infinity) .padding(.vertical, 12) .background(Color(.systemGray6)) .cornerRadius(8) } }