react-native-geo-activity-kit 1.2.1 → 1.2.5
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 +1 -2
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/rngeoactivitykit/ActivityTransitionReceiver.kt +88 -0
- package/android/src/main/java/com/rngeoactivitykit/LocationHelper.kt +44 -17
- package/android/src/main/java/com/rngeoactivitykit/MotionDetector.kt +70 -97
- package/android/src/main/java/com/rngeoactivitykit/SensorModule.kt +137 -80
- package/android/src/main/java/com/rngeoactivitykit/TrackingService.kt +16 -9
- package/lib/module/index.js +29 -12
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +26 -8
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.tsx +40 -19
- package/lib/module/NativeGeoActivityKit.js +0 -5
- package/lib/module/NativeGeoActivityKit.js.map +0 -1
- package/lib/typescript/src/NativeGeoActivityKit.d.ts +0 -7
- package/lib/typescript/src/NativeGeoActivityKit.d.ts.map +0 -1
- package/src/NativeGeoActivityKit.ts +0 -7
package/README.md
CHANGED
|
@@ -7,12 +7,25 @@
|
|
|
7
7
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
8
8
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
9
9
|
|
|
10
|
+
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
|
11
|
+
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
|
|
12
|
+
|
|
10
13
|
<application>
|
|
11
14
|
<service
|
|
12
15
|
android:name=".TrackingService"
|
|
13
16
|
android:enabled="true"
|
|
14
17
|
android:exported="false"
|
|
15
18
|
android:foregroundServiceType="location" />
|
|
19
|
+
|
|
20
|
+
<receiver
|
|
21
|
+
android:name=".ActivityTransitionReceiver"
|
|
22
|
+
android:enabled="true"
|
|
23
|
+
android:exported="false"
|
|
24
|
+
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
|
|
25
|
+
<intent-filter>
|
|
26
|
+
<action android:name="com.rngeoactivitykit.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
|
|
27
|
+
</intent-filter>
|
|
28
|
+
</receiver>
|
|
16
29
|
</application>
|
|
17
30
|
|
|
18
31
|
</manifest>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package com.rngeoactivitykit
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import com.google.android.gms.location.ActivityTransitionResult
|
|
11
|
+
import com.google.android.gms.location.DetectedActivity
|
|
12
|
+
|
|
13
|
+
class ActivityTransitionReceiver : BroadcastReceiver() {
|
|
14
|
+
|
|
15
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
16
|
+
if (ActivityTransitionResult.hasResult(intent)) {
|
|
17
|
+
val result = ActivityTransitionResult.extractResult(intent) ?: return
|
|
18
|
+
|
|
19
|
+
for (event in result.transitionEvents) {
|
|
20
|
+
val activityType = toActivityString(event.activityType)
|
|
21
|
+
val transitionType = toTransitionString(event.transitionType)
|
|
22
|
+
|
|
23
|
+
Log.d("ActivityReceiver", "🏃 Motion Event: $activityType ($transitionType)")
|
|
24
|
+
|
|
25
|
+
val isMoving = when(event.activityType) {
|
|
26
|
+
DetectedActivity.WALKING,
|
|
27
|
+
DetectedActivity.RUNNING,
|
|
28
|
+
DetectedActivity.ON_BICYCLE,
|
|
29
|
+
DetectedActivity.IN_VEHICLE -> true
|
|
30
|
+
else -> false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --- NEW: DIRECT NATIVE CONTROL (No JS required) ---
|
|
34
|
+
try {
|
|
35
|
+
if (isMoving) {
|
|
36
|
+
// Fast Updates (30s) when moving
|
|
37
|
+
LocationHelper.shared?.setLocationUpdateInterval(30000)
|
|
38
|
+
} else {
|
|
39
|
+
// Slow Updates (5 mins) when STILL - BATTERY SAVER
|
|
40
|
+
LocationHelper.shared?.setLocationUpdateInterval(5 * 60 * 1000)
|
|
41
|
+
}
|
|
42
|
+
} catch (e: Exception) {
|
|
43
|
+
Log.e("ActivityReceiver", "Failed to update location interval directly")
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
val reactContext = context.applicationContext as? ReactApplicationContext
|
|
49
|
+
?: TrackingService.instance?.application as? ReactApplicationContext
|
|
50
|
+
|
|
51
|
+
if (reactContext != null && reactContext.hasActiveCatalystInstance()) {
|
|
52
|
+
val params = Arguments.createMap()
|
|
53
|
+
params.putString("activity", activityType)
|
|
54
|
+
params.putString("transition", transitionType)
|
|
55
|
+
params.putBoolean("isMoving", isMoving)
|
|
56
|
+
params.putString("state", if (isMoving) "MOVING" else "STATIONARY")
|
|
57
|
+
|
|
58
|
+
reactContext
|
|
59
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
60
|
+
.emit("onMotionStateChanged", params)
|
|
61
|
+
}
|
|
62
|
+
} catch (e: Exception) {
|
|
63
|
+
Log.e("ActivityReceiver", "JS Bridge Error: ${e.message}")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private fun toActivityString(type: Int): String {
|
|
70
|
+
return when (type) {
|
|
71
|
+
DetectedActivity.STILL -> "STILL"
|
|
72
|
+
DetectedActivity.WALKING -> "WALKING"
|
|
73
|
+
DetectedActivity.RUNNING -> "RUNNING"
|
|
74
|
+
DetectedActivity.ON_BICYCLE -> "ON_BICYCLE"
|
|
75
|
+
DetectedActivity.IN_VEHICLE -> "IN_VEHICLE"
|
|
76
|
+
DetectedActivity.TILTING -> "TILTING"
|
|
77
|
+
else -> "UNKNOWN"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private fun toTransitionString(type: Int): String {
|
|
82
|
+
return when (type) {
|
|
83
|
+
com.google.android.gms.location.ActivityTransition.ACTIVITY_TRANSITION_ENTER -> "ENTER"
|
|
84
|
+
com.google.android.gms.location.ActivityTransition.ACTIVITY_TRANSITION_EXIT -> "EXIT"
|
|
85
|
+
else -> "UNKNOWN"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -16,6 +16,13 @@ import java.util.TimeZone
|
|
|
16
16
|
|
|
17
17
|
class LocationHelper(private val context: ReactApplicationContext) {
|
|
18
18
|
|
|
19
|
+
// --- ADDED THIS BLOCK (Singleton Pattern) ---
|
|
20
|
+
companion object {
|
|
21
|
+
@SuppressLint("StaticFieldLeak")
|
|
22
|
+
var shared: LocationHelper? = null
|
|
23
|
+
}
|
|
24
|
+
// --------------------------------------------
|
|
25
|
+
|
|
19
26
|
private val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
|
|
20
27
|
private var locationCallback: LocationCallback
|
|
21
28
|
private var locationRequest: LocationRequest
|
|
@@ -28,6 +35,10 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
init {
|
|
38
|
+
// --- ADDED THIS LINE ---
|
|
39
|
+
shared = this
|
|
40
|
+
// -----------------------
|
|
41
|
+
|
|
31
42
|
locationRequest = LocationRequest.create().apply {
|
|
32
43
|
interval = 30000
|
|
33
44
|
fastestInterval = 30000
|
|
@@ -39,21 +50,38 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
39
50
|
locationResult.lastLocation ?: return
|
|
40
51
|
val location = locationResult.lastLocation!!
|
|
41
52
|
|
|
53
|
+
Log.d("LocationHelper", "📍 New Location: ${location.latitude}, ${location.longitude} (Acc: ${location.accuracy}m)")
|
|
54
|
+
|
|
55
|
+
var isMock = false
|
|
56
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
|
57
|
+
isMock = location.isMock
|
|
58
|
+
} else {
|
|
59
|
+
isMock = location.isFromMockProvider
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
val params = Arguments.createMap()
|
|
43
63
|
params.putDouble("latitude", location.latitude)
|
|
44
64
|
params.putDouble("longitude", location.longitude)
|
|
45
65
|
params.putString("timestamp", isoFormatter.format(Date(location.time)))
|
|
46
66
|
params.putDouble("accuracy", location.accuracy.toDouble())
|
|
67
|
+
params.putBoolean("is_mock", isMock)
|
|
47
68
|
|
|
48
69
|
sendEvent("onLocationLog", params)
|
|
49
70
|
}
|
|
50
71
|
}
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
fun
|
|
54
|
-
|
|
74
|
+
fun setLocationUpdateInterval(intervalMs: Long) {
|
|
75
|
+
val newPriority = if (intervalMs < 10000) {
|
|
76
|
+
Priority.PRIORITY_HIGH_ACCURACY
|
|
77
|
+
} else {
|
|
78
|
+
Priority.PRIORITY_BALANCED_POWER_ACCURACY
|
|
79
|
+
}
|
|
80
|
+
updateLocationRequest(newPriority, intervalMs)
|
|
81
|
+
}
|
|
55
82
|
|
|
56
|
-
|
|
83
|
+
fun updateLocationRequest(priority: Int, intervalMs: Long) {
|
|
84
|
+
Log.d("LocationHelper", "🔄 Updating Request: Priority=$priority, Interval=${intervalMs}ms")
|
|
57
85
|
|
|
58
86
|
locationRequest = LocationRequest.create().apply {
|
|
59
87
|
this.interval = intervalMs
|
|
@@ -61,10 +89,11 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
61
89
|
this.priority = priority
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
// Restart if running to apply changes
|
|
65
92
|
if (isLocationClientRunning) {
|
|
66
93
|
stopLocationUpdates()
|
|
67
94
|
startLocationUpdates()
|
|
95
|
+
} else {
|
|
96
|
+
startLocationUpdates()
|
|
68
97
|
}
|
|
69
98
|
}
|
|
70
99
|
|
|
@@ -72,21 +101,15 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
72
101
|
fun startLocationUpdates() {
|
|
73
102
|
if (isLocationClientRunning) return
|
|
74
103
|
if (!hasLocationPermission()) {
|
|
75
|
-
|
|
76
|
-
params.putString("error", "LOCATION_PERMISSION_DENIED")
|
|
77
|
-
params.putString("message", "Location permission is not granted.")
|
|
78
|
-
sendEvent("onLocationError", params)
|
|
104
|
+
Log.e("LocationHelper", "Permission Missing")
|
|
79
105
|
return
|
|
80
106
|
}
|
|
81
107
|
try {
|
|
82
108
|
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
|
|
83
109
|
isLocationClientRunning = true
|
|
84
|
-
Log.
|
|
110
|
+
Log.d("LocationHelper", "✅ Location Updates STARTED.")
|
|
85
111
|
} catch (e: Exception) {
|
|
86
|
-
|
|
87
|
-
params.putString("error", "START_LOCATION_FAILED")
|
|
88
|
-
params.putString("message", "Error starting location: ${e.message}")
|
|
89
|
-
sendEvent("onLocationError", params)
|
|
112
|
+
Log.e("LocationHelper", "Error starting location: ${e.message}")
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
115
|
|
|
@@ -95,9 +118,9 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
95
118
|
try {
|
|
96
119
|
fusedLocationClient.removeLocationUpdates(locationCallback)
|
|
97
120
|
isLocationClientRunning = false
|
|
98
|
-
Log.
|
|
121
|
+
Log.d("LocationHelper", "🛑 Location Updates STOPPED.")
|
|
99
122
|
} catch (e: Exception) {
|
|
100
|
-
Log.e("LocationHelper", "Failed to stop
|
|
123
|
+
Log.e("LocationHelper", "Failed to stop: " + e.message)
|
|
101
124
|
}
|
|
102
125
|
}
|
|
103
126
|
|
|
@@ -108,8 +131,12 @@ class LocationHelper(private val context: ReactApplicationContext) {
|
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
private fun sendEvent(eventName: String, params: Any?) {
|
|
111
|
-
|
|
112
|
-
context.
|
|
134
|
+
try {
|
|
135
|
+
if (context.hasActiveCatalystInstance()) {
|
|
136
|
+
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit(eventName, params)
|
|
137
|
+
}
|
|
138
|
+
} catch (e: Exception) {
|
|
139
|
+
Log.e("LocationHelper", "JS Error: ${e.message}")
|
|
113
140
|
}
|
|
114
141
|
}
|
|
115
142
|
}
|
|
@@ -1,115 +1,88 @@
|
|
|
1
1
|
package com.rngeoactivitykit
|
|
2
2
|
|
|
3
|
-
import android.
|
|
4
|
-
import android.
|
|
5
|
-
import android.
|
|
6
|
-
import android.
|
|
7
|
-
import android.
|
|
8
|
-
import
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.annotation.SuppressLint
|
|
5
|
+
import android.app.PendingIntent
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.pm.PackageManager
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import androidx.core.content.ContextCompat
|
|
9
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
10
|
-
import com.
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
import com.google.android.gms.location.ActivityRecognition
|
|
12
|
+
import com.google.android.gms.location.ActivityTransition
|
|
13
|
+
import com.google.android.gms.location.ActivityTransitionRequest
|
|
14
|
+
import com.google.android.gms.location.DetectedActivity
|
|
15
|
+
|
|
16
|
+
class MotionDetector(private val context: ReactApplicationContext) {
|
|
17
|
+
|
|
18
|
+
private val activityClient = ActivityRecognition.getClient(context)
|
|
19
|
+
private var pendingIntent: PendingIntent? = null
|
|
20
|
+
|
|
21
|
+
// We define which activities trigger a wake-up
|
|
22
|
+
private val transitions = listOf(
|
|
23
|
+
// Detect when user STOPS moving
|
|
24
|
+
ActivityTransition.Builder()
|
|
25
|
+
.setActivityType(DetectedActivity.STILL)
|
|
26
|
+
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
27
|
+
.build(),
|
|
28
|
+
|
|
29
|
+
// Detect when user STARTS Walking
|
|
30
|
+
ActivityTransition.Builder()
|
|
31
|
+
.setActivityType(DetectedActivity.WALKING)
|
|
32
|
+
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
33
|
+
.build(),
|
|
34
|
+
|
|
35
|
+
// Detect when user STARTS Driving
|
|
36
|
+
ActivityTransition.Builder()
|
|
37
|
+
.setActivityType(DetectedActivity.IN_VEHICLE)
|
|
38
|
+
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
|
|
39
|
+
.build()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@SuppressLint("MissingPermission") // Checked in start()
|
|
43
|
+
fun start(): Boolean {
|
|
44
|
+
if (!hasPermission()) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
17
47
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
48
|
+
val request = ActivityTransitionRequest(transitions)
|
|
49
|
+
val intent = Intent(context, ActivityTransitionReceiver::class.java)
|
|
50
|
+
intent.action = "com.rngeoactivitykit.ACTION_PROCESS_ACTIVITY_TRANSITIONS"
|
|
21
51
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
53
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
54
|
+
} else {
|
|
55
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
56
|
+
}
|
|
26
57
|
|
|
27
|
-
|
|
28
|
-
private var potentialState: String = "STATIONARY"
|
|
29
|
-
private var consecutiveCount = 0
|
|
30
|
-
private var isStarted = false
|
|
58
|
+
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags)
|
|
31
59
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
isStarted = true
|
|
60
|
+
activityClient.requestActivityTransitionUpdates(request, pendingIntent!!)
|
|
61
|
+
.addOnSuccessListener {
|
|
62
|
+
// Success
|
|
63
|
+
}
|
|
64
|
+
.addOnFailureListener { e ->
|
|
65
|
+
e.printStackTrace()
|
|
66
|
+
}
|
|
40
67
|
|
|
41
|
-
sensorManager.registerListener(this, accelerometer, samplingPeriodUs)
|
|
42
68
|
return true
|
|
43
69
|
}
|
|
44
70
|
|
|
45
71
|
fun stop() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fun setUpdateInterval(ms: Int) {
|
|
51
|
-
samplingPeriodUs = ms * 1000
|
|
52
|
-
if (isStarted) {
|
|
53
|
-
stop()
|
|
54
|
-
sensorManager.registerListener(this, accelerometer, samplingPeriodUs)
|
|
55
|
-
isStarted = true
|
|
72
|
+
pendingIntent?.let {
|
|
73
|
+
activityClient.removeActivityTransitionUpdates(it)
|
|
74
|
+
pendingIntent = null
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
|
|
59
|
-
fun
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
|
|
68
|
-
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]
|
|
69
|
-
|
|
70
|
-
// Remove Gravity
|
|
71
|
-
linearAcceleration[0] = event.values[0] - gravity[0]
|
|
72
|
-
linearAcceleration[1] = event.values[1] - gravity[1]
|
|
73
|
-
linearAcceleration[2] = event.values[2] - gravity[2]
|
|
74
|
-
|
|
75
|
-
val magnitude = sqrt(
|
|
76
|
-
(linearAcceleration[0] * linearAcceleration[0] +
|
|
77
|
-
linearAcceleration[1] * linearAcceleration[1] +
|
|
78
|
-
linearAcceleration[2] * linearAcceleration[2]).toDouble()
|
|
79
|
-
).toFloat()
|
|
80
|
-
|
|
81
|
-
val newState = if (magnitude > motionThreshold) "MOVING" else "STATIONARY"
|
|
82
|
-
|
|
83
|
-
if (newState == potentialState) {
|
|
84
|
-
consecutiveCount++
|
|
85
|
-
} else {
|
|
86
|
-
potentialState = newState
|
|
87
|
-
consecutiveCount = 1
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
var stabilityMet = false
|
|
91
|
-
if (potentialState == "MOVING" && consecutiveCount >= startStabilityThreshold) {
|
|
92
|
-
stabilityMet = true
|
|
93
|
-
} else if (potentialState == "STATIONARY" && consecutiveCount >= stopStabilityThreshold) {
|
|
94
|
-
stabilityMet = true
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (stabilityMet && potentialState != currentState) {
|
|
98
|
-
currentState = potentialState
|
|
99
|
-
|
|
100
|
-
// Notify Listener (SensorModule)
|
|
101
|
-
onStateChange(currentState)
|
|
102
|
-
|
|
103
|
-
// Emit to JS
|
|
104
|
-
val params = Arguments.createMap()
|
|
105
|
-
params.putString("state", currentState)
|
|
106
|
-
if (context.hasActiveCatalystInstance()) {
|
|
107
|
-
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
108
|
-
.emit("onMotionStateChanged", params)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
78
|
+
private fun hasPermission(): Boolean {
|
|
79
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
80
|
+
ContextCompat.checkSelfPermission(
|
|
81
|
+
context,
|
|
82
|
+
Manifest.permission.ACTIVITY_RECOGNITION
|
|
83
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
84
|
+
} else {
|
|
85
|
+
true // Not required at runtime below Android 10
|
|
111
86
|
}
|
|
112
87
|
}
|
|
113
|
-
|
|
114
|
-
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
|
|
115
88
|
}
|
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
package com.rngeoactivitykit
|
|
2
2
|
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
3
5
|
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.location.LocationManager
|
|
4
8
|
import android.os.Build
|
|
5
9
|
import com.facebook.react.bridge.*
|
|
6
|
-
import com.
|
|
10
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
7
11
|
|
|
8
12
|
class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
9
13
|
|
|
10
14
|
private val notificationHelper = NotificationHelper(reactContext)
|
|
11
15
|
private val locationHelper = LocationHelper(reactContext)
|
|
12
16
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// NEW: MotionDetector no longer needs a callback because
|
|
18
|
+
// the ActivityTransitionReceiver handles the events.
|
|
19
|
+
private val motionDetector = MotionDetector(reactContext)
|
|
20
|
+
|
|
21
|
+
// --- GPS STATUS RECEIVER (Detects if user turns off Location Toggle) ---
|
|
22
|
+
private val gpsStatusReceiver = object : BroadcastReceiver() {
|
|
23
|
+
override fun onReceive(context: Context?, intent: Intent?) {
|
|
24
|
+
if (intent?.action == LocationManager.PROVIDERS_CHANGED_ACTION) {
|
|
25
|
+
try {
|
|
26
|
+
val locationManager = context?.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
|
27
|
+
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
|
28
|
+
|
|
29
|
+
val params = Arguments.createMap()
|
|
30
|
+
params.putBoolean("enabled", isGpsEnabled)
|
|
31
|
+
|
|
32
|
+
sendEvent("onGpsStatusChanged", params)
|
|
33
|
+
} catch (e: Exception) {
|
|
34
|
+
e.printStackTrace()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
16
38
|
}
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
override fun getName(): String {
|
|
41
|
+
return "RNSensorModule"
|
|
42
|
+
}
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
locationHelper.updateLocationRequest(Priority.PRIORITY_HIGH_ACCURACY, locationInterval)
|
|
27
|
-
locationHelper.startLocationUpdates()
|
|
28
|
-
} else {
|
|
29
|
-
// Low Power (Cell/Wifi) & Slow Updates
|
|
30
|
-
locationHelper.updateLocationRequest(Priority.PRIORITY_BALANCED_POWER_ACCURACY, 180000)
|
|
31
|
-
locationHelper.startLocationUpdates()
|
|
32
|
-
}
|
|
44
|
+
init {
|
|
45
|
+
// Register GPS Status Receiver
|
|
46
|
+
val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
|
|
47
|
+
reactContext.registerReceiver(gpsStatusReceiver, filter)
|
|
33
48
|
}
|
|
34
49
|
|
|
35
|
-
// ---
|
|
50
|
+
// --- SERVICE CONTROL ---
|
|
51
|
+
|
|
36
52
|
@ReactMethod
|
|
37
53
|
fun startForegroundService(title: String, body: String, promise: Promise) {
|
|
38
54
|
try {
|
|
@@ -48,7 +64,7 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
48
64
|
}
|
|
49
65
|
promise.resolve(true)
|
|
50
66
|
} catch (e: Exception) {
|
|
51
|
-
promise.reject("
|
|
67
|
+
promise.reject("START_SERVICE_FAILED", e.message)
|
|
52
68
|
}
|
|
53
69
|
}
|
|
54
70
|
|
|
@@ -57,87 +73,82 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
57
73
|
try {
|
|
58
74
|
val intent = Intent(reactApplicationContext, TrackingService::class.java)
|
|
59
75
|
intent.action = TrackingService.ACTION_STOP
|
|
60
|
-
reactApplicationContext.startService(intent)
|
|
76
|
+
reactApplicationContext.startService(intent) // Triggers stop logic in Service
|
|
61
77
|
promise.resolve(true)
|
|
62
78
|
} catch (e: Exception) {
|
|
63
|
-
promise.reject("
|
|
79
|
+
promise.reject("STOP_SERVICE_FAILED", e.message)
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
@ReactMethod
|
|
68
84
|
fun updateServiceNotification(title: String, body: String, promise: Promise) {
|
|
69
85
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
val intent = Intent(reactApplicationContext, TrackingService::class.java)
|
|
87
|
+
intent.action = TrackingService.ACTION_UPDATE
|
|
88
|
+
intent.putExtra("title", title)
|
|
89
|
+
intent.putExtra("body", body)
|
|
90
|
+
|
|
91
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
92
|
+
reactApplicationContext.startForegroundService(intent)
|
|
77
93
|
} else {
|
|
78
|
-
|
|
94
|
+
reactApplicationContext.startService(intent)
|
|
79
95
|
}
|
|
96
|
+
promise.resolve(true)
|
|
80
97
|
} catch (e: Exception) {
|
|
81
98
|
promise.reject("UPDATE_FAILED", e.message)
|
|
82
99
|
}
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
// ---
|
|
102
|
+
// --- MOTION DETECTOR (Updated for Activity Recognition) ---
|
|
103
|
+
|
|
86
104
|
@ReactMethod
|
|
87
105
|
fun startMotionDetector(threshold: Double, promise: Promise) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
// NOTE: 'threshold' is ignored now because Activity Recognition
|
|
107
|
+
// handles sensitivity automatically using Machine Learning.
|
|
108
|
+
try {
|
|
109
|
+
val success = motionDetector.start()
|
|
110
|
+
if (success) {
|
|
111
|
+
promise.resolve(true)
|
|
112
|
+
} else {
|
|
113
|
+
// This usually means Permissions are missing
|
|
114
|
+
promise.reject("PERMISSION_DENIED", "ACTIVITY_RECOGNITION permission is required")
|
|
115
|
+
}
|
|
116
|
+
} catch (e: Exception) {
|
|
117
|
+
promise.reject("START_MOTION_FAILED", e.message)
|
|
92
118
|
}
|
|
93
|
-
|
|
94
|
-
// Start Location immediately (Balanced Mode)
|
|
95
|
-
locationHelper.updateLocationRequest(Priority.PRIORITY_BALANCED_POWER_ACCURACY, 180000)
|
|
96
|
-
locationHelper.startLocationUpdates()
|
|
97
|
-
|
|
98
|
-
promise.resolve(true)
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
@ReactMethod
|
|
102
122
|
fun stopMotionDetector(promise: Promise) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
fun setLocationUpdateInterval(interval: Double, promise: Promise) {
|
|
110
|
-
locationInterval = interval.toLong()
|
|
111
|
-
promise.resolve(true)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
@ReactMethod
|
|
115
|
-
fun setStabilityThresholds(startThreshold: Int, stopThreshold: Int, promise: Promise) {
|
|
116
|
-
motionDetector.startStabilityThreshold = startThreshold
|
|
117
|
-
motionDetector.stopStabilityThreshold = stopThreshold
|
|
118
|
-
promise.resolve(true)
|
|
123
|
+
try {
|
|
124
|
+
motionDetector.stop()
|
|
125
|
+
promise.resolve(true)
|
|
126
|
+
} catch (e: Exception) {
|
|
127
|
+
promise.reject("STOP_MOTION_FAILED", e.message)
|
|
128
|
+
}
|
|
119
129
|
}
|
|
120
130
|
|
|
121
|
-
|
|
122
|
-
fun setUpdateInterval(ms: Int, promise: Promise) {
|
|
123
|
-
motionDetector.setUpdateInterval(ms)
|
|
124
|
-
promise.resolve(true)
|
|
125
|
-
}
|
|
131
|
+
// --- LOCATION CONTROL ---
|
|
126
132
|
|
|
127
133
|
@ReactMethod
|
|
128
|
-
fun
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
fun setLocationUpdateInterval(intervalMs: Int) {
|
|
135
|
+
try {
|
|
136
|
+
locationHelper.setLocationUpdateInterval(intervalMs.toLong())
|
|
137
|
+
} catch (e: Exception) {
|
|
138
|
+
e.printStackTrace()
|
|
139
|
+
}
|
|
133
140
|
}
|
|
141
|
+
|
|
142
|
+
// --- ALERTS & NOTIFICATIONS ---
|
|
134
143
|
|
|
135
144
|
@ReactMethod
|
|
136
145
|
fun fireGeofenceAlert(type: String, userName: String, promise: Promise) {
|
|
137
146
|
try {
|
|
138
147
|
notificationHelper.fireGeofenceAlert(type, userName)
|
|
139
148
|
promise.resolve(true)
|
|
140
|
-
} catch (e: Exception) {
|
|
149
|
+
} catch (e: Exception) {
|
|
150
|
+
promise.reject("NOTIFY_FAILED", e.message)
|
|
151
|
+
}
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
@ReactMethod
|
|
@@ -145,7 +156,9 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
145
156
|
try {
|
|
146
157
|
notificationHelper.fireGenericAlert(title, body, id)
|
|
147
158
|
promise.resolve(true)
|
|
148
|
-
} catch (e: Exception) {
|
|
159
|
+
} catch (e: Exception) {
|
|
160
|
+
promise.reject("NOTIFY_FAILED", e.message)
|
|
161
|
+
}
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
@ReactMethod
|
|
@@ -153,21 +166,65 @@ class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJava
|
|
|
153
166
|
try {
|
|
154
167
|
notificationHelper.cancelGenericAlert(id)
|
|
155
168
|
promise.resolve(true)
|
|
156
|
-
} catch (e: Exception) {
|
|
169
|
+
} catch (e: Exception) {
|
|
170
|
+
promise.reject("CANCEL_FAILED", e.message)
|
|
171
|
+
}
|
|
157
172
|
}
|
|
158
173
|
|
|
159
|
-
@ReactMethod
|
|
160
|
-
|
|
174
|
+
@ReactMethod
|
|
175
|
+
fun registerGpsListener(promise: Promise) {
|
|
176
|
+
// Already registered in init, just checking permission state
|
|
177
|
+
try {
|
|
178
|
+
val locationManager = reactApplicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
|
179
|
+
val isEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
|
180
|
+
promise.resolve(isEnabled)
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
promise.reject("GPS_CHECK_FAILED", e.message)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- EVENT EMITTER BOILERPLATE ---
|
|
187
|
+
|
|
188
|
+
@ReactMethod
|
|
189
|
+
fun addListener(eventName: String) {
|
|
190
|
+
// Required for NativeEventEmitter
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@ReactMethod
|
|
194
|
+
fun removeListeners(count: Int) {
|
|
195
|
+
// Required for NativeEventEmitter
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private fun sendEvent(eventName: String, params: Any?) {
|
|
199
|
+
try {
|
|
200
|
+
if (reactApplicationContext.hasActiveCatalystInstance()) {
|
|
201
|
+
reactApplicationContext
|
|
202
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
203
|
+
.emit(eventName, params)
|
|
204
|
+
}
|
|
205
|
+
} catch (e: Exception) {
|
|
206
|
+
e.printStackTrace()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// --- CLEANUP ---
|
|
161
211
|
|
|
162
|
-
// Cleanup
|
|
163
212
|
override fun onCatalystInstanceDestroy() {
|
|
164
213
|
super.onCatalystInstanceDestroy()
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
214
|
+
try {
|
|
215
|
+
// 1. Unregister Receivers
|
|
216
|
+
reactApplicationContext.unregisterReceiver(gpsStatusReceiver)
|
|
217
|
+
|
|
218
|
+
// 2. Stop Sensors
|
|
219
|
+
motionDetector.stop()
|
|
220
|
+
locationHelper.stopLocationUpdates()
|
|
221
|
+
|
|
222
|
+
// 3. Stop Service (Optional: Depending on whether you want it to persist after app kill)
|
|
223
|
+
// Ideally, we DO NOT stop the service here if we want it running in background.
|
|
224
|
+
// But we do clean up listeners.
|
|
225
|
+
|
|
226
|
+
} catch (e: Exception) {
|
|
227
|
+
e.printStackTrace()
|
|
228
|
+
}
|
|
172
229
|
}
|
|
173
230
|
}
|
|
@@ -11,6 +11,7 @@ import android.content.pm.ServiceInfo
|
|
|
11
11
|
import android.os.Build
|
|
12
12
|
import android.os.IBinder
|
|
13
13
|
import android.os.PowerManager
|
|
14
|
+
import android.util.Log
|
|
14
15
|
import androidx.core.app.NotificationCompat
|
|
15
16
|
|
|
16
17
|
class TrackingService : Service() {
|
|
@@ -19,7 +20,6 @@ class TrackingService : Service() {
|
|
|
19
20
|
var instance: TrackingService? = null
|
|
20
21
|
const val NOTIFICATION_ID = 9991
|
|
21
22
|
const val CHANNEL_ID = "geo_activity_kit_channel"
|
|
22
|
-
|
|
23
23
|
const val ACTION_START = "ACTION_START"
|
|
24
24
|
const val ACTION_STOP = "ACTION_STOP"
|
|
25
25
|
const val ACTION_UPDATE = "ACTION_UPDATE"
|
|
@@ -32,15 +32,16 @@ class TrackingService : Service() {
|
|
|
32
32
|
override fun onCreate() {
|
|
33
33
|
super.onCreate()
|
|
34
34
|
instance = this
|
|
35
|
+
Log.d("TrackingService", "✅ Service Created")
|
|
35
36
|
createNotificationChannel()
|
|
36
37
|
|
|
37
|
-
// Acquire WakeLock to keep CPU running
|
|
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?.acquire(
|
|
41
|
+
wakeLock?.acquire()
|
|
42
|
+
Log.d("TrackingService", "🔒 WakeLock Acquired (Permanent)")
|
|
42
43
|
} catch (e: Exception) {
|
|
43
|
-
e.
|
|
44
|
+
Log.e("TrackingService", "❌ Failed to acquire WakeLock: ${e.message}")
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -49,11 +50,13 @@ class TrackingService : Service() {
|
|
|
49
50
|
|
|
50
51
|
when (intent.action) {
|
|
51
52
|
ACTION_START -> {
|
|
53
|
+
Log.d("TrackingService", "➡️ Action: START")
|
|
52
54
|
val title = intent.getStringExtra("title") ?: "Location Active"
|
|
53
55
|
val body = intent.getStringExtra("body") ?: "Monitoring in background..."
|
|
54
56
|
startForegroundService(title, body)
|
|
55
57
|
}
|
|
56
58
|
ACTION_STOP -> {
|
|
59
|
+
Log.d("TrackingService", "⏹️ Action: STOP")
|
|
57
60
|
stopForegroundService()
|
|
58
61
|
}
|
|
59
62
|
ACTION_UPDATE -> {
|
|
@@ -67,11 +70,14 @@ class TrackingService : Service() {
|
|
|
67
70
|
|
|
68
71
|
private fun startForegroundService(title: String, body: String) {
|
|
69
72
|
val notification = buildNotification(title, body)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
try {
|
|
74
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
75
|
+
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
|
|
76
|
+
} else {
|
|
77
|
+
startForeground(NOTIFICATION_ID, notification)
|
|
78
|
+
}
|
|
79
|
+
} catch (e: Exception) {
|
|
80
|
+
Log.e("TrackingService", "Error starting foreground: ${e.message}")
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
|
|
@@ -127,6 +133,7 @@ class TrackingService : Service() {
|
|
|
127
133
|
|
|
128
134
|
override fun onDestroy() {
|
|
129
135
|
super.onDestroy()
|
|
136
|
+
Log.d("TrackingService", "❌ Service Destroyed")
|
|
130
137
|
instance = null
|
|
131
138
|
if (wakeLock?.isHeld == true) {
|
|
132
139
|
try { wakeLock?.release() } catch (_: Exception) {}
|
package/lib/module/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { NativeModules, NativeEventEmitter
|
|
4
|
-
const LINKING_ERROR = `The package 'react-native-geo-activity-kit' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
-
ios: "- You have run 'pod install'\n",
|
|
6
|
-
default: ''
|
|
7
|
-
}) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
3
|
+
import { NativeModules, NativeEventEmitter } from 'react-native';
|
|
4
|
+
const LINKING_ERROR = `The package 'react-native-geo-activity-kit' doesn't seem to be linked. Make sure: \n\n` + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
8
5
|
const RNSensorModule = NativeModules.RNSensorModule ? NativeModules.RNSensorModule : new Proxy({}, {
|
|
9
6
|
get() {
|
|
10
7
|
throw new Error(LINKING_ERROR);
|
|
@@ -12,23 +9,43 @@ const RNSensorModule = NativeModules.RNSensorModule ? NativeModules.RNSensorModu
|
|
|
12
9
|
});
|
|
13
10
|
const emitter = new NativeEventEmitter(RNSensorModule);
|
|
14
11
|
export default {
|
|
15
|
-
//
|
|
12
|
+
// --- SERVICE CONTROL ---
|
|
16
13
|
startForegroundService: (title, body) => RNSensorModule.startForegroundService(title, body),
|
|
17
14
|
stopForegroundService: () => RNSensorModule.stopForegroundService(),
|
|
18
15
|
updateServiceNotification: (title, body) => RNSensorModule.updateServiceNotification(title, body),
|
|
19
|
-
//
|
|
16
|
+
// --- MOTION DETECTION (ACTIVITY RECOGNITION) ---
|
|
17
|
+
// Note: Threshold is ignored by the new native engine, but kept for compatibility
|
|
20
18
|
startMotionDetector: (threshold = 0.8) => RNSensorModule.startMotionDetector(threshold),
|
|
21
19
|
stopMotionDetector: () => RNSensorModule.stopMotionDetector(),
|
|
22
|
-
|
|
20
|
+
// --- LOCATION CONTROL ---
|
|
23
21
|
setLocationUpdateInterval: (ms = 90000) => RNSensorModule.setLocationUpdateInterval(ms),
|
|
24
|
-
|
|
25
|
-
// Alerts
|
|
22
|
+
// --- ALERTS ---
|
|
26
23
|
fireGeofenceAlert: (type, userName) => RNSensorModule.fireGeofenceAlert(type, userName),
|
|
27
24
|
fireGenericAlert: (title, body, id) => RNSensorModule.fireGenericAlert(title, body, id),
|
|
28
25
|
cancelGenericAlert: id => RNSensorModule.cancelGenericAlert(id),
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// --- GPS STATUS ---
|
|
27
|
+
registerGpsListener: () => RNSensorModule.registerGpsListener(),
|
|
28
|
+
addGpsStatusListener: cb => emitter.addListener('onGpsStatusChanged', event => cb(event)),
|
|
29
|
+
// --- EVENT LISTENERS ---
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns:
|
|
33
|
+
* {
|
|
34
|
+
* activity: "WALKING" | "STILL" | "IN_VEHICLE" ...,
|
|
35
|
+
* isMoving: boolean,
|
|
36
|
+
* state: "MOVING" | "STATIONARY" (Legacy support)
|
|
37
|
+
* }
|
|
38
|
+
*/
|
|
31
39
|
addMotionListener: cb => emitter.addListener('onMotionStateChanged', cb),
|
|
40
|
+
/**
|
|
41
|
+
* Returns:
|
|
42
|
+
* {
|
|
43
|
+
* latitude: number,
|
|
44
|
+
* longitude: number,
|
|
45
|
+
* accuracy: number,
|
|
46
|
+
* is_mock: boolean (NEW: Detects Fake GPS)
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
32
49
|
addLocationLogListener: cb => emitter.addListener('onLocationLog', cb),
|
|
33
50
|
addLocationErrorListener: cb => emitter.addListener('onLocationError', cb)
|
|
34
51
|
};
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","NativeEventEmitter","
|
|
1
|
+
{"version":3,"names":["NativeModules","NativeEventEmitter","LINKING_ERROR","RNSensorModule","Proxy","get","Error","emitter","startForegroundService","title","body","stopForegroundService","updateServiceNotification","startMotionDetector","threshold","stopMotionDetector","setLocationUpdateInterval","ms","fireGeofenceAlert","type","userName","fireGenericAlert","id","cancelGenericAlert","registerGpsListener","addGpsStatusListener","cb","addListener","event","addMotionListener","addLocationLogListener","addLocationErrorListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,kBAAkB,QAAQ,cAAc;AAEhE,MAAMC,aAAa,GACjB,wFAAwF,GACxF,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,cAAc,GAAGH,aAAa,CAACG,cAAc,GAC/CH,aAAa,CAACG,cAAc,GAC5B,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACJ,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEL,MAAMK,OAAO,GAAG,IAAIN,kBAAkB,CAACE,cAAc,CAAC;AAEtD,eAAe;EACb;EACAK,sBAAsB,EAAEA,CAACC,KAAa,EAAEC,IAAY,KAClDP,cAAc,CAACK,sBAAsB,CAACC,KAAK,EAAEC,IAAI,CAAC;EAEpDC,qBAAqB,EAAEA,CAAA,KACrBR,cAAc,CAACQ,qBAAqB,CAAC,CAAC;EAExCC,yBAAyB,EAAEA,CAACH,KAAa,EAAEC,IAAY,KACrDP,cAAc,CAACS,yBAAyB,CAACH,KAAK,EAAEC,IAAI,CAAC;EAEvD;EACA;EACAG,mBAAmB,EAAEA,CAACC,SAAiB,GAAG,GAAG,KAC3CX,cAAc,CAACU,mBAAmB,CAACC,SAAS,CAAC;EAE/CC,kBAAkB,EAAEA,CAAA,KAClBZ,cAAc,CAACY,kBAAkB,CAAC,CAAC;EAErC;EACAC,yBAAyB,EAAEA,CAACC,EAAU,GAAG,KAAK,KAC5Cd,cAAc,CAACa,yBAAyB,CAACC,EAAE,CAAC;EAE9C;EACAC,iBAAiB,EAAEA,CAACC,IAAY,EAAEC,QAAgB,KAChDjB,cAAc,CAACe,iBAAiB,CAACC,IAAI,EAAEC,QAAQ,CAAC;EAElDC,gBAAgB,EAAEA,CAACZ,KAAa,EAAEC,IAAY,EAAEY,EAAU,KACxDnB,cAAc,CAACkB,gBAAgB,CAACZ,KAAK,EAAEC,IAAI,EAAEY,EAAE,CAAC;EAElDC,kBAAkB,EAAGD,EAAU,IAC7BnB,cAAc,CAACoB,kBAAkB,CAACD,EAAE,CAAC;EAEvC;EACAE,mBAAmB,EAAEA,CAAA,KACnBrB,cAAc,CAACqB,mBAAmB,CAAC,CAAC;EAEtCC,oBAAoB,EAAGC,EAAyC,IAC9DnB,OAAO,CAACoB,WAAW,CAAC,oBAAoB,EAAGC,KAAU,IAAKF,EAAE,CAACE,KAAK,CAAC,CAAC;EAEtE;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,iBAAiB,EAAGH,EAAwB,IAC1CnB,OAAO,CAACoB,WAAW,CAAC,sBAAsB,EAAED,EAAE,CAAC;EAEjD;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEI,sBAAsB,EAAGJ,EAAwB,IAC/CnB,OAAO,CAACoB,WAAW,CAAC,eAAe,EAAED,EAAE,CAAC;EAE1CK,wBAAwB,EAAGL,EAAwB,IACjDnB,OAAO,CAACoB,WAAW,CAAC,iBAAiB,EAAED,EAAE;AAC7C,CAAC","ignoreList":[]}
|
|
@@ -2,16 +2,34 @@ declare const _default: {
|
|
|
2
2
|
startForegroundService: (title: string, body: string) => Promise<boolean>;
|
|
3
3
|
stopForegroundService: () => Promise<boolean>;
|
|
4
4
|
updateServiceNotification: (title: string, body: string) => Promise<boolean>;
|
|
5
|
-
startMotionDetector: (threshold?: number) =>
|
|
6
|
-
stopMotionDetector: () =>
|
|
7
|
-
setUpdateInterval: (ms?: number) => any;
|
|
5
|
+
startMotionDetector: (threshold?: number) => Promise<boolean>;
|
|
6
|
+
stopMotionDetector: () => Promise<boolean>;
|
|
8
7
|
setLocationUpdateInterval: (ms?: number) => any;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
fireGeofenceAlert: (type: string, userName: string) => Promise<boolean>;
|
|
9
|
+
fireGenericAlert: (title: string, body: string, id: number) => Promise<boolean>;
|
|
10
|
+
cancelGenericAlert: (id: number) => Promise<boolean>;
|
|
11
|
+
registerGpsListener: () => Promise<boolean>;
|
|
12
|
+
addGpsStatusListener: (cb: (event: {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
}) => void) => import("react-native").EventSubscription;
|
|
15
|
+
/**
|
|
16
|
+
* Returns:
|
|
17
|
+
* {
|
|
18
|
+
* activity: "WALKING" | "STILL" | "IN_VEHICLE" ...,
|
|
19
|
+
* isMoving: boolean,
|
|
20
|
+
* state: "MOVING" | "STATIONARY" (Legacy support)
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
14
23
|
addMotionListener: (cb: (event: any) => void) => import("react-native").EventSubscription;
|
|
24
|
+
/**
|
|
25
|
+
* Returns:
|
|
26
|
+
* {
|
|
27
|
+
* latitude: number,
|
|
28
|
+
* longitude: number,
|
|
29
|
+
* accuracy: number,
|
|
30
|
+
* is_mock: boolean (NEW: Detects Fake GPS)
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
15
33
|
addLocationLogListener: (cb: (event: any) => void) => import("react-native").EventSubscription;
|
|
16
34
|
addLocationErrorListener: (cb: (event: any) => void) => import("react-native").EventSubscription;
|
|
17
35
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":";oCAsBkC,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;iCAG5C,OAAO,CAAC,OAAO,CAAC;uCAGR,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;sCAKzC,MAAM,KAAS,OAAO,CAAC,OAAO,CAAC;8BAGxC,OAAO,CAAC,OAAO,CAAC;qCAIR,MAAM;8BAIZ,MAAM,YAAY,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;8BAG3C,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;6BAGpD,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;+BAIzB,OAAO,CAAC,OAAO,CAAC;+BAGd,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI;IAKhE;;;;;;;OAOG;4BACqB,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;IAG5C;;;;;;;;OAQG;iCAC0B,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;mCAGlB,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;;AAjErD,wBAmEE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-geo-activity-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
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",
|
|
@@ -170,4 +170,4 @@
|
|
|
170
170
|
],
|
|
171
171
|
"version": "0.55.1"
|
|
172
172
|
}
|
|
173
|
-
}
|
|
173
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { NativeModules, NativeEventEmitter
|
|
1
|
+
import { NativeModules, NativeEventEmitter } from 'react-native';
|
|
2
2
|
|
|
3
3
|
const LINKING_ERROR =
|
|
4
4
|
`The package 'react-native-geo-activity-kit' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
-
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
5
|
'- You rebuilt the app after installing the package\n' +
|
|
7
6
|
'- You are not using Expo Go\n';
|
|
8
7
|
|
|
@@ -20,7 +19,7 @@ const RNSensorModule = NativeModules.RNSensorModule
|
|
|
20
19
|
const emitter = new NativeEventEmitter(RNSensorModule);
|
|
21
20
|
|
|
22
21
|
export default {
|
|
23
|
-
//
|
|
22
|
+
// --- SERVICE CONTROL ---
|
|
24
23
|
startForegroundService: (title: string, body: string): Promise<boolean> =>
|
|
25
24
|
RNSensorModule.startForegroundService(title, body),
|
|
26
25
|
|
|
@@ -30,35 +29,57 @@ export default {
|
|
|
30
29
|
updateServiceNotification: (title: string, body: string): Promise<boolean> =>
|
|
31
30
|
RNSensorModule.updateServiceNotification(title, body),
|
|
32
31
|
|
|
33
|
-
//
|
|
34
|
-
|
|
32
|
+
// --- MOTION DETECTION (ACTIVITY RECOGNITION) ---
|
|
33
|
+
// Note: Threshold is ignored by the new native engine, but kept for compatibility
|
|
34
|
+
startMotionDetector: (threshold: number = 0.8): Promise<boolean> =>
|
|
35
35
|
RNSensorModule.startMotionDetector(threshold),
|
|
36
36
|
|
|
37
|
-
stopMotionDetector: () =>
|
|
38
|
-
|
|
39
|
-
setUpdateInterval: (ms: number = 100) => RNSensorModule.setUpdateInterval(ms),
|
|
37
|
+
stopMotionDetector: (): Promise<boolean> =>
|
|
38
|
+
RNSensorModule.stopMotionDetector(),
|
|
40
39
|
|
|
40
|
+
// --- LOCATION CONTROL ---
|
|
41
41
|
setLocationUpdateInterval: (ms: number = 90000) =>
|
|
42
42
|
RNSensorModule.setLocationUpdateInterval(ms),
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Alerts
|
|
48
|
-
fireGeofenceAlert: (type: string, userName: string) =>
|
|
44
|
+
// --- ALERTS ---
|
|
45
|
+
fireGeofenceAlert: (type: string, userName: string): Promise<boolean> =>
|
|
49
46
|
RNSensorModule.fireGeofenceAlert(type, userName),
|
|
50
47
|
|
|
51
|
-
fireGenericAlert: (title: string, body: string, id: number) =>
|
|
48
|
+
fireGenericAlert: (title: string, body: string, id: number): Promise<boolean> =>
|
|
52
49
|
RNSensorModule.fireGenericAlert(title, body, id),
|
|
53
50
|
|
|
54
|
-
cancelGenericAlert: (id: number) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
cancelGenericAlert: (id: number): Promise<boolean> =>
|
|
52
|
+
RNSensorModule.cancelGenericAlert(id),
|
|
53
|
+
|
|
54
|
+
// --- GPS STATUS ---
|
|
55
|
+
registerGpsListener: (): Promise<boolean> =>
|
|
56
|
+
RNSensorModule.registerGpsListener(),
|
|
57
|
+
|
|
58
|
+
addGpsStatusListener: (cb: (event: { enabled: boolean }) => void) =>
|
|
59
|
+
emitter.addListener('onGpsStatusChanged', (event: any) => cb(event)),
|
|
60
|
+
|
|
61
|
+
// --- EVENT LISTENERS ---
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns:
|
|
65
|
+
* {
|
|
66
|
+
* activity: "WALKING" | "STILL" | "IN_VEHICLE" ...,
|
|
67
|
+
* isMoving: boolean,
|
|
68
|
+
* state: "MOVING" | "STATIONARY" (Legacy support)
|
|
69
|
+
* }
|
|
70
|
+
*/
|
|
59
71
|
addMotionListener: (cb: (event: any) => void) =>
|
|
60
72
|
emitter.addListener('onMotionStateChanged', cb),
|
|
61
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Returns:
|
|
76
|
+
* {
|
|
77
|
+
* latitude: number,
|
|
78
|
+
* longitude: number,
|
|
79
|
+
* accuracy: number,
|
|
80
|
+
* is_mock: boolean (NEW: Detects Fake GPS)
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
62
83
|
addLocationLogListener: (cb: (event: any) => void) =>
|
|
63
84
|
emitter.addListener('onLocationLog', cb),
|
|
64
85
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeGeoActivityKit.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAMpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,gBAAgB,CAAC","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NativeGeoActivityKit.d.ts","sourceRoot":"","sources":["../../../src/NativeGeoActivityKit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxC;;AAED,wBAAwE"}
|