react-native-nitro-geolocation 1.1.3 → 1.2.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/README.md +316 -0
- package/android/build.gradle +6 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidAccuracy.kt +105 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidHeadingManager.kt +313 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidLocationSettings.kt +313 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +46 -45
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/LocationMetadata.kt +26 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/LocationValues.kt +31 -0
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +1025 -140
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocationCompat.kt +11 -11
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/RequestAuthorization.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +46 -45
- package/ios/CLLocation+GeolocationMetadata.swift +32 -0
- package/ios/LocationManager.swift +205 -51
- package/ios/NitroGeolocation.swift +949 -110
- package/ios/NitroGeolocationCompat.swift +7 -7
- package/nitrogen/generated/android/c++/JAccuracyAuthorization.hpp +61 -0
- package/nitrogen/generated/android/c++/JAndroidAccuracyPreset.hpp +64 -0
- package/nitrogen/generated/android/c++/JAndroidGranularity.hpp +61 -0
- package/nitrogen/generated/android/c++/{JRNConfigurationInternal.hpp → JCompatGeolocationConfigurationInternal.hpp} +10 -10
- package/nitrogen/generated/android/c++/{JGeolocationError.hpp → JCompatGeolocationError.hpp} +10 -10
- package/nitrogen/generated/android/c++/JCompatGeolocationOptions.hpp +105 -0
- package/nitrogen/generated/android/c++/JCompatGeolocationResponse.hpp +67 -0
- package/nitrogen/generated/android/c++/JFunc_void_AccuracyAuthorization.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_CompatGeolocationError.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_CompatGeolocationResponse.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_GeolocationResponse.hpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_Heading.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_LocationProviderStatus.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_PermissionStatus.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_GeocodedLocation_.hpp +97 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_ReverseGeocodedAddress_.hpp +98 -0
- package/nitrogen/generated/android/c++/JGeocodedLocation.hpp +65 -0
- package/nitrogen/generated/android/c++/JGeocodingCoordinates.hpp +61 -0
- package/nitrogen/generated/android/c++/{JModernGeolocationConfiguration.hpp → JGeolocationConfiguration.hpp} +10 -10
- package/nitrogen/generated/android/c++/JGeolocationResponse.hpp +13 -3
- package/nitrogen/generated/android/c++/JHeading.hpp +69 -0
- package/nitrogen/generated/android/c++/JHeadingOptions.hpp +57 -0
- package/nitrogen/generated/android/c++/JHybridNitroGeolocationCompatSpec.cpp +46 -30
- package/nitrogen/generated/android/c++/JHybridNitroGeolocationCompatSpec.hpp +4 -4
- package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.cpp +169 -33
- package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.hpp +14 -3
- package/nitrogen/generated/android/c++/JIOSAccuracyPreset.hpp +73 -0
- package/nitrogen/generated/android/c++/JIOSActivityType.hpp +67 -0
- package/nitrogen/generated/android/c++/JLocationAccuracyOptions.hpp +65 -0
- package/nitrogen/generated/android/c++/JLocationAvailability.hpp +62 -0
- package/nitrogen/generated/android/c++/JLocationProviderStatus.hpp +77 -0
- package/nitrogen/generated/android/c++/JLocationProviderUsed.hpp +67 -0
- package/nitrogen/generated/android/c++/JLocationRequestOptions.hpp +49 -3
- package/nitrogen/generated/android/c++/{JGeolocationOptions.hpp → JLocationSettingsOptions.hpp} +28 -22
- package/nitrogen/generated/android/c++/JReverseGeocodedAddress.hpp +82 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AccuracyAuthorization.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidAccuracyPreset.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidGranularity.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{RNConfigurationInternal.kt → CompatGeolocationConfigurationInternal.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{GeolocationError.kt → CompatGeolocationError.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/CompatGeolocationOptions.kt +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/CompatGeolocationResponse.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_AccuracyAuthorization.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{Func_void_GeolocationError.kt → Func_void_CompatGeolocationError.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_CompatGeolocationResponse.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_Heading.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_LocationProviderStatus.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_PermissionStatus.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_std__vector_GeocodedLocation_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_std__vector_ReverseGeocodedAddress_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeocodedLocation.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeocodingCoordinates.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{ModernGeolocationConfiguration.kt → GeolocationConfiguration.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationResponse.kt +9 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Heading.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HeadingOptions.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationCompatSpec.kt +7 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationSpec.kt +92 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSAccuracyPreset.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSActivityType.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationAccuracyOptions.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationAvailability.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderStatus.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderUsed.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationRequestOptions.kt +30 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{GeolocationOptions.kt → LocationSettingsOptions.kt} +11 -11
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/ReverseGeocodedAddress.kt +56 -0
- package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +18 -4
- package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +76 -12
- package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +519 -77
- package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +61 -12
- package/nitrogen/generated/ios/c++/HybridNitroGeolocationCompatSpecSwift.hpp +28 -16
- package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.hpp +131 -13
- package/nitrogen/generated/ios/swift/AccuracyAuthorization.swift +44 -0
- package/nitrogen/generated/ios/swift/AndroidAccuracyPreset.swift +48 -0
- package/nitrogen/generated/ios/swift/AndroidGranularity.swift +44 -0
- package/nitrogen/generated/ios/swift/{RNConfigurationInternal.swift → CompatGeolocationConfigurationInternal.swift} +5 -5
- package/nitrogen/generated/ios/swift/{GeolocationError.swift → CompatGeolocationError.swift} +5 -5
- package/nitrogen/generated/ios/swift/CompatGeolocationOptions.swift +208 -0
- package/nitrogen/generated/ios/swift/CompatGeolocationResponse.swift +34 -0
- package/nitrogen/generated/ios/swift/Func_void_AccuracyAuthorization.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CompatGeolocationError.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CompatGeolocationResponse.swift +46 -0
- package/nitrogen/generated/ios/swift/{Func_void_GeolocationError.swift → Func_void_Heading.swift} +11 -11
- package/nitrogen/generated/ios/swift/Func_void_LocationAvailability.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_LocationProviderStatus.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_GeocodedLocation_.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_ReverseGeocodedAddress_.swift +46 -0
- package/nitrogen/generated/ios/swift/GeocodedLocation.swift +52 -0
- package/nitrogen/generated/ios/swift/GeocodingCoordinates.swift +34 -0
- package/nitrogen/generated/ios/swift/{ModernGeolocationConfiguration.swift → GeolocationConfiguration.swift} +5 -5
- package/nitrogen/generated/ios/swift/GeolocationResponse.swift +31 -2
- package/nitrogen/generated/ios/swift/Heading.swift +70 -0
- package/nitrogen/generated/ios/swift/HeadingOptions.swift +42 -0
- package/nitrogen/generated/ios/swift/HybridNitroGeolocationCompatSpec.swift +4 -4
- package/nitrogen/generated/ios/swift/HybridNitroGeolocationCompatSpec_cxx.swift +28 -28
- package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec.swift +14 -3
- package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec_cxx.swift +318 -15
- package/nitrogen/generated/ios/swift/IOSAccuracyPreset.swift +60 -0
- package/nitrogen/generated/ios/swift/IOSActivityType.swift +52 -0
- package/nitrogen/generated/ios/swift/LocationAccuracyOptions.swift +46 -0
- package/nitrogen/generated/ios/swift/LocationAvailability.swift +47 -0
- package/nitrogen/generated/ios/swift/LocationProviderStatus.swift +106 -0
- package/nitrogen/generated/ios/swift/LocationProviderUsed.swift +52 -0
- package/nitrogen/generated/ios/swift/LocationRequestOptions.swift +142 -1
- package/nitrogen/generated/ios/swift/{GeolocationOptions.swift → LocationSettingsOptions.swift} +39 -46
- package/nitrogen/generated/ios/swift/ReverseGeocodedAddress.swift +150 -0
- package/nitrogen/generated/shared/c++/AccuracyAuthorization.hpp +80 -0
- package/nitrogen/generated/shared/c++/AndroidAccuracyPreset.hpp +84 -0
- package/nitrogen/generated/shared/c++/AndroidGranularity.hpp +80 -0
- package/nitrogen/generated/shared/c++/{RNConfigurationInternal.hpp → CompatGeolocationConfigurationInternal.hpp} +11 -11
- package/nitrogen/generated/shared/c++/{GeolocationError.hpp → CompatGeolocationError.hpp} +11 -11
- package/nitrogen/generated/shared/c++/CompatGeolocationOptions.hpp +128 -0
- package/nitrogen/generated/shared/c++/CompatGeolocationResponse.hpp +88 -0
- package/nitrogen/generated/shared/c++/GeocodedLocation.hpp +91 -0
- package/nitrogen/generated/shared/c++/GeocodingCoordinates.hpp +87 -0
- package/nitrogen/generated/shared/c++/{ModernGeolocationConfiguration.hpp → GeolocationConfiguration.hpp} +11 -11
- package/nitrogen/generated/shared/c++/GeolocationResponse.hpp +14 -2
- package/nitrogen/generated/shared/c++/Heading.hpp +95 -0
- package/nitrogen/generated/shared/c++/HeadingOptions.hpp +83 -0
- package/nitrogen/generated/shared/c++/HybridNitroGeolocationCompatSpec.hpp +16 -16
- package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.cpp +11 -0
- package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.hpp +51 -12
- package/nitrogen/generated/shared/c++/IOSAccuracyPreset.hpp +96 -0
- package/nitrogen/generated/shared/c++/IOSActivityType.hpp +88 -0
- package/nitrogen/generated/shared/c++/LocationAccuracyOptions.hpp +92 -0
- package/nitrogen/generated/shared/c++/LocationAvailability.hpp +88 -0
- package/nitrogen/generated/shared/c++/LocationProviderStatus.hpp +103 -0
- package/nitrogen/generated/shared/c++/LocationProviderUsed.hpp +88 -0
- package/nitrogen/generated/shared/c++/LocationRequestOptions.hpp +47 -3
- package/nitrogen/generated/shared/c++/{GeolocationOptions.hpp → LocationSettingsOptions.hpp} +26 -24
- package/nitrogen/generated/shared/c++/ReverseGeocodedAddress.hpp +108 -0
- package/package.json +1 -1
- package/src/NitroGeolocation.nitro.ts +291 -17
- package/src/NitroGeolocationCompat.nitro.ts +12 -12
- package/src/api/geocode.ts +18 -0
- package/src/api/getAccuracyAuthorization.ts +12 -0
- package/src/api/getCurrentPosition.ts +5 -3
- package/src/api/getHeading.ts +13 -0
- package/src/api/getLastKnownPosition.ts +28 -0
- package/src/api/getLocationAvailability.ts +11 -0
- package/src/api/getProviderStatus.ts +16 -0
- package/src/api/hasServicesEnabled.ts +13 -0
- package/src/api/index.ts +11 -0
- package/src/api/requestLocationSettings.ts +29 -0
- package/src/api/requestPermission.ts +3 -1
- package/src/api/requestTemporaryFullAccuracy.ts +21 -0
- package/src/api/reverseGeocode.ts +23 -0
- package/src/api/setConfiguration.ts +8 -4
- package/src/api/watchHeading.ts +19 -0
- package/src/api/watchPosition.ts +2 -2
- package/src/compat/getCurrentPosition.ts +7 -7
- package/src/compat/index.tsx +5 -5
- package/src/compat/requestAuthorization.ts +2 -2
- package/src/compat/setRNConfiguration.ts +7 -5
- package/src/compat/watchPosition.ts +7 -7
- package/src/devtools/getCurrentPosition.ts +5 -3
- package/src/devtools/index.ts +1 -1
- package/src/devtools/watchPosition.ts +6 -7
- package/src/hooks/useWatchPosition.ts +2 -2
- package/src/index.tsx +35 -6
- package/src/publicTypes.ts +96 -0
- package/src/types.ts +113 -37
- package/src/utils/errors.test.ts +65 -0
- package/src/utils/errors.ts +45 -18
- package/src/utils/index.ts +2 -2
- package/src/utils/provider.test.ts +172 -1
- package/src/utils/provider.ts +50 -5
- package/nitrogen/generated/android/c++/JFunc_void_GeolocationError.hpp +0 -78
|
@@ -3,6 +3,8 @@ package com.margelo.nitro.nitrogeolocation
|
|
|
3
3
|
import android.Manifest
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.content.pm.PackageManager
|
|
6
|
+
import android.location.Address
|
|
7
|
+
import android.location.Geocoder
|
|
6
8
|
import android.location.Location
|
|
7
9
|
import android.location.LocationListener
|
|
8
10
|
import android.location.LocationManager as AndroidLocationManager
|
|
@@ -15,19 +17,19 @@ import androidx.core.app.ActivityCompat
|
|
|
15
17
|
import androidx.core.content.ContextCompat
|
|
16
18
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
17
19
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
18
|
-
import com.
|
|
20
|
+
import com.google.android.gms.common.ConnectionResult
|
|
21
|
+
import com.google.android.gms.common.GoogleApiAvailability
|
|
22
|
+
import com.google.android.gms.location.LocationCallback
|
|
23
|
+
import com.google.android.gms.location.LocationRequest as GmsLocationRequest
|
|
24
|
+
import com.google.android.gms.location.LocationResult
|
|
25
|
+
import com.google.android.gms.location.LocationServices
|
|
19
26
|
import com.margelo.nitro.NitroModules
|
|
27
|
+
import com.margelo.nitro.core.Promise
|
|
28
|
+
import java.io.IOException
|
|
29
|
+
import java.util.Locale
|
|
20
30
|
import java.util.UUID
|
|
21
31
|
import java.util.concurrent.ConcurrentHashMap
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Exception wrapper for LocationError struct.
|
|
25
|
-
* Nitrogen-generated LocationError doesn't extend Exception,
|
|
26
|
-
* so we need to wrap it for Promise.reject().
|
|
27
|
-
*/
|
|
28
|
-
private class GeolocationErrorException(
|
|
29
|
-
val locationError: LocationError
|
|
30
|
-
) : Exception(locationError.message)
|
|
32
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
31
33
|
|
|
32
34
|
private const val NO_LOCATION_PROVIDER_AVAILABLE_MESSAGE = "No location provider available"
|
|
33
35
|
private const val NO_APPROXIMATE_LOCATION_PROVIDER_AVAILABLE_MESSAGE =
|
|
@@ -35,10 +37,10 @@ private const val NO_APPROXIMATE_LOCATION_PROVIDER_AVAILABLE_MESSAGE =
|
|
|
35
37
|
"ACCESS_COARSE_LOCATION is granted, but no enabled coarse-compatible provider is available."
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
40
|
+
* Geolocation implementation for Android.
|
|
39
41
|
*
|
|
40
42
|
* Key features:
|
|
41
|
-
* -
|
|
43
|
+
* - Callback-based native permission and getCurrentPosition for structured errors
|
|
42
44
|
* - Token-based watch subscriptions (first-class functions!)
|
|
43
45
|
* - WatchPositionResult discriminated union
|
|
44
46
|
* - Automatic subscription management
|
|
@@ -53,10 +55,15 @@ class NitroGeolocation(
|
|
|
53
55
|
private data class ParsedOptions(
|
|
54
56
|
val timeout: Double,
|
|
55
57
|
val maximumAge: Double,
|
|
56
|
-
val
|
|
58
|
+
val androidAccuracy: AndroidAccuracyResolution,
|
|
57
59
|
val interval: Double,
|
|
58
60
|
val fastestInterval: Double,
|
|
59
|
-
val distanceFilter: Double
|
|
61
|
+
val distanceFilter: Double,
|
|
62
|
+
val granularity: AndroidGranularity,
|
|
63
|
+
val waitForAccurateLocation: Boolean,
|
|
64
|
+
val maxUpdateAge: Double?,
|
|
65
|
+
val maxUpdateDelay: Double,
|
|
66
|
+
val maxUpdates: Int?
|
|
60
67
|
) {
|
|
61
68
|
companion object {
|
|
62
69
|
private const val DEFAULT_TIMEOUT = 10.0 * 60 * 1000 // 10 minutes in ms
|
|
@@ -64,17 +71,42 @@ class NitroGeolocation(
|
|
|
64
71
|
private const val DEFAULT_INTERVAL = 1000.0
|
|
65
72
|
private const val DEFAULT_FASTEST_INTERVAL = 100.0
|
|
66
73
|
private const val DEFAULT_DISTANCE_FILTER = 0.0
|
|
74
|
+
private const val DEFAULT_MAX_UPDATE_DELAY = 0.0
|
|
75
|
+
|
|
76
|
+
fun parse(
|
|
77
|
+
options: LocationRequestOptions?,
|
|
78
|
+
defaultMaximumAge: Double = DEFAULT_MAXIMUM_AGE
|
|
79
|
+
): ParsedOptions {
|
|
80
|
+
val enableHighAccuracy = options?.enableHighAccuracy ?: false
|
|
81
|
+
val maxUpdates = options?.maxUpdates?.let { value ->
|
|
82
|
+
if (!value.isFinite()) {
|
|
83
|
+
0
|
|
84
|
+
} else {
|
|
85
|
+
value.toInt()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
67
88
|
|
|
68
|
-
fun parse(options: LocationRequestOptions?): ParsedOptions {
|
|
69
89
|
return ParsedOptions(
|
|
70
90
|
timeout = options?.timeout ?: DEFAULT_TIMEOUT,
|
|
71
|
-
maximumAge = options?.maximumAge ?:
|
|
72
|
-
|
|
91
|
+
maximumAge = options?.maximumAge ?: defaultMaximumAge,
|
|
92
|
+
androidAccuracy = resolveAndroidAccuracy(
|
|
93
|
+
options?.accuracy,
|
|
94
|
+
enableHighAccuracy
|
|
95
|
+
),
|
|
73
96
|
interval = options?.interval ?: DEFAULT_INTERVAL,
|
|
74
97
|
fastestInterval = options?.fastestInterval ?: DEFAULT_FASTEST_INTERVAL,
|
|
75
|
-
distanceFilter = options?.distanceFilter ?: DEFAULT_DISTANCE_FILTER
|
|
98
|
+
distanceFilter = options?.distanceFilter ?: DEFAULT_DISTANCE_FILTER,
|
|
99
|
+
granularity = options?.granularity ?: AndroidGranularity.PERMISSION,
|
|
100
|
+
waitForAccurateLocation = options?.waitForAccurateLocation ?: false,
|
|
101
|
+
maxUpdateAge = options?.maxUpdateAge,
|
|
102
|
+
maxUpdateDelay = options?.maxUpdateDelay ?: DEFAULT_MAX_UPDATE_DELAY,
|
|
103
|
+
maxUpdates = maxUpdates
|
|
76
104
|
)
|
|
77
105
|
}
|
|
106
|
+
|
|
107
|
+
fun parseLastKnown(options: LocationRequestOptions?): ParsedOptions {
|
|
108
|
+
return parse(options, defaultMaximumAge = Double.POSITIVE_INFINITY)
|
|
109
|
+
}
|
|
78
110
|
}
|
|
79
111
|
}
|
|
80
112
|
|
|
@@ -82,12 +114,18 @@ class NitroGeolocation(
|
|
|
82
114
|
val token: String,
|
|
83
115
|
val success: (GeolocationResponse) -> Unit,
|
|
84
116
|
val error: ((LocationError) -> Unit)?,
|
|
85
|
-
val options: ParsedOptions
|
|
117
|
+
val options: ParsedOptions,
|
|
118
|
+
var deliveredUpdates: Int = 0
|
|
86
119
|
)
|
|
87
120
|
|
|
121
|
+
private sealed interface PositionResult {
|
|
122
|
+
data class Success(val position: GeolocationResponse) : PositionResult
|
|
123
|
+
data class Failure(val error: LocationError) : PositionResult
|
|
124
|
+
}
|
|
125
|
+
|
|
88
126
|
private data class PositionRequest(
|
|
89
127
|
val id: UUID,
|
|
90
|
-
val resolver: (
|
|
128
|
+
val resolver: (PositionResult) -> Unit,
|
|
91
129
|
val options: ParsedOptions,
|
|
92
130
|
val handler: Handler,
|
|
93
131
|
val providers: List<String>,
|
|
@@ -102,13 +140,37 @@ class NitroGeolocation(
|
|
|
102
140
|
|
|
103
141
|
// MARK: - Properties
|
|
104
142
|
|
|
105
|
-
private var configuration:
|
|
143
|
+
private var configuration: GeolocationConfiguration? = null
|
|
106
144
|
private val locationManager: AndroidLocationManager by lazy {
|
|
107
145
|
reactContext.getSystemService(Context.LOCATION_SERVICE) as AndroidLocationManager
|
|
108
146
|
}
|
|
147
|
+
private val locationSettings: AndroidLocationSettings by lazy {
|
|
148
|
+
AndroidLocationSettings(
|
|
149
|
+
reactContext = reactContext,
|
|
150
|
+
locationManager = locationManager,
|
|
151
|
+
createLocationError = ::createLocationError,
|
|
152
|
+
createPlayServicesUnavailableError = ::createPlayServicesUnavailableError
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
private val fusedLocationClient by lazy {
|
|
156
|
+
LocationServices.getFusedLocationProviderClient(reactContext)
|
|
157
|
+
}
|
|
158
|
+
private val headingManager: AndroidHeadingManager by lazy {
|
|
159
|
+
AndroidHeadingManager(
|
|
160
|
+
context = reactContext,
|
|
161
|
+
createLocationError = ::createLocationError,
|
|
162
|
+
getReferenceLocation = {
|
|
163
|
+
lastLocation ?: getBestCachedLocation(
|
|
164
|
+
getValidProviders(resolveAndroidAccuracy(null, enableHighAccuracy = false)),
|
|
165
|
+
ParsedOptions.parseLastKnown(null)
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
private var lastLocation: Location? = null
|
|
109
171
|
|
|
110
|
-
// Permission
|
|
111
|
-
private val pendingPermissionResolvers = mutableListOf<(
|
|
172
|
+
// Permission callbacks
|
|
173
|
+
private val pendingPermissionResolvers = mutableListOf<(PermissionStatus) -> Unit>()
|
|
112
174
|
|
|
113
175
|
// getCurrentPosition requests
|
|
114
176
|
private val pendingPositionRequests = ConcurrentHashMap<UUID, PositionRequest>()
|
|
@@ -118,20 +180,24 @@ class NitroGeolocation(
|
|
|
118
180
|
|
|
119
181
|
// Location listener for watch subscriptions
|
|
120
182
|
private var watchLocationListener: LocationListener? = null
|
|
183
|
+
private var fusedWatchLocationCallback: LocationCallback? = null
|
|
121
184
|
private var currentWatchProvider: String? = null
|
|
122
185
|
|
|
123
186
|
// Error codes
|
|
187
|
+
private val INTERNAL_ERROR = -1.0
|
|
124
188
|
private val PERMISSION_DENIED = 1.0
|
|
125
189
|
private val POSITION_UNAVAILABLE = 2.0
|
|
126
190
|
private val TIMEOUT = 3.0
|
|
191
|
+
private val PLAY_SERVICE_NOT_AVAILABLE = 4.0
|
|
192
|
+
private val SETTINGS_NOT_SATISFIED = 5.0
|
|
127
193
|
|
|
128
194
|
// MARK: - Configuration
|
|
129
195
|
|
|
130
|
-
override fun setConfiguration(config:
|
|
196
|
+
override fun setConfiguration(config: GeolocationConfiguration) {
|
|
131
197
|
this.configuration = config
|
|
132
198
|
}
|
|
133
199
|
|
|
134
|
-
// MARK: - Permission API
|
|
200
|
+
// MARK: - Permission API
|
|
135
201
|
|
|
136
202
|
override fun checkPermission(): Promise<PermissionStatus> {
|
|
137
203
|
return Promise.async {
|
|
@@ -140,30 +206,29 @@ class NitroGeolocation(
|
|
|
140
206
|
}
|
|
141
207
|
}
|
|
142
208
|
|
|
143
|
-
override fun requestPermission(
|
|
144
|
-
|
|
145
|
-
|
|
209
|
+
override fun requestPermission(
|
|
210
|
+
success: (PermissionStatus) -> Unit,
|
|
211
|
+
error: ((LocationError) -> Unit)?
|
|
212
|
+
): Unit {
|
|
146
213
|
// Check if already determined
|
|
147
214
|
val currentStatus = getCurrentPermissionStatus()
|
|
148
215
|
if (currentStatus != PermissionStatus.UNDETERMINED) {
|
|
149
|
-
|
|
150
|
-
return
|
|
216
|
+
success(currentStatus)
|
|
217
|
+
return
|
|
151
218
|
}
|
|
152
219
|
|
|
153
220
|
// Check if we have an activity
|
|
154
221
|
val activity = reactContext.currentActivity
|
|
155
222
|
if (activity == null) {
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
error?.invoke(createLocationError(
|
|
224
|
+
INTERNAL_ERROR,
|
|
225
|
+
"No activity available"
|
|
226
|
+
))
|
|
227
|
+
return
|
|
158
228
|
}
|
|
159
229
|
|
|
160
230
|
// Queue resolver
|
|
161
|
-
pendingPermissionResolvers.add
|
|
162
|
-
result.fold(
|
|
163
|
-
onSuccess = { promise.resolve(it) },
|
|
164
|
-
onFailure = { promise.reject(it) }
|
|
165
|
-
)
|
|
166
|
-
}
|
|
231
|
+
pendingPermissionResolvers.add(success)
|
|
167
232
|
|
|
168
233
|
// Request permission
|
|
169
234
|
val permissions = arrayOf(
|
|
@@ -176,47 +241,259 @@ class NitroGeolocation(
|
|
|
176
241
|
permissions,
|
|
177
242
|
PERMISSION_REQUEST_CODE
|
|
178
243
|
)
|
|
244
|
+
}
|
|
179
245
|
|
|
246
|
+
// MARK: - Provider/Settings API
|
|
247
|
+
|
|
248
|
+
override fun hasServicesEnabled(): Promise<Boolean> {
|
|
249
|
+
return Promise.async {
|
|
250
|
+
locationSettings.hasServicesEnabled()
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
override fun getProviderStatus(): Promise<LocationProviderStatus> {
|
|
255
|
+
val promise = Promise<LocationProviderStatus>()
|
|
256
|
+
locationSettings.getProviderStatus { status ->
|
|
257
|
+
promise.resolve(status)
|
|
258
|
+
}
|
|
259
|
+
return promise
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
override fun getLocationAvailability(): Promise<LocationAvailability> {
|
|
263
|
+
val promise = Promise<LocationAvailability>()
|
|
264
|
+
|
|
265
|
+
if (!hasLocationPermission()) {
|
|
266
|
+
promise.resolve(createLocationAvailability(false, "permissionDenied"))
|
|
267
|
+
return promise
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!locationSettings.hasServicesEnabled()) {
|
|
271
|
+
promise.resolve(createLocationAvailability(false, "locationServicesDisabled"))
|
|
272
|
+
return promise
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (requiresPlayServices()) {
|
|
276
|
+
if (!isGooglePlayServicesAvailable()) {
|
|
277
|
+
promise.resolve(createLocationAvailability(false, "playServicesUnavailable"))
|
|
278
|
+
return promise
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fusedLocationClient.locationAvailability
|
|
282
|
+
.addOnSuccessListener { availability ->
|
|
283
|
+
promise.resolve(
|
|
284
|
+
createLocationAvailability(
|
|
285
|
+
availability.isLocationAvailable,
|
|
286
|
+
if (availability.isLocationAvailable) null else "fusedLocationUnavailable"
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
.addOnFailureListener { exception ->
|
|
291
|
+
promise.resolve(createLocationAvailability(
|
|
292
|
+
false,
|
|
293
|
+
"fusedLocationUnavailable: ${exception.message ?: "unknown error"}"
|
|
294
|
+
))
|
|
295
|
+
}
|
|
296
|
+
.addOnCanceledListener {
|
|
297
|
+
promise.resolve(createLocationAvailability(false, "fusedLocationUnavailable"))
|
|
298
|
+
}
|
|
299
|
+
return promise
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
val providers = getValidProviders(resolveAndroidAccuracy(null, enableHighAccuracy = false))
|
|
303
|
+
val reason = if (providers.isEmpty()) "noLocationProvider" else null
|
|
304
|
+
promise.resolve(createLocationAvailability(providers.isNotEmpty(), reason))
|
|
180
305
|
return promise
|
|
181
306
|
}
|
|
182
307
|
|
|
183
|
-
|
|
308
|
+
override fun requestLocationSettings(
|
|
309
|
+
success: (LocationProviderStatus) -> Unit,
|
|
310
|
+
error: ((LocationError) -> Unit)?,
|
|
311
|
+
options: LocationSettingsOptions?
|
|
312
|
+
) {
|
|
313
|
+
locationSettings.requestLocationSettings(success, error, options)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
override fun getAccuracyAuthorization(): Promise<AccuracyAuthorization> {
|
|
317
|
+
return Promise.async {
|
|
318
|
+
getCurrentAccuracyAuthorization()
|
|
319
|
+
}
|
|
320
|
+
}
|
|
184
321
|
|
|
185
|
-
override fun
|
|
186
|
-
|
|
322
|
+
override fun requestTemporaryFullAccuracy(
|
|
323
|
+
purposeKey: String,
|
|
324
|
+
success: (AccuracyAuthorization) -> Unit,
|
|
325
|
+
error: ((LocationError) -> Unit)?
|
|
326
|
+
) {
|
|
327
|
+
if (purposeKey.isBlank()) {
|
|
328
|
+
error?.invoke(createLocationError(
|
|
329
|
+
INTERNAL_ERROR,
|
|
330
|
+
"purposeKey must not be empty."
|
|
331
|
+
))
|
|
332
|
+
return
|
|
333
|
+
}
|
|
187
334
|
|
|
335
|
+
success(getCurrentAccuracyAuthorization())
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// MARK: - Get Current Position
|
|
339
|
+
|
|
340
|
+
override fun getCurrentPosition(
|
|
341
|
+
success: (GeolocationResponse) -> Unit,
|
|
342
|
+
error: ((LocationError) -> Unit)?,
|
|
343
|
+
options: LocationRequestOptions?
|
|
344
|
+
): Unit {
|
|
188
345
|
// Check permission
|
|
189
346
|
if (!hasLocationPermission()) {
|
|
190
|
-
|
|
347
|
+
error?.invoke(createLocationError(
|
|
191
348
|
PERMISSION_DENIED,
|
|
192
349
|
"Location permission not granted"
|
|
193
350
|
))
|
|
194
|
-
return
|
|
351
|
+
return
|
|
195
352
|
}
|
|
196
353
|
|
|
197
354
|
val parsedOptions = ParsedOptions.parse(options)
|
|
355
|
+
val validationError = validateParsedOptions(parsedOptions)
|
|
356
|
+
if (validationError != null) {
|
|
357
|
+
error?.invoke(validationError)
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
val permissionError = validateRequestPermission(parsedOptions)
|
|
361
|
+
if (permissionError != null) {
|
|
362
|
+
error?.invoke(permissionError)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
|
|
366
|
+
error?.invoke(createPlayServicesUnavailableError())
|
|
367
|
+
return
|
|
368
|
+
}
|
|
198
369
|
|
|
199
|
-
|
|
370
|
+
if (requiresPlayServices()) {
|
|
371
|
+
getCurrentPositionWithFused(success, error, parsedOptions)
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
val providers = getValidProviders(parsedOptions)
|
|
200
376
|
if (providers.isEmpty()) {
|
|
201
|
-
|
|
202
|
-
return
|
|
377
|
+
error?.invoke(createNoLocationProviderError(parsedOptions))
|
|
378
|
+
return
|
|
203
379
|
}
|
|
204
380
|
|
|
205
381
|
val cachedLocation = getBestCachedLocation(providers, parsedOptions)
|
|
206
382
|
if (cachedLocation != null) {
|
|
207
|
-
|
|
208
|
-
return
|
|
383
|
+
success(locationToPosition(cachedLocation))
|
|
384
|
+
return
|
|
209
385
|
}
|
|
210
386
|
|
|
211
387
|
// Request fresh location
|
|
212
388
|
requestFreshLocation(providers, parsedOptions) { result ->
|
|
213
|
-
result
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
389
|
+
when (result) {
|
|
390
|
+
is PositionResult.Success -> success(result.position)
|
|
391
|
+
is PositionResult.Failure -> error?.invoke(result.error)
|
|
392
|
+
}
|
|
217
393
|
}
|
|
394
|
+
}
|
|
218
395
|
|
|
219
|
-
|
|
396
|
+
override fun getLastKnownPosition(
|
|
397
|
+
success: (GeolocationResponse) -> Unit,
|
|
398
|
+
error: ((LocationError) -> Unit)?,
|
|
399
|
+
options: LocationRequestOptions?
|
|
400
|
+
) {
|
|
401
|
+
if (!hasLocationPermission()) {
|
|
402
|
+
error?.invoke(createLocationError(
|
|
403
|
+
PERMISSION_DENIED,
|
|
404
|
+
"Location permission not granted"
|
|
405
|
+
))
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
val parsedOptions = ParsedOptions.parseLastKnown(options)
|
|
410
|
+
val validationError = validateParsedOptions(parsedOptions)
|
|
411
|
+
if (validationError != null) {
|
|
412
|
+
error?.invoke(validationError)
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
val permissionError = validateRequestPermission(parsedOptions)
|
|
416
|
+
if (permissionError != null) {
|
|
417
|
+
error?.invoke(permissionError)
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
|
|
421
|
+
error?.invoke(createPlayServicesUnavailableError())
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (requiresPlayServices()) {
|
|
426
|
+
getLastKnownPositionWithFused(success, error, parsedOptions)
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
val providers = getValidProviders(parsedOptions)
|
|
431
|
+
if (providers.isEmpty()) {
|
|
432
|
+
error?.invoke(createNoLocationProviderError(parsedOptions))
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
val cachedLocation = getBestCachedLocation(providers, parsedOptions)
|
|
437
|
+
if (cachedLocation != null) {
|
|
438
|
+
success(locationToPosition(cachedLocation))
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
error?.invoke(createLocationError(
|
|
443
|
+
POSITION_UNAVAILABLE,
|
|
444
|
+
"No cached location available"
|
|
445
|
+
))
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// MARK: - Geocoding
|
|
449
|
+
|
|
450
|
+
override fun geocode(
|
|
451
|
+
address: String,
|
|
452
|
+
success: (Array<GeocodedLocation>) -> Unit,
|
|
453
|
+
error: ((LocationError) -> Unit)?
|
|
454
|
+
) {
|
|
455
|
+
val query = address.trim()
|
|
456
|
+
if (query.isEmpty()) {
|
|
457
|
+
error?.invoke(createLocationError(
|
|
458
|
+
INTERNAL_ERROR,
|
|
459
|
+
"address must not be empty."
|
|
460
|
+
))
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
runGeocoderOperation(success, error, "Unable to geocode address") {
|
|
465
|
+
val geocoder = Geocoder(reactContext, Locale.getDefault())
|
|
466
|
+
@Suppress("DEPRECATION")
|
|
467
|
+
geocoder.getFromLocationName(query, GEOCODER_MAX_RESULTS)
|
|
468
|
+
.orEmpty()
|
|
469
|
+
.mapNotNull { geocodedAddressToLocation(it) }
|
|
470
|
+
.toTypedArray()
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
override fun reverseGeocode(
|
|
475
|
+
coords: GeocodingCoordinates,
|
|
476
|
+
success: (Array<ReverseGeocodedAddress>) -> Unit,
|
|
477
|
+
error: ((LocationError) -> Unit)?
|
|
478
|
+
) {
|
|
479
|
+
val validationError = validateGeocodingCoordinates(coords)
|
|
480
|
+
if (validationError != null) {
|
|
481
|
+
error?.invoke(validationError)
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
runGeocoderOperation(success, error, "Unable to reverse geocode coordinates") {
|
|
486
|
+
val geocoder = Geocoder(reactContext, Locale.getDefault())
|
|
487
|
+
@Suppress("DEPRECATION")
|
|
488
|
+
geocoder.getFromLocation(
|
|
489
|
+
coords.latitude,
|
|
490
|
+
coords.longitude,
|
|
491
|
+
GEOCODER_MAX_RESULTS
|
|
492
|
+
)
|
|
493
|
+
.orEmpty()
|
|
494
|
+
.map { addressToReverseGeocodedAddress(it) }
|
|
495
|
+
.toTypedArray()
|
|
496
|
+
}
|
|
220
497
|
}
|
|
221
498
|
|
|
222
499
|
// MARK: - Watch Position (Callback-based with tokens)
|
|
@@ -228,6 +505,23 @@ class NitroGeolocation(
|
|
|
228
505
|
): String {
|
|
229
506
|
val token = UUID.randomUUID().toString()
|
|
230
507
|
val parsedOptions = ParsedOptions.parse(options)
|
|
508
|
+
val validationError = validateParsedOptions(parsedOptions)
|
|
509
|
+
if (validationError != null) {
|
|
510
|
+
error?.invoke(validationError)
|
|
511
|
+
return token
|
|
512
|
+
}
|
|
513
|
+
val permissionError = if (!hasLocationPermission()) {
|
|
514
|
+
createLocationError(
|
|
515
|
+
PERMISSION_DENIED,
|
|
516
|
+
"Location permission not granted"
|
|
517
|
+
)
|
|
518
|
+
} else {
|
|
519
|
+
validateRequestPermission(parsedOptions)
|
|
520
|
+
}
|
|
521
|
+
if (permissionError != null) {
|
|
522
|
+
error?.invoke(permissionError)
|
|
523
|
+
return token
|
|
524
|
+
}
|
|
231
525
|
|
|
232
526
|
val subscription = WatchSubscription(
|
|
233
527
|
token = token,
|
|
@@ -241,22 +535,64 @@ class NitroGeolocation(
|
|
|
241
535
|
// Start watching if first subscriber
|
|
242
536
|
if (watchSubscriptions.size == 1) {
|
|
243
537
|
startWatchingLocation()
|
|
538
|
+
} else {
|
|
539
|
+
restartWatchingLocation()
|
|
244
540
|
}
|
|
245
541
|
|
|
246
542
|
return token
|
|
247
543
|
}
|
|
248
544
|
|
|
545
|
+
override fun getHeading(
|
|
546
|
+
success: (Heading) -> Unit,
|
|
547
|
+
error: ((LocationError) -> Unit)?
|
|
548
|
+
) {
|
|
549
|
+
if (!hasLocationPermission()) {
|
|
550
|
+
error?.invoke(createLocationError(
|
|
551
|
+
PERMISSION_DENIED,
|
|
552
|
+
"Location permission not granted"
|
|
553
|
+
))
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
headingManager.getHeading(success, error)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
override fun watchHeading(
|
|
561
|
+
success: (Heading) -> Unit,
|
|
562
|
+
error: ((LocationError) -> Unit)?,
|
|
563
|
+
options: HeadingOptions?
|
|
564
|
+
): String {
|
|
565
|
+
if (!hasLocationPermission()) {
|
|
566
|
+
val token = UUID.randomUUID().toString()
|
|
567
|
+
error?.invoke(createLocationError(
|
|
568
|
+
PERMISSION_DENIED,
|
|
569
|
+
"Location permission not granted"
|
|
570
|
+
))
|
|
571
|
+
return token
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return headingManager.watchHeading(success, error, options)
|
|
575
|
+
}
|
|
576
|
+
|
|
249
577
|
override fun unwatch(token: String) {
|
|
250
|
-
watchSubscriptions.remove(token)
|
|
578
|
+
val didRemoveLocationSubscription = watchSubscriptions.remove(token) != null
|
|
579
|
+
headingManager.unwatch(token)
|
|
580
|
+
|
|
581
|
+
if (!didRemoveLocationSubscription) {
|
|
582
|
+
return
|
|
583
|
+
}
|
|
251
584
|
|
|
252
585
|
// Stop watching if no more subscribers
|
|
253
586
|
if (watchSubscriptions.isEmpty()) {
|
|
254
587
|
stopWatchingLocation()
|
|
588
|
+
} else {
|
|
589
|
+
restartWatchingLocation()
|
|
255
590
|
}
|
|
256
591
|
}
|
|
257
592
|
|
|
258
593
|
override fun stopObserving() {
|
|
259
594
|
watchSubscriptions.clear()
|
|
595
|
+
headingManager.stopObserving()
|
|
260
596
|
stopWatchingLocation()
|
|
261
597
|
}
|
|
262
598
|
|
|
@@ -306,6 +642,94 @@ class NitroGeolocation(
|
|
|
306
642
|
) == PackageManager.PERMISSION_GRANTED
|
|
307
643
|
}
|
|
308
644
|
|
|
645
|
+
private fun getCurrentAccuracyAuthorization(): AccuracyAuthorization {
|
|
646
|
+
return when {
|
|
647
|
+
hasFineLocationPermission() -> AccuracyAuthorization.FULL
|
|
648
|
+
hasCoarseLocationPermission() -> AccuracyAuthorization.REDUCED
|
|
649
|
+
else -> AccuracyAuthorization.UNKNOWN
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private fun validateParsedOptions(options: ParsedOptions): LocationError? {
|
|
654
|
+
if (!options.timeout.isFinite() || options.timeout < 0.0) {
|
|
655
|
+
return createLocationError(
|
|
656
|
+
INTERNAL_ERROR,
|
|
657
|
+
"timeout must be a finite number greater than or equal to 0."
|
|
658
|
+
)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (!options.maximumAge.isFinite() && options.maximumAge != Double.POSITIVE_INFINITY) {
|
|
662
|
+
return createLocationError(
|
|
663
|
+
INTERNAL_ERROR,
|
|
664
|
+
"maximumAge must be a finite number greater than or equal to 0."
|
|
665
|
+
)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (options.maximumAge < 0.0) {
|
|
669
|
+
return createLocationError(
|
|
670
|
+
INTERNAL_ERROR,
|
|
671
|
+
"maximumAge must be greater than or equal to 0."
|
|
672
|
+
)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (!options.interval.isFinite() || options.interval <= 0.0) {
|
|
676
|
+
return createLocationError(
|
|
677
|
+
INTERNAL_ERROR,
|
|
678
|
+
"interval must be a finite number greater than 0."
|
|
679
|
+
)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!options.fastestInterval.isFinite() || options.fastestInterval <= 0.0) {
|
|
683
|
+
return createLocationError(
|
|
684
|
+
INTERNAL_ERROR,
|
|
685
|
+
"fastestInterval must be a finite number greater than 0."
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (!options.distanceFilter.isFinite() || options.distanceFilter < 0.0) {
|
|
690
|
+
return createLocationError(
|
|
691
|
+
INTERNAL_ERROR,
|
|
692
|
+
"distanceFilter must be a finite number greater than or equal to 0."
|
|
693
|
+
)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
val maxUpdateAge = options.maxUpdateAge
|
|
697
|
+
if (maxUpdateAge != null && (!maxUpdateAge.isFinite() || maxUpdateAge < 0.0)) {
|
|
698
|
+
return createLocationError(
|
|
699
|
+
INTERNAL_ERROR,
|
|
700
|
+
"maxUpdateAge must be a finite number greater than or equal to 0."
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (!options.maxUpdateDelay.isFinite() || options.maxUpdateDelay < 0.0) {
|
|
705
|
+
return createLocationError(
|
|
706
|
+
INTERNAL_ERROR,
|
|
707
|
+
"maxUpdateDelay must be a finite number greater than or equal to 0."
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
val maxUpdates = options.maxUpdates
|
|
712
|
+
if (maxUpdates != null && maxUpdates < 1) {
|
|
713
|
+
return createLocationError(
|
|
714
|
+
INTERNAL_ERROR,
|
|
715
|
+
"maxUpdates must be greater than or equal to 1."
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return null
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private fun validateRequestPermission(options: ParsedOptions): LocationError? {
|
|
723
|
+
if (options.granularity == AndroidGranularity.FINE && !hasFineLocationPermission()) {
|
|
724
|
+
return createLocationError(
|
|
725
|
+
PERMISSION_DENIED,
|
|
726
|
+
"Fine location permission is required for granularity=fine."
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return null
|
|
731
|
+
}
|
|
732
|
+
|
|
309
733
|
// Handle permission request result (called from Activity)
|
|
310
734
|
fun onPermissionResult(requestCode: Int, grantResults: IntArray) {
|
|
311
735
|
if (requestCode != PERMISSION_REQUEST_CODE) return
|
|
@@ -315,29 +739,37 @@ class NitroGeolocation(
|
|
|
315
739
|
|
|
316
740
|
// Resolve all pending permission requests
|
|
317
741
|
for (resolver in pendingPermissionResolvers) {
|
|
318
|
-
resolver(
|
|
742
|
+
resolver(status)
|
|
319
743
|
}
|
|
320
744
|
pendingPermissionResolvers.clear()
|
|
321
745
|
}
|
|
322
746
|
|
|
323
747
|
// MARK: - Helper Functions - Provider Selection
|
|
324
748
|
|
|
325
|
-
private fun
|
|
326
|
-
return
|
|
749
|
+
private fun requiresPlayServices(): Boolean {
|
|
750
|
+
return configuration?.locationProvider == LocationProvider.PLAYSERVICES
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
private fun isGooglePlayServicesAvailable(): Boolean {
|
|
754
|
+
return GoogleApiAvailability.getInstance()
|
|
755
|
+
.isGooglePlayServicesAvailable(reactContext) == ConnectionResult.SUCCESS
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private fun getValidProvider(accuracy: AndroidAccuracyResolution): String? {
|
|
759
|
+
return getValidProviders(accuracy).firstOrNull()
|
|
327
760
|
}
|
|
328
761
|
|
|
329
|
-
private fun
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
else
|
|
333
|
-
AndroidLocationManager.NETWORK_PROVIDER
|
|
762
|
+
private fun getValidProvider(options: ParsedOptions): String? {
|
|
763
|
+
return getValidProviders(options).firstOrNull()
|
|
764
|
+
}
|
|
334
765
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
766
|
+
private fun getValidProviders(options: ParsedOptions): List<String> {
|
|
767
|
+
return getValidProviders(options.androidAccuracy)
|
|
768
|
+
.filter { provider -> options.granularity.allowsProvider(provider) }
|
|
769
|
+
}
|
|
339
770
|
|
|
340
|
-
|
|
771
|
+
private fun getValidProviders(accuracy: AndroidAccuracyResolution): List<String> {
|
|
772
|
+
return accuracy.providerOrder()
|
|
341
773
|
.distinct()
|
|
342
774
|
.filter { provider -> isProviderValid(provider) }
|
|
343
775
|
}
|
|
@@ -349,6 +781,7 @@ class NitroGeolocation(
|
|
|
349
781
|
when (provider) {
|
|
350
782
|
AndroidLocationManager.GPS_PROVIDER -> hasFineLocationPermission()
|
|
351
783
|
AndroidLocationManager.NETWORK_PROVIDER -> hasCoarseLocationPermission() || hasFineLocationPermission()
|
|
784
|
+
AndroidLocationManager.PASSIVE_PROVIDER -> hasLocationPermission()
|
|
352
785
|
else -> hasLocationPermission()
|
|
353
786
|
}
|
|
354
787
|
} catch (e: Exception) {
|
|
@@ -356,16 +789,16 @@ class NitroGeolocation(
|
|
|
356
789
|
}
|
|
357
790
|
}
|
|
358
791
|
|
|
359
|
-
private fun createNoLocationProviderError(options: ParsedOptions):
|
|
792
|
+
private fun createNoLocationProviderError(options: ParsedOptions): LocationError {
|
|
360
793
|
return createLocationError(
|
|
361
|
-
|
|
794
|
+
SETTINGS_NOT_SATISFIED,
|
|
362
795
|
getNoLocationProviderMessage(options)
|
|
363
796
|
)
|
|
364
797
|
}
|
|
365
798
|
|
|
366
799
|
private fun getNoLocationProviderMessage(options: ParsedOptions): String {
|
|
367
800
|
if (
|
|
368
|
-
|
|
801
|
+
options.androidAccuracy.mode != AndroidAccuracyMode.HIGH &&
|
|
369
802
|
hasCoarseLocationPermission() &&
|
|
370
803
|
!hasFineLocationPermission()
|
|
371
804
|
) {
|
|
@@ -378,8 +811,37 @@ class NitroGeolocation(
|
|
|
378
811
|
// MARK: - Helper Functions - Cache Validation
|
|
379
812
|
|
|
380
813
|
private fun isCachedLocationValid(location: Location, options: ParsedOptions): Boolean {
|
|
814
|
+
val maximumAge = effectiveMaximumAge(options)
|
|
815
|
+
if (maximumAge <= 0.0) return false
|
|
816
|
+
|
|
381
817
|
val locationAge = SystemClock.elapsedRealtime() - location.elapsedRealtimeNanos / 1_000_000
|
|
382
|
-
|
|
818
|
+
if (locationAge.coerceAtLeast(0L) >= maximumAge) {
|
|
819
|
+
return false
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (options.waitForAccurateLocation && !isLocationAccurateEnough(location, options)) {
|
|
823
|
+
return false
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return true
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private fun effectiveMaximumAge(options: ParsedOptions): Double {
|
|
830
|
+
val maxUpdateAge = options.maxUpdateAge ?: return options.maximumAge
|
|
831
|
+
return minOf(options.maximumAge, maxUpdateAge)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private fun isLocationAccurateEnough(location: Location, options: ParsedOptions): Boolean {
|
|
835
|
+
if (!location.hasAccuracy()) return false
|
|
836
|
+
|
|
837
|
+
val requiredAccuracy = when (options.androidAccuracy.mode) {
|
|
838
|
+
AndroidAccuracyMode.HIGH -> 25f
|
|
839
|
+
AndroidAccuracyMode.BALANCED -> 100f
|
|
840
|
+
AndroidAccuracyMode.LOW -> 500f
|
|
841
|
+
AndroidAccuracyMode.PASSIVE -> Float.MAX_VALUE
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return location.accuracy <= requiredAccuracy
|
|
383
845
|
}
|
|
384
846
|
|
|
385
847
|
private fun getBestCachedLocation(providers: List<String>, options: ParsedOptions): Location? {
|
|
@@ -395,7 +857,7 @@ class NitroGeolocation(
|
|
|
395
857
|
if (
|
|
396
858
|
lastKnownLocation != null &&
|
|
397
859
|
(isCachedLocationValid(lastKnownLocation, options) ||
|
|
398
|
-
options.maximumAge == Double.POSITIVE_INFINITY)
|
|
860
|
+
(options.maximumAge == Double.POSITIVE_INFINITY && options.maxUpdateAge == null))
|
|
399
861
|
) {
|
|
400
862
|
bestLocation = selectBestLocation(lastKnownLocation, bestLocation)
|
|
401
863
|
}
|
|
@@ -404,12 +866,139 @@ class NitroGeolocation(
|
|
|
404
866
|
return bestLocation
|
|
405
867
|
}
|
|
406
868
|
|
|
869
|
+
private fun getCurrentPositionWithFused(
|
|
870
|
+
success: (GeolocationResponse) -> Unit,
|
|
871
|
+
error: ((LocationError) -> Unit)?,
|
|
872
|
+
options: ParsedOptions
|
|
873
|
+
) {
|
|
874
|
+
if (effectiveMaximumAge(options) > 0.0) {
|
|
875
|
+
getFusedCachedLocation(options) { cachedLocation ->
|
|
876
|
+
if (cachedLocation != null) {
|
|
877
|
+
success(locationToPosition(cachedLocation))
|
|
878
|
+
return@getFusedCachedLocation
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
requestFusedFreshLocation(success, error, options)
|
|
882
|
+
}
|
|
883
|
+
return
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
requestFusedFreshLocation(success, error, options)
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private fun getLastKnownPositionWithFused(
|
|
890
|
+
success: (GeolocationResponse) -> Unit,
|
|
891
|
+
error: ((LocationError) -> Unit)?,
|
|
892
|
+
options: ParsedOptions
|
|
893
|
+
) {
|
|
894
|
+
getFusedCachedLocation(options) { cachedLocation ->
|
|
895
|
+
if (cachedLocation != null) {
|
|
896
|
+
success(locationToPosition(cachedLocation))
|
|
897
|
+
return@getFusedCachedLocation
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
error?.invoke(createLocationError(
|
|
901
|
+
POSITION_UNAVAILABLE,
|
|
902
|
+
"No cached location available"
|
|
903
|
+
))
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private fun getFusedCachedLocation(
|
|
908
|
+
options: ParsedOptions,
|
|
909
|
+
completion: (Location?) -> Unit
|
|
910
|
+
) {
|
|
911
|
+
// Fused lastLocation is not requested with LocationRequest granularity,
|
|
912
|
+
// so it cannot prove that a cached fix satisfies coarse-only callers.
|
|
913
|
+
if (options.granularity == AndroidGranularity.COARSE) {
|
|
914
|
+
completion(null)
|
|
915
|
+
return
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
try {
|
|
919
|
+
fusedLocationClient.lastLocation
|
|
920
|
+
.addOnSuccessListener { location ->
|
|
921
|
+
completion(location?.takeIf { isCachedLocationValid(it, options) })
|
|
922
|
+
}
|
|
923
|
+
.addOnFailureListener {
|
|
924
|
+
completion(null)
|
|
925
|
+
}
|
|
926
|
+
.addOnCanceledListener {
|
|
927
|
+
completion(null)
|
|
928
|
+
}
|
|
929
|
+
} catch (e: SecurityException) {
|
|
930
|
+
completion(null)
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private fun requestFusedFreshLocation(
|
|
935
|
+
success: (GeolocationResponse) -> Unit,
|
|
936
|
+
error: ((LocationError) -> Unit)?,
|
|
937
|
+
options: ParsedOptions
|
|
938
|
+
) {
|
|
939
|
+
val handler = Handler(Looper.getMainLooper())
|
|
940
|
+
val didComplete = AtomicBoolean(false)
|
|
941
|
+
lateinit var callback: LocationCallback
|
|
942
|
+
|
|
943
|
+
fun complete(result: PositionResult) {
|
|
944
|
+
if (!didComplete.compareAndSet(false, true)) return
|
|
945
|
+
|
|
946
|
+
handler.removeCallbacksAndMessages(null)
|
|
947
|
+
try {
|
|
948
|
+
fusedLocationClient.removeLocationUpdates(callback)
|
|
949
|
+
} catch (_: Exception) {
|
|
950
|
+
// Ignore cleanup races.
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
when (result) {
|
|
954
|
+
is PositionResult.Success -> success(result.position)
|
|
955
|
+
is PositionResult.Failure -> error?.invoke(result.error)
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
callback = object : LocationCallback() {
|
|
960
|
+
override fun onLocationResult(result: LocationResult) {
|
|
961
|
+
val location = result.lastLocation
|
|
962
|
+
if (location != null) {
|
|
963
|
+
complete(PositionResult.Success(locationToPosition(location)))
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
val timeoutRunnable = Runnable {
|
|
969
|
+
complete(PositionResult.Failure(createPositionTimeoutError(options)))
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
fusedLocationClient.requestLocationUpdates(
|
|
974
|
+
buildFusedLocationRequest(
|
|
975
|
+
options,
|
|
976
|
+
maxUpdatesOverride = 1,
|
|
977
|
+
includeDistanceFilter = false
|
|
978
|
+
),
|
|
979
|
+
callback,
|
|
980
|
+
Looper.getMainLooper()
|
|
981
|
+
)
|
|
982
|
+
handler.postDelayed(timeoutRunnable, coerceTimeoutMillis(options.timeout))
|
|
983
|
+
} catch (e: SecurityException) {
|
|
984
|
+
complete(PositionResult.Failure(createLocationError(
|
|
985
|
+
PERMISSION_DENIED,
|
|
986
|
+
"Security exception: ${e.message}"
|
|
987
|
+
)))
|
|
988
|
+
} catch (e: Exception) {
|
|
989
|
+
complete(PositionResult.Failure(createLocationError(
|
|
990
|
+
POSITION_UNAVAILABLE,
|
|
991
|
+
"Unable to request fused location: ${e.message}"
|
|
992
|
+
)))
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
407
996
|
// MARK: - Helper Functions - Request Fresh Location
|
|
408
997
|
|
|
409
998
|
private fun requestFreshLocation(
|
|
410
999
|
providers: List<String>,
|
|
411
1000
|
options: ParsedOptions,
|
|
412
|
-
resolver: (
|
|
1001
|
+
resolver: (PositionResult) -> Unit
|
|
413
1002
|
) {
|
|
414
1003
|
val id = UUID.randomUUID()
|
|
415
1004
|
val handler = Handler(Looper.getMainLooper())
|
|
@@ -433,21 +1022,22 @@ class NitroGeolocation(
|
|
|
433
1022
|
val remainingTimeoutMillis = request.remainingTimeoutMillis()
|
|
434
1023
|
|
|
435
1024
|
if (provider == null) {
|
|
436
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
437
|
-
createNoLocationProviderError(request.options)
|
|
438
|
-
)
|
|
1025
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1026
|
+
PositionResult.Failure(createNoLocationProviderError(request.options))
|
|
1027
|
+
)
|
|
439
1028
|
return
|
|
440
1029
|
}
|
|
441
1030
|
|
|
442
1031
|
if (remainingTimeoutMillis <= 0L) {
|
|
443
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
444
|
-
createPositionTimeoutError(request.options)
|
|
445
|
-
)
|
|
1032
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1033
|
+
PositionResult.Failure(createPositionTimeoutError(request.options))
|
|
1034
|
+
)
|
|
446
1035
|
return
|
|
447
1036
|
}
|
|
448
1037
|
|
|
449
|
-
//
|
|
450
|
-
|
|
1038
|
+
// Android's getCurrentLocation may resolve a recent historical fix. A maximumAge of 0
|
|
1039
|
+
// means callers explicitly asked us to wait for a fresh provider update.
|
|
1040
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && request.options.maximumAge > 0.0) {
|
|
451
1041
|
requestCurrentLocationModern(provider, requestId, request.handler, remainingTimeoutMillis)
|
|
452
1042
|
} else {
|
|
453
1043
|
requestCurrentLocationLegacy(provider, requestId, request.handler, remainingTimeoutMillis)
|
|
@@ -481,7 +1071,7 @@ class NitroGeolocation(
|
|
|
481
1071
|
if (location != null) {
|
|
482
1072
|
pendingPositionRequests.remove(requestId)
|
|
483
1073
|
val position = locationToPosition(location)
|
|
484
|
-
request.resolver(
|
|
1074
|
+
request.resolver(PositionResult.Success(position))
|
|
485
1075
|
} else {
|
|
486
1076
|
handleProviderFailure(requestId, createLocationError(
|
|
487
1077
|
POSITION_UNAVAILABLE,
|
|
@@ -531,7 +1121,7 @@ class NitroGeolocation(
|
|
|
531
1121
|
val request = pendingPositionRequests.remove(requestId)
|
|
532
1122
|
if (request != null) {
|
|
533
1123
|
val position = locationToPosition(location)
|
|
534
|
-
request.resolver(
|
|
1124
|
+
request.resolver(PositionResult.Success(position))
|
|
535
1125
|
}
|
|
536
1126
|
}
|
|
537
1127
|
oldLocation = location
|
|
@@ -579,7 +1169,7 @@ class NitroGeolocation(
|
|
|
579
1169
|
}
|
|
580
1170
|
}
|
|
581
1171
|
|
|
582
|
-
private fun handleProviderFailure(requestId: UUID, error:
|
|
1172
|
+
private fun handleProviderFailure(requestId: UUID, error: LocationError) {
|
|
583
1173
|
val request = pendingPositionRequests[requestId] ?: return
|
|
584
1174
|
|
|
585
1175
|
request.cancellationSignal?.cancel()
|
|
@@ -588,9 +1178,9 @@ class NitroGeolocation(
|
|
|
588
1178
|
|
|
589
1179
|
if (request.providerIndex < request.providers.size) {
|
|
590
1180
|
if (request.remainingTimeoutMillis() <= 0L) {
|
|
591
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
592
|
-
createPositionTimeoutError(request.options)
|
|
593
|
-
)
|
|
1181
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1182
|
+
PositionResult.Failure(createPositionTimeoutError(request.options))
|
|
1183
|
+
)
|
|
594
1184
|
return
|
|
595
1185
|
}
|
|
596
1186
|
|
|
@@ -598,7 +1188,7 @@ class NitroGeolocation(
|
|
|
598
1188
|
return
|
|
599
1189
|
}
|
|
600
1190
|
|
|
601
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1191
|
+
pendingPositionRequests.remove(requestId)?.resolver(PositionResult.Failure(error))
|
|
602
1192
|
}
|
|
603
1193
|
|
|
604
1194
|
private fun selectBestLocation(newLocation: Location, currentBest: Location?): Location {
|
|
@@ -633,29 +1223,27 @@ class NitroGeolocation(
|
|
|
633
1223
|
request.cancellationSignal?.cancel()
|
|
634
1224
|
request.cancellationSignal = null
|
|
635
1225
|
|
|
636
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
637
|
-
createPositionTimeoutError(request.options)
|
|
638
|
-
)
|
|
1226
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1227
|
+
PositionResult.Failure(createPositionTimeoutError(request.options))
|
|
1228
|
+
)
|
|
639
1229
|
}
|
|
640
1230
|
}
|
|
641
1231
|
|
|
642
1232
|
// MARK: - Helper Functions - Watch Position
|
|
643
1233
|
|
|
644
1234
|
private fun startWatchingLocation() {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1235
|
+
if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
|
|
1236
|
+
notifyWatchPlayServicesUnavailable()
|
|
1237
|
+
return
|
|
1238
|
+
}
|
|
649
1239
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
smallestInterval = minOf(smallestInterval, subscription.options.interval)
|
|
655
|
-
smallestDistanceFilter = minOf(smallestDistanceFilter, subscription.options.distanceFilter.toFloat())
|
|
1240
|
+
if (requiresPlayServices()) {
|
|
1241
|
+
startWatchingFusedLocation()
|
|
1242
|
+
return
|
|
656
1243
|
}
|
|
657
1244
|
|
|
658
|
-
val
|
|
1245
|
+
val mergedOptions = mergeWatchOptions()
|
|
1246
|
+
val provider = getValidProvider(mergedOptions)
|
|
659
1247
|
if (provider == null) {
|
|
660
1248
|
notifyWatchProviderUnavailable()
|
|
661
1249
|
return
|
|
@@ -665,16 +1253,12 @@ class NitroGeolocation(
|
|
|
665
1253
|
val listener = object : LocationListener {
|
|
666
1254
|
override fun onLocationChanged(location: Location) {
|
|
667
1255
|
val position = locationToPosition(location)
|
|
668
|
-
|
|
669
|
-
// Notify all subscribers
|
|
670
|
-
for ((_, subscription) in watchSubscriptions) {
|
|
671
|
-
subscription.success(position)
|
|
672
|
-
}
|
|
1256
|
+
deliverWatchPosition(position)
|
|
673
1257
|
}
|
|
674
1258
|
|
|
675
1259
|
override fun onProviderDisabled(provider: String) {
|
|
676
1260
|
val error = LocationError(
|
|
677
|
-
code =
|
|
1261
|
+
code = SETTINGS_NOT_SATISFIED,
|
|
678
1262
|
message = "Provider disabled: $provider"
|
|
679
1263
|
)
|
|
680
1264
|
|
|
@@ -693,8 +1277,8 @@ class NitroGeolocation(
|
|
|
693
1277
|
try {
|
|
694
1278
|
locationManager.requestLocationUpdates(
|
|
695
1279
|
provider,
|
|
696
|
-
|
|
697
|
-
|
|
1280
|
+
mergedOptions.interval.toLong(),
|
|
1281
|
+
mergedOptions.distanceFilter.toFloat(),
|
|
698
1282
|
listener,
|
|
699
1283
|
Looper.getMainLooper()
|
|
700
1284
|
)
|
|
@@ -710,15 +1294,157 @@ class NitroGeolocation(
|
|
|
710
1294
|
}
|
|
711
1295
|
}
|
|
712
1296
|
|
|
1297
|
+
private fun startWatchingFusedLocation() {
|
|
1298
|
+
val mergedOptions = mergeWatchOptions()
|
|
1299
|
+
val callback = object : LocationCallback() {
|
|
1300
|
+
override fun onLocationResult(result: LocationResult) {
|
|
1301
|
+
val location = result.lastLocation ?: return
|
|
1302
|
+
deliverWatchPosition(locationToPosition(location))
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
fusedWatchLocationCallback = callback
|
|
1307
|
+
|
|
1308
|
+
try {
|
|
1309
|
+
fusedLocationClient.requestLocationUpdates(
|
|
1310
|
+
buildFusedLocationRequest(mergedOptions),
|
|
1311
|
+
callback,
|
|
1312
|
+
Looper.getMainLooper()
|
|
1313
|
+
)
|
|
1314
|
+
} catch (e: SecurityException) {
|
|
1315
|
+
val error = LocationError(
|
|
1316
|
+
code = PERMISSION_DENIED,
|
|
1317
|
+
message = "Permission denied: ${e.message}"
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1321
|
+
subscription.error?.invoke(error)
|
|
1322
|
+
}
|
|
1323
|
+
} catch (e: Exception) {
|
|
1324
|
+
val error = LocationError(
|
|
1325
|
+
code = POSITION_UNAVAILABLE,
|
|
1326
|
+
message = "Unable to request fused location updates: ${e.message}"
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1330
|
+
subscription.error?.invoke(error)
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
private fun mergeWatchOptions(): ParsedOptions {
|
|
1336
|
+
var androidAccuracy: AndroidAccuracyResolution? = null
|
|
1337
|
+
var smallestInterval = Double.MAX_VALUE
|
|
1338
|
+
var smallestFastestInterval = Double.MAX_VALUE
|
|
1339
|
+
var smallestDistanceFilter = Double.MAX_VALUE
|
|
1340
|
+
var granularity = AndroidGranularity.PERMISSION
|
|
1341
|
+
var waitForAccurateLocation = false
|
|
1342
|
+
var maxUpdateAge: Double? = null
|
|
1343
|
+
var smallestMaxUpdateDelay = Double.MAX_VALUE
|
|
1344
|
+
|
|
1345
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1346
|
+
androidAccuracy = mostDemandingAndroidAccuracy(
|
|
1347
|
+
androidAccuracy,
|
|
1348
|
+
subscription.options.androidAccuracy
|
|
1349
|
+
)
|
|
1350
|
+
smallestInterval = minOf(smallestInterval, subscription.options.interval)
|
|
1351
|
+
smallestFastestInterval = minOf(
|
|
1352
|
+
smallestFastestInterval,
|
|
1353
|
+
subscription.options.fastestInterval
|
|
1354
|
+
)
|
|
1355
|
+
smallestDistanceFilter = minOf(
|
|
1356
|
+
smallestDistanceFilter,
|
|
1357
|
+
subscription.options.distanceFilter
|
|
1358
|
+
)
|
|
1359
|
+
granularity = mergeWatchGranularity(granularity, subscription.options.granularity)
|
|
1360
|
+
waitForAccurateLocation = waitForAccurateLocation ||
|
|
1361
|
+
subscription.options.waitForAccurateLocation
|
|
1362
|
+
maxUpdateAge = mergeNullableMinimum(maxUpdateAge, subscription.options.maxUpdateAge)
|
|
1363
|
+
smallestMaxUpdateDelay = minOf(
|
|
1364
|
+
smallestMaxUpdateDelay,
|
|
1365
|
+
subscription.options.maxUpdateDelay
|
|
1366
|
+
)
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
return ParsedOptions(
|
|
1370
|
+
timeout = Double.POSITIVE_INFINITY,
|
|
1371
|
+
maximumAge = 0.0,
|
|
1372
|
+
androidAccuracy = androidAccuracy ?: resolveAndroidAccuracy(null, enableHighAccuracy = false),
|
|
1373
|
+
interval = smallestInterval,
|
|
1374
|
+
fastestInterval = smallestFastestInterval,
|
|
1375
|
+
distanceFilter = smallestDistanceFilter,
|
|
1376
|
+
granularity = granularity,
|
|
1377
|
+
waitForAccurateLocation = waitForAccurateLocation,
|
|
1378
|
+
maxUpdateAge = maxUpdateAge,
|
|
1379
|
+
maxUpdateDelay = if (smallestMaxUpdateDelay == Double.MAX_VALUE) 0.0 else smallestMaxUpdateDelay,
|
|
1380
|
+
maxUpdates = null
|
|
1381
|
+
)
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
private fun deliverWatchPosition(position: GeolocationResponse) {
|
|
1385
|
+
val tokensToRemove = mutableListOf<String>()
|
|
1386
|
+
|
|
1387
|
+
for ((token, subscription) in watchSubscriptions) {
|
|
1388
|
+
subscription.success(position)
|
|
1389
|
+
subscription.deliveredUpdates += 1
|
|
1390
|
+
|
|
1391
|
+
val maxUpdates = subscription.options.maxUpdates
|
|
1392
|
+
if (maxUpdates != null && subscription.deliveredUpdates >= maxUpdates) {
|
|
1393
|
+
tokensToRemove.add(token)
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
for (token in tokensToRemove) {
|
|
1398
|
+
watchSubscriptions.remove(token)
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if (tokensToRemove.isNotEmpty()) {
|
|
1402
|
+
if (watchSubscriptions.isEmpty()) {
|
|
1403
|
+
stopWatchingLocation()
|
|
1404
|
+
} else {
|
|
1405
|
+
restartWatchingLocation()
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
713
1410
|
private fun notifyWatchProviderUnavailable() {
|
|
714
1411
|
for ((_, subscription) in watchSubscriptions) {
|
|
715
1412
|
subscription.error?.invoke(LocationError(
|
|
716
|
-
code =
|
|
1413
|
+
code = SETTINGS_NOT_SATISFIED,
|
|
717
1414
|
message = getNoLocationProviderMessage(subscription.options)
|
|
718
1415
|
))
|
|
719
1416
|
}
|
|
720
1417
|
}
|
|
721
1418
|
|
|
1419
|
+
private fun notifyWatchPlayServicesUnavailable() {
|
|
1420
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1421
|
+
subscription.error?.invoke(createPlayServicesUnavailableError())
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
private fun mergeWatchGranularity(
|
|
1426
|
+
current: AndroidGranularity,
|
|
1427
|
+
next: AndroidGranularity
|
|
1428
|
+
): AndroidGranularity {
|
|
1429
|
+
return when {
|
|
1430
|
+
current == AndroidGranularity.COARSE || next == AndroidGranularity.COARSE -> {
|
|
1431
|
+
AndroidGranularity.COARSE
|
|
1432
|
+
}
|
|
1433
|
+
current == AndroidGranularity.FINE || next == AndroidGranularity.FINE -> {
|
|
1434
|
+
AndroidGranularity.FINE
|
|
1435
|
+
}
|
|
1436
|
+
else -> AndroidGranularity.PERMISSION
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
private fun mergeNullableMinimum(current: Double?, next: Double?): Double? {
|
|
1441
|
+
return when {
|
|
1442
|
+
current == null -> next
|
|
1443
|
+
next == null -> current
|
|
1444
|
+
else -> minOf(current, next)
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
722
1448
|
private fun stopWatchingLocation() {
|
|
723
1449
|
watchLocationListener?.let { listener ->
|
|
724
1450
|
try {
|
|
@@ -727,59 +1453,169 @@ class NitroGeolocation(
|
|
|
727
1453
|
// Ignore
|
|
728
1454
|
}
|
|
729
1455
|
}
|
|
1456
|
+
fusedWatchLocationCallback?.let { callback ->
|
|
1457
|
+
try {
|
|
1458
|
+
fusedLocationClient.removeLocationUpdates(callback)
|
|
1459
|
+
} catch (e: Exception) {
|
|
1460
|
+
// Ignore
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
730
1463
|
watchLocationListener = null
|
|
1464
|
+
fusedWatchLocationCallback = null
|
|
731
1465
|
currentWatchProvider = null
|
|
732
1466
|
}
|
|
733
1467
|
|
|
1468
|
+
private fun restartWatchingLocation() {
|
|
1469
|
+
stopWatchingLocation()
|
|
1470
|
+
startWatchingLocation()
|
|
1471
|
+
}
|
|
1472
|
+
|
|
734
1473
|
// MARK: - Helper Functions - Conversion
|
|
735
1474
|
|
|
736
1475
|
private fun locationToPosition(location: Location): GeolocationResponse {
|
|
737
|
-
|
|
738
|
-
NullableDouble.create(location.altitude)
|
|
739
|
-
} else {
|
|
740
|
-
null
|
|
741
|
-
}
|
|
742
|
-
val altitudeAccuracy = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasVerticalAccuracy()) {
|
|
743
|
-
NullableDouble.create(location.verticalAccuracyMeters.toDouble())
|
|
744
|
-
} else {
|
|
745
|
-
null
|
|
746
|
-
}
|
|
747
|
-
val heading = if (location.hasBearing()) {
|
|
748
|
-
NullableDouble.create(location.bearing.toDouble())
|
|
749
|
-
} else {
|
|
750
|
-
null
|
|
751
|
-
}
|
|
752
|
-
val speed = if (location.hasSpeed()) {
|
|
753
|
-
NullableDouble.create(location.speed.toDouble())
|
|
754
|
-
} else {
|
|
755
|
-
null
|
|
756
|
-
}
|
|
1476
|
+
lastLocation = location
|
|
757
1477
|
|
|
758
1478
|
val coords = GeolocationCoordinates(
|
|
759
1479
|
latitude = location.latitude,
|
|
760
1480
|
longitude = location.longitude,
|
|
761
|
-
altitude =
|
|
1481
|
+
altitude = location.altitudeValue(),
|
|
762
1482
|
accuracy = location.accuracy.toDouble(),
|
|
763
|
-
altitudeAccuracy =
|
|
764
|
-
heading =
|
|
765
|
-
speed =
|
|
1483
|
+
altitudeAccuracy = location.altitudeAccuracyValue(),
|
|
1484
|
+
heading = location.headingValue(),
|
|
1485
|
+
speed = location.speedValue()
|
|
766
1486
|
)
|
|
767
1487
|
|
|
768
1488
|
return GeolocationResponse(
|
|
769
1489
|
coords = coords,
|
|
770
|
-
timestamp = location.time.toDouble()
|
|
1490
|
+
timestamp = location.time.toDouble(),
|
|
1491
|
+
mocked = location.isMocked(),
|
|
1492
|
+
provider = location.providerUsed()
|
|
771
1493
|
)
|
|
772
1494
|
}
|
|
773
1495
|
|
|
774
|
-
private fun
|
|
775
|
-
|
|
1496
|
+
private fun geocodedAddressToLocation(address: Address): GeocodedLocation? {
|
|
1497
|
+
if (!address.hasLatitude() || !address.hasLongitude()) {
|
|
1498
|
+
return null
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
return GeocodedLocation(
|
|
1502
|
+
latitude = address.latitude,
|
|
1503
|
+
longitude = address.longitude,
|
|
1504
|
+
accuracy = null
|
|
1505
|
+
)
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
private fun addressToReverseGeocodedAddress(address: Address): ReverseGeocodedAddress {
|
|
1509
|
+
return ReverseGeocodedAddress(
|
|
1510
|
+
country = address.countryName.nonBlankOrNull(),
|
|
1511
|
+
region = address.adminArea.nonBlankOrNull(),
|
|
1512
|
+
city = (address.locality ?: address.subAdminArea).nonBlankOrNull(),
|
|
1513
|
+
district = address.subLocality.nonBlankOrNull(),
|
|
1514
|
+
street = formatStreet(address),
|
|
1515
|
+
postalCode = address.postalCode.nonBlankOrNull(),
|
|
1516
|
+
formattedAddress = formatAddressLines(address)
|
|
1517
|
+
)
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
private fun formatStreet(address: Address): String? {
|
|
1521
|
+
return listOf(address.subThoroughfare, address.thoroughfare)
|
|
1522
|
+
.mapNotNull { it.nonBlankOrNull() }
|
|
1523
|
+
.joinToString(" ")
|
|
1524
|
+
.nonBlankOrNull()
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
private fun formatAddressLines(address: Address): String? {
|
|
1528
|
+
if (address.maxAddressLineIndex < 0) {
|
|
1529
|
+
return null
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
return (0..address.maxAddressLineIndex)
|
|
1533
|
+
.mapNotNull { index -> address.getAddressLine(index).nonBlankOrNull() }
|
|
1534
|
+
.joinToString(", ")
|
|
1535
|
+
.nonBlankOrNull()
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
private fun validateGeocodingCoordinates(coords: GeocodingCoordinates): LocationError? {
|
|
1539
|
+
if (!coords.latitude.isFinite() || coords.latitude < -90.0 || coords.latitude > 90.0) {
|
|
1540
|
+
return createLocationError(
|
|
1541
|
+
INTERNAL_ERROR,
|
|
1542
|
+
"latitude must be a finite number between -90 and 90."
|
|
1543
|
+
)
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (!coords.longitude.isFinite() || coords.longitude < -180.0 || coords.longitude > 180.0) {
|
|
1547
|
+
return createLocationError(
|
|
1548
|
+
INTERNAL_ERROR,
|
|
1549
|
+
"longitude must be a finite number between -180 and 180."
|
|
1550
|
+
)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
return null
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
private fun <T> runGeocoderOperation(
|
|
1557
|
+
success: (Array<T>) -> Unit,
|
|
1558
|
+
error: ((LocationError) -> Unit)?,
|
|
1559
|
+
failurePrefix: String,
|
|
1560
|
+
operation: () -> Array<T>
|
|
1561
|
+
) {
|
|
1562
|
+
if (!Geocoder.isPresent()) {
|
|
1563
|
+
error?.invoke(createLocationError(
|
|
1564
|
+
POSITION_UNAVAILABLE,
|
|
1565
|
+
"Platform geocoder is not available."
|
|
1566
|
+
))
|
|
1567
|
+
return
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
val handler = Handler(Looper.getMainLooper())
|
|
1571
|
+
|
|
1572
|
+
Thread {
|
|
1573
|
+
try {
|
|
1574
|
+
val results = operation()
|
|
1575
|
+
handler.post { success(results) }
|
|
1576
|
+
} catch (e: IOException) {
|
|
1577
|
+
handler.post {
|
|
1578
|
+
error?.invoke(createLocationError(
|
|
1579
|
+
POSITION_UNAVAILABLE,
|
|
1580
|
+
"$failurePrefix: ${e.message ?: "geocoder service unavailable"}"
|
|
1581
|
+
))
|
|
1582
|
+
}
|
|
1583
|
+
} catch (e: Exception) {
|
|
1584
|
+
handler.post {
|
|
1585
|
+
error?.invoke(createLocationError(
|
|
1586
|
+
INTERNAL_ERROR,
|
|
1587
|
+
"$failurePrefix: ${e.message ?: "unknown error"}"
|
|
1588
|
+
))
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}.start()
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
private fun createLocationAvailability(
|
|
1595
|
+
available: Boolean,
|
|
1596
|
+
reason: String?
|
|
1597
|
+
): LocationAvailability {
|
|
1598
|
+
return LocationAvailability(
|
|
1599
|
+
available = available,
|
|
1600
|
+
reason = reason
|
|
1601
|
+
)
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
private fun createLocationError(code: Double, message: String): LocationError {
|
|
1605
|
+
return LocationError(
|
|
776
1606
|
code = code,
|
|
777
1607
|
message = message
|
|
778
1608
|
)
|
|
779
|
-
return GeolocationErrorException(locationError)
|
|
780
1609
|
}
|
|
781
1610
|
|
|
782
|
-
private fun
|
|
1611
|
+
private fun createPlayServicesUnavailableError(): LocationError {
|
|
1612
|
+
return createLocationError(
|
|
1613
|
+
PLAY_SERVICE_NOT_AVAILABLE,
|
|
1614
|
+
"Google Play Services location provider is not available."
|
|
1615
|
+
)
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
private fun createPositionTimeoutError(options: ParsedOptions): LocationError {
|
|
783
1619
|
val timeoutSeconds = options.timeout / 1000.0
|
|
784
1620
|
val message = String.format("Unable to fetch location within %.1fs.", timeoutSeconds)
|
|
785
1621
|
return createLocationError(TIMEOUT, message)
|
|
@@ -805,8 +1641,57 @@ class NitroGeolocation(
|
|
|
805
1641
|
}
|
|
806
1642
|
}
|
|
807
1643
|
|
|
1644
|
+
private fun buildFusedLocationRequest(
|
|
1645
|
+
options: ParsedOptions,
|
|
1646
|
+
maxUpdatesOverride: Int? = null,
|
|
1647
|
+
includeDistanceFilter: Boolean = true
|
|
1648
|
+
): GmsLocationRequest {
|
|
1649
|
+
val builder = GmsLocationRequest
|
|
1650
|
+
.Builder(options.androidAccuracy.gmsPriority(), coercePositiveMillis(options.interval))
|
|
1651
|
+
.setMinUpdateIntervalMillis(coercePositiveMillis(options.fastestInterval))
|
|
1652
|
+
.setGranularity(options.granularity.gmsGranularity())
|
|
1653
|
+
.setWaitForAccurateLocation(options.waitForAccurateLocation)
|
|
1654
|
+
.setMaxUpdateDelayMillis(coerceNonNegativeMillis(options.maxUpdateDelay))
|
|
1655
|
+
|
|
1656
|
+
if (includeDistanceFilter) {
|
|
1657
|
+
builder.setMinUpdateDistanceMeters(options.distanceFilter.toFloat())
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
options.maxUpdateAge?.let { value ->
|
|
1661
|
+
builder.setMaxUpdateAgeMillis(coerceNonNegativeMillis(value))
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
val maxUpdates = maxUpdatesOverride ?: options.maxUpdates
|
|
1665
|
+
if (maxUpdates != null) {
|
|
1666
|
+
builder.setMaxUpdates(maxUpdates)
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
return builder.build()
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
private fun coercePositiveMillis(value: Double): Long {
|
|
1673
|
+
return when {
|
|
1674
|
+
value.isNaN() || value <= 0.0 -> 1L
|
|
1675
|
+
value.isInfinite() || value >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
|
|
1676
|
+
else -> value.toLong()
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
private fun coerceNonNegativeMillis(value: Double): Long {
|
|
1681
|
+
return when {
|
|
1682
|
+
value.isNaN() || value <= 0.0 -> 0L
|
|
1683
|
+
value.isInfinite() || value >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
|
|
1684
|
+
else -> value.toLong()
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
808
1688
|
companion object {
|
|
809
1689
|
private const val PERMISSION_REQUEST_CODE = 8947
|
|
1690
|
+
private const val GEOCODER_MAX_RESULTS = 5
|
|
810
1691
|
private const val TWO_MINUTES_MS = 2 * 60 * 1000L
|
|
811
1692
|
}
|
|
812
1693
|
}
|
|
1694
|
+
|
|
1695
|
+
private fun String?.nonBlankOrNull(): String? {
|
|
1696
|
+
return this?.trim()?.takeIf { it.isNotEmpty() }
|
|
1697
|
+
}
|