WIP
This commit is contained in:
@@ -43,6 +43,7 @@
|
|||||||
1CD0C6672A5CA19600970E52 /* BaseURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD0C6662A5CA19600970E52 /* BaseURLs.swift */; };
|
1CD0C6672A5CA19600970E52 /* BaseURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD0C6662A5CA19600970E52 /* BaseURLs.swift */; };
|
||||||
1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD0C6662A5CA19600970E52 /* BaseURLs.swift */; };
|
1CD0C6682A5CA1A200970E52 /* BaseURLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD0C6662A5CA19600970E52 /* BaseURLs.swift */; };
|
||||||
1CD0C66C2A5E4EA100970E52 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CD0C66B2A5E4EA100970E52 /* LaunchScreen.storyboard */; };
|
1CD0C66C2A5E4EA100970E52 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1CD0C66B2A5E4EA100970E52 /* LaunchScreen.storyboard */; };
|
||||||
|
1CEF74AB2A89937800C1AE6A /* HealthKitHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CEF74AA2A89937800C1AE6A /* HealthKitHelper.swift */; };
|
||||||
1CF65A262A3972840042FFBD /* Werkout_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */; };
|
1CF65A262A3972840042FFBD /* Werkout_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */; };
|
||||||
1CF65A282A3972840042FFBD /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A272A3972840042FFBD /* Persistence.swift */; };
|
1CF65A282A3972840042FFBD /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A272A3972840042FFBD /* Persistence.swift */; };
|
||||||
1CF65A2B2A3972840042FFBD /* Werkout_ios.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A292A3972840042FFBD /* Werkout_ios.xcdatamodeld */; };
|
1CF65A2B2A3972840042FFBD /* Werkout_ios.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A292A3972840042FFBD /* Werkout_ios.xcdatamodeld */; };
|
||||||
@@ -160,6 +161,7 @@
|
|||||||
1CD0C6622A5AF62900970E52 /* WorkoutOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutOverviewView.swift; sourceTree = "<group>"; };
|
1CD0C6622A5AF62900970E52 /* WorkoutOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutOverviewView.swift; sourceTree = "<group>"; };
|
||||||
1CD0C6662A5CA19600970E52 /* BaseURLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseURLs.swift; sourceTree = "<group>"; };
|
1CD0C6662A5CA19600970E52 /* BaseURLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseURLs.swift; sourceTree = "<group>"; };
|
||||||
1CD0C66B2A5E4EA100970E52 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
1CD0C66B2A5E4EA100970E52 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
1CEF74AA2A89937800C1AE6A /* HealthKitHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitHelper.swift; sourceTree = "<group>"; };
|
||||||
1CF65A222A3972840042FFBD /* Werkout_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Werkout_ios.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
1CF65A222A3972840042FFBD /* Werkout_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Werkout_ios.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Werkout_iosApp.swift; sourceTree = "<group>"; };
|
1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Werkout_iosApp.swift; sourceTree = "<group>"; };
|
||||||
1CF65A272A3972840042FFBD /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
1CF65A272A3972840042FFBD /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||||
@@ -293,6 +295,7 @@
|
|||||||
1CF65A292A3972840042FFBD /* Werkout_ios.xcdatamodeld */,
|
1CF65A292A3972840042FFBD /* Werkout_ios.xcdatamodeld */,
|
||||||
1CF65A312A3972850042FFBD /* Preview Content */,
|
1CF65A312A3972850042FFBD /* Preview Content */,
|
||||||
1CF65A862A4400E10042FFBD /* ToDo */,
|
1CF65A862A4400E10042FFBD /* ToDo */,
|
||||||
|
1CEF74AA2A89937800C1AE6A /* HealthKitHelper.swift */,
|
||||||
);
|
);
|
||||||
path = Werkout_ios;
|
path = Werkout_ios;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -612,6 +615,7 @@
|
|||||||
1C5190CC2A589D0000885849 /* CountdownView.swift in Sources */,
|
1C5190CC2A589D0000885849 /* CountdownView.swift in Sources */,
|
||||||
1CF65A2B2A3972840042FFBD /* Werkout_ios.xcdatamodeld in Sources */,
|
1CF65A2B2A3972840042FFBD /* Werkout_ios.xcdatamodeld in Sources */,
|
||||||
1C31C8872A55B2CC00350540 /* PlayerUIView.swift in Sources */,
|
1C31C8872A55B2CC00350540 /* PlayerUIView.swift in Sources */,
|
||||||
|
1CEF74AB2A89937800C1AE6A /* HealthKitHelper.swift in Sources */,
|
||||||
1CF65A2D2A3972840042FFBD /* MainView.swift in Sources */,
|
1CF65A2D2A3972840042FFBD /* MainView.swift in Sources */,
|
||||||
1CF65A7D2A41275D0042FFBD /* Network.swift in Sources */,
|
1CF65A7D2A41275D0042FFBD /* Network.swift in Sources */,
|
||||||
1C485C8A2A492BB400A6F896 /* LoginView.swift in Sources */,
|
1C485C8A2A492BB400A6F896 /* LoginView.swift in Sources */,
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ class BridgeModule: NSObject, ObservableObject {
|
|||||||
// workoutEndDate fills out WatchPackageModel.workoutEndDate which
|
// workoutEndDate fills out WatchPackageModel.workoutEndDate which
|
||||||
// tells the watch app to stop the workout
|
// tells the watch app to stop the workout
|
||||||
public private(set) var workoutEndDate: Date?
|
public private(set) var workoutEndDate: Date?
|
||||||
public private(set) var totalCaloire: Float?
|
public private(set) var healthKitUUID: UUID?
|
||||||
public private(set) var heartRates: [Int]?
|
|
||||||
|
|
||||||
var audioPlayer: AVAudioPlayer?
|
var audioPlayer: AVAudioPlayer?
|
||||||
var avPlayer: AVPlayer?
|
var avPlayer: AVPlayer?
|
||||||
@@ -315,8 +314,7 @@ extension BridgeModule: WCSessionDelegate {
|
|||||||
playFinished()
|
playFinished()
|
||||||
case .workoutComplete(let data):
|
case .workoutComplete(let data):
|
||||||
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
||||||
totalCaloire = Float(model.totalBurnedEnergery)
|
healthKitUUID = model.healthKitUUID
|
||||||
heartRates = model.allHeartRates
|
|
||||||
completedWorkout?()
|
completedWorkout?()
|
||||||
case .restartExercise:
|
case .restartExercise:
|
||||||
restartExercise()
|
restartExercise()
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class CurrentWorkoutInfo {
|
|||||||
currentRound = 1
|
currentRound = 1
|
||||||
|
|
||||||
if supersetIndex >= supersets.count {
|
if supersetIndex >= supersets.count {
|
||||||
complete?()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
126
Werkout_ios/HealthKitHelper.swift
Normal file
126
Werkout_ios/HealthKitHelper.swift
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// HealthKitHelper.swift
|
||||||
|
// Werkout_ios
|
||||||
|
//
|
||||||
|
// Created by Trey Tartt on 8/13/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
|
struct HealthKitWorkoutData {
|
||||||
|
var caloriesBurned: Double?
|
||||||
|
var minHeartRate: Double?
|
||||||
|
var maxHeartRate: Double?
|
||||||
|
var avgHeartRate: Double?
|
||||||
|
}
|
||||||
|
|
||||||
|
class HealthKitHelper {
|
||||||
|
// this is dirty and i dont care
|
||||||
|
var returnCount = 0
|
||||||
|
let healthStore = HKHealthStore()
|
||||||
|
|
||||||
|
var healthKitWorkoutData = HealthKitWorkoutData(
|
||||||
|
caloriesBurned: nil,
|
||||||
|
minHeartRate: nil,
|
||||||
|
maxHeartRate: nil,
|
||||||
|
avgHeartRate: nil)
|
||||||
|
|
||||||
|
var completion: ((HealthKitWorkoutData) -> Void)?
|
||||||
|
|
||||||
|
func getDetails(forHealthKitUUID uuid: UUID, completion: @escaping ((HealthKitWorkoutData) -> Void)) {
|
||||||
|
self.completion = completion
|
||||||
|
self.returnCount = 0
|
||||||
|
|
||||||
|
print("get details \(uuid.uuidString)")
|
||||||
|
|
||||||
|
let predicate = HKQuery.predicateForObject(with: uuid)
|
||||||
|
let query = HKSampleQuery(sampleType: HKWorkoutType.workoutType(),
|
||||||
|
predicate: predicate,
|
||||||
|
limit: 0,
|
||||||
|
sortDescriptors: nil)
|
||||||
|
{ (sampleQuery, results, error ) -> Void in
|
||||||
|
|
||||||
|
if let queryError = error {
|
||||||
|
print( "There was an error while reading the samples: \(queryError.localizedDescription)")
|
||||||
|
} else {
|
||||||
|
for samples: HKSample in results! {
|
||||||
|
let workout: HKWorkout = (samples as! HKWorkout)
|
||||||
|
self.getTotalBurned(forWorkout: workout)
|
||||||
|
self.getHeartRateStuff(forWorkout: workout)
|
||||||
|
print("got workout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
healthStore.execute(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeartRateStuff(forWorkout workout: HKWorkout) {
|
||||||
|
print("get heart")
|
||||||
|
let heartType = HKQuantityType.quantityType(forIdentifier: .heartRate)
|
||||||
|
let heartPredicate: NSPredicate? = HKQuery.predicateForSamples(withStart: workout.startDate,
|
||||||
|
end: workout.endDate,
|
||||||
|
options: HKQueryOptions.strictEndDate)
|
||||||
|
|
||||||
|
let heartQuery = HKStatisticsQuery(quantityType: heartType!,
|
||||||
|
quantitySamplePredicate: heartPredicate,
|
||||||
|
options: [.discreteAverage, .discreteMin, .discreteMax],
|
||||||
|
completionHandler: {(query: HKStatisticsQuery, result: HKStatistics?, error: Error?) -> Void in
|
||||||
|
if let result = result,
|
||||||
|
let minValue = result.minimumQuantity(),
|
||||||
|
let maxValue = result.maximumQuantity(),
|
||||||
|
let avgValue = result.averageQuantity() {
|
||||||
|
|
||||||
|
let _minHeartRate = minValue.doubleValue(
|
||||||
|
for: HKUnit(from: "count/min")
|
||||||
|
)
|
||||||
|
|
||||||
|
let _maxHeartRate = maxValue.doubleValue(
|
||||||
|
for: HKUnit(from: "count/min")
|
||||||
|
)
|
||||||
|
|
||||||
|
let _avgHeartRate = avgValue.doubleValue(
|
||||||
|
for: HKUnit(from: "count/min")
|
||||||
|
)
|
||||||
|
self.healthKitWorkoutData.avgHeartRate = _avgHeartRate
|
||||||
|
self.healthKitWorkoutData.minHeartRate = _minHeartRate
|
||||||
|
self.healthKitWorkoutData.maxHeartRate = _maxHeartRate
|
||||||
|
print("got heart")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.shitReturned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
healthStore.execute(heartQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTotalBurned(forWorkout workout: HKWorkout) {
|
||||||
|
print("get total burned")
|
||||||
|
let calType = HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)
|
||||||
|
let calPredicate: NSPredicate? = HKQuery.predicateForSamples(withStart: workout.startDate,
|
||||||
|
end: workout.endDate,
|
||||||
|
options: HKQueryOptions.strictEndDate)
|
||||||
|
|
||||||
|
let calQuery = HKStatisticsQuery(quantityType: calType!,
|
||||||
|
quantitySamplePredicate: calPredicate,
|
||||||
|
options: [.cumulativeSum],
|
||||||
|
completionHandler: {(query: HKStatisticsQuery, result: HKStatistics?, error: Error?) -> Void in
|
||||||
|
if let result = result {
|
||||||
|
self.healthKitWorkoutData.caloriesBurned = result.sumQuantity()?.doubleValue(for: HKUnit.kilocalorie()) ?? -1
|
||||||
|
print("got total burned")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.shitReturned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
healthStore.execute(calQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shitReturned() {
|
||||||
|
returnCount += 1
|
||||||
|
print("\(returnCount)")
|
||||||
|
if returnCount == 2 {
|
||||||
|
self.completion!(healthKitWorkoutData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
enum MainViewTypes: Int, CaseIterable {
|
enum MainViewTypes: Int, CaseIterable {
|
||||||
case AllWorkout = 0
|
case AllWorkout = 0
|
||||||
@@ -27,7 +28,7 @@ struct AllWorkoutsView: View {
|
|||||||
@State var isUpdating = false
|
@State var isUpdating = false
|
||||||
@State var workouts: [Workout]?
|
@State var workouts: [Workout]?
|
||||||
@State var uniqueWorkoutUsers: [RegisteredUser]?
|
@State var uniqueWorkoutUsers: [RegisteredUser]?
|
||||||
|
let healthStore = HKHealthStore()
|
||||||
var bridgeModule = BridgeModule.shared
|
var bridgeModule = BridgeModule.shared
|
||||||
@State public var needsUpdating: Bool = true
|
@State public var needsUpdating: Bool = true
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ struct AllWorkoutsView: View {
|
|||||||
}
|
}
|
||||||
}.onAppear{
|
}.onAppear{
|
||||||
// UserStore.shared.logout()
|
// UserStore.shared.logout()
|
||||||
|
authorizeHealthKit()
|
||||||
maybeUpdateShit()
|
maybeUpdateShit()
|
||||||
}
|
}
|
||||||
.sheet(item: $selectedWorkout) { item in
|
.sheet(item: $selectedWorkout) { item in
|
||||||
@@ -178,6 +180,21 @@ struct AllWorkoutsView: View {
|
|||||||
showLoginView = true
|
showLoginView = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authorizeHealthKit() {
|
||||||
|
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)))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AllWorkoutsView_Previews: PreviewProvider {
|
struct AllWorkoutsView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -6,17 +6,23 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
struct CompletedWorkoutView: View {
|
struct CompletedWorkoutView: View {
|
||||||
@ObservedObject var bridgeModule = BridgeModule.shared
|
@ObservedObject var bridgeModule = BridgeModule.shared
|
||||||
var postData: [String: Any]
|
var postData: [String: Any]
|
||||||
|
let healthKitHelper = HealthKitHelper()
|
||||||
let workout: Workout
|
let workout: Workout
|
||||||
|
let healthKitUUID: UUID?
|
||||||
|
@State var healthKitWorkoutData: HealthKitWorkoutData?
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@State var difficulty: Float = 0
|
@State var difficulty: Float = 0
|
||||||
@State var notes: String = ""
|
@State var notes: String = ""
|
||||||
let completedWorkoutDismissed: ((Bool) -> Void)?
|
let completedWorkoutDismissed: ((Bool) -> Void)?
|
||||||
@State var isUploading: Bool = false
|
@State var isUploading: Bool = false
|
||||||
|
@State var gettingHealthKitData: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -29,12 +35,41 @@ struct CompletedWorkoutView: View {
|
|||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
calsBurned()
|
if let calsBurned = healthKitWorkoutData?.caloriesBurned {
|
||||||
|
HStack {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "flame.fill")
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
.font(.title)
|
||||||
|
VStack {
|
||||||
|
Text("\(calsBurned, specifier: "%.0f")")
|
||||||
|
}
|
||||||
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
heartRates()
|
if let minHeart = healthKitWorkoutData?.minHeartRate,
|
||||||
|
let maxHeart = healthKitWorkoutData?.maxHeartRate,
|
||||||
|
let avgHeart = healthKitWorkoutData?.avgHeartRate {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "heart")
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.font(.title)
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text("\(minHeart, specifier: "%.0f")")
|
||||||
|
Text("-")
|
||||||
|
Text("\(maxHeart, specifier: "%.0f")")
|
||||||
|
}
|
||||||
|
Text("\(avgHeart, specifier: "%.0f")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rateWorkout()
|
rateWorkout()
|
||||||
.frame(maxHeight: 88)
|
.frame(maxHeight: 88)
|
||||||
@@ -48,6 +83,11 @@ struct CompletedWorkoutView: View {
|
|||||||
.overlay(RoundedRectangle(cornerRadius: 16).stroke(Color(uiColor: .clear))).background(Color(uiColor: .init(red: 200/255, green: 200/255, blue: 200/255, alpha: 0.2)))
|
.overlay(RoundedRectangle(cornerRadius: 16).stroke(Color(uiColor: .clear))).background(Color(uiColor: .init(red: 200/255, green: 200/255, blue: 200/255, alpha: 0.2)))
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
if gettingHealthKitData {
|
||||||
|
ProgressView("Getting HealthKit data")
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button("Upload", action: {
|
Button("Upload", action: {
|
||||||
@@ -64,6 +104,16 @@ struct CompletedWorkoutView: View {
|
|||||||
}
|
}
|
||||||
.padding([.leading, .trailing])
|
.padding([.leading, .trailing])
|
||||||
}
|
}
|
||||||
|
.onAppear{
|
||||||
|
if let healthKitUUID = healthKitUUID {
|
||||||
|
gettingHealthKitData = true
|
||||||
|
healthKitHelper.getDetails(forHealthKitUUID: healthKitUUID,
|
||||||
|
completion: { healthKitWorkoutData in
|
||||||
|
self.healthKitWorkoutData = healthKitWorkoutData
|
||||||
|
gettingHealthKitData = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func topViews() -> some View {
|
func topViews() -> some View {
|
||||||
@@ -83,26 +133,13 @@ struct CompletedWorkoutView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func calsBurned() -> some View {
|
|
||||||
VStack {
|
|
||||||
if let cals = postData["total_calories"] as? Float {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "flame.fill")
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
.font(.title)
|
|
||||||
VStack {
|
|
||||||
Text("\(cals, specifier: "%.0f")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rateWorkout() -> some View {
|
func rateWorkout() -> some View {
|
||||||
VStack {
|
VStack {
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
Text("No Rate")
|
||||||
|
.foregroundColor(.black)
|
||||||
Text("Easy")
|
Text("Easy")
|
||||||
.foregroundColor(.green)
|
.foregroundColor(.green)
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -112,7 +149,7 @@ struct CompletedWorkoutView: View {
|
|||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
gradient: Gradient(colors: [.green, .red]),
|
gradient: Gradient(colors: [.black, .green, .red]),
|
||||||
startPoint: .leading,
|
startPoint: .leading,
|
||||||
endPoint: .trailing
|
endPoint: .trailing
|
||||||
)
|
)
|
||||||
@@ -126,33 +163,13 @@ struct CompletedWorkoutView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func heartRates() -> some View {
|
|
||||||
VStack {
|
|
||||||
if let heartRates = postData["heart_rates"] as? [Int],
|
|
||||||
heartRates.count > 0 {
|
|
||||||
let avg = heartRates.reduce(0, +)/heartRates.count
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "heart")
|
|
||||||
.foregroundColor(.red)
|
|
||||||
.font(.title)
|
|
||||||
VStack {
|
|
||||||
HStack {
|
|
||||||
Text("\(heartRates.min() ?? 0)")
|
|
||||||
Text("-")
|
|
||||||
Text("\(heartRates.max() ?? 0)")
|
|
||||||
}
|
|
||||||
Text("\(avg)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func upload(postBody: [String: Any]) {
|
func upload(postBody: [String: Any]) {
|
||||||
var _postBody = postBody
|
var _postBody = postBody
|
||||||
_postBody["difficulty"] = difficulty
|
_postBody["difficulty"] = difficulty
|
||||||
_postBody["notes"] = notes
|
_postBody["notes"] = notes
|
||||||
|
if let healthKitUUID = healthKitUUID {
|
||||||
|
_postBody["health_kit_workout_uuid"] = healthKitUUID.uuidString
|
||||||
|
}
|
||||||
|
|
||||||
CompleteWorkoutFetchable(postData: _postBody).fetch(completion: { result in
|
CompleteWorkoutFetchable(postData: _postBody).fetch(completion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
@@ -163,6 +180,9 @@ struct CompletedWorkoutView: View {
|
|||||||
completedWorkoutDismissed?(true)
|
completedWorkoutDismissed?(true)
|
||||||
}
|
}
|
||||||
case .failure(let failure):
|
case .failure(let failure):
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.isUploading = false
|
||||||
|
}
|
||||||
print(failure)
|
print(failure)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -184,6 +204,7 @@ struct CompletedWorkoutView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
CompletedWorkoutView(postData: CompletedWorkoutView_Previews.postBody,
|
CompletedWorkoutView(postData: CompletedWorkoutView_Previews.postBody,
|
||||||
workout: workout,
|
workout: workout,
|
||||||
|
healthKitUUID: nil,
|
||||||
completedWorkoutDismissed: { _ in })
|
completedWorkoutDismissed: { _ in })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,10 @@ struct WorkoutDetailView: View {
|
|||||||
.sheet(item: $presentedSheet) { item in
|
.sheet(item: $presentedSheet) { item in
|
||||||
switch item {
|
switch item {
|
||||||
case .completedWorkout(let data):
|
case .completedWorkout(let data):
|
||||||
CompletedWorkoutView(postData: data, workout: workout, completedWorkoutDismissed: { uploaded in
|
CompletedWorkoutView(postData: data,
|
||||||
|
workout: workout,
|
||||||
|
healthKitUUID: bridgeModule.healthKitUUID,
|
||||||
|
completedWorkoutDismissed: { uploaded in
|
||||||
if uploaded {
|
if uploaded {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -179,9 +182,7 @@ struct WorkoutDetailView: View {
|
|||||||
"workout_start_time": startTime,
|
"workout_start_time": startTime,
|
||||||
"workout_end_time": endTime,
|
"workout_end_time": endTime,
|
||||||
"workout": workoutid,
|
"workout": workoutid,
|
||||||
"total_time": bridgeModule.currentWorkoutRunTimeInSeconds,
|
"total_time": bridgeModule.currentWorkoutRunTimeInSeconds
|
||||||
"total_calories": bridgeModule.totalCaloire ?? -1,
|
|
||||||
"heart_rates": bridgeModule.heartRates ?? [Int]()
|
|
||||||
] as [String : Any]
|
] as [String : Any]
|
||||||
|
|
||||||
return postBody
|
return postBody
|
||||||
|
|||||||
@@ -15,6 +15,5 @@ struct WatchPackageModel: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct WatchFinishWorkoutModel: Codable {
|
struct WatchFinishWorkoutModel: Codable {
|
||||||
var totalBurnedEnergery: Double
|
var healthKitUUID: UUID
|
||||||
var allHeartRates: [Int]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
|
|
||||||
func autorizeHealthKit() {
|
func autorizeHealthKit() {
|
||||||
let healthKitTypes: Set = [
|
let healthKitTypes: Set = [
|
||||||
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
|
HKObjectType.quantityType(forIdentifier: .heartRate)!,
|
||||||
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||||
|
HKObjectType.quantityType(forIdentifier: .oxygenSaturation)!,
|
||||||
HKQuantityType.workoutType()
|
HKQuantityType.workoutType()
|
||||||
]
|
]
|
||||||
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
|
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
|
||||||
@@ -50,21 +51,6 @@ class WatchMainViewModel: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startHeartRateQuery(quantityTypeIdentifier: HKQuantityTypeIdentifier) {
|
|
||||||
let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
|
|
||||||
let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void = {
|
|
||||||
query, samples, deletedObjects, queryAnchor, error in
|
|
||||||
guard let samples = samples as? [HKQuantitySample] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = HKAnchoredObjectQuery(type: HKObjectType.quantityType(forIdentifier: quantityTypeIdentifier)!, predicate: devicePredicate, anchor: nil, limit: HKObjectQueryNoLimit, resultsHandler: updateHandler)
|
|
||||||
|
|
||||||
query.updateHandler = updateHandler
|
|
||||||
healthStore.execute(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextExercise() {
|
func nextExercise() {
|
||||||
let nextExerciseAction = WatchActions.nextExercise
|
let nextExerciseAction = WatchActions.nextExercise
|
||||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||||
@@ -153,8 +139,13 @@ extension WatchMainViewModel {
|
|||||||
DispatchQueue.main.async() {
|
DispatchQueue.main.async() {
|
||||||
self.hkWorkoutSession = nil
|
self.hkWorkoutSession = nil
|
||||||
self.hkBuilder = nil
|
self.hkBuilder = nil
|
||||||
let totalEnergy = workout?.totalEnergyBurned?.doubleValue(for: .kilocalorie()) ?? -1
|
self.heartRates.removeAll()
|
||||||
let watchFinishWorkoutModel = WatchFinishWorkoutModel(totalBurnedEnergery: totalEnergy, allHeartRates: self.heartRates)
|
self.isInWorkout = 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)
|
||||||
@@ -162,9 +153,6 @@ extension WatchMainViewModel {
|
|||||||
if sendDetails {
|
if sendDetails {
|
||||||
self.send(watchActionData)
|
self.send(watchActionData)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.heartRates.removeAll()
|
|
||||||
self.isInWorkout = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user