react-native-nitro-geolocation 1.1.4 → 1.2.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/README.md +97 -9
- 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 +1027 -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 +292 -18
- 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 +108 -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,39 @@ 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
|
-
|
|
749
|
+
private fun requiresPlayServices(): Boolean {
|
|
750
|
+
// TODO: Switch auto/default Android provider selection to prefer
|
|
751
|
+
// Google Play Services when available.
|
|
752
|
+
return configuration?.locationProvider == LocationProvider.PLAYSERVICES
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
private fun isGooglePlayServicesAvailable(): Boolean {
|
|
756
|
+
return GoogleApiAvailability.getInstance()
|
|
757
|
+
.isGooglePlayServicesAvailable(reactContext) == ConnectionResult.SUCCESS
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
private fun getValidProvider(accuracy: AndroidAccuracyResolution): String? {
|
|
761
|
+
return getValidProviders(accuracy).firstOrNull()
|
|
327
762
|
}
|
|
328
763
|
|
|
329
|
-
private fun
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
else
|
|
333
|
-
AndroidLocationManager.NETWORK_PROVIDER
|
|
764
|
+
private fun getValidProvider(options: ParsedOptions): String? {
|
|
765
|
+
return getValidProviders(options).firstOrNull()
|
|
766
|
+
}
|
|
334
767
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
768
|
+
private fun getValidProviders(options: ParsedOptions): List<String> {
|
|
769
|
+
return getValidProviders(options.androidAccuracy)
|
|
770
|
+
.filter { provider -> options.granularity.allowsProvider(provider) }
|
|
771
|
+
}
|
|
339
772
|
|
|
340
|
-
|
|
773
|
+
private fun getValidProviders(accuracy: AndroidAccuracyResolution): List<String> {
|
|
774
|
+
return accuracy.providerOrder()
|
|
341
775
|
.distinct()
|
|
342
776
|
.filter { provider -> isProviderValid(provider) }
|
|
343
777
|
}
|
|
@@ -349,6 +783,7 @@ class NitroGeolocation(
|
|
|
349
783
|
when (provider) {
|
|
350
784
|
AndroidLocationManager.GPS_PROVIDER -> hasFineLocationPermission()
|
|
351
785
|
AndroidLocationManager.NETWORK_PROVIDER -> hasCoarseLocationPermission() || hasFineLocationPermission()
|
|
786
|
+
AndroidLocationManager.PASSIVE_PROVIDER -> hasLocationPermission()
|
|
352
787
|
else -> hasLocationPermission()
|
|
353
788
|
}
|
|
354
789
|
} catch (e: Exception) {
|
|
@@ -356,16 +791,16 @@ class NitroGeolocation(
|
|
|
356
791
|
}
|
|
357
792
|
}
|
|
358
793
|
|
|
359
|
-
private fun createNoLocationProviderError(options: ParsedOptions):
|
|
794
|
+
private fun createNoLocationProviderError(options: ParsedOptions): LocationError {
|
|
360
795
|
return createLocationError(
|
|
361
|
-
|
|
796
|
+
SETTINGS_NOT_SATISFIED,
|
|
362
797
|
getNoLocationProviderMessage(options)
|
|
363
798
|
)
|
|
364
799
|
}
|
|
365
800
|
|
|
366
801
|
private fun getNoLocationProviderMessage(options: ParsedOptions): String {
|
|
367
802
|
if (
|
|
368
|
-
|
|
803
|
+
options.androidAccuracy.mode != AndroidAccuracyMode.HIGH &&
|
|
369
804
|
hasCoarseLocationPermission() &&
|
|
370
805
|
!hasFineLocationPermission()
|
|
371
806
|
) {
|
|
@@ -378,8 +813,37 @@ class NitroGeolocation(
|
|
|
378
813
|
// MARK: - Helper Functions - Cache Validation
|
|
379
814
|
|
|
380
815
|
private fun isCachedLocationValid(location: Location, options: ParsedOptions): Boolean {
|
|
816
|
+
val maximumAge = effectiveMaximumAge(options)
|
|
817
|
+
if (maximumAge <= 0.0) return false
|
|
818
|
+
|
|
381
819
|
val locationAge = SystemClock.elapsedRealtime() - location.elapsedRealtimeNanos / 1_000_000
|
|
382
|
-
|
|
820
|
+
if (locationAge.coerceAtLeast(0L) >= maximumAge) {
|
|
821
|
+
return false
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (options.waitForAccurateLocation && !isLocationAccurateEnough(location, options)) {
|
|
825
|
+
return false
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return true
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private fun effectiveMaximumAge(options: ParsedOptions): Double {
|
|
832
|
+
val maxUpdateAge = options.maxUpdateAge ?: return options.maximumAge
|
|
833
|
+
return minOf(options.maximumAge, maxUpdateAge)
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private fun isLocationAccurateEnough(location: Location, options: ParsedOptions): Boolean {
|
|
837
|
+
if (!location.hasAccuracy()) return false
|
|
838
|
+
|
|
839
|
+
val requiredAccuracy = when (options.androidAccuracy.mode) {
|
|
840
|
+
AndroidAccuracyMode.HIGH -> 25f
|
|
841
|
+
AndroidAccuracyMode.BALANCED -> 100f
|
|
842
|
+
AndroidAccuracyMode.LOW -> 500f
|
|
843
|
+
AndroidAccuracyMode.PASSIVE -> Float.MAX_VALUE
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return location.accuracy <= requiredAccuracy
|
|
383
847
|
}
|
|
384
848
|
|
|
385
849
|
private fun getBestCachedLocation(providers: List<String>, options: ParsedOptions): Location? {
|
|
@@ -395,7 +859,7 @@ class NitroGeolocation(
|
|
|
395
859
|
if (
|
|
396
860
|
lastKnownLocation != null &&
|
|
397
861
|
(isCachedLocationValid(lastKnownLocation, options) ||
|
|
398
|
-
options.maximumAge == Double.POSITIVE_INFINITY)
|
|
862
|
+
(options.maximumAge == Double.POSITIVE_INFINITY && options.maxUpdateAge == null))
|
|
399
863
|
) {
|
|
400
864
|
bestLocation = selectBestLocation(lastKnownLocation, bestLocation)
|
|
401
865
|
}
|
|
@@ -404,12 +868,139 @@ class NitroGeolocation(
|
|
|
404
868
|
return bestLocation
|
|
405
869
|
}
|
|
406
870
|
|
|
871
|
+
private fun getCurrentPositionWithFused(
|
|
872
|
+
success: (GeolocationResponse) -> Unit,
|
|
873
|
+
error: ((LocationError) -> Unit)?,
|
|
874
|
+
options: ParsedOptions
|
|
875
|
+
) {
|
|
876
|
+
if (effectiveMaximumAge(options) > 0.0) {
|
|
877
|
+
getFusedCachedLocation(options) { cachedLocation ->
|
|
878
|
+
if (cachedLocation != null) {
|
|
879
|
+
success(locationToPosition(cachedLocation))
|
|
880
|
+
return@getFusedCachedLocation
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
requestFusedFreshLocation(success, error, options)
|
|
884
|
+
}
|
|
885
|
+
return
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
requestFusedFreshLocation(success, error, options)
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
private fun getLastKnownPositionWithFused(
|
|
892
|
+
success: (GeolocationResponse) -> Unit,
|
|
893
|
+
error: ((LocationError) -> Unit)?,
|
|
894
|
+
options: ParsedOptions
|
|
895
|
+
) {
|
|
896
|
+
getFusedCachedLocation(options) { cachedLocation ->
|
|
897
|
+
if (cachedLocation != null) {
|
|
898
|
+
success(locationToPosition(cachedLocation))
|
|
899
|
+
return@getFusedCachedLocation
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
error?.invoke(createLocationError(
|
|
903
|
+
POSITION_UNAVAILABLE,
|
|
904
|
+
"No cached location available"
|
|
905
|
+
))
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private fun getFusedCachedLocation(
|
|
910
|
+
options: ParsedOptions,
|
|
911
|
+
completion: (Location?) -> Unit
|
|
912
|
+
) {
|
|
913
|
+
// Fused lastLocation is not requested with LocationRequest granularity,
|
|
914
|
+
// so it cannot prove that a cached fix satisfies coarse-only callers.
|
|
915
|
+
if (options.granularity == AndroidGranularity.COARSE) {
|
|
916
|
+
completion(null)
|
|
917
|
+
return
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
try {
|
|
921
|
+
fusedLocationClient.lastLocation
|
|
922
|
+
.addOnSuccessListener { location ->
|
|
923
|
+
completion(location?.takeIf { isCachedLocationValid(it, options) })
|
|
924
|
+
}
|
|
925
|
+
.addOnFailureListener {
|
|
926
|
+
completion(null)
|
|
927
|
+
}
|
|
928
|
+
.addOnCanceledListener {
|
|
929
|
+
completion(null)
|
|
930
|
+
}
|
|
931
|
+
} catch (e: SecurityException) {
|
|
932
|
+
completion(null)
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
private fun requestFusedFreshLocation(
|
|
937
|
+
success: (GeolocationResponse) -> Unit,
|
|
938
|
+
error: ((LocationError) -> Unit)?,
|
|
939
|
+
options: ParsedOptions
|
|
940
|
+
) {
|
|
941
|
+
val handler = Handler(Looper.getMainLooper())
|
|
942
|
+
val didComplete = AtomicBoolean(false)
|
|
943
|
+
lateinit var callback: LocationCallback
|
|
944
|
+
|
|
945
|
+
fun complete(result: PositionResult) {
|
|
946
|
+
if (!didComplete.compareAndSet(false, true)) return
|
|
947
|
+
|
|
948
|
+
handler.removeCallbacksAndMessages(null)
|
|
949
|
+
try {
|
|
950
|
+
fusedLocationClient.removeLocationUpdates(callback)
|
|
951
|
+
} catch (_: Exception) {
|
|
952
|
+
// Ignore cleanup races.
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
when (result) {
|
|
956
|
+
is PositionResult.Success -> success(result.position)
|
|
957
|
+
is PositionResult.Failure -> error?.invoke(result.error)
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
callback = object : LocationCallback() {
|
|
962
|
+
override fun onLocationResult(result: LocationResult) {
|
|
963
|
+
val location = result.lastLocation
|
|
964
|
+
if (location != null) {
|
|
965
|
+
complete(PositionResult.Success(locationToPosition(location)))
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
val timeoutRunnable = Runnable {
|
|
971
|
+
complete(PositionResult.Failure(createPositionTimeoutError(options)))
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
try {
|
|
975
|
+
fusedLocationClient.requestLocationUpdates(
|
|
976
|
+
buildFusedLocationRequest(
|
|
977
|
+
options,
|
|
978
|
+
maxUpdatesOverride = 1,
|
|
979
|
+
includeDistanceFilter = false
|
|
980
|
+
),
|
|
981
|
+
callback,
|
|
982
|
+
Looper.getMainLooper()
|
|
983
|
+
)
|
|
984
|
+
handler.postDelayed(timeoutRunnable, coerceTimeoutMillis(options.timeout))
|
|
985
|
+
} catch (e: SecurityException) {
|
|
986
|
+
complete(PositionResult.Failure(createLocationError(
|
|
987
|
+
PERMISSION_DENIED,
|
|
988
|
+
"Security exception: ${e.message}"
|
|
989
|
+
)))
|
|
990
|
+
} catch (e: Exception) {
|
|
991
|
+
complete(PositionResult.Failure(createLocationError(
|
|
992
|
+
POSITION_UNAVAILABLE,
|
|
993
|
+
"Unable to request fused location: ${e.message}"
|
|
994
|
+
)))
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
407
998
|
// MARK: - Helper Functions - Request Fresh Location
|
|
408
999
|
|
|
409
1000
|
private fun requestFreshLocation(
|
|
410
1001
|
providers: List<String>,
|
|
411
1002
|
options: ParsedOptions,
|
|
412
|
-
resolver: (
|
|
1003
|
+
resolver: (PositionResult) -> Unit
|
|
413
1004
|
) {
|
|
414
1005
|
val id = UUID.randomUUID()
|
|
415
1006
|
val handler = Handler(Looper.getMainLooper())
|
|
@@ -433,21 +1024,22 @@ class NitroGeolocation(
|
|
|
433
1024
|
val remainingTimeoutMillis = request.remainingTimeoutMillis()
|
|
434
1025
|
|
|
435
1026
|
if (provider == null) {
|
|
436
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
437
|
-
createNoLocationProviderError(request.options)
|
|
438
|
-
)
|
|
1027
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1028
|
+
PositionResult.Failure(createNoLocationProviderError(request.options))
|
|
1029
|
+
)
|
|
439
1030
|
return
|
|
440
1031
|
}
|
|
441
1032
|
|
|
442
1033
|
if (remainingTimeoutMillis <= 0L) {
|
|
443
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
444
|
-
createPositionTimeoutError(request.options)
|
|
445
|
-
)
|
|
1034
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1035
|
+
PositionResult.Failure(createPositionTimeoutError(request.options))
|
|
1036
|
+
)
|
|
446
1037
|
return
|
|
447
1038
|
}
|
|
448
1039
|
|
|
449
|
-
//
|
|
450
|
-
|
|
1040
|
+
// Android's getCurrentLocation may resolve a recent historical fix. A maximumAge of 0
|
|
1041
|
+
// means callers explicitly asked us to wait for a fresh provider update.
|
|
1042
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && request.options.maximumAge > 0.0) {
|
|
451
1043
|
requestCurrentLocationModern(provider, requestId, request.handler, remainingTimeoutMillis)
|
|
452
1044
|
} else {
|
|
453
1045
|
requestCurrentLocationLegacy(provider, requestId, request.handler, remainingTimeoutMillis)
|
|
@@ -481,7 +1073,7 @@ class NitroGeolocation(
|
|
|
481
1073
|
if (location != null) {
|
|
482
1074
|
pendingPositionRequests.remove(requestId)
|
|
483
1075
|
val position = locationToPosition(location)
|
|
484
|
-
request.resolver(
|
|
1076
|
+
request.resolver(PositionResult.Success(position))
|
|
485
1077
|
} else {
|
|
486
1078
|
handleProviderFailure(requestId, createLocationError(
|
|
487
1079
|
POSITION_UNAVAILABLE,
|
|
@@ -531,7 +1123,7 @@ class NitroGeolocation(
|
|
|
531
1123
|
val request = pendingPositionRequests.remove(requestId)
|
|
532
1124
|
if (request != null) {
|
|
533
1125
|
val position = locationToPosition(location)
|
|
534
|
-
request.resolver(
|
|
1126
|
+
request.resolver(PositionResult.Success(position))
|
|
535
1127
|
}
|
|
536
1128
|
}
|
|
537
1129
|
oldLocation = location
|
|
@@ -579,7 +1171,7 @@ class NitroGeolocation(
|
|
|
579
1171
|
}
|
|
580
1172
|
}
|
|
581
1173
|
|
|
582
|
-
private fun handleProviderFailure(requestId: UUID, error:
|
|
1174
|
+
private fun handleProviderFailure(requestId: UUID, error: LocationError) {
|
|
583
1175
|
val request = pendingPositionRequests[requestId] ?: return
|
|
584
1176
|
|
|
585
1177
|
request.cancellationSignal?.cancel()
|
|
@@ -588,9 +1180,9 @@ class NitroGeolocation(
|
|
|
588
1180
|
|
|
589
1181
|
if (request.providerIndex < request.providers.size) {
|
|
590
1182
|
if (request.remainingTimeoutMillis() <= 0L) {
|
|
591
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
592
|
-
createPositionTimeoutError(request.options)
|
|
593
|
-
)
|
|
1183
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1184
|
+
PositionResult.Failure(createPositionTimeoutError(request.options))
|
|
1185
|
+
)
|
|
594
1186
|
return
|
|
595
1187
|
}
|
|
596
1188
|
|
|
@@ -598,7 +1190,7 @@ class NitroGeolocation(
|
|
|
598
1190
|
return
|
|
599
1191
|
}
|
|
600
1192
|
|
|
601
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1193
|
+
pendingPositionRequests.remove(requestId)?.resolver(PositionResult.Failure(error))
|
|
602
1194
|
}
|
|
603
1195
|
|
|
604
1196
|
private fun selectBestLocation(newLocation: Location, currentBest: Location?): Location {
|
|
@@ -633,29 +1225,27 @@ class NitroGeolocation(
|
|
|
633
1225
|
request.cancellationSignal?.cancel()
|
|
634
1226
|
request.cancellationSignal = null
|
|
635
1227
|
|
|
636
|
-
pendingPositionRequests.remove(requestId)?.resolver(
|
|
637
|
-
createPositionTimeoutError(request.options)
|
|
638
|
-
)
|
|
1228
|
+
pendingPositionRequests.remove(requestId)?.resolver(
|
|
1229
|
+
PositionResult.Failure(createPositionTimeoutError(request.options))
|
|
1230
|
+
)
|
|
639
1231
|
}
|
|
640
1232
|
}
|
|
641
1233
|
|
|
642
1234
|
// MARK: - Helper Functions - Watch Position
|
|
643
1235
|
|
|
644
1236
|
private fun startWatchingLocation() {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1237
|
+
if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
|
|
1238
|
+
notifyWatchPlayServicesUnavailable()
|
|
1239
|
+
return
|
|
1240
|
+
}
|
|
649
1241
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
smallestInterval = minOf(smallestInterval, subscription.options.interval)
|
|
655
|
-
smallestDistanceFilter = minOf(smallestDistanceFilter, subscription.options.distanceFilter.toFloat())
|
|
1242
|
+
if (requiresPlayServices()) {
|
|
1243
|
+
startWatchingFusedLocation()
|
|
1244
|
+
return
|
|
656
1245
|
}
|
|
657
1246
|
|
|
658
|
-
val
|
|
1247
|
+
val mergedOptions = mergeWatchOptions()
|
|
1248
|
+
val provider = getValidProvider(mergedOptions)
|
|
659
1249
|
if (provider == null) {
|
|
660
1250
|
notifyWatchProviderUnavailable()
|
|
661
1251
|
return
|
|
@@ -665,16 +1255,12 @@ class NitroGeolocation(
|
|
|
665
1255
|
val listener = object : LocationListener {
|
|
666
1256
|
override fun onLocationChanged(location: Location) {
|
|
667
1257
|
val position = locationToPosition(location)
|
|
668
|
-
|
|
669
|
-
// Notify all subscribers
|
|
670
|
-
for ((_, subscription) in watchSubscriptions) {
|
|
671
|
-
subscription.success(position)
|
|
672
|
-
}
|
|
1258
|
+
deliverWatchPosition(position)
|
|
673
1259
|
}
|
|
674
1260
|
|
|
675
1261
|
override fun onProviderDisabled(provider: String) {
|
|
676
1262
|
val error = LocationError(
|
|
677
|
-
code =
|
|
1263
|
+
code = SETTINGS_NOT_SATISFIED,
|
|
678
1264
|
message = "Provider disabled: $provider"
|
|
679
1265
|
)
|
|
680
1266
|
|
|
@@ -693,8 +1279,8 @@ class NitroGeolocation(
|
|
|
693
1279
|
try {
|
|
694
1280
|
locationManager.requestLocationUpdates(
|
|
695
1281
|
provider,
|
|
696
|
-
|
|
697
|
-
|
|
1282
|
+
mergedOptions.interval.toLong(),
|
|
1283
|
+
mergedOptions.distanceFilter.toFloat(),
|
|
698
1284
|
listener,
|
|
699
1285
|
Looper.getMainLooper()
|
|
700
1286
|
)
|
|
@@ -710,15 +1296,157 @@ class NitroGeolocation(
|
|
|
710
1296
|
}
|
|
711
1297
|
}
|
|
712
1298
|
|
|
1299
|
+
private fun startWatchingFusedLocation() {
|
|
1300
|
+
val mergedOptions = mergeWatchOptions()
|
|
1301
|
+
val callback = object : LocationCallback() {
|
|
1302
|
+
override fun onLocationResult(result: LocationResult) {
|
|
1303
|
+
val location = result.lastLocation ?: return
|
|
1304
|
+
deliverWatchPosition(locationToPosition(location))
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
fusedWatchLocationCallback = callback
|
|
1309
|
+
|
|
1310
|
+
try {
|
|
1311
|
+
fusedLocationClient.requestLocationUpdates(
|
|
1312
|
+
buildFusedLocationRequest(mergedOptions),
|
|
1313
|
+
callback,
|
|
1314
|
+
Looper.getMainLooper()
|
|
1315
|
+
)
|
|
1316
|
+
} catch (e: SecurityException) {
|
|
1317
|
+
val error = LocationError(
|
|
1318
|
+
code = PERMISSION_DENIED,
|
|
1319
|
+
message = "Permission denied: ${e.message}"
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1323
|
+
subscription.error?.invoke(error)
|
|
1324
|
+
}
|
|
1325
|
+
} catch (e: Exception) {
|
|
1326
|
+
val error = LocationError(
|
|
1327
|
+
code = POSITION_UNAVAILABLE,
|
|
1328
|
+
message = "Unable to request fused location updates: ${e.message}"
|
|
1329
|
+
)
|
|
1330
|
+
|
|
1331
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1332
|
+
subscription.error?.invoke(error)
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
private fun mergeWatchOptions(): ParsedOptions {
|
|
1338
|
+
var androidAccuracy: AndroidAccuracyResolution? = null
|
|
1339
|
+
var smallestInterval = Double.MAX_VALUE
|
|
1340
|
+
var smallestFastestInterval = Double.MAX_VALUE
|
|
1341
|
+
var smallestDistanceFilter = Double.MAX_VALUE
|
|
1342
|
+
var granularity = AndroidGranularity.PERMISSION
|
|
1343
|
+
var waitForAccurateLocation = false
|
|
1344
|
+
var maxUpdateAge: Double? = null
|
|
1345
|
+
var smallestMaxUpdateDelay = Double.MAX_VALUE
|
|
1346
|
+
|
|
1347
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1348
|
+
androidAccuracy = mostDemandingAndroidAccuracy(
|
|
1349
|
+
androidAccuracy,
|
|
1350
|
+
subscription.options.androidAccuracy
|
|
1351
|
+
)
|
|
1352
|
+
smallestInterval = minOf(smallestInterval, subscription.options.interval)
|
|
1353
|
+
smallestFastestInterval = minOf(
|
|
1354
|
+
smallestFastestInterval,
|
|
1355
|
+
subscription.options.fastestInterval
|
|
1356
|
+
)
|
|
1357
|
+
smallestDistanceFilter = minOf(
|
|
1358
|
+
smallestDistanceFilter,
|
|
1359
|
+
subscription.options.distanceFilter
|
|
1360
|
+
)
|
|
1361
|
+
granularity = mergeWatchGranularity(granularity, subscription.options.granularity)
|
|
1362
|
+
waitForAccurateLocation = waitForAccurateLocation ||
|
|
1363
|
+
subscription.options.waitForAccurateLocation
|
|
1364
|
+
maxUpdateAge = mergeNullableMinimum(maxUpdateAge, subscription.options.maxUpdateAge)
|
|
1365
|
+
smallestMaxUpdateDelay = minOf(
|
|
1366
|
+
smallestMaxUpdateDelay,
|
|
1367
|
+
subscription.options.maxUpdateDelay
|
|
1368
|
+
)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
return ParsedOptions(
|
|
1372
|
+
timeout = Double.POSITIVE_INFINITY,
|
|
1373
|
+
maximumAge = 0.0,
|
|
1374
|
+
androidAccuracy = androidAccuracy ?: resolveAndroidAccuracy(null, enableHighAccuracy = false),
|
|
1375
|
+
interval = smallestInterval,
|
|
1376
|
+
fastestInterval = smallestFastestInterval,
|
|
1377
|
+
distanceFilter = smallestDistanceFilter,
|
|
1378
|
+
granularity = granularity,
|
|
1379
|
+
waitForAccurateLocation = waitForAccurateLocation,
|
|
1380
|
+
maxUpdateAge = maxUpdateAge,
|
|
1381
|
+
maxUpdateDelay = if (smallestMaxUpdateDelay == Double.MAX_VALUE) 0.0 else smallestMaxUpdateDelay,
|
|
1382
|
+
maxUpdates = null
|
|
1383
|
+
)
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
private fun deliverWatchPosition(position: GeolocationResponse) {
|
|
1387
|
+
val tokensToRemove = mutableListOf<String>()
|
|
1388
|
+
|
|
1389
|
+
for ((token, subscription) in watchSubscriptions) {
|
|
1390
|
+
subscription.success(position)
|
|
1391
|
+
subscription.deliveredUpdates += 1
|
|
1392
|
+
|
|
1393
|
+
val maxUpdates = subscription.options.maxUpdates
|
|
1394
|
+
if (maxUpdates != null && subscription.deliveredUpdates >= maxUpdates) {
|
|
1395
|
+
tokensToRemove.add(token)
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
for (token in tokensToRemove) {
|
|
1400
|
+
watchSubscriptions.remove(token)
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (tokensToRemove.isNotEmpty()) {
|
|
1404
|
+
if (watchSubscriptions.isEmpty()) {
|
|
1405
|
+
stopWatchingLocation()
|
|
1406
|
+
} else {
|
|
1407
|
+
restartWatchingLocation()
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
713
1412
|
private fun notifyWatchProviderUnavailable() {
|
|
714
1413
|
for ((_, subscription) in watchSubscriptions) {
|
|
715
1414
|
subscription.error?.invoke(LocationError(
|
|
716
|
-
code =
|
|
1415
|
+
code = SETTINGS_NOT_SATISFIED,
|
|
717
1416
|
message = getNoLocationProviderMessage(subscription.options)
|
|
718
1417
|
))
|
|
719
1418
|
}
|
|
720
1419
|
}
|
|
721
1420
|
|
|
1421
|
+
private fun notifyWatchPlayServicesUnavailable() {
|
|
1422
|
+
for ((_, subscription) in watchSubscriptions) {
|
|
1423
|
+
subscription.error?.invoke(createPlayServicesUnavailableError())
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
private fun mergeWatchGranularity(
|
|
1428
|
+
current: AndroidGranularity,
|
|
1429
|
+
next: AndroidGranularity
|
|
1430
|
+
): AndroidGranularity {
|
|
1431
|
+
return when {
|
|
1432
|
+
current == AndroidGranularity.COARSE || next == AndroidGranularity.COARSE -> {
|
|
1433
|
+
AndroidGranularity.COARSE
|
|
1434
|
+
}
|
|
1435
|
+
current == AndroidGranularity.FINE || next == AndroidGranularity.FINE -> {
|
|
1436
|
+
AndroidGranularity.FINE
|
|
1437
|
+
}
|
|
1438
|
+
else -> AndroidGranularity.PERMISSION
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
private fun mergeNullableMinimum(current: Double?, next: Double?): Double? {
|
|
1443
|
+
return when {
|
|
1444
|
+
current == null -> next
|
|
1445
|
+
next == null -> current
|
|
1446
|
+
else -> minOf(current, next)
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
722
1450
|
private fun stopWatchingLocation() {
|
|
723
1451
|
watchLocationListener?.let { listener ->
|
|
724
1452
|
try {
|
|
@@ -727,59 +1455,169 @@ class NitroGeolocation(
|
|
|
727
1455
|
// Ignore
|
|
728
1456
|
}
|
|
729
1457
|
}
|
|
1458
|
+
fusedWatchLocationCallback?.let { callback ->
|
|
1459
|
+
try {
|
|
1460
|
+
fusedLocationClient.removeLocationUpdates(callback)
|
|
1461
|
+
} catch (e: Exception) {
|
|
1462
|
+
// Ignore
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
730
1465
|
watchLocationListener = null
|
|
1466
|
+
fusedWatchLocationCallback = null
|
|
731
1467
|
currentWatchProvider = null
|
|
732
1468
|
}
|
|
733
1469
|
|
|
1470
|
+
private fun restartWatchingLocation() {
|
|
1471
|
+
stopWatchingLocation()
|
|
1472
|
+
startWatchingLocation()
|
|
1473
|
+
}
|
|
1474
|
+
|
|
734
1475
|
// MARK: - Helper Functions - Conversion
|
|
735
1476
|
|
|
736
1477
|
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
|
-
}
|
|
1478
|
+
lastLocation = location
|
|
757
1479
|
|
|
758
1480
|
val coords = GeolocationCoordinates(
|
|
759
1481
|
latitude = location.latitude,
|
|
760
1482
|
longitude = location.longitude,
|
|
761
|
-
altitude =
|
|
1483
|
+
altitude = location.altitudeValue(),
|
|
762
1484
|
accuracy = location.accuracy.toDouble(),
|
|
763
|
-
altitudeAccuracy =
|
|
764
|
-
heading =
|
|
765
|
-
speed =
|
|
1485
|
+
altitudeAccuracy = location.altitudeAccuracyValue(),
|
|
1486
|
+
heading = location.headingValue(),
|
|
1487
|
+
speed = location.speedValue()
|
|
766
1488
|
)
|
|
767
1489
|
|
|
768
1490
|
return GeolocationResponse(
|
|
769
1491
|
coords = coords,
|
|
770
|
-
timestamp = location.time.toDouble()
|
|
1492
|
+
timestamp = location.time.toDouble(),
|
|
1493
|
+
mocked = location.isMocked(),
|
|
1494
|
+
provider = location.providerUsed()
|
|
771
1495
|
)
|
|
772
1496
|
}
|
|
773
1497
|
|
|
774
|
-
private fun
|
|
775
|
-
|
|
1498
|
+
private fun geocodedAddressToLocation(address: Address): GeocodedLocation? {
|
|
1499
|
+
if (!address.hasLatitude() || !address.hasLongitude()) {
|
|
1500
|
+
return null
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
return GeocodedLocation(
|
|
1504
|
+
latitude = address.latitude,
|
|
1505
|
+
longitude = address.longitude,
|
|
1506
|
+
accuracy = null
|
|
1507
|
+
)
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
private fun addressToReverseGeocodedAddress(address: Address): ReverseGeocodedAddress {
|
|
1511
|
+
return ReverseGeocodedAddress(
|
|
1512
|
+
country = address.countryName.nonBlankOrNull(),
|
|
1513
|
+
region = address.adminArea.nonBlankOrNull(),
|
|
1514
|
+
city = (address.locality ?: address.subAdminArea).nonBlankOrNull(),
|
|
1515
|
+
district = address.subLocality.nonBlankOrNull(),
|
|
1516
|
+
street = formatStreet(address),
|
|
1517
|
+
postalCode = address.postalCode.nonBlankOrNull(),
|
|
1518
|
+
formattedAddress = formatAddressLines(address)
|
|
1519
|
+
)
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
private fun formatStreet(address: Address): String? {
|
|
1523
|
+
return listOf(address.subThoroughfare, address.thoroughfare)
|
|
1524
|
+
.mapNotNull { it.nonBlankOrNull() }
|
|
1525
|
+
.joinToString(" ")
|
|
1526
|
+
.nonBlankOrNull()
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
private fun formatAddressLines(address: Address): String? {
|
|
1530
|
+
if (address.maxAddressLineIndex < 0) {
|
|
1531
|
+
return null
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
return (0..address.maxAddressLineIndex)
|
|
1535
|
+
.mapNotNull { index -> address.getAddressLine(index).nonBlankOrNull() }
|
|
1536
|
+
.joinToString(", ")
|
|
1537
|
+
.nonBlankOrNull()
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
private fun validateGeocodingCoordinates(coords: GeocodingCoordinates): LocationError? {
|
|
1541
|
+
if (!coords.latitude.isFinite() || coords.latitude < -90.0 || coords.latitude > 90.0) {
|
|
1542
|
+
return createLocationError(
|
|
1543
|
+
INTERNAL_ERROR,
|
|
1544
|
+
"latitude must be a finite number between -90 and 90."
|
|
1545
|
+
)
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
if (!coords.longitude.isFinite() || coords.longitude < -180.0 || coords.longitude > 180.0) {
|
|
1549
|
+
return createLocationError(
|
|
1550
|
+
INTERNAL_ERROR,
|
|
1551
|
+
"longitude must be a finite number between -180 and 180."
|
|
1552
|
+
)
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
return null
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
private fun <T> runGeocoderOperation(
|
|
1559
|
+
success: (Array<T>) -> Unit,
|
|
1560
|
+
error: ((LocationError) -> Unit)?,
|
|
1561
|
+
failurePrefix: String,
|
|
1562
|
+
operation: () -> Array<T>
|
|
1563
|
+
) {
|
|
1564
|
+
if (!Geocoder.isPresent()) {
|
|
1565
|
+
error?.invoke(createLocationError(
|
|
1566
|
+
POSITION_UNAVAILABLE,
|
|
1567
|
+
"Platform geocoder is not available."
|
|
1568
|
+
))
|
|
1569
|
+
return
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
val handler = Handler(Looper.getMainLooper())
|
|
1573
|
+
|
|
1574
|
+
Thread {
|
|
1575
|
+
try {
|
|
1576
|
+
val results = operation()
|
|
1577
|
+
handler.post { success(results) }
|
|
1578
|
+
} catch (e: IOException) {
|
|
1579
|
+
handler.post {
|
|
1580
|
+
error?.invoke(createLocationError(
|
|
1581
|
+
POSITION_UNAVAILABLE,
|
|
1582
|
+
"$failurePrefix: ${e.message ?: "geocoder service unavailable"}"
|
|
1583
|
+
))
|
|
1584
|
+
}
|
|
1585
|
+
} catch (e: Exception) {
|
|
1586
|
+
handler.post {
|
|
1587
|
+
error?.invoke(createLocationError(
|
|
1588
|
+
INTERNAL_ERROR,
|
|
1589
|
+
"$failurePrefix: ${e.message ?: "unknown error"}"
|
|
1590
|
+
))
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}.start()
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
private fun createLocationAvailability(
|
|
1597
|
+
available: Boolean,
|
|
1598
|
+
reason: String?
|
|
1599
|
+
): LocationAvailability {
|
|
1600
|
+
return LocationAvailability(
|
|
1601
|
+
available = available,
|
|
1602
|
+
reason = reason
|
|
1603
|
+
)
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
private fun createLocationError(code: Double, message: String): LocationError {
|
|
1607
|
+
return LocationError(
|
|
776
1608
|
code = code,
|
|
777
1609
|
message = message
|
|
778
1610
|
)
|
|
779
|
-
return GeolocationErrorException(locationError)
|
|
780
1611
|
}
|
|
781
1612
|
|
|
782
|
-
private fun
|
|
1613
|
+
private fun createPlayServicesUnavailableError(): LocationError {
|
|
1614
|
+
return createLocationError(
|
|
1615
|
+
PLAY_SERVICE_NOT_AVAILABLE,
|
|
1616
|
+
"Google Play Services location provider is not available."
|
|
1617
|
+
)
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
private fun createPositionTimeoutError(options: ParsedOptions): LocationError {
|
|
783
1621
|
val timeoutSeconds = options.timeout / 1000.0
|
|
784
1622
|
val message = String.format("Unable to fetch location within %.1fs.", timeoutSeconds)
|
|
785
1623
|
return createLocationError(TIMEOUT, message)
|
|
@@ -805,8 +1643,57 @@ class NitroGeolocation(
|
|
|
805
1643
|
}
|
|
806
1644
|
}
|
|
807
1645
|
|
|
1646
|
+
private fun buildFusedLocationRequest(
|
|
1647
|
+
options: ParsedOptions,
|
|
1648
|
+
maxUpdatesOverride: Int? = null,
|
|
1649
|
+
includeDistanceFilter: Boolean = true
|
|
1650
|
+
): GmsLocationRequest {
|
|
1651
|
+
val builder = GmsLocationRequest
|
|
1652
|
+
.Builder(options.androidAccuracy.gmsPriority(), coercePositiveMillis(options.interval))
|
|
1653
|
+
.setMinUpdateIntervalMillis(coercePositiveMillis(options.fastestInterval))
|
|
1654
|
+
.setGranularity(options.granularity.gmsGranularity())
|
|
1655
|
+
.setWaitForAccurateLocation(options.waitForAccurateLocation)
|
|
1656
|
+
.setMaxUpdateDelayMillis(coerceNonNegativeMillis(options.maxUpdateDelay))
|
|
1657
|
+
|
|
1658
|
+
if (includeDistanceFilter) {
|
|
1659
|
+
builder.setMinUpdateDistanceMeters(options.distanceFilter.toFloat())
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
options.maxUpdateAge?.let { value ->
|
|
1663
|
+
builder.setMaxUpdateAgeMillis(coerceNonNegativeMillis(value))
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
val maxUpdates = maxUpdatesOverride ?: options.maxUpdates
|
|
1667
|
+
if (maxUpdates != null) {
|
|
1668
|
+
builder.setMaxUpdates(maxUpdates)
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
return builder.build()
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
private fun coercePositiveMillis(value: Double): Long {
|
|
1675
|
+
return when {
|
|
1676
|
+
value.isNaN() || value <= 0.0 -> 1L
|
|
1677
|
+
value.isInfinite() || value >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
|
|
1678
|
+
else -> value.toLong()
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
private fun coerceNonNegativeMillis(value: Double): Long {
|
|
1683
|
+
return when {
|
|
1684
|
+
value.isNaN() || value <= 0.0 -> 0L
|
|
1685
|
+
value.isInfinite() || value >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
|
|
1686
|
+
else -> value.toLong()
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
808
1690
|
companion object {
|
|
809
1691
|
private const val PERMISSION_REQUEST_CODE = 8947
|
|
1692
|
+
private const val GEOCODER_MAX_RESULTS = 5
|
|
810
1693
|
private const val TWO_MINUTES_MS = 2 * 60 * 1000L
|
|
811
1694
|
}
|
|
812
1695
|
}
|
|
1696
|
+
|
|
1697
|
+
private fun String?.nonBlankOrNull(): String? {
|
|
1698
|
+
return this?.trim()?.takeIf { it.isNotEmpty() }
|
|
1699
|
+
}
|