WIP
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|||||||
@@ -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)")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user