// // MainView.swift // Werkout_ios // // Created by Trey Tartt on 6/14/23. // import SwiftUI import AVKit struct WorkoutDetailView: View { @StateObject var viewModel: WorkoutDetailViewModel @State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!) @StateObject var bridgeModule = BridgeModule.shared @Environment(\.dismiss) var dismiss @AppStorage("showNSFWVideos") private var showNSFWVideos = false enum Sheet: Identifiable { case completedWorkout([String: Any]) var id: String { return UUID().uuidString } } @State var presentedSheet: Sheet? @State var workoutToPlan: Workout? var showAddToCalendar = true var body: some View { ZStack { switch viewModel.status { case .loading: Text("Loading") case .showWorkout(let workout): VStack { if bridgeModule.isInWorkout { HStack { CurrentWorkoutElapsedTimeView() CountdownView() } .padding() GeometryReader { metrics in PlayerView(player: $avPlayer) .frame(width: metrics.size.width * 1, height: metrics.size.height * 1) .onAppear{ avPlayer.play() } } } InfoView(workout: workout) Divider() .padding([.leading, .trailing]) ExerciseListView(workout: workout) ActionsView(completedWorkout: { bridgeModule.completeWorkout() }, planWorkout: { workout in workoutToPlan = workout }, workout: workout, showAddToCalendar: showAddToCalendar) .frame(height: 44) } .sheet(item: $presentedSheet) { item in switch item { case .completedWorkout(let data): CompletedWorkoutView(postData: data, workout: workout, completedWorkoutDismissed: { uploaded in if uploaded { dismiss() } }) } } .sheet(item: $workoutToPlan) { workout in PlanWorkoutView(workout: workout, addedPlannedWorkout: { dismiss() }) } } } .onChange(of: bridgeModule.currentExercise, perform: { newValue in var _url: URL? if showNSFWVideos { if let viddd = newValue?.exercise.nsfwVideoURL, let url = URL(string: BaseURLs.currentBaseURL + viddd) { _url = url } } else { if let viddd = newValue?.exercise.videoURL, let url = URL(string: BaseURLs.currentBaseURL + viddd) { _url = url } } if let __url = _url { avPlayer = AVPlayer(url: __url) avPlayer.play() } }) .onAppear{ var _url: URL? if let currentExercise = bridgeModule.currentExercise { if showNSFWVideos { let viddd = currentExercise.exercise.nsfwVideoURL if let url = URL(string: BaseURLs.currentBaseURL + viddd) { _url = url } } else { let viddd = currentExercise.exercise.videoURL if let url = URL(string: BaseURLs.currentBaseURL + viddd) { _url = url } } if let __url = _url { avPlayer = AVPlayer(url: __url) avPlayer.play() } } bridgeModule.completedWorkout = { if let workoutData = createWorkoutData() { presentedSheet = .completedWorkout(workoutData) bridgeModule.resetCurrentWorkout() } } } } func createWorkoutData() -> [String:Any]? { guard let workoutid = bridgeModule.currentWorkout?.id, let startTime = bridgeModule.workoutStartDate?.timeFormatForUpload, let endTime = bridgeModule.workoutEndDate?.timeFormatForUpload else { return nil } let postBody = [ "difficulty": 1, "workout_start_time": startTime, "workout_end_time": endTime, "workout": workoutid, "total_time": bridgeModule.currentWorkoutRunTimeInSeconds, "total_calories": bridgeModule.totalCaloire ?? -1, "heart_rates": bridgeModule.heartRates ?? [Int]() ] as [String : Any] return postBody } } struct InfoView: View { @ObservedObject var bridgeModule = BridgeModule.shared var workout: Workout var body: some View { VStack { if bridgeModule.isInWorkout == false { Text(workout.name) .frame(maxWidth: .infinity, alignment: .leading) .font(.title3) .padding() if let desc = workout.description { Text(desc) .frame(maxWidth: .infinity, alignment: .leading) .font(.body) .padding([.leading, .trailing]) } } } } } struct ActionsView: View { @ObservedObject var bridgeModule = BridgeModule.shared var completedWorkout: (() -> Void)? var planWorkout: ((Workout) -> Void)? var workout: Workout @Environment(\.dismiss) var dismiss var showAddToCalendar: Bool var body: some View { HStack { if bridgeModule.isInWorkout == false { Button(action: { bridgeModule.resetCurrentWorkout() dismiss() }, label: { Image(systemName: "xmark.octagon.fill") .font(.title) .frame(maxWidth: .infinity, maxHeight: .infinity) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.red) .foregroundColor(.white) if showAddToCalendar { Button(action: { planWorkout?(workout) }, label: { Image(systemName: "calendar.badge.plus") .font(.title) .frame(maxWidth: .infinity, maxHeight: .infinity) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.blue) .foregroundColor(.white) } Button(action: { startWorkout() }, label: { Image(systemName: "arrowtriangle.forward.fill") .font(.title) .frame(maxWidth: .infinity, maxHeight: .infinity) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.green) .foregroundColor(.white) } else { Button(action: { nextExercise() }, label: { Image(systemName: "arrow.forward") .font(.title) .frame(maxWidth: .infinity, maxHeight: .infinity) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.green) .foregroundColor(.white) Button(action: { bridgeModule.pauseWorkout() }, label: { Image(systemName: "pause.circle.fill") .font(.title) .frame(maxWidth: .infinity, maxHeight: .infinity) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.yellow) .foregroundColor(.white) Button(action: { completedWorkout?() }, label: { Image(systemName: "checkmark") .font(.title) .frame(maxWidth: .infinity, maxHeight: .infinity) }) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.blue) .foregroundColor(.white) } } } func nextExercise() { bridgeModule.nextExercise() } func startWorkout() { bridgeModule.start(workout: workout) } } struct CurrentWorkoutElapsedTimeView: View { @ObservedObject var bridgeModule = BridgeModule.shared var body: some View { if bridgeModule.currentWorkoutRunTimeInSeconds > -1 { Text("\(bridgeModule.currentWorkoutRunTimeInSeconds)") .font(.title2) } } } struct ExerciseListView: View { @AppStorage("showNSFWVideos") private var showNSFWVideos = false @ObservedObject var bridgeModule = BridgeModule.shared var workout: Workout @State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!) @State var videoExercise: ExerciseExercise? { didSet { if showNSFWVideos { if let viddd = self.videoExercise?.nsfwVideoURL, let url = URL(string: BaseURLs.currentBaseURL + viddd) { avPlayer = AVPlayer(url: url) } } else { if let viddd = self.videoExercise?.videoURL, let url = URL(string: BaseURLs.currentBaseURL + viddd) { avPlayer = AVPlayer(url: url) } } } } var body: some View { ScrollViewReader { proxy in List() { ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in let obj = workout.exercisesSortedByCreated_at[i] HStack { if i == bridgeModule.currentExerciseIdx { Image(systemName: "checkmark") .foregroundColor(.green) } Text(obj.exercise.name) .id(i) if let reps = obj.reps, reps > 0 { Text("Reps: \(reps)") .frame(maxWidth: .infinity, alignment: .trailing) } if let weight = obj.weight, weight > 0 { Text(" - Weight: \(weight)") .frame(maxWidth: .infinity, alignment: .trailing) } if let duration = obj.duration, duration > 0 { Text("Duration: \(duration)") .frame(maxWidth: .infinity, alignment: .trailing) } Spacer() } .contentShape(Rectangle()) .onTapGesture { if bridgeModule.isInWorkout { bridgeModule.goToExerciseAt(index: i) } else { videoExercise = obj.exercise } } } } .onChange(of: bridgeModule.currentExerciseIdx, perform: { newValue in withAnimation { proxy.scrollTo(newValue, anchor: .top) } }) } .sheet(item: $videoExercise) { exercise in PlayerView(player: $avPlayer) .onAppear{ avPlayer.play() } } } } struct CountdownView: View { @StateObject var bridgeModule = BridgeModule.shared var body: some View { if let duration = bridgeModule.currentExercise?.duration, duration > 0 { HStack { if bridgeModule.currentExerciseTimeLeft >= 0 && duration > bridgeModule.currentExerciseTimeLeft { ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration)) Text("\(bridgeModule.currentExerciseTimeLeft)") .font(.body) } } } } } struct WorkoutDetailView_Previews: PreviewProvider { static let workoutDetail = PreviewData.workout() static var previews: some View { WorkoutDetailView(viewModel: WorkoutDetailViewModel(workout: WorkoutDetailView_Previews.workoutDetail, status: .showWorkout(WorkoutDetailView_Previews.workoutDetail))) } }