expo-pedometer 0.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/LICENSE +21 -0
- package/README.md +130 -0
- package/android/build.gradle +22 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/expo/modules/pedometer/ExpoPedometerModule.kt +169 -0
- package/android/src/main/java/expo/modules/pedometer/ExpoPedometerPermissionRationaleActivity.kt +87 -0
- package/app.plugin.js +1 -0
- package/build/ExpoPedometer.types.d.ts +10 -0
- package/build/ExpoPedometer.types.d.ts.map +1 -0
- package/build/ExpoPedometer.types.js +7 -0
- package/build/ExpoPedometer.types.js.map +1 -0
- package/build/ExpoPedometerModule.d.ts +11 -0
- package/build/ExpoPedometerModule.d.ts.map +1 -0
- package/build/ExpoPedometerModule.js +3 -0
- package/build/ExpoPedometerModule.js.map +1 -0
- package/build/ExpoPedometerModule.web.d.ts +11 -0
- package/build/ExpoPedometerModule.web.d.ts.map +1 -0
- package/build/ExpoPedometerModule.web.js +19 -0
- package/build/ExpoPedometerModule.web.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +16 -0
- package/build/index.js.map +1 -0
- package/build/usePermissions.d.ts +9 -0
- package/build/usePermissions.d.ts.map +1 -0
- package/build/usePermissions.js +41 -0
- package/build/usePermissions.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoPedometer.podspec +22 -0
- package/ios/ExpoPedometerModule.swift +118 -0
- package/package.json +65 -0
- package/plugin/build/index.d.ts +42 -0
- package/plugin/build/index.d.ts.map +1 -0
- package/plugin/build/index.js +152 -0
- package/plugin/build/index.js.map +1 -0
- package/src/ExpoPedometer.types.ts +10 -0
- package/src/ExpoPedometerModule.ts +12 -0
- package/src/ExpoPedometerModule.web.ts +24 -0
- package/src/index.ts +21 -0
- package/src/usePermissions.ts +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# expo-pedometer
|
|
2
|
+
|
|
3
|
+
Expo module for reading today's step count from platform health data.
|
|
4
|
+
|
|
5
|
+
- iOS reads step count with Core Motion `CMPedometer`.
|
|
6
|
+
- Android reads Health Connect `StepsRecord` aggregates.
|
|
7
|
+
- Web is unavailable and reports denied permissions.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install expo-pedometer
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Add the config plugin:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"expo": {
|
|
20
|
+
"plugins": [
|
|
21
|
+
[
|
|
22
|
+
"expo-pedometer",
|
|
23
|
+
{
|
|
24
|
+
"iosMotionPermission": "Allow this app to read your step count from Motion & Fitness.",
|
|
25
|
+
"androidMinSdkVersion": 26,
|
|
26
|
+
"androidHealthConnectRationaleTitle": "Step count access",
|
|
27
|
+
"androidHealthConnectRationaleDescription": "Step count is read from Health Connect to show today's walking progress.",
|
|
28
|
+
"androidHealthConnectPrivacyPolicyUrl": "https://example.com/privacy"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The plugin adds:
|
|
37
|
+
|
|
38
|
+
- iOS `NSMotionUsageDescription`.
|
|
39
|
+
- Android `android.permission.health.READ_STEPS`.
|
|
40
|
+
- Android Health Connect package query and permission rationale manifest entries.
|
|
41
|
+
- Android `android.minSdkVersion=26`, required by AndroidX Health Connect.
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
enum PermissionStatus {
|
|
47
|
+
GRANTED = "granted",
|
|
48
|
+
UNDETERMINED = "undetermined",
|
|
49
|
+
DENIED = "denied",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type PermissionResponse = {
|
|
53
|
+
status: PermissionStatus;
|
|
54
|
+
canAskAgain: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const isAvailableAsync: () => Promise<boolean>;
|
|
58
|
+
export const getPermissionsAsync: () => Promise<PermissionResponse>;
|
|
59
|
+
export const requestPermissionsAsync: () => Promise<PermissionResponse>;
|
|
60
|
+
export const getTodayStepCountAsync: () => Promise<number>;
|
|
61
|
+
export const usePermissions: () => [
|
|
62
|
+
isAvailable: boolean | null,
|
|
63
|
+
permission: PermissionResponse | null,
|
|
64
|
+
requestPermission: () => Promise<PermissionResponse>,
|
|
65
|
+
getPermission: () => Promise<PermissionResponse>,
|
|
66
|
+
];
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { PermissionStatus, getTodayStepCountAsync, usePermissions } from "expo-pedometer";
|
|
73
|
+
import { Button } from "react-native";
|
|
74
|
+
|
|
75
|
+
export function StepCount() {
|
|
76
|
+
const [isAvailable, permission, requestPermission] = usePermissions();
|
|
77
|
+
|
|
78
|
+
async function requestAndReadSteps() {
|
|
79
|
+
const nextPermission = await requestPermission();
|
|
80
|
+
if (nextPermission.status === PermissionStatus.GRANTED) {
|
|
81
|
+
const steps = await getTodayStepCountAsync();
|
|
82
|
+
console.log(steps);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isAvailable === false) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Button
|
|
92
|
+
title={permission?.status ?? "loading"}
|
|
93
|
+
onPress={requestAndReadSteps}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`usePermissions()` fetches availability and the current permission when the component mounts. Calling
|
|
100
|
+
`requestPermission()` or `getPermission()` updates the returned permission state.
|
|
101
|
+
|
|
102
|
+
## Android Rationale Localization
|
|
103
|
+
|
|
104
|
+
`androidHealthConnectRationaleTitle`, `androidHealthConnectRationaleDescription`, and
|
|
105
|
+
`androidHealthConnectPrivacyPolicyUrl` can be literal strings or Android string resource references:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
[
|
|
109
|
+
"expo-pedometer",
|
|
110
|
+
{
|
|
111
|
+
"androidHealthConnectRationaleTitle": "@string/pedometer_health_title",
|
|
112
|
+
"androidHealthConnectRationaleDescription": "@string/pedometer_health_description",
|
|
113
|
+
"androidHealthConnectPrivacyPolicyUrl": "@string/privacy_policy_url"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Define localized values in the app's Android resources, such as
|
|
119
|
+
`android/app/src/main/res/values/strings.xml` and
|
|
120
|
+
`android/app/src/main/res/values-ko/strings.xml`.
|
|
121
|
+
|
|
122
|
+
## Platform Notes
|
|
123
|
+
|
|
124
|
+
`getTodayStepCountAsync()` should be called after `isAvailableAsync()` and a granted permission response.
|
|
125
|
+
|
|
126
|
+
iOS Core Motion step data is device pedometer data, not Apple Health's full aggregate. It may differ from the Health app when Health includes other sources such as Apple Watch.
|
|
127
|
+
|
|
128
|
+
Android Health Connect availability depends on device, OS version, and Health Connect installation state. If Health Connect is unavailable or needs an update, `isAvailableAsync()` returns `false`.
|
|
129
|
+
|
|
130
|
+
Health data permissions can require store privacy declarations and an app privacy policy before release.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'expo-module-gradle-plugin'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
group = 'expo.modules.pedometer'
|
|
7
|
+
version = '0.1.0'
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace "expo.modules.pedometer"
|
|
11
|
+
defaultConfig {
|
|
12
|
+
versionCode 1
|
|
13
|
+
versionName "0.1.0"
|
|
14
|
+
}
|
|
15
|
+
lintOptions {
|
|
16
|
+
abortOnError false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
dependencies {
|
|
21
|
+
implementation "androidx.health.connect:connect-client:1.1.0"
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest />
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package expo.modules.pedometer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import androidx.health.connect.client.HealthConnectClient
|
|
6
|
+
import androidx.health.connect.client.PermissionController
|
|
7
|
+
import androidx.health.connect.client.permission.HealthPermission
|
|
8
|
+
import androidx.health.connect.client.records.StepsRecord
|
|
9
|
+
import androidx.health.connect.client.request.AggregateRequest
|
|
10
|
+
import androidx.health.connect.client.time.TimeRangeFilter
|
|
11
|
+
import expo.modules.kotlin.activityresult.AppContextActivityResultContract
|
|
12
|
+
import expo.modules.kotlin.activityresult.AppContextActivityResultLauncher
|
|
13
|
+
import expo.modules.kotlin.exception.CodedException
|
|
14
|
+
import expo.modules.kotlin.exception.Exceptions
|
|
15
|
+
import expo.modules.kotlin.functions.Coroutine
|
|
16
|
+
import expo.modules.kotlin.modules.Module
|
|
17
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
18
|
+
import java.io.Serializable
|
|
19
|
+
import java.time.Instant
|
|
20
|
+
import java.time.LocalDate
|
|
21
|
+
import java.time.ZoneId
|
|
22
|
+
|
|
23
|
+
class ExpoPedometerModule : Module() {
|
|
24
|
+
private lateinit var permissionsLauncher: AppContextActivityResultLauncher<PermissionRequestInput, Set<String>>
|
|
25
|
+
|
|
26
|
+
override fun definition() = ModuleDefinition {
|
|
27
|
+
Name("ExpoPedometer")
|
|
28
|
+
|
|
29
|
+
RegisterActivityContracts {
|
|
30
|
+
permissionsLauncher = registerForActivityResult(HealthConnectPermissionContract())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
AsyncFunction("isAvailableAsync") {
|
|
34
|
+
isHealthConnectAvailable()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
AsyncFunction("getPermissionsAsync").Coroutine<Map<String, Any>> {
|
|
38
|
+
getPermissionResponse()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AsyncFunction("requestPermissionsAsync").Coroutine<Map<String, Any>> {
|
|
42
|
+
if (!isHealthConnectAvailable()) {
|
|
43
|
+
permissionResponse(PERMISSION_DENIED, false)
|
|
44
|
+
} else if (hasRequestedPermission() && !getGrantedPermissions().contains(READ_STEPS_PERMISSION)) {
|
|
45
|
+
permissionResponse(PERMISSION_DENIED, false)
|
|
46
|
+
} else {
|
|
47
|
+
setHasRequestedPermission()
|
|
48
|
+
val result = permissionsLauncher.launch(PermissionRequestInput(REQUIRED_PERMISSIONS.toList()))
|
|
49
|
+
val isGranted = result.contains(READ_STEPS_PERMISSION)
|
|
50
|
+
permissionResponse(
|
|
51
|
+
if (isGranted) PERMISSION_GRANTED else PERMISSION_DENIED,
|
|
52
|
+
canAskAgain = false
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
AsyncFunction("getTodayStepCountAsync").Coroutine<Long> {
|
|
58
|
+
if (!isHealthConnectAvailable()) {
|
|
59
|
+
throw StepCountUnavailableException()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!getGrantedPermissions().contains(READ_STEPS_PERMISSION)) {
|
|
63
|
+
throw MissingStepsPermissionException()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
val now = Instant.now()
|
|
67
|
+
val startOfDay = LocalDate.now(ZoneId.systemDefault())
|
|
68
|
+
.atStartOfDay(ZoneId.systemDefault())
|
|
69
|
+
.toInstant()
|
|
70
|
+
val result = healthConnectClient.aggregate(
|
|
71
|
+
AggregateRequest(
|
|
72
|
+
metrics = setOf(StepsRecord.COUNT_TOTAL),
|
|
73
|
+
timeRangeFilter = TimeRangeFilter.between(startOfDay, now)
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
result[StepsRecord.COUNT_TOTAL] ?: 0L
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private val context: Context
|
|
82
|
+
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
|
|
83
|
+
|
|
84
|
+
private val healthConnectClient: HealthConnectClient
|
|
85
|
+
get() = HealthConnectClient.getOrCreate(context)
|
|
86
|
+
|
|
87
|
+
private fun isHealthConnectAvailable(): Boolean {
|
|
88
|
+
return HealthConnectClient.getSdkStatus(context) == HealthConnectClient.SDK_AVAILABLE
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private suspend fun getPermissionResponse(): Map<String, Any> {
|
|
92
|
+
if (!isHealthConnectAvailable()) {
|
|
93
|
+
return permissionResponse(PERMISSION_DENIED, false)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
val isGranted = getGrantedPermissions().contains(READ_STEPS_PERMISSION)
|
|
97
|
+
val status = when {
|
|
98
|
+
isGranted -> PERMISSION_GRANTED
|
|
99
|
+
hasRequestedPermission() -> PERMISSION_DENIED
|
|
100
|
+
else -> PERMISSION_UNDETERMINED
|
|
101
|
+
}
|
|
102
|
+
return permissionResponse(status, canAskAgain = status == PERMISSION_UNDETERMINED)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private suspend fun getGrantedPermissions(): Set<String> {
|
|
106
|
+
return healthConnectClient.permissionController.getGrantedPermissions()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private fun hasRequestedPermission(): Boolean {
|
|
110
|
+
return context
|
|
111
|
+
.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
|
112
|
+
.getBoolean(HAS_REQUESTED_PERMISSION_KEY, false)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private fun setHasRequestedPermission() {
|
|
116
|
+
context
|
|
117
|
+
.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
|
118
|
+
.edit()
|
|
119
|
+
.putBoolean(HAS_REQUESTED_PERMISSION_KEY, true)
|
|
120
|
+
.apply()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private fun permissionResponse(status: String, canAskAgain: Boolean): Map<String, Any> {
|
|
124
|
+
return mapOf(
|
|
125
|
+
"status" to status,
|
|
126
|
+
"canAskAgain" to canAskAgain
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private class HealthConnectPermissionContract :
|
|
131
|
+
AppContextActivityResultContract<PermissionRequestInput, Set<String>> {
|
|
132
|
+
private val contract = PermissionController.createRequestPermissionResultContract()
|
|
133
|
+
|
|
134
|
+
override fun createIntent(context: Context, input: PermissionRequestInput): Intent {
|
|
135
|
+
return contract.createIntent(context, input.permissions.toSet())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override fun parseResult(
|
|
139
|
+
input: PermissionRequestInput,
|
|
140
|
+
resultCode: Int,
|
|
141
|
+
intent: Intent?
|
|
142
|
+
): Set<String> {
|
|
143
|
+
return contract.parseResult(resultCode, intent)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private data class PermissionRequestInput(
|
|
148
|
+
val permissions: List<String>
|
|
149
|
+
) : Serializable
|
|
150
|
+
|
|
151
|
+
companion object {
|
|
152
|
+
private const val PERMISSION_GRANTED = "granted"
|
|
153
|
+
private const val PERMISSION_DENIED = "denied"
|
|
154
|
+
private const val PERMISSION_UNDETERMINED = "undetermined"
|
|
155
|
+
private const val PREFERENCES_NAME = "expo_pedometer"
|
|
156
|
+
private const val HAS_REQUESTED_PERMISSION_KEY = "has_requested_steps_permission"
|
|
157
|
+
|
|
158
|
+
private val READ_STEPS_PERMISSION = HealthPermission.getReadPermission(StepsRecord::class)
|
|
159
|
+
private val REQUIRED_PERMISSIONS = setOf(READ_STEPS_PERMISSION)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private class StepCountUnavailableException : CodedException(
|
|
164
|
+
message = "Health Connect step count is not available on this device"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
private class MissingStepsPermissionException : CodedException(
|
|
168
|
+
message = "Missing Health Connect READ_STEPS permission"
|
|
169
|
+
)
|
package/android/src/main/java/expo/modules/pedometer/ExpoPedometerPermissionRationaleActivity.kt
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
package expo.modules.pedometer
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.os.Bundle
|
|
8
|
+
import android.view.Gravity
|
|
9
|
+
import android.widget.Button
|
|
10
|
+
import android.widget.LinearLayout
|
|
11
|
+
import android.widget.ScrollView
|
|
12
|
+
import android.widget.TextView
|
|
13
|
+
|
|
14
|
+
class ExpoPedometerPermissionRationaleActivity : Activity() {
|
|
15
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
16
|
+
super.onCreate(savedInstanceState)
|
|
17
|
+
|
|
18
|
+
val metadata = packageManager
|
|
19
|
+
.getActivityInfo(componentName, PackageManager.GET_META_DATA)
|
|
20
|
+
.metaData
|
|
21
|
+
val privacyPolicyUrl = readStringMetaData(metadata, META_PRIVACY_POLICY_URL)
|
|
22
|
+
|
|
23
|
+
val content = LinearLayout(this).apply {
|
|
24
|
+
orientation = LinearLayout.VERTICAL
|
|
25
|
+
gravity = Gravity.CENTER_HORIZONTAL
|
|
26
|
+
setPadding(dp(24), dp(32), dp(24), dp(32))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
content.addView(TextView(this).apply {
|
|
30
|
+
text = readStringMetaData(metadata, META_TITLE, DEFAULT_TITLE)
|
|
31
|
+
textSize = 22f
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
content.addView(TextView(this).apply {
|
|
35
|
+
text = readStringMetaData(metadata, META_DESCRIPTION, DEFAULT_DESCRIPTION)
|
|
36
|
+
textSize = 16f
|
|
37
|
+
setPadding(0, dp(16), 0, 0)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (!privacyPolicyUrl.isNullOrBlank()) {
|
|
41
|
+
content.addView(Button(this).apply {
|
|
42
|
+
text = "Privacy Policy"
|
|
43
|
+
setPadding(0, dp(16), 0, 0)
|
|
44
|
+
setOnClickListener {
|
|
45
|
+
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(privacyPolicyUrl)))
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setContentView(ScrollView(this).apply {
|
|
51
|
+
addView(content)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun dp(value: Int): Int {
|
|
56
|
+
return (value * resources.displayMetrics.density).toInt()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun readStringMetaData(metadata: Bundle?, key: String, fallback: String? = null): String? {
|
|
60
|
+
return when (val value = metadata?.get(key)) {
|
|
61
|
+
is Int -> if (value != 0) getString(value) else fallback
|
|
62
|
+
is String -> resolveStringReference(value) ?: fallback
|
|
63
|
+
null -> fallback
|
|
64
|
+
else -> value.toString()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private fun resolveStringReference(value: String): String? {
|
|
69
|
+
if (!value.startsWith("@string/")) {
|
|
70
|
+
return value
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
val resourceName = value.removePrefix("@string/")
|
|
74
|
+
val resourceId = resources.getIdentifier(resourceName, "string", packageName)
|
|
75
|
+
return if (resourceId != 0) getString(resourceId) else value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
companion object {
|
|
79
|
+
const val META_TITLE = "expo.modules.pedometer.HEALTH_PERMISSIONS_RATIONALE_TITLE"
|
|
80
|
+
const val META_DESCRIPTION = "expo.modules.pedometer.HEALTH_PERMISSIONS_RATIONALE_DESCRIPTION"
|
|
81
|
+
const val META_PRIVACY_POLICY_URL = "expo.modules.pedometer.PRIVACY_POLICY_URL"
|
|
82
|
+
|
|
83
|
+
private const val DEFAULT_TITLE = "Health permissions"
|
|
84
|
+
private const val DEFAULT_DESCRIPTION =
|
|
85
|
+
"Step count is read from Health Connect to show today's walking progress."
|
|
86
|
+
}
|
|
87
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./plugin/build").default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare enum PermissionStatus {
|
|
2
|
+
GRANTED = "granted",
|
|
3
|
+
UNDETERMINED = "undetermined",
|
|
4
|
+
DENIED = "denied"
|
|
5
|
+
}
|
|
6
|
+
export type PermissionResponse = {
|
|
7
|
+
status: PermissionStatus;
|
|
8
|
+
canAskAgain: boolean;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=ExpoPedometer.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoPedometer.types.d.ts","sourceRoot":"","sources":["../src/ExpoPedometer.types.ts"],"names":[],"mappings":"AAAA,oBAAY,gBAAgB;IAC1B,OAAO,YAAY;IACnB,YAAY,iBAAiB;IAC7B,MAAM,WAAW;CAClB;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var PermissionStatus;
|
|
2
|
+
(function (PermissionStatus) {
|
|
3
|
+
PermissionStatus["GRANTED"] = "granted";
|
|
4
|
+
PermissionStatus["UNDETERMINED"] = "undetermined";
|
|
5
|
+
PermissionStatus["DENIED"] = "denied";
|
|
6
|
+
})(PermissionStatus || (PermissionStatus = {}));
|
|
7
|
+
//# sourceMappingURL=ExpoPedometer.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoPedometer.types.js","sourceRoot":"","sources":["../src/ExpoPedometer.types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,uCAAmB,CAAA;IACnB,iDAA6B,CAAA;IAC7B,qCAAiB,CAAA;AACnB,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B","sourcesContent":["export enum PermissionStatus {\n GRANTED = \"granted\",\n UNDETERMINED = \"undetermined\",\n DENIED = \"denied\",\n}\n\nexport type PermissionResponse = {\n status: PermissionStatus;\n canAskAgain: boolean;\n};\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NativeModule } from "expo";
|
|
2
|
+
import type { PermissionResponse } from "./ExpoPedometer.types";
|
|
3
|
+
declare class ExpoPedometerModule extends NativeModule {
|
|
4
|
+
isAvailableAsync(): Promise<boolean>;
|
|
5
|
+
getPermissionsAsync(): Promise<PermissionResponse>;
|
|
6
|
+
requestPermissionsAsync(): Promise<PermissionResponse>;
|
|
7
|
+
getTodayStepCountAsync(): Promise<number>;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: ExpoPedometerModule;
|
|
10
|
+
export default _default;
|
|
11
|
+
//# sourceMappingURL=ExpoPedometerModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoPedometerModule.d.ts","sourceRoot":"","sources":["../src/ExpoPedometerModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,OAAO,mBAAoB,SAAQ,YAAY;IACpD,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IACpC,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAClD,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IACtD,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;CAC1C;;AAED,wBAAyE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoPedometerModule.js","sourceRoot":"","sources":["../src/ExpoPedometerModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAWzD,eAAe,mBAAmB,CAAsB,eAAe,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport type { PermissionResponse } from \"./ExpoPedometer.types\";\n\ndeclare class ExpoPedometerModule extends NativeModule {\n isAvailableAsync(): Promise<boolean>;\n getPermissionsAsync(): Promise<PermissionResponse>;\n requestPermissionsAsync(): Promise<PermissionResponse>;\n getTodayStepCountAsync(): Promise<number>;\n}\n\nexport default requireNativeModule<ExpoPedometerModule>(\"ExpoPedometer\");\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NativeModule } from "expo";
|
|
2
|
+
import { type PermissionResponse } from "./ExpoPedometer.types";
|
|
3
|
+
declare class ExpoPedometerModule extends NativeModule {
|
|
4
|
+
isAvailableAsync(): Promise<boolean>;
|
|
5
|
+
getPermissionsAsync(): Promise<PermissionResponse>;
|
|
6
|
+
requestPermissionsAsync(): Promise<PermissionResponse>;
|
|
7
|
+
getTodayStepCountAsync(): Promise<number>;
|
|
8
|
+
}
|
|
9
|
+
declare const _default: typeof ExpoPedometerModule;
|
|
10
|
+
export default _default;
|
|
11
|
+
//# sourceMappingURL=ExpoPedometerModule.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoPedometerModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoPedometerModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAGvD,OAAO,EAAE,KAAK,kBAAkB,EAAoB,MAAM,uBAAuB,CAAC;AAElF,cAAM,mBAAoB,SAAQ,YAAY;IACtC,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIpC,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAIlD,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAItD,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;CAGhD;;AAED,wBAAuE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NativeModule, registerWebModule } from "expo";
|
|
2
|
+
import { UnavailabilityError } from "expo-modules-core";
|
|
3
|
+
import { PermissionStatus } from "./ExpoPedometer.types";
|
|
4
|
+
class ExpoPedometerModule extends NativeModule {
|
|
5
|
+
async isAvailableAsync() {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
async getPermissionsAsync() {
|
|
9
|
+
return { status: PermissionStatus.DENIED, canAskAgain: false };
|
|
10
|
+
}
|
|
11
|
+
async requestPermissionsAsync() {
|
|
12
|
+
return { status: PermissionStatus.DENIED, canAskAgain: false };
|
|
13
|
+
}
|
|
14
|
+
async getTodayStepCountAsync() {
|
|
15
|
+
throw new UnavailabilityError("ExpoPedometer", "getTodayStepCountAsync");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export default registerWebModule(ExpoPedometerModule, "ExpoPedometer");
|
|
19
|
+
//# sourceMappingURL=ExpoPedometerModule.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoPedometerModule.web.js","sourceRoot":"","sources":["../src/ExpoPedometerModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAA2B,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAElF,MAAM,mBAAoB,SAAQ,YAAY;IAC5C,KAAK,CAAC,gBAAgB;QACpB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,mBAAmB;QACvB,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,sBAAsB;QAC1B,MAAM,IAAI,mBAAmB,CAAC,eAAe,EAAE,wBAAwB,CAAC,CAAC;IAC3E,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from \"expo\";\nimport { UnavailabilityError } from \"expo-modules-core\";\n\nimport { type PermissionResponse, PermissionStatus } from \"./ExpoPedometer.types\";\n\nclass ExpoPedometerModule extends NativeModule {\n async isAvailableAsync(): Promise<boolean> {\n return false;\n }\n\n async getPermissionsAsync(): Promise<PermissionResponse> {\n return { status: PermissionStatus.DENIED, canAskAgain: false };\n }\n\n async requestPermissionsAsync(): Promise<PermissionResponse> {\n return { status: PermissionStatus.DENIED, canAskAgain: false };\n }\n\n async getTodayStepCountAsync(): Promise<number> {\n throw new UnavailabilityError(\"ExpoPedometer\", \"getTodayStepCountAsync\");\n }\n}\n\nexport default registerWebModule(ExpoPedometerModule, \"ExpoPedometer\");\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PermissionResponse } from "./ExpoPedometer.types";
|
|
2
|
+
export declare function isAvailableAsync(): Promise<boolean>;
|
|
3
|
+
export declare function getPermissionsAsync(): Promise<PermissionResponse>;
|
|
4
|
+
export declare function requestPermissionsAsync(): Promise<PermissionResponse>;
|
|
5
|
+
export declare function getTodayStepCountAsync(): Promise<number>;
|
|
6
|
+
export * from "./ExpoPedometer.types";
|
|
7
|
+
export * from "./usePermissions";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAEzD;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEvE;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE3E;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE9D;AAED,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ExpoPedometerModule from "./ExpoPedometerModule";
|
|
2
|
+
export async function isAvailableAsync() {
|
|
3
|
+
return await ExpoPedometerModule.isAvailableAsync();
|
|
4
|
+
}
|
|
5
|
+
export async function getPermissionsAsync() {
|
|
6
|
+
return await ExpoPedometerModule.getPermissionsAsync();
|
|
7
|
+
}
|
|
8
|
+
export async function requestPermissionsAsync() {
|
|
9
|
+
return await ExpoPedometerModule.requestPermissionsAsync();
|
|
10
|
+
}
|
|
11
|
+
export async function getTodayStepCountAsync() {
|
|
12
|
+
return await ExpoPedometerModule.getTodayStepCountAsync();
|
|
13
|
+
}
|
|
14
|
+
export * from "./ExpoPedometer.types";
|
|
15
|
+
export * from "./usePermissions";
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAExD,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,OAAO,MAAM,mBAAmB,CAAC,gBAAgB,EAAE,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,OAAO,MAAM,mBAAmB,CAAC,mBAAmB,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,OAAO,MAAM,mBAAmB,CAAC,uBAAuB,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,OAAO,MAAM,mBAAmB,CAAC,sBAAsB,EAAE,CAAC;AAC5D,CAAC;AAED,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC","sourcesContent":["import type { PermissionResponse } from \"./ExpoPedometer.types\";\nimport ExpoPedometerModule from \"./ExpoPedometerModule\";\n\nexport async function isAvailableAsync(): Promise<boolean> {\n return await ExpoPedometerModule.isAvailableAsync();\n}\n\nexport async function getPermissionsAsync(): Promise<PermissionResponse> {\n return await ExpoPedometerModule.getPermissionsAsync();\n}\n\nexport async function requestPermissionsAsync(): Promise<PermissionResponse> {\n return await ExpoPedometerModule.requestPermissionsAsync();\n}\n\nexport async function getTodayStepCountAsync(): Promise<number> {\n return await ExpoPedometerModule.getTodayStepCountAsync();\n}\n\nexport * from \"./ExpoPedometer.types\";\nexport * from \"./usePermissions\";\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PermissionResponse } from "./ExpoPedometer.types";
|
|
2
|
+
export type UsePermissionsResult = [
|
|
3
|
+
isAvailable: boolean | null,
|
|
4
|
+
permission: PermissionResponse | null,
|
|
5
|
+
requestPermission: () => Promise<PermissionResponse>,
|
|
6
|
+
getPermission: () => Promise<PermissionResponse>
|
|
7
|
+
];
|
|
8
|
+
export declare function usePermissions(): UsePermissionsResult;
|
|
9
|
+
//# sourceMappingURL=usePermissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePermissions.d.ts","sourceRoot":"","sources":["../src/usePermissions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,OAAO,GAAG,IAAI;IAC3B,UAAU,EAAE,kBAAkB,GAAG,IAAI;IACrC,iBAAiB,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC;IACpD,aAAa,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC;CACjD,CAAC;AAEF,wBAAgB,cAAc,IAAI,oBAAoB,CA0CrD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import ExpoPedometerModule from "./ExpoPedometerModule";
|
|
4
|
+
export function usePermissions() {
|
|
5
|
+
const isMounted = useRef(true);
|
|
6
|
+
const [isAvailable, setIsAvailable] = useState(null);
|
|
7
|
+
const [permission, setPermission] = useState(null);
|
|
8
|
+
const getAvailability = useCallback(async () => {
|
|
9
|
+
const response = await ExpoPedometerModule.isAvailableAsync();
|
|
10
|
+
if (isMounted.current) {
|
|
11
|
+
setIsAvailable(response);
|
|
12
|
+
}
|
|
13
|
+
return response;
|
|
14
|
+
}, []);
|
|
15
|
+
const getPermission = useCallback(async () => {
|
|
16
|
+
const response = await ExpoPedometerModule.getPermissionsAsync();
|
|
17
|
+
if (isMounted.current) {
|
|
18
|
+
setPermission(response);
|
|
19
|
+
}
|
|
20
|
+
return response;
|
|
21
|
+
}, []);
|
|
22
|
+
const requestPermission = useCallback(async () => {
|
|
23
|
+
const response = await ExpoPedometerModule.requestPermissionsAsync();
|
|
24
|
+
if (isMounted.current) {
|
|
25
|
+
setPermission(response);
|
|
26
|
+
}
|
|
27
|
+
return response;
|
|
28
|
+
}, []);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
void getAvailability();
|
|
31
|
+
void getPermission();
|
|
32
|
+
}, [getAvailability, getPermission]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
isMounted.current = true;
|
|
35
|
+
return () => {
|
|
36
|
+
isMounted.current = false;
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
return [isAvailable, permission, requestPermission, getPermission];
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=usePermissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePermissions.js","sourceRoot":"","sources":["../src/usePermissions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AASxD,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAA4B,IAAI,CAAC,CAAC;IAE9E,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC7C,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,gBAAgB,EAAE,CAAC;QAC9D,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC3C,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,mBAAmB,EAAE,CAAC;QACjE,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,iBAAiB,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC/C,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,uBAAuB,EAAE,CAAC;QACrE,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,eAAe,EAAE,CAAC;QACvB,KAAK,aAAa,EAAE,CAAC;IACvB,CAAC,EAAE,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC,CAAC;IAErC,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QACzB,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,OAAO,GAAG,KAAK,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;AACrE,CAAC","sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { PermissionResponse } from \"./ExpoPedometer.types\";\nimport ExpoPedometerModule from \"./ExpoPedometerModule\";\n\nexport type UsePermissionsResult = [\n isAvailable: boolean | null,\n permission: PermissionResponse | null,\n requestPermission: () => Promise<PermissionResponse>,\n getPermission: () => Promise<PermissionResponse>,\n];\n\nexport function usePermissions(): UsePermissionsResult {\n const isMounted = useRef(true);\n const [isAvailable, setIsAvailable] = useState<boolean | null>(null);\n const [permission, setPermission] = useState<PermissionResponse | null>(null);\n\n const getAvailability = useCallback(async () => {\n const response = await ExpoPedometerModule.isAvailableAsync();\n if (isMounted.current) {\n setIsAvailable(response);\n }\n return response;\n }, []);\n\n const getPermission = useCallback(async () => {\n const response = await ExpoPedometerModule.getPermissionsAsync();\n if (isMounted.current) {\n setPermission(response);\n }\n return response;\n }, []);\n\n const requestPermission = useCallback(async () => {\n const response = await ExpoPedometerModule.requestPermissionsAsync();\n if (isMounted.current) {\n setPermission(response);\n }\n return response;\n }, []);\n\n useEffect(() => {\n void getAvailability();\n void getPermission();\n }, [getAvailability, getPermission]);\n\n useEffect(() => {\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n return [isAvailable, permission, requestPermission, getPermission];\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Pod::Spec.new do |s|
|
|
2
|
+
s.name = 'ExpoPedometer'
|
|
3
|
+
s.version = '0.1.0'
|
|
4
|
+
s.summary = 'Core Motion step count module for Expo'
|
|
5
|
+
s.description = 'Reads today step count using Core Motion CMPedometer.'
|
|
6
|
+
s.author = 'Hills'
|
|
7
|
+
s.homepage = 'https://github.com/hills-corp/expo-pedometer'
|
|
8
|
+
s.platforms = {
|
|
9
|
+
:ios => '15.1'
|
|
10
|
+
}
|
|
11
|
+
s.source = { git: 'https://github.com/hills-corp/expo-pedometer.git' }
|
|
12
|
+
s.static_framework = true
|
|
13
|
+
|
|
14
|
+
s.dependency 'ExpoModulesCore'
|
|
15
|
+
|
|
16
|
+
# Swift/Objective-C compatibility
|
|
17
|
+
s.pod_target_xcconfig = {
|
|
18
|
+
'DEFINES_MODULE' => 'YES',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
22
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import CoreMotion
|
|
3
|
+
|
|
4
|
+
private let permissionGranted = "granted"
|
|
5
|
+
private let permissionDenied = "denied"
|
|
6
|
+
private let permissionUndetermined = "undetermined"
|
|
7
|
+
|
|
8
|
+
private final class StepCountUnavailableException: Exception, @unchecked Sendable {
|
|
9
|
+
override var reason: String {
|
|
10
|
+
"Core Motion step count is not available on this device"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private final class MissingStepsPermissionException: Exception, @unchecked Sendable {
|
|
15
|
+
override var reason: String {
|
|
16
|
+
"Motion & Fitness permission is required to read step count"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private final class PedometerQueryException: GenericException<String>, @unchecked Sendable {
|
|
21
|
+
override var reason: String {
|
|
22
|
+
"Unable to query Core Motion step count: \(param)"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public class ExpoPedometerModule: Module {
|
|
27
|
+
private let pedometer = CMPedometer()
|
|
28
|
+
|
|
29
|
+
public func definition() -> ModuleDefinition {
|
|
30
|
+
Name("ExpoPedometer")
|
|
31
|
+
|
|
32
|
+
AsyncFunction("isAvailableAsync") { () -> Bool in
|
|
33
|
+
Self.isStepCountAvailable()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
AsyncFunction("getPermissionsAsync") { (promise: Promise) in
|
|
37
|
+
promise.resolve(Self.permissionResponseForCurrentStatus())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
AsyncFunction("requestPermissionsAsync") { (promise: Promise) in
|
|
41
|
+
guard Self.isStepCountAvailable() else {
|
|
42
|
+
promise.resolve(Self.permissionResponse(permissionDenied, canAskAgain: false))
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
guard Self.permissionStatus() == permissionUndetermined else {
|
|
47
|
+
promise.resolve(Self.permissionResponseForCurrentStatus())
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let now = Date()
|
|
52
|
+
pedometer.queryPedometerData(from: now.addingTimeInterval(-1), to: now) { _, error in
|
|
53
|
+
if let error {
|
|
54
|
+
if Self.permissionStatus() != permissionUndetermined {
|
|
55
|
+
promise.resolve(Self.permissionResponseForCurrentStatus())
|
|
56
|
+
} else {
|
|
57
|
+
promise.reject(PedometerQueryException(error.localizedDescription))
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
promise.resolve(Self.permissionResponseForCurrentStatus())
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
AsyncFunction("getTodayStepCountAsync") { (promise: Promise) in
|
|
67
|
+
guard Self.isStepCountAvailable() else {
|
|
68
|
+
promise.reject(StepCountUnavailableException())
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
guard Self.permissionStatus() == permissionGranted else {
|
|
73
|
+
promise.reject(MissingStepsPermissionException())
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let now = Date()
|
|
78
|
+
let startOfDay = Calendar.current.startOfDay(for: now)
|
|
79
|
+
pedometer.queryPedometerData(from: startOfDay, to: now) { data, error in
|
|
80
|
+
if let error {
|
|
81
|
+
promise.reject(PedometerQueryException(error.localizedDescription))
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
promise.resolve(data?.numberOfSteps.intValue ?? 0)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private static func isStepCountAvailable() -> Bool {
|
|
91
|
+
CMPedometer.isStepCountingAvailable()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static func permissionStatus() -> String {
|
|
95
|
+
switch CMPedometer.authorizationStatus() {
|
|
96
|
+
case .authorized:
|
|
97
|
+
return permissionGranted
|
|
98
|
+
case .notDetermined:
|
|
99
|
+
return permissionUndetermined
|
|
100
|
+
case .denied, .restricted:
|
|
101
|
+
return permissionDenied
|
|
102
|
+
@unknown default:
|
|
103
|
+
return permissionDenied
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static func permissionResponseForCurrentStatus() -> [String: Any] {
|
|
108
|
+
let status = permissionStatus()
|
|
109
|
+
return permissionResponse(status, canAskAgain: status == permissionUndetermined)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static func permissionResponse(_ status: String, canAskAgain: Bool) -> [String: Any] {
|
|
113
|
+
[
|
|
114
|
+
"status": status,
|
|
115
|
+
"canAskAgain": canAskAgain
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "expo-pedometer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core Motion and Health Connect step count module for Expo",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "node internal/module_scripts/build.js",
|
|
9
|
+
"clean": "node internal/module_scripts/clean.js",
|
|
10
|
+
"lint": "biome check .",
|
|
11
|
+
"test": "node internal/module_scripts/test.js",
|
|
12
|
+
"prepare": "node internal/module_scripts/prepare.js",
|
|
13
|
+
"open:ios": "node internal/module_scripts/open-ios.js",
|
|
14
|
+
"open:android": "node internal/module_scripts/open-android.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"android/build.gradle",
|
|
18
|
+
"android/src",
|
|
19
|
+
"app.plugin.js",
|
|
20
|
+
"build",
|
|
21
|
+
"expo-module.config.json",
|
|
22
|
+
"ios",
|
|
23
|
+
"plugin/build",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react-native",
|
|
28
|
+
"expo",
|
|
29
|
+
"expo-pedometer",
|
|
30
|
+
"ExpoPedometer"
|
|
31
|
+
],
|
|
32
|
+
"repository": "https://github.com/hills-corp/expo-pedometer",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/hills-corp/expo-pedometer/issues"
|
|
35
|
+
},
|
|
36
|
+
"author": "Injung Chung <injung.chung@gmail.com> (mu29)",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"homepage": "https://github.com/hills-corp/expo-pedometer#readme",
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@babel/core": "7.26.0",
|
|
42
|
+
"@biomejs/biome": "2.4.8",
|
|
43
|
+
"@types/jest": "29.2.1",
|
|
44
|
+
"@types/react": "19.2.14",
|
|
45
|
+
"babel-preset-expo": "55.0.8",
|
|
46
|
+
"expo": "55.0.23",
|
|
47
|
+
"expo-modules-core": "55.0.23",
|
|
48
|
+
"jest": "29.7.0",
|
|
49
|
+
"jest-expo": "55.0.9",
|
|
50
|
+
"react-native": "0.83.6",
|
|
51
|
+
"typescript": "5.9.3"
|
|
52
|
+
},
|
|
53
|
+
"jest": {
|
|
54
|
+
"preset": "jest-expo",
|
|
55
|
+
"roots": [
|
|
56
|
+
"<rootDir>/src",
|
|
57
|
+
"<rootDir>/plugin/src"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"expo": "*",
|
|
62
|
+
"react": "*",
|
|
63
|
+
"react-native": "*"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ConfigPlugin } from "expo/config-plugins";
|
|
2
|
+
type ManifestElement = {
|
|
3
|
+
$: Record<string, string>;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
"intent-filter"?: ManifestElement[];
|
|
6
|
+
action?: ManifestElement[];
|
|
7
|
+
category?: ManifestElement[];
|
|
8
|
+
"meta-data"?: ManifestElement[];
|
|
9
|
+
};
|
|
10
|
+
type ManifestApplication = {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
activity?: ManifestElement[];
|
|
13
|
+
"activity-alias"?: ManifestElement[];
|
|
14
|
+
};
|
|
15
|
+
type AndroidManifest = {
|
|
16
|
+
manifest: {
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
queries?: {
|
|
19
|
+
package?: ManifestElement[];
|
|
20
|
+
}[];
|
|
21
|
+
"uses-permission"?: ManifestElement[];
|
|
22
|
+
application?: ManifestApplication[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export type ExpoPedometerPluginProps = {
|
|
26
|
+
iosMotionPermission?: string;
|
|
27
|
+
androidMinSdkVersion?: number;
|
|
28
|
+
androidHealthConnectRationaleTitle?: string;
|
|
29
|
+
androidHealthConnectRationaleDescription?: string;
|
|
30
|
+
androidHealthConnectPrivacyPolicyUrl?: string;
|
|
31
|
+
androidHealthConnectRationaleActivity?: string;
|
|
32
|
+
};
|
|
33
|
+
export declare const withExpoPedometer: ConfigPlugin<ExpoPedometerPluginProps>;
|
|
34
|
+
export declare function setIosInfoPlist<T extends Record<string, unknown>>(infoPlist: T, props: ExpoPedometerPluginProps): T;
|
|
35
|
+
export declare function setAndroidMinSdkVersion<T extends {
|
|
36
|
+
type: string;
|
|
37
|
+
key?: string;
|
|
38
|
+
value?: string;
|
|
39
|
+
}[]>(gradleProperties: T, minSdkVersion: number): T;
|
|
40
|
+
export declare function setAndroidManifest<T extends AndroidManifest>(androidManifest: T, props: ExpoPedometerPluginProps): T;
|
|
41
|
+
export default withExpoPedometer;
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAIlB,MAAM,qBAAqB,CAAC;AAa7B,KAAK,eAAe,GAAG;IACrB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QACvB,OAAO,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,eAAe,EAAE,CAAA;SAAE,EAAE,CAAC;QAC5C,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAC;QACtC,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;KACrC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,wCAAwC,CAAC,EAAE,MAAM,CAAC;IAClD,oCAAoC,CAAC,EAAE,MAAM,CAAC;IAC9C,qCAAqC,CAAC,EAAE,MAAM,CAAC;CAChD,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,YAAY,CAAC,wBAAwB,CAoBpE,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,SAAS,EAAE,CAAC,EACZ,KAAK,EAAE,wBAAwB,KAShC;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAChG,gBAAgB,EAAE,CAAC,EACnB,aAAa,EAAE,MAAM,KAkBtB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAC1D,eAAe,EAAE,CAAC,EAClB,KAAK,EAAE,wBAAwB,KAahC;AA+GD,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withExpoPedometer = void 0;
|
|
4
|
+
exports.setIosInfoPlist = setIosInfoPlist;
|
|
5
|
+
exports.setAndroidMinSdkVersion = setAndroidMinSdkVersion;
|
|
6
|
+
exports.setAndroidManifest = setAndroidManifest;
|
|
7
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
8
|
+
const READ_STEPS_PERMISSION = "android.permission.health.READ_STEPS";
|
|
9
|
+
const HEALTH_CONNECT_PACKAGE = "com.google.android.apps.healthdata";
|
|
10
|
+
const SHOW_PERMISSIONS_RATIONALE_ACTION = "androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE";
|
|
11
|
+
const VIEW_PERMISSION_USAGE_ACTION = "android.intent.action.VIEW_PERMISSION_USAGE";
|
|
12
|
+
const HEALTH_PERMISSIONS_CATEGORY = "android.intent.category.HEALTH_PERMISSIONS";
|
|
13
|
+
const START_VIEW_PERMISSION_USAGE_PERMISSION = "android.permission.START_VIEW_PERMISSION_USAGE";
|
|
14
|
+
const DEFAULT_RATIONALE_ACTIVITY = "expo.modules.pedometer.ExpoPedometerPermissionRationaleActivity";
|
|
15
|
+
const DEFAULT_PERMISSION_USAGE_ALIAS = "ViewPermissionUsageActivity";
|
|
16
|
+
const IOS_MOTION_USAGE_DESCRIPTION = "NSMotionUsageDescription";
|
|
17
|
+
const withExpoPedometer = (config, props = {}) => {
|
|
18
|
+
config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
19
|
+
config.modResults = setIosInfoPlist(config.modResults, props);
|
|
20
|
+
return config;
|
|
21
|
+
});
|
|
22
|
+
config = (0, config_plugins_1.withGradleProperties)(config, (config) => {
|
|
23
|
+
var _a;
|
|
24
|
+
config.modResults = setAndroidMinSdkVersion(config.modResults, (_a = props.androidMinSdkVersion) !== null && _a !== void 0 ? _a : 26);
|
|
25
|
+
return config;
|
|
26
|
+
});
|
|
27
|
+
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
28
|
+
setAndroidManifest(config.modResults, props);
|
|
29
|
+
return config;
|
|
30
|
+
});
|
|
31
|
+
return config;
|
|
32
|
+
};
|
|
33
|
+
exports.withExpoPedometer = withExpoPedometer;
|
|
34
|
+
function setIosInfoPlist(infoPlist, props) {
|
|
35
|
+
var _a;
|
|
36
|
+
const plist = infoPlist;
|
|
37
|
+
if (props.iosMotionPermission || !plist[IOS_MOTION_USAGE_DESCRIPTION]) {
|
|
38
|
+
plist[IOS_MOTION_USAGE_DESCRIPTION] =
|
|
39
|
+
(_a = props.iosMotionPermission) !== null && _a !== void 0 ? _a : "Allow this app to read your step count from Motion & Fitness.";
|
|
40
|
+
}
|
|
41
|
+
return infoPlist;
|
|
42
|
+
}
|
|
43
|
+
function setAndroidMinSdkVersion(gradleProperties, minSdkVersion) {
|
|
44
|
+
var _a;
|
|
45
|
+
const targetValue = String(Math.max(minSdkVersion, 26));
|
|
46
|
+
const existing = gradleProperties.find((property) => property.type === "property" && property.key === "android.minSdkVersion");
|
|
47
|
+
if (existing) {
|
|
48
|
+
existing.value = String(Math.max(Number((_a = existing.value) !== null && _a !== void 0 ? _a : 0), Number(targetValue)));
|
|
49
|
+
return gradleProperties;
|
|
50
|
+
}
|
|
51
|
+
gradleProperties.push({
|
|
52
|
+
type: "property",
|
|
53
|
+
key: "android.minSdkVersion",
|
|
54
|
+
value: targetValue,
|
|
55
|
+
});
|
|
56
|
+
return gradleProperties;
|
|
57
|
+
}
|
|
58
|
+
function setAndroidManifest(androidManifest, props) {
|
|
59
|
+
var _a;
|
|
60
|
+
addNamedElement(androidManifest.manifest, "uses-permission", READ_STEPS_PERMISSION);
|
|
61
|
+
addQueryPackage(androidManifest, HEALTH_CONNECT_PACKAGE);
|
|
62
|
+
const application = getMainApplication(androidManifest);
|
|
63
|
+
const rationaleActivity = (_a = props.androidHealthConnectRationaleActivity) !== null && _a !== void 0 ? _a : DEFAULT_RATIONALE_ACTIVITY;
|
|
64
|
+
addOrUpdateActivity(application, rationaleActivity, props);
|
|
65
|
+
addOrUpdatePermissionUsageAlias(application, rationaleActivity);
|
|
66
|
+
return androidManifest;
|
|
67
|
+
}
|
|
68
|
+
function addQueryPackage(androidManifest, packageName) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
const manifest = androidManifest.manifest;
|
|
71
|
+
manifest.queries = (_a = manifest.queries) !== null && _a !== void 0 ? _a : [{}];
|
|
72
|
+
const queries = manifest.queries[0];
|
|
73
|
+
queries.package = (_b = queries.package) !== null && _b !== void 0 ? _b : [];
|
|
74
|
+
addNamedElement(queries, "package", packageName);
|
|
75
|
+
}
|
|
76
|
+
function getMainApplication(androidManifest) {
|
|
77
|
+
var _a;
|
|
78
|
+
androidManifest.manifest.application = (_a = androidManifest.manifest.application) !== null && _a !== void 0 ? _a : [{}];
|
|
79
|
+
return androidManifest.manifest.application[0];
|
|
80
|
+
}
|
|
81
|
+
function addOrUpdateActivity(application, activityName, props) {
|
|
82
|
+
var _a, _b, _c, _d, _e;
|
|
83
|
+
application.activity = (_a = application.activity) !== null && _a !== void 0 ? _a : [];
|
|
84
|
+
const activity = (_b = application.activity.find((activity) => activity.$["android:name"] === activityName)) !== null && _b !== void 0 ? _b : addElement(application.activity, activityName);
|
|
85
|
+
activity.$["android:exported"] = "true";
|
|
86
|
+
activity["intent-filter"] = upsertIntentFilter(activity["intent-filter"], SHOW_PERMISSIONS_RATIONALE_ACTION);
|
|
87
|
+
activity["meta-data"] = upsertMetaData(activity["meta-data"], {
|
|
88
|
+
"expo.modules.pedometer.HEALTH_PERMISSIONS_RATIONALE_TITLE": (_c = props.androidHealthConnectRationaleTitle) !== null && _c !== void 0 ? _c : "Health permissions",
|
|
89
|
+
"expo.modules.pedometer.HEALTH_PERMISSIONS_RATIONALE_DESCRIPTION": (_d = props.androidHealthConnectRationaleDescription) !== null && _d !== void 0 ? _d : "Step count is read from Health Connect to show today's walking progress.",
|
|
90
|
+
"expo.modules.pedometer.PRIVACY_POLICY_URL": (_e = props.androidHealthConnectPrivacyPolicyUrl) !== null && _e !== void 0 ? _e : "",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function addOrUpdatePermissionUsageAlias(application, targetActivity) {
|
|
94
|
+
var _a, _b;
|
|
95
|
+
application["activity-alias"] = (_a = application["activity-alias"]) !== null && _a !== void 0 ? _a : [];
|
|
96
|
+
const alias = (_b = application["activity-alias"].find((alias) => alias.$["android:name"] === DEFAULT_PERMISSION_USAGE_ALIAS)) !== null && _b !== void 0 ? _b : addElement(application["activity-alias"], DEFAULT_PERMISSION_USAGE_ALIAS);
|
|
97
|
+
alias.$["android:exported"] = "true";
|
|
98
|
+
alias.$["android:targetActivity"] = targetActivity;
|
|
99
|
+
alias.$["android:permission"] = START_VIEW_PERMISSION_USAGE_PERMISSION;
|
|
100
|
+
alias["intent-filter"] = [
|
|
101
|
+
{
|
|
102
|
+
$: {},
|
|
103
|
+
action: [{ $: { "android:name": VIEW_PERMISSION_USAGE_ACTION } }],
|
|
104
|
+
category: [{ $: { "android:name": HEALTH_PERMISSIONS_CATEGORY } }],
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
function upsertIntentFilter(intentFilters, actionName) {
|
|
109
|
+
const nextIntentFilters = intentFilters !== null && intentFilters !== void 0 ? intentFilters : [];
|
|
110
|
+
const existing = nextIntentFilters.find((intentFilter) => { var _a; return (_a = intentFilter.action) === null || _a === void 0 ? void 0 : _a.some((action) => action.$["android:name"] === actionName); });
|
|
111
|
+
if (existing) {
|
|
112
|
+
return nextIntentFilters;
|
|
113
|
+
}
|
|
114
|
+
nextIntentFilters.push({
|
|
115
|
+
$: {},
|
|
116
|
+
action: [{ $: { "android:name": actionName } }],
|
|
117
|
+
});
|
|
118
|
+
return nextIntentFilters;
|
|
119
|
+
}
|
|
120
|
+
function upsertMetaData(metadata, values) {
|
|
121
|
+
const nextMetadata = metadata !== null && metadata !== void 0 ? metadata : [];
|
|
122
|
+
for (const [name, value] of Object.entries(values)) {
|
|
123
|
+
const existing = nextMetadata.find((item) => item.$["android:name"] === name);
|
|
124
|
+
if (existing) {
|
|
125
|
+
existing.$["android:value"] = value;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
nextMetadata.push({
|
|
129
|
+
$: {
|
|
130
|
+
"android:name": name,
|
|
131
|
+
"android:value": value,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return nextMetadata;
|
|
137
|
+
}
|
|
138
|
+
function addNamedElement(parent, key, name) {
|
|
139
|
+
var _a;
|
|
140
|
+
const elements = (_a = parent[key]) !== null && _a !== void 0 ? _a : [];
|
|
141
|
+
parent[key] = elements;
|
|
142
|
+
if (!elements.some((item) => item.$["android:name"] === name)) {
|
|
143
|
+
addElement(elements, name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function addElement(elements, name) {
|
|
147
|
+
const element = { $: { "android:name": name } };
|
|
148
|
+
elements.push(element);
|
|
149
|
+
return element;
|
|
150
|
+
}
|
|
151
|
+
exports.default = exports.withExpoPedometer;
|
|
152
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NativeModule, requireNativeModule } from "expo";
|
|
2
|
+
|
|
3
|
+
import type { PermissionResponse } from "./ExpoPedometer.types";
|
|
4
|
+
|
|
5
|
+
declare class ExpoPedometerModule extends NativeModule {
|
|
6
|
+
isAvailableAsync(): Promise<boolean>;
|
|
7
|
+
getPermissionsAsync(): Promise<PermissionResponse>;
|
|
8
|
+
requestPermissionsAsync(): Promise<PermissionResponse>;
|
|
9
|
+
getTodayStepCountAsync(): Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default requireNativeModule<ExpoPedometerModule>("ExpoPedometer");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NativeModule, registerWebModule } from "expo";
|
|
2
|
+
import { UnavailabilityError } from "expo-modules-core";
|
|
3
|
+
|
|
4
|
+
import { type PermissionResponse, PermissionStatus } from "./ExpoPedometer.types";
|
|
5
|
+
|
|
6
|
+
class ExpoPedometerModule extends NativeModule {
|
|
7
|
+
async isAvailableAsync(): Promise<boolean> {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async getPermissionsAsync(): Promise<PermissionResponse> {
|
|
12
|
+
return { status: PermissionStatus.DENIED, canAskAgain: false };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async requestPermissionsAsync(): Promise<PermissionResponse> {
|
|
16
|
+
return { status: PermissionStatus.DENIED, canAskAgain: false };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getTodayStepCountAsync(): Promise<number> {
|
|
20
|
+
throw new UnavailabilityError("ExpoPedometer", "getTodayStepCountAsync");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default registerWebModule(ExpoPedometerModule, "ExpoPedometer");
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PermissionResponse } from "./ExpoPedometer.types";
|
|
2
|
+
import ExpoPedometerModule from "./ExpoPedometerModule";
|
|
3
|
+
|
|
4
|
+
export async function isAvailableAsync(): Promise<boolean> {
|
|
5
|
+
return await ExpoPedometerModule.isAvailableAsync();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function getPermissionsAsync(): Promise<PermissionResponse> {
|
|
9
|
+
return await ExpoPedometerModule.getPermissionsAsync();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function requestPermissionsAsync(): Promise<PermissionResponse> {
|
|
13
|
+
return await ExpoPedometerModule.requestPermissionsAsync();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function getTodayStepCountAsync(): Promise<number> {
|
|
17
|
+
return await ExpoPedometerModule.getTodayStepCountAsync();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export * from "./ExpoPedometer.types";
|
|
21
|
+
export * from "./usePermissions";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import type { PermissionResponse } from "./ExpoPedometer.types";
|
|
5
|
+
import ExpoPedometerModule from "./ExpoPedometerModule";
|
|
6
|
+
|
|
7
|
+
export type UsePermissionsResult = [
|
|
8
|
+
isAvailable: boolean | null,
|
|
9
|
+
permission: PermissionResponse | null,
|
|
10
|
+
requestPermission: () => Promise<PermissionResponse>,
|
|
11
|
+
getPermission: () => Promise<PermissionResponse>,
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function usePermissions(): UsePermissionsResult {
|
|
15
|
+
const isMounted = useRef(true);
|
|
16
|
+
const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
|
|
17
|
+
const [permission, setPermission] = useState<PermissionResponse | null>(null);
|
|
18
|
+
|
|
19
|
+
const getAvailability = useCallback(async () => {
|
|
20
|
+
const response = await ExpoPedometerModule.isAvailableAsync();
|
|
21
|
+
if (isMounted.current) {
|
|
22
|
+
setIsAvailable(response);
|
|
23
|
+
}
|
|
24
|
+
return response;
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const getPermission = useCallback(async () => {
|
|
28
|
+
const response = await ExpoPedometerModule.getPermissionsAsync();
|
|
29
|
+
if (isMounted.current) {
|
|
30
|
+
setPermission(response);
|
|
31
|
+
}
|
|
32
|
+
return response;
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const requestPermission = useCallback(async () => {
|
|
36
|
+
const response = await ExpoPedometerModule.requestPermissionsAsync();
|
|
37
|
+
if (isMounted.current) {
|
|
38
|
+
setPermission(response);
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
void getAvailability();
|
|
45
|
+
void getPermission();
|
|
46
|
+
}, [getAvailability, getPermission]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
isMounted.current = true;
|
|
50
|
+
return () => {
|
|
51
|
+
isMounted.current = false;
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
return [isAvailable, permission, requestPermission, getPermission];
|
|
56
|
+
}
|