WIP
This commit is contained in:
@@ -27,3 +27,24 @@ struct CompletedWorkout: Codable {
|
|||||||
case totalCalories = "total_calories"
|
case totalCalories = "total_calories"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CompletedWorkoutPOSTReturn: Codable {
|
||||||
|
let id: Int
|
||||||
|
let workout: Int
|
||||||
|
let createdAt, updatedAt: String
|
||||||
|
let difficulty, totalTime: Int?
|
||||||
|
let workoutStartTime: String
|
||||||
|
let notes: String?
|
||||||
|
let totalCalories: Int?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id, workout
|
||||||
|
case createdAt = "created_at"
|
||||||
|
case updatedAt = "updated_at"
|
||||||
|
case difficulty
|
||||||
|
case totalTime = "total_time"
|
||||||
|
case workoutStartTime = "workout_start_time"
|
||||||
|
case notes
|
||||||
|
case totalCalories = "total_calories"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,28 +18,28 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
|
|
||||||
static let shared = BridgeModule()
|
static let shared = BridgeModule()
|
||||||
@Published var isShowingOnExternalDisplay = false
|
@Published var isShowingOnExternalDisplay = false
|
||||||
|
|
||||||
private var timer: Timer?
|
@Published var isInWorkout = false
|
||||||
@Published var timeLeft: Int = 0
|
var completedWorkout: (() -> Void)?
|
||||||
|
|
||||||
@Published var currentExercise: ExerciseElement?
|
|
||||||
var currentWorkout: Workout?
|
|
||||||
var currentExerciseIdx: Int = -1
|
|
||||||
|
|
||||||
var workoutStartDate: Date?
|
|
||||||
|
|
||||||
// workoutEndDate ties into WatchPackageModel.workoutEndDate which
|
|
||||||
// tells the watch app to stop the workout
|
|
||||||
var workoutEndDate: Date?
|
|
||||||
@Published var currentWorkoutRunTimeInSeconds: Int = -1
|
@Published var currentWorkoutRunTimeInSeconds: Int = -1
|
||||||
private var currentWorkoutRunTimer: Timer?
|
private var currentWorkoutRunTimer: Timer?
|
||||||
|
|
||||||
@Published var isInWorkout = false
|
var currentWorkout: Workout?
|
||||||
var completedWorkoutFromWatch: (() -> Void)?
|
public private(set) var workoutStartDate: Date?
|
||||||
var totalCaloire: Float?
|
|
||||||
var heartRates: [Int]?
|
private var currentExerciseTimer: Timer?
|
||||||
|
public private(set) var currentExerciseIdx: Int = -1
|
||||||
func start(workout: Workout, atExerciseIndex: Int = 0) {
|
@Published var currentExerciseTimeLeft: Int = 0
|
||||||
|
@Published var currentExercise: ExerciseElement?
|
||||||
|
|
||||||
|
private var isWatchConnected = false
|
||||||
|
// workoutEndDate fills out WatchPackageModel.workoutEndDate which
|
||||||
|
// tells the watch app to stop the workout
|
||||||
|
public private(set) var workoutEndDate: Date?
|
||||||
|
public private(set) var totalCaloire: Float?
|
||||||
|
public private(set) var heartRates: [Int]?
|
||||||
|
|
||||||
|
func start(workout: Workout) {
|
||||||
self.currentWorkout = workout
|
self.currentWorkout = workout
|
||||||
currentWorkoutRunTimeInSeconds = 0
|
currentWorkoutRunTimeInSeconds = 0
|
||||||
currentWorkoutRunTimer?.invalidate()
|
currentWorkoutRunTimer?.invalidate()
|
||||||
@@ -58,23 +58,35 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func goToExerciseAt(index: Int) {
|
||||||
|
guard let currentWorkout = currentWorkout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentExerciseIdx = index
|
||||||
|
let exercise = currentWorkout.exercises[index]
|
||||||
|
updateCurrent(exercise: exercise)
|
||||||
|
}
|
||||||
|
|
||||||
func resetCurrentWorkout() {
|
func resetCurrentWorkout() {
|
||||||
currentWorkoutRunTimeInSeconds = 0
|
DispatchQueue.main.async {
|
||||||
currentWorkoutRunTimer?.invalidate()
|
self.currentWorkoutRunTimeInSeconds = 0
|
||||||
currentWorkoutRunTimer = nil
|
self.currentWorkoutRunTimer?.invalidate()
|
||||||
|
self.currentWorkoutRunTimer = nil
|
||||||
currentWorkoutRunTimer?.invalidate()
|
|
||||||
currentWorkoutRunTimer = nil
|
self.currentExerciseTimer?.invalidate()
|
||||||
|
self.currentExerciseTimer = nil
|
||||||
currentWorkoutRunTimeInSeconds = -1
|
|
||||||
currentExerciseIdx = -1
|
self.currentWorkoutRunTimeInSeconds = -1
|
||||||
|
self.currentExerciseIdx = -1
|
||||||
currentExercise = nil
|
|
||||||
currentWorkout = nil
|
self.currentExercise = nil
|
||||||
|
self.currentWorkout = nil
|
||||||
isInWorkout = false
|
|
||||||
workoutStartDate = nil
|
self.isInWorkout = false
|
||||||
workoutEndDate = nil
|
self.workoutStartDate = nil
|
||||||
|
self.workoutEndDate = nil
|
||||||
|
}
|
||||||
|
|
||||||
let watchModel = WatchPackageModel(currentExerciseName: "", currentTimeLeft: -100, workoutStartDate: Date())
|
let watchModel = WatchPackageModel(currentExerciseName: "", currentTimeLeft: -100, workoutStartDate: Date())
|
||||||
let data = try! JSONEncoder().encode(watchModel)
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
@@ -94,30 +106,26 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
|
|
||||||
private func startTimerWith(duration: Int) {
|
private func startTimerWith(duration: Int) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.timer?.invalidate()
|
self.currentExerciseTimer?.invalidate()
|
||||||
self.timer = nil
|
self.currentExerciseTimer = nil
|
||||||
self.timeLeft = duration
|
self.currentExerciseTimeLeft = duration
|
||||||
self.timer = Timer.scheduledTimer(timeInterval: 1,
|
self.currentExerciseTimer = Timer.scheduledTimer(timeInterval: 1,
|
||||||
target: self,
|
target: self,
|
||||||
selector: #selector(self.updateCounter),
|
selector: #selector(self.updateCurrentExerciseTimer),
|
||||||
userInfo: nil,
|
userInfo: nil,
|
||||||
repeats: true)
|
repeats: true)
|
||||||
self.timer?.fire()
|
self.currentExerciseTimer?.fire()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateCounter() {
|
@objc func updateCurrentExerciseTimer() {
|
||||||
if timeLeft > 0 {
|
if currentExerciseTimeLeft > 0 {
|
||||||
timeLeft -= 1
|
currentExerciseTimeLeft -= 1
|
||||||
|
|
||||||
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: timeLeft, workoutStartDate: workoutStartDate ?? Date())
|
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date())
|
||||||
let data = try! JSONEncoder().encode(watchModel)
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
send(data)
|
send(data)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
timer?.invalidate()
|
|
||||||
timer = nil
|
|
||||||
|
|
||||||
nextExercise()
|
nextExercise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +137,7 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
let nextExercise = currentWorkout.exercises[currentExerciseIdx]
|
let nextExercise = currentWorkout.exercises[currentExerciseIdx]
|
||||||
updateCurrent(exercise: nextExercise)
|
updateCurrent(exercise: nextExercise)
|
||||||
} else {
|
} else {
|
||||||
|
completeWorkout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,11 +155,22 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func completeWorkout() {
|
||||||
|
workoutEndDate = Date()
|
||||||
|
|
||||||
|
//if connected to watch
|
||||||
|
if WCSession.default.isReachable {
|
||||||
|
self.sendWorkoutCompleteToWatch()
|
||||||
|
} else {
|
||||||
|
completedWorkout?()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BridgeModule: WCSessionDelegate {
|
extension BridgeModule: WCSessionDelegate {
|
||||||
func sendWorkoutCompleteToWatch() {
|
func sendWorkoutCompleteToWatch() {
|
||||||
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: timeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date())
|
let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: currentExerciseTimeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date())
|
||||||
let data = try! JSONEncoder().encode(watchModel)
|
let data = try! JSONEncoder().encode(watchModel)
|
||||||
send(data)
|
send(data)
|
||||||
}
|
}
|
||||||
@@ -165,7 +184,7 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
||||||
totalCaloire = Float(model.totalBurnedEnergery)
|
totalCaloire = Float(model.totalBurnedEnergery)
|
||||||
heartRates = model.allHeartRates
|
heartRates = model.allHeartRates
|
||||||
completedWorkoutFromWatch?()
|
completedWorkout?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,16 +192,25 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
|
|
||||||
func session(_ session: WCSession,
|
func session(_ session: WCSession,
|
||||||
activationDidCompleteWith activationState: WCSessionActivationState,
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||||
error: Error?) {}
|
error: Error?) {
|
||||||
|
switch activationState {
|
||||||
|
case .notActivated:
|
||||||
|
print("notActivated")
|
||||||
|
case .inactive:
|
||||||
|
print("inactive")
|
||||||
|
case .activated:
|
||||||
|
print("activated")
|
||||||
|
}
|
||||||
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
func sessionDidBecomeInactive(_ session: WCSession) {}
|
func sessionDidBecomeInactive(_ session: WCSession) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func sessionDidDeactivate(_ session: WCSession) {
|
func sessionDidDeactivate(_ session: WCSession) {
|
||||||
session.activate()
|
session.activate()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
func send(_ data: Data) {
|
func send(_ data: Data) {
|
||||||
guard WCSession.default.activationState == .activated else {
|
guard WCSession.default.activationState == .activated else {
|
||||||
return
|
return
|
||||||
@@ -196,8 +224,7 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
WCSession.default.sendMessageData(data, replyHandler: nil)
|
WCSession.default.sendMessageData(data, replyHandler: nil) { error in
|
||||||
{ error in
|
|
||||||
print("Cannot send message: \(String(describing: error))")
|
print("Cannot send message: \(String(describing: error))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class CompleteWorkoutFetchable: Postable {
|
|||||||
var postableData: [String : Any]?
|
var postableData: [String : Any]?
|
||||||
var successStatus = 201
|
var successStatus = 201
|
||||||
|
|
||||||
typealias Response = CompletedWorkout
|
typealias Response = CompletedWorkoutPOSTReturn
|
||||||
var endPoint: String = "/workout/complete/"
|
var endPoint: String = "/workout/complete/"
|
||||||
|
|
||||||
init(postData: [String: Any]) {
|
init(postData: [String: Any]) {
|
||||||
|
|||||||
@@ -121,7 +121,8 @@ struct CompletedWorkoutView: View {
|
|||||||
|
|
||||||
func heartRates() -> some View {
|
func heartRates() -> some View {
|
||||||
VStack {
|
VStack {
|
||||||
if let heartRates = postData["heart_rates"] as? [Int] {
|
if let heartRates = postData["heart_rates"] as? [Int],
|
||||||
|
heartRates.count > 0 {
|
||||||
let avg = heartRates.reduce(0, +)/heartRates.count
|
let avg = heartRates.reduce(0, +)/heartRates.count
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "heart")
|
Image(systemName: "heart")
|
||||||
|
|||||||
@@ -113,9 +113,9 @@ struct ExtCountdownView: View {
|
|||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
if let duration = currenExercise.duration {
|
if let duration = currenExercise.duration {
|
||||||
ProgressView(value: Float(bridgeModule.timeLeft), total: Float(duration))
|
ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration))
|
||||||
.scaleEffect(x: 1, y: 6, anchor: .center)
|
.scaleEffect(x: 1, y: 6, anchor: .center)
|
||||||
Text("\(bridgeModule.timeLeft)")
|
Text("\(bridgeModule.currentExerciseTimeLeft)")
|
||||||
.font(Font.system(size: 75))
|
.font(Font.system(size: 75))
|
||||||
.padding([.leading, .trailing])
|
.padding([.leading, .trailing])
|
||||||
} else if let reps = currenExercise.reps {
|
} else if let reps = currenExercise.reps {
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ struct WorkoutDetailView: View {
|
|||||||
CountdownView()
|
CountdownView()
|
||||||
ExerciseListView(workout: workout)
|
ExerciseListView(workout: workout)
|
||||||
ActionsView(completedWorkout: {
|
ActionsView(completedWorkout: {
|
||||||
bridgeModule.workoutEndDate = Date()
|
bridgeModule.completeWorkout()
|
||||||
bridgeModule.sendWorkoutCompleteToWatch()
|
|
||||||
}, planWorkout: { workout in
|
}, planWorkout: { workout in
|
||||||
workoutToPlan = workout
|
workoutToPlan = workout
|
||||||
}, workout: workout, showAddToCalendar: showAddToCalendar)
|
}, workout: workout, showAddToCalendar: showAddToCalendar)
|
||||||
@@ -62,7 +61,7 @@ struct WorkoutDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear{
|
.onAppear{
|
||||||
bridgeModule.completedWorkoutFromWatch = {
|
bridgeModule.completedWorkout = {
|
||||||
if let workoutData = createWorkoutData() {
|
if let workoutData = createWorkoutData() {
|
||||||
presentedSheet = .completedWorkout(workoutData)
|
presentedSheet = .completedWorkout(workoutData)
|
||||||
bridgeModule.resetCurrentWorkout()
|
bridgeModule.resetCurrentWorkout()
|
||||||
@@ -223,7 +222,7 @@ struct ExerciseListView: View {
|
|||||||
|
|
||||||
Text(obj.exercise.name)
|
Text(obj.exercise.name)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
bridgeModule.start(workout: workout, atExerciseIndex: i)
|
bridgeModule.goToExerciseAt(index: i)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -257,8 +256,8 @@ struct CountdownView: View {
|
|||||||
VStack {
|
VStack {
|
||||||
if let duration = bridgeModule.currentExercise?.duration {
|
if let duration = bridgeModule.currentExercise?.duration {
|
||||||
HStack {
|
HStack {
|
||||||
ProgressView(value: Float(bridgeModule.timeLeft), total: Float(duration))
|
ProgressView(value: Float(bridgeModule.currentExerciseTimeLeft), total: Float(duration))
|
||||||
Text("\(bridgeModule.timeLeft)")
|
Text("\(bridgeModule.currentExerciseTimeLeft)")
|
||||||
}.padding(16)
|
}.padding(16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user