react-native-nitro-geolocation 0.0.1 → 0.1.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.
- package/LICENSE +4 -1
- package/README.md +318 -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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
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.Looper
|
|
11
|
+
import android.util.Log
|
|
12
|
+
import androidx.core.content.ContextCompat
|
|
13
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
14
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
15
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
16
|
+
|
|
17
|
+
class WatchPosition(private val reactContext: ReactApplicationContext) {
|
|
18
|
+
|
|
19
|
+
private val watchCallbacks = ConcurrentHashMap<Int, WatchCallback>()
|
|
20
|
+
private val watchIdGenerator = AtomicInteger(0)
|
|
21
|
+
private var locationListener: LocationListener? = null
|
|
22
|
+
private var watchedProvider: String? = null
|
|
23
|
+
private var currentOptions: GeolocationOptions? = null
|
|
24
|
+
|
|
25
|
+
data class WatchCallback(
|
|
26
|
+
val success: (GeolocationResponse) -> Unit,
|
|
27
|
+
val error: ((GeolocationError) -> Unit)?,
|
|
28
|
+
val options: GeolocationOptions?
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
fun watch(
|
|
32
|
+
success: (GeolocationResponse) -> Unit,
|
|
33
|
+
error: ((GeolocationError) -> Unit)?,
|
|
34
|
+
options: GeolocationOptions?
|
|
35
|
+
): Int {
|
|
36
|
+
val watchId = watchIdGenerator.incrementAndGet()
|
|
37
|
+
watchCallbacks[watchId] = WatchCallback(success, error, options)
|
|
38
|
+
|
|
39
|
+
// Start observing if this is the first watch
|
|
40
|
+
if (watchCallbacks.size == 1) {
|
|
41
|
+
startObserving(options)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return watchId
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fun clearWatch(watchId: Int) {
|
|
48
|
+
watchCallbacks.remove(watchId)
|
|
49
|
+
|
|
50
|
+
// Stop observing if no more watches
|
|
51
|
+
if (watchCallbacks.isEmpty()) {
|
|
52
|
+
stopObserving()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun stopObserving() {
|
|
57
|
+
val locationManager =
|
|
58
|
+
reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
|
|
59
|
+
|
|
60
|
+
locationListener?.let { listener -> locationManager?.removeUpdates(listener) }
|
|
61
|
+
|
|
62
|
+
locationListener = null
|
|
63
|
+
watchedProvider = null
|
|
64
|
+
currentOptions = null
|
|
65
|
+
watchCallbacks.clear()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private fun startObserving(options: GeolocationOptions?) {
|
|
69
|
+
val locationManager =
|
|
70
|
+
reactContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
|
|
71
|
+
|
|
72
|
+
if (locationManager == null) {
|
|
73
|
+
Log.e(TAG, "LocationManager is not available")
|
|
74
|
+
emitErrorToAll(
|
|
75
|
+
createError(
|
|
76
|
+
GetCurrentPosition.POSITION_UNAVAILABLE,
|
|
77
|
+
"LocationManager is not available"
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
val opts = parseOptions(options)
|
|
84
|
+
val provider = getValidProvider(locationManager, opts.enableHighAccuracy)
|
|
85
|
+
|
|
86
|
+
if (provider == null) {
|
|
87
|
+
Log.e(TAG, "No location provider available")
|
|
88
|
+
emitErrorToAll(
|
|
89
|
+
createError(
|
|
90
|
+
GetCurrentPosition.POSITION_UNAVAILABLE,
|
|
91
|
+
"No location provider available"
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If already watching with the same provider, don't restart
|
|
98
|
+
if (provider == watchedProvider) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Remove old listener if exists
|
|
104
|
+
locationListener?.let { locationManager.removeUpdates(it) }
|
|
105
|
+
|
|
106
|
+
// Create new listener
|
|
107
|
+
val listener =
|
|
108
|
+
object : LocationListener {
|
|
109
|
+
override fun onLocationChanged(location: Location) {
|
|
110
|
+
val position = locationToPosition(location)
|
|
111
|
+
// Call all watch callbacks
|
|
112
|
+
watchCallbacks.values.forEach { callback -> callback.success(position) }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override fun onStatusChanged(
|
|
116
|
+
provider: String?,
|
|
117
|
+
status: Int,
|
|
118
|
+
extras: Bundle?
|
|
119
|
+
) {}
|
|
120
|
+
override fun onProviderEnabled(provider: String) {}
|
|
121
|
+
override fun onProviderDisabled(provider: String) {}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
locationManager.requestLocationUpdates(
|
|
125
|
+
provider,
|
|
126
|
+
opts.interval.toLong(),
|
|
127
|
+
opts.distanceFilter.toFloat(),
|
|
128
|
+
listener,
|
|
129
|
+
Looper.getMainLooper()
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
locationListener = listener
|
|
133
|
+
watchedProvider = provider
|
|
134
|
+
currentOptions = options
|
|
135
|
+
} catch (e: SecurityException) {
|
|
136
|
+
Log.e(TAG, "SecurityException: ${e.message}")
|
|
137
|
+
emitErrorToAll(
|
|
138
|
+
createError(
|
|
139
|
+
GetCurrentPosition.PERMISSION_DENIED,
|
|
140
|
+
"Location permission denied: ${e.message}"
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private fun emitErrorToAll(error: GeolocationError) {
|
|
147
|
+
watchCallbacks.values.forEach { callback -> callback.error?.invoke(error) }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun parseOptions(options: GeolocationOptions?): ParsedOptions {
|
|
151
|
+
return ParsedOptions(
|
|
152
|
+
interval = options?.interval ?: DEFAULT_INTERVAL,
|
|
153
|
+
distanceFilter = options?.distanceFilter ?: DEFAULT_DISTANCE_FILTER,
|
|
154
|
+
enableHighAccuracy = options?.enableHighAccuracy ?: false
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun getValidProvider(locationManager: LocationManager, highAccuracy: Boolean): String? {
|
|
159
|
+
val preferredProvider =
|
|
160
|
+
if (highAccuracy) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
|
|
161
|
+
val fallbackProvider =
|
|
162
|
+
if (highAccuracy) LocationManager.NETWORK_PROVIDER else LocationManager.GPS_PROVIDER
|
|
163
|
+
|
|
164
|
+
return when {
|
|
165
|
+
isProviderValid(locationManager, preferredProvider) -> preferredProvider
|
|
166
|
+
isProviderValid(locationManager, fallbackProvider) -> fallbackProvider
|
|
167
|
+
else -> null
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun isProviderValid(locationManager: LocationManager, provider: String): Boolean {
|
|
172
|
+
if (!locationManager.isProviderEnabled(provider)) return false
|
|
173
|
+
|
|
174
|
+
val permission =
|
|
175
|
+
if (provider == LocationManager.GPS_PROVIDER)
|
|
176
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
177
|
+
else Manifest.permission.ACCESS_COARSE_LOCATION
|
|
178
|
+
|
|
179
|
+
return ContextCompat.checkSelfPermission(reactContext, permission) ==
|
|
180
|
+
PackageManager.PERMISSION_GRANTED
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private fun locationToPosition(location: Location): GeolocationResponse {
|
|
184
|
+
return GeolocationResponse(
|
|
185
|
+
coords =
|
|
186
|
+
GeolocationCoordinates(
|
|
187
|
+
latitude = location.latitude,
|
|
188
|
+
longitude = location.longitude,
|
|
189
|
+
altitude = if (location.hasAltitude()) location.altitude else null,
|
|
190
|
+
accuracy = location.accuracy.toDouble(),
|
|
191
|
+
altitudeAccuracy =
|
|
192
|
+
if (android.os.Build.VERSION.SDK_INT >=
|
|
193
|
+
android.os.Build.VERSION_CODES.O &&
|
|
194
|
+
location.hasVerticalAccuracy()
|
|
195
|
+
)
|
|
196
|
+
location.verticalAccuracyMeters.toDouble()
|
|
197
|
+
else null,
|
|
198
|
+
heading =
|
|
199
|
+
if (location.hasBearing()) location.bearing.toDouble()
|
|
200
|
+
else null,
|
|
201
|
+
speed = if (location.hasSpeed()) location.speed.toDouble() else null
|
|
202
|
+
),
|
|
203
|
+
timestamp = location.time.toDouble()
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun createError(code: Int, message: String): GeolocationError {
|
|
208
|
+
return GeolocationError(
|
|
209
|
+
code = code.toDouble(),
|
|
210
|
+
message = message,
|
|
211
|
+
PERMISSION_DENIED = GetCurrentPosition.PERMISSION_DENIED.toDouble(),
|
|
212
|
+
POSITION_UNAVAILABLE = GetCurrentPosition.POSITION_UNAVAILABLE.toDouble(),
|
|
213
|
+
TIMEOUT = GetCurrentPosition.TIMEOUT.toDouble()
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private data class ParsedOptions(
|
|
218
|
+
val interval: Double,
|
|
219
|
+
val distanceFilter: Double,
|
|
220
|
+
val enableHighAccuracy: Boolean
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
companion object {
|
|
224
|
+
private const val TAG = "WatchPosition"
|
|
225
|
+
const val DEFAULT_INTERVAL = 1000.0 // 1 second
|
|
226
|
+
const val DEFAULT_DISTANCE_FILTER = 100.0 // 100 meters
|
|
227
|
+
}
|
|
228
|
+
}
|