WIP
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
1C31C8852A53AE3E00350540 /* long_beep.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 1C31C8832A53AE3E00350540 /* long_beep.m4a */; };
|
||||
1C31C8872A55B2CC00350540 /* PlayerUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C31C8862A55B2CC00350540 /* PlayerUIView.swift */; };
|
||||
@@ -144,6 +146,8 @@
|
||||
/* End PBXCopyFilesBuildPhase 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>"; };
|
||||
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>"; };
|
||||
@@ -495,6 +499,8 @@
|
||||
1C5190D32A59AEDE00885849 /* MainWatchView.swift */,
|
||||
1C5190D12A59ACA400885849 /* WatchControlView.swift */,
|
||||
1CF65AB52A4532940042FFBD /* WatchMainViewModel.swift */,
|
||||
1C0494802C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift */,
|
||||
1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */,
|
||||
1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */,
|
||||
1CF65A992A452D290042FFBD /* Assets.xcassets */,
|
||||
1CF65A9B2A452D290042FFBD /* Preview Content */,
|
||||
@@ -717,6 +723,7 @@
|
||||
1C4AFF162A60F27E0027710B /* ThotStyle.swift in Sources */,
|
||||
1C4AFF212A8801090027710B /* AudioQueue.swift in Sources */,
|
||||
1CF65AB12A452E1A0042FFBD /* BridgeModule.swift in Sources */,
|
||||
1C0494832C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift in Sources */,
|
||||
1CF65AAA2A452D9C0042FFBD /* RegisteredUser.swift in Sources */,
|
||||
1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */,
|
||||
1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */,
|
||||
@@ -728,6 +735,7 @@
|
||||
1C5190D22A59ACA400885849 /* WatchControlView.swift in Sources */,
|
||||
1C5190D42A59AEDE00885849 /* MainWatchView.swift in Sources */,
|
||||
1CF65AB42A4530200042FFBD /* WatchPackageModel.swift in Sources */,
|
||||
1C0494812C23C53E003D18BB /* WatchMainViewModel+WorkoutActions.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -54,6 +54,7 @@ class BridgeModule: NSObject, ObservableObject {
|
||||
|
||||
var audioPlayer: AVAudioPlayer?
|
||||
var avPlayer: AVPlayer?
|
||||
private let session: WCSession = WCSession.default
|
||||
|
||||
func start(workout: Workout) {
|
||||
currentExerciseInfo.complete = {
|
||||
@@ -73,8 +74,8 @@ class BridgeModule: NSObject, ObservableObject {
|
||||
isInWorkout = true
|
||||
|
||||
if WCSession.isSupported() {
|
||||
WCSession.default.delegate = self
|
||||
WCSession.default.activate()
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,15 +278,19 @@ extension BridgeModule: WCSessionDelegate {
|
||||
func sendResetToWatch() {
|
||||
let watchModel = PhoneToWatchActions.reset
|
||||
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() {
|
||||
if WCSession.default.isReachable {
|
||||
let model = PhoneToWatchActions.endWorkout
|
||||
let data = try! JSONEncoder().encode(model)
|
||||
send(data)
|
||||
}
|
||||
let model = PhoneToWatchActions.endWorkout
|
||||
let data = try! JSONEncoder().encode(model)
|
||||
|
||||
// user transferUserInfo b/c its guranteed to reach
|
||||
// and end the workout
|
||||
self.session.transferUserInfo(["package": data])
|
||||
}
|
||||
|
||||
func sendCurrentExerciseToWatch() {
|
||||
@@ -349,12 +354,14 @@ extension BridgeModule: WCSessionDelegate {
|
||||
let workoutConfiguration = HKWorkoutConfiguration()
|
||||
workoutConfiguration.activityType = .functionalStrengthTraining
|
||||
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
|
||||
print(error.debugDescription)
|
||||
})
|
||||
}
|
||||
#endif
|
||||
@unknown default:
|
||||
print("default")
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
@@ -367,19 +374,19 @@ extension BridgeModule: WCSessionDelegate {
|
||||
}
|
||||
#endif
|
||||
func send(_ data: Data) {
|
||||
guard WCSession.default.activationState == .activated else {
|
||||
guard session.activationState == .activated else {
|
||||
return
|
||||
}
|
||||
#if os(iOS)
|
||||
guard WCSession.default.isWatchAppInstalled else {
|
||||
guard session.isWatchAppInstalled else {
|
||||
return
|
||||
}
|
||||
#else
|
||||
guard WCSession.default.isCompanionAppInstalled else {
|
||||
guard session.isCompanionAppInstalled else {
|
||||
return
|
||||
}
|
||||
#endif
|
||||
WCSession.default.sendMessageData(data, replyHandler: nil) { error in
|
||||
session.sendMessageData(data, replyHandler: nil) { error in
|
||||
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() {
|
||||
let nextExerciseAction = WatchActions.nextExercise
|
||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||
@@ -80,87 +81,8 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||
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() {
|
||||
// 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) {
|
||||
func dataToAction(messageData: Data) {
|
||||
if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
|
||||
DispatchQueue.main.async {
|
||||
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