This commit is contained in:
Trey t
2023-06-14 21:26:50 -05:00
parent 6ee4a9420b
commit bf58ca5167
16 changed files with 1133 additions and 97 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
## User settings
xcuserdata/
## Xcode 8 and earlier
*.xcscmblueprint
*.xccheckout

11
Werkout-ios-Info.plist Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View File

@@ -10,9 +10,19 @@
1CF65A262A3972840042FFBD /* Werkout_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */; };
1CF65A282A3972840042FFBD /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A272A3972840042FFBD /* Persistence.swift */; };
1CF65A2B2A3972840042FFBD /* Werkout_ios.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A292A3972840042FFBD /* Werkout_ios.xcdatamodeld */; };
1CF65A2D2A3972840042FFBD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A2C2A3972840042FFBD /* ContentView.swift */; };
1CF65A2D2A3972840042FFBD /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A2C2A3972840042FFBD /* MainView.swift */; };
1CF65A2F2A3972850042FFBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A2E2A3972850042FFBD /* Assets.xcassets */; };
1CF65A332A3972850042FFBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A322A3972850042FFBD /* Preview Assets.xcassets */; };
1CF65A3C2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A3B2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift */; };
1CF65A432A39FB410042FFBD /* Workout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A422A39FB410042FFBD /* Workout.swift */; };
1CF65A452A39FB550042FFBD /* Exercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A442A39FB550042FFBD /* Exercise.swift */; };
1CF65A472A39FB6C0042FFBD /* RegisteredUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A462A39FB6C0042FFBD /* RegisteredUser.swift */; };
1CF65A4A2A39FBB10042FFBD /* WorkoutOne.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A492A39FBB10042FFBD /* WorkoutOne.json */; };
1CF65A4C2A39FDA20042FFBD /* WorkoutDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A4B2A39FDA20042FFBD /* WorkoutDetailView.swift */; };
1CF65A4E2A39FF200042FFBD /* WorkoutDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A4D2A39FF200042FFBD /* WorkoutDetailViewModel.swift */; };
1CF65A502A3A1EA90042FFBD /* BridgeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */; };
1CF65A522A3A90A00042FFBD /* PreviewWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A512A3A90A00042FFBD /* PreviewWorkout.swift */; };
1CF65A542A3A9AF30042FFBD /* Straight_Leg_Sit_Up.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A532A3A9A990042FFBD /* Straight_Leg_Sit_Up.mp4 */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -20,10 +30,21 @@
1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Werkout_iosApp.swift; sourceTree = "<group>"; };
1CF65A272A3972840042FFBD /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
1CF65A2A2A3972840042FFBD /* Werkout_ios.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Werkout_ios.xcdatamodel; sourceTree = "<group>"; };
1CF65A2C2A3972840042FFBD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
1CF65A2C2A3972840042FFBD /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
1CF65A2E2A3972850042FFBD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1CF65A302A3972850042FFBD /* Werkout_ios.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Werkout_ios.entitlements; sourceTree = "<group>"; };
1CF65A322A3972850042FFBD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
1CF65A3B2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalWorkoutDetailView.swift; sourceTree = "<group>"; };
1CF65A422A39FB410042FFBD /* Workout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workout.swift; sourceTree = "<group>"; };
1CF65A442A39FB550042FFBD /* Exercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exercise.swift; sourceTree = "<group>"; };
1CF65A462A39FB6C0042FFBD /* RegisteredUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredUser.swift; sourceTree = "<group>"; };
1CF65A492A39FBB10042FFBD /* WorkoutOne.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = WorkoutOne.json; sourceTree = "<group>"; };
1CF65A4B2A39FDA20042FFBD /* WorkoutDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutDetailView.swift; sourceTree = "<group>"; };
1CF65A4D2A39FF200042FFBD /* WorkoutDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutDetailViewModel.swift; sourceTree = "<group>"; };
1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgeModule.swift; sourceTree = "<group>"; };
1CF65A512A3A90A00042FFBD /* PreviewWorkout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewWorkout.swift; sourceTree = "<group>"; };
1CF65A532A3A9A990042FFBD /* Straight_Leg_Sit_Up.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = Straight_Leg_Sit_Up.mp4; sourceTree = "<group>"; };
1CF65A552A3AA6800042FFBD /* Werkout-ios-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Werkout-ios-Info.plist"; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -56,9 +77,14 @@
1CF65A242A3972840042FFBD /* Werkout_ios */ = {
isa = PBXGroup;
children = (
1CF65A552A3AA6800042FFBD /* Werkout-ios-Info.plist */,
1CF65A482A39FB910042FFBD /* JSON */,
1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */,
1CF65A272A3972840042FFBD /* Persistence.swift */,
1CF65A2C2A3972840042FFBD /* ContentView.swift */,
1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */,
1CF65A3F2A3973840042FFBD /* Views */,
1CF65A3E2A39737D0042FFBD /* Models */,
1CF65A3D2A3973760042FFBD /* Network */,
1CF65A2E2A3972850042FFBD /* Assets.xcassets */,
1CF65A302A3972850042FFBD /* Werkout_ios.entitlements */,
1CF65A292A3972840042FFBD /* Werkout_ios.xcdatamodeld */,
@@ -70,11 +96,49 @@
1CF65A312A3972850042FFBD /* Preview Content */ = {
isa = PBXGroup;
children = (
1CF65A532A3A9A990042FFBD /* Straight_Leg_Sit_Up.mp4 */,
1CF65A322A3972850042FFBD /* Preview Assets.xcassets */,
1CF65A512A3A90A00042FFBD /* PreviewWorkout.swift */,
);
path = "Preview Content";
sourceTree = "<group>";
};
1CF65A3D2A3973760042FFBD /* Network */ = {
isa = PBXGroup;
children = (
);
path = Network;
sourceTree = "<group>";
};
1CF65A3E2A39737D0042FFBD /* Models */ = {
isa = PBXGroup;
children = (
1CF65A422A39FB410042FFBD /* Workout.swift */,
1CF65A442A39FB550042FFBD /* Exercise.swift */,
1CF65A462A39FB6C0042FFBD /* RegisteredUser.swift */,
);
path = Models;
sourceTree = "<group>";
};
1CF65A3F2A3973840042FFBD /* Views */ = {
isa = PBXGroup;
children = (
1CF65A2C2A3972840042FFBD /* MainView.swift */,
1CF65A4B2A39FDA20042FFBD /* WorkoutDetailView.swift */,
1CF65A4D2A39FF200042FFBD /* WorkoutDetailViewModel.swift */,
1CF65A3B2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift */,
);
path = Views;
sourceTree = "<group>";
};
1CF65A482A39FB910042FFBD /* JSON */ = {
isa = PBXGroup;
children = (
1CF65A492A39FBB10042FFBD /* WorkoutOne.json */,
);
path = JSON;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -133,7 +197,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1CF65A4A2A39FBB10042FFBD /* WorkoutOne.json in Resources */,
1CF65A332A3972850042FFBD /* Preview Assets.xcassets in Resources */,
1CF65A542A3A9AF30042FFBD /* Straight_Leg_Sit_Up.mp4 in Resources */,
1CF65A2F2A3972850042FFBD /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -146,9 +212,17 @@
buildActionMask = 2147483647;
files = (
1CF65A262A3972840042FFBD /* Werkout_iosApp.swift in Sources */,
1CF65A3C2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift in Sources */,
1CF65A4E2A39FF200042FFBD /* WorkoutDetailViewModel.swift in Sources */,
1CF65A472A39FB6C0042FFBD /* RegisteredUser.swift in Sources */,
1CF65A522A3A90A00042FFBD /* PreviewWorkout.swift in Sources */,
1CF65A4C2A39FDA20042FFBD /* WorkoutDetailView.swift in Sources */,
1CF65A432A39FB410042FFBD /* Workout.swift in Sources */,
1CF65A502A3A1EA90042FFBD /* BridgeModule.swift in Sources */,
1CF65A2B2A3972840042FFBD /* Werkout_ios.xcdatamodeld in Sources */,
1CF65A2D2A3972840042FFBD /* ContentView.swift in Sources */,
1CF65A2D2A3972840042FFBD /* MainView.swift in Sources */,
1CF65A282A3972840042FFBD /* Persistence.swift in Sources */,
1CF65A452A39FB550042FFBD /* Exercise.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -277,6 +351,7 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Werkout-ios-Info.plist";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -315,6 +390,7 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Werkout-ios-Info.plist";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;

View File

@@ -0,0 +1,55 @@
//
// TimerModule.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import Foundation
class BridgeModule: ObservableObject {
static let shared = BridgeModule()
@Published var isShowingOnExternalDisplay = false
private var timer: Timer?
@Published var timeLeft: Int = 0
var timerCompleted: (() -> Void)?
@Published var currentExercise: ExerciseElement?
@Published var currentWorkout: Workout?
@Published var currentExerciseIdx: Int = -1
private func startTimerWith(duration: Int) {
timer?.invalidate()
timer = nil
timeLeft = duration
timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(updateCounter),
userInfo: nil,
repeats: true)
timer?.fire()
}
@objc func updateCounter() {
if timeLeft > 0 {
timeLeft -= 1
} else {
timer?.invalidate()
timer = nil
timerCompleted?()
}
}
func updateCurrent(workout: Workout) {
self.currentWorkout = workout
}
func updateCurrent(exercise: ExerciseElement) {
self.currentExercise = exercise
if let duration = exercise.duration {
startTimerWith(duration: duration)
}
}
}

View File

@@ -1,90 +0,0 @@
//
// ContentView.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/13/23.
//
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -0,0 +1,558 @@
{
"name": "Sample Workou 1",
"description": null,
"exercises": [
{
"workout": 4,
"exercise": {
"id": 790,
"muscles": [
"obliques",
"core"
],
"equipment": [
"Yoga Mat"
],
"audio_url": "/media/exercise_audio/Sprinter_Crunch.m4a",
"video_url": "/media/exercise_videos/Sprinter_Crunch.mp4",
"created_at": "2023-06-11T22:50:19.125036Z",
"updated_at": "2023-06-11T22:50:19.125042Z",
"name": "Sprinter Crunch",
"description": "With arms overhead and feet on the floor, crunch up off the floor and bring your elbow and opposite knee together. Unfold and switch sides",
"side": "",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": true,
"is_weight": false,
"is_distance": false,
"is_duration": true,
"is_reps": true,
"joints_used": "hip,shoulder,knee,lumbar spine,thoracic spine,elbow",
"movement_patterns": "core,core - rotational",
"equipment_required": "Yoga Mat",
"muscle_groups": "obliques,core",
"synonyms": ""
},
"weight": null,
"reps": null,
"duration": 35,
"duration_audio": "/media/quantities_audio/for_35_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-12T02:11:42.925404Z"
},
{
"workout": 4,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "/media/exercise_audio/Recover.m4a",
"video_url": "/media/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": null,
"reps": null,
"duration": 15,
"duration_audio": "/media/quantities_audio/for_15_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-12T02:11:42.929292Z"
},
{
"workout": 4,
"exercise": {
"id": 603,
"muscles": [
"core"
],
"equipment": [
"Yoga Mat"
],
"audio_url": "/media/exercise_audio/High_Plank.m4a",
"video_url": "/media/exercise_videos/High_Plank.mp4",
"created_at": "2023-06-11T22:50:19.053321Z",
"updated_at": "2023-06-11T22:50:19.053328Z",
"name": "High Plank",
"description": "Start with your hands directly under your shoulders while staying on the balls of your feet",
"side": "",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": false,
"is_weight": true,
"is_distance": false,
"is_duration": true,
"is_reps": false,
"joints_used": "shoulder,wrist,elbow,ankle",
"movement_patterns": "core,core - anti-extension",
"equipment_required": "Yoga Mat",
"muscle_groups": "core",
"synonyms": "Front Lean Rest"
},
"weight": null,
"reps": null,
"duration": 35,
"duration_audio": "/media/quantities_audio/for_35_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.946651Z"
},
{
"workout": 4,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "/media/exercise_audio/Recover.m4a",
"video_url": "/media/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": null,
"reps": null,
"duration": 15,
"duration_audio": "/media/quantities_audio/for_15_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.946937Z"
},
{
"workout": 4,
"exercise": {
"id": 142,
"muscles": [
"obliques",
"core",
"forearms"
],
"equipment": [
"Dumbbell"
],
"audio_url": "/media/exercise_audio/Single-Arm_Dumbbell_Suitcase_Carry.m4a",
"video_url": "/media/exercise_videos/Single-Arm_Dumbbell_Suitcase_Carry.mp4",
"created_at": "2023-06-11T22:50:18.866721Z",
"updated_at": "2023-06-11T22:50:18.866728Z",
"name": "Single-Arm Dumbbell Suitcase Carry",
"description": "Hold the dumbbell in your left hand.. Stay tall and keep your core tight,, as you walk forward.. Be careful not to lean to the side as you walk..",
"side": "left_arm",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": false,
"is_weight": true,
"is_distance": true,
"is_duration": true,
"is_reps": false,
"joints_used": "ankle,lumbar spine,elbow,hip,knee,wrist",
"movement_patterns": "core,core - anti-lateral flexion,core - carry",
"equipment_required": "Dumbbell",
"muscle_groups": "obliques,core,forearms",
"synonyms": "Single Arm Dumbbell Suitcase Carry"
},
"weight": 30,
"reps": null,
"duration": 35,
"duration_audio": "/media/quantities_audio/for_35_seconds.m4a",
"weight_audio": "/media/quantities_audio/for_30_pounds.m4a",
"created_at": "2023-06-14T13:28:52.947120Z"
},
{
"workout": 4,
"exercise": {
"id": 370,
"muscles": [
"glutes",
"hamstrings",
"obliques"
],
"equipment": [
"Dumbbell"
],
"audio_url": "/media/exercise_audio/Single-Arm_Dumbbell_Suitcase_Deadlift.m4a",
"video_url": "/media/exercise_videos/Single-Arm_Dumbbell_Suitcase_Deadlift.mp4",
"created_at": "2023-06-11T22:50:18.961690Z",
"updated_at": "2023-06-11T22:50:18.961696Z",
"name": "Single-Arm Dumbbell Suitcase Deadlift",
"description": "Start with your feet just outside your hips,, and the dumbbell in your right hand.. Hinge your hips back,, and let your shoulders forward to lower the weight.. Push through the ground to stand up..",
"side": "right_arm",
"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": "ankle,lumbar spine,elbow,hip,knee",
"movement_patterns": "lower pull,lower pull - hip hinge",
"equipment_required": "Dumbbell",
"muscle_groups": "glutes,hamstrings,obliques",
"synonyms": "Single Arm Dumbbell Suitcase Deadlift"
},
"weight": 30,
"reps": null,
"duration": 35,
"duration_audio": "/media/quantities_audio/for_35_seconds.m4a",
"weight_audio": "/media/quantities_audio/for_30_pounds.m4a",
"created_at": "2023-06-14T13:28:52.947298Z"
},
{
"workout": 4,
"exercise": {
"id": 1012,
"muscles": [
"core",
"hip adductors"
],
"equipment": [
"Yoga Mat"
],
"audio_url": "/media/exercise_audio/Scissors.m4a",
"video_url": "/media/exercise_videos/Scissors.mp4",
"created_at": "2023-06-11T22:50:19.204261Z",
"updated_at": "2023-06-11T22:50:19.204267Z",
"name": "Scissors",
"description": "",
"side": "",
"is_two_dumbbells": false,
"is_trackable_distance": false,
"is_alternating": true,
"is_weight": false,
"is_distance": false,
"is_duration": true,
"is_reps": true,
"joints_used": "lumbar spine,hip",
"movement_patterns": "core",
"equipment_required": "Yoga Mat",
"muscle_groups": "core,hip adductors",
"synonyms": ""
},
"weight": null,
"reps": null,
"duration": 35,
"duration_audio": "/media/quantities_audio/for_35_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.947467Z"
},
{
"workout": 4,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "/media/exercise_audio/Recover.m4a",
"video_url": "/media/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": null,
"reps": null,
"duration": 20,
"duration_audio": "/media/quantities_audio/for_20_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.947631Z"
},
{
"workout": 4,
"exercise": {
"id": 495,
"muscles": [
"glutes",
"quads"
],
"equipment": [
"Yoga Mat"
],
"audio_url": "/media/exercise_audio/Chair_Pose.m4a",
"video_url": "/media/exercise_videos/Chair_Pose.mp4",
"created_at": "2023-06-11T22:50:19.010855Z",
"updated_at": "2023-06-11T22:50:19.010862Z",
"name": "Chair Pose",
"description": "",
"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": "ankle,hip,knee,shoulder",
"movement_patterns": "yoga",
"equipment_required": "Yoga Mat",
"muscle_groups": "glutes,quads",
"synonyms": ""
},
"weight": null,
"reps": null,
"duration": 30,
"duration_audio": "/media/quantities_audio/for_30_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.947793Z"
},
{
"workout": 4,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "/media/exercise_audio/Recover.m4a",
"video_url": "/media/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": null,
"reps": null,
"duration": 10,
"duration_audio": "/media/quantities_audio/for_10_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.947967Z"
},
{
"workout": 4,
"exercise": {
"id": 448,
"muscles": [
"upper back",
"rotator cuff"
],
"equipment": [
"Wall"
],
"audio_url": "/media/exercise_audio/Wall_Slide_with_Lift_Off.m4a",
"video_url": "/media/exercise_videos/Wall_Slide_with_Lift_Off.mp4",
"created_at": "2023-06-11T22:50:18.992528Z",
"updated_at": "2023-06-11T22:50:18.992535Z",
"name": "Wall Slide with Lift Off",
"description": "Face a wall with the base of your hand touching the wall and palms facing each other. Slide your hands up the wall while maintaining the same back and forearm position. At the top of the movement, lift your hands off the wall. Replace your hands back on the wall before sliding back down.",
"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,elbow",
"movement_patterns": "mobility,mobility - dynamic",
"equipment_required": "Wall",
"muscle_groups": "upper back,rotator cuff",
"synonyms": ""
},
"weight": null,
"reps": null,
"duration": 30,
"duration_audio": "/media/quantities_audio/for_30_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.948128Z"
},
{
"workout": 4,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "/media/exercise_audio/Recover.m4a",
"video_url": "/media/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": null,
"reps": null,
"duration": 10,
"duration_audio": "/media/quantities_audio/for_10_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.948291Z"
},
{
"workout": 4,
"exercise": {
"id": 1045,
"muscles": [
"glutes",
"quads",
"hip flexor"
],
"equipment": [
"Yoga Mat"
],
"audio_url": "/media/exercise_audio/Crescent_Lunge.m4a",
"video_url": "/media/exercise_videos/Crescent_Lunge.mp4",
"created_at": "2023-06-11T22:50:19.216136Z",
"updated_at": "2023-06-11T22:50:19.216142Z",
"name": "Crescent Lunge",
"description": "",
"side": "left_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": "ankle,hip,knee,shoulder",
"movement_patterns": "yoga,mobility - static,lower push - lunge",
"equipment_required": "Yoga Mat",
"muscle_groups": "glutes,quads,hip flexor",
"synonyms": ""
},
"weight": null,
"reps": null,
"duration": 30,
"duration_audio": "/media/quantities_audio/for_30_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.948449Z"
},
{
"workout": 4,
"exercise": {
"id": 1047,
"muscles": [
"glutes",
"quads",
"hip flexor"
],
"equipment": [
"Yoga Mat"
],
"audio_url": "/media/exercise_audio/Crescent_Lunge.m4a",
"video_url": "/media/exercise_videos/Crescent_Lunge.mp4",
"created_at": "2023-06-11T22:50:19.216834Z",
"updated_at": "2023-06-11T22:50:19.216840Z",
"name": "Crescent Lunge",
"description": "",
"side": "right_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": "ankle,hip,knee,shoulder",
"movement_patterns": "yoga,mobility - static,lower push - lunge",
"equipment_required": "Yoga Mat",
"muscle_groups": "glutes,quads,hip flexor",
"synonyms": ""
},
"weight": null,
"reps": null,
"duration": 30,
"duration_audio": "/media/quantities_audio/for_30_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.948594Z"
},
{
"workout": 4,
"exercise": {
"id": 798,
"muscles": [],
"equipment": [],
"audio_url": "/media/exercise_audio/Recover.m4a",
"video_url": "/media/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": null,
"reps": null,
"duration": 10,
"duration_audio": "/media/quantities_audio/for_10_seconds.m4a",
"weight_audio": null,
"created_at": "2023-06-14T13:28:52.948713Z"
}
],
"registered_user": [
{
"id": 1,
"first_name": "test1_fist",
"last_name": "test1_last",
"image": "",
"nick_name": null
}
]
}

View File

@@ -0,0 +1,72 @@
//
// Exercise.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import Foundation
struct ExerciseElement: Codable {
let workout: Int
let exercise: ExerciseExercise
let weight: Int?
let reps: Int?
let duration: Int?
let durationAudio: String?
let weightAudio: String?
let createdAt: String?
enum CodingKeys: String, CodingKey {
case workout, exercise, weight, reps, duration
case durationAudio = "duration_audio"
case weightAudio = "weight_audio"
case createdAt = "created_at"
}
var createdAtDate: Date {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
df.locale = Locale(identifier: "en_US_POSIX")
return df.date(from: self.createdAt ?? "") ?? Date()
}
}
struct ExerciseExercise: Codable {
let id: Int
let muscles, equipment: [String]?
let videoURL, audioURL, createdAt, updatedAt, name: String?
let description, side: String?
let isTwoDumbbells, isTrackableDistance, isAlternating, isWeight: Bool?
let isDistance, isDuration, isReps: Bool?
let jointsUsed, movementPatterns, equipmentRequired, muscleGroups: String?
let synonyms: String?
enum CodingKeys: String, CodingKey {
case id, muscles, equipment
case videoURL = "video_url"
case audioURL = "audio_url"
case createdAt = "created_at"
case updatedAt = "updated_at"
case name, description, side
case isTwoDumbbells = "is_two_dumbbells"
case isTrackableDistance = "is_trackable_distance"
case isAlternating = "is_alternating"
case isWeight = "is_weight"
case isDistance = "is_distance"
case isDuration = "is_duration"
case isReps = "is_reps"
case jointsUsed = "joints_used"
case movementPatterns = "movement_patterns"
case equipmentRequired = "equipment_required"
case muscleGroups = "muscle_groups"
case synonyms
}
var createdAtDate: Date {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
df.locale = Locale(identifier: "en_US_POSIX")
return df.date(from: self.createdAt ?? "") ?? Date()
}
}

View File

@@ -0,0 +1,22 @@
//
// RegisteredUser.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import Foundation
struct RegisteredUser: Codable {
let id: Int
let firstName, lastName, image: String?
let nickName: String?
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
case image
case nickName = "nick_name"
}
}

View File

@@ -0,0 +1,26 @@
//
// Workout.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import Foundation
struct Workout: Codable {
let name: String
let description: String?
let exercises: [ExerciseElement]
let registeredUser: [RegisteredUser]
enum CodingKeys: String, CodingKey {
case name, description, exercises
case registeredUser = "registered_user"
}
var exercisesSortedByCreated_at: [ExerciseElement] {
return self.exercises.sorted(by: {
$0.createdAtDate < $1.createdAtDate
})
}
}

View File

@@ -0,0 +1,17 @@
//
// PreviewWorkout.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import Foundation
class PreviewWorkout {
class func workout() -> Workout {
let filepath = Bundle.main.path(forResource: "WorkoutOne", ofType: "json")!
let data = try! Data(NSData(contentsOfFile: filepath))
let workout = try! JSONDecoder().decode(Workout.self, from: data)
return workout
}
}

Binary file not shown.

View File

@@ -0,0 +1,105 @@
//
// ExternalView.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/13/23.
//
import SwiftUI
import AVKit
struct ExternalWorkoutDetailView: View {
@EnvironmentObject var bridgeModule: BridgeModule
@State var player = AVPlayer()
var body: some View {
if let workout = bridgeModule.currentWorkout {
GeometryReader { metrics in
VStack {
Text(workout.name)
.font(Font.system(size: 100))
.frame(width: metrics.size.width, height: metrics.size.height * 0.1)
HStack {
VideoPlayer(player: player)
.onChange(of: bridgeModule.currentExerciseIdx, perform: { newValue in
updateVideo()
})
if let workout = bridgeModule.currentWorkout {
List() {
ForEach(workout.exercisesSortedByCreated_at.indices, id: \.self) { i in
let obj = workout.exercisesSortedByCreated_at[i]
HStack {
if let _ = bridgeModule.currentExercise {
if i == bridgeModule.currentExerciseIdx {
Image(systemName: "checkmark")
.font(Font.system(size: 75))
.foregroundColor(.green)
}
}
Text(obj.exercise.name ?? "")
.font(Font.system(size: 75))
.padding()
}
}
}
.frame(width: metrics.size.width * 0.4)
}
}
.frame(width: metrics.size.width, height: metrics.size.height * 0.7)
HStack {
if let currenExercise = bridgeModule.currentExercise {
VStack {
Text(currenExercise.exercise.name ?? "")
.font(Font.system(size: 100))
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
if let duration = currenExercise.duration {
ProgressView(value: Float(bridgeModule.timeLeft), total: Float(duration))
.scaleEffect(x: 1, y: 6, anchor: .center)
Text("\(bridgeModule.timeLeft)")
.font(Font.system(size: 75))
.padding(.leading)
} else if let reps = currenExercise.reps {
Text("\(reps)")
}
}
.padding([.leading, .trailing], 50)
}
}
}
.frame(width: metrics.size.width, height: metrics.size.height * 0.2)
.padding([.leading, .trailing])
}
}
}
}
func updateVideo() {
if let videoURL = bridgeModule.currentExercise?.exercise.videoURL {
// let completeURL = "http://127.0.0.1:8000" + videoURL
player = AVPlayer(url: Bundle.main.url(forResource: "Straight_Leg_Sit_Up", withExtension: "mp4")!)
player.play()
// print(completeURL)
// player = AVPlayer(url: URL(string: completeURL)!)
// player.play()
}
}
}
struct ExternalWorkoutDetailView_Previews: PreviewProvider {
static var bridge = BridgeModule.shared
static var previews: some View {
ExternalWorkoutDetailView().environmentObject({ () -> BridgeModule in
let envObj = BridgeModule.shared
envObj.currentWorkout = PreviewWorkout.workout()
bridge.currentExercise = PreviewWorkout.workout().exercisesSortedByCreated_at.first!
return envObj
}() )
}
}

View File

@@ -0,0 +1,49 @@
//
// ContentView.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/13/23.
//
import SwiftUI
import CoreData
struct MainView: View {
@State var workout: Workout?
@EnvironmentObject var bridgeModule: BridgeModule
var body: some View {
ZStack {
if let workout = workout {
let vm = WorkoutDetailViewModel(workout: workout)
WorkoutDetailView(viewModel: vm)
} else {
Text("no workout selected")
}
}.onAppear{
testParse()
}
}
func testParse() {
if let filepath = Bundle.main.path(forResource: "WorkoutOne", ofType: "json") {
do {
let data = try Data(NSData(contentsOfFile: filepath))
let workout = try JSONDecoder().decode(Workout.self, from: data)
bridgeModule.currentWorkout = workout
self.workout = workout
} catch {
print(error)
fatalError()
}
} else {
fatalError()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MainView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -0,0 +1,63 @@
//
// MainView.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import SwiftUI
struct WorkoutDetailView: View {
@ObservedObject var viewModel: WorkoutDetailViewModel
@EnvironmentObject var bridgeModule: BridgeModule
@State var selectedIdx = -1 {
didSet {
runItemAt(idx: selectedIdx)
}
}
var body: some View {
VStack {
List() {
ForEach(viewModel.workout.exercisesSortedByCreated_at.indices, id: \.self) { i in
let obj = viewModel.workout.exercisesSortedByCreated_at[i]
Text(obj.exercise.name ?? "")
.onTapGesture { selectedIdx = i }
}
}
if let duration = bridgeModule.currentExercise?.duration {
HStack {
ProgressView(value: Float(bridgeModule.timeLeft), total: Float(duration))
Text("\(bridgeModule.timeLeft)")
}.padding(16)
}
}
.onAppear{
bridgeModule.timerCompleted = {
selectedIdx += 1
}
}
}
func runItemAt(idx: Int) {
if idx < viewModel.workout.exercises.count {
let exercise = viewModel.workout.exercises[idx]
bridgeModule.updateCurrent(exercise: exercise)
bridgeModule.currentExerciseIdx = idx
} else {
workoutComplete()
}
}
private func workoutComplete() {
}
}
struct WorkoutDetailView_Previews: PreviewProvider {
static var previews: some View {
WorkoutDetailView(viewModel: WorkoutDetailViewModel(workout: PreviewWorkout.workout()))
}
}

View File

@@ -0,0 +1,17 @@
//
// MainViewViewModel.swift
// Werkout_ios
//
// Created by Trey Tartt on 6/14/23.
//
import Combine
import Foundation
class WorkoutDetailViewModel: ObservableObject {
@Published var workout: Workout
init(workout: Workout) {
self.workout = workout
}
}

View File

@@ -6,15 +6,63 @@
//
import SwiftUI
import Combine
@main
struct Werkout_iosApp: App {
let persistenceController = PersistenceController.shared
@ObservedObject var bridgeModule = BridgeModule.shared
@State var additionalWindows: [UIWindow] = []
private var screenDidConnectPublisher: AnyPublisher<UIScreen, Never> {
NotificationCenter.default
.publisher(for: UIScreen.didConnectNotification)
.compactMap { $0.object as? UIScreen }
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
private var screenDidDisconnectPublisher: AnyPublisher<UIScreen, Never> {
NotificationCenter.default
.publisher(for: UIScreen.didDisconnectNotification)
.compactMap { $0.object as? UIScreen }
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
MainView()
.environmentObject(bridgeModule)
.onReceive(
screenDidConnectPublisher,
perform: screenDidConnect
)
.onReceive(
screenDidDisconnectPublisher,
perform: screenDidDisconnect
)
}
}
private func screenDidDisconnect(_ screen: UIScreen) {
additionalWindows.removeAll { $0.screen == screen }
bridgeModule.isShowingOnExternalDisplay = false
}
private func screenDidConnect(_ screen: UIScreen) {
let window = UIWindow(frame: screen.bounds)
window.windowScene = UIApplication.shared.connectedScenes
.first { ($0 as? UIWindowScene)?.screen == screen }
as? UIWindowScene
let view = ExternalWorkoutDetailView()
.environmentObject(bridgeModule)
let controller = UIHostingController(rootView: view)
window.rootViewController = controller
window.isHidden = false
additionalWindows.append(window)
bridgeModule.isShowingOnExternalDisplay = true
}
}