// // WatchMainViewModel+WorkoutActions.swift // Werkout_watch Watch App // // Created by Trey Tartt on 6/19/24. // import Foundation import WatchConnectivity import SwiftUI import HealthKit 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("did 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 self.heartRates.removeAll() self.isInWorkout = false guard let id = workout?.uuid else { return } let watchFinishWorkoutModel = WatchFinishWorkoutModel(healthKitUUID: id) let data = try! JSONEncoder().encode(watchFinishWorkoutModel) let watchAction = WatchActions.workoutComplete(data) let watchActionData = try! JSONEncoder().encode(watchAction) if sendDetails { self.send(watchActionData) } } } } } } extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) { print("[workoutSession] Changed State: \(toState.rawValue)") } func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) { print("[workoutSession] Encountered an error: \(error)") } func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set) { for type in collectedTypes { guard let quantityType = type as? HKQuantityType else { return } switch quantityType { case HKQuantityType.quantityType(forIdentifier: .heartRate): DispatchQueue.main.async() { let statistics = workoutBuilder.statistics(for: quantityType) let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute()) let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit) 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))") } default: return } } } func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) { guard let workoutEventType = workoutBuilder.workoutEvents.last?.type else { return } print("[workoutBuilderDidCollectEvent] Workout Builder changed event: \(workoutEventType.rawValue)") } }