From 85dfc0a9314bd2f8462fd63a91600289cb32aa8d Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 3 Jul 2023 20:18:08 -0500 Subject: [PATCH] WIP --- Werkout_ios/APIModels/Exercise.swift | 3 +- Werkout_ios/BridgeModule.swift | 21 ++++ Werkout_ios/ToDo | 2 +- .../Views/AccountView/AccountView.swift | 3 + .../Views/ExternalWorkoutDetailView.swift | 119 ++++++++++++------ Werkout_ios/Views/VideoPlayerView.swift | 7 ++ .../WorkoutDetail/WorkoutDetailView.swift | 113 +++++++++++------ Werkout_ios/Werkout_iosApp.swift | 2 + 8 files changed, 190 insertions(+), 80 deletions(-) diff --git a/Werkout_ios/APIModels/Exercise.swift b/Werkout_ios/APIModels/Exercise.swift index aab61e5..6f1b573 100644 --- a/Werkout_ios/APIModels/Exercise.swift +++ b/Werkout_ios/APIModels/Exercise.swift @@ -33,7 +33,7 @@ struct ExerciseExercise: Codable, Hashable, Identifiable { let id: Int let muscles: [ExerciseMuscle] let equipment: [ExerciseEquipment] - let audioURL, videoURL, createdAt, updatedAt: String + let audioURL, videoURL, createdAt, updatedAt, nsfwVideoURL: String let name, description, side: String let isTwoDumbbells, isTrackableDistance, isAlternating, isWeight: Bool let isDistance, isDuration, isReps: Bool @@ -59,5 +59,6 @@ struct ExerciseExercise: Codable, Hashable, Identifiable { case equipmentRequired = "equipment_required" case muscleGroups = "muscle_groups" case synonyms + case nsfwVideoURL = "nsfw_video_url" } } diff --git a/Werkout_ios/BridgeModule.swift b/Werkout_ios/BridgeModule.swift index d8dea64..3dc99ad 100644 --- a/Werkout_ios/BridgeModule.swift +++ b/Werkout_ios/BridgeModule.swift @@ -7,6 +7,7 @@ import Foundation import WatchConnectivity +import AVFoundation enum WatchActions: Codable { case nextExercise @@ -125,6 +126,14 @@ class BridgeModule: NSObject, ObservableObject { let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date()) let data = try! JSONEncoder().encode(watchModel) send(data) + + if currentExerciseTimeLeft == 0 { + playFinished() + } else { + if currentExerciseTimeLeft < 4 { + playBeep() + } + } } else { nextExercise() } @@ -166,6 +175,18 @@ class BridgeModule: NSObject, ObservableObject { completedWorkout?() } } + + func playBeep() { +#if os(iOS) + AudioServicesPlaySystemSound(SystemSoundID(1052)) +#endif + } + + func playFinished() { +#if os(iOS) + AudioServicesPlaySystemSound(SystemSoundID(1070)) +#endif + } } extension BridgeModule: WCSessionDelegate { diff --git a/Werkout_ios/ToDo b/Werkout_ios/ToDo index 33599ed..2df622a 100644 --- a/Werkout_ios/ToDo +++ b/Werkout_ios/ToDo @@ -7,6 +7,6 @@ workout history view -calorie upload -video view on iphone during workout +-video view on iphone during workout edit weights on workouts diff --git a/Werkout_ios/Views/AccountView/AccountView.swift b/Werkout_ios/Views/AccountView/AccountView.swift index 58be1d7..207e242 100644 --- a/Werkout_ios/Views/AccountView/AccountView.swift +++ b/Werkout_ios/Views/AccountView/AccountView.swift @@ -12,6 +12,7 @@ struct AccountView: View { @State var completedWorkouts: [CompletedWorkout]? @ObservedObject var userStore = UserStore.shared @State var showCompletedWorkouts: Bool = false + @AppStorage("showNSFWVideos") private var showNSFWVideos = false var body: some View { VStack(alignment: .leading) { @@ -60,6 +61,8 @@ struct AccountView: View { } } + Divider() + Toggle("Show NSFW Videos", isOn: $showNSFWVideos) Spacer() Button("Logout", action: { diff --git a/Werkout_ios/Views/ExternalWorkoutDetailView.swift b/Werkout_ios/Views/ExternalWorkoutDetailView.swift index 16493d6..2584772 100644 --- a/Werkout_ios/Views/ExternalWorkoutDetailView.swift +++ b/Werkout_ios/Views/ExternalWorkoutDetailView.swift @@ -11,7 +11,8 @@ import AVKit struct ExternalWorkoutDetailView: View { @StateObject var bridgeModule = BridgeModule.shared @State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!) - + @AppStorage("showNSFWVideos") private var showNSFWVideos = false + var body: some View { ZStack { if let workout = bridgeModule.currentWorkout { @@ -23,7 +24,7 @@ struct ExternalWorkoutDetailView: View { ExtExerciseList(workout: workout, currentExerciseIdx: bridgeModule.currentExerciseIdx) - .frame(width: metrics.size.width * 0.4, height: metrics.size.height * 0.8) + .frame(width: metrics.size.width * 0.3, height: metrics.size.height * 0.8) } @@ -38,9 +39,16 @@ struct ExternalWorkoutDetailView: View { } } .onChange(of: bridgeModule.currentExercise, perform: { newValue in - if let viddd = newValue?.exercise.videoURL, - let url = URL(string: BaseURLs.currentBaseURL + viddd) { - avPlayer = AVPlayer(url: url) + if showNSFWVideos { + if let viddd = newValue?.exercise.nsfwVideoURL, + let url = URL(string: BaseURLs.currentBaseURL + viddd) { + avPlayer = AVPlayer(url: url) + } + } else { + if let viddd = newValue?.exercise.videoURL, + let url = URL(string: BaseURLs.currentBaseURL + viddd) { + avPlayer = AVPlayer(url: url) + } } }) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -74,21 +82,35 @@ struct ExtExerciseList: View { var currentExerciseIdx: Int var body: some View { - List() { - ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in - let obj = workout.exercisesSortedByCreated_at[i] - HStack { - if i == currentExerciseIdx { - Image(systemName: "checkmark") + ScrollViewReader { proxy in + List() { + ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in + let obj = workout.exercisesSortedByCreated_at[i] + HStack { + if i == currentExerciseIdx { + Image(systemName: "checkmark") + .font(Font.system(size: 75)) + .scaledToFit() + .minimumScaleFactor(0.01) + .lineLimit(1) + .foregroundColor(.green) + } + + Text(obj.exercise.name) .font(Font.system(size: 75)) - .foregroundColor(.green) + .scaledToFit() + .minimumScaleFactor(0.01) + .lineLimit(1) + .padding() + .id(i) } - - Text(obj.exercise.name) - .font(Font.system(size: 75)) - .padding() } } + .onChange(of: currentExerciseIdx, perform: { newValue in + withAnimation { + proxy.scrollTo(newValue, anchor: .top) + } + }) } } } @@ -97,32 +119,49 @@ struct ExtCountdownView: View { @StateObject var bridgeModule = BridgeModule.shared var body: some View { - VStack { - if let currenExercise = bridgeModule.currentExercise { - HStack { - Text(currenExercise.exercise.name) - .font(Font.system(size: 100)) - .frame(maxWidth: .infinity, alignment: .leading) + GeometryReader { metrics in + VStack { + if let currenExercise = bridgeModule.currentExercise { + HStack { + Text(currenExercise.exercise.name) + .font(.system(size: 200)) + .scaledToFit() + .minimumScaleFactor(0.01) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .leading) + + if bridgeModule.currentWorkoutRunTimeInSeconds > -1 { + Text("\(bridgeModule.currentWorkoutRunTimeInSeconds)") + .font(Font.system(size: 100)) + .scaledToFit() + .minimumScaleFactor(0.01) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.trailing, 100) + } + } + .frame(height: metrics.size.height * 0.5) - if bridgeModule.currentWorkoutRunTimeInSeconds > -1 { - Text("\(bridgeModule.currentWorkoutRunTimeInSeconds)") - .font(Font.system(size: 100)) - .frame(maxWidth: .infinity, alignment: .trailing) - .padding(.trailing, 100) - } - } - HStack { - if let duration = currenExercise.duration { - ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration)) - .scaleEffect(x: 1, y: 6, anchor: .center) - Text("\(bridgeModule.currentExerciseTimeLeft)") - .font(Font.system(size: 75)) - .padding([.leading, .trailing]) - } else if let reps = currenExercise.reps { - Text("\(reps)") - .font(Font.system(size: 75)) - .padding([.leading, .trailing]) + HStack { + if let duration = currenExercise.duration { + ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration)) + .scaleEffect(x: 1, y: 6, anchor: .center) + Text("\(bridgeModule.currentExerciseTimeLeft)") + .font(Font.system(size: 75)) + .scaledToFit() + .minimumScaleFactor(0.01) + .lineLimit(1) + .padding([.leading, .trailing]) + } else if let reps = currenExercise.reps { + Text("\(reps)") + .font(Font.system(size: 75)) + .scaledToFit() + .minimumScaleFactor(0.01) + .lineLimit(1) + .padding([.leading, .trailing]) + } } + .frame(height: metrics.size.height * 0.5) } } } diff --git a/Werkout_ios/Views/VideoPlayerView.swift b/Werkout_ios/Views/VideoPlayerView.swift index cec25bf..8e4bf2d 100644 --- a/Werkout_ios/Views/VideoPlayerView.swift +++ b/Werkout_ios/Views/VideoPlayerView.swift @@ -31,6 +31,8 @@ struct VideoPlayerView: View { VideoPlayer(player: avPlayer) .onAppear{ + _ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers) + avPlayer.play() } } @@ -38,8 +40,13 @@ struct VideoPlayerView: View { .onReceive(pub) { (output) in avPlayer.pause() avPlayer.seek(to: .zero) + _ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers) + avPlayer.play() } + .onChange(of: avPlayer, perform: { newValue in + avPlayer.play() + }) } } diff --git a/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift b/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift index d601641..43d71d1 100644 --- a/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift +++ b/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift @@ -6,11 +6,15 @@ // import SwiftUI +import AVKit struct WorkoutDetailView: View { @StateObject var viewModel: WorkoutDetailViewModel - var bridgeModule = BridgeModule.shared + @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]) @@ -28,11 +32,21 @@ struct WorkoutDetailView: View { Text("Loading") case .showWorkout(let workout): VStack { + if bridgeModule.isInWorkout { + HStack { + CurrentWorkoutElapsedTimeView() + CountdownView() + } + .padding() + + GeometryReader { metrics in + WorkoutDetailVideoPlayerView(avPlayer: $avPlayer) + .frame(width: metrics.size.width * 1, height: metrics.size.height * 1) + } + } InfoView(workout: workout) Divider() - CurrentWorkoutElapsedTimeView() - .padding(.top) - CountdownView() + .padding([.leading, .trailing]) ExerciseListView(workout: workout) ActionsView(completedWorkout: { bridgeModule.completeWorkout() @@ -60,6 +74,19 @@ struct WorkoutDetailView: View { .interactiveDismissDisabled() } } + .onChange(of: bridgeModule.currentExercise, perform: { newValue in + if showNSFWVideos { + if let viddd = newValue?.exercise.nsfwVideoURL, + let url = URL(string: BaseURLs.currentBaseURL + viddd) { + avPlayer = AVPlayer(url: url) + } + } else { + if let viddd = newValue?.exercise.videoURL, + let url = URL(string: BaseURLs.currentBaseURL + viddd) { + avPlayer = AVPlayer(url: url) + } + } + }) .onAppear{ bridgeModule.completedWorkout = { if let workoutData = createWorkoutData() { @@ -68,6 +95,7 @@ struct WorkoutDetailView: View { } } } + } func createWorkoutData() -> [String:Any]? { @@ -90,6 +118,15 @@ struct WorkoutDetailView: View { } } +struct WorkoutDetailVideoPlayerView: View { + @ObservedObject var bridgeModule = BridgeModule.shared + @Binding var avPlayer: AVPlayer + + var body: some View { + VideoPlayerView(avPlayer: $avPlayer, showDoneButton: false) + } +} + struct InfoView: View { @ObservedObject var bridgeModule = BridgeModule.shared var workout: Workout @@ -209,43 +246,44 @@ struct ExerciseListView: View { @ObservedObject var bridgeModule = BridgeModule.shared var workout: Workout + var body: some View { List() { - ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in - let obj = workout.exercisesSortedByCreated_at[i] - VStack { - HStack { - if i == bridgeModule.currentExerciseIdx { - Image(systemName: "checkmark") - .foregroundColor(.green) - } - - Text(obj.exercise.name) - .onTapGesture { - bridgeModule.goToExerciseAt(index: i) - } - - Spacer() - } - if i == bridgeModule.currentExerciseIdx { + ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in + let obj = workout.exercisesSortedByCreated_at[i] + VStack { HStack { - if obj.exercise.isReps { - Text("is reps") - .frame(maxWidth: .infinity, maxHeight: .infinity) + if i == bridgeModule.currentExerciseIdx { + Image(systemName: "checkmark") + .foregroundColor(.green) } - if obj.exercise.isWeight { - Text("is weight") - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - if obj.exercise.isDuration { - Text("is duration") - .frame(maxWidth: .infinity, maxHeight: .infinity) + + Text(obj.exercise.name) + .onTapGesture { + bridgeModule.goToExerciseAt(index: i) + } + + Spacer() + } + if i == bridgeModule.currentExerciseIdx { + HStack { + if obj.exercise.isReps { + Text("is reps") + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + if obj.exercise.isWeight { + Text("is weight") + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + if obj.exercise.isDuration { + Text("is duration") + .frame(maxWidth: .infinity, maxHeight: .infinity) + } } } } } } - } } } @@ -253,12 +291,11 @@ struct CountdownView: View { @StateObject var bridgeModule = BridgeModule.shared var body: some View { - VStack { - if let duration = bridgeModule.currentExercise?.duration { - HStack { - ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration)) - Text("\(bridgeModule.currentExerciseTimeLeft)") - }.padding(16) + if let duration = bridgeModule.currentExercise?.duration { + HStack { + ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration)) + Text("\(bridgeModule.currentExerciseTimeLeft)") + .font(.body) } } } diff --git a/Werkout_ios/Werkout_iosApp.swift b/Werkout_ios/Werkout_iosApp.swift index 53693ae..d6f392d 100644 --- a/Werkout_ios/Werkout_iosApp.swift +++ b/Werkout_ios/Werkout_iosApp.swift @@ -7,6 +7,7 @@ import SwiftUI import Combine +import AVKit @main struct Werkout_iosApp: App { @@ -62,6 +63,7 @@ struct Werkout_iosApp: App { .tag(3) } .onAppear{ + _ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers) // UserStore.shared.logout() } .onReceive(pub) { (output) in