react-native-geo-activity-kit 3.0.4 → 4.0.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/README.md +186 -129
- package/android/src/main/java/com/rngeoactivitykit/ActivityTransitionReceiver.kt +23 -21
- package/android/src/main/java/com/rngeoactivitykit/LocationHelper.kt +21 -10
- package/android/src/main/java/com/rngeoactivitykit/MotionDetector.kt +14 -28
- package/android/src/main/java/com/rngeoactivitykit/SensorModule.kt +4 -38
- package/android/src/main/java/com/rngeoactivitykit/TrackingService.kt +8 -0
- package/lib/module/index.js +29 -28
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +79 -49
package/README.md
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React Native Geo Activity Kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A **production-grade, battery-efficient background tracking and activity recognition library for React Native**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This library is designed for **workforce management and tracking apps**. It combines **Google Fused Location Provider** with the **Activity Recognition Transition API** to create a **Smart Tracking Engine** that automatically adjusts GPS frequency based on user movement — ensuring **high accuracy when moving** and **near-zero battery drain when stationary**.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 🚀 Key Features
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### 🧠 Smart Battery Saver
|
|
12
|
+
- **Stationary Mode**: Defaults to **5-minute intervals** when the device is still.
|
|
13
|
+
- **High-Speed Mode**: Automatically switches to **30-second intervals** the millisecond movement is detected.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
- Battery drain is negligible.
|
|
15
|
+
### ⚡ Zero-Latency Wake Up
|
|
16
|
+
- Uses **EXIT STILL** transitions to trigger GPS immediately when a user stands up or picks up the phone.
|
|
16
17
|
|
|
17
|
-
###
|
|
18
|
+
### 🛡️ Foreground Service
|
|
19
|
+
- Runs reliably in the background with a persistent notification.
|
|
20
|
+
- Fully compatible with **Android 14** foreground service requirements.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
+
### 🔔 Local Notification System
|
|
23
|
+
- **Geofence Alerts**
|
|
24
|
+
- **Entry (Green)** and **Exit (Red)** pre-styled notifications.
|
|
25
|
+
- **Generic Alerts**
|
|
26
|
+
- Trigger custom notifications for background push messages or updates.
|
|
27
|
+
|
|
28
|
+
### 🏃 Motion Detection
|
|
29
|
+
Detects the following activities:
|
|
30
|
+
- STILL
|
|
31
|
+
- WALKING
|
|
32
|
+
- RUNNING
|
|
33
|
+
- IN_VEHICLE
|
|
34
|
+
- ON_BICYCLE
|
|
35
|
+
|
|
36
|
+
### 🛰️ GPS Status Monitoring
|
|
37
|
+
- Detects when the user physically toggles the **System Location (GPS)** switch ON/OFF.
|
|
38
|
+
|
|
39
|
+
### 🚫 Fake GPS Detection
|
|
40
|
+
- Every location update includes an `is_mock` flag to detect spoofed locations.
|
|
22
41
|
|
|
23
42
|
---
|
|
24
43
|
|
|
@@ -32,132 +51,156 @@ yarn add react-native-geo-activity-kit
|
|
|
32
51
|
|
|
33
52
|
---
|
|
34
53
|
|
|
35
|
-
##
|
|
54
|
+
## ⚙️ Android Configuration (Required)
|
|
55
|
+
|
|
56
|
+
Because this library runs continuously in the background, **AndroidManifest.xml configuration is mandatory**.
|
|
36
57
|
|
|
37
|
-
### 1
|
|
58
|
+
### 1️⃣ Add Permissions
|
|
38
59
|
|
|
39
|
-
Open `android/app/src/main/AndroidManifest.xml` and add
|
|
60
|
+
Open `android/app/src/main/AndroidManifest.xml` and add:
|
|
40
61
|
|
|
41
62
|
```xml
|
|
42
|
-
<manifest
|
|
63
|
+
<manifest ...>
|
|
43
64
|
|
|
44
|
-
<!-- Location -->
|
|
45
65
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
46
66
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
47
67
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
48
68
|
|
|
49
|
-
<!-- Foreground Service (Crucial for Background Tasks) -->
|
|
50
69
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
51
70
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
|
72
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
54
73
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
55
74
|
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
<application ...>
|
|
76
|
+
```
|
|
58
77
|
|
|
59
|
-
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 2️⃣ Register Services & Receivers
|
|
81
|
+
|
|
82
|
+
Inside the `<application>` tag:
|
|
83
|
+
|
|
84
|
+
```xml
|
|
85
|
+
<application ...>
|
|
86
|
+
|
|
87
|
+
<service
|
|
88
|
+
android:name="com.rngeoactivitykit.TrackingService"
|
|
89
|
+
android:enabled="true"
|
|
90
|
+
android:exported="false"
|
|
91
|
+
android:foregroundServiceType="location" />
|
|
92
|
+
|
|
93
|
+
<receiver
|
|
94
|
+
android:name="com.rngeoactivitykit.ActivityTransitionReceiver"
|
|
95
|
+
android:exported="false"
|
|
96
|
+
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
|
|
97
|
+
<intent-filter>
|
|
98
|
+
<action android:name="com.rngeoactivitykit.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
|
|
99
|
+
</intent-filter>
|
|
100
|
+
</receiver>
|
|
101
|
+
|
|
102
|
+
</application>
|
|
60
103
|
```
|
|
61
104
|
|
|
62
|
-
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📖 Usage Guide
|
|
108
|
+
|
|
109
|
+
### 1️⃣ Request Permissions
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
import { PermissionsAndroid, Platform } from 'react-native';
|
|
113
|
+
|
|
114
|
+
async function requestPermissions() {
|
|
115
|
+
if (Platform.OS === 'android') {
|
|
116
|
+
await PermissionsAndroid.requestMultiple([
|
|
117
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
118
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION,
|
|
119
|
+
PermissionsAndroid.PERMISSIONS.ACTIVITY_RECOGNITION,
|
|
120
|
+
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
63
125
|
|
|
64
126
|
---
|
|
65
127
|
|
|
66
|
-
|
|
128
|
+
### 2️⃣ Start the Smart Tracking Service
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
import GeoKit from 'react-native-geo-activity-kit';
|
|
132
|
+
|
|
133
|
+
const startTracking = async () => {
|
|
134
|
+
await GeoKit.startForegroundService(
|
|
135
|
+
'Workforce Tracking',
|
|
136
|
+
'Monitoring location in background...',
|
|
137
|
+
9991
|
|
138
|
+
);
|
|
67
139
|
|
|
68
|
-
|
|
140
|
+
await GeoKit.startMotionDetector();
|
|
141
|
+
};
|
|
142
|
+
```
|
|
69
143
|
|
|
70
|
-
|
|
144
|
+
---
|
|
71
145
|
|
|
72
|
-
###
|
|
146
|
+
### 3️⃣ Listen for Data
|
|
73
147
|
|
|
74
|
-
```
|
|
75
|
-
import
|
|
76
|
-
import { View, Text, Button, PermissionsAndroid, Platform } from 'react-native';
|
|
148
|
+
```javascript
|
|
149
|
+
import { useEffect } from 'react';
|
|
77
150
|
import GeoKit from 'react-native-geo-activity-kit';
|
|
78
151
|
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const motionSub = GeoKit.addMotionListener((event) => {
|
|
86
|
-
console.log('Motion State:', event.state);
|
|
87
|
-
setStatus(event.state);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// 2. Location Listener
|
|
91
|
-
const locationSub = GeoKit.addLocationLogListener((loc) => {
|
|
92
|
-
console.log('Location:', loc.latitude, loc.longitude);
|
|
93
|
-
setLogs((prev) => [
|
|
94
|
-
`${loc.timestamp}: ${loc.latitude}, ${loc.longitude}`,
|
|
95
|
-
...prev,
|
|
96
|
-
]);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// 3. Error Listener
|
|
100
|
-
const errorSub = GeoKit.addLocationErrorListener((err) => {
|
|
101
|
-
console.error('GeoKit Error:', err.message);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return () => {
|
|
105
|
-
motionSub.remove();
|
|
106
|
-
locationSub.remove();
|
|
107
|
-
errorSub.remove();
|
|
108
|
-
};
|
|
109
|
-
}, []);
|
|
110
|
-
|
|
111
|
-
const startService = async () => {
|
|
112
|
-
try {
|
|
113
|
-
// 1. Ask for permissions first (Standard Android Code)
|
|
114
|
-
if (Platform.OS === 'android') {
|
|
115
|
-
await PermissionsAndroid.requestMultiple([
|
|
116
|
-
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
117
|
-
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
|
|
118
|
-
]);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// 2. Start the Foreground Service (REQUIRED for background persistence)
|
|
122
|
-
await GeoKit.startForegroundService(
|
|
123
|
-
'Location Tracker',
|
|
124
|
-
'Tracking your trips in the background...'
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// 3. Start the Motion Detector (This activates the Smart Switch)
|
|
128
|
-
// Threshold 0.8 is recommended for walking/driving detection.
|
|
129
|
-
await GeoKit.startMotionDetector(0.8);
|
|
130
|
-
|
|
131
|
-
setStatus('Started');
|
|
132
|
-
} catch (e) {
|
|
133
|
-
console.error(e);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const locationSub = GeoKit.addLocationLogListener((event) => {
|
|
154
|
+
console.log(event.latitude, event.longitude);
|
|
155
|
+
|
|
156
|
+
if (event.is_mock) {
|
|
157
|
+
console.warn('FAKE GPS DETECTED');
|
|
134
158
|
}
|
|
135
|
-
};
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const motionSub = GeoKit.addMotionListener((event) => {
|
|
162
|
+
console.log(event.activity, event.isMoving);
|
|
163
|
+
});
|
|
136
164
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
165
|
+
const gpsSub = GeoKit.addGpsStatusListener((event) => {
|
|
166
|
+
if (!event.enabled) {
|
|
167
|
+
console.error('GPS turned OFF');
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
locationSub.remove();
|
|
173
|
+
motionSub.remove();
|
|
174
|
+
gpsSub.remove();
|
|
141
175
|
};
|
|
176
|
+
}, []);
|
|
177
|
+
```
|
|
142
178
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 🔔 Notification API
|
|
182
|
+
|
|
183
|
+
### Geofence Alerts
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
GeoKit.fireGeofenceAlert('OUT', 'John Doe');
|
|
187
|
+
GeoKit.fireGeofenceAlert('IN', 'John Doe');
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Generic Alerts
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
GeoKit.fireGenericAlert('New Task', 'You have a new site visit.', 1001);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### Cancel Notification
|
|
159
201
|
|
|
160
|
-
|
|
202
|
+
```javascript
|
|
203
|
+
GeoKit.cancelGenericAlert(1001);
|
|
161
204
|
```
|
|
162
205
|
|
|
163
206
|
---
|
|
@@ -166,39 +209,53 @@ export default App;
|
|
|
166
209
|
|
|
167
210
|
### Service Control
|
|
168
211
|
|
|
169
|
-
|
|
212
|
+
| Method | Description |
|
|
213
|
+
|------|------------|
|
|
214
|
+
| startForegroundService | Starts persistent background service |
|
|
215
|
+
| stopForegroundService | Stops service |
|
|
216
|
+
| updateServiceNotification | Updates notification text |
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Motion & Location
|
|
170
221
|
|
|
171
|
-
| Method
|
|
172
|
-
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
222
|
+
| Method | Description |
|
|
223
|
+
|------|------------|
|
|
224
|
+
| startMotionDetector | Enables activity recognition |
|
|
225
|
+
| stopMotionDetector | Stops motion detection |
|
|
226
|
+
| setLocationUpdateInterval | Manual GPS override |
|
|
227
|
+
| registerGpsListener | Monitor GPS toggle |
|
|
176
228
|
|
|
177
229
|
---
|
|
178
230
|
|
|
179
|
-
|
|
231
|
+
## 📱 Supported Platforms
|
|
180
232
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
| `startMotionDetector(threshold)` | `0.8` | Starts the accelerometer and GPS logic. `threshold` = force (G) to mark MOVING. |
|
|
184
|
-
| `stopMotionDetector()` | - | Stops the sensors and GPS updates. |
|
|
185
|
-
| `setUpdateInterval(ms)` | `100` | How often the accelerometer checks for force, in milliseconds. |
|
|
186
|
-
| `setLocationUpdateInterval(ms)` | `90000` | How often GPS logs are captured when MOVING (90 seconds default). |
|
|
187
|
-
| `setStabilityThresholds(start, stop)` | `20, 3000` | `start`: consecutive checks > threshold to become MOVING; `stop`: consecutive checks < threshold to become STATIONARY. |
|
|
233
|
+
- **Android**: SDK 29 (Android 10) to SDK 34 (Android 14)
|
|
234
|
+
- **iOS**: Not supported
|
|
188
235
|
|
|
189
236
|
---
|
|
190
237
|
|
|
191
|
-
|
|
238
|
+
## 🧩 Troubleshooting
|
|
192
239
|
|
|
193
|
-
|
|
240
|
+
### Service crashes on start
|
|
241
|
+
Ensure notification ID is an integer.
|
|
194
242
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
243
|
+
### Location stops after 20 minutes
|
|
244
|
+
This is expected behavior in stationary mode.
|
|
245
|
+
|
|
246
|
+
### Fake GPS not detected
|
|
247
|
+
Ensure a mock location app is actively spoofing.
|
|
200
248
|
|
|
201
249
|
---
|
|
202
250
|
|
|
203
251
|
## 📄 License
|
|
252
|
+
|
|
204
253
|
MIT
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 👤 Author
|
|
259
|
+
|
|
260
|
+
**Kartikey Mishra**
|
|
261
|
+
Creator & Maintainer of React Native Geo Activity Kit
|
|
@@ -7,6 +7,7 @@ import android.util.Log
|
|
|
7
7
|
import com.facebook.react.bridge.Arguments
|
|
8
8
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
9
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import com.google.android.gms.location.ActivityTransition
|
|
10
11
|
import com.google.android.gms.location.ActivityTransitionResult
|
|
11
12
|
import com.google.android.gms.location.DetectedActivity
|
|
12
13
|
|
|
@@ -17,41 +18,42 @@ class ActivityTransitionReceiver : BroadcastReceiver() {
|
|
|
17
18
|
val result = ActivityTransitionResult.extractResult(intent) ?: return
|
|
18
19
|
|
|
19
20
|
for (event in result.transitionEvents) {
|
|
20
|
-
val
|
|
21
|
-
val
|
|
21
|
+
val activityTypeStr = toActivityString(event.activityType)
|
|
22
|
+
val transitionTypeStr = toTransitionString(event.transitionType)
|
|
22
23
|
|
|
23
|
-
Log.d("ActivityReceiver", "🏃 Motion Event: $
|
|
24
|
+
Log.d("ActivityReceiver", "🏃 Motion Event: $activityTypeStr ($transitionTypeStr)")
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
// PROD GRADE LOGIC:
|
|
27
|
+
// We are "Moving" if we ENTER a moving state OR if we EXIT the Still state.
|
|
28
|
+
// Exiting "Still" is the fastest way to detect movement start.
|
|
29
|
+
val isMoving = (event.transitionType == ActivityTransition.ACTIVITY_TRANSITION_ENTER &&
|
|
30
|
+
(event.activityType == DetectedActivity.WALKING ||
|
|
31
|
+
event.activityType == DetectedActivity.IN_VEHICLE ||
|
|
32
|
+
event.activityType == DetectedActivity.ON_BICYCLE ||
|
|
33
|
+
event.activityType == DetectedActivity.RUNNING)) ||
|
|
34
|
+
(event.activityType == DetectedActivity.STILL && event.transitionType == ActivityTransition.ACTIVITY_TRANSITION_EXIT)
|
|
32
35
|
|
|
33
|
-
// --- NEW: DIRECT NATIVE CONTROL (No JS required) ---
|
|
34
36
|
try {
|
|
35
37
|
if (isMoving) {
|
|
36
|
-
//
|
|
38
|
+
// User is moving: Speed up to 30 seconds
|
|
37
39
|
LocationHelper.shared?.setLocationUpdateInterval(30000)
|
|
38
|
-
} else {
|
|
39
|
-
//
|
|
40
|
-
LocationHelper.shared?.setLocationUpdateInterval(
|
|
40
|
+
} else if (event.activityType == DetectedActivity.STILL && event.transitionType == ActivityTransition.ACTIVITY_TRANSITION_ENTER) {
|
|
41
|
+
// User stopped: Slow down to 5 minutes
|
|
42
|
+
LocationHelper.shared?.setLocationUpdateInterval(300000)
|
|
41
43
|
}
|
|
42
44
|
} catch (e: Exception) {
|
|
43
|
-
Log.e("ActivityReceiver", "Failed to update location interval directly")
|
|
45
|
+
Log.e("ActivityReceiver", "Failed to update location interval directly: ${e.message}")
|
|
44
46
|
}
|
|
45
|
-
// ---------------------------------------------------
|
|
46
47
|
|
|
48
|
+
// Send to JS
|
|
47
49
|
try {
|
|
48
50
|
val reactContext = context.applicationContext as? ReactApplicationContext
|
|
49
51
|
?: TrackingService.instance?.application as? ReactApplicationContext
|
|
50
52
|
|
|
51
53
|
if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
|
|
52
54
|
val params = Arguments.createMap()
|
|
53
|
-
params.putString("activity",
|
|
54
|
-
params.putString("transition",
|
|
55
|
+
params.putString("activity", activityTypeStr)
|
|
56
|
+
params.putString("transition", transitionTypeStr)
|
|
55
57
|
params.putBoolean("isMoving", isMoving)
|
|
56
58
|
params.putString("state", if (isMoving) "MOVING" else "STATIONARY")
|
|
57
59
|
|
|
@@ -80,8 +82,8 @@ class ActivityTransitionReceiver : BroadcastReceiver() {
|
|
|
80
82
|
|
|
81
83
|
private fun toTransitionString(type: Int): String {
|
|
82
84
|
return when (type) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
ActivityTransition.ACTIVITY_TRANSITION_ENTER -> "ENTER"
|
|
86
|
+
ActivityTransition.ACTIVITY_TRANSITION_EXIT -> "EXIT"
|
|
85
87
|
else -> "UNKNOWN"
|
|
86
88
|
}
|
|
87
89
|
}
|
|
@@ -3,6 +3,7 @@ package com.rngeoactivitykit
|
|
|
3
3
|
import android.Manifest
|
|
4
4
|
import android.annotation.SuppressLint
|
|
5
5
|
import android.content.pm.PackageManager
|
|
6
|
+
import android.os.Build
|
|
6
7
|
import android.os.Looper
|
|
7
8
|
import android.util.Log
|
|
8
9
|
import androidx.core.content.ContextCompat
|
|
@@ -16,12 +17,10 @@ import java.util.TimeZone
|
|
|
16
17
|
|
|
17
18
|
class LocationHelper(private val context: ReactApplicationContext) {
|
|
18
19
|
|
|
19
|
-
// --- ADDED THIS BLOCK (Singleton Pattern) ---
|
|
20
20
|
companion object {
|
|
21
21
|
@SuppressLint("StaticFieldLeak")
|
|
22
22
|
var shared: LocationHelper? = null
|
|
23
23
|
}
|
|
24
|
-
// --------------------------------------------
|
|
25
24
|
|
|
26
25
|
private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
|
|
27
26
|
private var locationCallback: LocationCallback
|
|
@@ -35,13 +34,15 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
init {
|
|
38
|
-
|
|
39
|
-
shared = this
|
|
40
|
-
// -----------------------
|
|
37
|
+
shared = this
|
|
41
38
|
|
|
39
|
+
// PROD GRADE: Start with 5 Minutes (Battery Saver)
|
|
40
|
+
// We assume the user is stationary until the Motion Detector proves otherwise.
|
|
41
|
+
val defaultInterval = 300000L // 5 Minutes
|
|
42
|
+
|
|
42
43
|
locationRequest = LocationRequest.create().apply {
|
|
43
|
-
interval =
|
|
44
|
-
fastestInterval =
|
|
44
|
+
interval = defaultInterval
|
|
45
|
+
fastestInterval = defaultInterval
|
|
45
46
|
priority = Priority.PRIORITY_BALANCED_POWER_ACCURACY
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -50,10 +51,13 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
50
51
|
locationResult.lastLocation ?: return
|
|
51
52
|
val location = locationResult.lastLocation!!
|
|
52
53
|
|
|
54
|
+
// PROD GRADE: Filter noise. If accuracy is very bad (>200m), ignore it to save processing.
|
|
55
|
+
if (location.accuracy > 200) return
|
|
56
|
+
|
|
53
57
|
Log.d("LocationHelper", "📍 New Location: ${location.latitude}, ${location.longitude} (Acc: ${location.accuracy}m)")
|
|
54
58
|
|
|
55
59
|
var isMock = false
|
|
56
|
-
if (
|
|
60
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
57
61
|
isMock = location.isMock
|
|
58
62
|
} else {
|
|
59
63
|
isMock = location.isFromMockProvider
|
|
@@ -72,15 +76,22 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
fun setLocationUpdateInterval(intervalMs: Long) {
|
|
75
|
-
val newPriority = if (intervalMs <
|
|
79
|
+
val newPriority = if (intervalMs < 60000) {
|
|
80
|
+
// High accuracy for intervals < 1 min
|
|
76
81
|
Priority.PRIORITY_HIGH_ACCURACY
|
|
77
82
|
} else {
|
|
83
|
+
// Balanced power for stationary/slow updates
|
|
78
84
|
Priority.PRIORITY_BALANCED_POWER_ACCURACY
|
|
79
85
|
}
|
|
80
86
|
updateLocationRequest(newPriority, intervalMs)
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
fun updateLocationRequest(priority: Int, intervalMs: Long) {
|
|
90
|
+
// PROD GRADE: Prevent restarting the hardware if nothing changed
|
|
91
|
+
if (locationRequest.interval == intervalMs && locationRequest.priority == priority && isLocationClientRunning) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
84
95
|
Log.d("LocationHelper", "🔄 Updating Request: Priority=$priority, Interval=${intervalMs}ms")
|
|
85
96
|
|
|
86
97
|
locationRequest = LocationRequest.create().apply {
|
|
@@ -93,7 +104,7 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
93
104
|
stopLocationUpdates()
|
|
94
105
|
startLocationUpdates()
|
|
95
106
|
} else {
|
|
96
|
-
|
|
107
|
+
// Do not auto-start if it wasn't running. Wait for explicit start.
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
110
|
|
|
@@ -18,40 +18,26 @@ class MotionDetector(private val context: ReactApplicationContext) {
|
|
|
18
18
|
private val activityClient = ActivityRecognition.getClient(context)
|
|
19
19
|
private var pendingIntent: PendingIntent? = null
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Monitor Enter AND Exit for precise state management
|
|
22
22
|
private val transitions = listOf(
|
|
23
|
-
//
|
|
24
|
-
ActivityTransition.Builder()
|
|
25
|
-
|
|
26
|
-
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
27
|
-
.build(),
|
|
23
|
+
// STILL
|
|
24
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.STILL).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER).build(),
|
|
25
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.STILL).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT).build(),
|
|
28
26
|
|
|
29
|
-
//
|
|
30
|
-
ActivityTransition.Builder()
|
|
31
|
-
|
|
32
|
-
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
33
|
-
.build(),
|
|
27
|
+
// WALKING
|
|
28
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.WALKING).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER).build(),
|
|
29
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.WALKING).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT).build(),
|
|
34
30
|
|
|
35
|
-
//
|
|
36
|
-
ActivityTransition.Builder()
|
|
37
|
-
|
|
38
|
-
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
39
|
-
.build(),
|
|
31
|
+
// VEHICLE
|
|
32
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.IN_VEHICLE).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER).build(),
|
|
33
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.IN_VEHICLE).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT).build(),
|
|
40
34
|
|
|
41
|
-
//
|
|
42
|
-
ActivityTransition.Builder()
|
|
43
|
-
|
|
44
|
-
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
45
|
-
.build(),
|
|
46
|
-
|
|
47
|
-
// 🟢 ADDED: Detect when user STARTS Cycling
|
|
48
|
-
ActivityTransition.Builder()
|
|
49
|
-
.setActivityType(DetectedActivity.ON_BICYCLE)
|
|
50
|
-
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
51
|
-
.build()
|
|
35
|
+
// RUNNING
|
|
36
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.RUNNING).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER).build(),
|
|
37
|
+
ActivityTransition.Builder().setActivityType(DetectedActivity.RUNNING).setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT).build()
|
|
52
38
|
)
|
|
53
39
|
|
|
54
|
-
@SuppressLint("MissingPermission")
|
|
40
|
+
@SuppressLint("MissingPermission")
|
|
55
41
|
fun start(): Boolean {
|
|
56
42
|
if (!hasPermission()) {
|
|
57
43
|
return false
|
|
@@ -13,12 +13,8 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
13
13
|
|
|
14
14
|
private val notificationHelper = NotificationHelper(reactContext)
|
|
15
15
|
private val locationHelper = LocationHelper(reactContext)
|
|
16
|
-
|
|
17
|
-
// NEW: MotionDetector no longer needs a callback because
|
|
18
|
-
// the ActivityTransitionReceiver handles the events.
|
|
19
16
|
private val motionDetector = MotionDetector(reactContext)
|
|
20
17
|
|
|
21
|
-
// --- GPS STATUS RECEIVER (Detects if user turns off Location Toggle) ---
|
|
22
18
|
private val gpsStatusReceiver = object : BroadcastReceiver() {
|
|
23
19
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
24
20
|
if (intent?.action == LocationManager.PROVIDERS_CHANGED_ACTION) {
|
|
@@ -42,22 +38,18 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
init {
|
|
45
|
-
// Register GPS Status Receiver
|
|
46
41
|
val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
|
|
47
42
|
reactContext.registerReceiver(gpsStatusReceiver, filter)
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
// --- SERVICE CONTROL ---
|
|
51
|
-
|
|
52
45
|
@ReactMethod
|
|
53
|
-
// ✅ ADD "id: Int" here. Now it matches JavaScript's 3 arguments.
|
|
54
46
|
fun startForegroundService(title: String, body: String, id: Int, promise: Promise) {
|
|
55
47
|
try {
|
|
56
48
|
val intent = Intent(reactApplicationContext, TrackingService::class.java)
|
|
57
49
|
intent.action = TrackingService.ACTION_START
|
|
58
50
|
intent.putExtra("title", title)
|
|
59
51
|
intent.putExtra("body", body)
|
|
60
|
-
// intent.putExtra("id", id) //
|
|
52
|
+
// intent.putExtra("id", id) // Pass ID if you want to make the persistent notification dynamic later
|
|
61
53
|
|
|
62
54
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
63
55
|
reactApplicationContext.startForegroundService(intent)
|
|
@@ -75,7 +67,7 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
75
67
|
try {
|
|
76
68
|
val intent = Intent(reactApplicationContext, TrackingService::class.java)
|
|
77
69
|
intent.action = TrackingService.ACTION_STOP
|
|
78
|
-
reactApplicationContext.startService(intent)
|
|
70
|
+
reactApplicationContext.startService(intent)
|
|
79
71
|
promise.resolve(true)
|
|
80
72
|
} catch (e: Exception) {
|
|
81
73
|
promise.reject("STOP_SERVICE_FAILED", e.message)
|
|
@@ -101,18 +93,13 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
101
93
|
}
|
|
102
94
|
}
|
|
103
95
|
|
|
104
|
-
// --- MOTION DETECTOR (Updated for Activity Recognition) ---
|
|
105
|
-
|
|
106
96
|
@ReactMethod
|
|
107
97
|
fun startMotionDetector(threshold: Double, promise: Promise) {
|
|
108
|
-
// NOTE: 'threshold' is ignored now because Activity Recognition
|
|
109
|
-
// handles sensitivity automatically using Machine Learning.
|
|
110
98
|
try {
|
|
111
99
|
val success = motionDetector.start()
|
|
112
100
|
if (success) {
|
|
113
101
|
promise.resolve(true)
|
|
114
102
|
} else {
|
|
115
|
-
// This usually means Permissions are missing
|
|
116
103
|
promise.reject("PERMISSION_DENIED", "ACTIVITY_RECOGNITION permission is required")
|
|
117
104
|
}
|
|
118
105
|
} catch (e: Exception) {
|
|
@@ -130,8 +117,6 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
130
117
|
}
|
|
131
118
|
}
|
|
132
119
|
|
|
133
|
-
// --- LOCATION CONTROL ---
|
|
134
|
-
|
|
135
120
|
@ReactMethod
|
|
136
121
|
fun setLocationUpdateInterval(intervalMs: Int) {
|
|
137
122
|
try {
|
|
@@ -141,8 +126,6 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
141
126
|
}
|
|
142
127
|
}
|
|
143
128
|
|
|
144
|
-
// --- ALERTS & NOTIFICATIONS ---
|
|
145
|
-
|
|
146
129
|
@ReactMethod
|
|
147
130
|
fun fireGeofenceAlert(type: String, userName: String, promise: Promise) {
|
|
148
131
|
try {
|
|
@@ -175,7 +158,6 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
175
158
|
|
|
176
159
|
@ReactMethod
|
|
177
160
|
fun registerGpsListener(promise: Promise) {
|
|
178
|
-
// Already registered in init, just checking permission state
|
|
179
161
|
try {
|
|
180
162
|
val locationManager = reactApplicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
|
181
163
|
val isEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
|
@@ -185,17 +167,11 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
185
167
|
}
|
|
186
168
|
}
|
|
187
169
|
|
|
188
|
-
// --- EVENT EMITTER BOILERPLATE ---
|
|
189
|
-
|
|
190
170
|
@ReactMethod
|
|
191
|
-
fun addListener(eventName: String) {
|
|
192
|
-
// Required for NativeEventEmitter
|
|
193
|
-
}
|
|
171
|
+
fun addListener(eventName: String) {}
|
|
194
172
|
|
|
195
173
|
@ReactMethod
|
|
196
|
-
fun removeListeners(count: Int) {
|
|
197
|
-
// Required for NativeEventEmitter
|
|
198
|
-
}
|
|
174
|
+
fun removeListeners(count: Int) {}
|
|
199
175
|
|
|
200
176
|
private fun sendEvent(eventName: String, params: Any?) {
|
|
201
177
|
try {
|
|
@@ -209,22 +185,12 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
209
185
|
}
|
|
210
186
|
}
|
|
211
187
|
|
|
212
|
-
// --- CLEANUP ---
|
|
213
|
-
|
|
214
188
|
override fun onCatalystInstanceDestroy() {
|
|
215
189
|
super.onCatalystInstanceDestroy()
|
|
216
190
|
try {
|
|
217
|
-
// 1. Unregister Receivers
|
|
218
191
|
reactApplicationContext.unregisterReceiver(gpsStatusReceiver)
|
|
219
|
-
|
|
220
|
-
// 2. Stop Sensors
|
|
221
192
|
motionDetector.stop()
|
|
222
193
|
locationHelper.stopLocationUpdates()
|
|
223
|
-
|
|
224
|
-
// 3. Stop Service (Optional: Depending on whether you want it to persist after app kill)
|
|
225
|
-
// Ideally, we DO NOT stop the service here if we want it running in background.
|
|
226
|
-
// But we do clean up listeners.
|
|
227
|
-
|
|
228
194
|
} catch (e: Exception) {
|
|
229
195
|
e.printStackTrace()
|
|
230
196
|
}
|
|
@@ -38,11 +38,15 @@ class TrackingService : Service() {
|
|
|
38
38
|
try {
|
|
39
39
|
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
|
|
40
40
|
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GeoKit::TrackingLock")
|
|
41
|
+
wakeLock?.setReferenceCounted(false) // PROD: Ensure we don't over-release
|
|
41
42
|
wakeLock?.acquire()
|
|
42
43
|
Log.d("TrackingService", "🔒 WakeLock Acquired (Permanent)")
|
|
43
44
|
} catch (e: Exception) {
|
|
44
45
|
Log.e("TrackingService", "❌ Failed to acquire WakeLock: ${e.message}")
|
|
45
46
|
}
|
|
47
|
+
|
|
48
|
+
// Start Location Updates on Create via Helper
|
|
49
|
+
LocationHelper.shared?.startLocationUpdates()
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
@@ -54,6 +58,9 @@ class TrackingService : Service() {
|
|
|
54
58
|
val title = intent.getStringExtra("title") ?: "Location Active"
|
|
55
59
|
val body = intent.getStringExtra("body") ?: "Monitoring in background..."
|
|
56
60
|
startForegroundService(title, body)
|
|
61
|
+
|
|
62
|
+
// Ensure sensors are running
|
|
63
|
+
LocationHelper.shared?.startLocationUpdates()
|
|
57
64
|
}
|
|
58
65
|
ACTION_STOP -> {
|
|
59
66
|
Log.d("TrackingService", "⏹️ Action: STOP")
|
|
@@ -83,6 +90,7 @@ class TrackingService : Service() {
|
|
|
83
90
|
|
|
84
91
|
private fun stopForegroundService() {
|
|
85
92
|
try {
|
|
93
|
+
LocationHelper.shared?.stopLocationUpdates()
|
|
86
94
|
stopForeground(true)
|
|
87
95
|
stopSelf()
|
|
88
96
|
} catch (e: Exception) {
|
package/lib/module/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
4
|
-
// ✅ Fixed: Modern Type & Correct Import
|
|
5
|
-
|
|
6
4
|
const LINKING_ERROR = `The package 'react-native-geo-activity-kit' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
|
|
7
5
|
ios: "- You have run 'pod install'\n",
|
|
8
6
|
default: ''
|
|
@@ -13,33 +11,36 @@ const RNSensorModule = NativeModules.RNSensorModule ? NativeModules.RNSensorModu
|
|
|
13
11
|
}
|
|
14
12
|
});
|
|
15
13
|
const emitter = new NativeEventEmitter(RNSensorModule);
|
|
14
|
+
export const startForegroundService = (title, body, id) => RNSensorModule.startForegroundService(title, body, id);
|
|
15
|
+
export const stopForegroundService = () => RNSensorModule.stopForegroundService();
|
|
16
|
+
export const updateServiceNotification = (title, body) => RNSensorModule.updateServiceNotification(title, body);
|
|
17
|
+
export const startMotionDetector = (confidence = 75) => RNSensorModule.startMotionDetector(confidence);
|
|
18
|
+
export const stopMotionDetector = () => RNSensorModule.stopMotionDetector();
|
|
19
|
+
export const setLocationUpdateInterval = ms => RNSensorModule.setLocationUpdateInterval(ms);
|
|
20
|
+
export const fireGeofenceAlert = (type, userName) => RNSensorModule.fireGeofenceAlert(type, userName);
|
|
21
|
+
export const fireGenericAlert = (title, body, id) => RNSensorModule.fireGenericAlert(title, body, id);
|
|
22
|
+
export const cancelGenericAlert = id => RNSensorModule.cancelGenericAlert(id);
|
|
23
|
+
export const registerGpsListener = () => RNSensorModule.registerGpsListener();
|
|
24
|
+
export const addGpsStatusListener = cb => emitter.addListener('onGpsStatusChanged', event => cb(event));
|
|
25
|
+
export const addMotionListener = cb => emitter.addListener('onMotionStateChanged', event => cb(event));
|
|
26
|
+
export const addLocationLogListener = cb => emitter.addListener('onLocationLog', event => cb(event));
|
|
27
|
+
export const addLocationErrorListener = cb => emitter.addListener('onLocationError', event => cb(event));
|
|
16
28
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
// 3. Export the Object
|
|
29
|
+
// Default export for backward compatibility
|
|
20
30
|
export default {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
registerGpsListener: () => RNSensorModule.registerGpsListener(),
|
|
36
|
-
// --- Event Listeners ---
|
|
37
|
-
// Note: We cast event to 'any' to avoid TS strict object checks,
|
|
38
|
-
// and return EventSubscription (modern) instead of EmitterSubscription (deprecated).
|
|
39
|
-
|
|
40
|
-
addGpsStatusListener: cb => emitter.addListener('onGpsStatusChanged', event => cb(event)),
|
|
41
|
-
addMotionListener: cb => emitter.addListener('onMotionStateChanged', event => cb(event)),
|
|
42
|
-
addLocationLogListener: cb => emitter.addListener('onLocationLog', event => cb(event)),
|
|
43
|
-
addLocationErrorListener: cb => emitter.addListener('onLocationError', event => cb(event))
|
|
31
|
+
startForegroundService,
|
|
32
|
+
stopForegroundService,
|
|
33
|
+
updateServiceNotification,
|
|
34
|
+
startMotionDetector,
|
|
35
|
+
stopMotionDetector,
|
|
36
|
+
setLocationUpdateInterval,
|
|
37
|
+
fireGeofenceAlert,
|
|
38
|
+
fireGenericAlert,
|
|
39
|
+
cancelGenericAlert,
|
|
40
|
+
registerGpsListener,
|
|
41
|
+
addGpsStatusListener,
|
|
42
|
+
addMotionListener,
|
|
43
|
+
addLocationLogListener,
|
|
44
|
+
addLocationErrorListener
|
|
44
45
|
};
|
|
45
46
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","NativeEventEmitter","Platform","LINKING_ERROR","select","ios","default","RNSensorModule","Proxy","get","Error","emitter","startForegroundService","title","body","id","stopForegroundService","updateServiceNotification","startMotionDetector","confidence","stopMotionDetector","setLocationUpdateInterval","ms","fireGeofenceAlert","type","userName","fireGenericAlert","cancelGenericAlert","registerGpsListener","addGpsStatusListener","cb","addListener","event","addMotionListener","addLocationLogListener","addLocationErrorListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,QAAQ,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["NativeModules","NativeEventEmitter","Platform","LINKING_ERROR","select","ios","default","RNSensorModule","Proxy","get","Error","emitter","startForegroundService","title","body","id","stopForegroundService","updateServiceNotification","startMotionDetector","confidence","stopMotionDetector","setLocationUpdateInterval","ms","fireGeofenceAlert","type","userName","fireGenericAlert","cancelGenericAlert","registerGpsListener","addGpsStatusListener","cb","addListener","event","addMotionListener","addLocationLogListener","addLocationErrorListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -15,6 +15,22 @@ export interface ErrorEvent {
|
|
|
15
15
|
error: string;
|
|
16
16
|
message: string;
|
|
17
17
|
}
|
|
18
|
+
export declare const startForegroundService: (title: string, body: string, id: number) => Promise<boolean>;
|
|
19
|
+
export declare const stopForegroundService: () => Promise<boolean>;
|
|
20
|
+
export declare const updateServiceNotification: (title: string, body: string) => Promise<boolean>;
|
|
21
|
+
export declare const startMotionDetector: (confidence?: number) => Promise<boolean>;
|
|
22
|
+
export declare const stopMotionDetector: () => Promise<boolean>;
|
|
23
|
+
export declare const setLocationUpdateInterval: (ms: number) => Promise<void>;
|
|
24
|
+
export declare const fireGeofenceAlert: (type: "IN" | "OUT", userName: string) => Promise<boolean>;
|
|
25
|
+
export declare const fireGenericAlert: (title: string, body: string, id: number) => Promise<boolean>;
|
|
26
|
+
export declare const cancelGenericAlert: (id: number) => Promise<boolean>;
|
|
27
|
+
export declare const registerGpsListener: () => Promise<boolean>;
|
|
28
|
+
export declare const addGpsStatusListener: (cb: (event: {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
}) => void) => EventSubscription;
|
|
31
|
+
export declare const addMotionListener: (cb: (event: MotionEvent) => void) => EventSubscription;
|
|
32
|
+
export declare const addLocationLogListener: (cb: (event: LocationEvent) => void) => EventSubscription;
|
|
33
|
+
export declare const addLocationErrorListener: (cb: (event: ErrorEvent) => void) => EventSubscription;
|
|
18
34
|
declare const _default: {
|
|
19
35
|
startForegroundService: (title: string, body: string, id: number) => Promise<boolean>;
|
|
20
36
|
stopForegroundService: () => Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-geo-activity-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Battery-efficient location tracking with motion detection and native notifications.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
2
|
-
import type { EventSubscription } from 'react-native';
|
|
2
|
+
import type { EventSubscription } from 'react-native';
|
|
3
3
|
|
|
4
4
|
const LINKING_ERROR =
|
|
5
5
|
`The package 'react-native-geo-activity-kit' doesn't seem to be linked. Make sure: \n\n` +
|
|
@@ -10,17 +10,16 @@ const LINKING_ERROR =
|
|
|
10
10
|
const RNSensorModule = NativeModules.RNSensorModule
|
|
11
11
|
? NativeModules.RNSensorModule
|
|
12
12
|
: new Proxy(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
{},
|
|
14
|
+
{
|
|
15
|
+
get() {
|
|
16
|
+
throw new Error(LINKING_ERROR);
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
20
|
|
|
21
21
|
const emitter = new NativeEventEmitter(RNSensorModule);
|
|
22
22
|
|
|
23
|
-
// 2. Define Types
|
|
24
23
|
export interface LocationEvent {
|
|
25
24
|
latitude: number;
|
|
26
25
|
longitude: number;
|
|
@@ -30,7 +29,13 @@ export interface LocationEvent {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export interface MotionEvent {
|
|
33
|
-
activity:
|
|
32
|
+
activity:
|
|
33
|
+
| 'STILL'
|
|
34
|
+
| 'WALKING'
|
|
35
|
+
| 'RUNNING'
|
|
36
|
+
| 'ON_BICYCLE'
|
|
37
|
+
| 'IN_VEHICLE'
|
|
38
|
+
| 'UNKNOWN';
|
|
34
39
|
isMoving: boolean;
|
|
35
40
|
state: 'MOVING' | 'STATIONARY';
|
|
36
41
|
}
|
|
@@ -40,56 +45,81 @@ export interface ErrorEvent {
|
|
|
40
45
|
message: string;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
export const startForegroundService = (
|
|
49
|
+
title: string,
|
|
50
|
+
body: string,
|
|
51
|
+
id: number
|
|
52
|
+
): Promise<boolean> => RNSensorModule.startForegroundService(title, body, id);
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
export const stopForegroundService = (): Promise<boolean> =>
|
|
55
|
+
RNSensorModule.stopForegroundService();
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
export const updateServiceNotification = (
|
|
58
|
+
title: string,
|
|
59
|
+
body: string
|
|
60
|
+
): Promise<boolean> => RNSensorModule.updateServiceNotification(title, body);
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
export const startMotionDetector = (
|
|
63
|
+
confidence: number = 75
|
|
64
|
+
): Promise<boolean> => RNSensorModule.startMotionDetector(confidence);
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
export const stopMotionDetector = (): Promise<boolean> =>
|
|
67
|
+
RNSensorModule.stopMotionDetector();
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
setLocationUpdateInterval
|
|
64
|
-
RNSensorModule.setLocationUpdateInterval(ms),
|
|
69
|
+
export const setLocationUpdateInterval = (ms: number): Promise<void> =>
|
|
70
|
+
RNSensorModule.setLocationUpdateInterval(ms);
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
export const fireGeofenceAlert = (
|
|
73
|
+
type: 'IN' | 'OUT',
|
|
74
|
+
userName: string
|
|
75
|
+
): Promise<boolean> => RNSensorModule.fireGeofenceAlert(type, userName);
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
export const fireGenericAlert = (
|
|
78
|
+
title: string,
|
|
79
|
+
body: string,
|
|
80
|
+
id: number
|
|
81
|
+
): Promise<boolean> => RNSensorModule.fireGenericAlert(title, body, id);
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
export const cancelGenericAlert = (id: number): Promise<boolean> =>
|
|
84
|
+
RNSensorModule.cancelGenericAlert(id);
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
registerGpsListener
|
|
78
|
-
RNSensorModule.registerGpsListener(),
|
|
86
|
+
export const registerGpsListener = (): Promise<boolean> =>
|
|
87
|
+
RNSensorModule.registerGpsListener();
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
export const addGpsStatusListener = (
|
|
90
|
+
cb: (event: { enabled: boolean }) => void
|
|
91
|
+
): EventSubscription =>
|
|
92
|
+
emitter.addListener('onGpsStatusChanged', (event: any) => cb(event));
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
export const addMotionListener = (
|
|
95
|
+
cb: (event: MotionEvent) => void
|
|
96
|
+
): EventSubscription =>
|
|
97
|
+
emitter.addListener('onMotionStateChanged', (event: any) => cb(event));
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
export const addLocationLogListener = (
|
|
100
|
+
cb: (event: LocationEvent) => void
|
|
101
|
+
): EventSubscription =>
|
|
102
|
+
emitter.addListener('onLocationLog', (event: any) => cb(event));
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
export const addLocationErrorListener = (
|
|
105
|
+
cb: (event: ErrorEvent) => void
|
|
106
|
+
): EventSubscription =>
|
|
107
|
+
emitter.addListener('onLocationError', (event: any) => cb(event));
|
|
92
108
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
// Default export for backward compatibility
|
|
110
|
+
export default {
|
|
111
|
+
startForegroundService,
|
|
112
|
+
stopForegroundService,
|
|
113
|
+
updateServiceNotification,
|
|
114
|
+
startMotionDetector,
|
|
115
|
+
stopMotionDetector,
|
|
116
|
+
setLocationUpdateInterval,
|
|
117
|
+
fireGeofenceAlert,
|
|
118
|
+
fireGenericAlert,
|
|
119
|
+
cancelGenericAlert,
|
|
120
|
+
registerGpsListener,
|
|
121
|
+
addGpsStatusListener,
|
|
122
|
+
addMotionListener,
|
|
123
|
+
addLocationLogListener,
|
|
124
|
+
addLocationErrorListener,
|
|
125
|
+
};
|