WIP
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "watchos",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
Werkout_watch Watch App/Assets.xcassets/Contents.json
Normal file
6
Werkout_watch Watch App/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
44
Werkout_watch Watch App/ContentView.swift
Normal file
44
Werkout_watch Watch App/ContentView.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Werkout_watch Watch App
|
||||
//
|
||||
// Created by Trey Tartt on 6/22/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
let exercise = PreviewWorkout.parseEquipment()[2]
|
||||
@StateObject var vm = WatchMainViewModel()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if let model = vm.watchPackageModel {
|
||||
Text(model.currentExerciseName)
|
||||
|
||||
Text("\(model.currentTimeLeft )")
|
||||
}
|
||||
|
||||
if let heartValue = vm.heartValue {
|
||||
HStack {
|
||||
Image(systemName: "heart.fill")
|
||||
Text("\(heartValue)")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
vm.nextExercise()
|
||||
}, label: {
|
||||
Image(systemName: "arrow.forward")
|
||||
.font(.title)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
})
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
195
Werkout_watch Watch App/WatchMainViewModel.swift
Normal file
195
Werkout_watch Watch App/WatchMainViewModel.swift
Normal file
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// WatchMainViewMoel.swift
|
||||
// Werkout_watch Watch App
|
||||
//
|
||||
// Created by Trey Tartt on 6/22/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WatchConnectivity
|
||||
import SwiftUI
|
||||
import HealthKit
|
||||
|
||||
class WatchMainViewModel: NSObject, ObservableObject {
|
||||
var session: WCSession
|
||||
@Published var watchPackageModel: WatchPackageModel?
|
||||
@Published var heartValue: Int?
|
||||
|
||||
let healthStore = HKHealthStore()
|
||||
var hkWorkoutSession: HKWorkoutSession? {
|
||||
didSet {
|
||||
print("here")
|
||||
}
|
||||
}
|
||||
var hkBuilder: HKLiveWorkoutBuilder?
|
||||
|
||||
override init() {
|
||||
session = WCSession.default
|
||||
super.init()
|
||||
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
autorizeHealthKit()
|
||||
}
|
||||
|
||||
func autorizeHealthKit() {
|
||||
let healthKitTypes: Set = [
|
||||
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
|
||||
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKQuantityType.workoutType()
|
||||
]
|
||||
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in
|
||||
if !succ {
|
||||
fatalError("Error requesting authorization from health store: \(String(describing: error)))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
let nextExerciseAction = WatchActions.nextExercise
|
||||
let data = try! JSONEncoder().encode(nextExerciseAction)
|
||||
send(data)
|
||||
}
|
||||
}
|
||||
|
||||
extension WatchMainViewModel: WCSessionDelegate {
|
||||
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
||||
if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) {
|
||||
DispatchQueue.main.async {
|
||||
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 send(_ data: Data) {
|
||||
guard WCSession.default.activationState == .activated else {
|
||||
return
|
||||
}
|
||||
#if os(iOS)
|
||||
guard WCSession.default.isWatchAppInstalled else {
|
||||
return
|
||||
}
|
||||
#else
|
||||
guard WCSession.default.isCompanionAppInstalled else {
|
||||
return
|
||||
}
|
||||
#endif
|
||||
WCSession.default.sendMessageData(data, replyHandler: nil)
|
||||
{ error in
|
||||
print("Cannot send message: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
|
||||
func initWorkout() {
|
||||
let configuration = HKWorkoutConfiguration()
|
||||
configuration.activityType = .functionalStrengthTraining
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
|
||||
print("[workoutSession] Changed State: \(toState.rawValue)")
|
||||
}
|
||||
|
||||
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
|
||||
print("[workoutSession] Encountered an error: \(error)")
|
||||
}
|
||||
|
||||
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
|
||||
for type in collectedTypes {
|
||||
guard let quantityType = type as? HKQuantityType else {
|
||||
return
|
||||
}
|
||||
switch quantityType {
|
||||
case HKQuantityType.quantityType(forIdentifier: .heartRate):
|
||||
let statistics = workoutBuilder.statistics(for: quantityType)
|
||||
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
|
||||
let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
|
||||
heartValue = Int(Double(round(1 * value!) / 1))
|
||||
print("[workoutBuilder] Heart Rate: \(String(describing: heartValue))")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
|
||||
guard let workoutEventType = workoutBuilder.workoutEvents.last?.type else { return }
|
||||
print("[workoutBuilderDidCollectEvent] Workout Builder changed event: \(workoutEventType.rawValue)")
|
||||
}
|
||||
}
|
||||
10
Werkout_watch Watch App/Werkout_watch Watch App.entitlements
Normal file
10
Werkout_watch Watch App/Werkout_watch Watch App.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.healthkit</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.healthkit.access</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
17
Werkout_watch Watch App/Werkout_watchApp.swift
Normal file
17
Werkout_watch Watch App/Werkout_watchApp.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Werkout_watchApp.swift
|
||||
// Werkout_watch Watch App
|
||||
//
|
||||
// Created by Trey Tartt on 6/22/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct Werkout_watch_Watch_AppApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user