react-native-geofence-manager 0.1.0-beta.1

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 = "GeofenceManager"
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/GilvanIzidio/react-native-geofence-manager.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) 2026 Izidio
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,37 @@
1
+ # react-native-geofence-manager
2
+
3
+ Robust manager for your geofencing areas.
4
+
5
+ ## Installation
6
+
7
+
8
+ ```sh
9
+ npm install react-native-geofence-manager
10
+ ```
11
+
12
+
13
+ ## Usage
14
+
15
+
16
+ ```js
17
+ import { multiply } from 'react-native-geofence-manager';
18
+
19
+ // ...
20
+
21
+ const result = multiply(3, 7);
22
+ ```
23
+
24
+
25
+ ## Contributing
26
+
27
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
28
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
29
+ - [Code of conduct](CODE_OF_CONDUCT.md)
30
+
31
+ ## License
32
+
33
+ MIT
34
+
35
+ ---
36
+
37
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,68 @@
1
+ buildscript {
2
+ ext.GeofenceManager = [
3
+ kotlinVersion: "2.0.21",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+
14
+ return GeofenceManager[prop]
15
+ }
16
+
17
+ repositories {
18
+ google()
19
+ mavenCentral()
20
+ }
21
+
22
+ dependencies {
23
+ classpath "com.android.tools.build:gradle:8.7.2"
24
+ // noinspection DifferentKotlinGradleVersion
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+
30
+ apply plugin: "com.android.library"
31
+ apply plugin: "kotlin-android"
32
+
33
+ apply plugin: "com.facebook.react"
34
+
35
+ android {
36
+ namespace "com.geofencemanager"
37
+
38
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
39
+
40
+ defaultConfig {
41
+ minSdkVersion getExtOrDefault("minSdkVersion")
42
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
43
+ }
44
+
45
+ buildFeatures {
46
+ buildConfig true
47
+ }
48
+
49
+ buildTypes {
50
+ release {
51
+ minifyEnabled false
52
+ }
53
+ }
54
+
55
+ lint {
56
+ disable "GradleCompatible"
57
+ }
58
+
59
+ compileOptions {
60
+ sourceCompatibility JavaVersion.VERSION_1_8
61
+ targetCompatibility JavaVersion.VERSION_1_8
62
+ }
63
+ }
64
+
65
+ dependencies {
66
+ implementation "com.facebook.react:react-android"
67
+ implementation "com.google.android.gms:play-services-location:21.0.1"
68
+ }
@@ -0,0 +1,16 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
3
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
4
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
5
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
6
+
7
+ <application>
8
+ <receiver
9
+ android:name=".GeofenceReceiver"
10
+ android:exported="false" />
11
+
12
+ <service
13
+ android:name=".GeofenceHeadlessTaskService"
14
+ android:exported="false" />
15
+ </application>
16
+ </manifest>
@@ -0,0 +1,40 @@
1
+ package com.geofencemanager
2
+
3
+ import android.content.Intent
4
+ import com.facebook.react.HeadlessJsTaskService
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.jstasks.HeadlessJsTaskConfig
7
+
8
+ class GeofenceHeadlessTaskService : HeadlessJsTaskService() {
9
+
10
+ override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
11
+ val extras = intent?.extras ?: return null
12
+
13
+ val eventName = extras.getString("eventName") ?: return null
14
+ val regionId = extras.getString("regionId") ?: return null
15
+
16
+ val regionMap = Arguments.createMap().apply {
17
+ putString("id", regionId)
18
+ putString("name", extras.getString("regionName") ?: "")
19
+ putString("city", extras.getString("regionCity") ?: "")
20
+ putDouble("latitude", extras.getDouble("regionLatitude", 0.0))
21
+ putDouble("longitude", extras.getDouble("regionLongitude", 0.0))
22
+ putDouble("radius", extras.getDouble("regionRadius", 100.0))
23
+ }
24
+
25
+ val data = Arguments.createMap().apply {
26
+ putString("event", eventName)
27
+ putMap("region", regionMap)
28
+ putDouble("timestamp", System.currentTimeMillis().toDouble())
29
+ }
30
+
31
+ val timeout = RegionManager.getHeadlessTimeout(applicationContext)
32
+
33
+ return HeadlessJsTaskConfig(
34
+ GeofenceReceiver.HEADLESS_TASK_NAME,
35
+ data,
36
+ timeout.toLong(),
37
+ true
38
+ )
39
+ }
40
+ }
@@ -0,0 +1,84 @@
1
+ package com.geofencemanager
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.LifecycleEventListener
5
+ import com.facebook.react.bridge.Promise
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.bridge.ReadableArray
8
+ import com.facebook.react.bridge.ReadableMap
9
+
10
+ class GeofenceManagerModule(reactContext: ReactApplicationContext) :
11
+ NativeGeofenceManagerSpec(reactContext), LifecycleEventListener {
12
+
13
+ companion object {
14
+ const val NAME = "GeofenceManager"
15
+ private const val TAG = "GeofenceManagerModule"
16
+ private const val MAX_REGIONS = 100
17
+ }
18
+
19
+ init {
20
+ GeofenceReceiver.reactContext = reactContext
21
+ reactContext.addLifecycleEventListener(this)
22
+ }
23
+
24
+ override fun getName(): String = NAME
25
+
26
+ override fun onHostResume() {
27
+ GeofenceReceiver.reactContext = reactApplicationContext
28
+ }
29
+
30
+ override fun onHostPause() {}
31
+
32
+ override fun onHostDestroy() {}
33
+
34
+ override fun start(regions: ReadableArray, options: ReadableMap?, promise: Promise) {
35
+ try {
36
+ if (regions.size() > MAX_REGIONS) {
37
+ promise.reject(
38
+ "GEOFENCE_LIMIT_EXCEEDED",
39
+ "Limite máximo de $MAX_REGIONS regiões excedido. Recebido: ${regions.size()}"
40
+ )
41
+ return
42
+ }
43
+
44
+ val regionList = parseRegions(regions)
45
+ val dwellDelay = options?.takeIf { it.hasKey("dwellDelay") }?.getInt("dwellDelay") ?: 5000
46
+ val headlessTaskTimeout = options?.takeIf { it.hasKey("headlessTaskTimeout") }?.getInt("headlessTaskTimeout") ?: 30000
47
+ Log.d(TAG, "Iniciando geofencing com ${regionList.size} região(ões)")
48
+ RegionManager.registerGeofences(reactApplicationContext, regionList, dwellDelay, headlessTaskTimeout)
49
+ promise.resolve(null)
50
+ } catch (e: Exception) {
51
+ Log.e(TAG, "Erro ao iniciar: ${e.message}")
52
+ promise.reject("GEOFENCE_ERROR", e.message)
53
+ }
54
+ }
55
+
56
+ override fun stop(promise: Promise) {
57
+ try {
58
+ Log.d(TAG, "Parando geofencing")
59
+ RegionManager.removeAllGeofences(reactApplicationContext)
60
+ promise.resolve(null)
61
+ } catch (e: Exception) {
62
+ Log.e(TAG, "Erro ao parar: ${e.message}")
63
+ promise.reject("GEOFENCE_ERROR", e.message)
64
+ }
65
+ }
66
+
67
+ private fun parseRegions(regions: ReadableArray): List<Region> {
68
+ val list = mutableListOf<Region>()
69
+ for (i in 0 until regions.size()) {
70
+ val map = regions.getMap(i) ?: continue
71
+ list.add(
72
+ Region(
73
+ id = map.getString("id") ?: "region_$i",
74
+ name = map.getString("name") ?: "",
75
+ city = map.getString("city") ?: "",
76
+ latitude = map.getDouble("latitude"),
77
+ longitude = map.getDouble("longitude"),
78
+ radius = map.takeIf { it.hasKey("radius") }?.getDouble("radius")?.toFloat() ?: 100f
79
+ )
80
+ )
81
+ }
82
+ return list
83
+ }
84
+ }
@@ -0,0 +1,33 @@
1
+ package com.geofencemanager
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class GeofenceManagerPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == GeofenceManagerModule.NAME) {
13
+ GeofenceManagerModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
+ return ReactModuleInfoProvider {
21
+ val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
+ moduleInfos[GeofenceManagerModule.NAME] = ReactModuleInfo(
23
+ GeofenceManagerModule.NAME,
24
+ GeofenceManagerModule.NAME,
25
+ false, // canOverrideExistingModule
26
+ false, // needsEagerInit
27
+ false, // isCxxModule
28
+ true // isTurboModule
29
+ )
30
+ moduleInfos
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,98 @@
1
+ package com.geofencemanager
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.ReactContext
9
+ import com.facebook.react.modules.core.DeviceEventManagerModule
10
+ import com.google.android.gms.location.Geofence
11
+ import com.google.android.gms.location.GeofencingEvent
12
+
13
+ class GeofenceReceiver : BroadcastReceiver() {
14
+
15
+ companion object {
16
+ private const val TAG = "GeofenceReceiver"
17
+ const val HEADLESS_TASK_NAME = "GeofenceHeadlessTask"
18
+
19
+ @Volatile
20
+ var reactContext: ReactContext? = null
21
+ }
22
+
23
+ override fun onReceive(context: Context, intent: Intent) {
24
+ val geofencingEvent = GeofencingEvent.fromIntent(intent) ?: run {
25
+ Log.e(TAG, "GeofencingEvent é null")
26
+ return
27
+ }
28
+
29
+ if (geofencingEvent.hasError()) {
30
+ Log.e(TAG, "Erro no geofencing: ${geofencingEvent.errorCode}")
31
+ return
32
+ }
33
+
34
+ val triggeringGeofences = geofencingEvent.triggeringGeofences
35
+ if (triggeringGeofences.isNullOrEmpty()) {
36
+ Log.w(TAG, "Nenhuma geofence disparada")
37
+ return
38
+ }
39
+
40
+ val eventName = when (geofencingEvent.geofenceTransition) {
41
+ Geofence.GEOFENCE_TRANSITION_ENTER -> "onEnter"
42
+ Geofence.GEOFENCE_TRANSITION_EXIT -> "onExit"
43
+ Geofence.GEOFENCE_TRANSITION_DWELL -> "onDwell"
44
+ else -> {
45
+ Log.w(TAG, "Transição desconhecida: ${geofencingEvent.geofenceTransition}")
46
+ return
47
+ }
48
+ }
49
+
50
+ for (geofence in triggeringGeofences) {
51
+ sendEvent(context, eventName, geofence.requestId)
52
+ }
53
+ }
54
+
55
+ private fun sendEvent(context: Context, eventName: String, regionId: String) {
56
+ val ctx = reactContext
57
+ val region = RegionManager.getRegionById(context, regionId)
58
+
59
+ if (ctx != null && ctx.hasActiveReactInstance()) {
60
+ try {
61
+ val params = buildRegionMap(region, regionId)
62
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
63
+ .emit(eventName, params)
64
+ return
65
+ } catch (e: Exception) {
66
+ Log.e(TAG, "Erro ao enviar evento: ${e.message}")
67
+ }
68
+ }
69
+
70
+ startHeadlessTask(context, eventName, region, regionId)
71
+ }
72
+
73
+ private fun buildRegionMap(region: Region?, regionId: String) = Arguments.createMap().apply {
74
+ putString("id", region?.id ?: regionId)
75
+ putString("name", region?.name ?: "")
76
+ putString("city", region?.city ?: "")
77
+ putDouble("latitude", region?.latitude ?: 0.0)
78
+ putDouble("longitude", region?.longitude ?: 0.0)
79
+ putDouble("radius", region?.radius?.toDouble() ?: 100.0)
80
+ }
81
+
82
+ private fun startHeadlessTask(context: Context, eventName: String, region: Region?, regionId: String) {
83
+ try {
84
+ val serviceIntent = Intent(context, GeofenceHeadlessTaskService::class.java).apply {
85
+ putExtra("eventName", eventName)
86
+ putExtra("regionId", region?.id ?: regionId)
87
+ putExtra("regionName", region?.name ?: "")
88
+ putExtra("regionCity", region?.city ?: "")
89
+ putExtra("regionLatitude", region?.latitude ?: 0.0)
90
+ putExtra("regionLongitude", region?.longitude ?: 0.0)
91
+ putExtra("regionRadius", region?.radius?.toDouble() ?: 100.0)
92
+ }
93
+ context.startService(serviceIntent)
94
+ } catch (e: Exception) {
95
+ Log.e(TAG, "Erro ao iniciar HeadlessTask: ${e.message}")
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,182 @@
1
+ package com.geofencemanager
2
+
3
+ import android.Manifest
4
+ import android.app.PendingIntent
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.content.SharedPreferences
8
+ import android.content.pm.PackageManager
9
+ import android.util.Log
10
+ import androidx.core.app.ActivityCompat
11
+ import com.google.android.gms.location.Geofence
12
+ import com.google.android.gms.location.GeofencingClient
13
+ import com.google.android.gms.location.GeofencingRequest
14
+ import com.google.android.gms.location.LocationServices
15
+ import org.json.JSONArray
16
+ import org.json.JSONObject
17
+
18
+ data class Region(
19
+ val id: String,
20
+ val name: String,
21
+ val city: String,
22
+ val latitude: Double,
23
+ val longitude: Double,
24
+ val radius: Float = 100f
25
+ )
26
+
27
+ object RegionManager {
28
+ private const val TAG = "RegionManager"
29
+ private const val PREFS_NAME = "GeofenceManagerPrefs"
30
+ private const val REGIONS_KEY = "registered_regions"
31
+ private const val HEADLESS_TIMEOUT_KEY = "headless_task_timeout"
32
+
33
+ private var geofencingClient: GeofencingClient? = null
34
+ private var pendingIntent: PendingIntent? = null
35
+
36
+ fun registerGeofences(context: Context, regions: List<Region>, dwellDelay: Int = 5000, headlessTaskTimeout: Int = 30000) {
37
+ if (regions.isEmpty()) {
38
+ Log.w(TAG, "Nenhuma região para registrar")
39
+ return
40
+ }
41
+
42
+ if (!hasLocationPermission(context)) {
43
+ Log.w(TAG, "Permissão de localização não concedida")
44
+ return
45
+ }
46
+
47
+ saveRegions(context, regions)
48
+ saveHeadlessTimeout(context, headlessTaskTimeout)
49
+ geofencingClient = LocationServices.getGeofencingClient(context)
50
+
51
+ val geofenceList = regions.map { region ->
52
+ Geofence.Builder()
53
+ .setRequestId(region.id)
54
+ .setCircularRegion(region.latitude, region.longitude, region.radius)
55
+ .setExpirationDuration(Geofence.NEVER_EXPIRE)
56
+ .setTransitionTypes(
57
+ Geofence.GEOFENCE_TRANSITION_ENTER or
58
+ Geofence.GEOFENCE_TRANSITION_EXIT or
59
+ Geofence.GEOFENCE_TRANSITION_DWELL
60
+ )
61
+ .setLoiteringDelay(dwellDelay)
62
+ .build()
63
+ }
64
+
65
+ val geofencingRequest = GeofencingRequest.Builder()
66
+ .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
67
+ .addGeofences(geofenceList)
68
+ .build()
69
+
70
+ try {
71
+ geofencingClient?.addGeofences(geofencingRequest, getGeofencePendingIntent(context))
72
+ ?.addOnSuccessListener {
73
+ Log.d(TAG, "Total: ${regions.size} geofence(s) registrada(s)")
74
+ }
75
+ ?.addOnFailureListener { e ->
76
+ Log.e(TAG, "Erro ao registrar: ${e.message}")
77
+ }
78
+ } catch (e: SecurityException) {
79
+ Log.e(TAG, "SecurityException: ${e.message}")
80
+ }
81
+ }
82
+
83
+ fun removeAllGeofences(context: Context) {
84
+ geofencingClient = LocationServices.getGeofencingClient(context)
85
+ pendingIntent?.let { intent ->
86
+ geofencingClient?.removeGeofences(intent)
87
+ ?.addOnSuccessListener {
88
+ Log.d(TAG, "Todas as geofences removidas")
89
+ clearRegions(context)
90
+ }
91
+ ?.addOnFailureListener { e ->
92
+ Log.e(TAG, "Erro ao remover: ${e.message}")
93
+ }
94
+ }
95
+ }
96
+
97
+ fun getRegionById(context: Context, id: String): Region? {
98
+ return getRegions(context).find { it.id == id }
99
+ }
100
+
101
+ fun getHeadlessTimeout(context: Context): Int {
102
+ return getPrefs(context).getInt(HEADLESS_TIMEOUT_KEY, 30000)
103
+ }
104
+
105
+ private fun saveHeadlessTimeout(context: Context, timeout: Int) {
106
+ getPrefs(context).edit()
107
+ .putInt(HEADLESS_TIMEOUT_KEY, timeout)
108
+ .apply()
109
+ }
110
+
111
+ fun getRegions(context: Context): List<Region> {
112
+ val json = getPrefs(context).getString(REGIONS_KEY, null) ?: return emptyList()
113
+
114
+ return try {
115
+ val jsonArray = JSONArray(json)
116
+ val list = mutableListOf<Region>()
117
+ for (i in 0 until jsonArray.length()) {
118
+ val obj = jsonArray.getJSONObject(i)
119
+ list.add(
120
+ Region(
121
+ id = obj.getString("id"),
122
+ name = obj.getString("name"),
123
+ city = obj.getString("city"),
124
+ latitude = obj.getDouble("latitude"),
125
+ longitude = obj.getDouble("longitude"),
126
+ radius = obj.getDouble("radius").toFloat()
127
+ )
128
+ )
129
+ }
130
+ list
131
+ } catch (e: Exception) {
132
+ Log.e(TAG, "Erro ao ler regiões: ${e.message}")
133
+ emptyList()
134
+ }
135
+ }
136
+
137
+ private fun saveRegions(context: Context, regions: List<Region>) {
138
+ val jsonArray = JSONArray()
139
+ for (region in regions) {
140
+ val obj = JSONObject().apply {
141
+ put("id", region.id)
142
+ put("name", region.name)
143
+ put("city", region.city)
144
+ put("latitude", region.latitude)
145
+ put("longitude", region.longitude)
146
+ put("radius", region.radius)
147
+ }
148
+ jsonArray.put(obj)
149
+ }
150
+
151
+ getPrefs(context).edit()
152
+ .putString(REGIONS_KEY, jsonArray.toString())
153
+ .apply()
154
+
155
+ Log.d(TAG, "Salvas ${regions.size} região(ões)")
156
+ }
157
+
158
+ private fun clearRegions(context: Context) {
159
+ getPrefs(context).edit()
160
+ .remove(REGIONS_KEY)
161
+ .apply()
162
+ }
163
+
164
+ private fun getPrefs(context: Context): SharedPreferences {
165
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
166
+ }
167
+
168
+ private fun getGeofencePendingIntent(context: Context): PendingIntent {
169
+ pendingIntent?.let { return it }
170
+ val intent = Intent(context, GeofenceReceiver::class.java)
171
+ val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
172
+ pendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags)
173
+ return pendingIntent!!
174
+ }
175
+
176
+ private fun hasLocationPermission(context: Context): Boolean {
177
+ return ActivityCompat.checkSelfPermission(
178
+ context,
179
+ Manifest.permission.ACCESS_FINE_LOCATION
180
+ ) == PackageManager.PERMISSION_GRANTED
181
+ }
182
+ }
@@ -0,0 +1,5 @@
1
+ #import <GeofenceManagerSpec/GeofenceManagerSpec.h>
2
+
3
+ @interface GeofenceManager : NSObject <NativeGeofenceManagerSpec>
4
+
5
+ @end
@@ -0,0 +1,21 @@
1
+ #import "GeofenceManager.h"
2
+
3
+ @implementation GeofenceManager
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::NativeGeofenceManagerSpecJSI>(params);
14
+ }
15
+
16
+ + (NSString *)moduleName
17
+ {
18
+ return @"GeofenceManager";
19
+ }
20
+
21
+ @end
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('GeofenceManager');
5
+ //# sourceMappingURL=NativeGeofenceManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"..\\..\\src","sources":["NativeGeofenceManager.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAqBpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,iBAAiB,CAAC","ignoreList":[]}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ import NativeGeofence from "./NativeGeofenceManager.js";
4
+ import { AppRegistry, NativeEventEmitter, Platform } from 'react-native';
5
+ const emitter = new NativeEventEmitter(NativeGeofence);
6
+ const MAX_REGIONS = 100;
7
+ export const GeofenceManager = {
8
+ start: (regions, options) => {
9
+ if (regions.length > MAX_REGIONS) {
10
+ return Promise.reject(new Error(`Limite máximo de ${MAX_REGIONS} regiões excedido. Recebido: ${regions.length}`));
11
+ }
12
+ return NativeGeofence.start(regions, options ?? {});
13
+ },
14
+ stop: NativeGeofence.stop,
15
+ onEnter: cb => emitter.addListener('onEnter', cb),
16
+ onExit: cb => emitter.addListener('onExit', cb),
17
+ onDwell: cb => emitter.addListener('onDwell', cb),
18
+ registerBackgroundHandler: handler => {
19
+ if (Platform.OS !== 'android') {
20
+ return;
21
+ }
22
+ AppRegistry.registerHeadlessTask('GeofenceHeadlessTask', () => handler);
23
+ }
24
+ };
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeGeofence","AppRegistry","NativeEventEmitter","Platform","emitter","MAX_REGIONS","GeofenceManager","start","regions","options","length","Promise","reject","Error","stop","onEnter","cb","addListener","onExit","onDwell","registerBackgroundHandler","handler","OS","registerHeadlessTask"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,cAAc,MAGd,4BAAyB;AAChC,SAASC,WAAW,EAAEC,kBAAkB,EAAEC,QAAQ,QAAQ,cAAc;AAExE,MAAMC,OAAO,GAAG,IAAIF,kBAAkB,CAACF,cAAqB,CAAC;AAE7D,MAAMK,WAAW,GAAG,GAAG;AAmBvB,OAAO,MAAMC,eAAe,GAAG;EAC7BC,KAAK,EAAEA,CAACC,OAAiB,EAAEC,OAAyB,KAAoB;IACtE,IAAID,OAAO,CAACE,MAAM,GAAGL,WAAW,EAAE;MAChC,OAAOM,OAAO,CAACC,MAAM,CACnB,IAAIC,KAAK,CACP,oBAAoBR,WAAW,gCAAgCG,OAAO,CAACE,MAAM,EAC/E,CACF,CAAC;IACH;IACA,OAAOV,cAAc,CAACO,KAAK,CAACC,OAAO,EAAEC,OAAO,IAAI,CAAC,CAAC,CAAC;EACrD,CAAC;EAEDK,IAAI,EAAEd,cAAc,CAACc,IAAI;EAEzBC,OAAO,EAAGC,EAAyC,IACjDZ,OAAO,CAACa,WAAW,CAAC,SAAS,EAAED,EAAS,CAAC;EAE3CE,MAAM,EAAGF,EAAyC,IAChDZ,OAAO,CAACa,WAAW,CAAC,QAAQ,EAAED,EAAS,CAAC;EAE1CG,OAAO,EAAGH,EAAyC,IACjDZ,OAAO,CAACa,WAAW,CAAC,SAAS,EAAED,EAAS,CAAC;EAE3CI,yBAAyB,EAAGC,OAAkC,IAAW;IACvE,IAAIlB,QAAQ,CAACmB,EAAE,KAAK,SAAS,EAAE;MAC7B;IACF;IACArB,WAAW,CAACsB,oBAAoB,CAAC,sBAAsB,EAAE,MAAMF,OAAO,CAAC;EACzE;AACF,CAAC","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,20 @@
1
+ import { type TurboModule } from 'react-native';
2
+ export type Region = {
3
+ id: string;
4
+ name: string;
5
+ city: string;
6
+ latitude: number;
7
+ longitude: number;
8
+ radius?: number;
9
+ };
10
+ export type GeofenceOptions = {
11
+ dwellDelay?: number;
12
+ headlessTaskTimeout?: number;
13
+ };
14
+ export interface Spec extends TurboModule {
15
+ start(regions: Region[], options?: GeofenceOptions): Promise<void>;
16
+ stop(): Promise<void>;
17
+ }
18
+ declare const _default: Spec;
19
+ export default _default;
20
+ //# sourceMappingURL=NativeGeofenceManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeGeofenceManager.d.ts","sourceRoot":"","sources":["../../../src/NativeGeofenceManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;;AAED,wBAAyE"}
@@ -0,0 +1,25 @@
1
+ import { type Region, type GeofenceOptions } from './NativeGeofenceManager';
2
+ export type GeofenceEventRegion = {
3
+ id: string;
4
+ name: string;
5
+ city: string;
6
+ latitude: number;
7
+ longitude: number;
8
+ radius: number;
9
+ };
10
+ export type GeofenceEvent = {
11
+ event: 'onEnter' | 'onExit' | 'onDwell';
12
+ region: GeofenceEventRegion;
13
+ timestamp: number;
14
+ };
15
+ export type GeofenceBackgroundHandler = (event: GeofenceEvent) => Promise<void>;
16
+ export declare const GeofenceManager: {
17
+ start: (regions: Region[], options?: GeofenceOptions) => Promise<void>;
18
+ stop: () => Promise<void>;
19
+ onEnter: (cb: (region: GeofenceEventRegion) => void) => import("react-native").EventSubscription;
20
+ onExit: (cb: (region: GeofenceEventRegion) => void) => import("react-native").EventSubscription;
21
+ onDwell: (cb: (region: GeofenceEventRegion) => void) => import("react-native").EventSubscription;
22
+ registerBackgroundHandler: (handler: GeofenceBackgroundHandler) => void;
23
+ };
24
+ export type { Region, GeofenceOptions } from './NativeGeofenceManager';
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAuB,EACrB,KAAK,MAAM,EACX,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAOjC,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhF,eAAO,MAAM,eAAe;qBACT,MAAM,EAAE,YAAY,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;;kBAatD,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI;iBAGtC,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI;kBAGpC,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI;yCAGd,yBAAyB,KAAG,IAAI;CAMtE,CAAC;AAEF,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,169 @@
1
+ {
2
+ "name": "react-native-geofence-manager",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "Robust manager for your geofencing areas.",
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-geofence-manager-example",
36
+ "prepare": "bob build",
37
+ "typecheck": "tsc",
38
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
39
+ "test": "jest",
40
+ "release": "release-it --only-version",
41
+ "clean": "rm -rf node_modules example/node_modules lib android/build example/android/build example/android/app/build example/android/.gradle .yarn/cache && yarn cache clean && yarn install && yarn prepare"
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/izidiodev/react-native-geofence-manager.git"
51
+ },
52
+ "author": "Izidio <izidio.dev.work@gmail.com> (https://github.com/izidiodev)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/izidiodev/react-native-geofence-manager/issues"
56
+ },
57
+ "homepage": "https://github.com/izidiodev/react-native-geofence-manager#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "devDependencies": {
62
+ "@commitlint/config-conventional": "^19.8.1",
63
+ "@eslint/compat": "^1.3.2",
64
+ "@eslint/eslintrc": "^3.3.1",
65
+ "@eslint/js": "^9.35.0",
66
+ "@react-native/babel-preset": "0.83.0",
67
+ "@react-native/eslint-config": "0.83.0",
68
+ "@release-it/conventional-changelog": "^10.0.1",
69
+ "@types/jest": "^29.5.14",
70
+ "@types/react": "^19.2.0",
71
+ "commitlint": "^19.8.1",
72
+ "del-cli": "^6.0.0",
73
+ "eslint": "^9.35.0",
74
+ "eslint-config-prettier": "^10.1.8",
75
+ "eslint-plugin-prettier": "^5.5.4",
76
+ "jest": "^29.7.0",
77
+ "lefthook": "^2.0.3",
78
+ "prettier": "^2.8.8",
79
+ "react": "19.2.0",
80
+ "react-native": "0.83.0",
81
+ "react-native-builder-bob": "^0.40.17",
82
+ "release-it": "^19.0.4",
83
+ "turbo": "^2.5.6",
84
+ "typescript": "^5.9.2"
85
+ },
86
+ "peerDependencies": {
87
+ "react": "*",
88
+ "react-native": "*"
89
+ },
90
+ "workspaces": [
91
+ "example"
92
+ ],
93
+ "packageManager": "yarn@4.11.0",
94
+ "react-native-builder-bob": {
95
+ "source": "src",
96
+ "output": "lib",
97
+ "targets": [
98
+ [
99
+ "module",
100
+ {
101
+ "esm": true
102
+ }
103
+ ],
104
+ [
105
+ "typescript",
106
+ {
107
+ "project": "tsconfig.build.json"
108
+ }
109
+ ]
110
+ ]
111
+ },
112
+ "codegenConfig": {
113
+ "name": "GeofenceManagerSpec",
114
+ "type": "modules",
115
+ "jsSrcsDir": "src",
116
+ "android": {
117
+ "javaPackageName": "com.geofencemanager"
118
+ }
119
+ },
120
+ "commitlint": {
121
+ "extends": [
122
+ "@commitlint/config-conventional"
123
+ ]
124
+ },
125
+ "prettier": {
126
+ "quoteProps": "consistent",
127
+ "singleQuote": true,
128
+ "tabWidth": 2,
129
+ "trailingComma": "es5",
130
+ "useTabs": false
131
+ },
132
+ "jest": {
133
+ "preset": "react-native",
134
+ "modulePathIgnorePatterns": [
135
+ "<rootDir>/example/node_modules",
136
+ "<rootDir>/lib/"
137
+ ]
138
+ },
139
+ "release-it": {
140
+ "git": {
141
+ "commitMessage": "chore: release ${version}",
142
+ "tagName": "v${version}"
143
+ },
144
+ "npm": {
145
+ "publish": true
146
+ },
147
+ "github": {
148
+ "release": true
149
+ },
150
+ "plugins": {
151
+ "@release-it/conventional-changelog": {
152
+ "preset": {
153
+ "name": "angular"
154
+ }
155
+ }
156
+ }
157
+ },
158
+ "create-react-native-library": {
159
+ "type": "turbo-module",
160
+ "languages": "kotlin-objc",
161
+ "tools": [
162
+ "lefthook",
163
+ "eslint",
164
+ "jest",
165
+ "release-it"
166
+ ],
167
+ "version": "0.57.0"
168
+ }
169
+ }
@@ -0,0 +1,22 @@
1
+ import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
+
3
+ export type Region = {
4
+ id: string;
5
+ name: string;
6
+ city: string;
7
+ latitude: number;
8
+ longitude: number;
9
+ radius?: number;
10
+ };
11
+
12
+ export type GeofenceOptions = {
13
+ dwellDelay?: number;
14
+ headlessTaskTimeout?: number;
15
+ };
16
+
17
+ export interface Spec extends TurboModule {
18
+ start(regions: Region[], options?: GeofenceOptions): Promise<void>;
19
+ stop(): Promise<void>;
20
+ }
21
+
22
+ export default TurboModuleRegistry.getEnforcing<Spec>('GeofenceManager');
package/src/index.tsx ADDED
@@ -0,0 +1,59 @@
1
+ import NativeGeofence, {
2
+ type Region,
3
+ type GeofenceOptions,
4
+ } from './NativeGeofenceManager';
5
+ import { AppRegistry, NativeEventEmitter, Platform } from 'react-native';
6
+
7
+ const emitter = new NativeEventEmitter(NativeGeofence as any);
8
+
9
+ const MAX_REGIONS = 100;
10
+
11
+ export type GeofenceEventRegion = {
12
+ id: string;
13
+ name: string;
14
+ city: string;
15
+ latitude: number;
16
+ longitude: number;
17
+ radius: number;
18
+ };
19
+
20
+ export type GeofenceEvent = {
21
+ event: 'onEnter' | 'onExit' | 'onDwell';
22
+ region: GeofenceEventRegion;
23
+ timestamp: number;
24
+ };
25
+
26
+ export type GeofenceBackgroundHandler = (event: GeofenceEvent) => Promise<void>;
27
+
28
+ export const GeofenceManager = {
29
+ start: (regions: Region[], options?: GeofenceOptions): Promise<void> => {
30
+ if (regions.length > MAX_REGIONS) {
31
+ return Promise.reject(
32
+ new Error(
33
+ `Limite máximo de ${MAX_REGIONS} regiões excedido. Recebido: ${regions.length}`
34
+ )
35
+ );
36
+ }
37
+ return NativeGeofence.start(regions, options ?? {});
38
+ },
39
+
40
+ stop: NativeGeofence.stop,
41
+
42
+ onEnter: (cb: (region: GeofenceEventRegion) => void) =>
43
+ emitter.addListener('onEnter', cb as any),
44
+
45
+ onExit: (cb: (region: GeofenceEventRegion) => void) =>
46
+ emitter.addListener('onExit', cb as any),
47
+
48
+ onDwell: (cb: (region: GeofenceEventRegion) => void) =>
49
+ emitter.addListener('onDwell', cb as any),
50
+
51
+ registerBackgroundHandler: (handler: GeofenceBackgroundHandler): void => {
52
+ if (Platform.OS !== 'android') {
53
+ return;
54
+ }
55
+ AppRegistry.registerHeadlessTask('GeofenceHeadlessTask', () => handler);
56
+ },
57
+ };
58
+
59
+ export type { Region, GeofenceOptions } from './NativeGeofenceManager';