react-native-geo-activity-kit 1.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/GeoActivityKit.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +15 -0
- package/android/build.gradle +78 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/rngeoactivitykit/SensorModule.kt +410 -0
- package/android/src/main/java/com/rngeoactivitykit/SensorPackage.kt +16 -0
- package/ios/GeoActivityKit.h +5 -0
- package/ios/GeoActivityKit.mm +21 -0
- package/lib/module/NativeGeoActivityKit.js +5 -0
- package/lib/module/NativeGeoActivityKit.js.map +1 -0
- package/lib/module/index.js +34 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeGeoActivityKit.d.ts +7 -0
- package/lib/typescript/src/NativeGeoActivityKit.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +175 -0
- package/src/NativeGeoActivityKit.ts +7 -0
- package/src/index.tsx +63 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "GeoActivityKit"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/kartikeycsjm/react-native-geo-activity-kit.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kartikey Mishra
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# react-native-geo-activity-kit
|
|
2
|
+
|
|
3
|
+
A battery-efficient background location tracker that uses the accelerometer to stop GPS when the device is stationary. It also includes a built-in notification engine for sticky services and alerts.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
* **🔋 Battery Efficient:** Automatically toggles GPS off when the device stops moving.
|
|
8
|
+
* **🏃 Motion Detection:** Exposes raw motion state ("MOVING" vs "STATIONARY").
|
|
9
|
+
* **🔔 Native Notifications:** Built-in engine for local alerts and foreground services.
|
|
10
|
+
* **📍 Fused Location:** Uses Google's FusedLocationProvider for high accuracy.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install react-native-geo-activity-kit
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['GeoActivityKit_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
apply plugin: "com.android.library"
|
|
20
|
+
apply plugin: "kotlin-android"
|
|
21
|
+
|
|
22
|
+
apply plugin: "com.facebook.react"
|
|
23
|
+
|
|
24
|
+
def getExtOrIntegerDefault(name) {
|
|
25
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["GeoActivityKit_" + name]).toInteger()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
android {
|
|
29
|
+
namespace "com.rngeoactivitykit"
|
|
30
|
+
|
|
31
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
32
|
+
|
|
33
|
+
defaultConfig {
|
|
34
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
35
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
buildFeatures {
|
|
39
|
+
buildConfig true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
buildTypes {
|
|
43
|
+
release {
|
|
44
|
+
minifyEnabled false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
lintOptions {
|
|
49
|
+
disable "GradleCompatible"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
compileOptions {
|
|
53
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
54
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sourceSets {
|
|
58
|
+
main {
|
|
59
|
+
java.srcDirs += [
|
|
60
|
+
"generated/java",
|
|
61
|
+
"generated/jni"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
repositories {
|
|
68
|
+
mavenCentral()
|
|
69
|
+
google()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
73
|
+
|
|
74
|
+
dependencies {
|
|
75
|
+
implementation "com.facebook.react:react-android"
|
|
76
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
77
|
+
implementation "com.google.android.gms:play-services-location:21.3.0"
|
|
78
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
4
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
5
|
+
|
|
6
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
7
|
+
|
|
8
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
9
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
10
|
+
|
|
11
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
12
|
+
|
|
13
|
+
</manifest>
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
package com.rngeoactivitykit
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.PendingIntent
|
|
7
|
+
import android.content.pm.PackageManager
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import androidx.core.app.NotificationCompat
|
|
10
|
+
import androidx.core.content.ContextCompat
|
|
11
|
+
import android.app.Notification
|
|
12
|
+
import android.annotation.SuppressLint
|
|
13
|
+
import android.content.Context
|
|
14
|
+
import android.hardware.Sensor
|
|
15
|
+
import android.hardware.SensorEvent
|
|
16
|
+
import android.hardware.SensorEventListener
|
|
17
|
+
import android.hardware.SensorManager
|
|
18
|
+
import android.os.Looper
|
|
19
|
+
import android.util.Log
|
|
20
|
+
import com.facebook.react.bridge.*
|
|
21
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
22
|
+
import com.google.android.gms.location.*
|
|
23
|
+
import com.google.android.gms.location.LocationCallback
|
|
24
|
+
import com.google.android.gms.location.LocationRequest
|
|
25
|
+
import com.google.android.gms.location.LocationResult
|
|
26
|
+
import java.util.Date
|
|
27
|
+
import java.text.SimpleDateFormat
|
|
28
|
+
import java.util.TimeZone
|
|
29
|
+
import kotlin.math.sqrt
|
|
30
|
+
|
|
31
|
+
class SensorModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), SensorEventListener {
|
|
32
|
+
private val sensorManager: SensorManager = reactContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
|
33
|
+
private var accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
|
34
|
+
|
|
35
|
+
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
|
36
|
+
private var locationCallback: LocationCallback
|
|
37
|
+
private lateinit var locationRequest: LocationRequest
|
|
38
|
+
@Volatile private var locationInterval: Long = 90000
|
|
39
|
+
private var isLocationClientRunning: Boolean = false
|
|
40
|
+
|
|
41
|
+
private val gravity = floatArrayOf(0f, 0f, 0f)
|
|
42
|
+
private val linearAcceleration = floatArrayOf(0f, 0f, 0f)
|
|
43
|
+
private val alpha: Float = 0.8f
|
|
44
|
+
@Volatile private var motionThreshold: Float = 0.8f
|
|
45
|
+
@Volatile private var currentState: String = "STATIONARY"
|
|
46
|
+
private var isMotionDetectorStarted: Boolean = false
|
|
47
|
+
@Volatile private var potentialState: String = "STATIONARY"
|
|
48
|
+
@Volatile private var consecutiveCount = 0
|
|
49
|
+
|
|
50
|
+
@Volatile private var startStabilityThreshold: Int = 20
|
|
51
|
+
@Volatile private var stopStabilityThreshold: Int = 3000
|
|
52
|
+
@Volatile private var samplingPeriodUs: Int = 100_000
|
|
53
|
+
|
|
54
|
+
private val isoFormatter: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").apply {
|
|
55
|
+
timeZone = TimeZone.getTimeZone("UTC")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private val notificationManager: NotificationManager =
|
|
59
|
+
reactContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
60
|
+
private val GEOFENCE_CHANNEL_ID = "geofence-channel-id"
|
|
61
|
+
private val GEOFENCE_OUT_ID = 101
|
|
62
|
+
private val GEOFENCE_IN_ID = 102
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
private val appIcon: Int = reactApplicationContext.applicationInfo.icon.let {
|
|
66
|
+
if (it != 0) it else android.R.drawable.ic_dialog_info
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
init {
|
|
70
|
+
fusedLocationClient = LocationServices.getFusedLocationProviderClient(reactContext)
|
|
71
|
+
|
|
72
|
+
updateLocationRequest()
|
|
73
|
+
locationCallback = object : LocationCallback() {
|
|
74
|
+
override fun onLocationResult(locationResult: LocationResult) {
|
|
75
|
+
locationResult.lastLocation ?: return
|
|
76
|
+
|
|
77
|
+
val location = locationResult.lastLocation!!
|
|
78
|
+
val params = Arguments.createMap()
|
|
79
|
+
params.putDouble("latitude", location.latitude)
|
|
80
|
+
params.putDouble("longitude", location.longitude)
|
|
81
|
+
params.putString("timestamp", isoFormatter.format(Date(location.time)))
|
|
82
|
+
|
|
83
|
+
emitEvent(reactApplicationContext, "onLocationLog", params)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
createNotificationChannel()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private fun createNotificationChannel() {
|
|
91
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
92
|
+
val name = "Geofence Alerts"
|
|
93
|
+
val descriptionText = "Notifications for geofence and work reminders."
|
|
94
|
+
val importance = NotificationManager.IMPORTANCE_HIGH
|
|
95
|
+
val channel = NotificationChannel(GEOFENCE_CHANNEL_ID, name, importance).apply {
|
|
96
|
+
|
|
97
|
+
description = descriptionText
|
|
98
|
+
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
99
|
+
enableVibration(true)
|
|
100
|
+
vibrationPattern = longArrayOf(300, 500)
|
|
101
|
+
}
|
|
102
|
+
notificationManager.createNotificationChannel(channel)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fun updateLocationRequest() {
|
|
107
|
+
locationRequest = LocationRequest.create().apply {
|
|
108
|
+
interval = locationInterval
|
|
109
|
+
fastestInterval = locationInterval
|
|
110
|
+
priority = Priority.PRIORITY_HIGH_ACCURACY
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override fun getName(): String = "RNSensorModule"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
override fun onSensorChanged(event: SensorEvent?) {
|
|
118
|
+
event ?: return
|
|
119
|
+
if (event.sensor.type == Sensor.TYPE_ACCELEROMETER && isMotionDetectorStarted) {
|
|
120
|
+
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
|
|
121
|
+
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
|
|
122
|
+
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]
|
|
123
|
+
linearAcceleration[0] = event.values[0] - gravity[0]
|
|
124
|
+
linearAcceleration[1] = event.values[1] - gravity[1]
|
|
125
|
+
linearAcceleration[2] = event.values[2] - gravity[2]
|
|
126
|
+
val magnitude = sqrt(
|
|
127
|
+
(linearAcceleration[0] * linearAcceleration[0] +
|
|
128
|
+
linearAcceleration[1] * linearAcceleration[1] +
|
|
129
|
+
linearAcceleration[2] * linearAcceleration[2]).toDouble()
|
|
130
|
+
).toFloat()
|
|
131
|
+
val newState = if (magnitude > motionThreshold) "MOVING" else "STATIONARY"
|
|
132
|
+
|
|
133
|
+
if (newState == potentialState) {
|
|
134
|
+
consecutiveCount++
|
|
135
|
+
} else {
|
|
136
|
+
potentialState = newState
|
|
137
|
+
consecutiveCount = 1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var stabilityMet = false
|
|
141
|
+
if (potentialState == "MOVING" && consecutiveCount >= startStabilityThreshold) {
|
|
142
|
+
stabilityMet = true
|
|
143
|
+
} else if (potentialState == "STATIONARY" && consecutiveCount >= stopStabilityThreshold) {
|
|
144
|
+
stabilityMet = true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (stabilityMet && potentialState != currentState) {
|
|
148
|
+
currentState = potentialState
|
|
149
|
+
|
|
150
|
+
if (currentState == "MOVING") {
|
|
151
|
+
startLocationUpdates()
|
|
152
|
+
} else {
|
|
153
|
+
stopLocationUpdates()
|
|
154
|
+
}
|
|
155
|
+
val params = Arguments.createMap()
|
|
156
|
+
params.putString("state", currentState)
|
|
157
|
+
emitEvent(reactApplicationContext, "onMotionStateChanged", params)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private fun hasLocationPermission(): Boolean {
|
|
167
|
+
val finePermission = ContextCompat.checkSelfPermission(
|
|
168
|
+
reactApplicationContext,
|
|
169
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
170
|
+
)
|
|
171
|
+
val coarsePermission = ContextCompat.checkSelfPermission(
|
|
172
|
+
reactApplicationContext,
|
|
173
|
+
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
174
|
+
)
|
|
175
|
+
return finePermission == PackageManager.PERMISSION_GRANTED || coarsePermission == PackageManager.PERMISSION_GRANTED
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@ReactMethod
|
|
180
|
+
fun startMotionDetector(threshold: Double, promise: Promise) {
|
|
181
|
+
if (accelerometer == null) {
|
|
182
|
+
promise.reject("NO_SENSOR", "Accelerometer not available on this device")
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
motionThreshold = threshold.toFloat()
|
|
186
|
+
isMotionDetectorStarted = true
|
|
187
|
+
currentState = "STATIONARY"
|
|
188
|
+
potentialState = "STATIONARY"
|
|
189
|
+
consecutiveCount = 0
|
|
190
|
+
sensorManager.registerListener(this, accelerometer, samplingPeriodUs)
|
|
191
|
+
|
|
192
|
+
promise.resolve(true)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@ReactMethod
|
|
196
|
+
fun stopMotionDetector(promise: Promise) {
|
|
197
|
+
isMotionDetectorStarted = false
|
|
198
|
+
sensorManager.unregisterListener(this, accelerometer)
|
|
199
|
+
stopLocationUpdates()
|
|
200
|
+
|
|
201
|
+
promise.resolve(true)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@ReactMethod
|
|
205
|
+
fun setLocationUpdateInterval(interval: Double, promise: Promise) {
|
|
206
|
+
locationInterval = interval.toLong()
|
|
207
|
+
updateLocationRequest()
|
|
208
|
+
|
|
209
|
+
if (isLocationClientRunning) {
|
|
210
|
+
stopLocationUpdates()
|
|
211
|
+
startLocationUpdates()
|
|
212
|
+
}
|
|
213
|
+
promise.resolve(true)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@ReactMethod
|
|
217
|
+
fun setStabilityThresholds(startThreshold: Int, stopThreshold: Int, promise: Promise) {
|
|
218
|
+
try {
|
|
219
|
+
startStabilityThreshold = startThreshold.coerceAtLeast(1)
|
|
220
|
+
stopStabilityThreshold = stopThreshold.coerceAtLeast(1)
|
|
221
|
+
promise.resolve(true)
|
|
222
|
+
} catch (e: Exception) {
|
|
223
|
+
promise.reject("CONFIG_ERROR", "Failed to set stability thresholds: ${e.message}")
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@ReactMethod
|
|
228
|
+
fun setUpdateInterval(ms: Int, promise: Promise) {
|
|
229
|
+
samplingPeriodUs = ms.coerceAtLeast(100) * 1000
|
|
230
|
+
|
|
231
|
+
if (isMotionDetectorStarted) {
|
|
232
|
+
sensorManager.unregisterListener(this, accelerometer)
|
|
233
|
+
sensorManager.registerListener(this, accelerometer, samplingPeriodUs)
|
|
234
|
+
}
|
|
235
|
+
promise.resolve(true)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@ReactMethod
|
|
239
|
+
fun isAvailable(promise: Promise) {
|
|
240
|
+
val map = Arguments.createMap()
|
|
241
|
+
map.putBoolean("accelerometer", accelerometer != null)
|
|
242
|
+
map.putBoolean("gyroscope", false)
|
|
243
|
+
promise.resolve(map)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@SuppressLint("MissingPermission")
|
|
248
|
+
private fun startLocationUpdates() {
|
|
249
|
+
if (isLocationClientRunning) {
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
if (!hasLocationPermission()) {
|
|
253
|
+
val params = Arguments.createMap()
|
|
254
|
+
params.putString("error", "LOCATION_PERMISSION_DENIED")
|
|
255
|
+
params.putString("message", "Location permission is not granted. Please request permission from the user.")
|
|
256
|
+
emitEvent(reactApplicationContext, "onLocationError", params)
|
|
257
|
+
Log.w("SensorModule", "Location permission denied.")
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
fusedLocationClient.requestLocationUpdates(
|
|
262
|
+
locationRequest,
|
|
263
|
+
locationCallback,
|
|
264
|
+
Looper.getMainLooper()
|
|
265
|
+
)
|
|
266
|
+
isLocationClientRunning = true
|
|
267
|
+
Log.i("SensorModule", "Location updates started.")
|
|
268
|
+
} catch (e: Exception) {
|
|
269
|
+
Log.e("SensorModule", "Failed to start location updates: " + e.message)
|
|
270
|
+
val params = Arguments.createMap()
|
|
271
|
+
params.putString("error", "START_LOCATION_FAILED")
|
|
272
|
+
params.putString("message", "An unexpected error occurred while starting location updates: ${e.message}")
|
|
273
|
+
emitEvent(reactApplicationContext, "onLocationError", params)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private fun stopLocationUpdates() {
|
|
278
|
+
if (!isLocationClientRunning) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
fusedLocationClient.removeLocationUpdates(locationCallback)
|
|
283
|
+
isLocationClientRunning = false
|
|
284
|
+
Log.i("SensorModule", "Location updates stopped.")
|
|
285
|
+
} catch (e: Exception) {
|
|
286
|
+
Log.e("SensorModule", "Failed to stop location updates: " + e.message)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private fun emitEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
|
|
291
|
+
if (reactContext.hasActiveCatalystInstance()) {
|
|
292
|
+
reactContext
|
|
293
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
294
|
+
.emit(eventName, params)
|
|
295
|
+
} else {
|
|
296
|
+
Log.w("SensorModule", "Catalyst instance not active for event $eventName")
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
private fun createPendingIntent(requestCode: Int): PendingIntent? {
|
|
302
|
+
val launchIntent = reactApplicationContext.packageManager.getLaunchIntentForPackage(reactApplicationContext.packageName)
|
|
303
|
+
val pendingIntentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
304
|
+
PendingIntent.FLAG_IMMUTABLE
|
|
305
|
+
} else {
|
|
306
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return launchIntent?.let {
|
|
310
|
+
PendingIntent.getActivity(
|
|
311
|
+
reactApplicationContext,
|
|
312
|
+
requestCode,
|
|
313
|
+
it,
|
|
314
|
+
pendingIntentFlag
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@ReactMethod
|
|
321
|
+
fun fireGeofenceAlert(type: String, userName: String, promise: Promise) {
|
|
322
|
+
try {
|
|
323
|
+
val pendingIntent = createPendingIntent(0)
|
|
324
|
+
|
|
325
|
+
if (type == "OUT") {
|
|
326
|
+
val notification = NotificationCompat.Builder(reactApplicationContext, GEOFENCE_CHANNEL_ID)
|
|
327
|
+
.setSmallIcon(appIcon)
|
|
328
|
+
.setContentTitle("Geofence Alert 🔔")
|
|
329
|
+
.setContentText("$userName, you seem to have moved out of your designated work area.")
|
|
330
|
+
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
331
|
+
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
|
332
|
+
.setContentIntent(pendingIntent)
|
|
333
|
+
.setOngoing(true)
|
|
334
|
+
.setAutoCancel(false)
|
|
335
|
+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
336
|
+
.build()
|
|
337
|
+
|
|
338
|
+
notificationManager.notify(GEOFENCE_OUT_ID, notification)
|
|
339
|
+
|
|
340
|
+
} else if (type == "IN") {
|
|
341
|
+
|
|
342
|
+
notificationManager.cancel(GEOFENCE_OUT_ID)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
val notification = NotificationCompat.Builder(reactApplicationContext, GEOFENCE_CHANNEL_ID)
|
|
346
|
+
.setSmallIcon(appIcon)
|
|
347
|
+
.setContentTitle("You are in again ✅")
|
|
348
|
+
.setContentText("$userName, you have moved back into your designated work area.")
|
|
349
|
+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
350
|
+
.setContentIntent(pendingIntent)
|
|
351
|
+
.setAutoCancel(true)
|
|
352
|
+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
353
|
+
.build()
|
|
354
|
+
|
|
355
|
+
notificationManager.notify(GEOFENCE_IN_ID, notification)
|
|
356
|
+
}
|
|
357
|
+
promise.resolve(true)
|
|
358
|
+
} catch (e: Exception) {
|
|
359
|
+
Log.e("SensorModule", "Failed to fire notification: " + e.message)
|
|
360
|
+
promise.reject("NOTIFY_FAILED", e.message)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@ReactMethod
|
|
365
|
+
fun fireGenericAlert(title: String, body: String, id: Int, promise: Promise) {
|
|
366
|
+
try {
|
|
367
|
+
val pendingIntent = createPendingIntent(id)
|
|
368
|
+
|
|
369
|
+
val notification = NotificationCompat.Builder(reactApplicationContext, GEOFENCE_CHANNEL_ID)
|
|
370
|
+
.setSmallIcon(appIcon)
|
|
371
|
+
.setContentTitle(title)
|
|
372
|
+
.setContentText(body)
|
|
373
|
+
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
374
|
+
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
|
375
|
+
.setContentIntent(pendingIntent)
|
|
376
|
+
.setAutoCancel(true)
|
|
377
|
+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
378
|
+
.build()
|
|
379
|
+
|
|
380
|
+
notificationManager.notify(id, notification)
|
|
381
|
+
|
|
382
|
+
promise.resolve(true)
|
|
383
|
+
} catch (e: Exception) {
|
|
384
|
+
Log.e("SensorModule", "Failed to fire generic notification: " + e.message)
|
|
385
|
+
promise.reject("NOTIFY_FAILED", e.message)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@ReactMethod
|
|
391
|
+
fun cancelGenericAlert(id: Int, promise: Promise) {
|
|
392
|
+
try {
|
|
393
|
+
notificationManager.cancel(id)
|
|
394
|
+
promise.resolve(true)
|
|
395
|
+
} catch (e: Exception) {
|
|
396
|
+
Log.e("SensorModule", "Failed to cancel generic notification: " + e.message)
|
|
397
|
+
promise.reject("CANCEL_FAILED", e.message)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@ReactMethod
|
|
402
|
+
fun addListener(eventName: String) {}
|
|
403
|
+
@ReactMethod
|
|
404
|
+
fun removeListeners(count: Int) {}
|
|
405
|
+
|
|
406
|
+
override fun onCatalystInstanceDestroy() {
|
|
407
|
+
super.onCatalystInstanceDestroy()
|
|
408
|
+
Log.w("SensorModule", "onCatalystInstanceDestroy called, but sensors are being kept alive.")
|
|
409
|
+
}
|
|
410
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.rngeoactivitykit
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class SensorPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(SensorModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#import "GeoActivityKit.h"
|
|
2
|
+
|
|
3
|
+
@implementation GeoActivityKit
|
|
4
|
+
- (NSNumber *)multiply:(double)a b:(double)b {
|
|
5
|
+
NSNumber *result = @(a * b);
|
|
6
|
+
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
11
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
12
|
+
{
|
|
13
|
+
return std::make_shared<facebook::react::NativeGeoActivityKitSpecJSI>(params);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
+ (NSString *)moduleName
|
|
17
|
+
{
|
|
18
|
+
return @"GeoActivityKit";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@end
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
4
|
+
const LINKING_ERROR = `The package 'react-native-geo-activity-kit' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
|
|
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';
|
|
8
|
+
|
|
9
|
+
// The name must match getName() in SensorModule.kt
|
|
10
|
+
const RNSensorModule = NativeModules.RNSensorModule ? NativeModules.RNSensorModule : new Proxy({}, {
|
|
11
|
+
get() {
|
|
12
|
+
throw new Error(LINKING_ERROR);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const emitter = new NativeEventEmitter(RNSensorModule);
|
|
16
|
+
export default {
|
|
17
|
+
startMotionDetector: (threshold = 0.8) => RNSensorModule.startMotionDetector(threshold),
|
|
18
|
+
stopMotionDetector: () => RNSensorModule.stopMotionDetector(),
|
|
19
|
+
setUpdateInterval: (ms = 100) => RNSensorModule.setUpdateInterval(ms),
|
|
20
|
+
setLocationUpdateInterval: (ms = 90000) => RNSensorModule.setLocationUpdateInterval(ms),
|
|
21
|
+
setStabilityThresholds: (startThreshold = 20, stopThreshold = 3000) => RNSensorModule.setStabilityThresholds(startThreshold, stopThreshold),
|
|
22
|
+
// Added types: string for text inputs
|
|
23
|
+
fireGeofenceAlert: (type, userName) => RNSensorModule.fireGeofenceAlert(type, userName),
|
|
24
|
+
// Added types: string for text, number for ID (assuming Android notification ID)
|
|
25
|
+
fireGenericAlert: (title, body, id) => RNSensorModule.fireGenericAlert(title, body, id),
|
|
26
|
+
// Added type: number to match the ID above
|
|
27
|
+
cancelGenericAlert: id => RNSensorModule.cancelGenericAlert(id),
|
|
28
|
+
isAvailable: () => RNSensorModule.isAvailable(),
|
|
29
|
+
// Added type: function that accepts an event (any)
|
|
30
|
+
addMotionListener: cb => emitter.addListener('onMotionStateChanged', cb),
|
|
31
|
+
addLocationLogListener: cb => emitter.addListener('onLocationLog', cb),
|
|
32
|
+
addLocationErrorListener: cb => emitter.addListener('onLocationError', cb)
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NativeModules","NativeEventEmitter","Platform","LINKING_ERROR","select","ios","default","RNSensorModule","Proxy","get","Error","emitter","startMotionDetector","threshold","stopMotionDetector","setUpdateInterval","ms","setLocationUpdateInterval","setStabilityThresholds","startThreshold","stopThreshold","fireGeofenceAlert","type","userName","fireGenericAlert","title","body","id","cancelGenericAlert","isAvailable","addMotionListener","cb","addListener","addLocationLogListener","addLocationErrorListener"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,QAAQ,QAAQ,cAAc;AAE1E,MAAMC,aAAa,GACjB,wFAAwF,GACxFD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;;AAEjC;AACA,MAAMC,cAAc,GAAGP,aAAa,CAACO,cAAc,GAC/CP,aAAa,CAACO,cAAc,GAC5B,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEL,MAAMQ,OAAO,GAAG,IAAIV,kBAAkB,CAACM,cAAc,CAAC;AAEtD,eAAe;EACbK,mBAAmB,EAAEA,CAACC,SAAiB,GAAG,GAAG,KAC3CN,cAAc,CAACK,mBAAmB,CAACC,SAAS,CAAC;EAE/CC,kBAAkB,EAAEA,CAAA,KAClBP,cAAc,CAACO,kBAAkB,CAAC,CAAC;EAErCC,iBAAiB,EAAEA,CAACC,EAAU,GAAG,GAAG,KAClCT,cAAc,CAACQ,iBAAiB,CAACC,EAAE,CAAC;EAEtCC,yBAAyB,EAAEA,CAACD,EAAU,GAAG,KAAK,KAC5CT,cAAc,CAACU,yBAAyB,CAACD,EAAE,CAAC;EAE9CE,sBAAsB,EAAEA,CAACC,cAAsB,GAAG,EAAE,EAAEC,aAAqB,GAAG,IAAI,KAChFb,cAAc,CAACW,sBAAsB,CAACC,cAAc,EAAEC,aAAa,CAAC;EAEtE;EACAC,iBAAiB,EAAEA,CAACC,IAAY,EAAEC,QAAgB,KAChDhB,cAAc,CAACc,iBAAiB,CAACC,IAAI,EAAEC,QAAQ,CAAC;EAElD;EACAC,gBAAgB,EAAEA,CAACC,KAAa,EAAEC,IAAY,EAAEC,EAAU,KACxDpB,cAAc,CAACiB,gBAAgB,CAACC,KAAK,EAAEC,IAAI,EAAEC,EAAE,CAAC;EAElD;EACAC,kBAAkB,EAAGD,EAAU,IAC7BpB,cAAc,CAACqB,kBAAkB,CAACD,EAAE,CAAC;EAEvCE,WAAW,EAAEA,CAAA,KACXtB,cAAc,CAACsB,WAAW,CAAC,CAAC;EAE9B;EACAC,iBAAiB,EAAGC,EAAwB,IAC1CpB,OAAO,CAACqB,WAAW,CAAC,sBAAsB,EAAED,EAAE,CAAC;EAEjDE,sBAAsB,EAAGF,EAAwB,IAC/CpB,OAAO,CAACqB,WAAW,CAAC,eAAe,EAAED,EAAE,CAAC;EAE1CG,wBAAwB,EAAGH,EAAwB,IACjDpB,OAAO,CAACqB,WAAW,CAAC,iBAAiB,EAAED,EAAE;AAC7C,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
startMotionDetector: (threshold?: number) => any;
|
|
3
|
+
stopMotionDetector: () => any;
|
|
4
|
+
setUpdateInterval: (ms?: number) => any;
|
|
5
|
+
setLocationUpdateInterval: (ms?: number) => any;
|
|
6
|
+
setStabilityThresholds: (startThreshold?: number, stopThreshold?: number) => any;
|
|
7
|
+
fireGeofenceAlert: (type: string, userName: string) => any;
|
|
8
|
+
fireGenericAlert: (title: string, body: string, id: number) => any;
|
|
9
|
+
cancelGenericAlert: (id: number) => any;
|
|
10
|
+
isAvailable: () => any;
|
|
11
|
+
addMotionListener: (cb: (event: any) => void) => import("react-native").EventSubscription;
|
|
12
|
+
addLocationLogListener: (cb: (event: any) => void) => import("react-native").EventSubscription;
|
|
13
|
+
addLocationErrorListener: (cb: (event: any) => void) => import("react-native").EventSubscription;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":";sCAuBmC,MAAM;;6BAMf,MAAM;qCAGE,MAAM;8CAGG,MAAM,kBAAsB,MAAM;8BAIjD,MAAM,YAAY,MAAM;8BAIxB,MAAM,QAAQ,MAAM,MAAM,MAAM;6BAIjC,MAAM;;4BAOP,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;iCAGf,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;mCAGlB,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;;AAtCrD,wBAwCE"}
|
package/package.json
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-geo-activity-kit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Battery-efficient location tracking with motion detection and native notifications.",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.tsx",
|
|
10
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
11
|
+
"default": "./lib/module/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"*.podspec",
|
|
22
|
+
"react-native.config.js",
|
|
23
|
+
"!ios/build",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!android/gradle",
|
|
26
|
+
"!android/gradlew",
|
|
27
|
+
"!android/gradlew.bat",
|
|
28
|
+
"!android/local.properties",
|
|
29
|
+
"!**/__tests__",
|
|
30
|
+
"!**/__fixtures__",
|
|
31
|
+
"!**/__mocks__",
|
|
32
|
+
"!**/.*"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"example": "yarn workspace react-native-geo-activity-kit-example",
|
|
36
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
37
|
+
"prepare": "bob build",
|
|
38
|
+
"typecheck": "tsc",
|
|
39
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
40
|
+
"release": "release-it --only-version",
|
|
41
|
+
"test": "jest"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react-native",
|
|
45
|
+
"location",
|
|
46
|
+
"geofence",
|
|
47
|
+
"background-service",
|
|
48
|
+
"motion-detection",
|
|
49
|
+
"accelerometer",
|
|
50
|
+
"android",
|
|
51
|
+
"battery-efficient"
|
|
52
|
+
],
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/kartikeycsjm/react-native-geo-activity-kit.git"
|
|
56
|
+
},
|
|
57
|
+
"author": "Kartikey Mishra <kartikeymishracsjm@gmail.com> (https://github.com/kartikeycsjm)",
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/kartikeycsjm/react-native-geo-activity-kit/issues"
|
|
61
|
+
},
|
|
62
|
+
"homepage": "https://github.com/kartikeycsjm/react-native-geo-activity-kit#readme",
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"registry": "https://registry.npmjs.org/"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
68
|
+
"@eslint/compat": "^1.3.2",
|
|
69
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
70
|
+
"@eslint/js": "^9.35.0",
|
|
71
|
+
"@react-native-community/cli": "20.0.1",
|
|
72
|
+
"@react-native/babel-preset": "0.81.1",
|
|
73
|
+
"@react-native/eslint-config": "^0.81.1",
|
|
74
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
75
|
+
"@types/jest": "^29.5.14",
|
|
76
|
+
"@types/react": "^19.1.0",
|
|
77
|
+
"commitlint": "^19.8.1",
|
|
78
|
+
"del-cli": "^6.0.0",
|
|
79
|
+
"eslint": "^9.35.0",
|
|
80
|
+
"eslint-config-prettier": "^10.1.8",
|
|
81
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
82
|
+
"jest": "^29.7.0",
|
|
83
|
+
"lefthook": "^2.0.3",
|
|
84
|
+
"prettier": "^2.8.8",
|
|
85
|
+
"react": "19.1.0",
|
|
86
|
+
"react-native": "0.81.1",
|
|
87
|
+
"react-native-builder-bob": "^0.40.16",
|
|
88
|
+
"release-it": "^19.0.4",
|
|
89
|
+
"turbo": "^2.5.6",
|
|
90
|
+
"typescript": "^5.9.2"
|
|
91
|
+
},
|
|
92
|
+
"peerDependencies": {
|
|
93
|
+
"react": "*",
|
|
94
|
+
"react-native": "*"
|
|
95
|
+
},
|
|
96
|
+
"workspaces": [
|
|
97
|
+
"example"
|
|
98
|
+
],
|
|
99
|
+
"packageManager": "yarn@4.11.0",
|
|
100
|
+
"react-native-builder-bob": {
|
|
101
|
+
"source": "src",
|
|
102
|
+
"output": "lib",
|
|
103
|
+
"targets": [
|
|
104
|
+
[
|
|
105
|
+
"module",
|
|
106
|
+
{
|
|
107
|
+
"esm": true
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
"typescript",
|
|
112
|
+
{
|
|
113
|
+
"project": "tsconfig.build.json"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"codegenConfig": {
|
|
119
|
+
"name": "GeoActivityKitSpec",
|
|
120
|
+
"type": "modules",
|
|
121
|
+
"jsSrcsDir": "src",
|
|
122
|
+
"android": {
|
|
123
|
+
"javaPackageName": "com.geoactivitykit"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"prettier": {
|
|
127
|
+
"quoteProps": "consistent",
|
|
128
|
+
"singleQuote": true,
|
|
129
|
+
"tabWidth": 2,
|
|
130
|
+
"trailingComma": "es5",
|
|
131
|
+
"useTabs": false
|
|
132
|
+
},
|
|
133
|
+
"commitlint": {
|
|
134
|
+
"extends": [
|
|
135
|
+
"@commitlint/config-conventional"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
"release-it": {
|
|
139
|
+
"git": {
|
|
140
|
+
"commitMessage": "chore: release ${version}",
|
|
141
|
+
"tagName": "v${version}"
|
|
142
|
+
},
|
|
143
|
+
"npm": {
|
|
144
|
+
"publish": true
|
|
145
|
+
},
|
|
146
|
+
"github": {
|
|
147
|
+
"release": true
|
|
148
|
+
},
|
|
149
|
+
"plugins": {
|
|
150
|
+
"@release-it/conventional-changelog": {
|
|
151
|
+
"preset": {
|
|
152
|
+
"name": "angular"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"jest": {
|
|
158
|
+
"preset": "react-native",
|
|
159
|
+
"modulePathIgnorePatterns": [
|
|
160
|
+
"<rootDir>/example/node_modules",
|
|
161
|
+
"<rootDir>/lib/"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"create-react-native-library": {
|
|
165
|
+
"languages": "kotlin-objc",
|
|
166
|
+
"type": "turbo-module",
|
|
167
|
+
"tools": [
|
|
168
|
+
"eslint",
|
|
169
|
+
"lefthook",
|
|
170
|
+
"release-it",
|
|
171
|
+
"jest"
|
|
172
|
+
],
|
|
173
|
+
"version": "0.55.1"
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
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
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo Go\n';
|
|
8
|
+
|
|
9
|
+
// The name must match getName() in SensorModule.kt
|
|
10
|
+
const RNSensorModule = NativeModules.RNSensorModule
|
|
11
|
+
? NativeModules.RNSensorModule
|
|
12
|
+
: new Proxy(
|
|
13
|
+
{},
|
|
14
|
+
{
|
|
15
|
+
get() {
|
|
16
|
+
throw new Error(LINKING_ERROR);
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const emitter = new NativeEventEmitter(RNSensorModule);
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
startMotionDetector: (threshold: number = 0.8) =>
|
|
25
|
+
RNSensorModule.startMotionDetector(threshold),
|
|
26
|
+
|
|
27
|
+
stopMotionDetector: () =>
|
|
28
|
+
RNSensorModule.stopMotionDetector(),
|
|
29
|
+
|
|
30
|
+
setUpdateInterval: (ms: number = 100) =>
|
|
31
|
+
RNSensorModule.setUpdateInterval(ms),
|
|
32
|
+
|
|
33
|
+
setLocationUpdateInterval: (ms: number = 90000) =>
|
|
34
|
+
RNSensorModule.setLocationUpdateInterval(ms),
|
|
35
|
+
|
|
36
|
+
setStabilityThresholds: (startThreshold: number = 20, stopThreshold: number = 3000) =>
|
|
37
|
+
RNSensorModule.setStabilityThresholds(startThreshold, stopThreshold),
|
|
38
|
+
|
|
39
|
+
// Added types: string for text inputs
|
|
40
|
+
fireGeofenceAlert: (type: string, userName: string) =>
|
|
41
|
+
RNSensorModule.fireGeofenceAlert(type, userName),
|
|
42
|
+
|
|
43
|
+
// Added types: string for text, number for ID (assuming Android notification ID)
|
|
44
|
+
fireGenericAlert: (title: string, body: string, id: number) =>
|
|
45
|
+
RNSensorModule.fireGenericAlert(title, body, id),
|
|
46
|
+
|
|
47
|
+
// Added type: number to match the ID above
|
|
48
|
+
cancelGenericAlert: (id: number) =>
|
|
49
|
+
RNSensorModule.cancelGenericAlert(id),
|
|
50
|
+
|
|
51
|
+
isAvailable: () =>
|
|
52
|
+
RNSensorModule.isAvailable(),
|
|
53
|
+
|
|
54
|
+
// Added type: function that accepts an event (any)
|
|
55
|
+
addMotionListener: (cb: (event: any) => void) =>
|
|
56
|
+
emitter.addListener('onMotionStateChanged', cb),
|
|
57
|
+
|
|
58
|
+
addLocationLogListener: (cb: (event: any) => void) =>
|
|
59
|
+
emitter.addListener('onLocationLog', cb),
|
|
60
|
+
|
|
61
|
+
addLocationErrorListener: (cb: (event: any) => void) =>
|
|
62
|
+
emitter.addListener('onLocationError', cb),
|
|
63
|
+
};
|