// // ExerciseListView.swift // Werkout_ios // // Created by Trey Tartt on 7/7/23. // import SwiftUI import AVKit struct ExerciseListView: View { @AppStorage(Constants.phoneThotStyle) private var phoneThotStyle: ThotStyle = .never @State private var avPlayer = AVPlayer(url: URL(string: BaseURLs.currentBaseURL + "/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4") ?? URL(fileURLWithPath: "/dev/null")) @State private var previewVideoURL: URL? var workout: Workout @Binding var showExecersizeInfo: Bool @AppStorage(Constants.thotGenderOption) private var thotGenderOption: String = "female" let isInWorkout: Bool let currentSupersetIndex: Int let currentExerciseIndex: Int let allSupersetExecerciseIndex: Int let currentExercise: SupersetExercise? let currentWorkout: Workout? let goToExerciseAt: (Int, Int) -> Void @State private var videoExercise: Exercise? { didSet { if let videoURL = VideoURLCreator.videoURL( thotStyle: phoneThotStyle, gender: thotGenderOption, defaultVideoURLStr: self.videoExercise?.videoURL, exerciseName: self.videoExercise?.name, workout: currentWorkout) { updatePreviewPlayer(for: videoURL) } } } var body: some View { let supersets = workout.supersets?.sorted(by: { $0.order < $1.order }) ?? [] if supersets.isEmpty == false { ScrollViewReader { proxy in List() { ForEach(Array(supersets.enumerated()), id: \.offset) { supersetIndex, superset in Section(content: { ForEach(Array(superset.exercises.enumerated()), id: \.offset) { exerciseIndex, supersetExecercise in let rowID = rowIdentifier( supersetIndex: supersetIndex, exerciseIndex: exerciseIndex, exercise: supersetExecercise ) let isCurrentExercise = isInWorkout && supersetIndex == currentSupersetIndex && exerciseIndex == currentExerciseIndex VStack { Button(action: { if isInWorkout { goToExerciseAt(supersetIndex, exerciseIndex) } else { videoExercise = supersetExecercise.exercise } }, label: { HStack { if isCurrentExercise { Image(systemName: "figure.run") .foregroundStyle(WerkoutTheme.accent) } Text(supersetExecercise.exercise.extName) .foregroundStyle(WerkoutTheme.textPrimary) Spacer() if let reps = supersetExecercise.reps, reps > 0 { HStack(spacing: 4) { Image(systemName: "number") Text("\(reps)") } .font(WerkoutTheme.caption) .foregroundStyle(.white) .padding(.horizontal, WerkoutTheme.sm) .padding(.vertical, WerkoutTheme.xs) .background(WerkoutTheme.accent) .clipShape(Capsule()) } if let duration = supersetExecercise.duration, duration > 0 { HStack(spacing: 4) { Image(systemName: "stopwatch") Text("\(duration)") } .font(WerkoutTheme.caption) .foregroundStyle(.white) .padding(.horizontal, WerkoutTheme.sm) .padding(.vertical, WerkoutTheme.xs) .background(WerkoutTheme.success) .clipShape(Capsule()) } } .contentShape(Rectangle()) }) .buttonStyle(.plain) .accessibilityLabel("Exercise \(supersetExecercise.exercise.extName)") .accessibilityHint(isInWorkout ? "Jump to this exercise in the workout" : "Preview exercise video") if isCurrentExercise && showExecersizeInfo { detailView(forExercise: supersetExecercise) } } .listRowBackground( isCurrentExercise ? WerkoutTheme.accent.opacity(0.1) : WerkoutTheme.surfaceCard ) .id(rowID) } }, header: { HStack { Text(superset.name ?? "--") .font(WerkoutTheme.sectionTitle) .foregroundStyle(WerkoutTheme.accent) Spacer() Text("\(superset.rounds) rounds") .font(WerkoutTheme.caption) .foregroundStyle(WerkoutTheme.accent) if let estimatedTime = superset.estimatedTime { Text("@ " + estimatedTime.asString(style: .abbreviated)) .font(WerkoutTheme.caption) .foregroundStyle(WerkoutTheme.accent) } } }) } } .scrollContentBackground(.hidden) .background(WerkoutTheme.background) .onChange(of: allSupersetExecerciseIndex) { _, _ in if let newCurrentExercise = currentExercise { withAnimation { proxy.scrollTo( rowIdentifier( supersetIndex: currentSupersetIndex, exerciseIndex: currentExerciseIndex, exercise: newCurrentExercise ), anchor: .top ) } } } .sheet(item: $videoExercise) { exercise in PlayerView(player: $avPlayer) .onAppear{ avPlayer.isMuted = true avPlayer.play() } } .onDisappear { avPlayer.pause() } } } } private func rowIdentifier(supersetIndex: Int, exerciseIndex: Int, exercise: SupersetExercise) -> String { if let uniqueID = exercise.uniqueID, uniqueID.isEmpty == false { return uniqueID } if let id = exercise.id { return "exercise-\(id)" } return "superset-\(supersetIndex)-exercise-\(exerciseIndex)" } private func updatePreviewPlayer(for url: URL) { if previewVideoURL == url { avPlayer.seek(to: .zero) avPlayer.isMuted = true avPlayer.play() return } previewVideoURL = url avPlayer = AVPlayer(url: url) avPlayer.isMuted = true avPlayer.play() } func detailView(forExercise supersetExecercise: SupersetExercise) -> some View { VStack(spacing: WerkoutTheme.sm) { Text(supersetExecercise.exercise.description) .font(WerkoutTheme.bodyText) .foregroundStyle(WerkoutTheme.textSecondary) .frame(maxWidth: .infinity, alignment: .leading) WerkoutTheme.divider.frame(height: 0.5) Text(supersetExecercise.exercise.muscles.map({ $0.name }).joined(separator: ", ")) .font(WerkoutTheme.caption) .foregroundStyle(WerkoutTheme.accent) .frame(maxWidth: .infinity, alignment: .leading) } } } struct ExerciseListView_Previews: PreviewProvider { static var previews: some View { ExerciseListView( workout: PreviewData.workout(), showExecersizeInfo: .constant(true), isInWorkout: false, currentSupersetIndex: 0, currentExerciseIndex: 0, allSupersetExecerciseIndex: 0, currentExercise: nil, currentWorkout: nil, goToExerciseAt: { _, _ in } ) } }