// // CreateViewModels.swift // Werkout_ios // // Created by Trey Tartt on 6/18/23. // import Combine import Foundation import SharedCore class CreateWorkoutExercise: ObservableObject, Identifiable { let id = UUID() var exercise: Exercise @Published var reps: Int = 0 @Published var duration: Int = 0 @Published var weight: Int = 0 init(exercise: Exercise, reps: Int = 0, duration: Int = 0, weight: Int = 0) { self.exercise = exercise self.reps = reps self.duration = duration self.weight = weight } func increaseReps() { self.reps += 1 } func decreaseReps() { self.reps -= 1 if self.reps < 0 { self.reps = 0 } } func increaseDuration() { self.duration += 15 self.reps = 0 } func decreaseDuration() { self.duration -= 15 if self.duration < 0 { self.duration = 0 } } func increaseWeight() { self.weight += 5 } func decreaseWeight() { self.weight -= 5 if self.weight < 0 { self.weight = 0 } } } class CreateWorkoutSuperSet: ObservableObject, Identifiable, Equatable { static func == (lhs: CreateWorkoutSuperSet, rhs: CreateWorkoutSuperSet) -> Bool { lhs.id == rhs.id } let id = UUID() @Published var title: String = "" @Published var exercises = [CreateWorkoutExercise]() @Published var numberOfRounds = 0 func increaseNumberOfRounds() { self.numberOfRounds += 1 } func decreaseNumberOfRounds() { self.numberOfRounds -= 1 if self.numberOfRounds < 0 { self.numberOfRounds = 0 } } func deleteExerciseForChosenSuperset(exercise: CreateWorkoutExercise) { if let idx = exercises.firstIndex(where: { $0.id == exercise.id }) { exercises.remove(at: idx) } } } class WorkoutViewModel: ObservableObject { @Published var superSets = [CreateWorkoutSuperSet]() @Published var title = String() @Published var description = String() @Published var validationError: String? @Published var isUploading = false // MARK: - Manual Invalidation // Workaround: nested ObservableObject changes don't propagate to parent. // Remove when migrating to @Observable (iOS 17+). @Published var randomValueForUpdatingValue = 0 func increaseRandomNumberForUpdating() { randomValueForUpdatingValue += 1 } func addNewSuperset() { increaseRandomNumberForUpdating() superSets.append(CreateWorkoutSuperSet()) } func delete(superset: CreateWorkoutSuperSet) { if let idx = superSets.firstIndex(where: { $0.id == superset.id }) { superSets.remove(at: idx) increaseRandomNumberForUpdating() } } func addExercise(_ exercise: Exercise, to superset: CreateWorkoutSuperSet) { let workoutExercise = CreateWorkoutExercise(exercise: exercise) superset.exercises.append(workoutExercise) if exercise.side?.isEmpty == false { autoAddSiblingExercises(for: exercise, to: superset) } increaseRandomNumberForUpdating() } private func autoAddSiblingExercises(for exercise: Exercise, to superset: CreateWorkoutSuperSet) { guard let allExercises = DataStore.shared.allExercise else { return } let siblings = allExercises.filter { $0.name == exercise.name } guard siblings.count == 2, let recover = allExercises.first(where: { $0.name.lowercased() == "recover" }) else { return } let recoverExercise = CreateWorkoutExercise(exercise: recover) superset.exercises.append(recoverExercise) for sibling in siblings where sibling.id != exercise.id { let otherSideExercise = CreateWorkoutExercise(exercise: sibling) superset.exercises.append(otherSideExercise) } } func showRoundsError() { validationError = "Each superset must have at least one round." } func showNoDurationOrReps() { validationError = "Each exercise must have reps or duration." } func uploadWorkout() { guard isUploading == false else { return } validationError = nil let normalizedTitle = title.trimmingCharacters(in: .whitespacesAndNewlines) guard normalizedTitle.isEmpty == false else { validationError = "Workout title is required." return } var supersets = [[String: Any]]() for (supersetOffset, superset) in superSets.enumerated() { var supersetInfo = [String: Any]() supersetInfo["name"] = "" supersetInfo["rounds"] = superset.numberOfRounds supersetInfo["order"] = supersetOffset + 1 var exercises = [[String: Any]]() for (exerciseOffset, exercise) in superset.exercises.enumerated() { let item = ["id": exercise.exercise.id, "reps": exercise.reps, "weight": exercise.weight, "duration": exercise.duration, "order": exerciseOffset + 1] as [String : Any] exercises.append(item) } supersetInfo["exercises"] = exercises supersets.append(supersetInfo) } if supersets.isEmpty { validationError = "Add at least one superset before uploading." return } let issues = WorkoutValidation.validateSupersets(supersets) if let issue = issues.first { switch issue.code { case "invalid_rounds": showRoundsError() case "invalid_exercise_payload": showNoDurationOrReps() default: validationError = issue.message } return } let uploadBody = ["name": normalizedTitle, "description": description, "supersets": supersets] as [String : Any] isUploading = true CreateWorkoutFetchable(postData: uploadBody).fetch(completion: { result in self.isUploading = false switch result { case .success(_): self.superSets.removeAll() self.title = "" self.description = "" NotificationCenter.default.post( name: AppNotifications.createdNewWorkout, object: nil, userInfo: nil ) case .failure(let failure): self.validationError = "Failed to upload workout: \(failure.localizedDescription)" } }) } }