This commit is contained in:
Trey t
2023-07-17 19:26:11 -05:00
parent 593cc496cd
commit af538362e8
23 changed files with 637 additions and 438 deletions

View File

@@ -17,6 +17,10 @@
1C485C8D2A49D95700A6F896 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A822A42347D0042FFBD /* Extensions.swift */; }; 1C485C8D2A49D95700A6F896 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A822A42347D0042FFBD /* Extensions.swift */; };
1C4AFF152A60F25F0027710B /* ThotStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF142A60F25E0027710B /* ThotStyle.swift */; }; 1C4AFF152A60F25F0027710B /* ThotStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF142A60F25E0027710B /* ThotStyle.swift */; };
1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF142A60F25E0027710B /* ThotStyle.swift */; }; 1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF142A60F25E0027710B /* ThotStyle.swift */; };
1C4AFF182A65CD290027710B /* Superset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF172A65CD290027710B /* Superset.swift */; };
1C4AFF192A65CD6F0027710B /* Superset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF172A65CD290027710B /* Superset.swift */; };
1C4AFF1B2A65FB190027710B /* CurrentWorkoutInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF1A2A65FB190027710B /* CurrentWorkoutInfo.swift */; };
1C4AFF1C2A65FB2B0027710B /* CurrentWorkoutInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4AFF1A2A65FB190027710B /* CurrentWorkoutInfo.swift */; };
1C5190C22A57CA5F00885849 /* OvalTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5190C12A57CA5F00885849 /* OvalTextFieldStyle.swift */; }; 1C5190C22A57CA5F00885849 /* OvalTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5190C12A57CA5F00885849 /* OvalTextFieldStyle.swift */; };
1C5190C42A589CAC00885849 /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5190C32A589CAC00885849 /* InfoView.swift */; }; 1C5190C42A589CAC00885849 /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5190C32A589CAC00885849 /* InfoView.swift */; };
1C5190C62A589CC100885849 /* ActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5190C52A589CC100885849 /* ActionsView.swift */; }; 1C5190C62A589CC100885849 /* ActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5190C52A589CC100885849 /* ActionsView.swift */; };
@@ -129,6 +133,8 @@
1C485C892A492BB400A6F896 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; }; 1C485C892A492BB400A6F896 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
1C485C8B2A49D65600A6F896 /* WorkoutHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutHistoryView.swift; sourceTree = "<group>"; }; 1C485C8B2A49D65600A6F896 /* WorkoutHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutHistoryView.swift; sourceTree = "<group>"; };
1C4AFF142A60F25E0027710B /* ThotStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThotStyle.swift; sourceTree = "<group>"; }; 1C4AFF142A60F25E0027710B /* ThotStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThotStyle.swift; sourceTree = "<group>"; };
1C4AFF172A65CD290027710B /* Superset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Superset.swift; sourceTree = "<group>"; };
1C4AFF1A2A65FB190027710B /* CurrentWorkoutInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWorkoutInfo.swift; sourceTree = "<group>"; };
1C5190C12A57CA5F00885849 /* OvalTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OvalTextFieldStyle.swift; sourceTree = "<group>"; }; 1C5190C12A57CA5F00885849 /* OvalTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OvalTextFieldStyle.swift; sourceTree = "<group>"; };
1C5190C32A589CAC00885849 /* InfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = "<group>"; }; 1C5190C32A589CAC00885849 /* InfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = "<group>"; };
1C5190C52A589CC100885849 /* ActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionsView.swift; sourceTree = "<group>"; }; 1C5190C52A589CC100885849 /* ActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionsView.swift; sourceTree = "<group>"; };
@@ -269,6 +275,7 @@
1CF65A822A42347D0042FFBD /* Extensions.swift */, 1CF65A822A42347D0042FFBD /* Extensions.swift */,
1CF65A272A3972840042FFBD /* Persistence.swift */, 1CF65A272A3972840042FFBD /* Persistence.swift */,
1CD0C6662A5CA19600970E52 /* BaseURLs.swift */, 1CD0C6662A5CA19600970E52 /* BaseURLs.swift */,
1C4AFF1A2A65FB190027710B /* CurrentWorkoutInfo.swift */,
1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */, 1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */,
1CF65A802A412AA30042FFBD /* DataStore.swift */, 1CF65A802A412AA30042FFBD /* DataStore.swift */,
1CF65AB92A4894430042FFBD /* UserStore.swift */, 1CF65AB92A4894430042FFBD /* UserStore.swift */,
@@ -314,6 +321,7 @@
1CAF4D892A5132F900B00E50 /* PlannedWorkout.swift */, 1CAF4D892A5132F900B00E50 /* PlannedWorkout.swift */,
1CF65A462A39FB6C0042FFBD /* RegisteredUser.swift */, 1CF65A462A39FB6C0042FFBD /* RegisteredUser.swift */,
1CF65A422A39FB410042FFBD /* Workout.swift */, 1CF65A422A39FB410042FFBD /* Workout.swift */,
1C4AFF172A65CD290027710B /* Superset.swift */,
); );
path = APIModels; path = APIModels;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -583,6 +591,7 @@
1CF65A852A43E8060042FFBD /* CompletedWorkout.swift in Sources */, 1CF65A852A43E8060042FFBD /* CompletedWorkout.swift in Sources */,
1CF65A6E2A3F60480042FFBD /* CreateViewModels.swift in Sources */, 1CF65A6E2A3F60480042FFBD /* CreateViewModels.swift in Sources */,
1CF65A4C2A39FDA20042FFBD /* WorkoutDetailView.swift in Sources */, 1CF65A4C2A39FDA20042FFBD /* WorkoutDetailView.swift in Sources */,
1C4AFF182A65CD290027710B /* Superset.swift in Sources */,
1CF65A8E2A44B78B0042FFBD /* CompletedWorkoutView.swift in Sources */, 1CF65A8E2A44B78B0042FFBD /* CompletedWorkoutView.swift in Sources */,
1CF65A432A39FB410042FFBD /* Workout.swift in Sources */, 1CF65A432A39FB410042FFBD /* Workout.swift in Sources */,
1CF65A502A3A1EA90042FFBD /* BridgeModule.swift in Sources */, 1CF65A502A3A1EA90042FFBD /* BridgeModule.swift in Sources */,
@@ -597,6 +606,7 @@
1CF65A2D2A3972840042FFBD /* MainView.swift in Sources */, 1CF65A2D2A3972840042FFBD /* MainView.swift in Sources */,
1CF65A7D2A41275D0042FFBD /* Network.swift in Sources */, 1CF65A7D2A41275D0042FFBD /* Network.swift in Sources */,
1C485C8A2A492BB400A6F896 /* LoginView.swift in Sources */, 1C485C8A2A492BB400A6F896 /* LoginView.swift in Sources */,
1C4AFF1B2A65FB190027710B /* CurrentWorkoutInfo.swift in Sources */,
1CF65A732A3F60D20042FFBD /* CreateExerciseActionsView.swift in Sources */, 1CF65A732A3F60D20042FFBD /* CreateExerciseActionsView.swift in Sources */,
1CF65A832A42347D0042FFBD /* Extensions.swift in Sources */, 1CF65A832A42347D0042FFBD /* Extensions.swift in Sources */,
1CF65A282A3972840042FFBD /* Persistence.swift in Sources */, 1CF65A282A3972840042FFBD /* Persistence.swift in Sources */,
@@ -619,6 +629,7 @@
files = ( files = (
1CF65A982A452D270042FFBD /* ContentView.swift in Sources */, 1CF65A982A452D270042FFBD /* ContentView.swift in Sources */,
1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */, 1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */,
1C4AFF192A65CD6F0027710B /* Superset.swift in Sources */,
1CF65AA92A452D9C0042FFBD /* Workout.swift in Sources */, 1CF65AA92A452D9C0042FFBD /* Workout.swift in Sources */,
1CF65AA62A452D9C0042FFBD /* Equipment.swift in Sources */, 1CF65AA62A452D9C0042FFBD /* Equipment.swift in Sources */,
1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */, 1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */,
@@ -627,6 +638,7 @@
1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */, 1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */,
1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */, 1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */,
1CF65AA72A452D9C0042FFBD /* Muscle.swift in Sources */, 1CF65AA72A452D9C0042FFBD /* Muscle.swift in Sources */,
1C4AFF1C2A65FB2B0027710B /* CurrentWorkoutInfo.swift in Sources */,
1C485C8D2A49D95700A6F896 /* Extensions.swift in Sources */, 1C485C8D2A49D95700A6F896 /* Extensions.swift in Sources */,
1CF65AAB2A452DAC0042FFBD /* PreviewData.swift in Sources */, 1CF65AAB2A452DAC0042FFBD /* PreviewData.swift in Sources */,
1CF65AA52A452D9C0042FFBD /* CompletedWorkout.swift in Sources */, 1CF65AA52A452D9C0042FFBD /* CompletedWorkout.swift in Sources */,

View File

@@ -6,28 +6,18 @@
// //
import Foundation import Foundation
struct Equipment: Codable { struct Equipment: Codable {
let id: Int let id: Int
let createdAt, updatedAt: String let name, createdAt, updatedAt: String
let category, name: String? let is_weight: Bool?
let category: String?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id case id, name
case createdAt = "created_at" case createdAt = "created_at"
case updatedAt = "updated_at" case updatedAt = "updated_at"
case category, name case is_weight
} case category
}
struct ExerciseEquipment: Codable, Hashable {
let id: Int
let createdAt, updatedAt: String
let exercise, equipment: Int
enum CodingKeys: String, CodingKey {
case id
case createdAt = "created_at"
case updatedAt = "updated_at"
case equipment, exercise
} }
} }

View File

@@ -7,32 +7,39 @@
import Foundation import Foundation
struct ExerciseElement: Codable, Equatable { struct SupersetExercise: Identifiable, Codable, Equatable, Hashable {
let workout: Int var id = UUID()
let exercise: ExerciseExercise
let workout: Int?
let exercise: Exercise
let weight: Int? let weight: Int?
let reps: Int? let reps: Int?
let duration: Int? let duration: Int?
let durationAudio: String? let durationAudio: String?
let weightAudio: String? let weightAudio: String?
let createdAt: String let createdAt: String
let order, superset: Int
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case workout, exercise, weight, reps, duration case workout, exercise, weight, reps, duration, order, superset
case durationAudio = "duration_audio" case durationAudio = "duration_audio"
case weightAudio = "weight_audio" case weightAudio = "weight_audio"
case createdAt = "created_at" case createdAt = "created_at"
} }
public func hash(into hasher: inout Hasher) {
return hasher.combine(id)
}
} }
struct ExerciseExercise: Codable, Hashable, Identifiable { struct Exercise: Identifiable, Codable, Equatable {
static func == (lhs: ExerciseExercise, rhs: ExerciseExercise) -> Bool { static func == (lhs: Exercise, rhs: Exercise) -> Bool {
lhs.id == rhs.id lhs.id == rhs.id
} }
let id: Int let id: Int
let muscles: [ExerciseMuscle] let equipment: [Equipment]
let equipment: [ExerciseEquipment] let muscles: [Muscle]
let audioURL, videoURL, createdAt, updatedAt: String let audioURL, videoURL, createdAt, updatedAt: String
let name, description, side: String let name, description, side: String
let isTwoDumbbells, isTrackableDistance, isAlternating, isWeight: Bool let isTwoDumbbells, isTrackableDistance, isAlternating, isWeight: Bool

View File

@@ -9,18 +9,11 @@ import Foundation
struct Muscle: Codable { struct Muscle: Codable {
let id: Int let id: Int
let name: String let name, createdAt, updatedAt: String
}
struct ExerciseMuscle: Codable, Hashable {
let id: Int
let createdAt, updatedAt: String
let exercise, muscle: Int
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id case id, name
case createdAt = "created_at" case createdAt = "created_at"
case updatedAt = "updated_at" case updatedAt = "updated_at"
case exercise, muscle
} }
} }

View File

@@ -0,0 +1,26 @@
//
// Superset.swift
// Werkout_ios
//
// Created by Trey Tartt on 7/17/23.
//
import Foundation
struct Superset: Codable, Identifiable, Hashable {
let id: Int
let exercises: [SupersetExercise]
let createdAt, updatedAt, name: String?
let rounds, order, workout: Int
enum CodingKeys: String, CodingKey {
case id, exercises
case createdAt = "created_at"
case updatedAt = "updated_at"
case name, rounds, order, workout
}
public func hash(into hasher: inout Hasher) {
return hasher.combine(id)
}
}

View File

@@ -15,17 +15,15 @@ struct Workout: Codable, Identifiable, Equatable {
let id: Int let id: Int
let name: String let name: String
let description: String? let description: String?
let exercises: [ExerciseElement] let supersets: [Superset]?
let registeredUser: RegisteredUser? let registeredUser: RegisteredUser?
let femaleVideos, maleVideos, bothVideos: [String]?
let muscles: [String]? let muscles: [String]?
let equipment: [String]? let equipment: [String]?
let exercise_count: Int? let exercise_count: Int?
let maleVideos: [String]?
let femaleVideos: [String]?
let bothVideos: [String]?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case name, description, exercises, id, muscles, equipment, exercise_count case id, name, description, supersets, exercise_count, muscles, equipment
case registeredUser = "registered_user" case registeredUser = "registered_user"
case maleVideos = "male_videos" case maleVideos = "male_videos"
case femaleVideos = "female_videos" case femaleVideos = "female_videos"
@@ -34,31 +32,18 @@ struct Workout: Codable, Identifiable, Equatable {
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
if let exercises = try container.decodeIfPresent([ExerciseElement].self, forKey: .exercises) {
self.exercises = exercises
} else {
self.exercises = [ExerciseElement]()
}
self.name = try container.decode(String.self, forKey: .name) self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "NA"
self.description = try container.decodeIfPresent(String.self, forKey: .description) self.description = try container.decodeIfPresent(String.self, forKey: .description)
self.registeredUser = try container.decodeIfPresent(RegisteredUser.self, forKey: .registeredUser) self.registeredUser = try container.decodeIfPresent(RegisteredUser.self, forKey: .registeredUser)
self.id = try container.decode(Int.self, forKey: .id) self.id = try container.decode(Int.self, forKey: .id)
self.muscles = try container.decodeIfPresent([String].self, forKey: .muscles)
self.equipment = try container.decodeIfPresent([String].self, forKey: .equipment)
self.exercise_count = try container.decodeIfPresent(Int.self, forKey: .exercise_count)
self.femaleVideos = try container.decodeIfPresent([String].self, forKey: .femaleVideos) self.femaleVideos = try container.decodeIfPresent([String].self, forKey: .femaleVideos)
self.maleVideos = try container.decodeIfPresent([String].self, forKey: .maleVideos) self.maleVideos = try container.decodeIfPresent([String].self, forKey: .maleVideos)
self.bothVideos = try container.decodeIfPresent([String].self, forKey: .bothVideos) self.bothVideos = try container.decodeIfPresent([String].self, forKey: .bothVideos)
} self.supersets = try container.decodeIfPresent([Superset].self, forKey: .supersets)
var exercisesSortedByCreated_at: [ExerciseElement] { self.equipment = try container.decodeIfPresent([String].self, forKey: .equipment)
return self.exercises.sorted(by: { self.muscles = try container.decodeIfPresent([String].self, forKey: .muscles)
if let lhsDate = $0.createdAt.dateFromServerDate, self.exercise_count = try container.decodeIfPresent(Int.self, forKey: .exercise_count)
let rhsDate = $1.createdAt.dateFromServerDate {
return lhsDate < rhsDate
}
return false
})
} }
} }

View File

@@ -12,6 +12,6 @@ enum BaseURLs: String {
case dev = "https://dev.werkout.fitness" case dev = "https://dev.werkout.fitness"
static var currentBaseURL: String { static var currentBaseURL: String {
return BaseURLs.dev.rawValue return BaseURLs.local.rawValue
} }
} }

View File

@@ -36,17 +36,14 @@ class BridgeModule: NSObject, ObservableObject {
@Published var currentWorkoutRunTimeInSeconds: Int = -1 @Published var currentWorkoutRunTimeInSeconds: Int = -1
private var currentWorkoutRunTimer: Timer? private var currentWorkoutRunTimer: Timer?
var currentWorkout: Workout?
public private(set) var workoutStartDate: Date? public private(set) var workoutStartDate: Date?
private var currentExerciseTimer: Timer? private var currentExerciseTimer: Timer?
public private(set) var currentExerciseIdx: Int = -1 {
didSet { @Published public private(set) var currentExerciseInfo = CurrentWorkoutInfo()
self.currentExercisePositionString = "\(self.currentExerciseIdx+1)/\(self.currentWorkout?.exercises.count ?? 0)" @Published var previewWorkout: Workout?
}
}
@Published var currentExerciseTimeLeft: Int = 0 @Published var currentExerciseTimeLeft: Int = 0
@Published var currentExercise: ExerciseElement?
var currentExercisePositionString: String? var currentExercisePositionString: String?
private var isWatchConnected = false private var isWatchConnected = false
@@ -59,33 +56,33 @@ class BridgeModule: NSObject, ObservableObject {
var audioPlayer: AVAudioPlayer? var audioPlayer: AVAudioPlayer?
func start(workout: Workout) { func start(workout: Workout) {
self.currentWorkout = workout currentExerciseInfo.complete = {
self.completeWorkout()
}
currentExerciseInfo.start(workout: workout)
currentWorkoutRunTimeInSeconds = 0 currentWorkoutRunTimeInSeconds = 0
currentWorkoutRunTimer?.invalidate() currentWorkoutRunTimer?.invalidate()
currentWorkoutRunTimer = nil currentWorkoutRunTimer = nil
currentExerciseIdx = 0 if let superetExercise = currentExerciseInfo.currentExercise {
let exercise = workout.exercises[currentExerciseIdx] updateCurrent(exercise: superetExercise)
updateCurrent(exercise: exercise) startWorkoutTimer()
startWorkoutTimer() workoutStartDate = Date()
workoutStartDate = Date() isInWorkout = true
isInWorkout = true
if WCSession.isSupported() { if WCSession.isSupported() {
WCSession.default.delegate = self WCSession.default.delegate = self
WCSession.default.activate() WCSession.default.activate()
}
} }
} }
func goToExerciseAt(index: Int) { func goToExerciseAt(section: Int, row: Int) {
guard let currentWorkout = currentWorkout else { if let superetExercise = currentExerciseInfo.goToWorkoutAt(supersetIndex: section,
return exerciseIndex: row) {
updateCurrent(exercise: superetExercise)
} }
currentExerciseIdx = index
let exercise = currentWorkout.exercises[index]
updateCurrent(exercise: exercise)
} }
func resetCurrentWorkout() { func resetCurrentWorkout() {
@@ -98,10 +95,7 @@ class BridgeModule: NSObject, ObservableObject {
self.currentExerciseTimer = nil self.currentExerciseTimer = nil
self.currentWorkoutRunTimeInSeconds = -1 self.currentWorkoutRunTimeInSeconds = -1
self.currentExerciseIdx = -1 self.currentExerciseInfo.reset()
self.currentExercise = nil
self.currentWorkout = nil
self.isInWorkout = false self.isInWorkout = false
self.workoutStartDate = nil self.workoutStartDate = nil
@@ -162,38 +156,24 @@ class BridgeModule: NSObject, ObservableObject {
} }
func nextExercise() { func nextExercise() {
currentExerciseIdx += 1 if let nextSupersetExercise = currentExerciseInfo.nextExercise {
if let currentWorkout = currentWorkout { updateCurrent(exercise: nextSupersetExercise)
if currentExerciseIdx < currentWorkout.exercises.count { } else {
let nextExercise = currentWorkout.exercises[currentExerciseIdx] completeWorkout()
updateCurrent(exercise: nextExercise)
} else {
completeWorkout()
}
} }
} }
func previousExercise() { func previousExercise() {
currentExerciseIdx -= 1 if let nextSupersetExercise = currentExerciseInfo.previousExercise {
if currentExerciseIdx < 0 { updateCurrent(exercise: nextSupersetExercise)
currentExerciseIdx = 0 } else {
} completeWorkout()
if let currentWorkout = currentWorkout {
if currentExerciseIdx < currentWorkout.exercises.count {
let nextExercise = currentWorkout.exercises[currentExerciseIdx]
updateCurrent(exercise: nextExercise)
} else {
completeWorkout()
}
} }
} }
func restartExercise() { func restartExercise() {
if let currentWorkout = currentWorkout { if let currentExercise = currentExerciseInfo.currentExercise {
if currentExerciseIdx < currentWorkout.exercises.count { updateCurrent(exercise: currentExercise)
let nextExercise = currentWorkout.exercises[currentExerciseIdx]
updateCurrent(exercise: nextExercise)
}
} }
} }
@@ -201,15 +181,12 @@ class BridgeModule: NSObject, ObservableObject {
currentWorkoutRunTimeInSeconds += 1 currentWorkoutRunTimeInSeconds += 1
} }
func updateCurrent(exercise: ExerciseElement) { func updateCurrent(exercise: SupersetExercise) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.currentExerciseTimer?.invalidate() self.currentExerciseTimer?.invalidate()
self.currentExerciseTimer = nil self.currentExerciseTimer = nil
self.currentExercise = exercise if let duration = exercise.duration, duration > 0 {
if let duration = exercise.duration,
duration > 0 {
self.startExerciseTimerWith(duration: duration) self.startExerciseTimerWith(duration: duration)
} }
self.sendCurrentExerciseToWatch() self.sendCurrentExerciseToWatch()
@@ -281,25 +258,25 @@ extension BridgeModule: WCSessionDelegate {
} }
func sendCurrentExerciseToWatch() { func sendCurrentExerciseToWatch() {
if let duration = currentExercise?.duration, if let currentExercise = currentExerciseInfo.currentExercise,
let duration = currentExercise.duration ,
duration > 0 { duration > 0 {
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date()) let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date())
let model = PhoneToWatchActions.inExercise(watchModel) let model = PhoneToWatchActions.inExercise(watchModel)
let data = try! JSONEncoder().encode(model) let data = try! JSONEncoder().encode(model)
send(data) send(data)
} else { } else {
var intWatchDispaly = -1 if let currentExercise = currentExerciseInfo.currentExercise,
if let reps = self.currentExercise?.reps, let reps = currentExercise.reps,
reps > 0 { reps > 0 {
intWatchDispaly = reps
}
// if not a timer we need to set the watch display with number of reps // if not a timer we need to set the watch display with number of reps
// if timer it will set when timer updates // if timer it will set when timer updates
let watchModel = WatchPackageModel(currentExerciseName: self.currentExercise?.exercise.name ?? "-", currentTimeLeft: intWatchDispaly, workoutStartDate: self.workoutStartDate ?? Date()) let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date())
let model = PhoneToWatchActions.inExercise(watchModel) let model = PhoneToWatchActions.inExercise(watchModel)
let data = try! JSONEncoder().encode(model) let data = try! JSONEncoder().encode(model)
self.send(data) self.send(data)
}
} }
} }

View File

@@ -0,0 +1,96 @@
//
// CurrentWorkoutInfo.swift
// Werkout_ios
//
// Created by Trey Tartt on 7/17/23.
//
import Foundation
class CurrentWorkoutInfo {
var supersetIndex: Int = 0
var exerciseIndex: Int = 0
var workout: Workout?
var complete: (() -> Void)?
var currentRound = 1
var superset: [Superset] {
return workout?.supersets?.sorted(by: { $0.order < $1.order }) ?? [Superset]()
}
var currentExercise: SupersetExercise? {
guard let supersets = workout?.supersets else { return nil }
let superset = supersets[supersetIndex]
let exercise = superset.exercises[exerciseIndex]
return exercise
}
var nextExercise: SupersetExercise? {
guard let workout = workout else { return nil }
guard let supersets = workout.supersets else { return nil }
exerciseIndex += 1
let currentSuperSet = supersets[supersetIndex]
if exerciseIndex >= currentSuperSet.exercises.count {
currentRound += 1
if currentRound > currentSuperSet.rounds {
supersetIndex += 1
currentRound = 1
if supersetIndex >= supersets.count {
complete?()
return nil
}
}
exerciseIndex = 0
}
let superset = supersets[supersetIndex]
let exercise = superset.exercises[exerciseIndex]
return exercise
}
var previousExercise: SupersetExercise? {
guard let workout = workout else { return nil }
guard let supersets = workout.supersets else { return nil }
exerciseIndex -= 1
if exerciseIndex < 0 {
if currentRound > 1 {
currentRound -= 1
} else {
if supersetIndex > 1 {
supersetIndex -= 1
}
}
exerciseIndex = 0
}
let superset = supersets[supersetIndex]
let exercise = superset.exercises[exerciseIndex]
return exercise
}
func start(workout: Workout) {
reset()
self.workout = workout
}
func reset() {
supersetIndex = 0
exerciseIndex = 0
currentRound = 1
self.workout = nil
}
func goToWorkoutAt(supersetIndex: Int, exerciseIndex: Int) -> SupersetExercise? {
self.supersetIndex = supersetIndex
self.exerciseIndex = exerciseIndex
return currentExercise
}
}

View File

@@ -19,7 +19,7 @@ class DataStore: ObservableObject {
public private(set) var allWorkouts: [Workout]? public private(set) var allWorkouts: [Workout]?
public private(set) var allMuscles: [Muscle]? public private(set) var allMuscles: [Muscle]?
public private(set) var allEquipment: [Equipment]? public private(set) var allEquipment: [Equipment]?
public private(set) var allExercise: [ExerciseExercise]? public private(set) var allExercise: [Exercise]?
@Published public private(set) var status = DataStoreStatus.idle @Published public private(set) var status = DataStoreStatus.idle

View File

@@ -2,151 +2,208 @@
"id": 21, "id": 21,
"name": "Ipad", "name": "Ipad",
"description": "description", "description": "description",
"exercises": [ "supersets": [
{ {
"workout": 21, "id": 1,
"exercise": { "exercises": [
"id": 520, {
"muscles": [ "id": 1,
{ "exercise": {
"id": 7264, "id": 520,
"created_at": "2023-06-14T17:05:39.760515Z", "muscles": [
"updated_at": "2023-06-14T17:05:39.761372Z", {
"exercise": 520, "id": 7264,
"muscle": 16 "name": "hip flexor",
"created_at": "2023-06-14T17:05:39.760515Z",
"updated_at": "2023-06-14T17:05:39.761372Z",
"exercise": 520,
"muscle": 16
},
{
"id": 7265,
"name": "glutes",
"created_at": "2023-06-14T17:05:39.762342Z",
"updated_at": "2023-06-14T17:05:39.762814Z",
"exercise": 520,
"muscle": 4
}
],
"equipment": [
{
"id": 941,
"name": "Wall",
"created_at": "2023-06-13T02:28:04.289213Z",
"updated_at": "2023-06-13T02:28:04.290354Z",
"exercise": 520,
"equipment": 1106
}
],
"audio_url": "exercise_audio/1-Step_Wall_March.m4a",
"video_url": "exercise_videos/1-Step_Wall_March.mp4",
"created_at": "2023-06-11T22:50:19.020826Z",
"updated_at": "2023-06-11T22:50:19.020834Z",
"name": "1-Step Wall March",
"description": "Keeping a tall posture, lean forward with both arms straight ahead, palms on the wall. While pushing your toes back through the floor, drive one knee up towards your ribs. Pause, then quickly repeat the same movement with the opposite leg. ",
"side": "",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": true,
"is_weight": true,
"is_distance": false,
"is_duration": true,
"is_reps": true,
"joints_used": "ankle,knee,hip,wrist,shoulder,elbow",
"movement_patterns": "plyometric",
"equipment_required": "Wall",
"muscle_groups": "hip flexor,glutes",
"synonyms": ""
}, },
{ "created_at": "2023-07-17T18:56:36.984049Z",
"id": 7265, "updated_at": "2023-07-17T19:06:00.534838Z",
"created_at": "2023-06-14T17:05:39.762342Z", "weight": null,
"updated_at": "2023-06-14T17:05:39.762814Z", "reps": null,
"exercise": 520, "duration": 30,
"muscle": 4 "order": 1,
} "superset": 1
], },
"equipment": [ {
{ "id": 2,
"id": 941, "exercise": {
"created_at": "2023-06-13T02:28:04.289213Z", "id": 992,
"updated_at": "2023-06-13T02:28:04.290354Z", "muscles": [
"exercise": 520, {
"equipment": 1106 "id": 7270,
} "name": "hamstrings",
], "created_at": "2023-06-14T17:05:39.769351Z",
"audio_url": "exercise_audio/1-Step_Wall_March.m4a", "updated_at": "2023-06-14T17:05:39.769758Z",
"video_url": "exercise_videos/1-Step_Wall_March.mp4", "exercise": 992,
"created_at": "2023-06-11T22:50:19.020826Z", "muscle": 6
"updated_at": "2023-06-11T22:50:19.020834Z", },
"name": "1-Step Wall March", {
"description": "Keeping a tall posture, lean forward with both arms straight ahead, palms on the wall. While pushing your toes back through the floor, drive one knee up towards your ribs. Pause, then quickly repeat the same movement with the opposite leg. ", "id": 7271,
"side": "", "name": "glutes",
"is_two_dumbbells": false, "created_at": "2023-06-14T17:05:39.770480Z",
"is_trackable_distance": false, "updated_at": "2023-06-14T17:05:39.771111Z",
"is_alternating": true, "exercise": 992,
"is_weight": true, "muscle": 4
"is_distance": false, }
"is_duration": true, ],
"is_reps": true, "equipment": [
"joints_used": "ankle,knee,hip,wrist,shoulder,elbow", {
"movement_patterns": "plyometric", "id": 944,
"equipment_required": "Wall", "name": "Dumbbell",
"muscle_groups": "hip flexor,glutes", "created_at": "2023-06-13T02:28:04.294180Z",
"synonyms": "" "updated_at": "2023-06-13T02:28:04.294658Z",
}, "exercise": 992,
"weight": 0, "equipment": 1091
"reps": 0, }
"duration": null, ],
"duration_audio": null, "audio_url": "exercise_audio/2_Dumbbell_Single-Leg_Deadlift.m4a",
"weight_audio": "/media/quantities_audio/for_0_pounds.m4a", "video_url": "exercise_videos/2_Dumbbell_Single-Leg_Deadlift.mp4",
"created_at": "2023-06-20T21:03:00.127620Z" "created_at": "2023-06-11T22:50:19.197099Z",
"updated_at": "2023-06-11T22:50:19.197105Z",
"name": "2 Dumbbell Single-Leg Deadlift",
"description": "Holding a dumbbell in each hand, with your right leg on the ground, hinge at your hips and let your body see-saw down until you are parallel with the ground. Snap back to a standing position.",
"side": "right_leg",
"is_two_dumbbells": true,
"is_trackable_distance": false,
"is_alternating": false,
"is_weight": true,
"is_distance": false,
"is_duration": true,
"is_reps": true,
"joints_used": "ankle,lumbar spine,hip,knee,wrist",
"movement_patterns": "lower pull,lower pull - hip hinge",
"equipment_required": "Dumbbell",
"muscle_groups": "hamstrings,glutes",
"synonyms": "2 Dumbbell Single Leg Deadlift"
},
"created_at": "2023-07-17T18:56:36.984523Z",
"updated_at": "2023-07-17T19:06:00.535334Z",
"weight": 30,
"reps": null,
"duration": null,
"order": 2,
"superset": 1
}
],
"created_at": "2023-07-17T18:56:36.983159Z",
"updated_at": "2023-07-17T19:06:00.534027Z",
"name": "test superste",
"rounds": 3,
"order": 1,
"workout": 21
}, },
{ {
"workout": 21, "id": 2,
"exercise": { "exercises": [
"id": 37, {
"muscles": [ "id": 3,
{ "exercise": {
"id": 7272, "id": 992,
"created_at": "2023-06-14T17:05:39.772195Z", "muscles": [
"updated_at": "2023-06-14T17:05:39.772765Z", {
"exercise": 37, "id": 7270,
"muscle": 4 "name": "hamstrings",
"created_at": "2023-06-14T17:05:39.769351Z",
"updated_at": "2023-06-14T17:05:39.769758Z",
"exercise": 992,
"muscle": 6
},
{
"id": 7271,
"name": "glutes",
"created_at": "2023-06-14T17:05:39.770480Z",
"updated_at": "2023-06-14T17:05:39.771111Z",
"exercise": 992,
"muscle": 4
}
],
"equipment": [
{
"id": 944,
"name": "Dumbbell",
"created_at": "2023-06-13T02:28:04.294180Z",
"updated_at": "2023-06-13T02:28:04.294658Z",
"exercise": 992,
"equipment": 1091
}
],
"audio_url": "exercise_audio/2_Dumbbell_Single-Leg_Deadlift.m4a",
"video_url": "exercise_videos/2_Dumbbell_Single-Leg_Deadlift.mp4",
"created_at": "2023-06-11T22:50:19.197099Z",
"updated_at": "2023-06-11T22:50:19.197105Z",
"name": "2 Dumbbell Single-Leg Deadlift",
"description": "Holding a dumbbell in each hand, with your right leg on the ground, hinge at your hips and let your body see-saw down until you are parallel with the ground. Snap back to a standing position.",
"side": "right_leg",
"is_two_dumbbells": true,
"is_trackable_distance": false,
"is_alternating": false,
"is_weight": true,
"is_distance": false,
"is_duration": true,
"is_reps": true,
"joints_used": "ankle,lumbar spine,hip,knee,wrist",
"movement_patterns": "lower pull,lower pull - hip hinge",
"equipment_required": "Dumbbell",
"muscle_groups": "hamstrings,glutes",
"synonyms": "2 Dumbbell Single Leg Deadlift"
}, },
{ "created_at": "2023-07-17T18:58:35.585418Z",
"id": 7273, "updated_at": "2023-07-17T18:58:35.585435Z",
"created_at": "2023-06-14T17:05:39.773621Z", "weight": 11,
"updated_at": "2023-06-14T17:05:39.774079Z", "reps": null,
"exercise": 37, "duration": null,
"muscle": 6 "order": 1,
} "superset": 2
], }
"equipment": [ ],
{ "created_at": "2023-07-17T18:58:35.584036Z",
"id": 945, "updated_at": "2023-07-17T19:03:57.175639Z",
"created_at": "2023-06-13T02:28:04.295672Z", "name": "two",
"updated_at": "2023-06-13T02:28:04.296237Z", "rounds": 3,
"exercise": 37, "order": 2,
"equipment": 1088 "workout": 21
}
],
"audio_url": "exercise_audio/2_Kettlebell_Clean.m4a",
"video_url": "exercise_videos/2_Kettlebell_Clean.mp4",
"created_at": "2023-06-11T22:50:18.813591Z",
"updated_at": "2023-06-11T22:50:18.813600Z",
"name": "2 Kettlebell Clean",
"description": "Standing with feet in a shoulder width stance, grab the handles of the bells and swing them back between the legs while keeping a neutral spine.\nExtend the hips, swing the bells forward, and allow them to flip around your wrists to end in the front rack position.",
"side": "",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": false,
"is_weight": true,
"is_distance": false,
"is_duration": true,
"is_reps": true,
"joints_used": "shoulder,ankle,knee,hip,elbow",
"movement_patterns": "lower pull - hip hinge,upper pull",
"equipment_required": "Kettlebell",
"muscle_groups": "glutes,hamstrings",
"synonyms": ""
},
"weight": 0,
"reps": 0,
"duration": null,
"duration_audio": null,
"weight_audio": "/media/quantities_audio/for_0_pounds.m4a",
"created_at": "2023-06-20T21:03:00.131018Z"
},
{
"workout": 21,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "exercise_audio/Recover.m4a",
"video_url": "exercise_videos/Recover.mp4",
"created_at": "2023-06-11T22:50:19.127914Z",
"updated_at": "2023-06-11T22:50:19.127921Z",
"name": "Recover",
"description": "Use this time to catch your breath. It will help you get more out of what's next",
"side": "",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": false,
"is_weight": false,
"is_distance": false,
"is_duration": true,
"is_reps": false,
"joints_used": "",
"movement_patterns": "",
"equipment_required": "",
"muscle_groups": "",
"synonyms": null
},
"weight": 0,
"reps": 0,
"duration": null,
"duration_audio": null,
"weight_audio": "/media/quantities_audio/for_0_pounds.m4a",
"created_at": "2023-06-20T21:03:00.134981Z"
} }
], ],
"registered_user": { "registered_user": {

View File

@@ -32,7 +32,7 @@ class AllEquipmentFetchable: Fetchable {
} }
class AllExerciseFetchable: Fetchable { class AllExerciseFetchable: Fetchable {
typealias Response = [ExerciseExercise] typealias Response = [Exercise]
var endPoint: String = "/exercise/all/" var endPoint: String = "/exercise/all/"
} }

View File

@@ -30,11 +30,11 @@ class PreviewData {
} }
} }
class func parseExercises() -> [ExerciseExercise] { class func parseExercises() -> [Exercise] {
if let filepath = Bundle.main.path(forResource: "Exercises", ofType: "json") { if let filepath = Bundle.main.path(forResource: "Exercises", ofType: "json") {
do { do {
let data = try Data(NSData(contentsOfFile: filepath)) let data = try Data(NSData(contentsOfFile: filepath))
let exercises = try JSONDecoder().decode([ExerciseExercise].self, from: data) let exercises = try JSONDecoder().decode([Exercise].self, from: data)
return exercises return exercises
} catch { } catch {
print(error) print(error)

View File

@@ -18,15 +18,15 @@ struct AddExerciseView: View {
@State var selectedMuscles = [Muscle]() @State var selectedMuscles = [Muscle]()
@State var selectedEquipment = [Equipment]() @State var selectedEquipment = [Equipment]()
@State var filteredExercises = [ExerciseExercise]() @State var filteredExercises = [Exercise]()
@StateObject var bridgeModule = BridgeModule.shared @StateObject var bridgeModule = BridgeModule.shared
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var selectedExercise: ((ExerciseExercise) -> Void) var selectedExercise: ((Exercise) -> Void)
@State var createWorkoutItemPickerViewModel: CreateWorkoutItemPickerViewModel? @State var createWorkoutItemPickerViewModel: CreateWorkoutItemPickerViewModel?
@State var createWorkoutItemPickerViewType: CreateWorkoutItemPickerViewType? @State var createWorkoutItemPickerViewType: CreateWorkoutItemPickerViewType?
@State var searchString: String = "" @State var searchString: String = ""
@State var videoExercise: ExerciseExercise? { @State var videoExercise: Exercise? {
didSet { didSet {
if let viddd = self.videoExercise?.videoURL, if let viddd = self.videoExercise?.videoURL,
let url = URL(string: BaseURLs.currentBaseURL + viddd) { let url = URL(string: BaseURLs.currentBaseURL + viddd) {
@@ -111,19 +111,19 @@ struct AddExerciseView: View {
func filterExercises() { func filterExercises() {
if selectedMuscles.count == 0 { if selectedMuscles.count == 0 {
filteredExercises = [ExerciseExercise]() filteredExercises = [Exercise]()
return return
} }
if selectedEquipment.count == 0 { if selectedEquipment.count == 0 {
filteredExercises = [ExerciseExercise]() filteredExercises = [Exercise]()
return return
} }
guard let exercises = DataStore.shared.allExercise, guard let exercises = DataStore.shared.allExercise,
let muscles = DataStore.shared.allMuscles, let muscles = DataStore.shared.allMuscles,
let equipment = DataStore.shared.allEquipment else { let equipment = DataStore.shared.allEquipment else {
filteredExercises = [ExerciseExercise]() filteredExercises = [Exercise]()
return return
} }
@@ -132,7 +132,7 @@ struct AddExerciseView: View {
if selectedMuscles.count == muscles.count { if selectedMuscles.count == muscles.count {
hasCorrectMuscles = true hasCorrectMuscles = true
} else { } else {
let exerciseMuscleIds = exercise.muscles.map({ $0.muscle }) let exerciseMuscleIds = exercise.muscles.map({ $0.id })
let selctedMuscleIds = selectedMuscles.map({ $0.id }) let selctedMuscleIds = selectedMuscles.map({ $0.id })
// if one items match // if one items match
if exerciseMuscleIds.contains(where: selctedMuscleIds.contains) { if exerciseMuscleIds.contains(where: selctedMuscleIds.contains) {
@@ -146,7 +146,7 @@ struct AddExerciseView: View {
if selectedEquipment.count == equipment.count { if selectedEquipment.count == equipment.count {
hasCorrectEquipment = true hasCorrectEquipment = true
} else { } else {
let exerciseEquipmentIds = exercise.equipment.map({ $0.equipment }) let exerciseEquipmentIds = exercise.equipment.map({ $0.id })
let selctedEquipmentIds = selectedEquipment.map({ $0.id }) let selctedEquipmentIds = selectedEquipment.map({ $0.id })
// if one items match // if one items match
if exerciseEquipmentIds.contains(where: selctedEquipmentIds.contains) { if exerciseEquipmentIds.contains(where: selctedEquipmentIds.contains) {
@@ -198,7 +198,7 @@ struct AddExerciseView: View {
var createWorkoutItemPickerModels = [CreateWorkoutItemPickerModel]() var createWorkoutItemPickerModels = [CreateWorkoutItemPickerModel]()
equipment.forEach({ equipment.forEach({
let model = CreateWorkoutItemPickerModel(id: $0.id, let model = CreateWorkoutItemPickerModel(id: $0.id,
name: $0.name?.lowercased() ?? "-") name: $0.name.lowercased())
createWorkoutItemPickerModels.append(model) createWorkoutItemPickerModels.append(model)
}) })
createWorkoutItemPickerModels = createWorkoutItemPickerModels.sorted(by: { createWorkoutItemPickerModels = createWorkoutItemPickerModels.sorted(by: {

View File

@@ -35,13 +35,13 @@ struct AllWorkoutsView: View {
@State private var showWorkoutDetail = false @State private var showWorkoutDetail = false
@State private var selectedWorkout: Workout? { @State private var selectedWorkout: Workout? {
didSet { didSet {
bridgeModule.currentWorkout = selectedWorkout bridgeModule.currentExerciseInfo.workout = selectedWorkout
} }
} }
@State private var selectedPlannedWorkout: Workout? { @State private var selectedPlannedWorkout: Workout? {
didSet { didSet {
bridgeModule.currentWorkout = selectedPlannedWorkout bridgeModule.currentExerciseInfo.workout = selectedPlannedWorkout
} }
} }
@@ -58,7 +58,7 @@ struct AllWorkoutsView: View {
AllWorkoutPickerView(mainViews: MainViewTypes.allCases, AllWorkoutPickerView(mainViews: MainViewTypes.allCases,
selectedSegment: $selectedSegment, selectedSegment: $selectedSegment,
showCurrentWorkout: { showCurrentWorkout: {
selectedWorkout = bridgeModule.currentWorkout selectedWorkout = bridgeModule.currentExerciseInfo.workout
}) })
switch selectedSegment { switch selectedSegment {
@@ -87,12 +87,13 @@ struct AllWorkoutsView: View {
maybeUpdateShit() maybeUpdateShit()
} }
.sheet(item: $selectedWorkout) { item in .sheet(item: $selectedWorkout) { item in
let viewModel = WorkoutDetailViewModel(workout: item) var isPreview = item.id == bridgeModule.currentExerciseInfo.workout?.id
let viewModel = WorkoutDetailViewModel(workout: item, isPreview: isPreview)
WorkoutDetailView(viewModel: viewModel) WorkoutDetailView(viewModel: viewModel)
} }
.sheet(item: $selectedPlannedWorkout) { item in .sheet(item: $selectedPlannedWorkout) { item in
let viewModel = WorkoutDetailViewModel(workout: item) let viewModel = WorkoutDetailViewModel(workout: item, isPreview: true)
WorkoutDetailView(viewModel: viewModel, showAddToCalendar: false) WorkoutDetailView(viewModel: viewModel)
} }
.sheet(isPresented: $showLoginView) { .sheet(isPresented: $showLoginView) {
LoginView(completion: { LoginView(completion: {

View File

@@ -9,12 +9,12 @@ import SwiftUI
class CreateWorkoutExercise: ObservableObject, Identifiable { class CreateWorkoutExercise: ObservableObject, Identifiable {
let id = UUID() let id = UUID()
var exercise: ExerciseExercise var exercise: Exercise
@Published var reps: Int = 0 @Published var reps: Int = 0
@Published var duration: Int = 0 @Published var duration: Int = 0
@Published var weight: Int = 0 @Published var weight: Int = 0
init(exercise: ExerciseExercise, reps: Int = 0, duration: Int = 0, weight: Int = 0) { init(exercise: Exercise, reps: Int = 0, duration: Int = 0, weight: Int = 0) {
self.exercise = exercise self.exercise = exercise
self.reps = reps self.reps = reps
self.duration = duration self.duration = duration

View File

@@ -15,7 +15,8 @@ struct ExternalWorkoutDetailView: View {
var body: some View { var body: some View {
ZStack { ZStack {
if let workout = bridgeModule.currentWorkout { if let workout = bridgeModule.currentExerciseInfo.workout,
let exercise = bridgeModule.currentExerciseInfo.currentExercise {
GeometryReader { metrics in GeometryReader { metrics in
VStack { VStack {
HStack { HStack {
@@ -26,8 +27,8 @@ struct ExternalWorkoutDetailView: View {
} }
VStack { VStack {
ExtExerciseList(workout: workout, ExtExerciseList(workout: workout,
currentExerciseIdx: bridgeModule.currentExerciseIdx) currentExercise: exercise)
if let currentExercisePositionString = bridgeModule.currentExercisePositionString { if let currentExercisePositionString = bridgeModule.currentExercisePositionString {
Text(currentExercisePositionString) Text(currentExercisePositionString)
@@ -53,18 +54,20 @@ struct ExternalWorkoutDetailView: View {
.scaledToFill() .scaledToFill()
} }
} }
.onChange(of: bridgeModule.currentExercise, perform: { newValue in .onChange(of: bridgeModule.currentExerciseInfo.exerciseIndex, perform: { newValue in
if let videoURL = VideoURLCreator.videoURL( if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
thotStyle: thotStyle, if let videoURL = VideoURLCreator.videoURL(
defaultVideoURLStr: bridgeModule.currentExercise?.exercise.videoURL, thotStyle: thotStyle,
exerciseName: bridgeModule.currentExercise?.exercise.name, defaultVideoURLStr: currentExtercise.exercise.videoURL,
workout: bridgeModule.currentWorkout) { exerciseName: currentExtercise.exercise.name,
avPlayer = AVPlayer(url: videoURL) workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer.play() avPlayer = AVPlayer(url: videoURL)
avPlayer.play()
}
} }
}) })
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background(bridgeModule.currentWorkout == nil ? Color(red: 157/255, green: 138/255, blue: 255/255) : Color(uiColor: .systemBackground)) .background(bridgeModule.currentExerciseInfo.workout == nil ? Color(red: 157/255, green: 138/255, blue: 255/255) : Color(uiColor: .systemBackground))
} }
} }
@@ -73,7 +76,7 @@ struct TitleView: View {
var body: some View { var body: some View {
HStack { HStack {
if let workout = bridgeModule.currentWorkout { if let workout = bridgeModule.currentExerciseInfo.workout {
Text(workout.name) Text(workout.name)
.font(Font.system(size: 100)) .font(Font.system(size: 100))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -91,36 +94,64 @@ struct TitleView: View {
struct ExtExerciseList: View { struct ExtExerciseList: View {
var workout: Workout var workout: Workout
var currentExerciseIdx: Int var currentExercise: SupersetExercise
var body: some View { var body: some View {
ScrollViewReader { proxy in if let supersets = workout.supersets {
List() { ScrollViewReader { proxy in
ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in List() {
let obj = workout.exercisesSortedByCreated_at[i] ForEach(supersets.indices, id: \.self) { supersetIndex in
HStack { let superset = supersets[supersetIndex]
if i == currentExerciseIdx {
Image(systemName: "checkmark")
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(1)
.foregroundColor(.green)
}
Text(obj.exercise.name) Section(content: {
.font(Font.system(size: 55)) ForEach(superset.exercises.indices, id: \.self) { exerciseIndex in
.minimumScaleFactor(0.01) let supersetExecercise = superset.exercises[exerciseIndex]
.lineLimit(3)
.padding() HStack {
.id(i) if supersetExecercise.id == currentExercise.id {
Image(systemName: "checkmark")
.foregroundColor(.green)
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(1)
.foregroundColor(.green)
}
Text(supersetExecercise.exercise.name)
.id(exerciseIndex)
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(3)
.padding()
.id(exerciseIndex)
Spacer()
}
}
}, header: {
HStack {
Text(superset.name ?? "--")
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(3)
.padding()
Spacer()
Text("\(superset.rounds) rounds")
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(3)
.padding()
}
})
} }
} }
.onChange(of: currentExercise, perform: { newValue in
withAnimation {
proxy.scrollTo(newValue, anchor: .top)
}
})
} }
.onChange(of: currentExerciseIdx, perform: { newValue in
withAnimation {
proxy.scrollTo(newValue, anchor: .top)
}
})
} }
} }
} }
@@ -131,7 +162,7 @@ struct ExtCountdownView: View {
var body: some View { var body: some View {
GeometryReader { metrics in GeometryReader { metrics in
VStack { VStack {
if let currenExercise = bridgeModule.currentExercise { if let currenExercise = bridgeModule.currentExerciseInfo.currentExercise {
HStack { HStack {
Text(currenExercise.exercise.name) Text(currenExercise.exercise.name)
.font(.system(size: 200)) .font(.system(size: 200))
@@ -203,15 +234,15 @@ struct ExtCountdownView: View {
} }
} }
struct ExternalWorkoutDetailView_Previews: PreviewProvider { //struct ExternalWorkoutDetailView_Previews: PreviewProvider {
static var bridge = BridgeModule.shared // static var bridge = BridgeModule.shared
//
static var previews: some View { // static var previews: some View {
ExternalWorkoutDetailView().environmentObject({ () -> BridgeModule in // ExternalWorkoutDetailView().environmentObject({ () -> BridgeModule in
let envObj = BridgeModule.shared // let envObj = BridgeModule.shared
envObj.currentWorkout = nil //PreviewData.workout() // envObj.currentWorkout = nil //PreviewData.workout()
bridge.currentExercise = PreviewData.workout().exercisesSortedByCreated_at.first! // bridge.currentExercise = PreviewData.workout().exercisesSortedByCreated_at.first!
return envObj // return envObj
}() ) // }() )
} // }
} //}

View File

@@ -15,7 +15,7 @@ struct MainView: View {
var body: some View { var body: some View {
ZStack { ZStack {
if let workout = workout { if let workout = workout {
let vm = WorkoutDetailViewModel(workout: workout) let vm = WorkoutDetailViewModel(workout: workout, isPreview: true)
WorkoutDetailView(viewModel: vm) WorkoutDetailView(viewModel: vm)
} else { } else {
Text("no workout selected") Text("no workout selected")

View File

@@ -11,7 +11,7 @@ struct CountdownView: View {
@StateObject var bridgeModule = BridgeModule.shared @StateObject var bridgeModule = BridgeModule.shared
var body: some View { var body: some View {
if let duration = bridgeModule.currentExercise?.duration, if let duration = bridgeModule.currentExerciseInfo.currentExercise?.duration,
duration > 0 { duration > 0 {
HStack { HStack {
if bridgeModule.currentExerciseTimeLeft >= 0 && duration > bridgeModule.currentExerciseTimeLeft { if bridgeModule.currentExerciseTimeLeft >= 0 && duration > bridgeModule.currentExerciseTimeLeft {

View File

@@ -11,16 +11,16 @@ import AVKit
struct ExerciseListView: View { struct ExerciseListView: View {
@AppStorage("thotStyle") private var thotStyle: ThotStyle = .never @AppStorage("thotStyle") private var thotStyle: ThotStyle = .never
@ObservedObject var bridgeModule = BridgeModule.shared @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 avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!)
var workout: Workout
@State var videoExercise: ExerciseExercise? { @State var videoExercise: Exercise? {
didSet { didSet {
if let videoURL = VideoURLCreator.videoURL( if let videoURL = VideoURLCreator.videoURL(
thotStyle: thotStyle, thotStyle: thotStyle,
defaultVideoURLStr: self.videoExercise?.videoURL, defaultVideoURLStr: self.videoExercise?.videoURL,
exerciseName: self.videoExercise?.name, exerciseName: self.videoExercise?.name,
workout: bridgeModule.currentWorkout) { workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer = AVPlayer(url: videoURL) avPlayer = AVPlayer(url: videoURL)
avPlayer.play() avPlayer.play()
} }
@@ -28,79 +28,97 @@ struct ExerciseListView: View {
} }
var body: some View { var body: some View {
ScrollViewReader { proxy in if let supersets = workout.supersets {
List() { ScrollViewReader { proxy in
ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in List() {
let obj = workout.exercisesSortedByCreated_at[i] ForEach(supersets.indices, id: \.self) { supersetIndex in
HStack { let superset = supersets[supersetIndex]
if i == bridgeModule.currentExerciseIdx { Section(content: {
Image(systemName: "checkmark") ForEach(superset.exercises.indices, id: \.self) { exerciseIndex in
.foregroundColor(.green) let supersetExecercise = superset.exercises[exerciseIndex]
}
Text(obj.exercise.name) HStack {
.id(i) if supersetExecercise.id == bridgeModule.currentExerciseInfo.currentExercise?.id {
Image(systemName: "checkmark")
.foregroundColor(.green)
}
Spacer() Text(supersetExecercise.exercise.name)
.id(exerciseIndex)
if let reps = obj.reps, Spacer()
reps > 0 {
HStack {
Image(systemName: "number")
.foregroundColor(.white)
.frame(width: 20, alignment: .leading)
Text("\(reps)")
.foregroundColor(.white)
.frame(width: 30, alignment: .trailing)
if let reps = supersetExecercise.reps,
reps > 0 {
HStack {
Image(systemName: "number")
.foregroundColor(.white)
.frame(width: 20, alignment: .leading)
Text("\(reps)")
.foregroundColor(.white)
.frame(width: 30, alignment: .trailing)
}
.padding([.top, .bottom], 5)
.padding([.leading], 10)
.padding([.trailing], 15)
.background(.blue)
.cornerRadius(5, corners: [.topLeft, .bottomLeft])
.frame(alignment: .trailing)
}
if let duration = supersetExecercise.duration,
duration > 0 {
HStack {
Image(systemName: "stopwatch")
.foregroundColor(.white)
.frame(width: 20, alignment: .leading)
Text("\(duration)")
.foregroundColor(.white)
.frame(width: 30, alignment: .trailing)
}
.padding([.top, .bottom], 5)
.padding([.leading], 10)
.padding([.trailing], 15)
.background(.green)
.cornerRadius(5, corners: [.topLeft, .bottomLeft])
}
}
.padding(.trailing, -20)
.contentShape(Rectangle())
.onTapGesture {
if bridgeModule.isInWorkout {
bridgeModule.goToExerciseAt(section: supersetIndex, row: exerciseIndex)
} 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()
}
}
} }
.padding([.top, .bottom], 5) }, header: {
.padding([.leading], 10)
.padding([.trailing], 15)
.background(.blue)
.cornerRadius(5, corners: [.topLeft, .bottomLeft])
.frame(alignment: .trailing)
}
if let duration = obj.duration,
duration > 0 {
HStack { HStack {
Image(systemName: "stopwatch") Text(superset.name ?? "--")
.foregroundColor(.white) .foregroundColor(Color("appColor"))
.frame(width: 20, alignment: .leading) .bold()
Text("\(duration)") Spacer()
.foregroundColor(.white) Text("\(superset.rounds) rounds")
.frame(width: 30, alignment: .trailing) .foregroundColor(Color("appColor"))
.bold()
} }
.padding([.top, .bottom], 5) })
.padding([.leading], 10)
.padding([.trailing], 15)
.background(.green)
.cornerRadius(5, corners: [.topLeft, .bottomLeft])
}
}
.padding(.trailing, -20)
.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()
}
} }
} }
} }

View File

@@ -23,7 +23,6 @@ struct WorkoutDetailView: View {
@State var presentedSheet: Sheet? @State var presentedSheet: Sheet?
@State var workoutToPlan: Workout? @State var workoutToPlan: Workout?
var showAddToCalendar = true
var body: some View { var body: some View {
ZStack { ZStack {
@@ -80,7 +79,7 @@ struct WorkoutDetailView: View {
bridgeModule.completeWorkout() bridgeModule.completeWorkout()
}, planWorkout: { workout in }, planWorkout: { workout in
workoutToPlan = workout workoutToPlan = workout
}, workout: workout, showAddToCalendar: showAddToCalendar) }, workout: workout, showAddToCalendar: viewModel.isPreview)
.frame(height: 44) .frame(height: 44)
} }
@@ -101,24 +100,28 @@ struct WorkoutDetailView: View {
} }
} }
} }
.onChange(of: bridgeModule.currentExercise, perform: { newValue in .onChange(of: bridgeModule.currentExerciseInfo.exerciseIndex, perform: { newValue in
if let videoURL = VideoURLCreator.videoURL( if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
thotStyle: thotStyle, if let videoURL = VideoURLCreator.videoURL(
defaultVideoURLStr: newValue?.exercise.videoURL, thotStyle: thotStyle,
exerciseName: newValue?.exercise.name, defaultVideoURLStr: currentExtercise.exercise.videoURL,
workout: bridgeModule.currentWorkout) { exerciseName: currentExtercise.exercise.name,
avPlayer = AVPlayer(url: videoURL) workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer.play() avPlayer = AVPlayer(url: videoURL)
avPlayer.play()
}
} }
}) })
.onAppear{ .onAppear{
if let videoURL = VideoURLCreator.videoURL( if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
thotStyle: thotStyle, if let videoURL = VideoURLCreator.videoURL(
defaultVideoURLStr: bridgeModule.currentExercise?.exercise.videoURL, thotStyle: thotStyle,
exerciseName: bridgeModule.currentExercise?.exercise.name, defaultVideoURLStr: currentExtercise.exercise.videoURL,
workout: bridgeModule.currentWorkout) { exerciseName: currentExtercise.exercise.name,
avPlayer = AVPlayer(url: videoURL) workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer.play() avPlayer = AVPlayer(url: videoURL)
avPlayer.play()
}
} }
bridgeModule.completedWorkout = { bridgeModule.completedWorkout = {
@@ -132,7 +135,7 @@ struct WorkoutDetailView: View {
} }
func createWorkoutData() -> [String:Any]? { func createWorkoutData() -> [String:Any]? {
guard let workoutid = bridgeModule.currentWorkout?.id, guard let workoutid = bridgeModule.currentExerciseInfo.workout?.id,
let startTime = bridgeModule.workoutStartDate?.timeFormatForUpload, let startTime = bridgeModule.workoutStartDate?.timeFormatForUpload,
let endTime = bridgeModule.workoutEndDate?.timeFormatForUpload else { let endTime = bridgeModule.workoutEndDate?.timeFormatForUpload else {
return nil return nil
@@ -154,6 +157,6 @@ struct WorkoutDetailView: View {
struct WorkoutDetailView_Previews: PreviewProvider { struct WorkoutDetailView_Previews: PreviewProvider {
static let workoutDetail = PreviewData.workout() static let workoutDetail = PreviewData.workout()
static var previews: some View { static var previews: some View {
WorkoutDetailView(viewModel: WorkoutDetailViewModel(workout: WorkoutDetailView_Previews.workoutDetail, status: .showWorkout(WorkoutDetailView_Previews.workoutDetail))) WorkoutDetailView(viewModel: WorkoutDetailViewModel(workout: WorkoutDetailView_Previews.workoutDetail, status: .showWorkout(WorkoutDetailView_Previews.workoutDetail), isPreview: true))
} }
} }

View File

@@ -13,10 +13,13 @@ class WorkoutDetailViewModel: ObservableObject {
case loading case loading
case showWorkout(Workout) case showWorkout(Workout)
} }
@Published var status: WorkoutDetailViewModelStatus
init(workout: Workout, status: WorkoutDetailViewModelStatus? = nil) { @Published var status: WorkoutDetailViewModelStatus
let isPreview: Bool
init(workout: Workout, status: WorkoutDetailViewModelStatus? = nil, isPreview: Bool) {
self.status = .loading self.status = .loading
self.isPreview = isPreview
if let passedStatus = status { if let passedStatus = status {
self.status = passedStatus self.status = passedStatus

View File

@@ -79,8 +79,8 @@ struct WorkoutHistoryView: View {
} }
} }
.sheet(item: $selectedPlannedWorkout) { item in .sheet(item: $selectedPlannedWorkout) { item in
let viewModel = WorkoutDetailViewModel(workout: item) let viewModel = WorkoutDetailViewModel(workout: item, isPreview: true)
WorkoutDetailView(viewModel: viewModel, showAddToCalendar: true) WorkoutDetailView(viewModel: viewModel)
} }
} }
} }