diff --git a/Werkout_ios/APIModels/Exercise.swift b/Werkout_ios/APIModels/Exercise.swift index bf303c0..ca06f81 100644 --- a/Werkout_ios/APIModels/Exercise.swift +++ b/Werkout_ios/APIModels/Exercise.swift @@ -8,7 +8,7 @@ import Foundation struct SupersetExercise: Identifiable, Codable, Equatable, Hashable { - var id: Int + var id: Int? let workout: Int? let exercise: Exercise @@ -17,9 +17,9 @@ struct SupersetExercise: Identifiable, Codable, Equatable, Hashable { let duration: Int? let durationAudio: String? let weightAudio: String? - let createdAt: String - let order, superset: Int - let uniqueID: String + let createdAt: String? + let order, superset: Int? + let uniqueID: String? let description: String? enum CodingKeys: String, CodingKey { diff --git a/Werkout_ios/APIModels/RegisteredUser.swift b/Werkout_ios/APIModels/RegisteredUser.swift index 4ff56f7..9b06ef3 100644 --- a/Werkout_ios/APIModels/RegisteredUser.swift +++ b/Werkout_ios/APIModels/RegisteredUser.swift @@ -13,6 +13,7 @@ struct RegisteredUser: Codable, Hashable { let nickName: String? let token: String? let email: String? + let hasNSFWToggle: Bool? enum CodingKeys: String, CodingKey { case id @@ -21,5 +22,10 @@ struct RegisteredUser: Codable, Hashable { case image, token case email = "email_address" case nickName = "nick_name" + case hasNSFWToggle = "has_nsfw_toggle" + } + + var NSFWValue: Bool { + return hasNSFWToggle ?? false } } diff --git a/Werkout_ios/APIModels/Superset.swift b/Werkout_ios/APIModels/Superset.swift index dc95cd7..0ae825d 100644 --- a/Werkout_ios/APIModels/Superset.swift +++ b/Werkout_ios/APIModels/Superset.swift @@ -8,7 +8,7 @@ import Foundation struct Superset: Codable, Identifiable, Hashable { - let id: Int + let id: Int? let exercises: [SupersetExercise] let createdAt, updatedAt, name: String? let rounds, order, workout: Int diff --git a/Werkout_ios/DataStore.swift b/Werkout_ios/DataStore.swift index 8064aae..0101a73 100644 --- a/Werkout_ios/DataStore.swift +++ b/Werkout_ios/DataStore.swift @@ -28,10 +28,20 @@ class DataStore: ObservableObject { public func randomVideoFor(gender: String) -> String? { return allNSFWVideos?.filter({ - $0.genderValue == gender + $0.genderValue.lowercased() == gender.lowercased() }).randomElement()?.videoFile ?? nil } + public var nsfwGenderOptions: [String]? { + let values = self.allNSFWVideos?.map({ + $0.genderValue + }) + if let values = values { + return Array(Set(values)) + } + return nil + } + public var workoutsUniqueUsers: [RegisteredUser]? { guard let workouts = allWorkouts else { return nil diff --git a/Werkout_ios/Network/Fetchables.swift b/Werkout_ios/Network/Fetchables.swift index 3733d37..6f972a4 100644 --- a/Werkout_ios/Network/Fetchables.swift +++ b/Werkout_ios/Network/Fetchables.swift @@ -102,3 +102,8 @@ class AllNSFWVideosFetchable: Fetchable { typealias Response = [NSFWVideo] var endPoint: String = "/videos/nsfw_videos/" } + +class RefreshUserInfoFetcable: Fetchable { + typealias Response = RegisteredUser + var endPoint: String = "/registered_user/refresh/" +} diff --git a/Werkout_ios/UserStore.swift b/Werkout_ios/UserStore.swift index d17e425..a8c6595 100644 --- a/Werkout_ios/UserStore.swift +++ b/Werkout_ios/UserStore.swift @@ -56,6 +56,25 @@ class UserStore: ObservableObject { }) } + public func refreshUserData() { + RefreshUserInfoFetcable().fetch(completion: { result in + switch result { + case .success(let registeredUser): + DispatchQueue.main.async { + if let data = try? JSONEncoder().encode(registeredUser) { + UserDefaults.standard.set(data, forKey: UserStore.userDefaultsRegisteredUserKey) + } + if let data = UserDefaults.standard.data(forKey: UserStore.userDefaultsRegisteredUserKey), + let model = try? JSONDecoder().decode(RegisteredUser.self, from: data) { + self.registeredUser = model + } + } + case .failure(let failure): + fatalError() + } + }) + } + func logout() { self.registeredUser = nil UserDefaults.standard.set(nil, forKey: UserStore.userDefaultsRegisteredUserKey) diff --git a/Werkout_ios/Views/AccountView/AccountView.swift b/Werkout_ios/Views/AccountView/AccountView.swift index a7a3a49..24112b1 100644 --- a/Werkout_ios/Views/AccountView/AccountView.swift +++ b/Werkout_ios/Views/AccountView/AccountView.swift @@ -15,6 +15,7 @@ struct AccountView: View { @AppStorage(Constants.phoneThotStyle) private var phoneThotStyle: ThotStyle = .never @AppStorage(Constants.extThotStyle) private var extThotStyle: ThotStyle = .never @AppStorage(Constants.extShowBothVideos) private var extShowBothVideos: Bool = false + @AppStorage(Constants.thotGenderOption) private var thotGenderOption: String = "female" var body: some View { VStack(alignment: .leading) { @@ -64,26 +65,41 @@ struct AccountView: View { } } Divider() - Group { - Text("Phone THOT Style:") - Picker("Phone THOT Style:", selection: $phoneThotStyle) { - ForEach(ThotStyle.allCases, id: \.self) { style in - Text(style.stringValue()) - .tag(phoneThotStyle.rawValue) + + if userStore.registeredUser?.NSFWValue ?? false { + Group { + Text("Phone THOT Style:") + Picker("Phone THOT Style:", selection: $phoneThotStyle) { + ForEach(ThotStyle.allCases, id: \.self) { style in + Text(style.stringValue()) + .tag(phoneThotStyle.rawValue) + } + } + .pickerStyle(.segmented) + + Divider() + + Text("External THOT Style:") + Picker("External THOT Style:", selection: $extThotStyle) { + ForEach(ThotStyle.allCases, id: \.self) { style in + Text(style.stringValue()) + .tag(extThotStyle.rawValue) + } + } + .pickerStyle(.segmented) + + if let genderOptions = DataStore.shared.nsfwGenderOptions { + Divider() + Text("Video Gender:") + Picker("Video Gender:", selection: $thotGenderOption) { + ForEach(genderOptions, id: \.self) { option in + Text(option.capitalized) + .tag(option.lowercased()) + } + } + .pickerStyle(.segmented) } } - .pickerStyle(.segmented) - - Divider() - - Text("External THOT Style:") - Picker("External THOT Style:", selection: $extThotStyle) { - ForEach(ThotStyle.allCases, id: \.self) { style in - Text(style.stringValue()) - .tag(extThotStyle.rawValue) - } - } - .pickerStyle(.segmented) } Group { @@ -96,16 +112,30 @@ struct AccountView: View { Spacer() - Button("Logout", action: { - userStore.logout() - }) - .frame(maxWidth: .infinity, alignment: .center) - .frame(height: 44) - .foregroundColor(.white) - .background(.red) - .cornerRadius(8) - .padding() - .frame(maxWidth: .infinity) + HStack { + Button("Logout", action: { + userStore.logout() + }) + .frame(maxWidth: .infinity, alignment: .center) + .frame(height: 44) + .foregroundColor(.white) + .background(.red) + .cornerRadius(8) + .padding() + .frame(maxWidth: .infinity) + + Button(action: { + userStore.refreshUserData() + }, label: { + Image(systemName: "arrow.triangle.2.circlepath") + }) + .frame(width: 44, height: 44) + .foregroundColor(.white) + .background(.green) + .cornerRadius(8) + .padding() + .frame(maxWidth: .infinity) + } } .padding() .sheet(isPresented: $showCompletedWorkouts) { diff --git a/Werkout_ios/Views/CreateWorkout/CreateExerciseActionsView.swift b/Werkout_ios/Views/CreateWorkout/CreateExerciseActionsView.swift index e155259..396ca0c 100644 --- a/Werkout_ios/Views/CreateWorkout/CreateExerciseActionsView.swift +++ b/Werkout_ios/Views/CreateWorkout/CreateExerciseActionsView.swift @@ -84,6 +84,7 @@ struct CreateExerciseActionsView: View { Button(action: { superset.deleteExerciseForChosenSuperset(exercise: workoutExercise) + viewModel.increaseRandomNumberForUpdating() viewModel.objectWillChange.send() }) { Image(systemName: "trash.fill") diff --git a/Werkout_ios/Views/CreateWorkout/CreateViewModels.swift b/Werkout_ios/Views/CreateWorkout/CreateViewModels.swift index 0e6e62e..6325f16 100644 --- a/Werkout_ios/Views/CreateWorkout/CreateViewModels.swift +++ b/Werkout_ios/Views/CreateWorkout/CreateViewModels.swift @@ -56,7 +56,11 @@ class CreateWorkoutExercise: ObservableObject, Identifiable { } } -class CreateWorkoutSuperSet: ObservableObject, Identifiable { +class CreateWorkoutSuperSet: ObservableObject, Identifiable, Equatable { + static func == (lhs: CreateWorkoutSuperSet, rhs: CreateWorkoutSuperSet) -> Bool { + lhs.id == rhs.id + } + let id = UUID() @Published var exercises = [CreateWorkoutExercise]() @Published var numberOfRounds = 0 @@ -85,8 +89,14 @@ class WorkoutViewModel: ObservableObject { @Published var superSets = [CreateWorkoutSuperSet]() @Published var title = String() @Published var description = String() + @Published var randomValueForUpdatingValue = 0 + + func increaseRandomNumberForUpdating() { + randomValueForUpdatingValue += 1 + } func addNewSuperset() { + increaseRandomNumberForUpdating() superSets.append(CreateWorkoutSuperSet()) } @@ -95,6 +105,7 @@ class WorkoutViewModel: ObservableObject { $0.id == superset.id }) { superSets.remove(at: idx) + increaseRandomNumberForUpdating() } } diff --git a/Werkout_ios/Views/CreateWorkout/CreateWorkoutMainView.swift b/Werkout_ios/Views/CreateWorkout/CreateWorkoutMainView.swift index e3d9204..9d98434 100644 --- a/Werkout_ios/Views/CreateWorkout/CreateWorkoutMainView.swift +++ b/Werkout_ios/Views/CreateWorkout/CreateWorkoutMainView.swift @@ -24,49 +24,61 @@ struct CreateWorkoutMainView: View { .padding() .frame(height: 55) .textFieldStyle(OvalTextFieldStyle()) - - List() { - ForEach($viewModel.superSets, id: \.id) { superset in - Section { - ForEach(superset.exercises, id: \.id) { exercise in - HStack { - VStack { - Text(exercise.wrappedValue.exercise.name) - .font(.title2) - .frame(maxWidth: .infinity) - if exercise.wrappedValue.exercise.side.count > 0 { - Text(exercise.wrappedValue.exercise.side) - .font(.title3) - .frame(maxWidth: .infinity, alignment: .center) + ScrollViewReader { proxy in + List() { + ForEach($viewModel.superSets, id: \.id) { superset in + Section { + ForEach(superset.exercises, id: \.id) { exercise in + HStack { + VStack { + Text(exercise.wrappedValue.exercise.name) + .font(.title2) + .frame(maxWidth: .infinity) + if exercise.wrappedValue.exercise.side.count > 0 { + Text(exercise.wrappedValue.exercise.side) + .font(.title3) + .frame(maxWidth: .infinity, alignment: .center) + } + CreateExerciseActionsView(workoutExercise: exercise.wrappedValue, + superset: superset.wrappedValue, + viewModel: viewModel) + } - CreateExerciseActionsView(workoutExercise: exercise.wrappedValue, - superset: superset.wrappedValue, - viewModel: viewModel) - } } + + HStack { + Stepper("Number of rounds", onIncrement: { + superset.wrappedValue.increaseNumberOfRounds() + viewModel.increaseRandomNumberForUpdating() + viewModel.objectWillChange.send() + }, onDecrement: { + superset.wrappedValue.decreaseNumberOfRounds() + viewModel.increaseRandomNumberForUpdating() + viewModel.objectWillChange.send() + }) + + Text("\(superset.wrappedValue.numberOfRounds)") + .foregroundColor(superset.numberOfRounds.wrappedValue > 0 ? .black : .red) + .bold() + } + + CreateWorkoutSupersetActionsView(workoutSuperSet: superset.wrappedValue, + showAddExercise: $showAddExercise, + viewModel: viewModel, + selectedCreateWorkoutSuperSet: $selectedCreateWorkoutSuperSet) } - - HStack { - Stepper("Number of rounds", onIncrement: { - superset.wrappedValue.increaseNumberOfRounds() - viewModel.objectWillChange.send() - }, onDecrement: { - superset.wrappedValue.decreaseNumberOfRounds() - viewModel.objectWillChange.send() - }) - Text("\(superset.wrappedValue.numberOfRounds)") - .foregroundColor(superset.numberOfRounds.wrappedValue > 0 ? .black : .red) - .bold() - } - - CreateWorkoutSupersetActionsView(workoutSuperSet: superset.wrappedValue, - showAddExercise: $showAddExercise, - viewModel: viewModel, - selectedCreateWorkoutSuperSet: $selectedCreateWorkoutSuperSet) } + Text("this is the bottom 🤷‍♂️") + .id(999) + + .listRowSeparator(.hidden) } - .listRowSeparator(.hidden) + .onChange(of: viewModel.randomValueForUpdatingValue, perform: { newValue in + withAnimation { + proxy.scrollTo(999, anchor: .bottom) + } + }) } // .overlay(Group { // if($viewModel.superSets.isEmpty) { @@ -137,6 +149,7 @@ struct CreateWorkoutMainView: View { } } + viewModel.increaseRandomNumberForUpdating() viewModel.objectWillChange.send() selectedCreateWorkoutSuperSet = nil }) diff --git a/Werkout_ios/Views/CreateWorkout/CreateWorkoutSupersetActionsView.swift b/Werkout_ios/Views/CreateWorkout/CreateWorkoutSupersetActionsView.swift index 2d7e7fa..7b65cff 100644 --- a/Werkout_ios/Views/CreateWorkout/CreateWorkoutSupersetActionsView.swift +++ b/Werkout_ios/Views/CreateWorkout/CreateWorkoutSupersetActionsView.swift @@ -30,6 +30,7 @@ struct CreateWorkoutSupersetActionsView: View { Button(action: { viewModel.delete(superset: workoutSuperSet) + viewModel.increaseRandomNumberForUpdating() viewModel.objectWillChange.send() }) { diff --git a/Werkout_ios/Views/ExternalWorkoutDetailView.swift b/Werkout_ios/Views/ExternalWorkoutDetailView.swift index d34a96f..9ae84e4 100644 --- a/Werkout_ios/Views/ExternalWorkoutDetailView.swift +++ b/Werkout_ios/Views/ExternalWorkoutDetailView.swift @@ -14,6 +14,7 @@ struct ExternalWorkoutDetailView: View { @State var smallAVPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!) @AppStorage(Constants.extThotStyle) private var extThotStyle: ThotStyle = .never @AppStorage(Constants.extShowBothVideos) private var extShowBothVideos: Bool = false + @AppStorage(Constants.thotGenderOption) private var thotGenderOption: String = "female" var body: some View { ZStack { @@ -77,6 +78,7 @@ struct ExternalWorkoutDetailView: View { if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise { if let videoURL = VideoURLCreator.videoURL( thotStyle: extThotStyle, + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { @@ -86,6 +88,7 @@ struct ExternalWorkoutDetailView: View { if extShowBothVideos { if let smallVideoURL = VideoURLCreator.videoURL( thotStyle: VideoURLCreator.otherVideoType(forVideoURL: videoURL), + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { @@ -100,6 +103,7 @@ struct ExternalWorkoutDetailView: View { if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise { if let videoURL = VideoURLCreator.videoURL( thotStyle: extThotStyle, + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { @@ -109,6 +113,7 @@ struct ExternalWorkoutDetailView: View { if extShowBothVideos { if let smallVideoURL = VideoURLCreator.videoURL( thotStyle: VideoURLCreator.otherVideoType(forVideoURL: videoURL), + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { diff --git a/Werkout_ios/Views/PlayerUIView.swift b/Werkout_ios/Views/PlayerUIView.swift index fe453ab..7e04ffe 100644 --- a/Werkout_ios/Views/PlayerUIView.swift +++ b/Werkout_ios/Views/PlayerUIView.swift @@ -90,28 +90,32 @@ class VideoURLCreator { return otherVideoStyle } - class func videoURL(thotStyle: ThotStyle, defaultVideoURLStr: String?, exerciseName: String?, workout: Workout?) -> URL? { + class func videoURL(thotStyle: ThotStyle, gender: String, defaultVideoURLStr: String?, exerciseName: String?, workout: Workout?) -> URL? { var urlString: String? - switch thotStyle { - case .always: - urlString = DataStore.shared.randomVideoFor(gender: "female") - case .never: + if UserStore.shared.registeredUser?.NSFWValue ?? false { + switch thotStyle { + case .always: + urlString = DataStore.shared.randomVideoFor(gender: gender) + case .never: + urlString = defaultVideoURLStr + case .recovery: + if exerciseName?.lowercased() == "recover" { + urlString = DataStore.shared.randomVideoFor(gender: gender) + } else { + urlString = defaultVideoURLStr + } + case .random: + if Bool.random() { + urlString = DataStore.shared.randomVideoFor(gender: gender) + } else { + urlString = defaultVideoURLStr + } + case .off: + return nil + } + } else { urlString = defaultVideoURLStr - case .recovery: - if exerciseName?.lowercased() == "recover" { - urlString = DataStore.shared.randomVideoFor(gender: "female") - } else { - urlString = defaultVideoURLStr - } - case .random: - if Bool.random() { - urlString = DataStore.shared.randomVideoFor(gender: "female") - } else { - urlString = defaultVideoURLStr - } - case .off: - return nil } if let urlString = urlString, diff --git a/Werkout_ios/Views/WorkoutDetail/ExerciseListView.swift b/Werkout_ios/Views/WorkoutDetail/ExerciseListView.swift index d151db6..e05699e 100644 --- a/Werkout_ios/Views/WorkoutDetail/ExerciseListView.swift +++ b/Werkout_ios/Views/WorkoutDetail/ExerciseListView.swift @@ -14,11 +14,13 @@ struct ExerciseListView: View { @State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!) var workout: Workout @Binding var showExecersizeInfo: Bool - + @AppStorage(Constants.thotGenderOption) private var thotGenderOption: String = "female" + @State var videoExercise: Exercise? { didSet { if let videoURL = VideoURLCreator.videoURL( thotStyle: phoneThotStyle, + gender: thotGenderOption, defaultVideoURLStr: self.videoExercise?.videoURL, exerciseName: self.videoExercise?.name, workout: bridgeModule.currentExerciseInfo.workout) { diff --git a/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift b/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift index 11cea4a..8341aa1 100644 --- a/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift +++ b/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift @@ -15,7 +15,8 @@ struct WorkoutDetailView: View { @StateObject var bridgeModule = BridgeModule.shared @Environment(\.dismiss) var dismiss @AppStorage(Constants.phoneThotStyle) private var phoneThotStyle: ThotStyle = .never - + @AppStorage(Constants.thotGenderOption) private var thotGenderOption: String = "female" + enum Sheet: Identifiable { case completedWorkout([String: Any]) var id: String { return UUID().uuidString } @@ -55,6 +56,7 @@ struct WorkoutDetailView: View { let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise, let otherVideoURL = VideoURLCreator.videoURL( thotStyle: VideoURLCreator.otherVideoType(forVideoURL: assetURL), + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { @@ -118,6 +120,7 @@ struct WorkoutDetailView: View { if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise { if let videoURL = VideoURLCreator.videoURL( thotStyle: phoneThotStyle, + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { @@ -130,6 +133,7 @@ struct WorkoutDetailView: View { if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise { if let videoURL = VideoURLCreator.videoURL( thotStyle: phoneThotStyle, + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { @@ -142,6 +146,7 @@ struct WorkoutDetailView: View { if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise { if let videoURL = VideoURLCreator.videoURL( thotStyle: phoneThotStyle, + gender: thotGenderOption, defaultVideoURLStr: currentExtercise.exercise.videoURL, exerciseName: currentExtercise.exercise.name, workout: bridgeModule.currentExerciseInfo.workout) { diff --git a/Werkout_ios/Werkout_iosApp.swift b/Werkout_ios/Werkout_iosApp.swift index b77bfe7..558dfda 100644 --- a/Werkout_ios/Werkout_iosApp.swift +++ b/Werkout_ios/Werkout_iosApp.swift @@ -14,6 +14,7 @@ struct Constants { static let phoneThotStyle = "phoneThotStyle" static let extThotStyle = "extThotStyle" static let extShowBothVideos = "extShowBothVideos" + static let thotGenderOption = "thotGenderOption" } @main