@craft-native/android 0.0.72

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.
@@ -0,0 +1,383 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import androidx.compose.foundation.layout.*
6
+ import androidx.compose.material3.*
7
+ import androidx.compose.runtime.*
8
+ import androidx.compose.ui.Modifier
9
+ import androidx.work.*
10
+ import com.google.android.material.color.DynamicColors
11
+ import com.google.firebase.analytics.FirebaseAnalytics
12
+ import com.google.firebase.crashlytics.FirebaseCrashlytics
13
+ import com.google.firebase.messaging.FirebaseMessaging
14
+ import com.google.firebase.remoteconfig.FirebaseRemoteConfig
15
+ import java.util.concurrent.TimeUnit
16
+
17
+ /// Android 13+ Advanced Features
18
+
19
+ // MARK: - Jetpack Compose Integration
20
+
21
+ @Composable
22
+ fun CraftComposeApp(content: @Composable () -> Unit) {
23
+ MaterialTheme(
24
+ colorScheme = dynamicColorScheme(),
25
+ typography = Typography(),
26
+ content = content
27
+ )
28
+ }
29
+
30
+ @Composable
31
+ fun dynamicColorScheme(): ColorScheme {
32
+ return if (DynamicColors.isDynamicColorAvailable()) {
33
+ dynamicDarkColorScheme()
34
+ } else {
35
+ darkColorScheme()
36
+ }
37
+ }
38
+
39
+ @Composable
40
+ fun CraftWebViewComposable(
41
+ url: String,
42
+ modifier: Modifier = Modifier
43
+ ) {
44
+ AndroidView(
45
+ factory = { context ->
46
+ android.webkit.WebView(context).apply {
47
+ settings.javaScriptEnabled = true
48
+ loadUrl(url)
49
+ }
50
+ },
51
+ modifier = modifier.fillMaxSize()
52
+ )
53
+ }
54
+
55
+ // MARK: - Material You Dynamic Colors
56
+
57
+ class DynamicColorManager(private val activity: Activity) {
58
+
59
+ fun applyDynamicColors() {
60
+ if (DynamicColors.isDynamicColorAvailable()) {
61
+ DynamicColors.applyToActivityIfAvailable(activity)
62
+ }
63
+ }
64
+
65
+ fun isDynamicColorAvailable(): Boolean {
66
+ return DynamicColors.isDynamicColorAvailable()
67
+ }
68
+ }
69
+
70
+ // MARK: - Predictive Back Gesture (Android 13+)
71
+
72
+ class PredictiveBackHandler(private val activity: Activity) {
73
+
74
+ fun enablePredictiveBack() {
75
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
76
+ activity.onBackPressedDispatcher.addCallback(activity, object : androidx.activity.OnBackPressedCallback(true) {
77
+ override fun handleOnBackPressed() {
78
+ // Handle predictive back gesture
79
+ // Can animate the exit transition
80
+ isEnabled = false
81
+ activity.onBackPressedDispatcher.onBackPressed()
82
+ }
83
+ })
84
+ }
85
+ }
86
+ }
87
+
88
+ // MARK: - Per-App Language Preferences (Android 13+)
89
+
90
+ class LanguageManager(private val context: Context) {
91
+
92
+ fun setAppLanguage(languageCode: String) {
93
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
94
+ val localeList = android.os.LocaleList(java.util.Locale.forLanguageTag(languageCode))
95
+ context.getSystemService(android.app.LocaleManager::class.java)
96
+ ?.applicationLocales = localeList
97
+ }
98
+ }
99
+
100
+ fun getAppLanguage(): String? {
101
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
102
+ val locales = context.getSystemService(android.app.LocaleManager::class.java)
103
+ ?.applicationLocales
104
+ return locales?.get(0)?.toLanguageTag()
105
+ }
106
+ return null
107
+ }
108
+ }
109
+
110
+ // MARK: - Photo Picker (Android 13+)
111
+
112
+ class PhotoPickerManager(private val activity: Activity) {
113
+
114
+ fun launchPhotoPicker(maxItems: Int = 1, callback: (List<android.net.Uri>) -> Unit) {
115
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
116
+ val intent = android.content.Intent(android.provider.MediaStore.ACTION_PICK_IMAGES).apply {
117
+ putExtra(android.provider.MediaStore.EXTRA_PICK_IMAGES_MAX, maxItems)
118
+ }
119
+ // Launch intent and handle result
120
+ }
121
+ }
122
+ }
123
+
124
+ // MARK: - Notification Permission (Android 13+)
125
+
126
+ class NotificationPermissionManager(private val activity: Activity) {
127
+
128
+ fun requestNotificationPermission(callback: (Boolean) -> Unit) {
129
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
130
+ if (androidx.core.app.ActivityCompat.checkSelfPermission(
131
+ activity,
132
+ android.Manifest.permission.POST_NOTIFICATIONS
133
+ ) != android.content.pm.PackageManager.PERMISSION_GRANTED
134
+ ) {
135
+ androidx.core.app.ActivityCompat.requestPermissions(
136
+ activity,
137
+ arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
138
+ 100
139
+ )
140
+ } else {
141
+ callback(true)
142
+ }
143
+ } else {
144
+ callback(true)
145
+ }
146
+ }
147
+
148
+ fun hasNotificationPermission(): Boolean {
149
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
150
+ return androidx.core.app.ActivityCompat.checkSelfPermission(
151
+ activity,
152
+ android.Manifest.permission.POST_NOTIFICATIONS
153
+ ) == android.content.pm.PackageManager.PERMISSION_GRANTED
154
+ }
155
+ return true
156
+ }
157
+ }
158
+
159
+ // MARK: - Foreground Services
160
+
161
+ class ForegroundServiceManager(private val context: Context) {
162
+
163
+ fun startForegroundService(
164
+ serviceClass: Class<*>,
165
+ notificationTitle: String,
166
+ notificationText: String
167
+ ) {
168
+ val intent = android.content.Intent(context, serviceClass)
169
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
170
+ context.startForegroundService(intent)
171
+ } else {
172
+ context.startService(intent)
173
+ }
174
+ }
175
+
176
+ fun stopForegroundService(serviceClass: Class<*>) {
177
+ val intent = android.content.Intent(context, serviceClass)
178
+ context.stopService(intent)
179
+ }
180
+ }
181
+
182
+ // MARK: - WorkManager for Background Tasks
183
+
184
+ class BackgroundTaskManager(private val context: Context) {
185
+
186
+ fun scheduleOneTimeWork(
187
+ tag: String,
188
+ initialDelay: Long = 0,
189
+ requiresCharging: Boolean = false,
190
+ requiresWifi: Boolean = false
191
+ ) {
192
+ val constraints = Constraints.Builder()
193
+ .setRequiresCharging(requiresCharging)
194
+ .setRequiredNetworkType(if (requiresWifi) NetworkType.UNMETERED else NetworkType.CONNECTED)
195
+ .build()
196
+
197
+ val workRequest = OneTimeWorkRequestBuilder<CraftWorker>()
198
+ .setConstraints(constraints)
199
+ .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
200
+ .addTag(tag)
201
+ .build()
202
+
203
+ WorkManager.getInstance(context).enqueue(workRequest)
204
+ }
205
+
206
+ fun schedulePeriodicWork(
207
+ tag: String,
208
+ intervalMinutes: Long,
209
+ requiresCharging: Boolean = false
210
+ ) {
211
+ val constraints = Constraints.Builder()
212
+ .setRequiresCharging(requiresCharging)
213
+ .build()
214
+
215
+ val workRequest = PeriodicWorkRequestBuilder<CraftWorker>(
216
+ intervalMinutes, TimeUnit.MINUTES
217
+ )
218
+ .setConstraints(constraints)
219
+ .addTag(tag)
220
+ .build()
221
+
222
+ WorkManager.getInstance(context).enqueue(workRequest)
223
+ }
224
+
225
+ fun cancelWork(tag: String) {
226
+ WorkManager.getInstance(context).cancelAllWorkByTag(tag)
227
+ }
228
+
229
+ class CraftWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
230
+ override fun doWork(): Result {
231
+ // Perform background task
232
+ return Result.success()
233
+ }
234
+ }
235
+ }
236
+
237
+ // MARK: - Firebase Integration
238
+
239
+ class FirebaseManager(private val context: Context) {
240
+
241
+ private val analytics: FirebaseAnalytics by lazy {
242
+ FirebaseAnalytics.getInstance(context)
243
+ }
244
+
245
+ private val crashlytics: FirebaseCrashlytics by lazy {
246
+ FirebaseCrashlytics.getInstance()
247
+ }
248
+
249
+ private val remoteConfig: FirebaseRemoteConfig by lazy {
250
+ FirebaseRemoteConfig.getInstance()
251
+ }
252
+
253
+ // Analytics
254
+ fun logEvent(eventName: String, params: Map<String, Any>) {
255
+ val bundle = android.os.Bundle().apply {
256
+ params.forEach { (key, value) ->
257
+ when (value) {
258
+ is String -> putString(key, value)
259
+ is Int -> putInt(key, value)
260
+ is Long -> putLong(key, value)
261
+ is Double -> putDouble(key, value)
262
+ is Boolean -> putBoolean(key, value)
263
+ }
264
+ }
265
+ }
266
+ analytics.logEvent(eventName, bundle)
267
+ }
268
+
269
+ fun setUserProperty(name: String, value: String) {
270
+ analytics.setUserProperty(name, value)
271
+ }
272
+
273
+ // Crashlytics
274
+ fun logCrash(message: String) {
275
+ crashlytics.log(message)
276
+ }
277
+
278
+ fun recordException(throwable: Throwable) {
279
+ crashlytics.recordException(throwable)
280
+ }
281
+
282
+ fun setUserId(userId: String) {
283
+ crashlytics.setUserId(userId)
284
+ }
285
+
286
+ // Remote Config
287
+ fun fetchRemoteConfig(callback: (Boolean) -> Unit) {
288
+ remoteConfig.fetchAndActivate()
289
+ .addOnCompleteListener { task ->
290
+ callback(task.isSuccessful)
291
+ }
292
+ }
293
+
294
+ fun getString(key: String): String {
295
+ return remoteConfig.getString(key)
296
+ }
297
+
298
+ fun getBoolean(key: String): Boolean {
299
+ return remoteConfig.getBoolean(key)
300
+ }
301
+
302
+ fun getLong(key: String): Long {
303
+ return remoteConfig.getLong(key)
304
+ }
305
+
306
+ // FCM
307
+ fun getFirebaseToken(callback: (String?) -> Unit) {
308
+ FirebaseMessaging.getInstance().token
309
+ .addOnCompleteListener { task ->
310
+ if (task.isSuccessful) {
311
+ callback(task.result)
312
+ } else {
313
+ callback(null)
314
+ }
315
+ }
316
+ }
317
+
318
+ fun subscribeToTopic(topic: String) {
319
+ FirebaseMessaging.getInstance().subscribeToTopic(topic)
320
+ }
321
+
322
+ fun unsubscribeFromTopic(topic: String) {
323
+ FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
324
+ }
325
+ }
326
+
327
+ // MARK: - Bottom Sheet
328
+
329
+ @OptIn(ExperimentalMaterial3Api::class)
330
+ @Composable
331
+ fun CraftBottomSheet(
332
+ sheetContent: @Composable ColumnScope.() -> Unit,
333
+ content: @Composable () -> Unit
334
+ ) {
335
+ val sheetState = rememberModalBottomSheetState()
336
+ var showBottomSheet by remember { mutableStateOf(false) }
337
+
338
+ if (showBottomSheet) {
339
+ ModalBottomSheet(
340
+ onDismissRequest = { showBottomSheet = false },
341
+ sheetState = sheetState
342
+ ) {
343
+ sheetContent()
344
+ }
345
+ }
346
+
347
+ content()
348
+ }
349
+
350
+ // MARK: - Navigation Drawer
351
+
352
+ @Composable
353
+ fun CraftNavigationDrawer(
354
+ drawerContent: @Composable ColumnScope.() -> Unit,
355
+ content: @Composable () -> Unit
356
+ ) {
357
+ val drawerState = rememberDrawerState(DrawerValue.Closed)
358
+ val scope = rememberCoroutineScope()
359
+
360
+ ModalNavigationDrawer(
361
+ drawerState = drawerState,
362
+ drawerContent = {
363
+ ModalDrawerSheet {
364
+ drawerContent()
365
+ }
366
+ }
367
+ ) {
368
+ content()
369
+ }
370
+ }
371
+
372
+ // MARK: - Bridge Integration
373
+
374
+ fun CraftBridge.initializeAdvancedFeatures() {
375
+ // Initialize all advanced Android features
376
+ DynamicColorManager(activity).applyDynamicColors()
377
+ PredictiveBackHandler(activity).enablePredictiveBack()
378
+ }
379
+
380
+ fun CraftBridge.setupFirebase() {
381
+ // Setup Firebase services
382
+ // Called from MainActivity onCreate
383
+ }
@@ -0,0 +1,200 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.webkit.WebView
6
+ import androidx.health.connect.client.HealthConnectClient
7
+ import androidx.health.connect.client.PermissionController
8
+ import androidx.health.connect.client.permission.HealthPermission
9
+ import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
10
+ import androidx.health.connect.client.records.DistanceRecord
11
+ import androidx.health.connect.client.records.ExerciseSessionRecord
12
+ import androidx.health.connect.client.records.HeartRateRecord
13
+ import androidx.health.connect.client.records.StepsRecord
14
+ import androidx.health.connect.client.records.metadata.Metadata
15
+ import androidx.health.connect.client.request.AggregateRequest
16
+ import androidx.health.connect.client.time.TimeRangeFilter
17
+ import androidx.health.connect.client.units.Energy
18
+ import androidx.health.connect.client.units.Length
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
+ import org.json.JSONArray
25
+ import org.json.JSONObject
26
+ import java.time.Instant
27
+ import java.time.ZoneId
28
+
29
+ class CraftHealthConnect(
30
+ private val activity: Activity,
31
+ private val webView: WebView
32
+ ) {
33
+ companion object {
34
+ private const val PERMISSION_REQUEST_CODE = 8407
35
+ }
36
+
37
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
38
+ private val permissionContract = PermissionController.createRequestPermissionResultContract()
39
+ private var pendingPermissions: Set<String> = emptySet()
40
+
41
+ private val client: HealthConnectClient?
42
+ get() = if (HealthConnectClient.getSdkStatus(activity) == HealthConnectClient.SDK_AVAILABLE) {
43
+ HealthConnectClient.getOrCreate(activity)
44
+ } else {
45
+ null
46
+ }
47
+
48
+ fun requestAuthorization(typesJson: String) {
49
+ val requested = permissionsFor(JSONArray(typesJson))
50
+ val healthClient = client ?: return reject("_craftFitnessAuthReject", "Health Connect is unavailable or requires an update")
51
+ scope.launch {
52
+ try {
53
+ val granted = healthClient.permissionController.getGrantedPermissions()
54
+ if (granted.containsAll(requested)) {
55
+ resolve("_craftFitnessAuthResolve", "true")
56
+ } else {
57
+ pendingPermissions = requested
58
+ activity.runOnUiThread {
59
+ val intent = permissionContract.createIntent(activity, requested)
60
+ activity.startActivityForResult(intent, PERMISSION_REQUEST_CODE)
61
+ }
62
+ }
63
+ } catch (error: Exception) {
64
+ reject("_craftFitnessAuthReject", error.message ?: "Health permissions could not be requested")
65
+ }
66
+ }
67
+ }
68
+
69
+ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
70
+ if (requestCode != PERMISSION_REQUEST_CODE) return false
71
+ val granted = permissionContract.parseResult(resultCode, data)
72
+ resolve("_craftFitnessAuthResolve", if (granted.containsAll(pendingPermissions)) "true" else "false")
73
+ pendingPermissions = emptySet()
74
+ return true
75
+ }
76
+
77
+ fun getData(type: String, startDate: Long, endDate: Long) {
78
+ val healthClient = client ?: return reject("_craftFitnessDataReject", "Health Connect is unavailable or requires an update")
79
+ val end = if (endDate > 0) Instant.ofEpochMilli(endDate) else Instant.now()
80
+ val start = if (startDate > 0) Instant.ofEpochMilli(startDate) else end.minusSeconds(7 * 24 * 60 * 60)
81
+ scope.launch {
82
+ try {
83
+ val (unit, value) = when (type) {
84
+ "steps" -> {
85
+ val metric = StepsRecord.COUNT_TOTAL
86
+ val result = healthClient.aggregate(AggregateRequest(setOf(metric), TimeRangeFilter.between(start, end)))
87
+ Pair("count", (result[metric] ?: 0L).toDouble())
88
+ }
89
+ "heartRate" -> {
90
+ val metric = HeartRateRecord.BPM_AVG
91
+ val result = healthClient.aggregate(AggregateRequest(setOf(metric), TimeRangeFilter.between(start, end)))
92
+ Pair("count/min", (result[metric] ?: 0L).toDouble())
93
+ }
94
+ "activeEnergy" -> {
95
+ val metric = ActiveCaloriesBurnedRecord.ACTIVE_CALORIES_TOTAL
96
+ val result = healthClient.aggregate(AggregateRequest(setOf(metric), TimeRangeFilter.between(start, end)))
97
+ Pair("kcal", (result[metric] ?: Energy.kilocalories(0.0)).inKilocalories)
98
+ }
99
+ "distance" -> {
100
+ val metric = DistanceRecord.DISTANCE_TOTAL
101
+ val result = healthClient.aggregate(AggregateRequest(setOf(metric), TimeRangeFilter.between(start, end)))
102
+ Pair("m", (result[metric] ?: Length.meters(0.0)).inMeters)
103
+ }
104
+ else -> throw IllegalArgumentException("Unsupported Health Connect data type")
105
+ }
106
+ val payload = JSONObject().put("value", value).put("unit", unit).toString()
107
+ resolve("_craftFitnessDataResolve", payload)
108
+ } catch (error: Exception) {
109
+ reject("_craftFitnessDataReject", error.message ?: "Health data could not be read")
110
+ }
111
+ }
112
+ }
113
+
114
+ fun saveWorkout(workoutJson: String) {
115
+ val healthClient = client ?: return reject("_craftFitnessSaveReject", "Health Connect is unavailable or requires an update")
116
+ scope.launch {
117
+ try {
118
+ val data = JSONObject(workoutJson)
119
+ val activityId = data.getString("activityId")
120
+ val start = Instant.ofEpochMilli(data.getLong("startDate"))
121
+ val end = Instant.ofEpochMilli(data.getLong("endDate"))
122
+ require(end.isAfter(start)) { "Workout endDate must be after startDate" }
123
+ val zone = ZoneId.systemDefault().rules.getOffset(start)
124
+ val exerciseType = when (data.getString("type")) {
125
+ "running" -> ExerciseSessionRecord.EXERCISE_TYPE_RUNNING
126
+ "walking" -> ExerciseSessionRecord.EXERCISE_TYPE_WALKING
127
+ "hiking" -> ExerciseSessionRecord.EXERCISE_TYPE_HIKING
128
+ "cycling" -> ExerciseSessionRecord.EXERCISE_TYPE_BIKING
129
+ else -> throw IllegalArgumentException("Unsupported workout type")
130
+ }
131
+ val records = mutableListOf<androidx.health.connect.client.records.Record>()
132
+ records.add(ExerciseSessionRecord(
133
+ startTime = start,
134
+ startZoneOffset = zone,
135
+ endTime = end,
136
+ endZoneOffset = zone,
137
+ exerciseType = exerciseType,
138
+ title = "WildLoop",
139
+ metadata = Metadata.manualEntry(clientRecordId = activityId)
140
+ ))
141
+ val distance = data.optDouble("distanceMeters", 0.0)
142
+ if (distance > 0) records.add(DistanceRecord(
143
+ startTime = start,
144
+ startZoneOffset = zone,
145
+ endTime = end,
146
+ endZoneOffset = zone,
147
+ distance = Length.meters(distance),
148
+ metadata = Metadata.manualEntry(clientRecordId = "$activityId-distance")
149
+ ))
150
+ val calories = data.optDouble("activeEnergyCalories", 0.0)
151
+ if (calories > 0) records.add(ActiveCaloriesBurnedRecord(
152
+ startTime = start,
153
+ startZoneOffset = zone,
154
+ endTime = end,
155
+ endZoneOffset = zone,
156
+ energy = Energy.kilocalories(calories),
157
+ metadata = Metadata.manualEntry(clientRecordId = "$activityId-energy")
158
+ ))
159
+ val result = healthClient.insertRecords(records)
160
+ val payload = JSONObject().put("id", result.recordIdsList.firstOrNull() ?: activityId).toString()
161
+ resolve("_craftFitnessSaveResolve", payload)
162
+ } catch (error: Exception) {
163
+ reject("_craftFitnessSaveReject", error.message ?: "Workout could not be saved")
164
+ }
165
+ }
166
+ }
167
+
168
+ fun close() {
169
+ scope.cancel()
170
+ }
171
+
172
+ private fun permissionsFor(types: JSONArray): Set<String> {
173
+ val permissions = mutableSetOf<String>()
174
+ for (index in 0 until types.length()) {
175
+ when (types.optString(index)) {
176
+ "steps" -> permissions.add(HealthPermission.getReadPermission(StepsRecord::class))
177
+ "heartRate" -> permissions.add(HealthPermission.getReadPermission(HeartRateRecord::class))
178
+ "activeEnergy" -> permissions.add(HealthPermission.getReadPermission(ActiveCaloriesBurnedRecord::class))
179
+ "distance" -> permissions.add(HealthPermission.getReadPermission(DistanceRecord::class))
180
+ "workouts" -> {
181
+ permissions.add(HealthPermission.getReadPermission(ExerciseSessionRecord::class))
182
+ permissions.add(HealthPermission.getWritePermission(ExerciseSessionRecord::class))
183
+ permissions.add(HealthPermission.getWritePermission(ActiveCaloriesBurnedRecord::class))
184
+ permissions.add(HealthPermission.getWritePermission(DistanceRecord::class))
185
+ }
186
+ }
187
+ }
188
+ return permissions
189
+ }
190
+
191
+ private fun resolve(callback: String, json: String) {
192
+ activity.runOnUiThread {
193
+ webView.evaluateJavascript("window.$callback && window.$callback($json)", null)
194
+ }
195
+ }
196
+
197
+ private fun reject(callback: String, message: String) {
198
+ resolve(callback, JSONObject.quote(message))
199
+ }
200
+ }
@@ -0,0 +1,28 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.webkit.WebView
6
+ import org.json.JSONObject
7
+
8
+ class CraftHealthConnect(
9
+ private val activity: Activity,
10
+ private val webView: WebView
11
+ ) {
12
+ fun requestAuthorization(typesJson: String) = reject("_craftFitnessAuthReject")
13
+
14
+ fun getData(type: String, startDate: Long, endDate: Long) = reject("_craftFitnessDataReject")
15
+
16
+ fun saveWorkout(workoutJson: String) = reject("_craftFitnessSaveReject")
17
+
18
+ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean = false
19
+
20
+ fun close() = Unit
21
+
22
+ private fun reject(callback: String) {
23
+ activity.runOnUiThread {
24
+ val reason = JSONObject.quote("Health Connect is disabled")
25
+ webView.evaluateJavascript("window.$callback && window.$callback($reason)", null)
26
+ }
27
+ }
28
+ }