This commit is contained in:
Trey t
2023-07-08 11:48:14 -05:00
parent 5ca99cf832
commit 0b477bc182
5 changed files with 168 additions and 121 deletions

View File

@@ -18,6 +18,12 @@ enum WatchActions: Codable {
case workoutComplete(Data) case workoutComplete(Data)
} }
enum PhoneToWatchActions: Codable {
case inExercise(WatchPackageModel)
case reset
case endWorkout
}
class BridgeModule: NSObject, ObservableObject { class BridgeModule: NSObject, ObservableObject {
private let kMessageKey = "message" private let kMessageKey = "message"
@@ -100,10 +106,7 @@ class BridgeModule: NSObject, ObservableObject {
self.workoutStartDate = nil self.workoutStartDate = nil
self.workoutEndDate = nil self.workoutEndDate = nil
} }
sendResetToWatch()
let watchModel = WatchPackageModel(currentExerciseName: "", currentTimeLeft: -100, workoutStartDate: Date())
let data = try! JSONEncoder().encode(watchModel)
send(data)
} }
private func startWorkoutTimer() { private func startWorkoutTimer() {
@@ -135,10 +138,6 @@ class BridgeModule: NSObject, ObservableObject {
if currentExerciseTimeLeft > 0 { if currentExerciseTimeLeft > 0 {
currentExerciseTimeLeft -= 1 currentExerciseTimeLeft -= 1
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date())
let data = try! JSONEncoder().encode(watchModel)
send(data)
if currentExerciseTimeLeft == 0 { if currentExerciseTimeLeft == 0 {
playFinished() playFinished()
} else { } else {
@@ -146,6 +145,7 @@ class BridgeModule: NSObject, ObservableObject {
playBeep() playBeep()
} }
} }
sendCurrentExerciseToWatch()
} else { } else {
nextExercise() nextExercise()
} }
@@ -209,25 +209,16 @@ class BridgeModule: NSObject, ObservableObject {
if let duration = exercise.duration, if let duration = exercise.duration,
duration > 0 { duration > 0 {
print(duration)
self.startExerciseTimerWith(duration: duration) self.startExerciseTimerWith(duration: duration)
} else {
var intWatchDispaly = -1
if let reps = self.currentExercise?.reps,
reps > 0 {
intWatchDispaly = reps
}
// if not a timer we need to set the watch display with number of reps
// if timer it will set when timer updates
let watchModel = WatchPackageModel(currentExerciseName: self.currentExercise?.exercise.name ?? "-", currentTimeLeft: intWatchDispaly, workoutStartDate: self.workoutStartDate ?? Date())
let data = try! JSONEncoder().encode(watchModel)
self.send(data)
} }
self.sendCurrentExerciseToWatch()
} }
} }
func completeWorkout() { func completeWorkout() {
self.currentExerciseTimer?.invalidate()
self.currentExerciseTimer = nil
workoutEndDate = Date() workoutEndDate = Date()
//if connected to watch //if connected to watch
@@ -276,12 +267,41 @@ class BridgeModule: NSObject, ObservableObject {
} }
extension BridgeModule: WCSessionDelegate { extension BridgeModule: WCSessionDelegate {
func sendWorkoutCompleteToWatch() { func sendResetToWatch() {
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date()) let watchModel = PhoneToWatchActions.reset
let data = try! JSONEncoder().encode(watchModel) let data = try! JSONEncoder().encode(watchModel)
send(data) send(data)
} }
func sendWorkoutCompleteToWatch() {
let model = PhoneToWatchActions.endWorkout
let data = try! JSONEncoder().encode(model)
send(data)
}
func sendCurrentExerciseToWatch() {
if let duration = currentExercise?.duration,
duration > 0 {
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date())
let model = PhoneToWatchActions.inExercise(watchModel)
let data = try! JSONEncoder().encode(model)
send(data)
} else {
var intWatchDispaly = -1
if let reps = self.currentExercise?.reps,
reps > 0 {
intWatchDispaly = reps
}
// if not a timer we need to set the watch display with number of reps
// if timer it will set when timer updates
let watchModel = WatchPackageModel(currentExerciseName: self.currentExercise?.exercise.name ?? "-", currentTimeLeft: intWatchDispaly, workoutStartDate: self.workoutStartDate ?? Date())
let model = PhoneToWatchActions.inExercise(watchModel)
let data = try! JSONEncoder().encode(model)
self.send(data)
}
}
func session(_ session: WCSession, didReceiveMessageData messageData: Data) { func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) { if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) {
switch model { switch model {

View File

@@ -6,14 +6,13 @@
// //
import SwiftUI import SwiftUI
import WatchKit
struct ContentView: View { struct ContentView: View {
var body: some View { var body: some View {
TabView { TabView {
MainWatchView() MainWatchView()
WatchControlView() WatchControlView()
NowPlayingView()
} }
.padding() .padding()
.tabViewStyle(PageTabViewStyle()) .tabViewStyle(PageTabViewStyle())

View File

@@ -8,20 +8,20 @@
import SwiftUI import SwiftUI
struct MainWatchView: View { struct MainWatchView: View {
@StateObject var vm = WatchMainViewModel() @StateObject var vm = WatchMainViewModel.shared
var body: some View { var body: some View {
VStack { VStack {
HStack { HStack {
if let model = vm.watchPackageModel { if vm.isInWorkout {
Text(model.currentExerciseName) Text(vm.watchPackageModel.currentExerciseName)
.font(Font.system(size: 55)) .font(Font.system(size: 55))
.scaledToFit() .scaledToFit()
.minimumScaleFactor(0.01) .minimumScaleFactor(0.01)
.lineLimit(1) .lineLimit(1)
.foregroundColor(.white) .foregroundColor(.white)
Divider() Divider()
Text("\(model.currentTimeLeft )") Text("\(vm.watchPackageModel.currentTimeLeft )")
.font(Font.system(size: 55)) .font(Font.system(size: 55))
.scaledToFit() .scaledToFit()
.minimumScaleFactor(0.01) .minimumScaleFactor(0.01)
@@ -48,14 +48,19 @@ struct MainWatchView: View {
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
} }
Button(action: {
vm.nextExercise() if vm.isInWorkout {
}, label: { Button(action: {
Image(systemName: "arrow.forward") vm.nextExercise()
.font(.title) }, label: {
.frame(maxWidth: .infinity, maxHeight: .infinity) Image(systemName: "arrow.forward")
}) .font(.title)
.buttonStyle(BorderedButtonStyle(tint: .green)) .frame(maxWidth: .infinity, maxHeight: .infinity)
})
.buttonStyle(BorderedButtonStyle(tint: .green))
} else {
Text("No Werkout")
}
} }
} }
} }

View File

@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
struct WatchControlView: View { struct WatchControlView: View {
@StateObject var vm = WatchMainViewModel() @StateObject var vm = WatchMainViewModel.shared
var body: some View { var body: some View {
HStack { HStack {
@@ -54,8 +54,8 @@ struct WatchControlView: View {
} }
} }
struct WatchControlView_Previews: PreviewProvider { //struct WatchControlView_Previews: PreviewProvider {
static var previews: some View { // static var previews: some View {
WatchControlView(vm: WatchMainViewModel()) // WatchControlView(vm: WatchMainViewModel())
} // }
} //}

View File

@@ -11,10 +11,18 @@ import SwiftUI
import HealthKit import HealthKit
class WatchMainViewModel: NSObject, ObservableObject { class WatchMainViewModel: NSObject, ObservableObject {
static let shared = WatchMainViewModel()
var session: WCSession var session: WCSession
@Published var watchPackageModel: WatchPackageModel?
@Published var isInWorkout = false
@Published var watchPackageModel = WatchMainViewModel.defualtPackageModle
@Published var heartValue: Int? @Published var heartValue: Int?
static var defualtPackageModle: WatchPackageModel {
WatchPackageModel(currentExerciseName: "", currentTimeLeft: -1, workoutStartDate: Date())
}
let healthStore = HKHealthStore() let healthStore = HKHealthStore()
var hkWorkoutSession: HKWorkoutSession? var hkWorkoutSession: HKWorkoutSession?
var hkBuilder: HKLiveWorkoutBuilder? var hkBuilder: HKLiveWorkoutBuilder?
@@ -82,27 +90,106 @@ class WatchMainViewModel: NSObject, ObservableObject {
} }
} }
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
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)
if sendDetails {
self.send(watchActionData)
}
self.heartRates.removeAll()
self.isInWorkout = false
}
}
}
}
}
extension WatchMainViewModel: WCSessionDelegate { extension WatchMainViewModel: WCSessionDelegate {
func session(_ session: WCSession, didReceiveMessageData messageData: Data) { func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) { if let model = try? JSONDecoder().decode(PhoneToWatchActions.self, from: messageData) {
DispatchQueue.main.async { DispatchQueue.main.async {
if model.currentTimeLeft == -100 { switch model {
self.watchPackageModel = nil case .inExercise(let data):
return if self.isInWorkout == false {
self.startWorkout()
}
self.watchPackageModel = data
case .reset:
self.isInWorkout = false
self.watchPackageModel = WatchMainViewModel.defualtPackageModle
self.stopWorkout(sendDetails: false)
case .endWorkout:
self.isInWorkout = false
self.watchPackageModel = WatchMainViewModel.defualtPackageModle
self.stopWorkout(sendDetails: true)
} }
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 session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
print("activation did complete")
} }
func send(_ data: Data) { func send(_ data: Data) {
@@ -126,70 +213,6 @@ extension WatchMainViewModel: WCSessionDelegate {
} }
extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { 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) { func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
print("[workoutSession] Changed State: \(toState.rawValue)") print("[workoutSession] Changed State: \(toState.rawValue)")
} }