209 lines
7.9 KiB
Swift
209 lines
7.9 KiB
Swift
//
|
|
// WatchMainViewMoel.swift
|
|
// Werkout_watch Watch App
|
|
//
|
|
// Created by Trey Tartt on 6/22/23.
|
|
//
|
|
|
|
import Foundation
|
|
import WatchConnectivity
|
|
import SwiftUI
|
|
import HealthKit
|
|
|
|
class WatchMainViewModel: NSObject, ObservableObject {
|
|
var session: WCSession
|
|
@Published var watchPackageModel: WatchPackageModel?
|
|
@Published var heartValue: Int?
|
|
|
|
let healthStore = HKHealthStore()
|
|
var hkWorkoutSession: HKWorkoutSession?
|
|
var hkBuilder: HKLiveWorkoutBuilder?
|
|
var heartRates = [Int]()
|
|
|
|
override init() {
|
|
session = WCSession.default
|
|
super.init()
|
|
|
|
session.delegate = self
|
|
session.activate()
|
|
autorizeHealthKit()
|
|
}
|
|
|
|
func autorizeHealthKit() {
|
|
let healthKitTypes: Set = [
|
|
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
|
|
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
|
HKQuantityType.workoutType()
|
|
]
|
|
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
|
|
if !succ {
|
|
fatalError("Error requesting authorization from health store: \(String(describing: error)))")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startHeartRateQuery(quantityTypeIdentifier: HKQuantityTypeIdentifier) {
|
|
let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
|
|
let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void = {
|
|
query, samples, deletedObjects, queryAnchor, error in
|
|
guard let samples = samples as? [HKQuantitySample] else {
|
|
return
|
|
}
|
|
}
|
|
|
|
let query = HKAnchoredObjectQuery(type: HKObjectType.quantityType(forIdentifier: quantityTypeIdentifier)!, predicate: devicePredicate, anchor: nil, limit: HKObjectQueryNoLimit, resultsHandler: updateHandler)
|
|
|
|
query.updateHandler = updateHandler
|
|
healthStore.execute(query)
|
|
}
|
|
|
|
func nextExercise() {
|
|
let nextExerciseAction = WatchActions.nextExercise
|
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
|
send(data)
|
|
}
|
|
}
|
|
|
|
extension WatchMainViewModel: WCSessionDelegate {
|
|
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
|
if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) {
|
|
DispatchQueue.main.async {
|
|
if model.currentTimeLeft == -100 {
|
|
self.watchPackageModel = nil
|
|
return
|
|
}
|
|
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?) {
|
|
|
|
}
|
|
|
|
func send(_ data: Data) {
|
|
guard WCSession.default.activationState == .activated else {
|
|
return
|
|
}
|
|
#if os(iOS)
|
|
guard WCSession.default.isWatchAppInstalled else {
|
|
return
|
|
}
|
|
#else
|
|
guard WCSession.default.isCompanionAppInstalled else {
|
|
return
|
|
}
|
|
#endif
|
|
WCSession.default.sendMessageData(data, replyHandler: nil)
|
|
{ error in
|
|
print("Cannot send message: \(String(describing: error))")
|
|
}
|
|
}
|
|
}
|
|
|
|
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)")
|
|
}
|
|
|
|
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
|
|
print("[workoutSession] Encountered an error: \(error)")
|
|
}
|
|
|
|
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
|
|
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)")
|
|
}
|
|
}
|