react-native-nitro-geolocation 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -1
- package/README.md +598 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +341 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +76 -5
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/RequestAuthorization.kt +164 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +228 -0
- package/ios/LocationManager.swift +529 -0
- package/ios/NitroGeolocation.swift +96 -2
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/c++/JAuthorizationLevelInternal.hpp +62 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
- package/nitrogen/generated/android/c++/JFunc_void_GeolocationError.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_GeolocationResponse.hpp +79 -0
- package/nitrogen/generated/android/c++/JGeolocationCoordinates.hpp +77 -0
- package/nitrogen/generated/android/c++/JGeolocationError.hpp +69 -0
- package/nitrogen/generated/android/c++/JGeolocationOptions.hpp +77 -0
- package/nitrogen/generated/android/c++/JGeolocationResponse.hpp +59 -0
- package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.cpp +98 -0
- package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JLocationProviderInternal.hpp +62 -0
- package/nitrogen/generated/android/c++/JRNConfigurationInternal.hpp +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AuthorizationLevelInternal.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_GeolocationError.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_GeolocationResponse.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationCoordinates.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationError.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationOptions.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationResponse.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationSpec.kt +87 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderInternal.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/RNConfigurationInternal.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/nitrogeolocationOnLoad.kt +35 -0
- package/nitrogen/generated/android/nitrogeolocation+autolinking.cmake +81 -0
- package/nitrogen/generated/android/nitrogeolocation+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +50 -0
- package/nitrogen/generated/android/nitrogeolocationOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/NitroGeolocation+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +56 -0
- package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +252 -0
- package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +67 -0
- package/nitrogen/generated/ios/NitroGeolocationAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroGeolocationAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.hpp +125 -0
- package/nitrogen/generated/ios/swift/AuthorizationLevelInternal.swift +44 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_GeolocationError.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_GeolocationResponse.swift +47 -0
- package/nitrogen/generated/ios/swift/GeolocationCoordinates.swift +149 -0
- package/nitrogen/generated/ios/swift/GeolocationError.swift +79 -0
- package/nitrogen/generated/ios/swift/GeolocationOptions.swift +185 -0
- package/nitrogen/generated/ios/swift/GeolocationResponse.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec_cxx.swift +236 -0
- package/nitrogen/generated/ios/swift/LocationProviderInternal.swift +44 -0
- package/nitrogen/generated/ios/swift/RNConfigurationInternal.swift +104 -0
- package/nitrogen/generated/shared/c++/AuthorizationLevelInternal.hpp +80 -0
- package/nitrogen/generated/shared/c++/GeolocationCoordinates.hpp +91 -0
- package/nitrogen/generated/shared/c++/GeolocationError.hpp +83 -0
- package/nitrogen/generated/shared/c++/GeolocationOptions.hpp +91 -0
- package/nitrogen/generated/shared/c++/GeolocationResponse.hpp +72 -0
- package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.hpp +79 -0
- package/nitrogen/generated/shared/c++/LocationProviderInternal.hpp +80 -0
- package/nitrogen/generated/shared/c++/RNConfigurationInternal.hpp +84 -0
- package/package.json +34 -10
- package/src/NitroGeolocation.nitro.ts +38 -3
- package/src/NitroGeolocationModule.ts +5 -0
- package/src/clearWatch.ts +13 -0
- package/src/getCurrentPosition.ts +14 -0
- package/src/index.tsx +32 -7
- package/src/requestAuthorization.ts +9 -0
- package/src/setRNConfiguration.ts +22 -0
- package/src/stopObserving.ts +12 -0
- package/src/types.ts +43 -0
- package/src/watchPosition.ts +26 -0
- package/nitro.json +0 -17
- package/turbo.json +0 -42
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrogeolocation
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.location.Location
|
|
7
|
+
import android.location.LocationListener
|
|
8
|
+
import android.location.LocationManager
|
|
9
|
+
import android.os.Bundle
|
|
10
|
+
import android.os.Handler
|
|
11
|
+
import android.os.Looper
|
|
12
|
+
import android.util.Log
|
|
13
|
+
import androidx.core.content.ContextCompat
|
|
14
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
15
|
+
|
|
16
|
+
class GetCurrentPosition(private val reactContext: ReactApplicationContext) {
|
|
17
|
+
|
|
18
|
+
fun execute(
|
|
19
|
+
success: (position: GeolocationResponse) -> Unit,
|
|
20
|
+
error: ((error: GeolocationError) -> Unit)?,
|
|
21
|
+
options: GeolocationOptions?
|
|
22
|
+
) {
|
|
23
|
+
val locationManager =
|
|
24
|
+
reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
|
|
25
|
+
if (locationManager == null) {
|
|
26
|
+
Log.e(TAG, "LocationManager is not available")
|
|
27
|
+
error?.invoke(createError(POSITION_UNAVAILABLE, "LocationManager is not available"))
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
val opts = parseOptions(options)
|
|
32
|
+
val provider = getValidProvider(locationManager, opts.enableHighAccuracy)
|
|
33
|
+
|
|
34
|
+
if (provider == null) {
|
|
35
|
+
Log.e(TAG, "No location provider available")
|
|
36
|
+
error?.invoke(createError(POSITION_UNAVAILABLE, "No location provider available"))
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
val lastKnownLocation = locationManager.getLastKnownLocation(provider)
|
|
42
|
+
|
|
43
|
+
// Check if cached location is fresh enough
|
|
44
|
+
if (lastKnownLocation != null && isCachedLocationValid(lastKnownLocation, opts)) {
|
|
45
|
+
success(locationToPosition(lastKnownLocation))
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If maximumAge is Infinity and we have a last known location, use it
|
|
50
|
+
if (lastKnownLocation != null && opts.maximumAge == Double.POSITIVE_INFINITY) {
|
|
51
|
+
success(locationToPosition(lastKnownLocation))
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Request fresh location
|
|
56
|
+
requestFreshLocation(locationManager, provider, opts, success, error, lastKnownLocation)
|
|
57
|
+
} catch (e: SecurityException) {
|
|
58
|
+
Log.e(TAG, "Security exception: ${e.message}")
|
|
59
|
+
error?.invoke(
|
|
60
|
+
createError(PERMISSION_DENIED, "Location permission denied: ${e.message}")
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ===== Helper Functions =====
|
|
66
|
+
|
|
67
|
+
private fun parseOptions(options: GeolocationOptions?): ParsedOptions {
|
|
68
|
+
return ParsedOptions(
|
|
69
|
+
timeout = options?.timeout ?: DEFAULT_TIMEOUT,
|
|
70
|
+
maximumAge = options?.maximumAge ?: DEFAULT_MAXIMUM_AGE,
|
|
71
|
+
enableHighAccuracy = options?.enableHighAccuracy ?: false
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun isCachedLocationValid(location: Location, options: ParsedOptions): Boolean {
|
|
76
|
+
val age = System.currentTimeMillis() - location.time
|
|
77
|
+
return age < options.maximumAge
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fun getValidProvider(locationManager: LocationManager, highAccuracy: Boolean): String? {
|
|
81
|
+
val preferredProvider =
|
|
82
|
+
if (highAccuracy) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
|
|
83
|
+
val fallbackProvider =
|
|
84
|
+
if (highAccuracy) LocationManager.NETWORK_PROVIDER else LocationManager.GPS_PROVIDER
|
|
85
|
+
|
|
86
|
+
return when {
|
|
87
|
+
isProviderValid(locationManager, preferredProvider) -> preferredProvider
|
|
88
|
+
isProviderValid(locationManager, fallbackProvider) -> fallbackProvider
|
|
89
|
+
else -> null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private fun isProviderValid(locationManager: LocationManager, provider: String): Boolean {
|
|
94
|
+
if (!locationManager.isProviderEnabled(provider)) return false
|
|
95
|
+
|
|
96
|
+
val permission =
|
|
97
|
+
if (provider == LocationManager.GPS_PROVIDER)
|
|
98
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
99
|
+
else Manifest.permission.ACCESS_COARSE_LOCATION
|
|
100
|
+
|
|
101
|
+
return ContextCompat.checkSelfPermission(reactContext, permission) ==
|
|
102
|
+
PackageManager.PERMISSION_GRANTED
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun requestFreshLocation(
|
|
106
|
+
locationManager: LocationManager,
|
|
107
|
+
provider: String,
|
|
108
|
+
options: ParsedOptions,
|
|
109
|
+
success: (GeolocationResponse) -> Unit,
|
|
110
|
+
error: ((GeolocationError) -> Unit)?,
|
|
111
|
+
fallbackLocation: Location?
|
|
112
|
+
) {
|
|
113
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
|
114
|
+
// Android 11+ supports getCurrentLocation
|
|
115
|
+
requestCurrentLocationModern(
|
|
116
|
+
locationManager,
|
|
117
|
+
provider,
|
|
118
|
+
options,
|
|
119
|
+
success,
|
|
120
|
+
error,
|
|
121
|
+
fallbackLocation
|
|
122
|
+
)
|
|
123
|
+
} else {
|
|
124
|
+
// Fallback to requestLocationUpdates
|
|
125
|
+
requestCurrentLocationLegacy(
|
|
126
|
+
locationManager,
|
|
127
|
+
provider,
|
|
128
|
+
options,
|
|
129
|
+
success,
|
|
130
|
+
error,
|
|
131
|
+
fallbackLocation
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.R)
|
|
137
|
+
private fun requestCurrentLocationModern(
|
|
138
|
+
locationManager: LocationManager,
|
|
139
|
+
provider: String,
|
|
140
|
+
options: ParsedOptions,
|
|
141
|
+
success: (GeolocationResponse) -> Unit,
|
|
142
|
+
error: ((GeolocationError) -> Unit)?,
|
|
143
|
+
fallbackLocation: Location?
|
|
144
|
+
) {
|
|
145
|
+
val handler = Handler(Looper.getMainLooper())
|
|
146
|
+
val cancellationSignal = android.os.CancellationSignal()
|
|
147
|
+
|
|
148
|
+
val timeoutRunnable = Runnable {
|
|
149
|
+
cancellationSignal.cancel()
|
|
150
|
+
error?.invoke(createError(TIMEOUT, "Location request timed out"))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
locationManager.getCurrentLocation(
|
|
155
|
+
provider,
|
|
156
|
+
cancellationSignal,
|
|
157
|
+
{ runnable -> handler.post(runnable) }
|
|
158
|
+
) { location ->
|
|
159
|
+
handler.removeCallbacks(timeoutRunnable)
|
|
160
|
+
|
|
161
|
+
val bestLocation = selectBestLocation(location, fallbackLocation)
|
|
162
|
+
|
|
163
|
+
if (bestLocation != null) {
|
|
164
|
+
success(locationToPosition(bestLocation))
|
|
165
|
+
} else {
|
|
166
|
+
Log.e(TAG, "No location available")
|
|
167
|
+
error?.invoke(createError(POSITION_UNAVAILABLE, "Unable to get location"))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
handler.postDelayed(timeoutRunnable, options.timeout.toLong())
|
|
171
|
+
} catch (e: SecurityException) {
|
|
172
|
+
Log.e(TAG, "SecurityException: ${e.message}")
|
|
173
|
+
error?.invoke(createError(PERMISSION_DENIED, "Permission denied: ${e.message}"))
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fun selectBestLocation(newLocation: Location?, fallbackLocation: Location?): Location? {
|
|
178
|
+
return when {
|
|
179
|
+
newLocation == null -> fallbackLocation
|
|
180
|
+
fallbackLocation == null -> newLocation
|
|
181
|
+
isBetterLocation(newLocation, fallbackLocation) -> newLocation
|
|
182
|
+
else -> fallbackLocation
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private fun requestCurrentLocationLegacy(
|
|
187
|
+
locationManager: LocationManager,
|
|
188
|
+
provider: String,
|
|
189
|
+
options: ParsedOptions,
|
|
190
|
+
success: (GeolocationResponse) -> Unit,
|
|
191
|
+
error: ((GeolocationError) -> Unit)?,
|
|
192
|
+
fallbackLocation: Location?
|
|
193
|
+
) {
|
|
194
|
+
val handler = Handler(Looper.getMainLooper())
|
|
195
|
+
var isResolved = false
|
|
196
|
+
var oldLocation = fallbackLocation
|
|
197
|
+
lateinit var listener: LocationListener
|
|
198
|
+
|
|
199
|
+
val timeoutRunnable = Runnable {
|
|
200
|
+
if (!isResolved) {
|
|
201
|
+
isResolved = true
|
|
202
|
+
locationManager.removeUpdates(listener)
|
|
203
|
+
error?.invoke(createError(TIMEOUT, "Location request timed out"))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
listener =
|
|
208
|
+
object : LocationListener {
|
|
209
|
+
override fun onLocationChanged(location: Location) {
|
|
210
|
+
synchronized(this) {
|
|
211
|
+
if (!isResolved) {
|
|
212
|
+
val bestLocation = selectBestLocation(location, oldLocation)
|
|
213
|
+
if (bestLocation == location) {
|
|
214
|
+
isResolved = true
|
|
215
|
+
handler.removeCallbacks(timeoutRunnable)
|
|
216
|
+
locationManager.removeUpdates(this)
|
|
217
|
+
success(locationToPosition(location))
|
|
218
|
+
}
|
|
219
|
+
oldLocation = location
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
|
225
|
+
override fun onProviderEnabled(provider: String) {}
|
|
226
|
+
override fun onProviderDisabled(provider: String) {}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
locationManager.requestLocationUpdates(
|
|
231
|
+
provider,
|
|
232
|
+
100,
|
|
233
|
+
1f,
|
|
234
|
+
listener,
|
|
235
|
+
Looper.getMainLooper()
|
|
236
|
+
)
|
|
237
|
+
handler.postDelayed(timeoutRunnable, options.timeout.toLong())
|
|
238
|
+
} catch (e: SecurityException) {
|
|
239
|
+
Log.e(TAG, "SecurityException in requestLocationUpdates: ${e.message}")
|
|
240
|
+
error?.invoke(createError(PERMISSION_DENIED, "Permission denied: ${e.message}"))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Determines whether one Location reading is better than the current Location fix Taken from
|
|
246
|
+
* Android Examples: https://developer.android.com/guide/topics/location/strategies.html
|
|
247
|
+
*/
|
|
248
|
+
private fun isBetterLocation(location: Location, currentBestLocation: Location?): Boolean {
|
|
249
|
+
if (currentBestLocation == null) {
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
val timeDelta = location.time - currentBestLocation.time
|
|
254
|
+
val isSignificantlyNewer = timeDelta > TWO_MINUTES
|
|
255
|
+
val isSignificantlyOlder = timeDelta < -TWO_MINUTES
|
|
256
|
+
val isNewer = timeDelta > 0
|
|
257
|
+
|
|
258
|
+
if (isSignificantlyNewer) {
|
|
259
|
+
return true
|
|
260
|
+
} else if (isSignificantlyOlder) {
|
|
261
|
+
return false
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
val accuracyDelta = (location.accuracy - currentBestLocation.accuracy).toInt()
|
|
265
|
+
val isLessAccurate = accuracyDelta > 0
|
|
266
|
+
val isMoreAccurate = accuracyDelta < 0
|
|
267
|
+
val isSignificantlyLessAccurate = accuracyDelta > 200
|
|
268
|
+
|
|
269
|
+
val isFromSameProvider = isSameProvider(location.provider, currentBestLocation.provider)
|
|
270
|
+
|
|
271
|
+
return when {
|
|
272
|
+
isMoreAccurate -> true
|
|
273
|
+
isNewer && !isLessAccurate -> true
|
|
274
|
+
isNewer && !isSignificantlyLessAccurate && isFromSameProvider -> true
|
|
275
|
+
else -> false
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private fun isSameProvider(provider1: String?, provider2: String?): Boolean {
|
|
280
|
+
if (provider1 == null) {
|
|
281
|
+
return provider2 == null
|
|
282
|
+
}
|
|
283
|
+
return provider1 == provider2
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ===== Data Conversion =====
|
|
287
|
+
|
|
288
|
+
private fun locationToPosition(location: Location): GeolocationResponse {
|
|
289
|
+
return GeolocationResponse(
|
|
290
|
+
coords =
|
|
291
|
+
GeolocationCoordinates(
|
|
292
|
+
latitude = location.latitude,
|
|
293
|
+
longitude = location.longitude,
|
|
294
|
+
altitude = if (location.hasAltitude()) location.altitude else null,
|
|
295
|
+
accuracy = location.accuracy.toDouble(),
|
|
296
|
+
altitudeAccuracy =
|
|
297
|
+
if (android.os.Build.VERSION.SDK_INT >=
|
|
298
|
+
android.os.Build.VERSION_CODES.O &&
|
|
299
|
+
location.hasVerticalAccuracy()
|
|
300
|
+
)
|
|
301
|
+
location.verticalAccuracyMeters.toDouble()
|
|
302
|
+
else null,
|
|
303
|
+
heading =
|
|
304
|
+
if (location.hasBearing()) location.bearing.toDouble()
|
|
305
|
+
else null,
|
|
306
|
+
speed = if (location.hasSpeed()) location.speed.toDouble() else null
|
|
307
|
+
),
|
|
308
|
+
timestamp = location.time.toDouble()
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private fun createError(code: Int, message: String): GeolocationError {
|
|
313
|
+
return GeolocationError(
|
|
314
|
+
code = code.toDouble(),
|
|
315
|
+
message = message,
|
|
316
|
+
PERMISSION_DENIED = PERMISSION_DENIED.toDouble(),
|
|
317
|
+
POSITION_UNAVAILABLE = POSITION_UNAVAILABLE.toDouble(),
|
|
318
|
+
TIMEOUT = TIMEOUT.toDouble()
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ===== Data Classes =====
|
|
323
|
+
|
|
324
|
+
private data class ParsedOptions(
|
|
325
|
+
val timeout: Double,
|
|
326
|
+
val maximumAge: Double,
|
|
327
|
+
val enableHighAccuracy: Boolean
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
companion object {
|
|
331
|
+
private const val TAG = "GetCurrentPosition"
|
|
332
|
+
const val PERMISSION_DENIED = 1
|
|
333
|
+
const val POSITION_UNAVAILABLE = 2
|
|
334
|
+
const val TIMEOUT = 3
|
|
335
|
+
|
|
336
|
+
const val DEFAULT_TIMEOUT = 10 * 60 * 1000.0 // 10 minutes
|
|
337
|
+
const val DEFAULT_MAXIMUM_AGE = Double.POSITIVE_INFINITY
|
|
338
|
+
|
|
339
|
+
private const val TWO_MINUTES = 1000 * 60 * 2L // 2 minutes in milliseconds
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -1,10 +1,81 @@
|
|
|
1
1
|
package com.margelo.nitro.nitrogeolocation
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.margelo.nitro.NitroModules
|
|
4
6
|
|
|
5
7
|
@DoNotStrip
|
|
6
|
-
class NitroGeolocation
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
class NitroGeolocation(
|
|
9
|
+
private val reactContext: ReactApplicationContext = NitroModules.applicationContext!!
|
|
10
|
+
) : HybridNitroGeolocationSpec() {
|
|
11
|
+
private var configuration: RNConfigurationInternal =
|
|
12
|
+
RNConfigurationInternal(
|
|
13
|
+
skipPermissionRequests = false,
|
|
14
|
+
authorizationLevel = null,
|
|
15
|
+
enableBackgroundLocationUpdates = null,
|
|
16
|
+
locationProvider = null
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
private val requestAuthorizationHandler by lazy {
|
|
20
|
+
RequestAuthorization(
|
|
21
|
+
reactContext = reactContext,
|
|
22
|
+
onPermissionResult = { result, success, error ->
|
|
23
|
+
when (result) {
|
|
24
|
+
is PermissionResult.Granted -> success?.invoke()
|
|
25
|
+
is PermissionResult.Denied ->
|
|
26
|
+
error?.invoke(
|
|
27
|
+
createPermissionError(
|
|
28
|
+
"Location permission was not granted."
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private val watchPositionHandler by lazy { WatchPosition(reactContext) }
|
|
37
|
+
|
|
38
|
+
override fun setRNConfiguration(config: RNConfigurationInternal) {
|
|
39
|
+
configuration = config
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun requestAuthorization(
|
|
43
|
+
success: (() -> Unit)?,
|
|
44
|
+
error: ((error: GeolocationError) -> Unit)?
|
|
45
|
+
) {
|
|
46
|
+
requestAuthorizationHandler.execute(success, error)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun getCurrentPosition(
|
|
50
|
+
success: (position: GeolocationResponse) -> Unit,
|
|
51
|
+
error: ((error: GeolocationError) -> Unit)?,
|
|
52
|
+
options: GeolocationOptions?
|
|
53
|
+
) {
|
|
54
|
+
GetCurrentPosition(reactContext).execute(success, error, options)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun watchPosition(
|
|
58
|
+
success: (position: GeolocationResponse) -> Unit,
|
|
59
|
+
error: ((error: GeolocationError) -> Unit)?,
|
|
60
|
+
options: GeolocationOptions?
|
|
61
|
+
): Double {
|
|
62
|
+
return watchPositionHandler.watch(success, error, options).toDouble()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun clearWatch(watchId: Double) {
|
|
66
|
+
watchPositionHandler.clearWatch(watchId.toInt())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun stopObserving() {
|
|
70
|
+
watchPositionHandler.stopObserving()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun createPermissionError(message: String) =
|
|
74
|
+
GeolocationError(
|
|
75
|
+
code = RequestAuthorization.PERMISSION_DENIED.toDouble(),
|
|
76
|
+
message = message,
|
|
77
|
+
PERMISSION_DENIED = RequestAuthorization.PERMISSION_DENIED.toDouble(),
|
|
78
|
+
POSITION_UNAVAILABLE = RequestAuthorization.POSITION_UNAVAILABLE.toDouble(),
|
|
79
|
+
TIMEOUT = RequestAuthorization.TIMEOUT.toDouble()
|
|
80
|
+
)
|
|
10
81
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
package com.margelo.nitro.nitrogeolocation
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import androidx.core.app.ActivityCompat
|
|
7
|
+
import androidx.core.content.ContextCompat
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
10
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
11
|
+
|
|
12
|
+
// ===== Data Models =====
|
|
13
|
+
sealed class PermissionState {
|
|
14
|
+
object LegacyAndroid : PermissionState()
|
|
15
|
+
object AlreadyGranted : PermissionState()
|
|
16
|
+
object NeedsRequest : PermissionState()
|
|
17
|
+
data class Error(val message: String) : PermissionState()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
sealed class PermissionResult {
|
|
21
|
+
object Granted : PermissionResult()
|
|
22
|
+
object Denied : PermissionResult()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class RequestAuthorization(
|
|
26
|
+
private val reactContext: ReactApplicationContext,
|
|
27
|
+
private val onPermissionResult:
|
|
28
|
+
(PermissionResult, (() -> Unit)?, ((GeolocationError) -> Unit)?) -> Unit
|
|
29
|
+
) {
|
|
30
|
+
private var pendingAuthSuccess: (() -> Unit)? = null
|
|
31
|
+
private var pendingAuthError: ((error: GeolocationError) -> Unit)? = null
|
|
32
|
+
|
|
33
|
+
fun execute(success: (() -> Unit)?, error: ((error: GeolocationError) -> Unit)?) {
|
|
34
|
+
val state = determinePermissionState()
|
|
35
|
+
executePermissionAction(state, success, error)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ===== State Determination (Pure Functions) =====
|
|
39
|
+
private fun determinePermissionState(): PermissionState =
|
|
40
|
+
when {
|
|
41
|
+
isLegacyAndroid() -> PermissionState.LegacyAndroid
|
|
42
|
+
else -> determineModernAndroidState()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private fun determineModernAndroidState(): PermissionState =
|
|
46
|
+
when {
|
|
47
|
+
hasLocationPermission(reactContext) -> PermissionState.AlreadyGranted
|
|
48
|
+
reactContext.currentActivity == null ->
|
|
49
|
+
PermissionState.Error("Current activity is null")
|
|
50
|
+
else -> PermissionState.NeedsRequest
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private fun isLegacyAndroid(): Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|
|
54
|
+
|
|
55
|
+
private fun hasLocationPermission(context: ReactApplicationContext): Boolean =
|
|
56
|
+
LOCATION_PERMISSIONS.any { permission ->
|
|
57
|
+
ContextCompat.checkSelfPermission(context, permission) ==
|
|
58
|
+
PackageManager.PERMISSION_GRANTED
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private fun determinePermissionResult(
|
|
62
|
+
permissions: Array<String>,
|
|
63
|
+
grantResults: IntArray
|
|
64
|
+
): PermissionResult {
|
|
65
|
+
val hasGrantedPermission =
|
|
66
|
+
permissions.indices.any { i ->
|
|
67
|
+
isLocationPermission(permissions[i]) &&
|
|
68
|
+
grantResults[i] == PackageManager.PERMISSION_GRANTED
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return when {
|
|
72
|
+
hasGrantedPermission -> PermissionResult.Granted
|
|
73
|
+
else -> PermissionResult.Denied
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private fun isLocationPermission(permission: String): Boolean =
|
|
78
|
+
permission in LOCATION_PERMISSIONS
|
|
79
|
+
|
|
80
|
+
// ===== Actions (Side Effects) =====
|
|
81
|
+
private fun executePermissionAction(
|
|
82
|
+
state: PermissionState,
|
|
83
|
+
success: (() -> Unit)?,
|
|
84
|
+
error: ((error: GeolocationError) -> Unit)?
|
|
85
|
+
) {
|
|
86
|
+
when (state) {
|
|
87
|
+
is PermissionState.LegacyAndroid -> success?.invoke()
|
|
88
|
+
is PermissionState.AlreadyGranted -> success?.invoke()
|
|
89
|
+
is PermissionState.NeedsRequest -> showPermissionDialog(success, error)
|
|
90
|
+
is PermissionState.Error -> error?.invoke(createPermissionError(state.message))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private fun showPermissionDialog(
|
|
95
|
+
success: (() -> Unit)?,
|
|
96
|
+
error: ((error: GeolocationError) -> Unit)?
|
|
97
|
+
) {
|
|
98
|
+
pendingAuthSuccess = success
|
|
99
|
+
pendingAuthError = error
|
|
100
|
+
|
|
101
|
+
reactContext.currentActivity?.let { activity ->
|
|
102
|
+
requestPermissionsFromActivity(activity)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fun requestPermissionsFromActivity(activity: android.app.Activity) {
|
|
107
|
+
val permissions = LOCATION_PERMISSIONS.toTypedArray()
|
|
108
|
+
|
|
109
|
+
when (val permissionAware = activity as? PermissionAwareActivity) {
|
|
110
|
+
null ->
|
|
111
|
+
ActivityCompat.requestPermissions(
|
|
112
|
+
activity,
|
|
113
|
+
permissions,
|
|
114
|
+
PERMISSION_REQUEST_CODE
|
|
115
|
+
)
|
|
116
|
+
else ->
|
|
117
|
+
permissionAware.requestPermissions(
|
|
118
|
+
permissions,
|
|
119
|
+
PERMISSION_REQUEST_CODE,
|
|
120
|
+
createPermissionListener()
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private fun createPermissionListener() =
|
|
126
|
+
PermissionListener { requestCode, permissions, grantResults ->
|
|
127
|
+
when (requestCode) {
|
|
128
|
+
PERMISSION_REQUEST_CODE -> {
|
|
129
|
+
val result = determinePermissionResult(permissions, grantResults)
|
|
130
|
+
onPermissionResult(result, pendingAuthSuccess, pendingAuthError)
|
|
131
|
+
clearPendingCallbacks()
|
|
132
|
+
true
|
|
133
|
+
}
|
|
134
|
+
else -> false
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private fun clearPendingCallbacks() {
|
|
139
|
+
pendingAuthSuccess = null
|
|
140
|
+
pendingAuthError = null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private fun createPermissionError(message: String) =
|
|
144
|
+
GeolocationError(
|
|
145
|
+
code = PERMISSION_DENIED.toDouble(),
|
|
146
|
+
message = message,
|
|
147
|
+
PERMISSION_DENIED = PERMISSION_DENIED.toDouble(),
|
|
148
|
+
POSITION_UNAVAILABLE = POSITION_UNAVAILABLE.toDouble(),
|
|
149
|
+
TIMEOUT = TIMEOUT.toDouble()
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
companion object {
|
|
153
|
+
const val PERMISSION_DENIED = 1
|
|
154
|
+
const val POSITION_UNAVAILABLE = 2
|
|
155
|
+
const val TIMEOUT = 3
|
|
156
|
+
const val PERMISSION_REQUEST_CODE = 2025
|
|
157
|
+
|
|
158
|
+
private val LOCATION_PERMISSIONS =
|
|
159
|
+
listOf(
|
|
160
|
+
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
161
|
+
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|