umpordez 1.3.2 → 1.4.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.
@@ -1,238 +0,0 @@
1
- /**
2
- * Health — unified surface over Apple Health (iOS, react-native-health)
3
- * and Health Connect (Android, react-native-health-connect).
4
- *
5
- * Both libraries are lazy-loaded with try/require so Expo Go (which
6
- * has neither linked) gets a no-op service instead of a hard crash.
7
- *
8
- * The module exposes a small, opinionated API:
9
- * - requestPermissions — surface the platform's permissions UI
10
- * - getStepsToday — read example
11
- * - writeExerciseSession — write example: minutes-of-X workout
12
- *
13
- * Adapt to whatever data your app actually cares about. Mirror this
14
- * shape: one method per "thing the app needs" rather than
15
- * one-method-per-data-type, so a swap of platforms or libraries
16
- * stays a contained change.
17
- *
18
- * All errors are swallowed by callers — health is best-effort.
19
- * Returns null / 0 on failure rather than throwing.
20
- */
21
-
22
- import { Platform } from 'react-native';
23
-
24
- // ── iOS HealthKit (react-native-health) ──────────────────────────────
25
- interface AppleHealthKit {
26
- initHealthKit: (
27
- options: unknown,
28
- cb: (err: string | null) => void
29
- ) => void;
30
- getStepCount: (
31
- opts: unknown,
32
- cb: (err: string | null, result: { value: number }) => void
33
- ) => void;
34
- saveWorkout: (
35
- opts: unknown,
36
- cb: (err: string | null) => void
37
- ) => void;
38
- Constants?: { Permissions?: Record<string, string> };
39
- }
40
-
41
- // ── Android Health Connect (react-native-health-connect) ─────────────
42
- interface HealthConnect {
43
- initialize: () => Promise<boolean>;
44
- requestPermission: (
45
- permissions: { accessType: string; recordType: string }[]
46
- ) => Promise<{ accessType: string; recordType: string }[]>;
47
- readRecords: (
48
- recordType: string,
49
- opts: { timeRangeFilter: { operator: string; startTime: string; endTime: string } }
50
- ) => Promise<{ records: { count: number }[] }>;
51
- insertRecords: (records: unknown[]) => Promise<unknown>;
52
- }
53
-
54
- let _appleHealth: AppleHealthKit | null | undefined = undefined;
55
- let _healthConnect: HealthConnect | null | undefined = undefined;
56
-
57
- function getAppleHealth(): AppleHealthKit | null {
58
- if (Platform.OS !== 'ios') {
59
- return null;
60
- }
61
- if (_appleHealth !== undefined) {
62
- return _appleHealth;
63
- }
64
- try {
65
- // eslint-disable-next-line @typescript-eslint/no-require-imports
66
- const pkg = require('react-native-health');
67
- _appleHealth = (pkg.default ?? pkg) as AppleHealthKit;
68
- } catch {
69
- _appleHealth = null;
70
- }
71
- return _appleHealth;
72
- }
73
-
74
- function getHealthConnect(): HealthConnect | null {
75
- if (Platform.OS !== 'android') {
76
- return null;
77
- }
78
- if (_healthConnect !== undefined) {
79
- return _healthConnect;
80
- }
81
- try {
82
- // eslint-disable-next-line @typescript-eslint/no-require-imports
83
- _healthConnect = require('react-native-health-connect') as
84
- HealthConnect;
85
- } catch {
86
- _healthConnect = null;
87
- }
88
- return _healthConnect;
89
- }
90
-
91
- const APPLE_PERMISSIONS = {
92
- permissions: {
93
- read: ['Steps'],
94
- write: ['Workout']
95
- }
96
- };
97
-
98
- const ANDROID_PERMISSIONS = [
99
- { accessType: 'read', recordType: 'Steps' },
100
- { accessType: 'write', recordType: 'ExerciseSession' }
101
- ];
102
-
103
- /**
104
- * Surface the platform's permission sheet. Returns true if the user
105
- * granted enough permissions for our subsequent reads/writes to work.
106
- */
107
- export async function requestHealthPermissions(): Promise<boolean> {
108
- if (Platform.OS === 'ios') {
109
- const ahk = getAppleHealth();
110
- if (!ahk) {
111
- return false;
112
- }
113
- return new Promise(resolve => {
114
- ahk.initHealthKit(APPLE_PERMISSIONS, err => {
115
- resolve(!err);
116
- });
117
- });
118
- }
119
- if (Platform.OS === 'android') {
120
- const hc = getHealthConnect();
121
- if (!hc) {
122
- return false;
123
- }
124
- try {
125
- const ready = await hc.initialize();
126
- if (!ready) {
127
- return false;
128
- }
129
- const granted = await hc.requestPermission(
130
- ANDROID_PERMISSIONS
131
- );
132
- return granted.length === ANDROID_PERMISSIONS.length;
133
- } catch {
134
- return false;
135
- }
136
- }
137
- return false;
138
- }
139
-
140
- export async function getStepsToday(): Promise<number> {
141
- const start = new Date();
142
- start.setHours(0, 0, 0, 0);
143
- const end = new Date();
144
-
145
- if (Platform.OS === 'ios') {
146
- const ahk = getAppleHealth();
147
- if (!ahk) {
148
- return 0;
149
- }
150
- return new Promise(resolve => {
151
- ahk.getStepCount({ date: end.toISOString() }, (err, res) => {
152
- if (err) {
153
- resolve(0);
154
- return;
155
- }
156
- resolve(res?.value ?? 0);
157
- });
158
- });
159
- }
160
- if (Platform.OS === 'android') {
161
- const hc = getHealthConnect();
162
- if (!hc) {
163
- return 0;
164
- }
165
- try {
166
- const res = await hc.readRecords('Steps', {
167
- timeRangeFilter: {
168
- operator: 'between',
169
- startTime: start.toISOString(),
170
- endTime: end.toISOString()
171
- }
172
- });
173
- return res.records.reduce((sum, r) => sum + (r.count ?? 0), 0);
174
- } catch {
175
- return 0;
176
- }
177
- }
178
- return 0;
179
- }
180
-
181
- export interface ExerciseSession {
182
- type: string;
183
- startedAt: Date;
184
- durationMs: number;
185
- energyBurned?: number;
186
- }
187
-
188
- /**
189
- * Write a completed exercise session to the platform's health store.
190
- * `type` is loosely free-form on iOS (Apple maps it to HKWorkoutType)
191
- * and constrained on Android (must be a Health Connect ExerciseType).
192
- * For barebones we leave the mapping to the caller — wrap this in a
193
- * domain method like `recordYogaSession` if you only ever record one
194
- * kind.
195
- */
196
- export async function writeExerciseSession(
197
- s: ExerciseSession
198
- ): Promise<boolean> {
199
- const startISO = s.startedAt.toISOString();
200
- const endISO = new Date(
201
- s.startedAt.getTime() + s.durationMs
202
- ).toISOString();
203
-
204
- if (Platform.OS === 'ios') {
205
- const ahk = getAppleHealth();
206
- if (!ahk) {
207
- return false;
208
- }
209
- return new Promise(resolve => {
210
- ahk.saveWorkout({
211
- type: s.type,
212
- startDate: startISO,
213
- endDate: endISO,
214
- energyBurned: s.energyBurned ?? 0,
215
- energyBurnedUnit: 'kilocalorie'
216
- }, err => resolve(!err));
217
- });
218
- }
219
- if (Platform.OS === 'android') {
220
- const hc = getHealthConnect();
221
- if (!hc) {
222
- return false;
223
- }
224
- try {
225
- await hc.insertRecords([{
226
- recordType: 'ExerciseSession',
227
- exerciseType: s.type,
228
- startTime: startISO,
229
- endTime: endISO,
230
- title: s.type
231
- }]);
232
- return true;
233
- } catch {
234
- return false;
235
- }
236
- }
237
- return false;
238
- }