react-native-nitro-location-tracking 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -11
- package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/LocationEngine.kt +30 -9
- package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTracking.kt +140 -33
- package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/PermissionStatusMonitor.kt +26 -0
- package/ios/LocationEngine.swift +11 -0
- package/ios/NitroLocationTracking.swift +51 -4
- package/lib/typescript/src/NitroLocationTracking.nitro.d.ts +15 -1
- package/lib/typescript/src/NitroLocationTracking.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.cpp +15 -3
- package/nitrogen/generated/android/c++/JHybridNitroLocationTrackingSpec.hpp +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrolocationtracking/HybridNitroLocationTrackingSpec.kt +1 -1
- package/nitrogen/generated/ios/c++/HybridNitroLocationTrackingSpecSwift.hpp +4 -2
- package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNitroLocationTrackingSpec_cxx.swift +12 -4
- package/nitrogen/generated/shared/c++/HybridNitroLocationTrackingSpec.hpp +1 -4
- package/package.json +1 -1
- package/src/NitroLocationTracking.nitro.ts +15 -1
package/README.md
CHANGED
|
@@ -453,6 +453,36 @@ NitroLocationModule.onProviderStatusChange((gps, network) => {
|
|
|
453
453
|
});
|
|
454
454
|
```
|
|
455
455
|
|
|
456
|
+
### Prompt user to enable GPS
|
|
457
|
+
|
|
458
|
+
Ask the user to turn on device location. On Android this shows the native
|
|
459
|
+
Google Play Services in-app dialog ("For better experience, turn on device
|
|
460
|
+
location…") without leaving the app. On iOS there is no equivalent system
|
|
461
|
+
dialog, so this opens your app's Settings page and resolves after the user
|
|
462
|
+
returns to the app.
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
import NitroLocationModule from 'react-native-nitro-location-tracking';
|
|
466
|
+
|
|
467
|
+
async function ensureGpsOn() {
|
|
468
|
+
if (NitroLocationModule.isLocationServicesEnabled()) {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
const enabled = await NitroLocationModule.openLocationSettings();
|
|
472
|
+
if (enabled) {
|
|
473
|
+
NitroLocationModule.startTracking();
|
|
474
|
+
} else {
|
|
475
|
+
// User declined the dialog (Android) or did not enable GPS (iOS)
|
|
476
|
+
}
|
|
477
|
+
return enabled;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Platform behavior:**
|
|
482
|
+
|
|
483
|
+
- **Android** — Uses `SettingsClient.checkLocationSettings()` + `startResolutionForResult`. Resolves `true` if GPS is already on or if the user accepts the dialog, `false` if the user declines or the dialog cannot be shown.
|
|
484
|
+
- **iOS** — Opens the app's Settings page via `UIApplication.openSettingsURLString` and listens for `UIApplication.didBecomeActiveNotification` to detect the return to foreground. Resolves `true` if `CLLocationManager.locationServicesEnabled()` is on after the user returns, `false` otherwise.
|
|
485
|
+
|
|
456
486
|
### Permission Status
|
|
457
487
|
|
|
458
488
|
Check the current location permission status without prompting the user:
|
|
@@ -703,17 +733,17 @@ type PermissionStatus =
|
|
|
703
733
|
| `stopTripCalculation()` | `TripStats` | Stop recording and get final stats |
|
|
704
734
|
| `getTripStats()` | `TripStats` | Get current trip stats without stopping |
|
|
705
735
|
| `resetTripCalculation()` | `void` | Reset trip calculator |
|
|
706
|
-
| `isLocationServicesEnabled()` | `boolean` | Check if GPS/location is enabled on device
|
|
707
|
-
| `openLocationSettings(
|
|
708
|
-
| `onProviderStatusChange(callback)` | `void` | Register GPS/network provider status callback
|
|
709
|
-
| `isAirplaneModeEnabled()` | `boolean` | Check if Airplane mode is active on Android
|
|
710
|
-
| `onAirplaneModeChange(callback)` | `void` | Register Airplane mode state-transition callback
|
|
711
|
-
| `getLocationPermissionStatus()` | `PermissionStatus` | Check current location permission without prompting
|
|
712
|
-
| `requestLocationPermission()` | `Promise<PermissionStatus>` | Request location permission and return the resulting status
|
|
713
|
-
| `onPermissionStatusChange(callback)` | `void` | Register a callback that fires when location permission status changes
|
|
714
|
-
| `showLocalNotification(title, body)` | `void` | Show a local notification
|
|
715
|
-
| `updateForegroundNotification(title, body)` | `void` | Update the foreground service notification
|
|
716
|
-
| `destroy()` | `void` | Stop tracking and disconnect
|
|
736
|
+
| `isLocationServicesEnabled()` | `boolean` | Check if GPS/location is enabled on device |
|
|
737
|
+
| `openLocationSettings()` | `Promise<boolean>` | Prompt user to enable GPS. Resolves `true` if enabled, `false` if not |
|
|
738
|
+
| `onProviderStatusChange(callback)` | `void` | Register GPS/network provider status callback |
|
|
739
|
+
| `isAirplaneModeEnabled()` | `boolean` | Check if Airplane mode is active on Android |
|
|
740
|
+
| `onAirplaneModeChange(callback)` | `void` | Register Airplane mode state-transition callback |
|
|
741
|
+
| `getLocationPermissionStatus()` | `PermissionStatus` | Check current location permission without prompting |
|
|
742
|
+
| `requestLocationPermission()` | `Promise<PermissionStatus>` | Request location permission and return the resulting status |
|
|
743
|
+
| `onPermissionStatusChange(callback)` | `void` | Register a callback that fires when location permission status changes |
|
|
744
|
+
| `showLocalNotification(title, body)` | `void` | Show a local notification |
|
|
745
|
+
| `updateForegroundNotification(title, body)` | `void` | Update the foreground service notification |
|
|
746
|
+
| `destroy()` | `void` | Stop tracking and disconnect |
|
|
717
747
|
|
|
718
748
|
### Utility Exports
|
|
719
749
|
|
|
@@ -34,7 +34,7 @@ class LocationEngine(private val context: Context) {
|
|
|
34
34
|
val isTracking: Boolean get() = tracking
|
|
35
35
|
|
|
36
36
|
@SuppressLint("MissingPermission")
|
|
37
|
-
fun start(config: LocationConfig) {
|
|
37
|
+
fun start(config: LocationConfig): Boolean {
|
|
38
38
|
if (tracking) {
|
|
39
39
|
locationCallback?.let { fusedClient.removeLocationUpdates(it) }
|
|
40
40
|
}
|
|
@@ -59,9 +59,25 @@ class LocationEngine(private val context: Context) {
|
|
|
59
59
|
Log.d(TAG, "onLocationAvailability — isLocationAvailable=${availability.isLocationAvailable}")
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
return try {
|
|
63
|
+
fusedClient.requestLocationUpdates(
|
|
64
|
+
request, locationCallback!!, Looper.getMainLooper())
|
|
65
|
+
tracking = true
|
|
66
|
+
true
|
|
67
|
+
} catch (e: SecurityException) {
|
|
68
|
+
// Permission was revoked between our caller's check and this call, or
|
|
69
|
+
// the FusedLocationProvider is otherwise refusing access. Fail closed
|
|
70
|
+
// instead of crashing the process.
|
|
71
|
+
Log.w(TAG, "requestLocationUpdates refused — permission missing: ${e.message}")
|
|
72
|
+
locationCallback = null
|
|
73
|
+
tracking = false
|
|
74
|
+
false
|
|
75
|
+
} catch (e: Exception) {
|
|
76
|
+
Log.e(TAG, "requestLocationUpdates failed: ${e.message}")
|
|
77
|
+
locationCallback = null
|
|
78
|
+
tracking = false
|
|
79
|
+
false
|
|
80
|
+
}
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
fun stop() {
|
|
@@ -72,13 +88,18 @@ class LocationEngine(private val context: Context) {
|
|
|
72
88
|
|
|
73
89
|
@SuppressLint("MissingPermission")
|
|
74
90
|
fun getCurrentLocation(callback: (LocationData?) -> Unit) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
try {
|
|
92
|
+
fusedClient.lastLocation.addOnSuccessListener { location ->
|
|
93
|
+
if (location != null) {
|
|
94
|
+
callback(locationToData(location))
|
|
95
|
+
} else {
|
|
96
|
+
callback(null)
|
|
97
|
+
}
|
|
98
|
+
}.addOnFailureListener {
|
|
79
99
|
callback(null)
|
|
80
100
|
}
|
|
81
|
-
}
|
|
101
|
+
} catch (e: SecurityException) {
|
|
102
|
+
Log.w(TAG, "getCurrentLocation refused — permission missing: ${e.message}")
|
|
82
103
|
callback(null)
|
|
83
104
|
}
|
|
84
105
|
}
|
package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/NitroLocationTracking.kt
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
package com.margelo.nitro.nitrolocationtracking
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
3
5
|
import android.content.pm.PackageManager
|
|
4
6
|
import android.util.Log
|
|
5
7
|
import androidx.core.app.ActivityCompat
|
|
6
8
|
import androidx.core.content.ContextCompat
|
|
7
9
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
10
|
+
import com.facebook.react.bridge.BaseActivityEventListener
|
|
8
11
|
import com.facebook.react.bridge.ReactContext
|
|
9
12
|
import com.margelo.nitro.NitroModules
|
|
10
13
|
import com.margelo.nitro.core.Promise
|
|
14
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
11
15
|
import kotlin.coroutines.resume
|
|
12
16
|
import kotlin.coroutines.resumeWithException
|
|
13
17
|
import kotlin.coroutines.suspendCoroutine
|
|
@@ -18,6 +22,7 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
18
22
|
companion object {
|
|
19
23
|
private const val TAG = "NitroLocationTracking"
|
|
20
24
|
private const val PERMISSION_REQUEST_CODE = 9001
|
|
25
|
+
private const val GPS_RESOLUTION_REQUEST_CODE = 9002
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
private var locationEngine: LocationEngine? = null
|
|
@@ -61,6 +66,26 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
61
66
|
airplaneModeMonitor = AirplaneModeMonitor(context)
|
|
62
67
|
locationEngine?.dbWriter = dbWriter
|
|
63
68
|
connectionManager.dbWriter = dbWriter
|
|
69
|
+
|
|
70
|
+
// Always watch for permission revocation so we can proactively tear
|
|
71
|
+
// down tracking + the foreground service before the OS kills us for
|
|
72
|
+
// holding a location-type FGS without the matching permission.
|
|
73
|
+
permissionStatusMonitor?.setInternalCallback { status ->
|
|
74
|
+
if (status == PermissionStatus.DENIED || status == PermissionStatus.RESTRICTED) {
|
|
75
|
+
Log.w(TAG, "Location permission revoked — stopping tracking and foreground service")
|
|
76
|
+
try {
|
|
77
|
+
locationEngine?.stop()
|
|
78
|
+
} catch (e: Exception) {
|
|
79
|
+
Log.w(TAG, "Error stopping location engine: ${e.message}")
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
notificationService?.stopForegroundService()
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
Log.w(TAG, "Error stopping foreground service: ${e.message}")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
Log.d(TAG, "Components initialized successfully")
|
|
65
90
|
return true
|
|
66
91
|
}
|
|
@@ -81,6 +106,20 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
81
106
|
Log.e(TAG, "startTracking failed — could not initialize components")
|
|
82
107
|
return
|
|
83
108
|
}
|
|
109
|
+
|
|
110
|
+
// Permission guard. Starting a foreground service of type `location`
|
|
111
|
+
// without holding ACCESS_FINE_LOCATION throws SecurityException on
|
|
112
|
+
// Android 14+. And even on older versions, requestLocationUpdates
|
|
113
|
+
// will throw SecurityException. Fail closed and notify JS instead
|
|
114
|
+
// of crashing the process.
|
|
115
|
+
val permissionStatus = getLocationPermissionStatus()
|
|
116
|
+
if (permissionStatus == PermissionStatus.DENIED ||
|
|
117
|
+
permissionStatus == PermissionStatus.RESTRICTED) {
|
|
118
|
+
Log.w(TAG, "startTracking aborted — location permission is $permissionStatus")
|
|
119
|
+
permissionStatusCallback?.invoke(permissionStatus)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
84
123
|
val engine = locationEngine ?: return
|
|
85
124
|
engine.onLocation = { data ->
|
|
86
125
|
locationCallback?.invoke(data)
|
|
@@ -88,7 +127,14 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
88
127
|
engine.onMotionChange = { isMoving ->
|
|
89
128
|
motionCallback?.invoke(isMoving)
|
|
90
129
|
}
|
|
91
|
-
engine.start(config)
|
|
130
|
+
val started = engine.start(config)
|
|
131
|
+
if (!started) {
|
|
132
|
+
// engine.start() already logged the reason. Don't start the FGS
|
|
133
|
+
// if tracking itself could not be started — the FGS would just
|
|
134
|
+
// be killed immediately by the OS.
|
|
135
|
+
Log.w(TAG, "startTracking aborted — location engine refused to start")
|
|
136
|
+
return
|
|
137
|
+
}
|
|
92
138
|
|
|
93
139
|
try {
|
|
94
140
|
notificationService?.startForegroundService(
|
|
@@ -97,6 +143,12 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
97
143
|
)
|
|
98
144
|
} catch (e: SecurityException) {
|
|
99
145
|
Log.w(TAG, "Could not start foreground service — missing runtime permissions: ${e.message}")
|
|
146
|
+
// Roll back the tracking session so we don't leak a location
|
|
147
|
+
// request with no owning foreground service.
|
|
148
|
+
try { engine.stop() } catch (_: Exception) {}
|
|
149
|
+
} catch (e: Exception) {
|
|
150
|
+
Log.w(TAG, "Could not start foreground service: ${e.message}")
|
|
151
|
+
try { engine.stop() } catch (_: Exception) {}
|
|
100
152
|
}
|
|
101
153
|
}
|
|
102
154
|
|
|
@@ -367,41 +419,96 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
367
419
|
}
|
|
368
420
|
}
|
|
369
421
|
|
|
370
|
-
override fun openLocationSettings(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
422
|
+
override fun openLocationSettings(): Promise<Boolean> {
|
|
423
|
+
return Promise.async {
|
|
424
|
+
suspendCoroutine { cont ->
|
|
425
|
+
val context = NitroModules.applicationContext
|
|
426
|
+
if (context == null) {
|
|
427
|
+
cont.resume(false)
|
|
428
|
+
return@suspendCoroutine
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
val reactContext = context as? ReactContext
|
|
432
|
+
val activity = reactContext?.currentActivity
|
|
433
|
+
if (reactContext == null || activity == null) {
|
|
434
|
+
Log.w(TAG, "openLocationSettings — no current Activity, using fallback")
|
|
435
|
+
openLocationSettingsFallback(context)
|
|
436
|
+
cont.resume(isLocationServicesEnabled())
|
|
437
|
+
return@suspendCoroutine
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Use a high-accuracy location request for the settings check.
|
|
441
|
+
// These values are internal to the SettingsClient call — they do
|
|
442
|
+
// not affect the tracking configuration.
|
|
443
|
+
val locationRequest = com.google.android.gms.location.LocationRequest.Builder(
|
|
444
|
+
com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY,
|
|
445
|
+
10_000L
|
|
446
|
+
).build()
|
|
447
|
+
|
|
448
|
+
val settingsRequest = com.google.android.gms.location.LocationSettingsRequest.Builder()
|
|
449
|
+
.addLocationRequest(locationRequest)
|
|
450
|
+
.setAlwaysShow(true)
|
|
451
|
+
.build()
|
|
452
|
+
|
|
453
|
+
val client = com.google.android.gms.location.LocationServices.getSettingsClient(context)
|
|
454
|
+
val task = client.checkLocationSettings(settingsRequest)
|
|
455
|
+
|
|
456
|
+
// Guard against double-resume — the continuation may otherwise
|
|
457
|
+
// be resumed by both the success listener and the activity
|
|
458
|
+
// result listener in rare race conditions.
|
|
459
|
+
val resumed = AtomicBoolean(false)
|
|
460
|
+
fun safeResume(value: Boolean) {
|
|
461
|
+
if (resumed.compareAndSet(false, true)) {
|
|
462
|
+
cont.resume(value)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
task.addOnSuccessListener {
|
|
467
|
+
// Location settings are already satisfied — GPS is on.
|
|
468
|
+
safeResume(true)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
task.addOnFailureListener { exception ->
|
|
472
|
+
if (exception is com.google.android.gms.common.api.ResolvableApiException) {
|
|
473
|
+
// Register the activity result listener BEFORE showing
|
|
474
|
+
// the dialog so we cannot miss the result.
|
|
475
|
+
val listener = object : BaseActivityEventListener() {
|
|
476
|
+
override fun onActivityResult(
|
|
477
|
+
activity: Activity,
|
|
478
|
+
requestCode: Int,
|
|
479
|
+
resultCode: Int,
|
|
480
|
+
data: Intent?
|
|
481
|
+
) {
|
|
482
|
+
if (requestCode != GPS_RESOLUTION_REQUEST_CODE) return
|
|
483
|
+
reactContext.removeActivityEventListener(this)
|
|
484
|
+
// RESULT_OK = user tapped "OK" in the dialog and GPS is now on.
|
|
485
|
+
// RESULT_CANCELED = user tapped "No thanks" or dismissed.
|
|
486
|
+
val enabled = resultCode == Activity.RESULT_OK
|
|
487
|
+
safeResume(enabled)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
reactContext.addActivityEventListener(listener)
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
exception.startResolutionForResult(
|
|
494
|
+
activity, GPS_RESOLUTION_REQUEST_CODE
|
|
495
|
+
)
|
|
496
|
+
} catch (e: Exception) {
|
|
497
|
+
Log.w(TAG, "Failed to show resolution dialog: ${e.message}")
|
|
498
|
+
reactContext.removeActivityEventListener(listener)
|
|
499
|
+
openLocationSettingsFallback(context)
|
|
500
|
+
safeResume(isLocationServicesEnabled())
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
// Not resolvable (e.g. SETTINGS_CHANGE_UNAVAILABLE on an
|
|
504
|
+
// airplane-mode-locked device) — fall back to the system
|
|
505
|
+
// settings screen.
|
|
506
|
+
Log.w(TAG, "Location settings not resolvable: ${exception.message}")
|
|
397
507
|
openLocationSettingsFallback(context)
|
|
508
|
+
safeResume(isLocationServicesEnabled())
|
|
398
509
|
}
|
|
399
|
-
} else {
|
|
400
|
-
openLocationSettingsFallback(context)
|
|
401
510
|
}
|
|
402
511
|
}
|
|
403
|
-
} else {
|
|
404
|
-
openLocationSettingsFallback(context)
|
|
405
512
|
}
|
|
406
513
|
}
|
|
407
514
|
|
|
@@ -416,7 +523,7 @@ class NitroLocationTracking : HybridNitroLocationTrackingSpec() {
|
|
|
416
523
|
}
|
|
417
524
|
|
|
418
525
|
// === Device State Monitoring ===
|
|
419
|
-
|
|
526
|
+
|
|
420
527
|
override fun isAirplaneModeEnabled(): Boolean {
|
|
421
528
|
ensureInitialized()
|
|
422
529
|
return airplaneModeMonitor?.isAirplaneModeEnabled() ?: false
|
package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/PermissionStatusMonitor.kt
CHANGED
|
@@ -25,7 +25,9 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
private var callback: ((PermissionStatus) -> Unit)? = null
|
|
28
|
+
private var internalCallback: ((PermissionStatus) -> Unit)? = null
|
|
28
29
|
private var lastStatus: PermissionStatus? = null
|
|
30
|
+
private var observerRegistered = false
|
|
29
31
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
30
32
|
|
|
31
33
|
private val lifecycleObserver = object : DefaultLifecycleObserver {
|
|
@@ -36,8 +38,25 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
36
38
|
|
|
37
39
|
fun setCallback(callback: (PermissionStatus) -> Unit) {
|
|
38
40
|
this.callback = callback
|
|
41
|
+
ensureObserverRegistered()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Internal callback used by the native module to react to permission loss
|
|
46
|
+
* (e.g. auto-stop tracking) independently of any JS listener. Registering
|
|
47
|
+
* this also starts the lifecycle observer, so native cleanup works even
|
|
48
|
+
* when the user has not called onPermissionStatusChange() from JS.
|
|
49
|
+
*/
|
|
50
|
+
fun setInternalCallback(callback: (PermissionStatus) -> Unit) {
|
|
51
|
+
this.internalCallback = callback
|
|
52
|
+
ensureObserverRegistered()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun ensureObserverRegistered() {
|
|
56
|
+
if (observerRegistered) return
|
|
39
57
|
// Capture the current status so we only fire on actual changes
|
|
40
58
|
lastStatus = getCurrentPermissionStatus()
|
|
59
|
+
observerRegistered = true
|
|
41
60
|
|
|
42
61
|
// addObserver MUST be called on the main thread
|
|
43
62
|
mainHandler.post {
|
|
@@ -46,6 +65,7 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
46
65
|
Log.d(TAG, "Registered lifecycle observer for permission changes")
|
|
47
66
|
} catch (e: Exception) {
|
|
48
67
|
Log.e(TAG, "Failed to register lifecycle observer: ${e.message}")
|
|
68
|
+
observerRegistered = false
|
|
49
69
|
}
|
|
50
70
|
}
|
|
51
71
|
}
|
|
@@ -55,6 +75,10 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
55
75
|
if (current != lastStatus) {
|
|
56
76
|
Log.d(TAG, "Permission status changed: $lastStatus -> $current")
|
|
57
77
|
lastStatus = current
|
|
78
|
+
// Native cleanup first, JS notification second — so by the time JS
|
|
79
|
+
// hears about the denial, the native side has already released
|
|
80
|
+
// resources (tracking session, foreground service).
|
|
81
|
+
internalCallback?.invoke(current)
|
|
58
82
|
callback?.invoke(current)
|
|
59
83
|
}
|
|
60
84
|
}
|
|
@@ -86,7 +110,9 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
86
110
|
ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
|
|
87
111
|
} catch (_: Exception) {}
|
|
88
112
|
}
|
|
113
|
+
observerRegistered = false
|
|
89
114
|
callback = null
|
|
115
|
+
internalCallback = null
|
|
90
116
|
lastStatus = null
|
|
91
117
|
}
|
|
92
118
|
}
|
package/ios/LocationEngine.swift
CHANGED
|
@@ -238,6 +238,17 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
// Auto-stop if permission was revoked while tracking. Without this the
|
|
242
|
+
// engine stays flagged as tracking but Core Location silently delivers
|
|
243
|
+
// nothing, leaving the app in an inconsistent state.
|
|
244
|
+
if authStatus == .denied || authStatus == .restricted {
|
|
245
|
+
if tracking {
|
|
246
|
+
locationManager.stopUpdatingLocation()
|
|
247
|
+
tracking = false
|
|
248
|
+
pendingStartAfterPermission = false
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
241
252
|
// Notify JS about permission status change (deduplicated)
|
|
242
253
|
let permStatus = Self.mapAuthStatus(authStatus)
|
|
243
254
|
if permStatus != lastPermissionStatus {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import CoreLocation
|
|
3
3
|
import NitroModules
|
|
4
|
+
import UIKit
|
|
4
5
|
|
|
5
6
|
class NitroLocationTracking: HybridNitroLocationTrackingSpec {
|
|
6
7
|
private let locationEngine = LocationEngine()
|
|
@@ -249,14 +250,60 @@ class NitroLocationTracking: HybridNitroLocationTrackingSpec {
|
|
|
249
250
|
locationEngine.permissionStatusCallback = callback
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
func openLocationSettings(
|
|
253
|
+
func openLocationSettings() throws -> Promise<Bool> {
|
|
254
|
+
let promise = Promise<Bool>()
|
|
255
|
+
|
|
256
|
+
// Guard against double-resolve — didBecomeActive can fire more than
|
|
257
|
+
// once while the user bounces between apps, and we also resolve from
|
|
258
|
+
// the open(url) completion handler on failure.
|
|
259
|
+
let resolvedLock = NSLock()
|
|
260
|
+
var hasResolved = false
|
|
261
|
+
func safeResolve(_ value: Bool) {
|
|
262
|
+
resolvedLock.lock()
|
|
263
|
+
defer { resolvedLock.unlock() }
|
|
264
|
+
if hasResolved { return }
|
|
265
|
+
hasResolved = true
|
|
266
|
+
promise.resolve(withResult: value)
|
|
267
|
+
}
|
|
268
|
+
|
|
253
269
|
DispatchQueue.main.async {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
270
|
+
guard let url = URL(string: UIApplication.openSettingsURLString),
|
|
271
|
+
UIApplication.shared.canOpenURL(url) else {
|
|
272
|
+
safeResolve(CLLocationManager.locationServicesEnabled())
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Register the observer BEFORE opening Settings so we never miss
|
|
277
|
+
// the return-to-foreground event.
|
|
278
|
+
var observer: NSObjectProtocol?
|
|
279
|
+
observer = NotificationCenter.default.addObserver(
|
|
280
|
+
forName: UIApplication.didBecomeActiveNotification,
|
|
281
|
+
object: nil,
|
|
282
|
+
queue: .main
|
|
283
|
+
) { _ in
|
|
284
|
+
if let obs = observer {
|
|
285
|
+
NotificationCenter.default.removeObserver(obs)
|
|
286
|
+
observer = nil
|
|
287
|
+
}
|
|
288
|
+
// Give iOS a short beat to refresh the location-services flag
|
|
289
|
+
// after the user toggles it in Settings.
|
|
290
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
291
|
+
safeResolve(CLLocationManager.locationServicesEnabled())
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
UIApplication.shared.open(url, options: [:]) { success in
|
|
296
|
+
if !success {
|
|
297
|
+
if let obs = observer {
|
|
298
|
+
NotificationCenter.default.removeObserver(obs)
|
|
299
|
+
observer = nil
|
|
300
|
+
}
|
|
301
|
+
safeResolve(CLLocationManager.locationServicesEnabled())
|
|
257
302
|
}
|
|
258
303
|
}
|
|
259
304
|
}
|
|
305
|
+
|
|
306
|
+
return promise
|
|
260
307
|
}
|
|
261
308
|
|
|
262
309
|
// MARK: - Device State Monitoring
|
|
@@ -101,7 +101,21 @@ export interface NitroLocationTracking extends HybridObject<{
|
|
|
101
101
|
getLocationPermissionStatus(): PermissionStatus;
|
|
102
102
|
requestLocationPermission(): Promise<PermissionStatus>;
|
|
103
103
|
onPermissionStatusChange(callback: PermissionStatusCallback): void;
|
|
104
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Prompt the user to enable device location (GPS).
|
|
106
|
+
*
|
|
107
|
+
* On Android this shows the native Google Play Services in-app resolution
|
|
108
|
+
* dialog ("For better experience, turn on device location…"). The promise
|
|
109
|
+
* resolves `true` if GPS is enabled (either because it already was, or
|
|
110
|
+
* because the user accepted the dialog), and `false` if the user declined
|
|
111
|
+
* or the dialog could not be shown.
|
|
112
|
+
*
|
|
113
|
+
* On iOS there is no in-app dialog for enabling location services, so this
|
|
114
|
+
* opens the app's Settings page. The promise resolves after the app
|
|
115
|
+
* returns to foreground: `true` if `locationServicesEnabled()` is now on,
|
|
116
|
+
* `false` otherwise.
|
|
117
|
+
*/
|
|
118
|
+
openLocationSettings(): Promise<boolean>;
|
|
105
119
|
isAirplaneModeEnabled(): boolean;
|
|
106
120
|
onAirplaneModeChange(callback: (isEnabled: boolean) => void): void;
|
|
107
121
|
getDistanceBetween(lat1: number, lon1: number, lat2: number, lon2: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NitroLocationTracking.nitro.d.ts","sourceRoot":"","sources":["../../../src/NitroLocationTracking.nitro.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"NitroLocationTracking.nitro.d.ts","sourceRoot":"","sources":["../../../src/NitroLocationTracking.nitro.ts"],"names":[],"mappings":"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"}
|
|
@@ -290,9 +290,21 @@ namespace margelo::nitro::nitrolocationtracking {
|
|
|
290
290
|
static const auto method = javaClassStatic()->getMethod<void(jni::alias_ref<JFunc_void_PermissionStatus::javaobject> /* callback */)>("onPermissionStatusChange_cxx");
|
|
291
291
|
method(_javaPart, JFunc_void_PermissionStatus_cxx::fromCpp(callback));
|
|
292
292
|
}
|
|
293
|
-
|
|
294
|
-
static const auto method = javaClassStatic()->getMethod<
|
|
295
|
-
method(_javaPart
|
|
293
|
+
std::shared_ptr<Promise<bool>> JHybridNitroLocationTrackingSpec::openLocationSettings() {
|
|
294
|
+
static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("openLocationSettings");
|
|
295
|
+
auto __result = method(_javaPart);
|
|
296
|
+
return [&]() {
|
|
297
|
+
auto __promise = Promise<bool>::create();
|
|
298
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
|
|
299
|
+
auto __result = jni::static_ref_cast<jni::JBoolean>(__boxedResult);
|
|
300
|
+
__promise->resolve(static_cast<bool>(__result->value()));
|
|
301
|
+
});
|
|
302
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
303
|
+
jni::JniException __jniError(__throwable);
|
|
304
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
305
|
+
});
|
|
306
|
+
return __promise;
|
|
307
|
+
}();
|
|
296
308
|
}
|
|
297
309
|
bool JHybridNitroLocationTrackingSpec::isAirplaneModeEnabled() {
|
|
298
310
|
static const auto method = javaClassStatic()->getMethod<jboolean()>("isAirplaneModeEnabled");
|
|
@@ -89,7 +89,7 @@ namespace margelo::nitro::nitrolocationtracking {
|
|
|
89
89
|
PermissionStatus getLocationPermissionStatus() override;
|
|
90
90
|
std::shared_ptr<Promise<PermissionStatus>> requestLocationPermission() override;
|
|
91
91
|
void onPermissionStatusChange(const std::function<void(PermissionStatus /* status */)>& callback) override;
|
|
92
|
-
|
|
92
|
+
std::shared_ptr<Promise<bool>> openLocationSettings() override;
|
|
93
93
|
bool isAirplaneModeEnabled() override;
|
|
94
94
|
void onAirplaneModeChange(const std::function<void(bool /* isEnabled */)>& callback) override;
|
|
95
95
|
double getDistanceBetween(double lat1, double lon1, double lat2, double lon2) override;
|
|
@@ -229,7 +229,7 @@ abstract class HybridNitroLocationTrackingSpec: HybridObject() {
|
|
|
229
229
|
|
|
230
230
|
@DoNotStrip
|
|
231
231
|
@Keep
|
|
232
|
-
abstract fun openLocationSettings(
|
|
232
|
+
abstract fun openLocationSettings(): Promise<Boolean>
|
|
233
233
|
|
|
234
234
|
@DoNotStrip
|
|
235
235
|
@Keep
|
|
@@ -330,11 +330,13 @@ namespace margelo::nitro::nitrolocationtracking {
|
|
|
330
330
|
std::rethrow_exception(__result.error());
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
-
inline
|
|
334
|
-
auto __result = _swiftPart.openLocationSettings(
|
|
333
|
+
inline std::shared_ptr<Promise<bool>> openLocationSettings() override {
|
|
334
|
+
auto __result = _swiftPart.openLocationSettings();
|
|
335
335
|
if (__result.hasError()) [[unlikely]] {
|
|
336
336
|
std::rethrow_exception(__result.error());
|
|
337
337
|
}
|
|
338
|
+
auto __value = std::move(__result.value());
|
|
339
|
+
return __value;
|
|
338
340
|
}
|
|
339
341
|
inline bool isAirplaneModeEnabled() override {
|
|
340
342
|
auto __result = _swiftPart.isAirplaneModeEnabled();
|
|
@@ -47,7 +47,7 @@ public protocol HybridNitroLocationTrackingSpec_protocol: HybridObject {
|
|
|
47
47
|
func getLocationPermissionStatus() throws -> PermissionStatus
|
|
48
48
|
func requestLocationPermission() throws -> Promise<PermissionStatus>
|
|
49
49
|
func onPermissionStatusChange(callback: @escaping (_ status: PermissionStatus) -> Void) throws -> Void
|
|
50
|
-
func openLocationSettings(
|
|
50
|
+
func openLocationSettings() throws -> Promise<Bool>
|
|
51
51
|
func isAirplaneModeEnabled() throws -> Bool
|
|
52
52
|
func onAirplaneModeChange(callback: @escaping (_ isEnabled: Bool) -> Void) throws -> Void
|
|
53
53
|
func getDistanceBetween(lat1: Double, lon1: Double, lat2: Double, lon2: Double) throws -> Double
|
|
@@ -576,13 +576,21 @@ open class HybridNitroLocationTrackingSpec_cxx {
|
|
|
576
576
|
}
|
|
577
577
|
|
|
578
578
|
@inline(__always)
|
|
579
|
-
public final func openLocationSettings(
|
|
579
|
+
public final func openLocationSettings() -> bridge.Result_std__shared_ptr_Promise_bool___ {
|
|
580
580
|
do {
|
|
581
|
-
try self.__implementation.openLocationSettings(
|
|
582
|
-
|
|
581
|
+
let __result = try self.__implementation.openLocationSettings()
|
|
582
|
+
let __resultCpp = { () -> bridge.std__shared_ptr_Promise_bool__ in
|
|
583
|
+
let __promise = bridge.create_std__shared_ptr_Promise_bool__()
|
|
584
|
+
let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_bool__(__promise)
|
|
585
|
+
__result
|
|
586
|
+
.then({ __result in __promiseHolder.resolve(__result) })
|
|
587
|
+
.catch({ __error in __promiseHolder.reject(__error.toCpp()) })
|
|
588
|
+
return __promise
|
|
589
|
+
}()
|
|
590
|
+
return bridge.create_Result_std__shared_ptr_Promise_bool___(__resultCpp)
|
|
583
591
|
} catch (let __error) {
|
|
584
592
|
let __exceptionPtr = __error.toCpp()
|
|
585
|
-
return bridge.
|
|
593
|
+
return bridge.create_Result_std__shared_ptr_Promise_bool___(__exceptionPtr)
|
|
586
594
|
}
|
|
587
595
|
}
|
|
588
596
|
|
|
@@ -35,8 +35,6 @@ namespace margelo::nitro::nitrolocationtracking { struct TripStats; }
|
|
|
35
35
|
namespace margelo::nitro::nitrolocationtracking { enum class LocationProviderStatus; }
|
|
36
36
|
// Forward declaration of `PermissionStatus` to properly resolve imports.
|
|
37
37
|
namespace margelo::nitro::nitrolocationtracking { enum class PermissionStatus; }
|
|
38
|
-
// Forward declaration of `AccuracyLevel` to properly resolve imports.
|
|
39
|
-
namespace margelo::nitro::nitrolocationtracking { enum class AccuracyLevel; }
|
|
40
38
|
|
|
41
39
|
#include "LocationConfig.hpp"
|
|
42
40
|
#include "LocationData.hpp"
|
|
@@ -52,7 +50,6 @@ namespace margelo::nitro::nitrolocationtracking { enum class AccuracyLevel; }
|
|
|
52
50
|
#include "TripStats.hpp"
|
|
53
51
|
#include "LocationProviderStatus.hpp"
|
|
54
52
|
#include "PermissionStatus.hpp"
|
|
55
|
-
#include "AccuracyLevel.hpp"
|
|
56
53
|
|
|
57
54
|
namespace margelo::nitro::nitrolocationtracking {
|
|
58
55
|
|
|
@@ -119,7 +116,7 @@ namespace margelo::nitro::nitrolocationtracking {
|
|
|
119
116
|
virtual PermissionStatus getLocationPermissionStatus() = 0;
|
|
120
117
|
virtual std::shared_ptr<Promise<PermissionStatus>> requestLocationPermission() = 0;
|
|
121
118
|
virtual void onPermissionStatusChange(const std::function<void(PermissionStatus /* status */)>& callback) = 0;
|
|
122
|
-
virtual
|
|
119
|
+
virtual std::shared_ptr<Promise<bool>> openLocationSettings() = 0;
|
|
123
120
|
virtual bool isAirplaneModeEnabled() = 0;
|
|
124
121
|
virtual void onAirplaneModeChange(const std::function<void(bool /* isEnabled */)>& callback) = 0;
|
|
125
122
|
virtual double getDistanceBetween(double lat1, double lon1, double lat2, double lon2) = 0;
|
package/package.json
CHANGED
|
@@ -149,7 +149,21 @@ export interface NitroLocationTracking
|
|
|
149
149
|
getLocationPermissionStatus(): PermissionStatus;
|
|
150
150
|
requestLocationPermission(): Promise<PermissionStatus>;
|
|
151
151
|
onPermissionStatusChange(callback: PermissionStatusCallback): void;
|
|
152
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Prompt the user to enable device location (GPS).
|
|
154
|
+
*
|
|
155
|
+
* On Android this shows the native Google Play Services in-app resolution
|
|
156
|
+
* dialog ("For better experience, turn on device location…"). The promise
|
|
157
|
+
* resolves `true` if GPS is enabled (either because it already was, or
|
|
158
|
+
* because the user accepted the dialog), and `false` if the user declined
|
|
159
|
+
* or the dialog could not be shown.
|
|
160
|
+
*
|
|
161
|
+
* On iOS there is no in-app dialog for enabling location services, so this
|
|
162
|
+
* opens the app's Settings page. The promise resolves after the app
|
|
163
|
+
* returns to foreground: `true` if `locationServicesEnabled()` is now on,
|
|
164
|
+
* `false` otherwise.
|
|
165
|
+
*/
|
|
166
|
+
openLocationSettings(): Promise<boolean>;
|
|
153
167
|
|
|
154
168
|
// === Device State Monitoring ===
|
|
155
169
|
isAirplaneModeEnabled(): boolean;
|