This commit is contained in:
Trey t
2023-07-24 11:22:29 -05:00
parent 2753e31f24
commit 2dcd260887
12 changed files with 298 additions and 150 deletions

View File

@@ -8,7 +8,7 @@
import Foundation
struct SupersetExercise: Identifiable, Codable, Equatable, Hashable {
var id = UUID()
var id: Int
let workout: Int?
let exercise: Exercise
@@ -19,12 +19,15 @@ struct SupersetExercise: Identifiable, Codable, Equatable, Hashable {
let weightAudio: String?
let createdAt: String
let order, superset: Int
let uniqueID: String
let description: String?
enum CodingKeys: String, CodingKey {
case workout, exercise, weight, reps, duration, order, superset
case workout, exercise, weight, reps, duration, order, superset, id, description
case durationAudio = "duration_audio"
case weightAudio = "weight_audio"
case createdAt = "created_at"
case uniqueID = "unique_id"
}
public func hash(into hasher: inout Hasher) {
@@ -67,4 +70,13 @@ struct Exercise: Identifiable, Codable, Equatable {
case muscleGroups = "muscle_groups"
case synonyms
}
var extName: String {
if side.count > 0 {
var returnString = name + " - " + side
returnString = returnString.replacingOccurrences(of: "_", with: " ")
return returnString.capitalized
}
return name
}
}

View File

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

View File

@@ -133,12 +133,8 @@ class BridgeModule: NSObject, ObservableObject {
if currentExerciseTimeLeft > 0 {
currentExerciseTimeLeft -= 1
if currentExerciseTimeLeft == 0 {
playFinished()
} else {
if currentExerciseTimeLeft <= 3 {
playBeep()
}
if currentExerciseTimeLeft <= 3 {
playBeep()
}
sendCurrentExerciseToWatch()
} else {
@@ -158,6 +154,7 @@ class BridgeModule: NSObject, ObservableObject {
func nextExercise() {
if let nextSupersetExercise = currentExerciseInfo.nextExercise {
updateCurrent(exercise: nextSupersetExercise)
playFinished()
} else {
completeWorkout()
}
@@ -196,6 +193,7 @@ class BridgeModule: NSObject, ObservableObject {
func completeWorkout() {
self.currentExerciseTimer?.invalidate()
self.currentExerciseTimer = nil
self.isInWorkout = false
workoutEndDate = Date()

View File

@@ -21,7 +21,13 @@ class CurrentWorkoutInfo {
var currentExercise: SupersetExercise? {
guard let supersets = workout?.supersets else { return nil }
if supersetIndex >= supersets.count { return nil }
let superset = supersets[supersetIndex]
// will be -1 for a moment while going to previous workout / superset
if exerciseIndex < 0 { return nil }
if exerciseIndex > superset.exercises.count { return nil }
let exercise = superset.exercises[exerciseIndex]
return exercise
}
@@ -62,12 +68,18 @@ class CurrentWorkoutInfo {
if exerciseIndex < 0 {
if currentRound > 1 {
currentRound -= 1
let superset = supersets[supersetIndex]
exerciseIndex = superset.exercises.count-1
} else {
if supersetIndex > 1 {
if supersetIndex > 0 {
supersetIndex -= 1
let superset = supersets[supersetIndex]
currentRound = superset.rounds
exerciseIndex = superset.exercises.count-1
} else {
exerciseIndex = 0
}
}
exerciseIndex = 0
}
let superset = supersets[supersetIndex]

View File

@@ -14,6 +14,7 @@ struct AccountView: View {
@State var showCompletedWorkouts: Bool = false
@AppStorage(Constants.phoneThotStyle) private var phoneThotStyle: ThotStyle = .never
@AppStorage(Constants.extThotStyle) private var extThotStyle: ThotStyle = .never
@AppStorage(Constants.extShowBothVideos) private var extShowBothVideos: Bool = false
var body: some View {
VStack(alignment: .leading) {
@@ -63,26 +64,36 @@ struct AccountView: View {
}
}
Divider()
Text("Phone THOT Style:")
Picker("Phone THOT Style:", selection: $phoneThotStyle) {
ForEach(ThotStyle.allCases, id: \.self) { style in
Text(style.stringValue())
.tag(phoneThotStyle.rawValue)
Group {
Text("Phone THOT Style:")
Picker("Phone THOT Style:", selection: $phoneThotStyle) {
ForEach(ThotStyle.allCases, id: \.self) { style in
Text(style.stringValue())
.tag(phoneThotStyle.rawValue)
}
}
}
.pickerStyle(.segmented)
Divider()
Text("External THOT Style:")
Picker("External THOT Style:", selection: $extThotStyle) {
ForEach(ThotStyle.allCases, id: \.self) { style in
Text(style.stringValue())
.tag(extThotStyle.rawValue)
.pickerStyle(.segmented)
Divider()
Text("External THOT Style:")
Picker("External THOT Style:", selection: $extThotStyle) {
ForEach(ThotStyle.allCases, id: \.self) { style in
Text(style.stringValue())
.tag(extThotStyle.rawValue)
}
}
.pickerStyle(.segmented)
}
.pickerStyle(.segmented)
Group {
Divider()
Toggle(isOn: $extShowBothVideos, label: {
Text("Show both videos on external")
})
}
Spacer()
Button("Logout", action: {

View File

@@ -11,8 +11,10 @@ import AVKit
struct ExternalWorkoutDetailView: View {
@StateObject var bridgeModule = BridgeModule.shared
@State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!)
@State var smallAVPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!)
@AppStorage(Constants.extThotStyle) private var extThotStyle: ThotStyle = .never
@AppStorage(Constants.extShowBothVideos) private var extShowBothVideos: Bool = false
var body: some View {
ZStack {
if let workout = bridgeModule.currentExerciseInfo.workout,
@@ -20,11 +22,13 @@ struct ExternalWorkoutDetailView: View {
GeometryReader { metrics in
VStack {
HStack {
PlayerView(player: $avPlayer)
.frame(width: metrics.size.width * 0.5, height: metrics.size.height * 0.8)
.onAppear{
avPlayer.play()
}
if extThotStyle != .off {
PlayerView(player: $avPlayer)
.frame(width: metrics.size.width * 0.5, height: metrics.size.height * 0.8)
.onAppear{
avPlayer.play()
}
}
VStack {
ExtExerciseList(workout: workout,
@@ -42,9 +46,25 @@ struct ExternalWorkoutDetailView: View {
.frame(width: metrics.size.width * 0.4, height: metrics.size.height * 0.8)
}
ExtCountdownView()
.frame(width: metrics.size.width-50, height: metrics.size.height * 0.2)
.padding([.leading, .trailing], 50)
HStack {
if extShowBothVideos && extThotStyle != .off {
ExtCountdownView()
.frame(width: metrics.size.width * 0.8, height: metrics.size.height * 0.2)
.padding(.leading, 50)
.padding(.trailing, 5)
PlayerView(player: $smallAVPlayer)
.frame(width: metrics.size.width * 0.2, height: metrics.size.height * 0.2)
.padding(.trailing, 50)
.onAppear{
avPlayer.play()
}
} else {
ExtCountdownView()
.frame(width: metrics.size.width-50, height: metrics.size.height * 0.2)
.padding([.leading, .trailing], 50)
}
}
}
}
} else {
@@ -54,6 +74,29 @@ struct ExternalWorkoutDetailView: View {
.scaledToFill()
}
}
.onChange(of: bridgeModule.isInWorkout, perform: { newValue in
if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
if let videoURL = VideoURLCreator.videoURL(
thotStyle: extThotStyle,
defaultVideoURLStr: currentExtercise.exercise.videoURL,
exerciseName: currentExtercise.exercise.name,
workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer = AVPlayer(url: videoURL)
avPlayer.play()
if extShowBothVideos {
if let smallVideoURL = VideoURLCreator.videoURL(
thotStyle: VideoURLCreator.otherVideoType(forVideoURL: videoURL),
defaultVideoURLStr: currentExtercise.exercise.videoURL,
exerciseName: currentExtercise.exercise.name,
workout: bridgeModule.currentExerciseInfo.workout) {
smallAVPlayer = AVPlayer(url: smallVideoURL)
smallAVPlayer.play()
}
}
}
}
})
.onChange(of: bridgeModule.currentExerciseInfo.exerciseIndex, perform: { newValue in
if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
if let videoURL = VideoURLCreator.videoURL(
@@ -63,11 +106,27 @@ struct ExternalWorkoutDetailView: View {
workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer = AVPlayer(url: videoURL)
avPlayer.play()
if extShowBothVideos {
if let smallVideoURL = VideoURLCreator.videoURL(
thotStyle: VideoURLCreator.otherVideoType(forVideoURL: videoURL),
defaultVideoURLStr: currentExtercise.exercise.videoURL,
exerciseName: currentExtercise.exercise.name,
workout: bridgeModule.currentExerciseInfo.workout) {
smallAVPlayer = AVPlayer(url: smallVideoURL)
smallAVPlayer.play()
}
}
}
}
})
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(bridgeModule.currentExerciseInfo.workout == nil ? Color(red: 157/255, green: 138/255, blue: 255/255) : Color(uiColor: .systemBackground))
.onReceive(NotificationCenter.default.publisher(
for: UIScene.willEnterForegroundNotification)) { _ in
avPlayer.play()
smallAVPlayer.play()
}
}
}
@@ -116,17 +175,16 @@ struct ExtExerciseList: View {
.lineLimit(1)
.foregroundColor(.green)
}
Text(supersetExecercise.exercise.name)
.id(exerciseIndex)
.font(Font.system(size: 55))
.minimumScaleFactor(0.01)
.lineLimit(3)
.padding()
.id(exerciseIndex)
Spacer()
}
.id(supersetExecercise.id)
}
}, header: {
HStack {
@@ -148,7 +206,8 @@ struct ExtExerciseList: View {
}
.onChange(of: currentExercise, perform: { newValue in
withAnimation {
proxy.scrollTo(newValue, anchor: .top)
print(newValue.id)
proxy.scrollTo(newValue.id, anchor: .top)
}
})
}
@@ -164,22 +223,13 @@ struct ExtCountdownView: View {
VStack {
if let currenExercise = bridgeModule.currentExerciseInfo.currentExercise {
HStack {
Text(currenExercise.exercise.name)
Text(currenExercise.exercise.extName)
.font(.system(size: 200))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
if currenExercise.exercise.side.count > 0 {
Text(" - " + currenExercise.exercise.side)
.font(.system(size: 200))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
}
if bridgeModule.currentWorkoutRunTimeInSeconds > -1 {
Text("\(Double(bridgeModule.currentWorkoutRunTimeInSeconds).asString(style: .positional))")
.font(Font.system(size: 100))

View File

@@ -82,6 +82,14 @@ struct PlayerView: UIViewRepresentable {
}
class VideoURLCreator {
class func otherVideoType(forVideoURL videoURL: URL) -> ThotStyle {
var otherVideoStyle = ThotStyle.never
if videoURL.absoluteString.contains("exercise_videos") {
otherVideoStyle = .always
}
return otherVideoStyle
}
class func videoURL(thotStyle: ThotStyle, defaultVideoURLStr: String?, exerciseName: String?, workout: Workout?) -> URL? {
var urlString: String?
@@ -102,6 +110,8 @@ class VideoURLCreator {
} else {
urlString = defaultVideoURLStr
}
case .off:
return nil
}
if let urlString = urlString,

View File

@@ -12,6 +12,7 @@ enum ThotStyle: Int, CaseIterable {
case never = 2
case recovery = 3
case random = 4
case off
func stringValue() -> String {
switch(self) {
@@ -23,6 +24,8 @@ enum ThotStyle: Int, CaseIterable {
return "Recovery"
case .random:
return "Random"
case .off:
return "Off"
}
}
}

View File

@@ -13,6 +13,7 @@ struct ExerciseListView: View {
@ObservedObject var bridgeModule = BridgeModule.shared
@State var avPlayer = AVPlayer(url: URL(string: "https://dev.werkout.fitness/media/exercise_videos/2_Dumbbell_Lateral_Lunges.mp4")!)
var workout: Workout
@Binding var showExecersizeInfo: Bool
@State var videoExercise: Exercise? {
didSet {
@@ -36,74 +37,72 @@ struct ExerciseListView: View {
Section(content: {
ForEach(superset.exercises.indices, id: \.self) { exerciseIndex in
let supersetExecercise = superset.exercises[exerciseIndex]
HStack {
if supersetExecercise.id == bridgeModule.currentExerciseInfo.currentExercise?.id {
Image(systemName: "checkmark")
.foregroundColor(.green)
}
Text(supersetExecercise.exercise.name)
.id(exerciseIndex)
Spacer()
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)
VStack {
HStack {
if bridgeModule.isInWorkout &&
supersetIndex == bridgeModule.currentExerciseInfo.supersetIndex &&
exerciseIndex == bridgeModule.currentExerciseInfo.exerciseIndex {
Image(systemName: "checkmark")
.foregroundColor(.green)
}
Text(supersetExecercise.exercise.extName)
Spacer()
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 = supersetExecercise.exercise
}
.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])
if bridgeModule.isInWorkout &&
supersetIndex == bridgeModule.currentExerciseInfo.supersetIndex &&
exerciseIndex == bridgeModule.currentExerciseInfo.exerciseIndex &&
showExecersizeInfo {
detailView(forExercise: supersetExecercise)
}
}
.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()
}
}
}.id(supersetExecercise.id)
}
}, header: {
HStack {
@@ -118,13 +117,36 @@ struct ExerciseListView: View {
})
}
}
.onChange(of: bridgeModule.currentExerciseInfo.exerciseIndex, perform: { newValue in
if let newCurrentExercise = bridgeModule.currentExerciseInfo.currentExercise {
withAnimation {
proxy.scrollTo(newCurrentExercise.id, anchor: .top)
}
}
})
.sheet(item: $videoExercise) { exercise in
PlayerView(player: $avPlayer)
.onAppear{
avPlayer.play()
}
}
}
}
}
func detailView(forExercise supersetExecercise: SupersetExercise) -> some View {
VStack {
Text(supersetExecercise.exercise.description)
.frame(alignment: .leading)
Divider()
Text(supersetExecercise.exercise.muscles.map({ $0.name }).joined(separator: ", "))
.frame(alignment: .leading)
}
}
}
struct ExerciseListView_Previews: PreviewProvider {
static var previews: some View {
ExerciseListView(workout: PreviewData.workout())
}
}
//struct ExerciseListView_Previews: PreviewProvider {
// static var previews: some View {
// ExerciseListView(workout: PreviewData.workout(), showExecersizeInfo: )
// }
//}

View File

@@ -23,6 +23,7 @@ struct WorkoutDetailView: View {
@State var presentedSheet: Sheet?
@State var workoutToPlan: Workout?
@State var showExecersizeInfo: Bool = false
var body: some View {
ZStack {
@@ -39,33 +40,47 @@ struct WorkoutDetailView: View {
.padding()
.frame(maxWidth: .infinity)
GeometryReader { metrics in
ZStack {
PlayerView(player: $avPlayer)
.frame(width: metrics.size.width * 1, height: metrics.size.height * 1)
.onAppear{
avPlayer.play()
}
if phoneThotStyle != .off {
GeometryReader { metrics in
ZStack {
PlayerView(player: $avPlayer)
.frame(width: metrics.size.width * 1, height: metrics.size.height * 1)
.onAppear{
avPlayer.play()
}
}
Button(action: {
if let assetURL = ((avPlayer.currentItem?.asset) as? AVURLAsset)?.url {
if assetURL.absoluteString.lowercased().contains("exercise_videos") {
} else {
}
// avPlayer = AVPlayer(url: videoURL)
// avPlayer.play()
if let assetURL = ((avPlayer.currentItem?.asset) as? AVURLAsset)?.url,
let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise,
let otherVideoURL = VideoURLCreator.videoURL(
thotStyle: VideoURLCreator.otherVideoType(forVideoURL: assetURL),
defaultVideoURLStr: currentExtercise.exercise.videoURL,
exerciseName: currentExtercise.exercise.name,
workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer = AVPlayer(url: otherVideoURL)
avPlayer.play()
}
}, label: {
Text("Toggle THOT")
.padding()
Image(systemName: "arrow.triangle.2.circlepath.camera.fill")
.frame(width: 44, height: 44)
})
.background(.yellow)
.foregroundColor(.blue)
.cornerRadius(4)
.frame(width: 160, height: 60)
.position(x: metrics.size.width - 80, y: metrics.size.height - 30)
.frame(width: 160, height: 120)
.position(x: metrics.size.width - 22, y: metrics.size.height - 30)
Button(action: {
showExecersizeInfo.toggle()
}, label: {
Image(systemName: "info.circle.fill")
.frame(width: 44, height: 44)
})
.foregroundColor(.blue)
.cornerRadius(4)
.frame(width: 120, height: 120)
.position(x: 22, y: metrics.size.height - 30)
}
}
}
@@ -73,7 +88,7 @@ struct WorkoutDetailView: View {
InfoView(workout: workout)
.padding(.bottom)
ExerciseListView(workout: workout)
ExerciseListView(workout: workout, showExecersizeInfo: $showExecersizeInfo)
ActionsView(completedWorkout: {
bridgeModule.completeWorkout()
@@ -112,6 +127,18 @@ struct WorkoutDetailView: View {
}
}
})
.onChange(of: bridgeModule.isInWorkout, perform: { newValue in
if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
if let videoURL = VideoURLCreator.videoURL(
thotStyle: phoneThotStyle,
defaultVideoURLStr: currentExtercise.exercise.videoURL,
exerciseName: currentExtercise.exercise.name,
workout: bridgeModule.currentExerciseInfo.workout) {
avPlayer = AVPlayer(url: videoURL)
avPlayer.play()
}
}
})
.onAppear{
if let currentExtercise = bridgeModule.currentExerciseInfo.currentExercise {
if let videoURL = VideoURLCreator.videoURL(
@@ -131,7 +158,10 @@ struct WorkoutDetailView: View {
}
}
}
.onReceive(NotificationCenter.default.publisher(
for: UIScene.willEnterForegroundNotification)) { _ in
avPlayer.play()
}
}
func createWorkoutData() -> [String:Any]? {

View File

@@ -13,6 +13,7 @@ import AVKit
struct Constants {
static let phoneThotStyle = "phoneThotStyle"
static let extThotStyle = "extThotStyle"
static let extShowBothVideos = "extShowBothVideos"
}
@main

View File

@@ -15,18 +15,17 @@ struct MainWatchView: View {
HStack {
if vm.isInWorkout {
Text(vm.watchPackageModel.currentExerciseName)
.font(Font.system(size: 55))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.font(.body)
.foregroundColor(.white)
.lineLimit(10)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
Divider()
Text("\(vm.watchPackageModel.currentTimeLeft )")
.font(Font.system(size: 55))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.font(.title)
.foregroundColor(.white)
.lineLimit(10)
.fixedSize(horizontal: false, vertical: true)
}
}