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
struct RegisteredUser: Codable {
struct RegisteredUser: Codable, Hashable {
let id: Int
let firstName, lastName, image: String?
let nickName: String?

View File

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

View File

@@ -21,6 +21,9 @@ struct Workout: Codable, Identifiable, Equatable {
let muscles: [String]?
let equipment: [String]?
let exercise_count: Int?
let createdAt: Date?
let estimatedTime: Double?
let allSupersetExecercise: [SupersetExercise]?
enum CodingKeys: String, CodingKey {
case id, name, description, supersets, exercise_count, muscles, equipment
@@ -28,6 +31,9 @@ struct Workout: Codable, Identifiable, Equatable {
case maleVideos = "male_videos"
case femaleVideos = "female_videos"
case bothVideos = "both_videos"
case createdAt = "created_at"
case estimatedTime = "estimated_time"
case allSupersetExecercise = "all_superset_exercise"
}
init(from decoder: Decoder) throws {
@@ -45,5 +51,20 @@ struct Workout: Codable, Identifiable, Equatable {
self.equipment = try container.decodeIfPresent([String].self, forKey: .equipment)
self.muscles = try container.decodeIfPresent([String].self, forKey: .muscles)
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"
static var currentBaseURL: String {
return BaseURLs.dev.rawValue
return BaseURLs.local.rawValue
}
}

View File

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

View File

@@ -25,7 +25,16 @@ class DataStore: ObservableObject {
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
fetchAllDataQueue.enter()
@@ -35,6 +44,7 @@ class DataStore: ObservableObject {
fetchAllDataQueue.notify(queue: .main) {
self.status = .idle
completion()
}
AllWorkoutFetchable().fetch(completion: { result in

View File

@@ -8,36 +8,27 @@
import SwiftUI
struct AllWorkoutsListView: View {
@State var searchString: String = ""
let workouts: [Workout]
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
}
enum SortType: String, CaseIterable {
case name = "Name"
case date = "Date"
}
@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)
@State var currentSort: SortType?
var body: some View {
VStack {
if let filteredRegisterdUser = filteredRegisterdUser {
Text((filteredRegisterdUser.firstName ?? "NA") + "'s Workouts")
}
ScrollView {
LazyVStack(spacing: 20) {
ForEach(filteredWorkouts, id:\.id) { workout in
@@ -51,24 +42,120 @@ struct AllWorkoutsListView: View {
}
}
.refreshable {
refresh()
refresh()
}
TextField("Filter" ,text: $searchString)
.padding()
.textFieldStyle(OvalTextFieldStyle())
HStack {
TextField("Filter" ,text: $searchString)
.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 {
static var previews: some View {
AllWorkoutsListView(workouts: PreviewData.allWorkouts(),
selectedWorkout: { workout in
},
refresh: {
})
AllWorkoutsListView(uniqueWorkoutUsers: .constant([]),
workouts: PreviewData.allWorkouts(),
selectedWorkout: { workout in },
refresh: { })
}
}

View File

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

View File

@@ -19,6 +19,17 @@ struct WorkoutOverviewView: View {
Text(workout.description ?? "")
.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 {
VStack {

View File

@@ -17,8 +17,7 @@ struct ExternalWorkoutDetailView: View {
var body: some View {
ZStack {
if let workout = bridgeModule.currentExerciseInfo.workout,
let exercise = bridgeModule.currentExerciseInfo.currentExercise {
if let workout = bridgeModule.currentExerciseInfo.workout {
GeometryReader { metrics in
VStack {
HStack {
@@ -32,7 +31,7 @@ struct ExternalWorkoutDetailView: View {
VStack {
ExtExerciseList(workout: workout,
currentExercise: exercise)
allSupersetExecerciseIndex: bridgeModule.currentExerciseInfo.allSupersetExecerciseIndex)
if let currentExercisePositionString = bridgeModule.currentExercisePositionString {
Text(currentExercisePositionString)
@@ -153,63 +152,64 @@ struct TitleView: View {
struct ExtExerciseList: View {
var workout: Workout
var currentExercise: SupersetExercise
var allSupersetExecerciseIndex: Int
var body: some View {
if let supersets = workout.supersets {
ScrollViewReader { proxy in
List() {
ForEach(supersets.indices, id: \.self) { supersetIndex in
let superset = supersets[supersetIndex]
Section(content: {
ForEach(superset.exercises.indices, id: \.self) { exerciseIndex in
let supersetExecercise = superset.exercises[exerciseIndex]
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)
if let allSupersetExecercise = workout.allSupersetExecercise {
ZStack {
ScrollViewReader { proxy in
List() {
ForEach(allSupersetExecercise.indices, id: \.self) { supersetExecerciseIdx in
let supersetExecercise = allSupersetExecercise[supersetExecerciseIdx]
HStack {
if supersetExecerciseIdx == allSupersetExecerciseIndex {
Image(systemName: "checkmark")
.foregroundColor(.green)
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(3)
.padding()
Spacer()
.lineLimit(1)
.foregroundColor(.green)
}
.id(supersetExecercise.id)
}
}, header: {
HStack {
Text(superset.name ?? "--")
Text(supersetExecercise.exercise.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()
}
})
.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")
.foregroundColor(Color("appColor"))
.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)
.font(.title3)
.padding()
if let desc = workout.description {
Text(desc)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.body)
.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 {
GeometryReader { metrics in
ZStack {
PlayerView(player: $avPlayer)
.frame(width: metrics.size.width * 1, height: metrics.size.height * 1)
.onAppear{