This commit is contained in:
Trey t
2023-07-28 11:15:13 -05:00
parent 2dcd260887
commit 1997abeff6
13 changed files with 264 additions and 101 deletions

View File

@@ -7,7 +7,7 @@
import Foundation import Foundation
struct RegisteredUser: Codable { struct RegisteredUser: Codable, Hashable {
let id: Int let id: Int
let firstName, lastName, image: String? let firstName, lastName, image: String?
let nickName: String? let nickName: String?

View File

@@ -12,12 +12,14 @@ struct Superset: Codable, Identifiable, Hashable {
let exercises: [SupersetExercise] let exercises: [SupersetExercise]
let createdAt, updatedAt, name: String? let createdAt, updatedAt, name: String?
let rounds, order, workout: Int let rounds, order, workout: Int
let estimatedTime: Double?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id, exercises case id, exercises
case createdAt = "created_at" case createdAt = "created_at"
case updatedAt = "updated_at" case updatedAt = "updated_at"
case name, rounds, order, workout case name, rounds, order, workout
case estimatedTime = "estimated_time"
} }
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {

View File

@@ -21,6 +21,9 @@ struct Workout: Codable, Identifiable, Equatable {
let muscles: [String]? let muscles: [String]?
let equipment: [String]? let equipment: [String]?
let exercise_count: Int? let exercise_count: Int?
let createdAt: Date?
let estimatedTime: Double?
let allSupersetExecercise: [SupersetExercise]?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case id, name, description, supersets, exercise_count, muscles, equipment case id, name, description, supersets, exercise_count, muscles, equipment
@@ -28,6 +31,9 @@ struct Workout: Codable, Identifiable, Equatable {
case maleVideos = "male_videos" case maleVideos = "male_videos"
case femaleVideos = "female_videos" case femaleVideos = "female_videos"
case bothVideos = "both_videos" case bothVideos = "both_videos"
case createdAt = "created_at"
case estimatedTime = "estimated_time"
case allSupersetExecercise = "all_superset_exercise"
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
@@ -45,5 +51,20 @@ struct Workout: Codable, Identifiable, Equatable {
self.equipment = try container.decodeIfPresent([String].self, forKey: .equipment) self.equipment = try container.decodeIfPresent([String].self, forKey: .equipment)
self.muscles = try container.decodeIfPresent([String].self, forKey: .muscles) self.muscles = try container.decodeIfPresent([String].self, forKey: .muscles)
self.exercise_count = try container.decodeIfPresent(Int.self, forKey: .exercise_count) self.exercise_count = try container.decodeIfPresent(Int.self, forKey: .exercise_count)
let createdAtStr = try container.decodeIfPresent(String.self, forKey: .createdAt)
if let createdAtStr = createdAtStr {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date = formatter.date(from: createdAtStr)
self.createdAt = date
} else {
self.createdAt = nil
}
self.estimatedTime = try container.decodeIfPresent(Double.self, forKey: .estimatedTime)
allSupersetExecercise = try container.decodeIfPresent([SupersetExercise].self, forKey: .allSupersetExecercise)
} }
} }

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

@@ -14,6 +14,7 @@ class CurrentWorkoutInfo {
var complete: (() -> Void)? var complete: (() -> Void)?
var currentRound = 1 var currentRound = 1
var allSupersetExecerciseIndex = 0
var superset: [Superset] { var superset: [Superset] {
return workout?.supersets?.sorted(by: { $0.order < $1.order }) ?? [Superset]() return workout?.supersets?.sorted(by: { $0.order < $1.order }) ?? [Superset]()
@@ -57,6 +58,7 @@ class CurrentWorkoutInfo {
let superset = supersets[supersetIndex] let superset = supersets[supersetIndex]
let exercise = superset.exercises[exerciseIndex] let exercise = superset.exercises[exerciseIndex]
allSupersetExecerciseIndex += 1
return exercise return exercise
} }
@@ -84,6 +86,12 @@ class CurrentWorkoutInfo {
let superset = supersets[supersetIndex] let superset = supersets[supersetIndex]
let exercise = superset.exercises[exerciseIndex] let exercise = superset.exercises[exerciseIndex]
allSupersetExecerciseIndex -= 1
if allSupersetExecerciseIndex < 0 {
allSupersetExecerciseIndex = 0
}
return exercise return exercise
} }
@@ -96,12 +104,21 @@ class CurrentWorkoutInfo {
supersetIndex = 0 supersetIndex = 0
exerciseIndex = 0 exerciseIndex = 0
currentRound = 1 currentRound = 1
allSupersetExecerciseIndex = 0
self.workout = nil self.workout = nil
} }
func goToWorkoutAt(supersetIndex: Int, exerciseIndex: Int) -> SupersetExercise? { func goToWorkoutAt(supersetIndex: Int, exerciseIndex: Int) -> SupersetExercise? {
self.supersetIndex = supersetIndex self.supersetIndex = supersetIndex
self.exerciseIndex = exerciseIndex self.exerciseIndex = exerciseIndex
self.currentRound = 1
let currentExercise = currentExercise
if let firstIdx = workout?.allSupersetExecercise?.firstIndex(where: {
$0.exercise.id == currentExercise?.exercise.id
}) {
allSupersetExecerciseIndex = firstIdx
}
return currentExercise return currentExercise
} }

View File

@@ -25,7 +25,16 @@ class DataStore: ObservableObject {
private let fetchAllDataQueue = DispatchGroup() private let fetchAllDataQueue = DispatchGroup()
public func fetchAllData() { public var workoutsUniqueUsers: [RegisteredUser]? {
guard let workouts = allWorkouts else {
return nil
}
let users = workouts.compactMap({ $0.registeredUser })
return Array(Set(users))
}
public func fetchAllData(completion: @escaping (() -> Void)) {
status = .loading status = .loading
fetchAllDataQueue.enter() fetchAllDataQueue.enter()
@@ -35,6 +44,7 @@ class DataStore: ObservableObject {
fetchAllDataQueue.notify(queue: .main) { fetchAllDataQueue.notify(queue: .main) {
self.status = .idle self.status = .idle
completion()
} }
AllWorkoutFetchable().fetch(completion: { result in AllWorkoutFetchable().fetch(completion: { result in

View File

@@ -8,36 +8,27 @@
import SwiftUI import SwiftUI
struct AllWorkoutsListView: View { struct AllWorkoutsListView: View {
@State var searchString: String = "" enum SortType: String, CaseIterable {
let workouts: [Workout] case name = "Name"
case date = "Date"
let selectedWorkout: ((Workout) -> Void)
var filteredWorkouts: [Workout] {
if !searchString.isEmpty, searchString.count > 0 {
return workouts.filter({
if $0.name.lowercased().contains(searchString.lowercased()) {
return true
}
if let equipment = $0.equipment?.joined(separator: "").lowercased(),
equipment.contains(searchString.lowercased()) {
return true
}
if let muscles = $0.muscles?.joined(separator: "").lowercased(),
muscles.contains(searchString.lowercased()) {
return true
}
return false
})
} else {
return workouts
}
} }
@State var searchString: String = ""
@Binding var uniqueWorkoutUsers: [RegisteredUser]?
@State private var filteredRegisterdUser: RegisteredUser?
let workouts: [Workout]
let selectedWorkout: ((Workout) -> Void)
@State var filteredWorkouts = [Workout]()
var refresh: (() -> Void) var refresh: (() -> Void)
@State var currentSort: SortType?
var body: some View { var body: some View {
VStack { VStack {
if let filteredRegisterdUser = filteredRegisterdUser {
Text((filteredRegisterdUser.firstName ?? "NA") + "'s Workouts")
}
ScrollView { ScrollView {
LazyVStack(spacing: 20) { LazyVStack(spacing: 20) {
ForEach(filteredWorkouts, id:\.id) { workout in ForEach(filteredWorkouts, id:\.id) { workout in
@@ -51,24 +42,120 @@ struct AllWorkoutsListView: View {
} }
} }
.refreshable { .refreshable {
refresh() refresh()
} }
TextField("Filter" ,text: $searchString) HStack {
.padding() TextField("Filter" ,text: $searchString)
.textFieldStyle(OvalTextFieldStyle()) .padding()
.textFieldStyle(OvalTextFieldStyle())
if let uniqueWorkoutUsers = uniqueWorkoutUsers {
Menu(content: {
ForEach(uniqueWorkoutUsers, id: \.self) { index in
Button(action: {
filteredRegisterdUser = index
filteredWorkouts = filterWorkouts()
}, label: {
Text((index.firstName ?? "") + " -" + (index.lastName ?? ""))
})
}
Button(action: {
filteredRegisterdUser = nil
filteredWorkouts = filterWorkouts()
}, label: {
Text("All")
})
}, label: {
Image(systemName: filteredRegisterdUser == nil ? "person.2" : "person.2.fill")
.padding(.trailing)
})
}
Menu(content: {
ForEach(SortType.allCases, id: \.self) { index in
Button(action: {
sortWorkouts(sortType: index)
}, label: {
Text(index.rawValue)
})
}
}, label: {
Image(systemName: "list.number")
.padding(.trailing)
})
}
} }
.onChange(of: searchString) { newValue in
filteredWorkouts = filterWorkouts()
}
.onAppear{
filteredWorkouts = filterWorkouts()
}
}
func sortWorkouts(sortType: SortType) {
if currentSort == sortType {
filteredWorkouts = filteredWorkouts.reversed()
return
}
switch sortType {
case .name:
filteredWorkouts = filteredWorkouts.sorted(by: {
$0.name < $1.name
})
case .date:
filteredWorkouts = filteredWorkouts.sorted(by: {
$0.createdAt ?? Date() < $1.createdAt ?? Date()
})
}
currentSort = sortType
}
func filterWorkouts() -> [Workout] {
var matchingWorkouts = [Workout]()
if (!searchString.isEmpty && searchString.count > 0) {
matchingWorkouts = workouts.filter({
if $0.name.lowercased().contains(searchString.lowercased()) {
return true
}
if let equipment = $0.equipment?.joined(separator: "").lowercased(),
equipment.contains(searchString.lowercased()) {
return true
}
if let muscles = $0.muscles?.joined(separator: "").lowercased(),
muscles.contains(searchString.lowercased()) {
return true
}
return false
})
}
if matchingWorkouts.isEmpty {
matchingWorkouts.append(contentsOf: workouts)
}
if let filteredRegisterdUser = filteredRegisterdUser {
matchingWorkouts = matchingWorkouts.filter({
$0.registeredUser == filteredRegisterdUser
})
}
return matchingWorkouts
} }
} }
struct AllWorkoutsListView_Previews: PreviewProvider { struct AllWorkoutsListView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
AllWorkoutsListView(workouts: PreviewData.allWorkouts(), AllWorkoutsListView(uniqueWorkoutUsers: .constant([]),
selectedWorkout: { workout in workouts: PreviewData.allWorkouts(),
selectedWorkout: { workout in },
}, refresh: { })
refresh: {
})
} }
} }

View File

@@ -24,9 +24,10 @@ enum MainViewTypes: Int, CaseIterable {
} }
struct AllWorkoutsView: View { struct AllWorkoutsView: View {
@State var isUpdating = false @State var isUpdating = false
@State var workouts: [Workout]? @State var workouts: [Workout]?
@State var uniqueWorkoutUsers: [RegisteredUser]?
var bridgeModule = BridgeModule.shared var bridgeModule = BridgeModule.shared
@State public var needsUpdating: Bool = true @State public var needsUpdating: Bool = true
@@ -61,6 +62,7 @@ struct AllWorkoutsView: View {
selectedWorkout = bridgeModule.currentExerciseInfo.workout selectedWorkout = bridgeModule.currentExerciseInfo.workout
}) })
switch selectedSegment { switch selectedSegment {
case .AllWorkout: case .AllWorkout:
if isUpdating { if isUpdating {
@@ -68,7 +70,9 @@ struct AllWorkoutsView: View {
.progressViewStyle(.circular) .progressViewStyle(.circular)
} }
AllWorkoutsListView(workouts: workouts, selectedWorkout: { workout in AllWorkoutsListView(uniqueWorkoutUsers: $uniqueWorkoutUsers,
workouts: workouts,
selectedWorkout: { workout in
selectedWorkout = workout selectedWorkout = workout
}, refresh: { }, refresh: {
self.needsUpdating = true self.needsUpdating = true
@@ -155,21 +159,19 @@ struct AllWorkoutsView: View {
if needsUpdating { if needsUpdating {
self.isUpdating = true self.isUpdating = true
dataStore.fetchAllData() dataStore.fetchAllData(completion: {
DispatchQueue.main.async {
AllWorkoutFetchable().fetch(completion: { result in guard let allWorkouts = dataStore.allWorkouts else {
needsUpdating = false return
switch result {
case .success(let model):
DispatchQueue.main.async {
self.workouts = model
self.isUpdating = false
}
case .failure(_):
DispatchQueue.main.async {
self.isUpdating = false
} }
self.workouts = allWorkouts.sorted(by: {
$0.createdAt ?? Date() < $1.createdAt ?? Date()
})
self.isUpdating = false
self.uniqueWorkoutUsers = dataStore.workoutsUniqueUsers
} }
self.isUpdating = false
}) })
} }
} else { } else {

View File

@@ -19,6 +19,17 @@ struct WorkoutOverviewView: View {
Text(workout.description ?? "") Text(workout.description ?? "")
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
if let estimatedTime = workout.estimatedTime {
Text("Time: " + estimatedTime.asString(style: .abbreviated))
.frame(maxWidth: .infinity, alignment: .leading)
}
if let createdAt = workout.createdAt {
Text(createdAt, style: .date)
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
}
} }
if let exerciseCount = workout.exercise_count { if let exerciseCount = workout.exercise_count {
VStack { VStack {

View File

@@ -17,8 +17,7 @@ struct ExternalWorkoutDetailView: View {
var body: some View { var body: some View {
ZStack { ZStack {
if let workout = bridgeModule.currentExerciseInfo.workout, if let workout = bridgeModule.currentExerciseInfo.workout {
let exercise = bridgeModule.currentExerciseInfo.currentExercise {
GeometryReader { metrics in GeometryReader { metrics in
VStack { VStack {
HStack { HStack {
@@ -32,7 +31,7 @@ struct ExternalWorkoutDetailView: View {
VStack { VStack {
ExtExerciseList(workout: workout, ExtExerciseList(workout: workout,
currentExercise: exercise) allSupersetExecerciseIndex: bridgeModule.currentExerciseInfo.allSupersetExecerciseIndex)
if let currentExercisePositionString = bridgeModule.currentExercisePositionString { if let currentExercisePositionString = bridgeModule.currentExercisePositionString {
Text(currentExercisePositionString) Text(currentExercisePositionString)
@@ -153,63 +152,64 @@ struct TitleView: View {
struct ExtExerciseList: View { struct ExtExerciseList: View {
var workout: Workout var workout: Workout
var currentExercise: SupersetExercise var allSupersetExecerciseIndex: Int
var body: some View { var body: some View {
if let supersets = workout.supersets { if let allSupersetExecercise = workout.allSupersetExecercise {
ScrollViewReader { proxy in ZStack {
List() { ScrollViewReader { proxy in
ForEach(supersets.indices, id: \.self) { supersetIndex in List() {
let superset = supersets[supersetIndex] ForEach(allSupersetExecercise.indices, id: \.self) { supersetExecerciseIdx in
let supersetExecercise = allSupersetExecercise[supersetExecerciseIdx]
Section(content: { HStack {
ForEach(superset.exercises.indices, id: \.self) { exerciseIndex in if supersetExecerciseIdx == allSupersetExecerciseIndex {
let supersetExecercise = superset.exercises[exerciseIndex] Image(systemName: "checkmark")
.foregroundColor(.green)
HStack {
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)
.font(Font.system(size: 55)) .font(Font.system(size: 55))
.minimumScaleFactor(0.01) .minimumScaleFactor(0.01)
.lineLimit(3) .lineLimit(1)
.padding() .foregroundColor(.green)
Spacer()
} }
.id(supersetExecercise.id)
} Text(supersetExecercise.exercise.name)
}, header: {
HStack {
Text(superset.name ?? "--")
.font(Font.system(size: 55)) .font(Font.system(size: 55))
.minimumScaleFactor(0.01) .minimumScaleFactor(0.01)
.lineLimit(3) .lineLimit(3)
.padding() .padding()
Spacer() Spacer()
Text("\(superset.rounds) rounds")
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(3)
.padding()
} }
}) .id(supersetExecerciseIdx)
}
} }
.onChange(of: allSupersetExecerciseIndex, perform: { newValue in
withAnimation {
proxy.scrollTo(allSupersetExecerciseIndex, anchor: .top)
}
})
}
VStack {
Text("\(allSupersetExecerciseIndex+1)/\(workout.allSupersetExecercise?.count ?? 0)")
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(1)
.padding()
.bold()
.foregroundColor(.white)
.background(
Capsule()
.strokeBorder(Color.black, lineWidth: 0.8)
.background(Color(uiColor: UIColor(red: 148/255,
green: 0,
blue: 211/255,
alpha: 0.5)))
.clipped()
)
.clipShape(Capsule())
Spacer()
} }
.onChange(of: currentExercise, perform: { newValue in
withAnimation {
print(newValue.id)
proxy.scrollTo(newValue.id, anchor: .top)
}
})
} }
} }
} }

View File

@@ -113,6 +113,12 @@ struct ExerciseListView: View {
Text("\(superset.rounds) rounds") Text("\(superset.rounds) rounds")
.foregroundColor(Color("appColor")) .foregroundColor(Color("appColor"))
.bold() .bold()
if let estimatedTime = superset.estimatedTime {
Text("@ " + estimatedTime.asString(style: .abbreviated))
.foregroundColor(Color("appColor"))
.bold()
}
} }
}) })
} }

View File

@@ -18,12 +18,20 @@ struct InfoView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.font(.title3) .font(.title3)
.padding() .padding()
if let desc = workout.description { if let desc = workout.description {
Text(desc) Text(desc)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.font(.body) .font(.body)
.padding([.leading, .trailing]) .padding([.leading, .trailing])
} }
if let estimatedTime = workout.estimatedTime {
Text(estimatedTime.asString(style: .abbreviated))
.frame(maxWidth: .infinity, alignment: .leading)
.font(.body)
.padding([.leading, .trailing])
}
} }
} }
} }

View File

@@ -43,7 +43,6 @@ struct WorkoutDetailView: View {
if phoneThotStyle != .off { if phoneThotStyle != .off {
GeometryReader { metrics in GeometryReader { metrics in
ZStack { ZStack {
PlayerView(player: $avPlayer) PlayerView(player: $avPlayer)
.frame(width: metrics.size.width * 1, height: metrics.size.height * 1) .frame(width: metrics.size.width * 1, height: metrics.size.height * 1)
.onAppear{ .onAppear{