try to fix watch stuff

This commit is contained in:
Trey t
2024-07-01 21:22:12 -05:00
parent cee84d7776
commit f1781744ca
14 changed files with 187 additions and 157 deletions

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>

View File

@@ -7,7 +7,6 @@
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 */; }; 1C0494832C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */; };
1C0494872C23E7BD003D18BB /* BridgeModule+Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */; }; 1C0494872C23E7BD003D18BB /* BridgeModule+Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */; };
1C0494882C23E7C5003D18BB /* BridgeModule+Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */; }; 1C0494882C23E7C5003D18BB /* BridgeModule+Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */; };
@@ -17,6 +16,8 @@
1C04948E2C25CD3D003D18BB /* BridgeModule+WorkoutActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */; }; 1C04948E2C25CD3D003D18BB /* BridgeModule+WorkoutActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */; };
1C0494932C25CEF0003D18BB /* BridgeModule+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */; }; 1C0494932C25CEF0003D18BB /* BridgeModule+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */; };
1C0494942C25CEF4003D18BB /* BridgeModule+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */; }; 1C0494942C25CEF4003D18BB /* BridgeModule+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */; };
1C1A3C722C3373E10010CDD5 /* WatchDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C1A3C712C3373E10010CDD5 /* WatchDelegate.swift */; };
1C1A3C742C3376150010CDD5 /* WatchWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C1A3C732C3376150010CDD5 /* WatchWorkout.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 */; };
@@ -154,12 +155,13 @@
/* 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>"; }; 1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatchMainViewModel+WCSessionDelegate.swift"; sourceTree = "<group>"; };
1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+Watch.swift"; sourceTree = "<group>"; }; 1C0494862C23E7BD003D18BB /* BridgeModule+Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+Watch.swift"; sourceTree = "<group>"; };
1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+WorkoutActions.swift"; sourceTree = "<group>"; }; 1C0494892C25CB4F003D18BB /* BridgeModule+WorkoutActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+WorkoutActions.swift"; sourceTree = "<group>"; };
1C04948B2C25CB80003D18BB /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = "<group>"; }; 1C04948B2C25CB80003D18BB /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = "<group>"; };
1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+Timer.swift"; sourceTree = "<group>"; }; 1C0494922C25CEF0003D18BB /* BridgeModule+Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BridgeModule+Timer.swift"; sourceTree = "<group>"; };
1C1A3C712C3373E10010CDD5 /* WatchDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchDelegate.swift; sourceTree = "<group>"; };
1C1A3C732C3376150010CDD5 /* WatchWorkout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchWorkout.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>"; };
@@ -511,11 +513,12 @@
children = ( children = (
1CF65AB72A4534DC0042FFBD /* Werkout_watch Watch App.entitlements */, 1CF65AB72A4534DC0042FFBD /* Werkout_watch Watch App.entitlements */,
1CF65AB82A45387B0042FFBD /* Werkout-watch-Watch-App-Info.plist */, 1CF65AB82A45387B0042FFBD /* Werkout-watch-Watch-App-Info.plist */,
1C1A3C732C3376150010CDD5 /* WatchWorkout.swift */,
1C1A3C712C3373E10010CDD5 /* WatchDelegate.swift */,
1CF65A972A452D270042FFBD /* ContentView.swift */, 1CF65A972A452D270042FFBD /* ContentView.swift */,
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 */, 1C0494822C23C56E003D18BB /* WatchMainViewModel+WCSessionDelegate.swift */,
1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */, 1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */,
1CF65A992A452D290042FFBD /* Assets.xcassets */, 1CF65A992A452D290042FFBD /* Assets.xcassets */,
@@ -735,6 +738,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1C4AFF222A885EBD0027710B /* PreviewData.swift in Sources */, 1C4AFF222A885EBD0027710B /* PreviewData.swift in Sources */,
1C1A3C722C3373E10010CDD5 /* WatchDelegate.swift in Sources */,
1CF65A982A452D270042FFBD /* ContentView.swift in Sources */, 1CF65A982A452D270042FFBD /* ContentView.swift in Sources */,
1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */, 1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */,
1C4AFF192A65CD6F0027710B /* Superset.swift in Sources */, 1C4AFF192A65CD6F0027710B /* Superset.swift in Sources */,
@@ -759,7 +763,7 @@
1C04948E2C25CD3D003D18BB /* BridgeModule+WorkoutActions.swift in Sources */, 1C04948E2C25CD3D003D18BB /* BridgeModule+WorkoutActions.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 */, 1C1A3C742C3376150010CDD5 /* WatchWorkout.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -13,6 +13,7 @@ extension BridgeModule {
currentWorkoutRunTimer = nil currentWorkoutRunTimer = nil
currentWorkoutRunTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in currentWorkoutRunTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in
self.currentWorkoutRunTimeInSeconds += 1 self.currentWorkoutRunTimeInSeconds += 1
self.sendCurrentExerciseToWatch()
}) })
currentWorkoutRunTimer?.fire() currentWorkoutRunTimer?.fire()
} }
@@ -50,7 +51,6 @@ extension BridgeModule {
} }
} }
} }
sendCurrentExerciseToWatch()
} else { } else {
nextExercise() nextExercise()
} }

View File

@@ -50,7 +50,6 @@ extension BridgeModule {
if let duration = exercise.duration, duration > 0 { if let duration = exercise.duration, duration > 0 {
self.startExerciseTimerWith(duration: duration) self.startExerciseTimerWith(duration: duration)
} }
self.sendCurrentExerciseToWatch()
} }
} }
@@ -104,6 +103,9 @@ extension BridgeModule {
func resetCurrentWorkout() { func resetCurrentWorkout() {
DispatchQueue.main.async { DispatchQueue.main.async {
if self.isInWorkout {
self.sendWorkoutCompleteToWatch()
}
self.currentWorkoutRunTimeInSeconds = 0 self.currentWorkoutRunTimeInSeconds = 0
self.currentWorkoutRunTimer?.invalidate() self.currentWorkoutRunTimer?.invalidate()
self.currentWorkoutRunTimer = nil self.currentWorkoutRunTimer = nil

View File

@@ -42,6 +42,7 @@ struct ExternalWorkoutDetailView: View {
ExtCountdownView() ExtCountdownView()
.padding(.leading, 50) .padding(.leading, 50)
.padding(.bottom, 20) .padding(.bottom, 20)
if extShowNextVideo && extThotStyle != .off { if extShowNextVideo && extThotStyle != .off {
PlayerView(player: $smallAVPlayer) PlayerView(player: $smallAVPlayer)
.frame(width: metrics.size.width * 0.2, .frame(width: metrics.size.width * 0.2,
@@ -60,6 +61,34 @@ struct ExternalWorkoutDetailView: View {
.edgesIgnoringSafeArea(.all) .edgesIgnoringSafeArea(.all)
.scaledToFill() .scaledToFill()
} }
VStack {
HStack {
if bridgeModule.currentWorkoutRunTimeInSeconds > -1 {
Text(" \(Double(bridgeModule.currentWorkoutRunTimeInSeconds).asString(style: .positional)) ")
.font(Font.system(size: 120))
.minimumScaleFactor(0.01)
.lineLimit(1)
.padding()
.bold()
.foregroundColor(.white)
.background(
Capsule()
.strokeBorder(Color.black, lineWidth: 0.8)
.background(Color(uiColor: UIColor(red: 148/255,
green: 0,
blue: 211/255,
alpha: 0.5)))
.clipped()
)
.clipShape(Capsule())
}
Spacer()
}
.padding(.leading, 50)
Spacer()
}
} }
.onChange(of: bridgeModule.isInWorkout, perform: { _ in .onChange(of: bridgeModule.isInWorkout, perform: { _ in
playVideos() playVideos()

View File

@@ -176,9 +176,8 @@ struct WorkoutDetailView: View {
bridgeModule.completedWorkout = { bridgeModule.completedWorkout = {
if let workoutData = createWorkoutData() { if let workoutData = createWorkoutData() {
workoutComplete = .completedWorkout(workoutData) workoutComplete = .completedWorkout(workoutData)
} else {
bridgeModule.resetCurrentWorkout()
} }
bridgeModule.resetCurrentWorkout()
} }
} }
.onReceive(NotificationCenter.default.publisher( .onReceive(NotificationCenter.default.publisher(

View File

@@ -21,16 +21,6 @@ struct ExtCountdownView: View {
.minimumScaleFactor(0.01) .minimumScaleFactor(0.01)
.lineLimit(1) .lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
if bridgeModule.currentWorkoutRunTimeInSeconds > -1 {
Text("\(Double(bridgeModule.currentWorkoutRunTimeInSeconds).asString(style: .positional))")
.font(Font.system(size: 100))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing, 100)
}
} }
.frame(height: metrics.size.height * 0.5) .frame(height: metrics.size.height * 0.5)

View File

@@ -8,18 +8,20 @@
import SwiftUI import SwiftUI
struct MainWatchView: View { struct MainWatchView: View {
@StateObject var vm = WatchMainViewModel.shared @ObservedObject var vm = WatchMainViewModel.shared
@ObservedObject var watchWorkout = WatchWorkout.shared
var body: some View { var body: some View {
VStack { VStack {
HStack { if watchWorkout.isInWorkout {
if vm.isInWorkout { HStack {
Text(vm.watchPackageModel.currentExerciseName) Text(vm.watchPackageModel.currentExerciseName)
.font(.body) .font(.body)
.foregroundColor(.white) .foregroundColor(.white)
.lineLimit(10) .lineLimit(10)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
Divider() Divider()
Text("\(vm.watchPackageModel.currentTimeLeft )") Text("\(vm.watchPackageModel.currentTimeLeft )")
.font(.title) .font(.title)
@@ -27,28 +29,27 @@ struct MainWatchView: View {
.lineLimit(10) .lineLimit(10)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
}
HStack {
if let heartValue = vm.heartValue {
VStack {
Image(systemName: "heart.fill")
.font(Font.system(size: 22))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.foregroundColor(.red)
Text("\(heartValue)")
.font(Font.system(size: 55))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.foregroundColor(.red)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
if vm.isInWorkout {
HStack {
if let heartValue = watchWorkout.heartValue {
VStack {
Image(systemName: "heart.fill")
.font(Font.system(size: 22))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.foregroundColor(.red)
Text("\(heartValue)")
.font(Font.system(size: 55))
.scaledToFit()
.minimumScaleFactor(0.01)
.lineLimit(1)
.foregroundColor(.red)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
Button(action: { Button(action: {
vm.nextExercise() vm.nextExercise()
}, label: { }, label: {
@@ -57,12 +58,10 @@ struct MainWatchView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
}) })
.buttonStyle(BorderedButtonStyle(tint: .green)) .buttonStyle(BorderedButtonStyle(tint: .green))
} else {
VStack {
Text("No Werkout")
Text("🍑")
}
} }
} else {
Text("No Werkout")
Text("🍑")
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import SwiftUI
struct WatchControlView: View { struct WatchControlView: View {
@StateObject var vm = WatchMainViewModel.shared @StateObject var vm = WatchMainViewModel.shared
@StateObject var watchWorkout = WatchWorkout.shared
var body: some View { var body: some View {
HStack { HStack {
@@ -44,7 +45,7 @@ struct WatchControlView: View {
Button(action: { Button(action: {
vm.pauseWorkout() vm.pauseWorkout()
}, label: { }, label: {
if vm.isPaused { if watchWorkout.isPaused {
Image(systemName: "play") Image(systemName: "play")
.font(.title) .font(.title)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)

View File

@@ -0,0 +1,41 @@
//
// WatchDelegate.swift
// Werkout_watch Watch App
//
// Created by Trey Tartt on 7/1/24.
//
import WatchKit
import HealthKit
class WatchDelegate: NSObject, WKApplicationDelegate {
func applicationDidFinishLaunching() {
autorizeHealthKit()
}
func applicationDidBecomeActive() {
}
func applicationWillResignActive() {
}
func handle(_ workoutConfiguration: HKWorkoutConfiguration) {
// WatchWorkout.shared.startWorkout()
}
func autorizeHealthKit() {
let healthKitTypes: Set = [
HKObjectType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKObjectType.quantityType(forIdentifier: .oxygenSaturation)!,
HKQuantityType.workoutType()
]
HKHealthStore().requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
if !succ {
fatalError("Error requesting authorization from health store: \(String(describing: error)))")
}
}
}
}

View File

@@ -18,8 +18,10 @@ extension WatchMainViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("activation did complete") print("activation did complete")
} }
}
func send(_ data: Data) {
class DataSender {
static func send(_ data: Data) {
guard WCSession.default.activationState == .activated else { guard WCSession.default.activationState == .activated else {
return return
} }

View File

@@ -12,81 +12,59 @@ import HealthKit
class WatchMainViewModel: NSObject, ObservableObject { class WatchMainViewModel: NSObject, ObservableObject {
static let shared = WatchMainViewModel() static let shared = WatchMainViewModel()
var session: WCSession var session: WCSession
@Published var isInWorkout = false
@Published var watchPackageModel = WatchMainViewModel.defualtPackageModle @Published var watchPackageModel = WatchMainViewModel.defualtPackageModle
@Published var heartValue: Int?
static var defualtPackageModle: WatchPackageModel { static var defualtPackageModle: WatchPackageModel {
WatchPackageModel(currentExerciseName: "", currentExerciseID: -1, currentTimeLeft: -1, workoutStartDate: Date()) WatchPackageModel(currentExerciseName: "", currentExerciseID: -1, currentTimeLeft: -1, workoutStartDate: Date())
} }
let healthStore = HKHealthStore()
var hkWorkoutSession: HKWorkoutSession?
var hkBuilder: HKLiveWorkoutBuilder?
var heartRates = [Int]()
@Published var isPaused = false
override init() { override init() {
session = WCSession.default session = WCSession.default
super.init() super.init()
session.delegate = self session.delegate = self
session.activate() session.activate()
autorizeHealthKit()
}
func autorizeHealthKit() {
let healthKitTypes: Set = [
HKObjectType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKObjectType.quantityType(forIdentifier: .oxygenSaturation)!,
HKQuantityType.workoutType()
]
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
if !succ {
fatalError("Error requesting authorization from health store: \(String(describing: error)))")
}
}
} }
// actions from view // 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)
send(data) DataSender.send(data)
WKInterfaceDevice.current().play(.start) WKInterfaceDevice.current().play(.start)
} }
func restartExercise() { func restartExercise() {
let nextExerciseAction = WatchActions.restartExercise let nextExerciseAction = WatchActions.restartExercise
let data = try! JSONEncoder().encode(nextExerciseAction) let data = try! JSONEncoder().encode(nextExerciseAction)
send(data) DataSender.send(data)
WKInterfaceDevice.current().play(.start) WKInterfaceDevice.current().play(.start)
} }
func previousExercise() { func previousExercise() {
let nextExerciseAction = WatchActions.previousExercise let nextExerciseAction = WatchActions.previousExercise
let data = try! JSONEncoder().encode(nextExerciseAction) let data = try! JSONEncoder().encode(nextExerciseAction)
send(data) DataSender.send(data)
WKInterfaceDevice.current().play(.start) WKInterfaceDevice.current().play(.start)
} }
func completeWorkout() { func completeWorkout() {
let nextExerciseAction = WatchActions.stopWorkout let nextExerciseAction = WatchActions.stopWorkout
let data = try! JSONEncoder().encode(nextExerciseAction) let data = try! JSONEncoder().encode(nextExerciseAction)
send(data) DataSender.send(data)
WKInterfaceDevice.current().play(.start) WKInterfaceDevice.current().play(.start)
} }
func pauseWorkout() { func pauseWorkout() {
let nextExerciseAction = WatchActions.pauseWorkout let nextExerciseAction = WatchActions.pauseWorkout
let data = try! JSONEncoder().encode(nextExerciseAction) let data = try! JSONEncoder().encode(nextExerciseAction)
send(data) DataSender.send(data)
isPaused = !isPaused
WKInterfaceDevice.current().play(.start) WKInterfaceDevice.current().play(.start)
WatchWorkout.shared.togglePaused()
} }
func dataToAction(messageData: Data) { func dataToAction(messageData: Data) {
@@ -94,27 +72,19 @@ class WatchMainViewModel: NSObject, ObservableObject {
DispatchQueue.main.async { DispatchQueue.main.async {
switch model { switch model {
case .inExercise(let newWatchPackageModel): case .inExercise(let newWatchPackageModel):
if !self.isInWorkout { WatchWorkout.shared.startWorkout()
self.startWorkout()
}
if self.watchPackageModel.currentExerciseID != newWatchPackageModel.currentExerciseID { if self.watchPackageModel.currentExerciseID != newWatchPackageModel.currentExerciseID {
self.isPaused = false
WKInterfaceDevice.current().play(.start) WKInterfaceDevice.current().play(.start)
} }
self.watchPackageModel = newWatchPackageModel self.watchPackageModel = newWatchPackageModel
case .reset: case .reset:
self.isInWorkout = false
self.watchPackageModel = WatchMainViewModel.defualtPackageModle self.watchPackageModel = WatchMainViewModel.defualtPackageModle
self.stopWorkout(sendDetails: false) WatchWorkout.shared.stopWorkout(sendDetails: false)
case .endWorkout: case .endWorkout:
self.isInWorkout = false
self.watchPackageModel = WatchMainViewModel.defualtPackageModle self.watchPackageModel = WatchMainViewModel.defualtPackageModle
self.stopWorkout(sendDetails: true) WatchWorkout.shared.stopWorkout(sendDetails: true)
case .startWorkout: case .startWorkout:
if self.isInWorkout { WatchWorkout.shared.startWorkout()
self.stopWorkout(sendDetails: false)
}
self.startWorkout()
} }
} }
} }

View File

@@ -1,96 +1,85 @@
// //
// WatchMainViewModel+WorkoutActions.swift // WatchWorkout.swift
// Werkout_watch Watch App // Werkout_watch Watch App
// //
// Created by Trey Tartt on 6/19/24. // Created by Trey Tartt on 7/1/24.
// //
import Foundation import Foundation
import WatchConnectivity
import SwiftUI
import HealthKit import HealthKit
extension WatchMainViewModel { class WatchWorkout: NSObject, ObservableObject, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
func initWorkout() -> Bool { static let shared = WatchWorkout()
let configuration = HKWorkoutConfiguration()
configuration.activityType = .functionalStrengthTraining @Published var heartValue: Int?
configuration.locationType = .indoor
let healthStore = HKHealthStore()
var hkWorkoutSession: HKWorkoutSession
var hkBuilder: HKLiveWorkoutBuilder
var heartRates = [Int]()
@Published var isInWorkout = false
@Published var isPaused = false
private override init() {
do { do {
let configuration = HKWorkoutConfiguration()
configuration.activityType = .functionalStrengthTraining
configuration.locationType = .indoor
hkWorkoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration) hkWorkoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
hkBuilder = hkWorkoutSession?.associatedWorkoutBuilder() hkBuilder = hkWorkoutSession.associatedWorkoutBuilder()
super.init()
hkWorkoutSession.delegate = self
hkBuilder.delegate = self
/// Set the workout builder's data source.
hkBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
workoutConfiguration: configuration)
} catch { } catch {
fatalError("Unable to create the workout session!") fatalError()
} }
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 startWorkout() {
// Initialize our workout if isInWorkout { return }
if initWorkout() { // Start the workout session and begin data collection
hkWorkoutSession.startActivity(with: Date())
guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { hkBuilder.beginCollection(withStart: Date()) { (succ, error) in
return if !succ {
fatalError("Error beginning collection from builder: \(String(describing: error)))")
} }
// 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
isPaused = false
WKInterfaceDevice.current().play(.start)
} else {
print("did not init workout")
} }
isInWorkout = true
//WKInterfaceDevice.current().play(.start)
} }
func stopWorkout(sendDetails: Bool) { func stopWorkout(sendDetails: Bool) {
guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else {
return
}
hkWorkoutSession.end() hkWorkoutSession.end()
self.heartRates.removeAll()
self.isInWorkout = false
hkBuilder.endCollection(withEnd: Date()) { (success, error) in hkBuilder.endCollection(withEnd: Date()) { (success, error) in
hkBuilder.finishWorkout { (workout, error) in if !success || error != nil { return }
self.hkBuilder.finishWorkout { (workout, error) in
guard let workout = workout else { return }
if !sendDetails { return }
DispatchQueue.main.async() { DispatchQueue.main.async() {
self.hkWorkoutSession = nil let watchFinishWorkoutModel = WatchFinishWorkoutModel(healthKitUUID: workout.uuid)
self.hkBuilder = nil
self.heartRates.removeAll()
self.isInWorkout = false
self.isPaused = false
guard let id = workout?.uuid else {
return
}
let watchFinishWorkoutModel = WatchFinishWorkoutModel(healthKitUUID: id)
let data = try! JSONEncoder().encode(watchFinishWorkoutModel) let data = try! JSONEncoder().encode(watchFinishWorkoutModel)
let watchAction = WatchActions.workoutComplete(data) let watchAction = WatchActions.workoutComplete(data)
let watchActionData = try! JSONEncoder().encode(watchAction) let watchActionData = try! JSONEncoder().encode(watchAction)
DataSender.send(watchActionData)
if sendDetails {
self.send(watchActionData)
}
} }
} }
} }
} }
}
func togglePaused() {
extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { self.isPaused.toggle()
}
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) { func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
print("[workoutSession] Changed State: \(toState.rawValue)") print("[workoutSession] Changed State: \(toState.rawValue)")
} }

View File

@@ -9,6 +9,8 @@ import SwiftUI
@main @main
struct Werkout_watch_Watch_AppApp: App { struct Werkout_watch_Watch_AppApp: App {
@WKApplicationDelegateAdaptor(WatchDelegate.self) var delegate: WatchDelegate
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView()