init
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## Xcode 8 and earlier
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
11
Werkout-ios-Info.plist
Normal file
11
Werkout-ios-Info.plist
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
55
Werkout_ios/BridgeModule.swift
Normal file
55
Werkout_ios/BridgeModule.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
558
Werkout_ios/JSON/WorkoutOne.json
Normal file
558
Werkout_ios/JSON/WorkoutOne.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
72
Werkout_ios/Models/Exercise.swift
Normal file
72
Werkout_ios/Models/Exercise.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
22
Werkout_ios/Models/RegisteredUser.swift
Normal file
22
Werkout_ios/Models/RegisteredUser.swift
Normal 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"
|
||||
}
|
||||
}
|
||||
26
Werkout_ios/Models/Workout.swift
Normal file
26
Werkout_ios/Models/Workout.swift
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
17
Werkout_ios/Preview Content/PreviewWorkout.swift
Normal file
17
Werkout_ios/Preview Content/PreviewWorkout.swift
Normal 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
|
||||
}
|
||||
}
|
||||
BIN
Werkout_ios/Preview Content/Straight_Leg_Sit_Up.mp4
Normal file
BIN
Werkout_ios/Preview Content/Straight_Leg_Sit_Up.mp4
Normal file
Binary file not shown.
105
Werkout_ios/Views/ExternalWorkoutDetailView.swift
Normal file
105
Werkout_ios/Views/ExternalWorkoutDetailView.swift
Normal 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
|
||||
}() )
|
||||
}
|
||||
}
|
||||
49
Werkout_ios/Views/MainView.swift
Normal file
49
Werkout_ios/Views/MainView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
63
Werkout_ios/Views/WorkoutDetailView.swift
Normal file
63
Werkout_ios/Views/WorkoutDetailView.swift
Normal 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()))
|
||||
}
|
||||
}
|
||||
17
Werkout_ios/Views/WorkoutDetailViewModel.swift
Normal file
17
Werkout_ios/Views/WorkoutDetailViewModel.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user