From 0b477bc182e5804e2560093b603f334e5cbecf5e Mon Sep 17 00:00:00 2001 From: Trey t Date: Sat, 8 Jul 2023 11:48:14 -0500 Subject: [PATCH] WIP --- Werkout_ios/BridgeModule.swift | 68 ++++--- Werkout_watch Watch App/ContentView.swift | 3 +- Werkout_watch Watch App/MainWatchView.swift | 29 +-- .../WatchControlView.swift | 12 +- .../WatchMainViewModel.swift | 177 ++++++++++-------- 5 files changed, 168 insertions(+), 121 deletions(-) diff --git a/Werkout_ios/BridgeModule.swift b/Werkout_ios/BridgeModule.swift index 149ac55..33865bd 100644 --- a/Werkout_ios/BridgeModule.swift +++ b/Werkout_ios/BridgeModule.swift @@ -18,6 +18,12 @@ enum WatchActions: Codable { case workoutComplete(Data) } +enum PhoneToWatchActions: Codable { + case inExercise(WatchPackageModel) + case reset + case endWorkout +} + class BridgeModule: NSObject, ObservableObject { private let kMessageKey = "message" @@ -100,10 +106,7 @@ class BridgeModule: NSObject, ObservableObject { self.workoutStartDate = nil self.workoutEndDate = nil } - - let watchModel = WatchPackageModel(currentExerciseName: "", currentTimeLeft: -100, workoutStartDate: Date()) - let data = try! JSONEncoder().encode(watchModel) - send(data) + sendResetToWatch() } private func startWorkoutTimer() { @@ -134,11 +137,7 @@ class BridgeModule: NSObject, ObservableObject { @objc func updateCurrentExerciseTimer() { if currentExerciseTimeLeft > 0 { currentExerciseTimeLeft -= 1 - - let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date()) - let data = try! JSONEncoder().encode(watchModel) - send(data) - + if currentExerciseTimeLeft == 0 { playFinished() } else { @@ -146,6 +145,7 @@ class BridgeModule: NSObject, ObservableObject { playBeep() } } + sendCurrentExerciseToWatch() } else { nextExercise() } @@ -209,25 +209,16 @@ class BridgeModule: NSObject, ObservableObject { if let duration = exercise.duration, duration > 0 { - print(duration) self.startExerciseTimerWith(duration: duration) - } else { - var intWatchDispaly = -1 - if let reps = self.currentExercise?.reps, - reps > 0 { - intWatchDispaly = reps - } - - // if not a timer we need to set the watch display with number of reps - // if timer it will set when timer updates - let watchModel = WatchPackageModel(currentExerciseName: self.currentExercise?.exercise.name ?? "-", currentTimeLeft: intWatchDispaly, workoutStartDate: self.workoutStartDate ?? Date()) - let data = try! JSONEncoder().encode(watchModel) - self.send(data) } + self.sendCurrentExerciseToWatch() } } func completeWorkout() { + self.currentExerciseTimer?.invalidate() + self.currentExerciseTimer = nil + workoutEndDate = Date() //if connected to watch @@ -276,12 +267,41 @@ class BridgeModule: NSObject, ObservableObject { } extension BridgeModule: WCSessionDelegate { - func sendWorkoutCompleteToWatch() { - let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date()) + func sendResetToWatch() { + let watchModel = PhoneToWatchActions.reset let data = try! JSONEncoder().encode(watchModel) send(data) } + func sendWorkoutCompleteToWatch() { + let model = PhoneToWatchActions.endWorkout + let data = try! JSONEncoder().encode(model) + send(data) + } + + func sendCurrentExerciseToWatch() { + if let duration = currentExercise?.duration, + duration > 0 { + let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date()) + let model = PhoneToWatchActions.inExercise(watchModel) + let data = try! JSONEncoder().encode(model) + send(data) + } else { + var intWatchDispaly = -1 + if let reps = self.currentExercise?.reps, + reps > 0 { + intWatchDispaly = reps + } + + // if not a timer we need to set the watch display with number of reps + // if timer it will set when timer updates + let watchModel = WatchPackageModel(currentExerciseName: self.currentExercise?.exercise.name ?? "-", currentTimeLeft: intWatchDispaly, workoutStartDate: self.workoutStartDate ?? Date()) + let model = PhoneToWatchActions.inExercise(watchModel) + let data = try! JSONEncoder().encode(model) + self.send(data) + } + } + func session(_ session: WCSession, didReceiveMessageData messageData: Data) { if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) { switch model { diff --git a/Werkout_watch Watch App/ContentView.swift b/Werkout_watch Watch App/ContentView.swift index a3db4be..7de7484 100644 --- a/Werkout_watch Watch App/ContentView.swift +++ b/Werkout_watch Watch App/ContentView.swift @@ -6,14 +6,13 @@ // import SwiftUI -import WatchKit struct ContentView: View { + var body: some View { TabView { MainWatchView() WatchControlView() - NowPlayingView() } .padding() .tabViewStyle(PageTabViewStyle()) diff --git a/Werkout_watch Watch App/MainWatchView.swift b/Werkout_watch Watch App/MainWatchView.swift index a88961b..314e9f5 100644 --- a/Werkout_watch Watch App/MainWatchView.swift +++ b/Werkout_watch Watch App/MainWatchView.swift @@ -8,20 +8,20 @@ import SwiftUI struct MainWatchView: View { - @StateObject var vm = WatchMainViewModel() + @StateObject var vm = WatchMainViewModel.shared var body: some View { VStack { HStack { - if let model = vm.watchPackageModel { - Text(model.currentExerciseName) + if vm.isInWorkout { + Text(vm.watchPackageModel.currentExerciseName) .font(Font.system(size: 55)) .scaledToFit() .minimumScaleFactor(0.01) .lineLimit(1) .foregroundColor(.white) Divider() - Text("\(model.currentTimeLeft )") + Text("\(vm.watchPackageModel.currentTimeLeft )") .font(Font.system(size: 55)) .scaledToFit() .minimumScaleFactor(0.01) @@ -48,14 +48,19 @@ struct MainWatchView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) } - Button(action: { - vm.nextExercise() - }, label: { - Image(systemName: "arrow.forward") - .font(.title) - .frame(maxWidth: .infinity, maxHeight: .infinity) - }) - .buttonStyle(BorderedButtonStyle(tint: .green)) + + if vm.isInWorkout { + Button(action: { + vm.nextExercise() + }, label: { + Image(systemName: "arrow.forward") + .font(.title) + .frame(maxWidth: .infinity, maxHeight: .infinity) + }) + .buttonStyle(BorderedButtonStyle(tint: .green)) + } else { + Text("No Werkout") + } } } } diff --git a/Werkout_watch Watch App/WatchControlView.swift b/Werkout_watch Watch App/WatchControlView.swift index e3a36db..d8e96c4 100644 --- a/Werkout_watch Watch App/WatchControlView.swift +++ b/Werkout_watch Watch App/WatchControlView.swift @@ -8,7 +8,7 @@ import SwiftUI struct WatchControlView: View { - @StateObject var vm = WatchMainViewModel() + @StateObject var vm = WatchMainViewModel.shared var body: some View { HStack { @@ -54,8 +54,8 @@ struct WatchControlView: View { } } -struct WatchControlView_Previews: PreviewProvider { - static var previews: some View { - WatchControlView(vm: WatchMainViewModel()) - } -} +//struct WatchControlView_Previews: PreviewProvider { +// static var previews: some View { +// WatchControlView(vm: WatchMainViewModel()) +// } +//} diff --git a/Werkout_watch Watch App/WatchMainViewModel.swift b/Werkout_watch Watch App/WatchMainViewModel.swift index 1827ae6..4676d3c 100644 --- a/Werkout_watch Watch App/WatchMainViewModel.swift +++ b/Werkout_watch Watch App/WatchMainViewModel.swift @@ -11,10 +11,18 @@ import SwiftUI import HealthKit class WatchMainViewModel: NSObject, ObservableObject { + static let shared = WatchMainViewModel() var session: WCSession - @Published var watchPackageModel: WatchPackageModel? + + @Published var isInWorkout = false + @Published var watchPackageModel = WatchMainViewModel.defualtPackageModle + @Published var heartValue: Int? + static var defualtPackageModle: WatchPackageModel { + WatchPackageModel(currentExerciseName: "", currentTimeLeft: -1, workoutStartDate: Date()) + } + let healthStore = HKHealthStore() var hkWorkoutSession: HKWorkoutSession? var hkBuilder: HKLiveWorkoutBuilder? @@ -82,27 +90,106 @@ class WatchMainViewModel: NSObject, ObservableObject { } } +extension WatchMainViewModel { + func initWorkout() -> Bool { + let configuration = HKWorkoutConfiguration() + configuration.activityType = .functionalStrengthTraining + configuration.locationType = .indoor + + do { + hkWorkoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration) + hkBuilder = hkWorkoutSession?.associatedWorkoutBuilder() + } catch { + fatalError("Unable to create the workout session!") + } + guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { + return false + } + // Setup session and builder. + hkWorkoutSession.delegate = self + hkBuilder.delegate = self + + /// Set the workout builder's data source. + hkBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, + workoutConfiguration: configuration) + return true + } + + func startWorkout() { + // Initialize our workout + if initWorkout() { + + guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { + return + } + + // Start the workout session and begin data collection + hkWorkoutSession.startActivity(with: Date()) + hkBuilder.beginCollection(withStart: Date()) { (succ, error) in + if !succ { + fatalError("Error beginning collection from builder: \(String(describing: error)))") + } + } + isInWorkout = true + } else { + print("didn not init workout") + } + } + + func stopWorkout(sendDetails: Bool) { + guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { + return + } + + hkWorkoutSession.end() + hkBuilder.endCollection(withEnd: Date()) { (success, error) in + hkBuilder.finishWorkout { (workout, error) in + DispatchQueue.main.async() { + self.hkWorkoutSession = 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) + + if sendDetails { + self.send(watchActionData) + } + + self.heartRates.removeAll() + self.isInWorkout = false + } + } + } + } +} + extension WatchMainViewModel: WCSessionDelegate { func session(_ session: WCSession, didReceiveMessageData messageData: Data) { - if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) { + if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) { DispatchQueue.main.async { - if model.currentTimeLeft == -100 { - self.watchPackageModel = nil - return + switch model { + case .inExercise(let data): + if self.isInWorkout == false { + self.startWorkout() + } + self.watchPackageModel = data + case .reset: + self.isInWorkout = false + self.watchPackageModel = WatchMainViewModel.defualtPackageModle + self.stopWorkout(sendDetails: false) + case .endWorkout: + self.isInWorkout = false + self.watchPackageModel = WatchMainViewModel.defualtPackageModle + self.stopWorkout(sendDetails: true) } - if self.watchPackageModel?.workoutEndDate != nil { - self.watchPackageModel = nil - self.stopWorkout() - } else if self.watchPackageModel == nil { - self.startWorkout() - } - self.watchPackageModel = model } } } func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - + print("activation did complete") } func send(_ data: Data) { @@ -126,70 +213,6 @@ extension WatchMainViewModel: WCSessionDelegate { } extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { - func initWorkout() { - let configuration = HKWorkoutConfiguration() - configuration.activityType = .functionalStrengthTraining - configuration.locationType = .indoor - - do { - hkWorkoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration) - hkBuilder = hkWorkoutSession?.associatedWorkoutBuilder() - } catch { - fatalError("Unable to create the workout session!") - } - guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { - return - } - // Setup session and builder. - hkWorkoutSession.delegate = self - hkBuilder.delegate = self - - /// Set the workout builder's data source. - hkBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, - workoutConfiguration: configuration) - } - - func startWorkout() { - // Initialize our workout - initWorkout() - - guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { - return - } - - // Start the workout session and begin data collection - hkWorkoutSession.startActivity(with: Date()) - hkBuilder.beginCollection(withStart: Date()) { (succ, error) in - if !succ { - fatalError("Error beginning collection from builder: \(String(describing: error)))") - } - } - } - - func stopWorkout() { - guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { - return - } - - hkWorkoutSession.end() - hkBuilder.endCollection(withEnd: Date()) { (success, error) in - hkBuilder.finishWorkout { (workout, error) in - DispatchQueue.main.async() { - self.hkWorkoutSession = 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() - } - } - } - } - func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) { print("[workoutSession] Changed State: \(toState.rawValue)") }