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.
@@ -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,5 @@
1
+ GeoActivityKit_kotlinVersion=2.0.21
2
+ GeoActivityKit_minSdkVersion=24
3
+ GeoActivityKit_targetSdkVersion=34
4
+ GeoActivityKit_compileSdkVersion=35
5
+ GeoActivityKit_ndkVersion=27.1.12297006
@@ -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,5 @@
1
+ #import <GeoActivityKitSpec/GeoActivityKitSpec.h>
2
+
3
+ @interface GeoActivityKit : NSObject <NativeGeoActivityKitSpec>
4
+
5
+ @end
@@ -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,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('GeoActivityKit');
5
+ //# sourceMappingURL=NativeGeoActivityKit.js.map
@@ -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,7 @@
1
+ import { type TurboModule } from 'react-native';
2
+ export interface Spec extends TurboModule {
3
+ multiply(a: number, b: number): number;
4
+ }
5
+ declare const _default: Spec;
6
+ export default _default;
7
+ //# sourceMappingURL=NativeGeoActivityKit.d.ts.map
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
+
3
+ export interface Spec extends TurboModule {
4
+ multiply(a: number, b: number): number;
5
+ }
6
+
7
+ export default TurboModuleRegistry.getEnforcing<Spec>('GeoActivityKit');
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
+ };