@tryvital/vital-health-react-native 0.3.2 → 1.1.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/android/build.gradle +5 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/vitalhealthreactnative/VitalHealthEvent.kt +19 -0
- package/android/src/main/java/com/vitalhealthreactnative/VitalHealthReactNativeModule.kt +371 -10
- package/ios/VitalHealthReactNative.m +11 -0
- package/ios/VitalHealthReactNative.swift +45 -2
- package/lib/health_config.d.ts +13 -0
- package/lib/health_config.js +13 -0
- package/lib/index.d.ts +8 -1
- package/lib/index.js +17 -2
- package/package.json +1 -29
- package/src/health_config.ts +15 -0
- package/src/index.tsx +35 -6
package/android/build.gradle
CHANGED
|
@@ -134,6 +134,11 @@ dependencies {
|
|
|
134
134
|
//noinspection GradleDynamicVersion
|
|
135
135
|
implementation "com.facebook.react:react-native:+"
|
|
136
136
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
137
|
+
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
|
|
138
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
|
|
139
|
+
implementation 'com.github.tryVital.vital-android:VitalClient:v1.0.0-alpha.16'
|
|
140
|
+
implementation 'com.github.tryVital.vital-android:VitalHealthConnect:v1.0.0-alpha.16'
|
|
141
|
+
implementation "androidx.health.connect:connect-client:1.0.0-alpha08"
|
|
137
142
|
// From node_modules
|
|
138
143
|
}
|
|
139
144
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.vitalhealthreactnative
|
|
2
|
+
|
|
3
|
+
sealed class VitalHealthEvent(val value: String) {
|
|
4
|
+
object Status : VitalHealthEvent("Status")
|
|
5
|
+
|
|
6
|
+
companion object {
|
|
7
|
+
fun values(): Array<VitalHealthEvent> {
|
|
8
|
+
return arrayOf(Status)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
fun valueOf(value: String): VitalHealthEvent {
|
|
12
|
+
return when (value) {
|
|
13
|
+
"Status" -> Status
|
|
14
|
+
else -> throw IllegalArgumentException("Invalid VitalHealthEvent value: $value")
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -1,49 +1,410 @@
|
|
|
1
1
|
package com.vitalhealthreactnative
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import androidx.health.connect.client.PermissionController
|
|
7
|
+
import androidx.health.connect.client.permission.HealthPermission
|
|
8
|
+
import androidx.health.connect.client.records.*
|
|
9
|
+
import com.facebook.react.bridge.*
|
|
10
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
11
|
+
import io.tryvital.client.Environment
|
|
12
|
+
import io.tryvital.client.Region
|
|
13
|
+
import io.tryvital.client.VitalClient
|
|
14
|
+
import io.tryvital.client.utils.VitalLogger
|
|
15
|
+
import io.tryvital.vitalhealthconnect.VitalHealthConnectManager
|
|
16
|
+
import io.tryvital.vitalhealthconnect.model.HealthResource
|
|
17
|
+
import io.tryvital.vitalhealthconnect.model.SyncStatus
|
|
18
|
+
import kotlinx.coroutines.*
|
|
19
|
+
import java.time.Instant
|
|
20
|
+
import kotlin.reflect.KClass
|
|
7
21
|
|
|
8
22
|
class VitalHealthReactNativeModule(reactContext: ReactApplicationContext) :
|
|
9
|
-
ReactContextBaseJavaModule(reactContext) {
|
|
23
|
+
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
24
|
+
|
|
25
|
+
private val logger = VitalLogger.create()
|
|
26
|
+
|
|
27
|
+
private var vitalClient: VitalClient? = null
|
|
28
|
+
private var vitalHealthConnectManager: VitalHealthConnectManager? = null
|
|
29
|
+
|
|
30
|
+
private var askForResourcesResult: Promise? = null
|
|
31
|
+
private var askedHealthPermissions: Set<HealthPermission>? = null
|
|
32
|
+
|
|
33
|
+
private var mainScope: CoroutineScope? = null
|
|
34
|
+
private var statusScope: CoroutineScope? = null
|
|
35
|
+
private var writeScope: CoroutineScope? = null
|
|
10
36
|
|
|
11
37
|
override fun getName(): String {
|
|
12
38
|
return NAME
|
|
13
39
|
}
|
|
14
40
|
|
|
41
|
+
init {
|
|
42
|
+
reactContext.addActivityEventListener(this)
|
|
43
|
+
}
|
|
44
|
+
|
|
15
45
|
@ReactMethod
|
|
16
46
|
fun configure(
|
|
17
|
-
|
|
47
|
+
syncOnAppStart: Boolean,
|
|
18
48
|
numberOfDaysToBackFill: Int,
|
|
19
49
|
enableLogs: Boolean,
|
|
20
50
|
promise: Promise
|
|
21
51
|
) {
|
|
52
|
+
if (vitalClient == null) {
|
|
53
|
+
promise.reject(
|
|
54
|
+
"VitalClient is not configured",
|
|
55
|
+
"VitalClient is not configured",
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
vitalHealthConnectManager = VitalHealthConnectManager.create(
|
|
60
|
+
reactApplicationContext,
|
|
61
|
+
vitalClient!!.apiKey,
|
|
62
|
+
vitalClient!!.region,
|
|
63
|
+
vitalClient!!.environment
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
mainScope?.cancel()
|
|
67
|
+
mainScope = MainScope()
|
|
68
|
+
mainScope!!.launch {
|
|
69
|
+
vitalHealthConnectManager!!.configureHealthConnectClient(
|
|
70
|
+
logsEnabled = enableLogs,
|
|
71
|
+
syncOnAppStart = syncOnAppStart,
|
|
72
|
+
numberOfDaysToBackFill = numberOfDaysToBackFill,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
startStatusUpdate()
|
|
77
|
+
|
|
22
78
|
promise.resolve(null)
|
|
23
79
|
}
|
|
24
80
|
|
|
25
81
|
@ReactMethod
|
|
26
|
-
fun
|
|
82
|
+
fun setUserId(userId: String, promise: Promise) {
|
|
83
|
+
if (vitalHealthConnectManager == null) {
|
|
84
|
+
promise.reject(
|
|
85
|
+
"VitalHealthConnect is not configured",
|
|
86
|
+
"VitalHealthConnect is not configured",
|
|
87
|
+
)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mainScope?.cancel()
|
|
92
|
+
mainScope = MainScope()
|
|
93
|
+
mainScope!!.launch {
|
|
94
|
+
vitalHealthConnectManager!!.setUserId(userId)
|
|
95
|
+
promise.resolve(null)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@ReactMethod
|
|
100
|
+
fun configureClient(
|
|
101
|
+
apiKey: String,
|
|
102
|
+
environment: String,
|
|
103
|
+
region: String,
|
|
104
|
+
enableLogs: Boolean,
|
|
105
|
+
promise: Promise
|
|
106
|
+
) {
|
|
107
|
+
logger.enabled = enableLogs
|
|
108
|
+
|
|
109
|
+
vitalClient = VitalClient(
|
|
110
|
+
reactApplicationContext,
|
|
111
|
+
stringToRegion(region),
|
|
112
|
+
stringToEnvironment(environment),
|
|
113
|
+
apiKey
|
|
114
|
+
)
|
|
115
|
+
|
|
27
116
|
promise.resolve(null)
|
|
28
117
|
}
|
|
29
118
|
|
|
119
|
+
@ReactMethod
|
|
120
|
+
fun askForResources(
|
|
121
|
+
resources: ReadableArray,
|
|
122
|
+
promise: Promise
|
|
123
|
+
) {
|
|
124
|
+
return ask(resources, null, promise)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@ReactMethod
|
|
128
|
+
fun ask(
|
|
129
|
+
readResources: ReadableArray,
|
|
130
|
+
writeResources: ReadableArray?,
|
|
131
|
+
promise: Promise
|
|
132
|
+
) {
|
|
133
|
+
val requestPermissionActivityContract =
|
|
134
|
+
PermissionController.createRequestPermissionResultContract()
|
|
135
|
+
val healthPermissions =
|
|
136
|
+
readResources.toArrayList().toList().map { mapReadResourceToHealthRecord(it as String) }
|
|
137
|
+
.flatten()
|
|
138
|
+
.map { HealthPermission.createReadPermission(it) }.toSet().plus(
|
|
139
|
+
writeResources?.toArrayList()?.toList()
|
|
140
|
+
?.map { mapWriteResourceToHealthRecord(it as String) }
|
|
141
|
+
?.flatten()
|
|
142
|
+
?.map { HealthPermission.createWritePermission(it) }?.toSet() ?: emptySet()
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
askForResourcesResult = promise
|
|
146
|
+
askedHealthPermissions = healthPermissions
|
|
147
|
+
|
|
148
|
+
currentActivity?.startActivityForResult(
|
|
149
|
+
requestPermissionActivityContract.createIntent(
|
|
150
|
+
reactApplicationContext,
|
|
151
|
+
healthPermissions
|
|
152
|
+
), 666
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
30
156
|
@ReactMethod
|
|
31
157
|
fun hasAskedForPermission(resource: String, promise: Promise) {
|
|
32
|
-
promise.resolve(
|
|
158
|
+
promise.resolve(false)
|
|
33
159
|
}
|
|
34
160
|
|
|
35
161
|
@ReactMethod
|
|
36
|
-
fun syncData(resources:
|
|
37
|
-
|
|
162
|
+
fun syncData(resources: ReadableArray, promise: Promise) {
|
|
163
|
+
if (vitalHealthConnectManager == null) {
|
|
164
|
+
promise.reject(
|
|
165
|
+
"VitalHealthConnect is not configured",
|
|
166
|
+
"VitalHealthConnect is not configured",
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
mainScope?.cancel()
|
|
172
|
+
mainScope = MainScope()
|
|
173
|
+
mainScope!!.launch {
|
|
174
|
+
vitalHealthConnectManager!!.syncData(
|
|
175
|
+
resources.toArrayList().toList().mapNotNull { mapStringToHealthResource(it as String) }
|
|
176
|
+
.toSet()
|
|
177
|
+
)
|
|
178
|
+
promise.resolve(null)
|
|
179
|
+
}
|
|
38
180
|
}
|
|
39
181
|
|
|
40
182
|
@ReactMethod
|
|
41
183
|
fun cleanUp(promise: Promise) {
|
|
184
|
+
statusScope?.cancel()
|
|
185
|
+
writeScope?.cancel()
|
|
186
|
+
mainScope?.cancel()
|
|
187
|
+
|
|
42
188
|
promise.resolve(null)
|
|
43
189
|
}
|
|
44
190
|
|
|
191
|
+
@ReactMethod
|
|
192
|
+
private fun writeHealthData(
|
|
193
|
+
resource: String,
|
|
194
|
+
startDate: Long,
|
|
195
|
+
endDate: Long,
|
|
196
|
+
value: Double,
|
|
197
|
+
promise: Promise
|
|
198
|
+
) {
|
|
199
|
+
if (vitalHealthConnectManager == null) {
|
|
200
|
+
promise.reject(
|
|
201
|
+
"VitalHealthConnect is not configured",
|
|
202
|
+
"VitalHealthConnect is not configured",
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
writeScope?.cancel()
|
|
208
|
+
writeScope = MainScope()
|
|
209
|
+
writeScope!!.launch {
|
|
210
|
+
try {
|
|
211
|
+
vitalHealthConnectManager!!.addHealthResource(
|
|
212
|
+
mapStringToHealthResource(resource)!!,
|
|
213
|
+
startDate = Instant.ofEpochMilli(startDate),
|
|
214
|
+
endDate = Instant.ofEpochMilli(endDate),
|
|
215
|
+
value = value,
|
|
216
|
+
)
|
|
217
|
+
promise.resolve(null)
|
|
218
|
+
} catch (e: Exception) {
|
|
219
|
+
promise.reject(
|
|
220
|
+
"Failed to write data",
|
|
221
|
+
e.message,
|
|
222
|
+
e
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@ReactMethod
|
|
229
|
+
fun addListener(eventName: String?) {
|
|
230
|
+
// Keep: Required for RN built in Event Emitter Calls.
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@ReactMethod
|
|
234
|
+
fun removeListeners(count: Int) {
|
|
235
|
+
// Keep: Required for RN built in Event Emitter Calls.
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private fun startStatusUpdate() {
|
|
239
|
+
statusScope?.cancel()
|
|
240
|
+
statusScope = MainScope()
|
|
241
|
+
statusScope?.launch {
|
|
242
|
+
try {
|
|
243
|
+
withContext(Dispatchers.Default) {
|
|
244
|
+
vitalHealthConnectManager?.status?.collect {
|
|
245
|
+
logger.logI("Status: $it")
|
|
246
|
+
withContext(Dispatchers.Main) {
|
|
247
|
+
when (it) {
|
|
248
|
+
is SyncStatus.ResourceSyncFailed -> {
|
|
249
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
250
|
+
putString("status", "failedSyncing")
|
|
251
|
+
putString("resource", it.resource.name)
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
is SyncStatus.ResourceNothingToSync -> {
|
|
255
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
256
|
+
putString("status", "nothingToSync")
|
|
257
|
+
putString("resource", it.resource.name)
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
is SyncStatus.ResourceSyncing -> {
|
|
261
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
262
|
+
putString("status", "syncing")
|
|
263
|
+
putString("resource", it.resource.name)
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
is SyncStatus.ResourceSyncingComplete -> {
|
|
267
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
268
|
+
putString("status", "successSyncing")
|
|
269
|
+
putString("resource", it.resource.name)
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
SyncStatus.SyncingCompleted -> {
|
|
273
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
274
|
+
putString("status", "syncingCompleted")
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
SyncStatus.Unknown -> {
|
|
278
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
279
|
+
putString("status", "unknown")
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch (e: Exception) {
|
|
287
|
+
withContext(Dispatchers.Main) {
|
|
288
|
+
sendEvent(VitalHealthEvent.Status, WritableNativeMap().apply {
|
|
289
|
+
putString("status", "failedSyncing")
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private fun sendEvent(event: VitalHealthEvent, params: Any) {
|
|
297
|
+
try {
|
|
298
|
+
reactApplicationContext
|
|
299
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
300
|
+
.emit(event.value, params)
|
|
301
|
+
} catch (e: Exception) {
|
|
302
|
+
Log.e("VitalHealth", "sendEvent: $e")
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
override fun getConstants(): MutableMap<String, Any> {
|
|
307
|
+
return VitalHealthEvent.values().associate { it.value to it.value }.toMutableMap()
|
|
308
|
+
}
|
|
45
309
|
|
|
46
310
|
companion object {
|
|
47
311
|
const val NAME = "VitalHealthReactNative"
|
|
48
312
|
}
|
|
313
|
+
|
|
314
|
+
override fun onActivityResult(p0: Activity?, p1: Int, p2: Int, p3: Intent?) {
|
|
315
|
+
if (p1 == 666) {
|
|
316
|
+
mainScope?.cancel()
|
|
317
|
+
mainScope = MainScope()
|
|
318
|
+
mainScope!!.launch {
|
|
319
|
+
val grantedPermissions =
|
|
320
|
+
vitalHealthConnectManager!!.getGrantedPermissions(reactApplicationContext).toSet()
|
|
321
|
+
|
|
322
|
+
val notGrantedPermissions = (askedHealthPermissions
|
|
323
|
+
?: emptySet()).filter { !grantedPermissions.contains(it) }
|
|
324
|
+
|
|
325
|
+
if (notGrantedPermissions.isEmpty()) {
|
|
326
|
+
askForResourcesResult?.resolve(true)
|
|
327
|
+
} else {
|
|
328
|
+
vitalClient?.vitalLogger?.logI("Not granted permissions: $notGrantedPermissions")
|
|
329
|
+
askForResourcesResult?.resolve(false)
|
|
330
|
+
}
|
|
331
|
+
askedHealthPermissions = null
|
|
332
|
+
askForResourcesResult = null
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
override fun onNewIntent(p0: Intent?) {
|
|
338
|
+
// Not used
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private fun stringToRegion(region: String): Region {
|
|
343
|
+
when (region) {
|
|
344
|
+
"eu" -> return Region.EU
|
|
345
|
+
"us" -> return Region.US
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
throw Exception("Unsupported region $region")
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private fun stringToEnvironment(environment: String): Environment {
|
|
352
|
+
when (environment) {
|
|
353
|
+
"production" -> return Environment.Production
|
|
354
|
+
"sandbox" -> return Environment.Sandbox
|
|
355
|
+
"dev" -> return Environment.Dev
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
throw Exception("Unsupported environment $environment")
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private fun mapReadResourceToHealthRecord(resource: String): List<KClass<out Record>> {
|
|
362
|
+
when (resource) {
|
|
363
|
+
"profile" -> return listOf(HeightRecord::class, WeightRecord::class)
|
|
364
|
+
"body" -> return listOf(BodyFatRecord::class)
|
|
365
|
+
"workout" -> return listOf(ExerciseSessionRecord::class)
|
|
366
|
+
"activity" -> return listOf(
|
|
367
|
+
ActiveCaloriesBurnedRecord::class,
|
|
368
|
+
BasalMetabolicRateRecord::class,
|
|
369
|
+
StepsRecord::class,
|
|
370
|
+
DistanceRecord::class,
|
|
371
|
+
FloorsClimbedRecord::class,
|
|
372
|
+
Vo2MaxRecord::class
|
|
373
|
+
)
|
|
374
|
+
"sleep" -> return listOf(SleepSessionRecord::class)
|
|
375
|
+
"glucose" -> return listOf(BloodGlucoseRecord::class)
|
|
376
|
+
"bloodPressure" -> return listOf(BloodPressureRecord::class)
|
|
377
|
+
"heartRate" -> return listOf(HeartRateVariabilitySdnnRecord::class)
|
|
378
|
+
"steps" -> return listOf(StepsRecord::class)
|
|
379
|
+
"activeEnergyBurned" -> return listOf(ActiveCaloriesBurnedRecord::class)
|
|
380
|
+
"basalEnergyBurned" -> return listOf(BasalMetabolicRateRecord::class)
|
|
381
|
+
"water" -> return listOf(HydrationRecord::class)
|
|
382
|
+
}
|
|
383
|
+
return listOf()
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private fun mapWriteResourceToHealthRecord(resource: String): List<KClass<out Record>> {
|
|
387
|
+
when (resource) {
|
|
388
|
+
"water" -> return listOf(HydrationRecord::class)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return listOf()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private fun mapStringToHealthResource(resource: String): HealthResource? {
|
|
395
|
+
return when (resource) {
|
|
396
|
+
"profile" -> return HealthResource.Profile
|
|
397
|
+
"body" -> return HealthResource.Body
|
|
398
|
+
"workout" -> return HealthResource.Workout
|
|
399
|
+
"activity" -> return HealthResource.Activity
|
|
400
|
+
"sleep" -> return HealthResource.Sleep
|
|
401
|
+
"glucose" -> return HealthResource.Glucose
|
|
402
|
+
"bloodPressure" -> return HealthResource.BloodPressure
|
|
403
|
+
"heartRate" -> return HealthResource.HeartRate
|
|
404
|
+
"steps" -> return HealthResource.Steps
|
|
405
|
+
"activeEnergyBurned" -> return HealthResource.ActiveEnergyBurned
|
|
406
|
+
"basalEnergyBurned" -> return HealthResource.BasalEnergyBurned
|
|
407
|
+
"water" -> return HealthResource.Water
|
|
408
|
+
else -> null
|
|
409
|
+
}
|
|
49
410
|
}
|
|
@@ -8,6 +8,17 @@ RCT_EXTERN_METHOD(configure:(BOOL)backgroundDeliveryEnabled
|
|
|
8
8
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
9
9
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
10
10
|
|
|
11
|
+
RCT_EXTERN_METHOD(setUserId:(NSString *)userId
|
|
12
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
13
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
14
|
+
|
|
15
|
+
RCT_EXTERN_METHOD(configureClient:(NSString *)apiKey
|
|
16
|
+
environment:(NSString *)environment
|
|
17
|
+
region:(NSString *)region
|
|
18
|
+
enableLogs:(BOOL)enableLogs
|
|
19
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
20
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
21
|
+
|
|
11
22
|
RCT_EXTERN_METHOD(askForResources:(NSArray<NSString *> *)resources
|
|
12
23
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
13
24
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -41,12 +41,12 @@ class VitalHealthReactNative: RCTEventEmitter {
|
|
|
41
41
|
payload["status"] = "completed"
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
self.sendEvent(withName: "
|
|
44
|
+
self.sendEvent(withName: "Status", body: payload)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
override func supportedEvents() -> [String]! {
|
|
49
|
-
return ["
|
|
49
|
+
return ["Status"]
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
@objc(configure:numberOfDaysToBackFill:enableLogs:resolver:rejecter:)
|
|
@@ -69,6 +69,49 @@ class VitalHealthReactNative: RCTEventEmitter {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
@objc(setUserId:resolver:rejecter:)
|
|
73
|
+
func setUserId(_ userId: String, resolve: @escaping RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) {
|
|
74
|
+
guard let userId = UUID.init(uuidString: userId) else {
|
|
75
|
+
reject(nil, "userId must be an UUID", nil)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Task {
|
|
80
|
+
await VitalClient.setUserId(userId)
|
|
81
|
+
resolve(())
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@objc(configureClient:environment:region:enableLogs:resolver:rejecter:)
|
|
86
|
+
func configureClient(
|
|
87
|
+
_ apiKey: String,
|
|
88
|
+
environment: String,
|
|
89
|
+
region: String,
|
|
90
|
+
enableLogs: Bool,
|
|
91
|
+
resolve:@escaping RCTPromiseResolveBlock,
|
|
92
|
+
reject:RCTPromiseRejectBlock
|
|
93
|
+
) {
|
|
94
|
+
|
|
95
|
+
let env: Environment
|
|
96
|
+
switch (environment, region) {
|
|
97
|
+
case ("production", "us"):
|
|
98
|
+
env = .production(.us)
|
|
99
|
+
case ("production", "eu"):
|
|
100
|
+
env = .production(.eu)
|
|
101
|
+
case ("sandbox", "us"):
|
|
102
|
+
env = .sandbox(.us)
|
|
103
|
+
case ("sandbox", "eu"):
|
|
104
|
+
env = .sandbox(.eu)
|
|
105
|
+
default:
|
|
106
|
+
reject(nil, "enviroment / region values not accepted", nil)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Task {
|
|
111
|
+
await VitalClient.configure(apiKey: apiKey, environment: env, configuration: .init(logsEnable: enableLogs))
|
|
112
|
+
resolve(())
|
|
113
|
+
}
|
|
114
|
+
}
|
|
72
115
|
|
|
73
116
|
@objc(ask:writeResources:resolver:rejecter:)
|
|
74
117
|
func ask(
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class HealthConfig {
|
|
2
|
+
logsEnabled: boolean;
|
|
3
|
+
numberOfDaysToBackFill: number;
|
|
4
|
+
androidConfig: AndroidHealthConfig;
|
|
5
|
+
iOSConfig: IOSHealthConfig;
|
|
6
|
+
}
|
|
7
|
+
export declare class AndroidHealthConfig {
|
|
8
|
+
syncOnAppStart: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class IOSHealthConfig {
|
|
11
|
+
dataPushMode: string;
|
|
12
|
+
backgroundDeliveryEnabled: boolean;
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class HealthConfig {
|
|
2
|
+
logsEnabled = true;
|
|
3
|
+
numberOfDaysToBackFill = 90;
|
|
4
|
+
androidConfig = new AndroidHealthConfig();
|
|
5
|
+
iOSConfig = new IOSHealthConfig();
|
|
6
|
+
}
|
|
7
|
+
export class AndroidHealthConfig {
|
|
8
|
+
syncOnAppStart = true;
|
|
9
|
+
}
|
|
10
|
+
export class IOSHealthConfig {
|
|
11
|
+
dataPushMode = 'automatic';
|
|
12
|
+
backgroundDeliveryEnabled = true;
|
|
13
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { NativeEventEmitter } from 'react-native';
|
|
2
|
+
import type { HealthConfig } from './health_config';
|
|
3
|
+
export declare const VitalHealthReactNativeModule: any;
|
|
4
|
+
export declare const VitalHealthEvents: {
|
|
5
|
+
statusEvent: string;
|
|
6
|
+
};
|
|
2
7
|
export declare class VitalHealth {
|
|
3
8
|
static status: NativeEventEmitter;
|
|
4
|
-
static configure(
|
|
9
|
+
static configure(healthConfig: HealthConfig): Promise<void>;
|
|
10
|
+
static setUserId(userId: string): Promise<void>;
|
|
11
|
+
static configureClient(apiKey: string, environment: string, region: string, enableLogs: boolean): Promise<void>;
|
|
5
12
|
static askForResources(resources: VitalResource[]): Promise<void>;
|
|
6
13
|
static ask(readResources: VitalResource[], writeResources: VitalWriteResource[]): Promise<void>;
|
|
7
14
|
static writeHealthData(resource: VitalWriteResource, value: number, startDate: Date, endDate: Date): Promise<void>;
|
package/lib/index.js
CHANGED
|
@@ -10,10 +10,25 @@ const VitalHealthReactNative = NativeModules.VitalHealthReactNative
|
|
|
10
10
|
throw new Error(LINKING_ERROR);
|
|
11
11
|
},
|
|
12
12
|
});
|
|
13
|
+
export const VitalHealthReactNativeModule = VitalHealthReactNative;
|
|
14
|
+
export const VitalHealthEvents = {
|
|
15
|
+
statusEvent: 'Status',
|
|
16
|
+
};
|
|
13
17
|
export class VitalHealth {
|
|
14
18
|
static status = new NativeEventEmitter(VitalHealthReactNative);
|
|
15
|
-
static configure(
|
|
16
|
-
|
|
19
|
+
static configure(healthConfig) {
|
|
20
|
+
if (Platform.OS === 'android') {
|
|
21
|
+
return VitalHealthReactNative.configure(healthConfig.androidConfig.syncOnAppStart, healthConfig.numberOfDaysToBackFill, healthConfig.logsEnabled);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return VitalHealthReactNative.configure(healthConfig.iOSConfig.backgroundDeliveryEnabled, healthConfig.numberOfDaysToBackFill, healthConfig.logsEnabled);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static setUserId(userId) {
|
|
28
|
+
return VitalHealthReactNative.setUserId(userId);
|
|
29
|
+
}
|
|
30
|
+
static configureClient(apiKey, environment, region, enableLogs) {
|
|
31
|
+
return VitalHealthReactNative.configureClient(apiKey, environment, region, enableLogs);
|
|
17
32
|
}
|
|
18
33
|
static askForResources(resources) {
|
|
19
34
|
return VitalHealthReactNative.ask(resources, []);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryvital/vital-health-react-native",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Client to access iOS's HealthKit and Android HealthConnect",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"test": "jest",
|
|
31
31
|
"typescript": "tsc --declaration",
|
|
32
32
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
33
|
-
"release": "release-it",
|
|
34
33
|
"example": "yarn --cwd example",
|
|
35
34
|
"bootstrap": "yarn example && yarn install && yarn example pods",
|
|
36
35
|
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build"
|
|
@@ -54,14 +53,10 @@
|
|
|
54
53
|
"react-native": "*"
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|
|
57
|
-
"@arkweid/lefthook": "^0.7.7",
|
|
58
|
-
"@commitlint/config-conventional": "^17.0.2",
|
|
59
56
|
"@react-native-community/eslint-config": "^3.0.2",
|
|
60
|
-
"@release-it/conventional-changelog": "^5.0.0",
|
|
61
57
|
"@types/jest": "^28.1.2",
|
|
62
58
|
"@types/react": "~17.0.21",
|
|
63
59
|
"@types/react-native": "0.70.0",
|
|
64
|
-
"commitlint": "^17.0.2",
|
|
65
60
|
"del-cli": "^5.0.0",
|
|
66
61
|
"eslint": "^8.4.1",
|
|
67
62
|
"eslint-config-prettier": "^8.5.0",
|
|
@@ -74,7 +69,6 @@
|
|
|
74
69
|
"react-native": "0.70.6",
|
|
75
70
|
"react-native-builder-bob": "^0.20.2",
|
|
76
71
|
"react-native-version": "^4.0.0",
|
|
77
|
-
"release-it": "^15.0.0",
|
|
78
72
|
"typescript": "^4.5.2"
|
|
79
73
|
},
|
|
80
74
|
"resolutions": {
|
|
@@ -91,28 +85,6 @@
|
|
|
91
85
|
"<rootDir>/lib/"
|
|
92
86
|
]
|
|
93
87
|
},
|
|
94
|
-
"commitlint": {
|
|
95
|
-
"extends": [
|
|
96
|
-
"@commitlint/config-conventional"
|
|
97
|
-
]
|
|
98
|
-
},
|
|
99
|
-
"release-it": {
|
|
100
|
-
"git": {
|
|
101
|
-
"commitMessage": "chore: release ${version}",
|
|
102
|
-
"tagName": "v${version}"
|
|
103
|
-
},
|
|
104
|
-
"npm": {
|
|
105
|
-
"publish": true
|
|
106
|
-
},
|
|
107
|
-
"github": {
|
|
108
|
-
"release": true
|
|
109
|
-
},
|
|
110
|
-
"plugins": {
|
|
111
|
-
"@release-it/conventional-changelog": {
|
|
112
|
-
"preset": "angular"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
88
|
"eslintConfig": {
|
|
117
89
|
"root": true,
|
|
118
90
|
"extends": [
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class HealthConfig {
|
|
2
|
+
logsEnabled = true;
|
|
3
|
+
numberOfDaysToBackFill = 90;
|
|
4
|
+
androidConfig = new AndroidHealthConfig();
|
|
5
|
+
iOSConfig = new IOSHealthConfig();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class AndroidHealthConfig {
|
|
9
|
+
syncOnAppStart = true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class IOSHealthConfig {
|
|
13
|
+
dataPushMode = 'automatic';
|
|
14
|
+
backgroundDeliveryEnabled = true;
|
|
15
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
2
|
+
import type { HealthConfig } from './health_config';
|
|
2
3
|
|
|
3
4
|
const LINKING_ERROR =
|
|
4
5
|
`The package 'vital-health-react-native' doesn't seem to be linked. Make sure: \n\n` +
|
|
@@ -17,17 +18,45 @@ const VitalHealthReactNative = NativeModules.VitalHealthReactNative
|
|
|
17
18
|
}
|
|
18
19
|
);
|
|
19
20
|
|
|
21
|
+
export const VitalHealthReactNativeModule = VitalHealthReactNative;
|
|
22
|
+
|
|
23
|
+
export const VitalHealthEvents = {
|
|
24
|
+
statusEvent: 'Status',
|
|
25
|
+
};
|
|
26
|
+
|
|
20
27
|
export class VitalHealth {
|
|
21
28
|
static status = new NativeEventEmitter(VitalHealthReactNative);
|
|
22
29
|
|
|
23
|
-
static configure(
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
static configure(healthConfig: HealthConfig): Promise<void> {
|
|
31
|
+
if (Platform.OS === 'android') {
|
|
32
|
+
return VitalHealthReactNative.configure(
|
|
33
|
+
healthConfig.androidConfig.syncOnAppStart,
|
|
34
|
+
healthConfig.numberOfDaysToBackFill,
|
|
35
|
+
healthConfig.logsEnabled
|
|
36
|
+
);
|
|
37
|
+
} else {
|
|
38
|
+
return VitalHealthReactNative.configure(
|
|
39
|
+
healthConfig.iOSConfig.backgroundDeliveryEnabled,
|
|
40
|
+
healthConfig.numberOfDaysToBackFill,
|
|
41
|
+
healthConfig.logsEnabled
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static setUserId(userId: string): Promise<void> {
|
|
47
|
+
return VitalHealthReactNative.setUserId(userId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static configureClient(
|
|
51
|
+
apiKey: string,
|
|
52
|
+
environment: string,
|
|
53
|
+
region: string,
|
|
26
54
|
enableLogs: boolean
|
|
27
55
|
): Promise<void> {
|
|
28
|
-
return VitalHealthReactNative.
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
return VitalHealthReactNative.configureClient(
|
|
57
|
+
apiKey,
|
|
58
|
+
environment,
|
|
59
|
+
region,
|
|
31
60
|
enableLogs
|
|
32
61
|
);
|
|
33
62
|
}
|