Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add getWorkoutPlanId function #76

Merged
merged 67 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
a7ad899
chore: changes to point to library
veritypowell Jul 31, 2023
9566dc3
chore: changes to point to library
veritypowell Jul 31, 2023
e791959
chore: changes to point to library
veritypowell Jul 31, 2023
b52d945
Merge branch 'master' of https://github.com/runna-app/react-native-he…
veritypowell Jul 31, 2023
ae77729
chore: changes to point to library
veritypowell Jul 31, 2023
d2beb97
chore: changes to point to library
veritypowell Jul 31, 2023
13258e2
chore: changes to point to library
veritypowell Jul 31, 2023
49e91ef
chore: add running native types
veritypowell Jul 31, 2023
cbe83b5
chore: add running native types
veritypowell Jul 31, 2023
eef64ca
chore: add running native types
veritypowell Aug 2, 2023
8e93b4f
chore: add running native types
veritypowell Aug 2, 2023
ef27d4b
chore: add running native types
veritypowell Aug 2, 2023
a1dd09f
chore: add running native types
veritypowell Aug 2, 2023
4a105d1
chore: add events
veritypowell Aug 2, 2023
791ed0a
chore: add events
veritypowell Aug 2, 2023
fe7e56f
chore: add events
veritypowell Aug 2, 2023
ada2d93
chore: add events
veritypowell Aug 2, 2023
9cf3a97
chore: add events
veritypowell Aug 3, 2023
16b2225
chore: add events
veritypowell Aug 4, 2023
1e77ca7
chore: add events
veritypowell Aug 4, 2023
6158dae
chore: add activities
veritypowell Aug 8, 2023
36e8a9f
chore: add activities
veritypowell Aug 8, 2023
86ead1e
chore: add activities 2
veritypowell Aug 8, 2023
ee6d9ba
chore: add activities 2
veritypowell Aug 8, 2023
c3e306f
chore: add activities 2
veritypowell Aug 8, 2023
85e3d1a
chore: add activities 2
veritypowell Aug 8, 2023
49cbc49
chore: add activities 2
veritypowell Aug 8, 2023
cf2f321
chore: add activities 2
veritypowell Aug 8, 2023
04f145d
chore: add activities 2
veritypowell Aug 8, 2023
6c5a530
chore: test workoutplan
veritypowell Aug 15, 2023
bd2b79b
chore: test workoutplan 2
veritypowell Aug 16, 2023
c278e67
Merge branch 'kingstinct:master' into master
walterholohan Aug 30, 2023
60f2cbc
chore: PR feedback
walterholohan Aug 30, 2023
c80e053
chore: bob build
walterholohan Aug 30, 2023
8b45ea7
chore: tidy up code
walterholohan Aug 30, 2023
38cc08e
chore: more tidyup
walterholohan Aug 30, 2023
e97f784
chore: small changes
walterholohan Aug 30, 2023
c100a64
feat: add more types
walterholohan Aug 31, 2023
c740b75
chore: add canImport tags
walterholohan Aug 31, 2023
1146385
fix: handle when enddate is nil
walterholohan Aug 31, 2023
e9eaee8
feat: saveWorkoutRoute function
ThomasMoran Sep 1, 2023
34203b1
Merge branch 'master' into feature/create-route
ThomasMoran Sep 1, 2023
8bb9529
chore: bob build
ThomasMoran Sep 1, 2023
8c623a5
chore: remove unneeded changes
ThomasMoran Sep 1, 2023
103e5bb
chore: fix changes
ThomasMoran Sep 1, 2023
3314eba
chore: tidy up
ThomasMoran Sep 1, 2023
b59b11f
chore: tidy up
ThomasMoran Sep 1, 2023
170b6f6
chore: update example app and fix regression
ThomasMoran Sep 1, 2023
fa6e517
chore: bob build
ThomasMoran Sep 1, 2023
34f616b
chore: fix lint errors
ThomasMoran Sep 1, 2023
5a013e7
Merge pull request #1 from runna-app/feature/create-route
ThomasMoran Sep 1, 2023
0073ed1
chore: add read permissions for workout routes
ThomasMoran Sep 4, 2023
7cda2d8
Merge branch 'master' of github.com:runna-app/react-native-healthkit
walterholohan Sep 6, 2023
ec3d7ef
feat: create getWorkoutPlanId function
walterholohan Sep 6, 2023
3389e17
fix: swift placeholder for workoutPlanId
walterholohan Sep 6, 2023
250c83f
chore: add missing configs
walterholohan Sep 6, 2023
9679e8a
fix: return uuid string
walterholohan Sep 6, 2023
b42d0fd
chore: remove lib folder
walterholohan Sep 6, 2023
7c4a301
chore: PR tidy up
walterholohan Sep 6, 2023
29dfefa
chore: tidy up
walterholohan Sep 6, 2023
ff252ac
feat: rename function to getWorkoutPlanById and return id and activit…
walterholohan Sep 8, 2023
edfecf2
feat: rename function to getWorkoutPlanById and return id and activit…
walterholohan Sep 8, 2023
d72d3e5
feat: move getWorkoutPlan into its own async function
walterholohan Sep 8, 2023
81a5e01
feat: handle when function is called by OS less than 17
walterholohan Sep 8, 2023
7ecfd9b
chore: handle xcode 14
walterholohan Sep 8, 2023
89c57db
feat: allow totals in saveWorkoutSamples
ThomasMoran Sep 12, 2023
cf6bc9b
Merge pull request #2 from runna-app/feature/save-workout-totals
ThomasMoran Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ PODS:
- hermes-engine (0.71.8):
- hermes-engine/Pre-built (= 0.71.8)
- hermes-engine/Pre-built (0.71.8)
- kingstinct-react-native-healthkit (6.1.0):
- kingstinct-react-native-healthkit (7.0.6):
- React
- libevent (2.1.12)
- OpenSSL-Universal (1.1.1100)
Expand Down Expand Up @@ -609,7 +609,7 @@ SPEC CHECKSUMS:
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 47986d26692ae75ee7a17ab049caee8864f855de
kingstinct-react-native-healthkit: dccae4f18de0566a64eb4e5a108e0b0942600602
kingstinct-react-native-healthkit: f4205a19d97fd5fa2839a8361c91294cad298896
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
Expand Down Expand Up @@ -648,4 +648,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 765d5dfe48aadc2d21be923b71fd2b906d2f703b

COCOAPODS: 1.12.0
COCOAPODS: 1.12.1
2 changes: 1 addition & 1 deletion ios/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ let HKDataTypeIdentifierHeartbeatSeries = "HKDataTypeIdentifierHeartbeatSeries"

let SpeedUnit = HKUnit(from: "m/s") // HKUnit.meter().unitDivided(by: HKUnit.second())
// Support for MET data: HKAverageMETs 8.24046 kcal/hr·kg
let METUnit = HKUnit(from: "kcal/hr·kg")
let METUnit = HKUnit(from: "kcal/hr·kg")
3 changes: 2 additions & 1 deletion ios/ReactNativeHealthkit.m
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ @interface RCT_EXTERN_MODULE(ReactNativeHealthkit, RCTEventEmitter)
quantities:(NSArray)quantities
start:(NSDate)start
end:(NSDate)end
totals:(NSDictionary)totals
metadata:(NSDictionary)metadata
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
Expand Down Expand Up @@ -211,7 +212,7 @@ @interface RCT_EXTERN_MODULE(ReactNativeHealthkit, RCTEventEmitter)
reject:(RCTPromiseRejectBlock)reject
)

RCT_EXTERN_METHOD(getWorkoutRoutes:(NSString)workoutUUID
RCT_EXTERN_METHOD(getWorkoutPlanById:(NSString)workoutUUID
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject
)
Expand Down
97 changes: 74 additions & 23 deletions ios/ReactNativeHealthkit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
self._runningQueries = [String: HKQuery]()
self._dateFormatter = ISO8601DateFormatter()
self._dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

if HKHealthStore.isHealthDataAvailable() {
self._store = HKHealthStore.init()
}
Expand Down Expand Up @@ -297,7 +296,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
if let type = HKSampleType.categoryType(forIdentifier: typeId) {
let value = sample["value"] as! Int
let metadata = sample["metadata"] as? [String: Any]

let categorySample = HKCategorySample.init(type: type, value: value, start: start, end: end, metadata: metadata)
initializedSamples.insert(categorySample)
}
Expand All @@ -315,8 +313,8 @@ class ReactNativeHealthkit: RCTEventEmitter {
}
}

@objc(saveWorkoutSample:quantities:start:end:metadata:resolve:reject:)
func saveWorkoutSample(typeIdentifier: UInt, quantities: [[String: Any]], start: Date, end: Date, metadata: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
@objc(saveWorkoutSample:quantities:start:end:totals:metadata:resolve:reject:)
func saveWorkoutSample(typeIdentifier: UInt, quantities: [[String: Any]], start: Date, end: Date, totals: [String: Any], metadata: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
guard let store = _store else {
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
}
Expand All @@ -337,7 +335,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
var totalDistance: HKQuantity?
var totalSwimmingStrokeCount: HKQuantity?
var totalFlightsClimbed: HKQuantity?

// generating quantity samples
for quantity in quantities {
let typeId = HKQuantityTypeIdentifier.init(rawValue: quantity["quantityType"] as! String)
Expand All @@ -362,7 +359,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
if typeId == HKQuantityTypeIdentifier.flightsClimbed {
totalFlightsClimbed = quantity
}

if let quantityStart, let quantityEnd {
let quantityStartDate = self._dateFormatter.date(from: quantityStart) ?? start
let quantityEndDate = self._dateFormatter.date(from: quantityEnd) ?? end
Expand All @@ -376,6 +372,17 @@ class ReactNativeHealthkit: RCTEventEmitter {
}
}

// if totals are provided override samples
let rawTotalDistance = totals["distance"] as? Double ?? 0.0
let rawTotalEnergy = totals["energyBurned"] as? Double ?? 0.0

if rawTotalDistance != 0.0 {
totalDistance = HKQuantity(unit: .meter(), doubleValue: rawTotalDistance)
}
if rawTotalEnergy != 0.0 {
totalEnergyBurned = HKQuantity(unit: .kilocalorie(), doubleValue: rawTotalEnergy)
}

// creating workout
var workout: HKWorkout?

Expand Down Expand Up @@ -405,6 +412,10 @@ class ReactNativeHealthkit: RCTEventEmitter {
return
}

if initializedSamples.isEmpty {
return resolve(workout.uuid.uuidString)
}

store.add(initializedSamples, to: workout) { (_, error: Error?) in
guard error == nil else {
reject(GENERIC_ERROR, error!.localizedDescription, error)
Expand Down Expand Up @@ -454,7 +465,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
if clLocations.isEmpty {
return reject(GENERIC_ERROR, "No locations provided", nil)
}

// create route
let routeBuilder = HKWorkoutRouteBuilder(healthStore: store, device: nil)
try await routeBuilder.insertRouteData(clLocations)
Expand Down Expand Up @@ -706,7 +716,6 @@ class ReactNativeHealthkit: RCTEventEmitter {

@objc(queryWorkoutSamples:distanceUnitString:from:to:limit:ascending:resolve:reject:)
func queryWorkoutSamples(energyUnitString: String, distanceUnitString: String, from: Date, to: Date, limit: Int, ascending: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {

guard let store = _store else {
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
}
Expand Down Expand Up @@ -797,21 +806,6 @@ class ReactNativeHealthkit: RCTEventEmitter {
dict.setValue(serializeQuantity(unit: HKUnit.count(), quantity: workout.totalFlightsClimbed), forKey: "totalFlightsClimbed")
}

#if canImport(WorkoutKit)
if #available(iOS 17.0, *) {
Task {
robertherber marked this conversation as resolved.
Show resolved Hide resolved
do {
let workoutplan = try await workout.workoutPlan
if let workoutplanId = workoutplan?.id {
dict["workoutPlanId"] = workoutplanId.uuidString
}
} catch {
// handle error
}
}
}
#endif

arr.add(dict)
}
}
Expand Down Expand Up @@ -1263,6 +1257,63 @@ class ReactNativeHealthkit: RCTEventEmitter {
return allRoutes
}

@available(iOS 17.0.0, *)
func getWorkoutPlan(workout: HKWorkout) async -> [String: Any]? {
#if canImport(WorkoutKit)
do {
let workoutPlan = try await workout.workoutPlan

var dict = [String: Any]()
if (workoutPlan?.id) != nil {
dict["id"] = workoutPlan?.id.uuidString

}
if (workoutPlan?.workout.activity) != nil {
dict["activityType"] = workoutPlan?.workout.activity.rawValue
}

if dict.isEmpty {
return nil
}
return dict
} catch {
return nil
}
#else
return nil
#endif
}

@objc(getWorkoutPlanById:resolve:reject:)
func getWorkoutPlanById(workoutUUID: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
if #available(iOS 17.0, *) {
#if canImport(WorkoutKit)
guard let store = _store else {
return reject(INIT_ERROR, INIT_ERROR_MESSAGE, nil)
}

Task {
if let uuid = UUID(uuidString: workoutUUID) {
let workout = await self.getWorkoutByID(store: store, workoutUUID: uuid)
if let workout {
let workoutPlan = await self.getWorkoutPlan(workout: workout)

return resolve(workoutPlan)
} else {
return reject(GENERIC_ERROR, "No workout found", nil)
}
} else {
return reject(GENERIC_ERROR, "Invalid UUID", nil)
}
}
#else
return resolve(nil)
#endif
} else {
return resolve(nil)
}
}

func serializeLocation(location: CLLocation, previousLocation: CLLocation?) -> [String: Any] {
var distance: CLLocationDistance?
if let previousLocation = previousLocation {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import getMostRecentWorkout from './utils/getMostRecentWorkout'
import getPreferredUnit from './utils/getPreferredUnit'
import getPreferredUnits from './utils/getPreferredUnits'
import getRequestStatusForAuthorization from './utils/getRequestStatusForAuthorization'
import getWorkoutPlanById from './utils/getWorkoutPlanById'
import queryCategorySamples from './utils/queryCategorySamples'
import queryCategorySamplesWithAnchor from './utils/queryCategorySamplesWithAnchor'
import queryCorrelationSamples from './utils/queryCorrelationSamples'
Expand Down Expand Up @@ -150,6 +151,7 @@ export default {
* @see {@link https://developer.apple.com/documentation/healthkit/hkworkoutroutequery HKWorkoutRouteQuery (Apple Docs)}
*/
getWorkoutRoutes,
getWorkoutPlanById,

getPreferredUnit,
getPreferredUnits,
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const Healthkit = {
requestAuthorization: UnavailableFn(Promise.resolve(false)),
deleteQuantitySample: UnavailableFn(Promise.resolve(false)),
deleteSamples: UnavailableFn(Promise.resolve(false)),
getWorkoutPlanById: UnavailableFn(Promise.resolve(null)),
saveCategorySample: UnavailableFn(Promise.resolve(false)),
saveCorrelationSample: UnavailableFn(Promise.resolve(false)),
saveQuantitySample: UnavailableFn(Promise.resolve(false)),
Expand Down
3 changes: 2 additions & 1 deletion src/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NativeModule, NativeModules } from 'react-native'

import type Native from './native-types'

const mockModule: (NativeModule & typeof Native) = {
const mockModule: NativeModule & typeof Native = {
isHealthDataAvailable: jest.fn(),
addListener: jest.fn(),
removeListeners: jest.fn(),
Expand Down Expand Up @@ -40,6 +40,7 @@ const mockModule: (NativeModule & typeof Native) = {
// Todo [>8]: Remove to align with Apple function name (isProtectedDataAvailable)
canAccessProtectedData: jest.fn(),
saveWorkoutRoute: jest.fn(),
getWorkoutPlanById: jest.fn(),
}

NativeModules.ReactNativeHealthkit = mockModule
Loading