WIP
This commit is contained in:
@@ -7,6 +7,8 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
1C0494812C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494802C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift */; };
|
||||||
|
1C0494832C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */; };
|
||||||
1C31C8842A53AE3E00350540 /* short_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8822A53AE3E00350540 /* short_beep.m4a */; };
|
1C31C8842A53AE3E00350540 /* short_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8822A53AE3E00350540 /* short_beep.m4a */; };
|
||||||
1C31C8852A53AE3E00350540 /* long_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8832A53AE3E00350540 /* long_beep.m4a */; };
|
1C31C8852A53AE3E00350540 /* long_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8832A53AE3E00350540 /* long_beep.m4a */; };
|
||||||
1C31C8872A55B2CC00350540 /* PlayerUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C31C8862A55B2CC00350540 /* PlayerUIView.swift */; };
|
1C31C8872A55B2CC00350540 /* PlayerUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C31C8862A55B2CC00350540 /* PlayerUIView.swift */; };
|
||||||
@@ -144,6 +146,8 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
1C0494802C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchMainViewModel+WorkoutActions.swift"; sourceTree = "<group>"; };
|
||||||
|
1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchMainViewModel+WCSessionDelegate.swift"; sourceTree = "<group>"; };
|
||||||
1C31C8822A53AE3E00350540 /* short_beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = short_beep.m4a; sourceTree = "<group>"; };
|
1C31C8822A53AE3E00350540 /* short_beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = short_beep.m4a; sourceTree = "<group>"; };
|
||||||
1C31C8832A53AE3E00350540 /* long_beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = long_beep.m4a; sourceTree = "<group>"; };
|
1C31C8832A53AE3E00350540 /* long_beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = long_beep.m4a; sourceTree = "<group>"; };
|
||||||
1C31C8862A55B2CC00350540 /* PlayerUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerUIView.swift; sourceTree = "<group>"; };
|
1C31C8862A55B2CC00350540 /* PlayerUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerUIView.swift; sourceTree = "<group>"; };
|
||||||
@@ -495,6 +499,8 @@
|
|||||||
1C5190D32A59AEDE00885849 /* MainWatchView.swift */,
|
1C5190D32A59AEDE00885849 /* MainWatchView.swift */,
|
||||||
1C5190D12A59ACA400885849 /* WatchControlView.swift */,
|
1C5190D12A59ACA400885849 /* WatchControlView.swift */,
|
||||||
1CF65AB52A4532940042FFBD /* WatchMainViewModel.swift */,
|
1CF65AB52A4532940042FFBD /* WatchMainViewModel.swift */,
|
||||||
|
1C0494802C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift */,
|
||||||
|
1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */,
|
||||||
1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */,
|
1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */,
|
||||||
1CF65A992A452D290042FFBD /* Assets.xcassets */,
|
1CF65A992A452D290042FFBD /* Assets.xcassets */,
|
||||||
1CF65A9B2A452D290042FFBD /* Preview Content */,
|
1CF65A9B2A452D290042FFBD /* Preview Content */,
|
||||||
@@ -717,6 +723,7 @@
|
|||||||
1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */,
|
1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */,
|
||||||
1C4AFF212A8801090027710B /* AudioQueue.swift in Sources */,
|
1C4AFF212A8801090027710B /* AudioQueue.swift in Sources */,
|
||||||
1CF65AB12A452E1A0042FFBD /* BridgeModule.swift in Sources */,
|
1CF65AB12A452E1A0042FFBD /* BridgeModule.swift in Sources */,
|
||||||
|
1C0494832C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift in Sources */,
|
||||||
1CF65AAA2A452D9C0042FFBD /* RegisteredUser.swift in Sources */,
|
1CF65AAA2A452D9C0042FFBD /* RegisteredUser.swift in Sources */,
|
||||||
1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */,
|
1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */,
|
||||||
1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */,
|
1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */,
|
||||||
@@ -728,6 +735,7 @@
|
|||||||
1C5190D22A59ACA400885849 /* WatchControlView.swift in Sources */,
|
1C5190D22A59ACA400885849 /* WatchControlView.swift in Sources */,
|
||||||
1C5190D42A59AEDE00885849 /* MainWatchView.swift in Sources */,
|
1C5190D42A59AEDE00885849 /* MainWatchView.swift in Sources */,
|
||||||
1CF65AB42A4530200042FFBD /* WatchPackageModel.swift in Sources */,
|
1CF65AB42A4530200042FFBD /* WatchPackageModel.swift in Sources */,
|
||||||
|
1C0494812C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
|
|
||||||
var audioPlayer: AVAudioPlayer?
|
var audioPlayer: AVAudioPlayer?
|
||||||
var avPlayer: AVPlayer?
|
var avPlayer: AVPlayer?
|
||||||
|
private let session: WCSession = WCSession.default
|
||||||
|
|
||||||
func start(workout: Workout) {
|
func start(workout: Workout) {
|
||||||
currentExerciseInfo.complete = {
|
currentExerciseInfo.complete = {
|
||||||
@@ -73,8 +74,8 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
isInWorkout = true
|
isInWorkout = true
|
||||||
|
|
||||||
if WCSession.isSupported() {
|
if WCSession.isSupported() {
|
||||||
WCSession.default.delegate = self
|
session.delegate = self
|
||||||
WCSession.default.activate()
|
session.activate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,15 +278,19 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
func sendResetToWatch() {
|
func sendResetToWatch() {
|
||||||
let watchModel = PhoneToWatchActions.reset
|
let watchModel = PhoneToWatchActions.reset
|
||||||
let data = try! JSONEncoder().encode(watchModel)
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
send(data)
|
|
||||||
|
// user transferUserInfo b/c its guranteed to reach
|
||||||
|
// and end the workout
|
||||||
|
self.session.transferUserInfo(["package": data])
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendWorkoutCompleteToWatch() {
|
func sendWorkoutCompleteToWatch() {
|
||||||
if WCSession.default.isReachable {
|
let model = PhoneToWatchActions.endWorkout
|
||||||
let model = PhoneToWatchActions.endWorkout
|
let data = try! JSONEncoder().encode(model)
|
||||||
let data = try! JSONEncoder().encode(model)
|
|
||||||
send(data)
|
// user transferUserInfo b/c its guranteed to reach
|
||||||
}
|
// and end the workout
|
||||||
|
self.session.transferUserInfo(["package": data])
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendCurrentExerciseToWatch() {
|
func sendCurrentExerciseToWatch() {
|
||||||
@@ -349,12 +354,14 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
let workoutConfiguration = HKWorkoutConfiguration()
|
let workoutConfiguration = HKWorkoutConfiguration()
|
||||||
workoutConfiguration.activityType = .functionalStrengthTraining
|
workoutConfiguration.activityType = .functionalStrengthTraining
|
||||||
workoutConfiguration.locationType = .indoor
|
workoutConfiguration.locationType = .indoor
|
||||||
if WCSession.isSupported(), WCSession.default.activationState == .activated, WCSession.default.isWatchAppInstalled {
|
if WCSession.isSupported(), session.activationState == .activated, session.isWatchAppInstalled {
|
||||||
HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in
|
HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in
|
||||||
print(error.debugDescription)
|
print(error.debugDescription)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@unknown default:
|
||||||
|
print("default")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -367,19 +374,19 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
func send(_ data: Data) {
|
func send(_ data: Data) {
|
||||||
guard WCSession.default.activationState == .activated else {
|
guard session.activationState == .activated else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
guard WCSession.default.isWatchAppInstalled else {
|
guard session.isWatchAppInstalled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
guard WCSession.default.isCompanionAppInstalled else {
|
guard session.isCompanionAppInstalled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
WCSession.default.sendMessageData(data, replyHandler: nil) { error in
|
session.sendMessageData(data, replyHandler: nil) { error in
|
||||||
print("Cannot send message: \(String(describing: error))")
|
print("Cannot send message: \(String(describing: error))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// WatchMainViewModel+WCSessionDelegate.swift
|
||||||
|
// Werkout_watch Watch App
|
||||||
|
//
|
||||||
|
// Created by Trey Tartt on 6/19/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WatchConnectivity
|
||||||
|
import SwiftUI
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
|
extension WatchMainViewModel: WCSessionDelegate {
|
||||||
|
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
||||||
|
dataToAction(messageData: messageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
||||||
|
print("activation did complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
|
||||||
|
if let messageData = applicationContext["package"] as? Data {
|
||||||
|
dataToAction(messageData: messageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
|
||||||
|
if let messageData = userInfo["package"] as? Data {
|
||||||
|
dataToAction(messageData: messageData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
//
|
||||||
|
// 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("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
|
||||||
|
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<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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// actions from view
|
||||||
func nextExercise() {
|
func nextExercise() {
|
||||||
let nextExerciseAction = WatchActions.nextExercise
|
let nextExerciseAction = WatchActions.nextExercise
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
@@ -80,87 +81,8 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
send(data)
|
send(data)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
func dataToAction(messageData: Data) {
|
||||||
// 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
|
|
||||||
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: WCSessionDelegate {
|
|
||||||
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
|
||||||
if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
|
if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch model {
|
switch model {
|
||||||
@@ -181,63 +103,4 @@ extension WatchMainViewModel: WCSessionDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
|
|
||||||
print("activation did complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user