@asiriindatissa/capacitor-health 8.2.1
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/LICENSE +373 -0
- package/README.md +426 -0
- package/android/build.gradle +70 -0
- package/android/src/main/AndroidManifest.xml +41 -0
- package/android/src/main/java/app/capgo/plugin/health/HealthDataType.kt +34 -0
- package/android/src/main/java/app/capgo/plugin/health/HealthManager.kt +420 -0
- package/android/src/main/java/app/capgo/plugin/health/HealthPlugin.kt +393 -0
- package/android/src/main/java/app/capgo/plugin/health/PermissionsRationaleActivity.kt +57 -0
- package/android/src/main/java/app/capgo/plugin/health/WorkoutType.kt +64 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +773 -0
- package/dist/esm/definitions.d.ts +146 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +15 -0
- package/dist/esm/web.js +35 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +49 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +52 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
package app.capgo.plugin.health
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import androidx.activity.result.ActivityResult
|
|
7
|
+
import com.getcapacitor.JSArray
|
|
8
|
+
import com.getcapacitor.JSObject
|
|
9
|
+
import com.getcapacitor.Plugin
|
|
10
|
+
import com.getcapacitor.PluginCall
|
|
11
|
+
import com.getcapacitor.PluginMethod
|
|
12
|
+
import com.getcapacitor.annotation.ActivityCallback
|
|
13
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
14
|
+
import androidx.health.connect.client.HealthConnectClient
|
|
15
|
+
import androidx.health.connect.client.PermissionController
|
|
16
|
+
import java.time.Instant
|
|
17
|
+
import java.time.Duration
|
|
18
|
+
import java.time.format.DateTimeParseException
|
|
19
|
+
import kotlinx.coroutines.CoroutineScope
|
|
20
|
+
import kotlinx.coroutines.Dispatchers
|
|
21
|
+
import kotlinx.coroutines.SupervisorJob
|
|
22
|
+
import kotlinx.coroutines.cancel
|
|
23
|
+
import kotlinx.coroutines.launch
|
|
24
|
+
|
|
25
|
+
@CapacitorPlugin(name = "Health")
|
|
26
|
+
class HealthPlugin : Plugin() {
|
|
27
|
+
private val pluginVersion = "7.2.14"
|
|
28
|
+
private val manager = HealthManager()
|
|
29
|
+
private val pluginScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
30
|
+
private val permissionContract = PermissionController.createRequestPermissionResultContract()
|
|
31
|
+
|
|
32
|
+
// Store pending request data for callback
|
|
33
|
+
private var pendingReadTypes: List<HealthDataType> = emptyList()
|
|
34
|
+
private var pendingWriteTypes: List<HealthDataType> = emptyList()
|
|
35
|
+
private var pendingIncludeWorkouts: Boolean = false
|
|
36
|
+
|
|
37
|
+
override fun handleOnDestroy() {
|
|
38
|
+
super.handleOnDestroy()
|
|
39
|
+
pluginScope.cancel()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@PluginMethod
|
|
43
|
+
fun isAvailable(call: PluginCall) {
|
|
44
|
+
val status = HealthConnectClient.getSdkStatus(context)
|
|
45
|
+
call.resolve(availabilityPayload(status))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@PluginMethod
|
|
49
|
+
fun requestAuthorization(call: PluginCall) {
|
|
50
|
+
val (readTypes, includeWorkouts) = try {
|
|
51
|
+
parseTypeListWithWorkouts(call, "read")
|
|
52
|
+
} catch (e: IllegalArgumentException) {
|
|
53
|
+
call.reject(e.message, null, e)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
val writeTypes = try {
|
|
58
|
+
parseTypeList(call, "write")
|
|
59
|
+
} catch (e: IllegalArgumentException) {
|
|
60
|
+
call.reject(e.message, null, e)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pluginScope.launch {
|
|
65
|
+
val client = getClientOrReject(call) ?: return@launch
|
|
66
|
+
val permissions = manager.permissionsFor(readTypes, writeTypes, includeWorkouts)
|
|
67
|
+
|
|
68
|
+
if (permissions.isEmpty()) {
|
|
69
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
70
|
+
call.resolve(status)
|
|
71
|
+
return@launch
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val granted = client.permissionController.getGrantedPermissions()
|
|
75
|
+
if (granted.containsAll(permissions)) {
|
|
76
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
77
|
+
call.resolve(status)
|
|
78
|
+
return@launch
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Store types for callback
|
|
82
|
+
pendingReadTypes = readTypes
|
|
83
|
+
pendingWriteTypes = writeTypes
|
|
84
|
+
pendingIncludeWorkouts = includeWorkouts
|
|
85
|
+
|
|
86
|
+
// Create intent using the Health Connect permission contract
|
|
87
|
+
val intent = permissionContract.createIntent(context, permissions)
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
startActivityForResult(call, intent, "handlePermissionResult")
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
pendingReadTypes = emptyList()
|
|
93
|
+
pendingWriteTypes = emptyList()
|
|
94
|
+
call.reject("Failed to launch Health Connect permission request.", null, e)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@ActivityCallback
|
|
100
|
+
private fun handlePermissionResult(call: PluginCall?, result: ActivityResult) {
|
|
101
|
+
if (call == null) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
val readTypes = pendingReadTypes
|
|
106
|
+
val writeTypes = pendingWriteTypes
|
|
107
|
+
val includeWorkouts = pendingIncludeWorkouts
|
|
108
|
+
pendingReadTypes = emptyList()
|
|
109
|
+
pendingWriteTypes = emptyList()
|
|
110
|
+
pendingIncludeWorkouts = false
|
|
111
|
+
|
|
112
|
+
pluginScope.launch {
|
|
113
|
+
val client = getClientOrReject(call) ?: return@launch
|
|
114
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
115
|
+
call.resolve(status)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@PluginMethod
|
|
120
|
+
fun checkAuthorization(call: PluginCall) {
|
|
121
|
+
val (readTypes, includeWorkouts) = try {
|
|
122
|
+
parseTypeListWithWorkouts(call, "read")
|
|
123
|
+
} catch (e: IllegalArgumentException) {
|
|
124
|
+
call.reject(e.message, null, e)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
val writeTypes = try {
|
|
129
|
+
parseTypeList(call, "write")
|
|
130
|
+
} catch (e: IllegalArgumentException) {
|
|
131
|
+
call.reject(e.message, null, e)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pluginScope.launch {
|
|
136
|
+
val client = getClientOrReject(call) ?: return@launch
|
|
137
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
138
|
+
call.resolve(status)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@PluginMethod
|
|
143
|
+
fun readSamples(call: PluginCall) {
|
|
144
|
+
val identifier = call.getString("dataType")
|
|
145
|
+
if (identifier.isNullOrBlank()) {
|
|
146
|
+
call.reject("dataType is required")
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
val dataType = HealthDataType.from(identifier)
|
|
151
|
+
if (dataType == null) {
|
|
152
|
+
call.reject("Unsupported data type: $identifier")
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
val limit = (call.getInt("limit") ?: DEFAULT_LIMIT).coerceAtLeast(0)
|
|
157
|
+
val ascending = call.getBoolean("ascending") ?: false
|
|
158
|
+
|
|
159
|
+
val startInstant = try {
|
|
160
|
+
manager.parseInstant(call.getString("startDate"), Instant.now().minus(DEFAULT_PAST_DURATION))
|
|
161
|
+
} catch (e: DateTimeParseException) {
|
|
162
|
+
call.reject(e.message, null, e)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
val endInstant = try {
|
|
167
|
+
manager.parseInstant(call.getString("endDate"), Instant.now())
|
|
168
|
+
} catch (e: DateTimeParseException) {
|
|
169
|
+
call.reject(e.message, null, e)
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (endInstant.isBefore(startInstant)) {
|
|
174
|
+
call.reject("endDate must be greater than or equal to startDate")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
pluginScope.launch {
|
|
179
|
+
val client = getClientOrReject(call) ?: return@launch
|
|
180
|
+
try {
|
|
181
|
+
val samples = manager.readSamples(client, dataType, startInstant, endInstant, limit, ascending)
|
|
182
|
+
val result = JSObject().apply { put("samples", samples) }
|
|
183
|
+
call.resolve(result)
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
call.reject(e.message ?: "Failed to read samples.", null, e)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@PluginMethod
|
|
191
|
+
fun saveSample(call: PluginCall) {
|
|
192
|
+
val identifier = call.getString("dataType")
|
|
193
|
+
if (identifier.isNullOrBlank()) {
|
|
194
|
+
call.reject("dataType is required")
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
val dataType = HealthDataType.from(identifier)
|
|
199
|
+
if (dataType == null) {
|
|
200
|
+
call.reject("Unsupported data type: $identifier")
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
val value = call.getDouble("value")
|
|
205
|
+
if (value == null) {
|
|
206
|
+
call.reject("value is required")
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
val unit = call.getString("unit")
|
|
211
|
+
if (unit != null && unit != dataType.unit) {
|
|
212
|
+
call.reject("Unsupported unit $unit for ${dataType.identifier}. Expected ${dataType.unit}.")
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
val startInstant = try {
|
|
217
|
+
manager.parseInstant(call.getString("startDate"), Instant.now())
|
|
218
|
+
} catch (e: DateTimeParseException) {
|
|
219
|
+
call.reject(e.message, null, e)
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
val endInstant = try {
|
|
224
|
+
manager.parseInstant(call.getString("endDate"), startInstant)
|
|
225
|
+
} catch (e: DateTimeParseException) {
|
|
226
|
+
call.reject(e.message, null, e)
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (endInstant.isBefore(startInstant)) {
|
|
231
|
+
call.reject("endDate must be greater than or equal to startDate")
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
val metadataObj = call.getObject("metadata")
|
|
236
|
+
val metadata = metadataObj?.let { obj ->
|
|
237
|
+
val iterator = obj.keys()
|
|
238
|
+
val map = mutableMapOf<String, String>()
|
|
239
|
+
while (iterator.hasNext()) {
|
|
240
|
+
val key = iterator.next()
|
|
241
|
+
val rawValue = obj.opt(key)
|
|
242
|
+
if (rawValue is String) {
|
|
243
|
+
map[key] = rawValue
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
map.takeIf { it.isNotEmpty() }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
pluginScope.launch {
|
|
250
|
+
val client = getClientOrReject(call) ?: return@launch
|
|
251
|
+
try {
|
|
252
|
+
manager.saveSample(client, dataType, value, startInstant, endInstant, metadata)
|
|
253
|
+
call.resolve()
|
|
254
|
+
} catch (e: Exception) {
|
|
255
|
+
call.reject(e.message ?: "Failed to save sample.", null, e)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private fun parseTypeList(call: PluginCall, key: String): List<HealthDataType> {
|
|
261
|
+
val array = call.getArray(key) ?: JSArray()
|
|
262
|
+
val result = mutableListOf<HealthDataType>()
|
|
263
|
+
for (i in 0 until array.length()) {
|
|
264
|
+
val identifier = array.optString(i, null) ?: continue
|
|
265
|
+
val dataType = HealthDataType.from(identifier)
|
|
266
|
+
?: throw IllegalArgumentException("Unsupported data type: $identifier")
|
|
267
|
+
result.add(dataType)
|
|
268
|
+
}
|
|
269
|
+
return result
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private fun parseTypeListWithWorkouts(call: PluginCall, key: String): Pair<List<HealthDataType>, Boolean> {
|
|
273
|
+
val array = call.getArray(key) ?: JSArray()
|
|
274
|
+
val result = mutableListOf<HealthDataType>()
|
|
275
|
+
var includeWorkouts = false
|
|
276
|
+
for (i in 0 until array.length()) {
|
|
277
|
+
val identifier = array.optString(i, null) ?: continue
|
|
278
|
+
if (identifier == "workouts") {
|
|
279
|
+
includeWorkouts = true
|
|
280
|
+
} else {
|
|
281
|
+
val dataType = HealthDataType.from(identifier)
|
|
282
|
+
?: throw IllegalArgumentException("Unsupported data type: $identifier")
|
|
283
|
+
result.add(dataType)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return Pair(result, includeWorkouts)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private fun getClientOrReject(call: PluginCall): HealthConnectClient? {
|
|
290
|
+
val status = HealthConnectClient.getSdkStatus(context)
|
|
291
|
+
if (status != HealthConnectClient.SDK_AVAILABLE) {
|
|
292
|
+
call.reject(availabilityReason(status))
|
|
293
|
+
return null
|
|
294
|
+
}
|
|
295
|
+
return HealthConnectClient.getOrCreate(context)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun availabilityPayload(status: Int): JSObject {
|
|
299
|
+
val payload = JSObject()
|
|
300
|
+
payload.put("platform", "android")
|
|
301
|
+
payload.put("available", status == HealthConnectClient.SDK_AVAILABLE)
|
|
302
|
+
if (status != HealthConnectClient.SDK_AVAILABLE) {
|
|
303
|
+
payload.put("reason", availabilityReason(status))
|
|
304
|
+
}
|
|
305
|
+
return payload
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private fun availabilityReason(status: Int): String {
|
|
309
|
+
return when (status) {
|
|
310
|
+
HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> "Health Connect needs an update."
|
|
311
|
+
HealthConnectClient.SDK_UNAVAILABLE -> "Health Connect is unavailable on this device."
|
|
312
|
+
else -> "Health Connect availability unknown."
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@PluginMethod
|
|
317
|
+
fun getPluginVersion(call: PluginCall) {
|
|
318
|
+
try {
|
|
319
|
+
val ret = JSObject()
|
|
320
|
+
ret.put("version", pluginVersion)
|
|
321
|
+
call.resolve(ret)
|
|
322
|
+
} catch (e: Exception) {
|
|
323
|
+
call.reject("Could not get plugin version", e)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@PluginMethod
|
|
328
|
+
fun openHealthConnectSettings(call: PluginCall) {
|
|
329
|
+
try {
|
|
330
|
+
val intent = Intent(HEALTH_CONNECT_SETTINGS_ACTION)
|
|
331
|
+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
332
|
+
context.startActivity(intent)
|
|
333
|
+
call.resolve()
|
|
334
|
+
} catch (e: Exception) {
|
|
335
|
+
call.reject("Failed to open Health Connect settings", null, e)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
@PluginMethod
|
|
340
|
+
fun showPrivacyPolicy(call: PluginCall) {
|
|
341
|
+
try {
|
|
342
|
+
val intent = Intent(context, PermissionsRationaleActivity::class.java)
|
|
343
|
+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
344
|
+
context.startActivity(intent)
|
|
345
|
+
call.resolve()
|
|
346
|
+
} catch (e: Exception) {
|
|
347
|
+
call.reject("Failed to show privacy policy", null, e)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@PluginMethod
|
|
352
|
+
fun queryWorkouts(call: PluginCall) {
|
|
353
|
+
val workoutType = call.getString("workoutType")
|
|
354
|
+
val limit = (call.getInt("limit") ?: DEFAULT_LIMIT).coerceAtLeast(0)
|
|
355
|
+
val ascending = call.getBoolean("ascending") ?: false
|
|
356
|
+
|
|
357
|
+
val startInstant = try {
|
|
358
|
+
manager.parseInstant(call.getString("startDate"), Instant.now().minus(DEFAULT_PAST_DURATION))
|
|
359
|
+
} catch (e: DateTimeParseException) {
|
|
360
|
+
call.reject(e.message, null, e)
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
val endInstant = try {
|
|
365
|
+
manager.parseInstant(call.getString("endDate"), Instant.now())
|
|
366
|
+
} catch (e: DateTimeParseException) {
|
|
367
|
+
call.reject(e.message, null, e)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (endInstant.isBefore(startInstant)) {
|
|
372
|
+
call.reject("endDate must be greater than or equal to startDate")
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
pluginScope.launch {
|
|
377
|
+
val client = getClientOrReject(call) ?: return@launch
|
|
378
|
+
try {
|
|
379
|
+
val workouts = manager.queryWorkouts(client, workoutType, startInstant, endInstant, limit, ascending)
|
|
380
|
+
val result = JSObject().apply { put("workouts", workouts) }
|
|
381
|
+
call.resolve(result)
|
|
382
|
+
} catch (e: Exception) {
|
|
383
|
+
call.reject(e.message ?: "Failed to query workouts.", null, e)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
companion object {
|
|
389
|
+
private const val DEFAULT_LIMIT = 100
|
|
390
|
+
private val DEFAULT_PAST_DURATION: Duration = Duration.ofDays(1)
|
|
391
|
+
private const val HEALTH_CONNECT_SETTINGS_ACTION = "androidx.health.ACTION_HEALTH_CONNECT_SETTINGS"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package app.capgo.plugin.health
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.webkit.WebView
|
|
6
|
+
import android.webkit.WebViewClient
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Activity that displays the app's privacy policy for Health Connect permissions.
|
|
10
|
+
*
|
|
11
|
+
* This activity is launched by Health Connect when the user wants to see why the app
|
|
12
|
+
* needs health data access. It displays a WebView with the privacy policy.
|
|
13
|
+
*
|
|
14
|
+
* The privacy policy URL can be customized by defining a string resource named
|
|
15
|
+
* "health_connect_privacy_policy_url" in your app's res/values/strings.xml:
|
|
16
|
+
*
|
|
17
|
+
* ```xml
|
|
18
|
+
* <resources>
|
|
19
|
+
* <string name="health_connect_privacy_policy_url">https://yourapp.com/privacy</string>
|
|
20
|
+
* </resources>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Alternatively, you can place an HTML file at www/privacypolicy.html in your assets.
|
|
24
|
+
*/
|
|
25
|
+
class PermissionsRationaleActivity : Activity() {
|
|
26
|
+
|
|
27
|
+
private val defaultUrl = "file:///android_asset/public/privacypolicy.html"
|
|
28
|
+
|
|
29
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
30
|
+
super.onCreate(savedInstanceState)
|
|
31
|
+
|
|
32
|
+
val webView = WebView(applicationContext)
|
|
33
|
+
webView.webViewClient = WebViewClient()
|
|
34
|
+
webView.settings.javaScriptEnabled = false
|
|
35
|
+
setContentView(webView)
|
|
36
|
+
|
|
37
|
+
val url = getPrivacyPolicyUrl()
|
|
38
|
+
webView.loadUrl(url)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private fun getPrivacyPolicyUrl(): String {
|
|
42
|
+
return try {
|
|
43
|
+
val resId = resources.getIdentifier(
|
|
44
|
+
"health_connect_privacy_policy_url",
|
|
45
|
+
"string",
|
|
46
|
+
packageName
|
|
47
|
+
)
|
|
48
|
+
if (resId != 0) {
|
|
49
|
+
getString(resId)
|
|
50
|
+
} else {
|
|
51
|
+
defaultUrl
|
|
52
|
+
}
|
|
53
|
+
} catch (e: Exception) {
|
|
54
|
+
defaultUrl
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package app.capgo.plugin.health
|
|
2
|
+
|
|
3
|
+
import androidx.health.connect.client.records.ExerciseSessionRecord
|
|
4
|
+
|
|
5
|
+
object WorkoutType {
|
|
6
|
+
fun fromString(type: String?): Int? {
|
|
7
|
+
if (type.isNullOrBlank()) return null
|
|
8
|
+
|
|
9
|
+
return when (type) {
|
|
10
|
+
"running" -> ExerciseSessionRecord.EXERCISE_TYPE_RUNNING
|
|
11
|
+
"cycling" -> ExerciseSessionRecord.EXERCISE_TYPE_BIKING
|
|
12
|
+
"walking" -> ExerciseSessionRecord.EXERCISE_TYPE_WALKING
|
|
13
|
+
"swimming" -> ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_POOL
|
|
14
|
+
"yoga" -> ExerciseSessionRecord.EXERCISE_TYPE_YOGA
|
|
15
|
+
"strengthTraining" -> ExerciseSessionRecord.EXERCISE_TYPE_STRENGTH_TRAINING
|
|
16
|
+
"hiking" -> ExerciseSessionRecord.EXERCISE_TYPE_HIKING
|
|
17
|
+
"tennis" -> ExerciseSessionRecord.EXERCISE_TYPE_TENNIS
|
|
18
|
+
"basketball" -> ExerciseSessionRecord.EXERCISE_TYPE_BASKETBALL
|
|
19
|
+
"soccer" -> ExerciseSessionRecord.EXERCISE_TYPE_SOCCER
|
|
20
|
+
"americanFootball" -> ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_AMERICAN
|
|
21
|
+
"baseball" -> ExerciseSessionRecord.EXERCISE_TYPE_BASEBALL
|
|
22
|
+
"crossTraining" -> ExerciseSessionRecord.EXERCISE_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING
|
|
23
|
+
"elliptical" -> ExerciseSessionRecord.EXERCISE_TYPE_ELLIPTICAL
|
|
24
|
+
"rowing" -> ExerciseSessionRecord.EXERCISE_TYPE_ROWING
|
|
25
|
+
"stairClimbing" -> ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING
|
|
26
|
+
"traditionalStrengthTraining" -> ExerciseSessionRecord.EXERCISE_TYPE_STRENGTH_TRAINING
|
|
27
|
+
"waterFitness" -> ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_POOL
|
|
28
|
+
"waterPolo" -> ExerciseSessionRecord.EXERCISE_TYPE_WATER_POLO
|
|
29
|
+
"waterSports" -> ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER
|
|
30
|
+
"wrestling" -> ExerciseSessionRecord.EXERCISE_TYPE_MARTIAL_ARTS
|
|
31
|
+
"other" -> ExerciseSessionRecord.EXERCISE_TYPE_OTHER_WORKOUT
|
|
32
|
+
else -> null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun toWorkoutTypeString(exerciseType: Int): String {
|
|
37
|
+
return when (exerciseType) {
|
|
38
|
+
ExerciseSessionRecord.EXERCISE_TYPE_RUNNING -> "running"
|
|
39
|
+
ExerciseSessionRecord.EXERCISE_TYPE_BIKING -> "cycling"
|
|
40
|
+
ExerciseSessionRecord.EXERCISE_TYPE_BIKING_STATIONARY -> "cycling"
|
|
41
|
+
ExerciseSessionRecord.EXERCISE_TYPE_WALKING -> "walking"
|
|
42
|
+
ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_POOL -> "swimming"
|
|
43
|
+
ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER -> "swimming"
|
|
44
|
+
ExerciseSessionRecord.EXERCISE_TYPE_YOGA -> "yoga"
|
|
45
|
+
ExerciseSessionRecord.EXERCISE_TYPE_STRENGTH_TRAINING -> "strengthTraining"
|
|
46
|
+
ExerciseSessionRecord.EXERCISE_TYPE_HIKING -> "hiking"
|
|
47
|
+
ExerciseSessionRecord.EXERCISE_TYPE_TENNIS -> "tennis"
|
|
48
|
+
ExerciseSessionRecord.EXERCISE_TYPE_BASKETBALL -> "basketball"
|
|
49
|
+
ExerciseSessionRecord.EXERCISE_TYPE_SOCCER -> "soccer"
|
|
50
|
+
ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_AMERICAN -> "americanFootball"
|
|
51
|
+
ExerciseSessionRecord.EXERCISE_TYPE_BASEBALL -> "baseball"
|
|
52
|
+
ExerciseSessionRecord.EXERCISE_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING -> "crossTraining"
|
|
53
|
+
ExerciseSessionRecord.EXERCISE_TYPE_ELLIPTICAL -> "elliptical"
|
|
54
|
+
ExerciseSessionRecord.EXERCISE_TYPE_ROWING -> "rowing"
|
|
55
|
+
ExerciseSessionRecord.EXERCISE_TYPE_ROWING_MACHINE -> "rowing"
|
|
56
|
+
ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING -> "stairClimbing"
|
|
57
|
+
ExerciseSessionRecord.EXERCISE_TYPE_STAIR_CLIMBING_MACHINE -> "stairClimbing"
|
|
58
|
+
ExerciseSessionRecord.EXERCISE_TYPE_WATER_POLO -> "waterPolo"
|
|
59
|
+
ExerciseSessionRecord.EXERCISE_TYPE_MARTIAL_ARTS -> "wrestling"
|
|
60
|
+
ExerciseSessionRecord.EXERCISE_TYPE_OTHER_WORKOUT -> "other"
|
|
61
|
+
else -> "other"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
File without changes
|