WIP
This commit is contained in:
@@ -8,8 +8,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import WatchConnectivity
|
import WatchConnectivity
|
||||||
|
|
||||||
enum WatchActions: String, Codable {
|
enum WatchActions: Codable {
|
||||||
case nextExercise
|
case nextExercise
|
||||||
|
case workoutComplete(Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
class BridgeModule: NSObject, ObservableObject {
|
class BridgeModule: NSObject, ObservableObject {
|
||||||
@@ -26,11 +27,17 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
var currentExerciseIdx: Int = -1
|
var currentExerciseIdx: Int = -1
|
||||||
|
|
||||||
var workoutStartDate: Date?
|
var workoutStartDate: Date?
|
||||||
|
|
||||||
|
// workoutEndDate ties into WatchPackageModel.workoutEndDate which
|
||||||
|
// tells the watch app to stop the workout
|
||||||
var workoutEndDate: Date?
|
var workoutEndDate: Date?
|
||||||
@Published var currentWorkoutRunTimeInSeconds: Int = -1
|
@Published var currentWorkoutRunTimeInSeconds: Int = -1
|
||||||
private var currentWorkoutRunTimer: Timer?
|
private var currentWorkoutRunTimer: Timer?
|
||||||
|
|
||||||
@Published var isInWorkout = false
|
@Published var isInWorkout = false
|
||||||
|
var completedWorkoutFromWatch: (() -> Void)?
|
||||||
|
var totalCaloire: Float?
|
||||||
|
var heartRates: [Int]?
|
||||||
|
|
||||||
func start(workout: Workout, atExerciseIndex: Int = 0) {
|
func start(workout: Workout, atExerciseIndex: Int = 0) {
|
||||||
self.currentWorkout = workout
|
self.currentWorkout = workout
|
||||||
@@ -64,15 +71,14 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
|
|
||||||
currentExercise = nil
|
currentExercise = nil
|
||||||
currentWorkout = nil
|
currentWorkout = nil
|
||||||
|
|
||||||
if isInWorkout {
|
|
||||||
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: timeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date())
|
|
||||||
let data = try! JSONEncoder().encode(watchModel)
|
|
||||||
send(data)
|
|
||||||
}
|
|
||||||
isInWorkout = false
|
isInWorkout = false
|
||||||
workoutStartDate = nil
|
workoutStartDate = nil
|
||||||
workoutEndDate = nil
|
workoutEndDate = nil
|
||||||
|
|
||||||
|
let watchModel = WatchPackageModel(currentExerciseName: "", currentTimeLeft: -100, workoutStartDate: Date())
|
||||||
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
|
send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startWorkoutTimer() {
|
private func startWorkoutTimer() {
|
||||||
@@ -140,11 +146,22 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension BridgeModule: WCSessionDelegate {
|
extension BridgeModule: WCSessionDelegate {
|
||||||
|
func sendWorkoutCompleteToWatch() {
|
||||||
|
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: timeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date())
|
||||||
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
|
send(data)
|
||||||
|
}
|
||||||
|
|
||||||
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
||||||
if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) {
|
if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) {
|
||||||
switch model {
|
switch model {
|
||||||
case .nextExercise:
|
case .nextExercise:
|
||||||
nextExercise()
|
nextExercise()
|
||||||
|
case .workoutComplete(let data):
|
||||||
|
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
||||||
|
totalCaloire = Float(model.totalBurnedEnergery)
|
||||||
|
heartRates = model.allHeartRates
|
||||||
|
completedWorkoutFromWatch?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
completed workout view
|
-completed workout view
|
||||||
add notes and slider for difficulty
|
- add notes and slider for difficulty
|
||||||
|
|
||||||
|
-apple watch
|
||||||
|
|
||||||
account view
|
account view
|
||||||
workout history view
|
workout history view
|
||||||
apple watch
|
|
||||||
calorie upload
|
calorie upload
|
||||||
edit weights on workouts
|
|
||||||
video view on iphone during workout
|
video view on iphone during workout
|
||||||
|
|
||||||
|
edit weights on workouts
|
||||||
|
|||||||
@@ -19,37 +19,18 @@ struct CompletedWorkoutView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Text(workout.name)
|
topViews()
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.font(.title3)
|
|
||||||
.padding(.top
|
|
||||||
)
|
|
||||||
if let desc = workout.description {
|
|
||||||
Text(desc)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.font(.body)
|
|
||||||
.padding(.top)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
calsBurned()
|
||||||
|
|
||||||
Text("how hard was this shit")
|
heartRates()
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("easy")
|
|
||||||
Spacer()
|
|
||||||
Text("Death")
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider(value: $difficulty, in: 0...5, step: 1)
|
|
||||||
|
|
||||||
|
rateWorkout()
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
TextField("Notes", text: $notes)
|
TextField("Notes", text: $notes)
|
||||||
|
|
||||||
// Divider()
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button("Upload", action: {
|
Button("Upload", action: {
|
||||||
@@ -66,6 +47,57 @@ struct CompletedWorkoutView: View {
|
|||||||
.padding([.leading, .trailing])
|
.padding([.leading, .trailing])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func topViews() -> some View {
|
||||||
|
VStack {
|
||||||
|
Text(workout.name)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.font(.title3)
|
||||||
|
.padding(.top
|
||||||
|
)
|
||||||
|
if let desc = workout.description {
|
||||||
|
Text(desc)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.font(.body)
|
||||||
|
.padding(.top)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calsBurned() -> some View {
|
||||||
|
VStack {
|
||||||
|
Divider()
|
||||||
|
Text("calroies burned")
|
||||||
|
Text("\(postData["total_calories"] as! Float)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rateWorkout() -> some View {
|
||||||
|
VStack {
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Text("how hard was this shit")
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("easy")
|
||||||
|
Spacer()
|
||||||
|
Text("Death")
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: $difficulty, in: 0...5, step: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func heartRates() -> some View {
|
||||||
|
VStack {
|
||||||
|
Divider()
|
||||||
|
if let heartRates = postData["heart_rates"] as? [Int] {
|
||||||
|
let avg = heartRates.reduce(0, +)/heartRates.count
|
||||||
|
Text("Avg heart rate: \(avg)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func upload(postBody: [String: Any]) {
|
func upload(postBody: [String: Any]) {
|
||||||
var _postBody = postBody
|
var _postBody = postBody
|
||||||
_postBody["difficulty"] = difficulty
|
_postBody["difficulty"] = difficulty
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ struct WorkoutDetailView: View {
|
|||||||
ExerciseListView(workout: workout)
|
ExerciseListView(workout: workout)
|
||||||
ActionsView(completedWorkout: {
|
ActionsView(completedWorkout: {
|
||||||
bridgeModule.workoutEndDate = Date()
|
bridgeModule.workoutEndDate = Date()
|
||||||
if let workoutData = createWorkoutData() {
|
bridgeModule.sendWorkoutCompleteToWatch()
|
||||||
presentedSheet = .completedWorkout(workoutData)
|
|
||||||
bridgeModule.resetCurrentWorkout()
|
|
||||||
}
|
|
||||||
}, workout: workout)
|
}, workout: workout)
|
||||||
.frame(height: 44)
|
.frame(height: 44)
|
||||||
|
|
||||||
@@ -55,6 +52,14 @@ struct WorkoutDetailView: View {
|
|||||||
.interactiveDismissDisabled()
|
.interactiveDismissDisabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear{
|
||||||
|
bridgeModule.completedWorkoutFromWatch = {
|
||||||
|
if let workoutData = createWorkoutData() {
|
||||||
|
presentedSheet = .completedWorkout(workoutData)
|
||||||
|
bridgeModule.resetCurrentWorkout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWorkoutData() -> [String:Any]? {
|
func createWorkoutData() -> [String:Any]? {
|
||||||
@@ -68,7 +73,9 @@ struct WorkoutDetailView: View {
|
|||||||
"workout_start_time": startTime,
|
"workout_start_time": startTime,
|
||||||
"workout_end_time": endTime,
|
"workout_end_time": endTime,
|
||||||
"workout": workoutid,
|
"workout": workoutid,
|
||||||
"total_time": bridgeModule.currentWorkoutRunTimeInSeconds
|
"total_time": bridgeModule.currentWorkoutRunTimeInSeconds,
|
||||||
|
"total_calories": bridgeModule.totalCaloire ?? -1,
|
||||||
|
"heart_rates": bridgeModule.heartRates ?? [Int]()
|
||||||
] as [String : Any]
|
] as [String : Any]
|
||||||
|
|
||||||
return postBody
|
return postBody
|
||||||
|
|||||||
@@ -13,3 +13,8 @@ struct WatchPackageModel: Codable {
|
|||||||
var workoutStartDate: Date
|
var workoutStartDate: Date
|
||||||
var workoutEndDate: Date?
|
var workoutEndDate: Date?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WatchFinishWorkoutModel: Codable {
|
||||||
|
var totalBurnedEnergery: Double
|
||||||
|
var allHeartRates: [Int]
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,12 +16,9 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
@Published var heartValue: Int?
|
@Published var heartValue: Int?
|
||||||
|
|
||||||
let healthStore = HKHealthStore()
|
let healthStore = HKHealthStore()
|
||||||
var hkWorkoutSession: HKWorkoutSession? {
|
var hkWorkoutSession: HKWorkoutSession?
|
||||||
didSet {
|
|
||||||
print("here")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var hkBuilder: HKLiveWorkoutBuilder?
|
var hkBuilder: HKLiveWorkoutBuilder?
|
||||||
|
var heartRates = [Int]()
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
session = WCSession.default
|
session = WCSession.default
|
||||||
@@ -71,6 +68,10 @@ extension WatchMainViewModel: WCSessionDelegate {
|
|||||||
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
||||||
if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) {
|
if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
if model.currentTimeLeft == -100 {
|
||||||
|
self.watchPackageModel = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
if self.watchPackageModel?.workoutEndDate != nil {
|
if self.watchPackageModel?.workoutEndDate != nil {
|
||||||
self.watchPackageModel = nil
|
self.watchPackageModel = nil
|
||||||
self.stopWorkout()
|
self.stopWorkout()
|
||||||
@@ -157,6 +158,14 @@ extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDele
|
|||||||
DispatchQueue.main.async() {
|
DispatchQueue.main.async() {
|
||||||
self.hkWorkoutSession = nil
|
self.hkWorkoutSession = nil
|
||||||
self.hkBuilder = nil
|
self.hkBuilder = nil
|
||||||
|
let totalEnergy = workout?.totalEnergyBurned?.doubleValue(for: .kilocalorie()) ?? -1
|
||||||
|
let watchFinishWorkoutModel = WatchFinishWorkoutModel(totalBurnedEnergery: totalEnergy, allHeartRates: self.heartRates)
|
||||||
|
let data = try! JSONEncoder().encode(watchFinishWorkoutModel)
|
||||||
|
let watchAction = WatchActions.workoutComplete(data)
|
||||||
|
let watchActionData = try! JSONEncoder().encode(watchAction)
|
||||||
|
self.send(watchActionData)
|
||||||
|
|
||||||
|
self.heartRates.removeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,6 +191,7 @@ extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDele
|
|||||||
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
|
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
|
||||||
let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
|
let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
|
||||||
self.heartValue = Int(Double(round(1 * value!) / 1))
|
self.heartValue = Int(Double(round(1 * value!) / 1))
|
||||||
|
self.heartRates.append(Int(Double(round(1 * value!) / 1)))
|
||||||
print("[workoutBuilder] Heart Rate: \(String(describing: self.heartValue))")
|
print("[workoutBuilder] Heart Rate: \(String(describing: self.heartValue))")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user