@kingstinct/react-native-healthkit 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/ios/ReactNativeHealthkit.m +15 -10
- package/ios/ReactNativeHealthkit.swift +364 -175
- package/lib/commonjs/index.ios.js +17 -8
- package/lib/commonjs/index.ios.js.map +1 -1
- package/lib/commonjs/index.js +9 -8
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native-types.js +17 -1
- package/lib/commonjs/native-types.js.map +1 -1
- package/lib/commonjs/types.js +4 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/example/App.js +197 -0
- package/lib/index.ios.js +310 -0
- package/lib/index.js +44 -0
- package/lib/module/index.ios.js +17 -8
- package/lib/module/index.ios.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native-types.js +16 -1
- package/lib/module/native-types.js.map +1 -1
- package/lib/module/types.js +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/native-types.js +447 -0
- package/lib/src/index.ios.js +314 -0
- package/lib/src/index.js +45 -0
- package/lib/src/native-types.js +453 -0
- package/lib/src/types.js +1 -0
- package/lib/types.js +1 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.ios.d.ts +1 -1
- package/lib/typescript/src/native-types.d.ts +29 -1
- package/lib/typescript/src/types.d.ts +3 -1
- package/package.json +8 -7
- package/src/index.ios.tsx +17 -12
- package/src/index.tsx +2 -1
- package/src/native-types.ts +65 -33
- package/src/types.ts +8 -2
- package/ios/.DS_Store +0 -0
- package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
- package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/ReactNativeHealthkit.xcodeproj/project.xcworkspace/xcuserdata/robertherber.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/ReactNativeHealthkit.xcodeproj/xcuserdata/robertherber.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/* eslint-disable react-native/no-inline-styles */
|
|
2
|
+
import 'expo-dev-client';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Button, ScrollView, Text } from 'react-native';
|
|
5
|
+
import { DataTable } from 'react-native-paper';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
import Healthkit, { HKCategoryTypeIdentifier, HKCharacteristicTypeIdentifier, HKCorrelationTypeIdentifier, HKInsulinDeliveryReason, HKQuantityTypeIdentifier, HKStatisticsOptions, HKUnit, HKWeatherCondition, HKWorkoutActivityType, } from '../src/index'; // this way we can work with the working copy - but keep in mind native changes requires a new build 🚀
|
|
8
|
+
const DisplayWorkout = ({ workout }) => {
|
|
9
|
+
return (React.createElement(DataTable.Row, null,
|
|
10
|
+
React.createElement(DataTable.Cell, null, HKWorkoutActivityType[workout.workoutActivityType]),
|
|
11
|
+
React.createElement(DataTable.Cell, { style: { paddingRight: 10 }, numeric: true }, workout ? workout.duration.toFixed(0) + 's' : '-'),
|
|
12
|
+
React.createElement(DataTable.Cell, null, workout
|
|
13
|
+
? workout.totalDistance?.quantity.toFixed(1) +
|
|
14
|
+
' ' +
|
|
15
|
+
workout.totalDistance?.unit
|
|
16
|
+
: '-'),
|
|
17
|
+
React.createElement(DataTable.Cell, null, workout
|
|
18
|
+
? workout.totalEnergyBurned?.quantity.toFixed(1) +
|
|
19
|
+
' ' +
|
|
20
|
+
workout.totalEnergyBurned?.unit
|
|
21
|
+
: '-')));
|
|
22
|
+
};
|
|
23
|
+
const DisplayQuantitySample = ({ title, sample }) => {
|
|
24
|
+
return (React.createElement(DataTable.Row, null,
|
|
25
|
+
React.createElement(DataTable.Cell, null, title),
|
|
26
|
+
React.createElement(DataTable.Cell, { style: { paddingRight: 10 }, numeric: true }, sample ? sample.quantity.toFixed(1) : '-'),
|
|
27
|
+
React.createElement(DataTable.Cell, null, sample ? sample.unit : '-'),
|
|
28
|
+
React.createElement(DataTable.Cell, null, sample ? sample.startDate.toLocaleTimeString() : '-')));
|
|
29
|
+
};
|
|
30
|
+
const DisplayCategorySample = ({ title, sample }) => {
|
|
31
|
+
return (React.createElement(DataTable.Row, null,
|
|
32
|
+
React.createElement(DataTable.Cell, null, title),
|
|
33
|
+
React.createElement(DataTable.Cell, { style: { paddingRight: 10 }, numeric: true }, sample ? sample.value : '-'),
|
|
34
|
+
React.createElement(DataTable.Cell, null, sample ? sample.startDate.toLocaleTimeString() : '-'),
|
|
35
|
+
React.createElement(DataTable.Cell, null, sample ? sample.endDate.toLocaleTimeString() : '-')));
|
|
36
|
+
};
|
|
37
|
+
const DisplayStat = ({ title, sample }) => {
|
|
38
|
+
return (React.createElement(DataTable.Row, null,
|
|
39
|
+
React.createElement(DataTable.Cell, null, title),
|
|
40
|
+
React.createElement(DataTable.Cell, { style: { paddingRight: 10 }, numeric: true }, sample ? sample.quantity.toFixed(1) : '-'),
|
|
41
|
+
React.createElement(DataTable.Cell, null, sample ? sample.unit : '-'),
|
|
42
|
+
React.createElement(DataTable.Cell, null, "N/A")));
|
|
43
|
+
};
|
|
44
|
+
function DataView() {
|
|
45
|
+
const [dateOfBirth, setDateOfBirth] = React.useState(null);
|
|
46
|
+
const [bloodGlucoseSamples, setBloodGlucoseSamples] = React.useState(null);
|
|
47
|
+
const bodyFat = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.bodyFatPercentage);
|
|
48
|
+
const bloodGlucose = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.bloodGlucose);
|
|
49
|
+
const bodyWeight = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.bodyMass);
|
|
50
|
+
const heartRate = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.heartRate);
|
|
51
|
+
const lastWorkout = Healthkit.useMostRecentWorkout();
|
|
52
|
+
const lastMindfulSession = Healthkit.useMostRecentCategorySample(HKCategoryTypeIdentifier.mindfulSession);
|
|
53
|
+
const walkingSpeed = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.walkingSpeed);
|
|
54
|
+
const sixMinWalk = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.sixMinuteWalkTestDistance);
|
|
55
|
+
const walkingStepLength = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.walkingStepLength);
|
|
56
|
+
const walkingAsymmetryPercentage = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.walkingAsymmetryPercentage);
|
|
57
|
+
const walkingDoubleSupportPercentage = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.walkingDoubleSupportPercentage);
|
|
58
|
+
const stairAscentSpeed = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.stairAscentSpeed);
|
|
59
|
+
const stairDescentSpeed = Healthkit.useMostRecentQuantitySample(HKQuantityTypeIdentifier.stairDescentSpeed);
|
|
60
|
+
const [queryStatisticsResponse, setQueryStatisticsResponse] = React.useState(null);
|
|
61
|
+
const writeSampleToHealthkit = () => {
|
|
62
|
+
Healthkit.saveQuantitySample(HKQuantityTypeIdentifier.insulinDelivery, HKUnit.InternationalUnit, 4.2, {
|
|
63
|
+
metadata: {
|
|
64
|
+
HKInsulinDeliveryReason: HKInsulinDeliveryReason.basal,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
Healthkit.saveCorrelationSample(HKCorrelationTypeIdentifier.food, [
|
|
68
|
+
{
|
|
69
|
+
quantityType: HKQuantityTypeIdentifier.dietaryCaffeine,
|
|
70
|
+
unit: HKUnit.Grams,
|
|
71
|
+
quantity: 1,
|
|
72
|
+
metadata: {},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
quantityType: HKQuantityTypeIdentifier.dietaryEnergyConsumed,
|
|
76
|
+
unit: HKUnit.Kilocalories,
|
|
77
|
+
quantity: 1,
|
|
78
|
+
metadata: {},
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
Healthkit.saveWorkoutSample(HKWorkoutActivityType.archery, [
|
|
82
|
+
{
|
|
83
|
+
quantityType: HKQuantityTypeIdentifier.activeEnergyBurned,
|
|
84
|
+
unit: HKUnit.Kilocalories,
|
|
85
|
+
quantity: 63,
|
|
86
|
+
metadata: {},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
quantityType: HKQuantityTypeIdentifier.appleExerciseTime,
|
|
90
|
+
unit: HKUnit.Minutes,
|
|
91
|
+
quantity: 11,
|
|
92
|
+
metadata: {},
|
|
93
|
+
},
|
|
94
|
+
], new Date(), {
|
|
95
|
+
metadata: {
|
|
96
|
+
HKWeatherCondition: HKWeatherCondition.hurricane,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
Healthkit.getDateOfBirth().then(setDateOfBirth);
|
|
100
|
+
Healthkit.queryStatisticsForQuantity(HKQuantityTypeIdentifier.heartRate, [
|
|
101
|
+
HKStatisticsOptions.discreteAverage,
|
|
102
|
+
HKStatisticsOptions.discreteMax,
|
|
103
|
+
HKStatisticsOptions.discreteMin,
|
|
104
|
+
], dayjs().startOf('day').toDate()).then(setQueryStatisticsResponse);
|
|
105
|
+
Healthkit.queryQuantitySamples(HKQuantityTypeIdentifier.bloodGlucose, {
|
|
106
|
+
ascending: true,
|
|
107
|
+
from: dayjs().startOf('day').toDate(),
|
|
108
|
+
to: new Date(),
|
|
109
|
+
}).then(setBloodGlucoseSamples);
|
|
110
|
+
};
|
|
111
|
+
console.log(walkingDoubleSupportPercentage);
|
|
112
|
+
return (React.createElement(ScrollView, { style: { flex: 1, paddingTop: 40 } },
|
|
113
|
+
React.createElement(Button, { title: "Write Sample to HealthKit", onPress: () => writeSampleToHealthkit() }),
|
|
114
|
+
React.createElement(Text, null,
|
|
115
|
+
"Date of birth: ",
|
|
116
|
+
dateOfBirth?.toLocaleDateString()),
|
|
117
|
+
React.createElement(DataTable, null,
|
|
118
|
+
React.createElement(DataTable.Header, null,
|
|
119
|
+
React.createElement(DataTable.Title, null, "Metric"),
|
|
120
|
+
React.createElement(DataTable.Title, { style: { paddingRight: 10 }, numeric: true }, "Value"),
|
|
121
|
+
React.createElement(DataTable.Title, null, "Unit"),
|
|
122
|
+
React.createElement(DataTable.Title, null, "Time")),
|
|
123
|
+
React.createElement(DisplayQuantitySample, { sample: bodyFat, title: "Body fat" }),
|
|
124
|
+
React.createElement(DisplayQuantitySample, { sample: bodyWeight, title: "Weight" }),
|
|
125
|
+
React.createElement(DisplayQuantitySample, { sample: heartRate, title: "Heart rate" }),
|
|
126
|
+
React.createElement(DisplayQuantitySample, { sample: bloodGlucose, title: "Glucose" }),
|
|
127
|
+
React.createElement(DisplayStat, { sample: queryStatisticsResponse?.averageQuantity, title: "Avg. HR" }),
|
|
128
|
+
React.createElement(DisplayStat, { sample: queryStatisticsResponse?.maximumQuantity, title: "High HR" }),
|
|
129
|
+
React.createElement(DisplayStat, { sample: queryStatisticsResponse?.minimumQuantity, title: "Low HR" }),
|
|
130
|
+
React.createElement(DisplayCategorySample, { sample: lastMindfulSession, title: "Mindful" }),
|
|
131
|
+
React.createElement(DataTable.Header, null,
|
|
132
|
+
React.createElement(DataTable.Title, null, "Workout"),
|
|
133
|
+
React.createElement(DataTable.Title, { style: { paddingRight: 10 }, numeric: true }, "Duration"),
|
|
134
|
+
React.createElement(DataTable.Title, null, "Distance"),
|
|
135
|
+
React.createElement(DataTable.Title, null, "Energy")),
|
|
136
|
+
lastWorkout ? React.createElement(DisplayWorkout, { workout: lastWorkout }) : null,
|
|
137
|
+
React.createElement(DataTable.Header, null,
|
|
138
|
+
React.createElement(DataTable.Title, null, "Blood Glucose"),
|
|
139
|
+
React.createElement(DataTable.Title, { style: { paddingRight: 10 }, numeric: true }, "Value"),
|
|
140
|
+
React.createElement(DataTable.Title, null, "Units"),
|
|
141
|
+
React.createElement(DataTable.Title, null, "Time")),
|
|
142
|
+
bloodGlucoseSamples
|
|
143
|
+
? bloodGlucoseSamples.map((sample) => (React.createElement(DisplayQuantitySample, { sample: sample, title: "Glucose" })))
|
|
144
|
+
: null,
|
|
145
|
+
React.createElement(DataTable.Header, null,
|
|
146
|
+
React.createElement(DataTable.Title, null, "Mobility"),
|
|
147
|
+
React.createElement(DataTable.Title, { style: { paddingRight: 10 }, numeric: true }, "Value"),
|
|
148
|
+
React.createElement(DataTable.Title, null, "Units"),
|
|
149
|
+
React.createElement(DataTable.Title, null, "Time")),
|
|
150
|
+
React.createElement(DisplayQuantitySample, { sample: walkingSpeed, title: "Walking speed" }),
|
|
151
|
+
React.createElement(DisplayQuantitySample, { sample: sixMinWalk, title: "Six-minute walk test" }),
|
|
152
|
+
React.createElement(DisplayQuantitySample, { sample: walkingStepLength, title: "Walking Step Length" }),
|
|
153
|
+
React.createElement(DisplayQuantitySample, { sample: walkingAsymmetryPercentage, title: "Walking Asymmetry" }),
|
|
154
|
+
React.createElement(DisplayQuantitySample, { sample: walkingDoubleSupportPercentage, title: "Walking Double Support" }),
|
|
155
|
+
React.createElement(DisplayQuantitySample, { sample: stairAscentSpeed, title: "Stair Ascent" }),
|
|
156
|
+
React.createElement(DisplayQuantitySample, { sample: stairDescentSpeed, title: "Stair Descent" }))));
|
|
157
|
+
}
|
|
158
|
+
const App = () => {
|
|
159
|
+
const [hasPermissions, setHasPermissions] = React.useState(false);
|
|
160
|
+
React.useEffect(() => {
|
|
161
|
+
Healthkit.requestAuthorization([
|
|
162
|
+
HKCharacteristicTypeIdentifier.biologicalSex,
|
|
163
|
+
HKCharacteristicTypeIdentifier.bloodType,
|
|
164
|
+
HKCharacteristicTypeIdentifier.dateOfBirth,
|
|
165
|
+
HKCharacteristicTypeIdentifier.fitzpatrickSkinType,
|
|
166
|
+
HKQuantityTypeIdentifier.waistCircumference,
|
|
167
|
+
HKQuantityTypeIdentifier.bodyMassIndex,
|
|
168
|
+
HKQuantityTypeIdentifier.bodyMass,
|
|
169
|
+
HKQuantityTypeIdentifier.heartRate,
|
|
170
|
+
HKQuantityTypeIdentifier.bloodGlucose,
|
|
171
|
+
HKQuantityTypeIdentifier.insulinDelivery,
|
|
172
|
+
HKQuantityTypeIdentifier.activeEnergyBurned,
|
|
173
|
+
HKCategoryTypeIdentifier.mindfulSession,
|
|
174
|
+
HKQuantityTypeIdentifier.dietaryCaffeine,
|
|
175
|
+
HKQuantityTypeIdentifier.dietaryEnergyConsumed,
|
|
176
|
+
HKQuantityTypeIdentifier.walkingSpeed,
|
|
177
|
+
HKQuantityTypeIdentifier.walkingAsymmetryPercentage,
|
|
178
|
+
HKQuantityTypeIdentifier.walkingDoubleSupportPercentage,
|
|
179
|
+
HKQuantityTypeIdentifier.stairAscentSpeed,
|
|
180
|
+
HKQuantityTypeIdentifier.stairDescentSpeed,
|
|
181
|
+
HKQuantityTypeIdentifier.walkingStepLength,
|
|
182
|
+
'HKWorkoutTypeIdentifier',
|
|
183
|
+
], [
|
|
184
|
+
HKQuantityTypeIdentifier.waistCircumference,
|
|
185
|
+
HKQuantityTypeIdentifier.activeEnergyBurned,
|
|
186
|
+
HKQuantityTypeIdentifier.bloodGlucose,
|
|
187
|
+
HKQuantityTypeIdentifier.insulinDelivery,
|
|
188
|
+
HKQuantityTypeIdentifier.bodyFatPercentage,
|
|
189
|
+
HKCategoryTypeIdentifier.mindfulSession,
|
|
190
|
+
HKQuantityTypeIdentifier.dietaryCaffeine,
|
|
191
|
+
HKQuantityTypeIdentifier.dietaryEnergyConsumed,
|
|
192
|
+
'HKWorkoutTypeIdentifier',
|
|
193
|
+
]).then(setHasPermissions);
|
|
194
|
+
}, []);
|
|
195
|
+
return hasPermissions ? (React.createElement(DataView, null)) : (React.createElement(Text, { style: { paddingTop: 40, textAlign: 'center' } }, "Waiting for user to authorize.."));
|
|
196
|
+
};
|
|
197
|
+
export default App;
|
package/lib/index.ios.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import Native, { EventEmitter, HKQuantityTypeIdentifier, HKUnit, } from './native-types';
|
|
3
|
+
const getPreferredUnit = async (type) => {
|
|
4
|
+
const [unit] = await getPreferredUnits([type]);
|
|
5
|
+
return unit;
|
|
6
|
+
};
|
|
7
|
+
const ensureUnit = async (type, providedUnit) => {
|
|
8
|
+
if (providedUnit) {
|
|
9
|
+
return providedUnit;
|
|
10
|
+
}
|
|
11
|
+
const unit = await Native.getPreferredUnits([type]);
|
|
12
|
+
return unit[type];
|
|
13
|
+
};
|
|
14
|
+
function deserializeSample(sample) {
|
|
15
|
+
return {
|
|
16
|
+
...sample,
|
|
17
|
+
startDate: new Date(sample.startDate),
|
|
18
|
+
endDate: new Date(sample.endDate),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function deserializeWorkout(sample) {
|
|
22
|
+
return {
|
|
23
|
+
...sample,
|
|
24
|
+
startDate: new Date(sample.startDate),
|
|
25
|
+
endDate: new Date(sample.endDate),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const deserializCategorySample = (sample) => {
|
|
29
|
+
return {
|
|
30
|
+
...sample,
|
|
31
|
+
startDate: new Date(sample.startDate),
|
|
32
|
+
endDate: new Date(sample.endDate),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const serializeDate = (date) => {
|
|
36
|
+
return date ? date.toISOString() : new Date(0).toISOString();
|
|
37
|
+
};
|
|
38
|
+
const prepareOptions = (options) => {
|
|
39
|
+
const limit = !options.limit || options.limit === Infinity ? 0 : options.limit;
|
|
40
|
+
const ascending = options.ascending ?? limit === 0;
|
|
41
|
+
const from = serializeDate(options.from);
|
|
42
|
+
const to = serializeDate(options.to);
|
|
43
|
+
return { limit, ascending, from, to };
|
|
44
|
+
};
|
|
45
|
+
const queryQuantitySamples = async (identifier, options) => {
|
|
46
|
+
const unit = await ensureUnit(identifier, options.unit);
|
|
47
|
+
const opts = prepareOptions(options);
|
|
48
|
+
const quantitySamples = await Native.queryQuantitySamples(identifier, unit, opts.from, opts.to, opts.limit, opts.ascending);
|
|
49
|
+
return quantitySamples.map(deserializeSample);
|
|
50
|
+
};
|
|
51
|
+
const subscribeToChanges = async (identifier, callback) => {
|
|
52
|
+
const subscription = EventEmitter.addListener('onChange', ({ typeIdentifier }) => {
|
|
53
|
+
if (typeIdentifier === identifier) {
|
|
54
|
+
callback();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const queryId = await Native.subscribeToObserverQuery(identifier).catch((error) => {
|
|
58
|
+
subscription.remove();
|
|
59
|
+
return Promise.reject(error);
|
|
60
|
+
});
|
|
61
|
+
return () => {
|
|
62
|
+
subscription.remove();
|
|
63
|
+
return Native.unsubscribeQuery(queryId);
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
const getMostRecentQuantitySample = async (identifier, unit) => {
|
|
67
|
+
const samples = await queryQuantitySamples(identifier, {
|
|
68
|
+
limit: 1,
|
|
69
|
+
unit: unit,
|
|
70
|
+
});
|
|
71
|
+
return samples[0];
|
|
72
|
+
};
|
|
73
|
+
function useMostRecentWorkout(options) {
|
|
74
|
+
const [workout, setWorkout] = useState(null);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
let cancelSubscription;
|
|
77
|
+
const init = async () => {
|
|
78
|
+
const { energyUnit, distanceUnit } = await getPreferredUnitsTyped(options);
|
|
79
|
+
cancelSubscription = await subscribeToChanges('HKWorkoutTypeIdentifier', () => {
|
|
80
|
+
getMostRecentWorkout({ energyUnit, distanceUnit }).then(setWorkout);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
init();
|
|
84
|
+
return () => {
|
|
85
|
+
cancelSubscription && cancelSubscription();
|
|
86
|
+
};
|
|
87
|
+
}, [options]);
|
|
88
|
+
return workout;
|
|
89
|
+
}
|
|
90
|
+
const getMostRecentCategorySample = async (identifier) => {
|
|
91
|
+
const samples = await queryCategorySamples(identifier, {
|
|
92
|
+
limit: 1,
|
|
93
|
+
ascending: false,
|
|
94
|
+
});
|
|
95
|
+
return samples[0];
|
|
96
|
+
};
|
|
97
|
+
function useMostRecentCategorySample(identifier) {
|
|
98
|
+
const [category, setCategory] = useState(null);
|
|
99
|
+
const updater = useCallback(() => {
|
|
100
|
+
getMostRecentCategorySample(identifier).then(setCategory);
|
|
101
|
+
}, [identifier]);
|
|
102
|
+
useSubscribeToChanges(identifier, updater);
|
|
103
|
+
return category;
|
|
104
|
+
}
|
|
105
|
+
function useSubscribeToChanges(identifier, onChange) {
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
let cancelSubscription;
|
|
108
|
+
const init = async () => {
|
|
109
|
+
cancelSubscription = await subscribeToChanges(identifier, onChange);
|
|
110
|
+
};
|
|
111
|
+
init();
|
|
112
|
+
return () => {
|
|
113
|
+
cancelSubscription && cancelSubscription();
|
|
114
|
+
};
|
|
115
|
+
}, [identifier, onChange]);
|
|
116
|
+
}
|
|
117
|
+
function useMostRecentQuantitySample(identifier, unit) {
|
|
118
|
+
const [lastSample, setLastSample] = useState(null);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
let cancelSubscription;
|
|
121
|
+
const init = async () => {
|
|
122
|
+
const actualUnit = await ensureUnit(identifier, unit);
|
|
123
|
+
cancelSubscription = await subscribeToChanges(identifier, () => {
|
|
124
|
+
getMostRecentQuantitySample(identifier, actualUnit).then((value) => {
|
|
125
|
+
setLastSample(value);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
init();
|
|
130
|
+
return () => {
|
|
131
|
+
cancelSubscription && cancelSubscription();
|
|
132
|
+
};
|
|
133
|
+
}, [identifier, unit]);
|
|
134
|
+
return lastSample;
|
|
135
|
+
}
|
|
136
|
+
const saveQuantitySample = (identifier, unit, value, options) => {
|
|
137
|
+
const start = options?.start || options?.end || new Date();
|
|
138
|
+
const end = options?.end || options?.start || new Date();
|
|
139
|
+
const metadata = options?.metadata || {};
|
|
140
|
+
return Native.saveQuantitySample(identifier, unit, value, start.toISOString(), end.toISOString(), metadata);
|
|
141
|
+
};
|
|
142
|
+
const queryStatisticsForQuantity = async (identifier, options, from, to, unit) => {
|
|
143
|
+
const actualUnit = await ensureUnit(identifier, unit);
|
|
144
|
+
const toDate = to || new Date();
|
|
145
|
+
const { mostRecentQuantityDateInterval, ...rawResponse } = await Native.queryStatisticsForQuantity(identifier, actualUnit, from.toISOString(), toDate.toISOString(), options);
|
|
146
|
+
const response = {
|
|
147
|
+
...rawResponse,
|
|
148
|
+
...(mostRecentQuantityDateInterval
|
|
149
|
+
? {
|
|
150
|
+
mostRecentQuantityDateInterval: {
|
|
151
|
+
from: new Date(mostRecentQuantityDateInterval.from),
|
|
152
|
+
to: new Date(mostRecentQuantityDateInterval.to),
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
156
|
+
};
|
|
157
|
+
return response;
|
|
158
|
+
};
|
|
159
|
+
const requestAuthorization = (read, write = []) => {
|
|
160
|
+
const readPermissions = read.reduce((obj, cur) => {
|
|
161
|
+
return { ...obj, [cur]: true };
|
|
162
|
+
}, {});
|
|
163
|
+
const writePermissions = write.reduce((obj, cur) => {
|
|
164
|
+
return { ...obj, [cur]: true };
|
|
165
|
+
}, {});
|
|
166
|
+
return Native.requestAuthorization(writePermissions, readPermissions);
|
|
167
|
+
};
|
|
168
|
+
const getDateOfBirth = async () => {
|
|
169
|
+
const dateOfBirth = await Native.getDateOfBirth();
|
|
170
|
+
return new Date(dateOfBirth);
|
|
171
|
+
};
|
|
172
|
+
const getRequestStatusForAuthorization = (read, write = []) => {
|
|
173
|
+
const readPermissions = read.reduce((obj, cur) => {
|
|
174
|
+
return { ...obj, [cur]: true };
|
|
175
|
+
}, {});
|
|
176
|
+
const writePermissions = write.reduce((obj, cur) => {
|
|
177
|
+
return { ...obj, [cur]: true };
|
|
178
|
+
}, {});
|
|
179
|
+
return Native.getRequestStatusForAuthorization(writePermissions, readPermissions);
|
|
180
|
+
};
|
|
181
|
+
const queryCategorySamples = async (identifier, options) => {
|
|
182
|
+
const opts = prepareOptions(options);
|
|
183
|
+
const results = await Native.queryCategorySamples(identifier, opts.from, opts.to, opts.limit, opts.ascending);
|
|
184
|
+
return results.map(deserializCategorySample);
|
|
185
|
+
};
|
|
186
|
+
async function getPreferredUnitsTyped(options) {
|
|
187
|
+
let energyUnit = options?.energyUnit;
|
|
188
|
+
let distanceUnit = options?.distanceUnit;
|
|
189
|
+
if (!energyUnit || !distanceUnit) {
|
|
190
|
+
const units = await Native.getPreferredUnits([
|
|
191
|
+
HKQuantityTypeIdentifier.distanceWalkingRunning,
|
|
192
|
+
HKQuantityTypeIdentifier.activeEnergyBurned,
|
|
193
|
+
]);
|
|
194
|
+
if (!energyUnit) {
|
|
195
|
+
energyUnit = units[HKQuantityTypeIdentifier.distanceWalkingRunning];
|
|
196
|
+
}
|
|
197
|
+
if (!distanceUnit) {
|
|
198
|
+
distanceUnit = units[HKQuantityTypeIdentifier.activeEnergyBurned];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!energyUnit) {
|
|
202
|
+
energyUnit = HKUnit.Kilocalories;
|
|
203
|
+
}
|
|
204
|
+
if (!distanceUnit) {
|
|
205
|
+
distanceUnit = HKUnit.Meters;
|
|
206
|
+
}
|
|
207
|
+
return { energyUnit, distanceUnit };
|
|
208
|
+
}
|
|
209
|
+
const queryWorkouts = async (options) => {
|
|
210
|
+
const { energyUnit, distanceUnit } = await getPreferredUnitsTyped(options);
|
|
211
|
+
const opts = prepareOptions(options);
|
|
212
|
+
const workouts = await Native.queryWorkoutSamples(energyUnit, distanceUnit, opts.from, opts.to, opts.limit, opts.ascending);
|
|
213
|
+
return workouts.map(deserializeWorkout);
|
|
214
|
+
};
|
|
215
|
+
const getMostRecentWorkout = async (options) => {
|
|
216
|
+
const workouts = await queryWorkouts({
|
|
217
|
+
limit: 1,
|
|
218
|
+
ascending: false,
|
|
219
|
+
energyUnit: options?.energyUnit,
|
|
220
|
+
distanceUnit: options?.distanceUnit,
|
|
221
|
+
});
|
|
222
|
+
return workouts[0];
|
|
223
|
+
};
|
|
224
|
+
function saveCategorySample(identifier, value, options) {
|
|
225
|
+
const start = options?.start || options?.end || new Date();
|
|
226
|
+
const end = options?.end || options?.start || new Date();
|
|
227
|
+
const metadata = options?.metadata || {};
|
|
228
|
+
return Native.saveCategorySample(identifier, value, start.toISOString(), end.toISOString(), metadata || {});
|
|
229
|
+
}
|
|
230
|
+
const getPreferredUnits = async (identifiers) => {
|
|
231
|
+
const units = await Native.getPreferredUnits(identifiers);
|
|
232
|
+
return identifiers.map((i) => units[i]);
|
|
233
|
+
};
|
|
234
|
+
const buildUnitWithPrefix = (prefix, unit) => {
|
|
235
|
+
return `${prefix}${unit}`;
|
|
236
|
+
};
|
|
237
|
+
function deserializeCorrelation(s) {
|
|
238
|
+
return {
|
|
239
|
+
...s,
|
|
240
|
+
objects: s.objects.map((o) => {
|
|
241
|
+
// @ts-ignore
|
|
242
|
+
if (o.quantity !== undefined) {
|
|
243
|
+
return deserializeSample(o);
|
|
244
|
+
}
|
|
245
|
+
return deserializCategorySample(o);
|
|
246
|
+
}),
|
|
247
|
+
endDate: new Date(s.endDate),
|
|
248
|
+
startDate: new Date(s.startDate),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function ensureMetadata(metadata) {
|
|
252
|
+
return metadata || {};
|
|
253
|
+
}
|
|
254
|
+
const queryCorrelationSamples = async (typeIdentifier, options) => {
|
|
255
|
+
const opts = prepareOptions(options);
|
|
256
|
+
const correlations = await Native.queryCorrelationSamples(typeIdentifier, opts.from, opts.to);
|
|
257
|
+
return correlations.map(deserializeCorrelation);
|
|
258
|
+
};
|
|
259
|
+
const saveCorrelationSample = async (typeIdentifier, samples, options) => {
|
|
260
|
+
const start = (options?.start || new Date()).toISOString();
|
|
261
|
+
const end = (options?.end || new Date()).toISOString();
|
|
262
|
+
return Native.saveCorrelationSample(typeIdentifier, samples, start, end, ensureMetadata(options?.metadata));
|
|
263
|
+
};
|
|
264
|
+
const saveWorkoutSample = (typeIdentifier, quantities, _start, options) => {
|
|
265
|
+
const start = _start.toISOString();
|
|
266
|
+
const end = (options?.end || new Date()).toISOString();
|
|
267
|
+
return Native.saveWorkoutSample(typeIdentifier, quantities, start, end, ensureMetadata(options?.metadata));
|
|
268
|
+
};
|
|
269
|
+
const Healthkit = {
|
|
270
|
+
authorizationStatusFor: Native.authorizationStatusFor,
|
|
271
|
+
isHealthDataAvailable: Native.isHealthDataAvailable,
|
|
272
|
+
buildUnitWithPrefix,
|
|
273
|
+
disableAllBackgroundDelivery: Native.disableAllBackgroundDelivery,
|
|
274
|
+
disableBackgroundDelivery: Native.disableBackgroundDelivery,
|
|
275
|
+
enableBackgroundDelivery: Native.enableBackgroundDelivery,
|
|
276
|
+
// simple convenience getters
|
|
277
|
+
getBiologicalSex: Native.getBiologicalSex,
|
|
278
|
+
getFitzpatrickSkinType: Native.getFitzpatrickSkinType,
|
|
279
|
+
getWheelchairUse: Native.getWheelchairUse,
|
|
280
|
+
getBloodType: Native.getBloodType,
|
|
281
|
+
getDateOfBirth,
|
|
282
|
+
getMostRecentQuantitySample,
|
|
283
|
+
getMostRecentCategorySample,
|
|
284
|
+
getMostRecentWorkout,
|
|
285
|
+
getPreferredUnit,
|
|
286
|
+
getPreferredUnits,
|
|
287
|
+
getRequestStatusForAuthorization,
|
|
288
|
+
// query methods
|
|
289
|
+
queryCategorySamples,
|
|
290
|
+
queryCorrelationSamples,
|
|
291
|
+
queryQuantitySamples,
|
|
292
|
+
queryStatisticsForQuantity,
|
|
293
|
+
queryWorkouts,
|
|
294
|
+
requestAuthorization,
|
|
295
|
+
// save methods
|
|
296
|
+
saveCategorySample,
|
|
297
|
+
saveCorrelationSample,
|
|
298
|
+
saveQuantitySample,
|
|
299
|
+
saveWorkoutSample,
|
|
300
|
+
// subscriptions
|
|
301
|
+
subscribeToChanges,
|
|
302
|
+
// hooks
|
|
303
|
+
useMostRecentCategorySample,
|
|
304
|
+
useMostRecentQuantitySample,
|
|
305
|
+
useMostRecentWorkout,
|
|
306
|
+
useSubscribeToChanges,
|
|
307
|
+
};
|
|
308
|
+
export * from './types';
|
|
309
|
+
export * from './native-types';
|
|
310
|
+
export default Healthkit;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
const notAvailableError = 'Platform "' +
|
|
3
|
+
Platform.OS +
|
|
4
|
+
'" not supported, use isHealthDataAvailable to check for availability before using it';
|
|
5
|
+
const UnavailableFn = () => {
|
|
6
|
+
throw new Error(notAvailableError);
|
|
7
|
+
};
|
|
8
|
+
const Healthkit = {
|
|
9
|
+
authorizationStatusFor: UnavailableFn,
|
|
10
|
+
buildUnitWithPrefix: UnavailableFn,
|
|
11
|
+
disableAllBackgroundDelivery: UnavailableFn,
|
|
12
|
+
disableBackgroundDelivery: UnavailableFn,
|
|
13
|
+
enableBackgroundDelivery: UnavailableFn,
|
|
14
|
+
getBiologicalSex: UnavailableFn,
|
|
15
|
+
getBloodType: UnavailableFn,
|
|
16
|
+
getDateOfBirth: UnavailableFn,
|
|
17
|
+
getFitzpatrickSkinType: UnavailableFn,
|
|
18
|
+
getMostRecentCategorySample: UnavailableFn,
|
|
19
|
+
getMostRecentQuantitySample: UnavailableFn,
|
|
20
|
+
getMostRecentWorkout: UnavailableFn,
|
|
21
|
+
getPreferredUnit: UnavailableFn,
|
|
22
|
+
getPreferredUnits: UnavailableFn,
|
|
23
|
+
getRequestStatusForAuthorization: UnavailableFn,
|
|
24
|
+
getWheelchairUse: UnavailableFn,
|
|
25
|
+
isHealthDataAvailable: () => Promise.resolve(false),
|
|
26
|
+
queryCategorySamples: UnavailableFn,
|
|
27
|
+
queryCorrelationSamples: UnavailableFn,
|
|
28
|
+
queryQuantitySamples: UnavailableFn,
|
|
29
|
+
queryStatisticsForQuantity: UnavailableFn,
|
|
30
|
+
queryWorkouts: UnavailableFn,
|
|
31
|
+
requestAuthorization: UnavailableFn,
|
|
32
|
+
saveCategorySample: UnavailableFn,
|
|
33
|
+
saveCorrelationSample: UnavailableFn,
|
|
34
|
+
saveQuantitySample: UnavailableFn,
|
|
35
|
+
saveWorkoutSample: UnavailableFn,
|
|
36
|
+
subscribeToChanges: UnavailableFn,
|
|
37
|
+
useMostRecentCategorySample: UnavailableFn,
|
|
38
|
+
useMostRecentQuantitySample: UnavailableFn,
|
|
39
|
+
useMostRecentWorkout: UnavailableFn,
|
|
40
|
+
useSubscribeToChanges: UnavailableFn,
|
|
41
|
+
};
|
|
42
|
+
export * from './types';
|
|
43
|
+
export * from './native-types';
|
|
44
|
+
export default Healthkit;
|
package/lib/module/index.ios.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import Native, { EventEmitter, HKQuantityTypeIdentifier, HKUnit } from './native-types';
|
|
3
3
|
|
|
4
4
|
const getPreferredUnit = async type => {
|
|
@@ -63,9 +63,11 @@ const queryQuantitySamples = async (identifier, options) => {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const subscribeToChanges = async (identifier, callback) => {
|
|
66
|
-
const subscription = EventEmitter.addListener('onChange',
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const subscription = EventEmitter.addListener('onChange', _ref => {
|
|
67
|
+
let {
|
|
68
|
+
typeIdentifier
|
|
69
|
+
} = _ref;
|
|
70
|
+
|
|
69
71
|
if (typeIdentifier === identifier) {
|
|
70
72
|
callback();
|
|
71
73
|
}
|
|
@@ -114,7 +116,7 @@ function useMostRecentWorkout(options) {
|
|
|
114
116
|
return workout;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
const getMostRecentCategorySample = async
|
|
119
|
+
const getMostRecentCategorySample = async identifier => {
|
|
118
120
|
const samples = await queryCategorySamples(identifier, {
|
|
119
121
|
limit: 1,
|
|
120
122
|
ascending: false
|
|
@@ -193,7 +195,8 @@ const queryStatisticsForQuantity = async (identifier, options, from, to, unit) =
|
|
|
193
195
|
return response;
|
|
194
196
|
};
|
|
195
197
|
|
|
196
|
-
const requestAuthorization = (read
|
|
198
|
+
const requestAuthorization = function (read) {
|
|
199
|
+
let write = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
197
200
|
const readPermissions = read.reduce((obj, cur) => {
|
|
198
201
|
return { ...obj,
|
|
199
202
|
[cur]: true
|
|
@@ -212,7 +215,8 @@ const getDateOfBirth = async () => {
|
|
|
212
215
|
return new Date(dateOfBirth);
|
|
213
216
|
};
|
|
214
217
|
|
|
215
|
-
const getRequestStatusForAuthorization = (read
|
|
218
|
+
const getRequestStatusForAuthorization = function (read) {
|
|
219
|
+
let write = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
216
220
|
const readPermissions = read.reduce((obj, cur) => {
|
|
217
221
|
return { ...obj,
|
|
218
222
|
[cur]: true
|
|
@@ -336,6 +340,10 @@ const saveWorkoutSample = (typeIdentifier, quantities, _start, options) => {
|
|
|
336
340
|
return Native.saveWorkoutSample(typeIdentifier, quantities, start, end, ensureMetadata(options === null || options === void 0 ? void 0 : options.metadata));
|
|
337
341
|
};
|
|
338
342
|
|
|
343
|
+
const getWorkoutRoutes = workoutUUID => {
|
|
344
|
+
return Native.getWorkoutRoutes(workoutUUID);
|
|
345
|
+
};
|
|
346
|
+
|
|
339
347
|
const Healthkit = {
|
|
340
348
|
authorizationStatusFor: Native.authorizationStatusFor,
|
|
341
349
|
isHealthDataAvailable: Native.isHealthDataAvailable,
|
|
@@ -355,6 +363,7 @@ const Healthkit = {
|
|
|
355
363
|
getPreferredUnit,
|
|
356
364
|
getPreferredUnits,
|
|
357
365
|
getRequestStatusForAuthorization,
|
|
366
|
+
getWorkoutRoutes,
|
|
358
367
|
// query methods
|
|
359
368
|
queryCategorySamples,
|
|
360
369
|
queryCorrelationSamples,
|
|
@@ -375,7 +384,7 @@ const Healthkit = {
|
|
|
375
384
|
useMostRecentWorkout,
|
|
376
385
|
useSubscribeToChanges
|
|
377
386
|
};
|
|
378
|
-
export * from './types';
|
|
379
387
|
export * from './native-types';
|
|
388
|
+
export * from './types';
|
|
380
389
|
export default Healthkit;
|
|
381
390
|
//# sourceMappingURL=index.ios.js.map
|