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
|
@@ -2,28 +2,6 @@ import Foundation
|
|
|
2
2
|
import CoreLocation
|
|
3
3
|
import NitroModules
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Swift Error wrapper for LocationError struct.
|
|
7
|
-
*/
|
|
8
|
-
private struct GeolocationErrorWrapper: Error, LocalizedError {
|
|
9
|
-
let locationError: LocationError
|
|
10
|
-
|
|
11
|
-
var errorDescription: String? {
|
|
12
|
-
return locationError.message
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
var localizedDescription: String {
|
|
16
|
-
return locationError.message
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
init(code: Int, message: String) {
|
|
20
|
-
self.locationError = LocationError(
|
|
21
|
-
code: Double(code),
|
|
22
|
-
message: message
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
5
|
/**
|
|
28
6
|
* LocationManager Delegate class to handle CLLocationManager callbacks.
|
|
29
7
|
*/
|
|
@@ -46,13 +24,21 @@ private class LocationManagerDelegate: NSObject, CLLocationManagerDelegate {
|
|
|
46
24
|
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
47
25
|
geolocation?.handleLocationError(error)
|
|
48
26
|
}
|
|
27
|
+
|
|
28
|
+
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
|
|
29
|
+
geolocation?.handleHeadingUpdate(newHeading)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func locationManagerShouldDisplayHeadingCalibration(_ manager: CLLocationManager) -> Bool {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
49
35
|
}
|
|
50
36
|
|
|
51
37
|
/**
|
|
52
|
-
*
|
|
38
|
+
* Geolocation implementation for the native Modern API contract.
|
|
53
39
|
*
|
|
54
40
|
* Key features:
|
|
55
|
-
* -
|
|
41
|
+
* - Callback-based native permission and getCurrentPosition for structured errors
|
|
56
42
|
* - Token-based watch subscriptions (functions are first-class!)
|
|
57
43
|
* - WatchPositionResult discriminated union
|
|
58
44
|
* - Automatic subscription management
|
|
@@ -66,17 +52,24 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
66
52
|
let accuracy: CLLocationAccuracy
|
|
67
53
|
let distanceFilter: CLLocationDistance
|
|
68
54
|
let useSignificantChanges: Bool
|
|
55
|
+
let activityType: CLActivityType?
|
|
56
|
+
let pausesLocationUpdatesAutomatically: Bool?
|
|
57
|
+
let showsBackgroundLocationIndicator: Bool?
|
|
69
58
|
|
|
70
59
|
static let DEFAULT_TIMEOUT: Double = 10 * 60 * 1000 // 10 minutes in ms
|
|
71
60
|
static let DEFAULT_MAXIMUM_AGE: Double = 0
|
|
72
61
|
|
|
73
|
-
static func parse(
|
|
62
|
+
static func parse(
|
|
63
|
+
from options: LocationRequestOptions?,
|
|
64
|
+
defaultMaximumAge: Double = DEFAULT_MAXIMUM_AGE
|
|
65
|
+
) -> ParsedOptions {
|
|
74
66
|
let timeout = options?.timeout ?? DEFAULT_TIMEOUT
|
|
75
|
-
let maximumAge = options?.maximumAge ??
|
|
67
|
+
let maximumAge = options?.maximumAge ?? defaultMaximumAge
|
|
76
68
|
let enableHighAccuracy = options?.enableHighAccuracy ?? false
|
|
77
|
-
let accuracy =
|
|
78
|
-
|
|
79
|
-
:
|
|
69
|
+
let accuracy = resolveAccuracy(
|
|
70
|
+
preset: options?.accuracy?.ios,
|
|
71
|
+
enableHighAccuracy: enableHighAccuracy
|
|
72
|
+
)
|
|
80
73
|
let distanceFilter = options?.distanceFilter ?? kCLDistanceFilterNone
|
|
81
74
|
let useSignificantChanges = options?.useSignificantChanges ?? false
|
|
82
75
|
|
|
@@ -85,9 +78,63 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
85
78
|
maximumAge: maximumAge,
|
|
86
79
|
accuracy: accuracy,
|
|
87
80
|
distanceFilter: distanceFilter,
|
|
88
|
-
useSignificantChanges: useSignificantChanges
|
|
81
|
+
useSignificantChanges: useSignificantChanges,
|
|
82
|
+
activityType: resolveActivityType(options?.activityType),
|
|
83
|
+
pausesLocationUpdatesAutomatically: options?.pausesLocationUpdatesAutomatically,
|
|
84
|
+
showsBackgroundLocationIndicator: options?.showsBackgroundLocationIndicator
|
|
89
85
|
)
|
|
90
86
|
}
|
|
87
|
+
|
|
88
|
+
static func parseLastKnown(from options: LocationRequestOptions?) -> ParsedOptions {
|
|
89
|
+
return parse(from: options, defaultMaximumAge: Double.infinity)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private static func resolveAccuracy(
|
|
93
|
+
preset: IOSAccuracyPreset?,
|
|
94
|
+
enableHighAccuracy: Bool
|
|
95
|
+
) -> CLLocationAccuracy {
|
|
96
|
+
guard let preset else {
|
|
97
|
+
return enableHighAccuracy
|
|
98
|
+
? kCLLocationAccuracyBest
|
|
99
|
+
: kCLLocationAccuracyHundredMeters
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch preset {
|
|
103
|
+
case .bestfornavigation:
|
|
104
|
+
return kCLLocationAccuracyBestForNavigation
|
|
105
|
+
case .best:
|
|
106
|
+
return kCLLocationAccuracyBest
|
|
107
|
+
case .nearesttenmeters:
|
|
108
|
+
return kCLLocationAccuracyNearestTenMeters
|
|
109
|
+
case .hundredmeters:
|
|
110
|
+
return kCLLocationAccuracyHundredMeters
|
|
111
|
+
case .kilometer:
|
|
112
|
+
return kCLLocationAccuracyKilometer
|
|
113
|
+
case .threekilometers:
|
|
114
|
+
return kCLLocationAccuracyThreeKilometers
|
|
115
|
+
case .reduced:
|
|
116
|
+
return kCLLocationAccuracyReduced
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private static func resolveActivityType(_ activityType: IOSActivityType?) -> CLActivityType? {
|
|
121
|
+
guard let activityType else {
|
|
122
|
+
return nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
switch activityType {
|
|
126
|
+
case .other:
|
|
127
|
+
return .other
|
|
128
|
+
case .automotivenavigation:
|
|
129
|
+
return .automotiveNavigation
|
|
130
|
+
case .fitness:
|
|
131
|
+
return .fitness
|
|
132
|
+
case .othernavigation:
|
|
133
|
+
return .otherNavigation
|
|
134
|
+
case .airborne:
|
|
135
|
+
return .airborne
|
|
136
|
+
}
|
|
137
|
+
}
|
|
91
138
|
}
|
|
92
139
|
|
|
93
140
|
// Watch subscription storage (first-class functions!)
|
|
@@ -98,23 +145,49 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
98
145
|
let options: ParsedOptions
|
|
99
146
|
}
|
|
100
147
|
|
|
148
|
+
private struct ParsedHeadingOptions {
|
|
149
|
+
let headingFilter: CLLocationDegrees
|
|
150
|
+
|
|
151
|
+
static func parse(from options: HeadingOptions?) -> ParsedHeadingOptions {
|
|
152
|
+
return ParsedHeadingOptions(
|
|
153
|
+
headingFilter: options?.headingFilter ?? 0
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private struct HeadingRequest {
|
|
159
|
+
let id: UUID
|
|
160
|
+
let success: (Heading) -> Void
|
|
161
|
+
let error: (LocationError) -> Void
|
|
162
|
+
var timer: DispatchSourceTimer?
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private struct HeadingSubscription {
|
|
166
|
+
let token: String
|
|
167
|
+
let success: (Heading) -> Void
|
|
168
|
+
let error: ((LocationError) -> Void)?
|
|
169
|
+
let options: ParsedHeadingOptions
|
|
170
|
+
var lastDeliveredHeading: Double?
|
|
171
|
+
}
|
|
172
|
+
|
|
101
173
|
// MARK: - Properties
|
|
102
174
|
|
|
103
|
-
private var configuration:
|
|
175
|
+
private var configuration: GeolocationConfiguration?
|
|
104
176
|
private var locationManager: CLLocationManager?
|
|
105
177
|
private var locationManagerDelegate: LocationManagerDelegate?
|
|
106
178
|
private var lastLocation: CLLocation?
|
|
107
179
|
private var usingSignificantChanges: Bool = false
|
|
108
180
|
|
|
109
|
-
// Permission
|
|
110
|
-
private var pendingPermissionResolvers: [(
|
|
181
|
+
// Permission callbacks
|
|
182
|
+
private var pendingPermissionResolvers: [(PermissionStatus) -> Void] = []
|
|
111
183
|
|
|
112
184
|
// getCurrentPosition promise resolvers with timeout
|
|
113
185
|
private var pendingPositionRequests: [UUID: PositionRequest] = [:]
|
|
114
186
|
|
|
115
187
|
private struct PositionRequest {
|
|
116
188
|
let id: UUID
|
|
117
|
-
let
|
|
189
|
+
let success: (GeolocationResponse) -> Void
|
|
190
|
+
let error: (LocationError) -> Void
|
|
118
191
|
let options: ParsedOptions
|
|
119
192
|
var timer: DispatchSourceTimer?
|
|
120
193
|
}
|
|
@@ -122,18 +195,27 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
122
195
|
// Watch subscriptions (token -> callback)
|
|
123
196
|
private var watchSubscriptions: [String: WatchSubscription] = [:]
|
|
124
197
|
|
|
198
|
+
// Heading requests/subscriptions
|
|
199
|
+
private var pendingHeadingRequests: [UUID: HeadingRequest] = [:]
|
|
200
|
+
private var headingSubscriptions: [String: HeadingSubscription] = [:]
|
|
201
|
+
private var activeGeocoders: [UUID: CLGeocoder] = [:]
|
|
202
|
+
|
|
125
203
|
// Error codes
|
|
204
|
+
private let INTERNAL_ERROR = -1
|
|
126
205
|
private let PERMISSION_DENIED = 1
|
|
127
206
|
private let POSITION_UNAVAILABLE = 2
|
|
128
207
|
private let TIMEOUT = 3
|
|
208
|
+
private let PLAY_SERVICE_NOT_AVAILABLE = 4
|
|
209
|
+
private let SETTINGS_NOT_SATISFIED = 5
|
|
210
|
+
private let DEFAULT_HEADING_TIMEOUT_MS: Double = 10_000
|
|
129
211
|
|
|
130
212
|
// MARK: - Configuration
|
|
131
213
|
|
|
132
|
-
func setConfiguration(config:
|
|
214
|
+
func setConfiguration(config: GeolocationConfiguration) {
|
|
133
215
|
self.configuration = config
|
|
134
216
|
}
|
|
135
217
|
|
|
136
|
-
// MARK: - Permission API
|
|
218
|
+
// MARK: - Permission API
|
|
137
219
|
|
|
138
220
|
func checkPermission() throws -> Promise<PermissionStatus> {
|
|
139
221
|
return Promise.async {
|
|
@@ -142,9 +224,10 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
142
224
|
}
|
|
143
225
|
}
|
|
144
226
|
|
|
145
|
-
func requestPermission(
|
|
146
|
-
|
|
147
|
-
|
|
227
|
+
func requestPermission(
|
|
228
|
+
success: @escaping (PermissionStatus) -> Void,
|
|
229
|
+
error: ((LocationError) -> Void)?
|
|
230
|
+
) throws -> Void {
|
|
148
231
|
self.initializeLocationManagerIfNeeded()
|
|
149
232
|
|
|
150
233
|
let currentStatus = CLLocationManager.authorizationStatus()
|
|
@@ -152,53 +235,152 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
152
235
|
// Already determined
|
|
153
236
|
if currentStatus != .notDetermined {
|
|
154
237
|
let status = self.mapCLAuthorizationStatus(currentStatus)
|
|
155
|
-
|
|
156
|
-
return
|
|
238
|
+
success(status)
|
|
239
|
+
return
|
|
157
240
|
}
|
|
158
241
|
|
|
159
242
|
// Queue resolver
|
|
160
|
-
self.pendingPermissionResolvers.append
|
|
161
|
-
switch result {
|
|
162
|
-
case .success(let status):
|
|
163
|
-
promise.resolve(withResult: status)
|
|
164
|
-
case .failure(let error):
|
|
165
|
-
promise.reject(withError: error)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
243
|
+
self.pendingPermissionResolvers.append(success)
|
|
168
244
|
|
|
169
245
|
// Request permission
|
|
170
246
|
let authLevel = self.determineAuthorizationLevel()
|
|
171
247
|
self.requestSystemPermission(for: authLevel)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// MARK: - Provider/Settings API
|
|
251
|
+
|
|
252
|
+
func hasServicesEnabled() throws -> Promise<Bool> {
|
|
253
|
+
return Promise.async {
|
|
254
|
+
return CLLocationManager.locationServicesEnabled()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func getProviderStatus() throws -> Promise<LocationProviderStatus> {
|
|
259
|
+
return Promise.async {
|
|
260
|
+
return self.createLocationProviderStatus()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
func getLocationAvailability() throws -> Promise<LocationAvailability> {
|
|
265
|
+
return Promise.async {
|
|
266
|
+
guard CLLocationManager.locationServicesEnabled() else {
|
|
267
|
+
return LocationAvailability(
|
|
268
|
+
available: false,
|
|
269
|
+
reason: "locationServicesDisabled"
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let status = CLLocationManager.authorizationStatus()
|
|
274
|
+
switch status {
|
|
275
|
+
case .authorizedAlways, .authorizedWhenInUse:
|
|
276
|
+
return LocationAvailability(available: true, reason: nil)
|
|
277
|
+
case .notDetermined:
|
|
278
|
+
return LocationAvailability(
|
|
279
|
+
available: false,
|
|
280
|
+
reason: "permissionUndetermined"
|
|
281
|
+
)
|
|
282
|
+
case .denied:
|
|
283
|
+
return LocationAvailability(
|
|
284
|
+
available: false,
|
|
285
|
+
reason: "permissionDenied"
|
|
286
|
+
)
|
|
287
|
+
case .restricted:
|
|
288
|
+
return LocationAvailability(
|
|
289
|
+
available: false,
|
|
290
|
+
reason: "permissionRestricted"
|
|
291
|
+
)
|
|
292
|
+
@unknown default:
|
|
293
|
+
return LocationAvailability(
|
|
294
|
+
available: false,
|
|
295
|
+
reason: "authorizationUnknown"
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
172
300
|
|
|
173
|
-
|
|
301
|
+
func requestLocationSettings(
|
|
302
|
+
success: @escaping (LocationProviderStatus) -> Void,
|
|
303
|
+
error: ((LocationError) -> Void)?,
|
|
304
|
+
options: LocationSettingsOptions?
|
|
305
|
+
) throws -> Void {
|
|
306
|
+
success(createLocationProviderStatus())
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
func getAccuracyAuthorization() throws -> Promise<AccuracyAuthorization> {
|
|
310
|
+
return Promise.async {
|
|
311
|
+
return self.getCurrentAccuracyAuthorizationOnMain()
|
|
312
|
+
}
|
|
174
313
|
}
|
|
175
314
|
|
|
176
|
-
|
|
315
|
+
func requestTemporaryFullAccuracy(
|
|
316
|
+
purposeKey: String,
|
|
317
|
+
success: @escaping (AccuracyAuthorization) -> Void,
|
|
318
|
+
error: ((LocationError) -> Void)?
|
|
319
|
+
) throws -> Void {
|
|
320
|
+
let trimmedPurposeKey = purposeKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
321
|
+
guard !trimmedPurposeKey.isEmpty else {
|
|
322
|
+
error?(createLocationError(
|
|
323
|
+
code: INTERNAL_ERROR,
|
|
324
|
+
message: "purposeKey must not be empty."
|
|
325
|
+
))
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
initializeLocationManagerIfNeeded()
|
|
330
|
+
|
|
331
|
+
DispatchQueue.main.async {
|
|
332
|
+
guard #available(iOS 14.0, *), let manager = self.locationManager else {
|
|
333
|
+
success(.unknown)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if manager.accuracyAuthorization == .fullAccuracy {
|
|
338
|
+
success(.full)
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
manager.requestTemporaryFullAccuracyAuthorization(
|
|
343
|
+
withPurposeKey: trimmedPurposeKey
|
|
344
|
+
) { requestError in
|
|
345
|
+
if let requestError {
|
|
346
|
+
error?(self.createLocationError(
|
|
347
|
+
code: self.INTERNAL_ERROR,
|
|
348
|
+
message: "Unable to request temporary full accuracy: \(requestError.localizedDescription)"
|
|
349
|
+
))
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
success(self.getCurrentAccuracyAuthorization())
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
177
357
|
|
|
178
|
-
|
|
179
|
-
let promise = Promise<GeolocationResponse>()
|
|
358
|
+
// MARK: - Get Current Position
|
|
180
359
|
|
|
360
|
+
func getCurrentPosition(
|
|
361
|
+
success: @escaping (GeolocationResponse) -> Void,
|
|
362
|
+
error: ((LocationError) -> Void)?,
|
|
363
|
+
options: LocationRequestOptions?
|
|
364
|
+
) throws -> Void {
|
|
181
365
|
// Check permission
|
|
182
366
|
let status = CLLocationManager.authorizationStatus()
|
|
183
367
|
if status == .denied || status == .restricted {
|
|
184
368
|
let message = status == .restricted
|
|
185
369
|
? "This application is not authorized to use location services"
|
|
186
370
|
: "User denied access to location services."
|
|
187
|
-
|
|
371
|
+
error?(createLocationError(
|
|
188
372
|
code: self.PERMISSION_DENIED,
|
|
189
373
|
message: message
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
return promise
|
|
374
|
+
))
|
|
375
|
+
return
|
|
193
376
|
}
|
|
194
377
|
|
|
195
378
|
if !CLLocationManager.locationServicesEnabled() {
|
|
196
|
-
|
|
197
|
-
code: self.
|
|
379
|
+
error?(createLocationError(
|
|
380
|
+
code: self.SETTINGS_NOT_SATISFIED,
|
|
198
381
|
message: "Location services disabled."
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
return promise
|
|
382
|
+
))
|
|
383
|
+
return
|
|
202
384
|
}
|
|
203
385
|
|
|
204
386
|
self.initializeLocationManagerIfNeeded()
|
|
@@ -206,24 +388,20 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
206
388
|
let parsedOptions = ParsedOptions.parse(from: options)
|
|
207
389
|
|
|
208
390
|
// Check cached location
|
|
209
|
-
if let cached = self.
|
|
210
|
-
|
|
391
|
+
if let cached = self.getBestCachedLocation(options: parsedOptions) {
|
|
392
|
+
self.lastLocation = cached
|
|
211
393
|
let position = self.locationToPosition(cached)
|
|
212
|
-
|
|
213
|
-
return
|
|
394
|
+
success(position)
|
|
395
|
+
return
|
|
214
396
|
}
|
|
215
397
|
|
|
216
398
|
// Create position request
|
|
217
399
|
let id = UUID()
|
|
218
400
|
var request = PositionRequest(
|
|
219
401
|
id: id,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
promise.resolve(withResult: response)
|
|
224
|
-
case .failure(let error):
|
|
225
|
-
promise.reject(withError: error)
|
|
226
|
-
}
|
|
402
|
+
success: success,
|
|
403
|
+
error: { locationError in
|
|
404
|
+
error?(locationError)
|
|
227
405
|
},
|
|
228
406
|
options: parsedOptions,
|
|
229
407
|
timer: nil
|
|
@@ -243,8 +421,203 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
243
421
|
// Update configuration and start monitoring
|
|
244
422
|
self.updateLocationManagerConfiguration()
|
|
245
423
|
self.startMonitoring()
|
|
424
|
+
}
|
|
246
425
|
|
|
247
|
-
|
|
426
|
+
func getLastKnownPosition(
|
|
427
|
+
success: @escaping (GeolocationResponse) -> Void,
|
|
428
|
+
error: ((LocationError) -> Void)?,
|
|
429
|
+
options: LocationRequestOptions?
|
|
430
|
+
) throws -> Void {
|
|
431
|
+
let status = CLLocationManager.authorizationStatus()
|
|
432
|
+
if status == .denied || status == .restricted {
|
|
433
|
+
let message = status == .restricted
|
|
434
|
+
? "This application is not authorized to use location services"
|
|
435
|
+
: "User denied access to location services."
|
|
436
|
+
error?(createLocationError(
|
|
437
|
+
code: self.PERMISSION_DENIED,
|
|
438
|
+
message: message
|
|
439
|
+
))
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let parsedOptions = ParsedOptions.parseLastKnown(from: options)
|
|
444
|
+
guard let cached = self.getBestCachedLocation(options: parsedOptions) else {
|
|
445
|
+
error?(createLocationError(
|
|
446
|
+
code: self.POSITION_UNAVAILABLE,
|
|
447
|
+
message: "No cached location available."
|
|
448
|
+
))
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
self.lastLocation = cached
|
|
453
|
+
success(self.locationToPosition(cached))
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// MARK: - Geocoding
|
|
457
|
+
|
|
458
|
+
func geocode(
|
|
459
|
+
address: String,
|
|
460
|
+
success: @escaping ([GeocodedLocation]) -> Void,
|
|
461
|
+
error: ((LocationError) -> Void)?
|
|
462
|
+
) throws -> Void {
|
|
463
|
+
let query = address.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
464
|
+
guard !query.isEmpty else {
|
|
465
|
+
error?(createLocationError(
|
|
466
|
+
code: INTERNAL_ERROR,
|
|
467
|
+
message: "address must not be empty."
|
|
468
|
+
))
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
DispatchQueue.main.async {
|
|
473
|
+
let id = UUID()
|
|
474
|
+
let geocoder = CLGeocoder()
|
|
475
|
+
self.activeGeocoders[id] = geocoder
|
|
476
|
+
|
|
477
|
+
geocoder.geocodeAddressString(query) { [weak self] placemarks, geocodeError in
|
|
478
|
+
guard let self else { return }
|
|
479
|
+
|
|
480
|
+
DispatchQueue.main.async {
|
|
481
|
+
self.activeGeocoders.removeValue(forKey: id)
|
|
482
|
+
|
|
483
|
+
if let geocodeError {
|
|
484
|
+
if self.isNoGeocoderResult(geocodeError) {
|
|
485
|
+
success([])
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
error?(self.createGeocoderError(
|
|
490
|
+
geocodeError,
|
|
491
|
+
messagePrefix: "Unable to geocode address"
|
|
492
|
+
))
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let locations = (placemarks ?? []).compactMap {
|
|
497
|
+
self.placemarkToGeocodedLocation($0)
|
|
498
|
+
}
|
|
499
|
+
success(locations)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
func reverseGeocode(
|
|
506
|
+
coords: GeocodingCoordinates,
|
|
507
|
+
success: @escaping ([ReverseGeocodedAddress]) -> Void,
|
|
508
|
+
error: ((LocationError) -> Void)?
|
|
509
|
+
) throws -> Void {
|
|
510
|
+
if let validationError = validateGeocodingCoordinates(coords) {
|
|
511
|
+
error?(validationError)
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
DispatchQueue.main.async {
|
|
516
|
+
let id = UUID()
|
|
517
|
+
let geocoder = CLGeocoder()
|
|
518
|
+
self.activeGeocoders[id] = geocoder
|
|
519
|
+
|
|
520
|
+
let location = CLLocation(
|
|
521
|
+
latitude: coords.latitude,
|
|
522
|
+
longitude: coords.longitude
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
geocoder.reverseGeocodeLocation(location) { [weak self] placemarks, geocodeError in
|
|
526
|
+
guard let self else { return }
|
|
527
|
+
|
|
528
|
+
DispatchQueue.main.async {
|
|
529
|
+
self.activeGeocoders.removeValue(forKey: id)
|
|
530
|
+
|
|
531
|
+
if let geocodeError {
|
|
532
|
+
if self.isNoGeocoderResult(geocodeError) {
|
|
533
|
+
success([])
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
error?(self.createGeocoderError(
|
|
538
|
+
geocodeError,
|
|
539
|
+
messagePrefix: "Unable to reverse geocode coordinates"
|
|
540
|
+
))
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let addresses = (placemarks ?? []).map {
|
|
545
|
+
self.placemarkToReverseGeocodedAddress($0)
|
|
546
|
+
}
|
|
547
|
+
success(addresses)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// MARK: - Heading
|
|
554
|
+
|
|
555
|
+
func getHeading(
|
|
556
|
+
success: @escaping (Heading) -> Void,
|
|
557
|
+
error: ((LocationError) -> Void)?
|
|
558
|
+
) throws -> Void {
|
|
559
|
+
guard validateHeadingAvailability(error: error) else { return }
|
|
560
|
+
|
|
561
|
+
initializeLocationManagerIfNeeded()
|
|
562
|
+
|
|
563
|
+
let id = UUID()
|
|
564
|
+
var request = HeadingRequest(
|
|
565
|
+
id: id,
|
|
566
|
+
success: success,
|
|
567
|
+
error: { headingError in
|
|
568
|
+
error?(headingError)
|
|
569
|
+
},
|
|
570
|
+
timer: nil
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
let timer = DispatchSource.makeTimerSource(queue: .main)
|
|
574
|
+
timer.schedule(deadline: .now() + DEFAULT_HEADING_TIMEOUT_MS / 1000.0)
|
|
575
|
+
timer.setEventHandler { [weak self] in
|
|
576
|
+
self?.handleHeadingTimeout(requestId: id)
|
|
577
|
+
}
|
|
578
|
+
timer.resume()
|
|
579
|
+
request.timer = timer
|
|
580
|
+
|
|
581
|
+
pendingHeadingRequests[id] = request
|
|
582
|
+
updateHeadingConfiguration()
|
|
583
|
+
startHeadingMonitoring()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
func watchHeading(
|
|
587
|
+
success: @escaping (Heading) -> Void,
|
|
588
|
+
error: ((LocationError) -> Void)?,
|
|
589
|
+
options: HeadingOptions?
|
|
590
|
+
) throws -> String {
|
|
591
|
+
let token = UUID().uuidString
|
|
592
|
+
let parsedOptions = ParsedHeadingOptions.parse(from: options)
|
|
593
|
+
|
|
594
|
+
if !parsedOptions.headingFilter.isFinite || parsedOptions.headingFilter < 0 {
|
|
595
|
+
error?(createLocationError(
|
|
596
|
+
code: INTERNAL_ERROR,
|
|
597
|
+
message: "headingFilter must be a finite number greater than or equal to 0."
|
|
598
|
+
))
|
|
599
|
+
return token
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
guard validateHeadingAvailability(error: error) else {
|
|
603
|
+
return token
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let subscription = HeadingSubscription(
|
|
607
|
+
token: token,
|
|
608
|
+
success: success,
|
|
609
|
+
error: error,
|
|
610
|
+
options: parsedOptions,
|
|
611
|
+
lastDeliveredHeading: nil
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
headingSubscriptions[token] = subscription
|
|
615
|
+
|
|
616
|
+
initializeLocationManagerIfNeeded()
|
|
617
|
+
updateHeadingConfiguration()
|
|
618
|
+
startHeadingMonitoring()
|
|
619
|
+
|
|
620
|
+
return token
|
|
248
621
|
}
|
|
249
622
|
|
|
250
623
|
// MARK: - Watch Position (Callback-based with tokens)
|
|
@@ -275,19 +648,37 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
275
648
|
|
|
276
649
|
func unwatch(token: String) {
|
|
277
650
|
watchSubscriptions.removeValue(forKey: token)
|
|
651
|
+
headingSubscriptions.removeValue(forKey: token)
|
|
278
652
|
|
|
279
653
|
// Stop monitoring if no more subscriptions or pending requests
|
|
280
654
|
if watchSubscriptions.isEmpty && pendingPositionRequests.isEmpty {
|
|
281
655
|
stopMonitoring()
|
|
656
|
+
} else {
|
|
657
|
+
updateLocationManagerConfiguration()
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if headingSubscriptions.isEmpty && pendingHeadingRequests.isEmpty {
|
|
661
|
+
stopHeadingMonitoring()
|
|
662
|
+
} else {
|
|
663
|
+
updateHeadingConfiguration()
|
|
282
664
|
}
|
|
283
665
|
}
|
|
284
666
|
|
|
285
667
|
func stopObserving() {
|
|
286
668
|
watchSubscriptions.removeAll()
|
|
669
|
+
headingSubscriptions.removeAll()
|
|
287
670
|
|
|
288
671
|
// Stop monitoring if no pending requests
|
|
289
672
|
if pendingPositionRequests.isEmpty {
|
|
290
673
|
stopMonitoring()
|
|
674
|
+
} else {
|
|
675
|
+
updateLocationManagerConfiguration()
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if pendingHeadingRequests.isEmpty {
|
|
679
|
+
stopHeadingMonitoring()
|
|
680
|
+
} else {
|
|
681
|
+
updateHeadingConfiguration()
|
|
291
682
|
}
|
|
292
683
|
}
|
|
293
684
|
|
|
@@ -299,7 +690,7 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
299
690
|
|
|
300
691
|
// Resolve pending permission requests
|
|
301
692
|
for resolver in pendingPermissionResolvers {
|
|
302
|
-
resolver(
|
|
693
|
+
resolver(mappedStatus)
|
|
303
694
|
}
|
|
304
695
|
pendingPermissionResolvers.removeAll()
|
|
305
696
|
|
|
@@ -320,7 +711,7 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
320
711
|
// 1. Resolve all pending getCurrentPosition requests
|
|
321
712
|
for (id, request) in pendingPositionRequests {
|
|
322
713
|
request.timer?.cancel()
|
|
323
|
-
request.
|
|
714
|
+
request.success(position)
|
|
324
715
|
}
|
|
325
716
|
pendingPositionRequests.removeAll()
|
|
326
717
|
|
|
@@ -332,12 +723,13 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
332
723
|
// 3. Stop monitoring if no more subscriptions or pending requests
|
|
333
724
|
if watchSubscriptions.isEmpty && pendingPositionRequests.isEmpty {
|
|
334
725
|
stopMonitoring()
|
|
726
|
+
} else {
|
|
727
|
+
updateLocationManagerConfiguration()
|
|
335
728
|
}
|
|
336
729
|
}
|
|
337
730
|
|
|
338
731
|
fileprivate func handleLocationError(_ error: Error) {
|
|
339
732
|
let locationError: LocationError
|
|
340
|
-
let errorWrapper: GeolocationErrorWrapper
|
|
341
733
|
|
|
342
734
|
if let clError = error as? CLError {
|
|
343
735
|
switch clError.code {
|
|
@@ -346,10 +738,6 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
346
738
|
code: PERMISSION_DENIED,
|
|
347
739
|
message: "User denied access to location services."
|
|
348
740
|
)
|
|
349
|
-
errorWrapper = GeolocationErrorWrapper(
|
|
350
|
-
code: PERMISSION_DENIED,
|
|
351
|
-
message: "User denied access to location services."
|
|
352
|
-
)
|
|
353
741
|
case .locationUnknown:
|
|
354
742
|
// Temporarily unavailable, keep trying
|
|
355
743
|
return
|
|
@@ -358,26 +746,18 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
358
746
|
code: POSITION_UNAVAILABLE,
|
|
359
747
|
message: "Unable to retrieve location: \(error.localizedDescription)"
|
|
360
748
|
)
|
|
361
|
-
errorWrapper = GeolocationErrorWrapper(
|
|
362
|
-
code: POSITION_UNAVAILABLE,
|
|
363
|
-
message: "Unable to retrieve location: \(error.localizedDescription)"
|
|
364
|
-
)
|
|
365
749
|
}
|
|
366
750
|
} else {
|
|
367
751
|
locationError = createLocationError(
|
|
368
752
|
code: POSITION_UNAVAILABLE,
|
|
369
753
|
message: "Unable to retrieve location: \(error.localizedDescription)"
|
|
370
754
|
)
|
|
371
|
-
errorWrapper = GeolocationErrorWrapper(
|
|
372
|
-
code: POSITION_UNAVAILABLE,
|
|
373
|
-
message: "Unable to retrieve location: \(error.localizedDescription)"
|
|
374
|
-
)
|
|
375
755
|
}
|
|
376
756
|
|
|
377
757
|
// 1. Reject all pending getCurrentPosition requests
|
|
378
758
|
for (_, request) in pendingPositionRequests {
|
|
379
759
|
request.timer?.cancel()
|
|
380
|
-
request.
|
|
760
|
+
request.error(locationError)
|
|
381
761
|
}
|
|
382
762
|
pendingPositionRequests.removeAll()
|
|
383
763
|
|
|
@@ -386,9 +766,45 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
386
766
|
subscription.error?(locationError)
|
|
387
767
|
}
|
|
388
768
|
|
|
769
|
+
notifyHeadingConsumersOfLocationError(locationError)
|
|
389
770
|
stopMonitoring()
|
|
390
771
|
}
|
|
391
772
|
|
|
773
|
+
fileprivate func handleHeadingUpdate(_ clHeading: CLHeading) {
|
|
774
|
+
let heading = headingToResponse(clHeading)
|
|
775
|
+
|
|
776
|
+
for (id, request) in Array(pendingHeadingRequests) {
|
|
777
|
+
request.timer?.cancel()
|
|
778
|
+
request.success(heading)
|
|
779
|
+
pendingHeadingRequests.removeValue(forKey: id)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
for (token, subscription) in Array(headingSubscriptions) {
|
|
783
|
+
let shouldDeliver: Bool
|
|
784
|
+
if let lastDeliveredHeading = subscription.lastDeliveredHeading {
|
|
785
|
+
shouldDeliver = angularDistance(
|
|
786
|
+
lastDeliveredHeading,
|
|
787
|
+
heading.magneticHeading
|
|
788
|
+
) >= subscription.options.headingFilter
|
|
789
|
+
} else {
|
|
790
|
+
shouldDeliver = true
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if shouldDeliver {
|
|
794
|
+
var nextSubscription = subscription
|
|
795
|
+
nextSubscription.lastDeliveredHeading = heading.magneticHeading
|
|
796
|
+
headingSubscriptions[token] = nextSubscription
|
|
797
|
+
nextSubscription.success(heading)
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if pendingHeadingRequests.isEmpty && headingSubscriptions.isEmpty {
|
|
802
|
+
stopHeadingMonitoring()
|
|
803
|
+
} else {
|
|
804
|
+
updateHeadingConfiguration()
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
392
808
|
// MARK: - Helper Functions
|
|
393
809
|
|
|
394
810
|
private func initializeLocationManagerIfNeeded() {
|
|
@@ -407,28 +823,190 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
407
823
|
}
|
|
408
824
|
}
|
|
409
825
|
|
|
826
|
+
private func validateHeadingAvailability(
|
|
827
|
+
error: ((LocationError) -> Void)?
|
|
828
|
+
) -> Bool {
|
|
829
|
+
let status = CLLocationManager.authorizationStatus()
|
|
830
|
+
if status == .denied || status == .restricted {
|
|
831
|
+
let message = status == .restricted
|
|
832
|
+
? "This application is not authorized to use location services"
|
|
833
|
+
: "User denied access to location services."
|
|
834
|
+
error?(createLocationError(
|
|
835
|
+
code: PERMISSION_DENIED,
|
|
836
|
+
message: message
|
|
837
|
+
))
|
|
838
|
+
return false
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if !CLLocationManager.locationServicesEnabled() {
|
|
842
|
+
error?(createLocationError(
|
|
843
|
+
code: SETTINGS_NOT_SATISFIED,
|
|
844
|
+
message: "Location services disabled."
|
|
845
|
+
))
|
|
846
|
+
return false
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if !CLLocationManager.headingAvailable() {
|
|
850
|
+
error?(createLocationError(
|
|
851
|
+
code: POSITION_UNAVAILABLE,
|
|
852
|
+
message: "Heading is not available on this device."
|
|
853
|
+
))
|
|
854
|
+
return false
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return true
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private func updateHeadingConfiguration() {
|
|
861
|
+
guard let manager = locationManager else { return }
|
|
862
|
+
|
|
863
|
+
var smallestHeadingFilter: CLLocationDegrees?
|
|
864
|
+
for (_, subscription) in headingSubscriptions {
|
|
865
|
+
smallestHeadingFilter = mergeHeadingFilter(
|
|
866
|
+
smallestHeadingFilter,
|
|
867
|
+
subscription.options.headingFilter
|
|
868
|
+
)
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
manager.headingFilter = smallestHeadingFilter ?? kCLHeadingFilterNone
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private func startHeadingMonitoring() {
|
|
875
|
+
locationManager?.startUpdatingHeading()
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private func stopHeadingMonitoring() {
|
|
879
|
+
locationManager?.stopUpdatingHeading()
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
private func mergeHeadingFilter(
|
|
883
|
+
_ current: CLLocationDegrees?,
|
|
884
|
+
_ next: CLLocationDegrees
|
|
885
|
+
) -> CLLocationDegrees {
|
|
886
|
+
guard let current else {
|
|
887
|
+
return next
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if current == kCLHeadingFilterNone || next == kCLHeadingFilterNone {
|
|
891
|
+
return kCLHeadingFilterNone
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return min(current, next)
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private func handleHeadingTimeout(requestId: UUID) {
|
|
898
|
+
guard let request = pendingHeadingRequests.removeValue(forKey: requestId) else {
|
|
899
|
+
return
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
request.timer?.cancel()
|
|
903
|
+
let timeoutSeconds = DEFAULT_HEADING_TIMEOUT_MS / 1000.0
|
|
904
|
+
let message = String(format: "Unable to fetch heading within %.1fs.", timeoutSeconds)
|
|
905
|
+
request.error(createLocationError(code: TIMEOUT, message: message))
|
|
906
|
+
|
|
907
|
+
if pendingHeadingRequests.isEmpty && headingSubscriptions.isEmpty {
|
|
908
|
+
stopHeadingMonitoring()
|
|
909
|
+
} else {
|
|
910
|
+
updateHeadingConfiguration()
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
private func notifyHeadingConsumersOfLocationError(_ locationError: LocationError) {
|
|
915
|
+
guard !pendingHeadingRequests.isEmpty || !headingSubscriptions.isEmpty else {
|
|
916
|
+
return
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
for (_, request) in pendingHeadingRequests {
|
|
920
|
+
request.timer?.cancel()
|
|
921
|
+
request.error(locationError)
|
|
922
|
+
}
|
|
923
|
+
pendingHeadingRequests.removeAll()
|
|
924
|
+
|
|
925
|
+
for (_, subscription) in headingSubscriptions {
|
|
926
|
+
subscription.error?(locationError)
|
|
927
|
+
}
|
|
928
|
+
headingSubscriptions.removeAll()
|
|
929
|
+
|
|
930
|
+
stopHeadingMonitoring()
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
private func headingToResponse(_ clHeading: CLHeading) -> Heading {
|
|
934
|
+
let trueHeading = clHeading.trueHeading >= 0
|
|
935
|
+
? clHeading.trueHeading
|
|
936
|
+
: nil
|
|
937
|
+
let accuracy = clHeading.headingAccuracy >= 0
|
|
938
|
+
? clHeading.headingAccuracy
|
|
939
|
+
: nil
|
|
940
|
+
|
|
941
|
+
return Heading(
|
|
942
|
+
magneticHeading: normalizeHeading(clHeading.magneticHeading),
|
|
943
|
+
trueHeading: trueHeading.map(normalizeHeading),
|
|
944
|
+
accuracy: accuracy,
|
|
945
|
+
timestamp: clHeading.timestamp.timeIntervalSince1970 * 1000
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private func angularDistance(_ first: Double, _ second: Double) -> Double {
|
|
950
|
+
let distance = abs(first - second).truncatingRemainder(dividingBy: 360)
|
|
951
|
+
return distance > 180 ? 360 - distance : distance
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private func normalizeHeading(_ value: Double) -> Double {
|
|
955
|
+
let normalized = value.truncatingRemainder(dividingBy: 360)
|
|
956
|
+
return normalized < 0 ? normalized + 360 : normalized
|
|
957
|
+
}
|
|
958
|
+
|
|
410
959
|
private func updateLocationManagerConfiguration() {
|
|
411
960
|
guard let manager = locationManager else { return }
|
|
412
961
|
|
|
413
962
|
// Merge configurations from all pending requests and watches
|
|
414
|
-
var bestAccuracy
|
|
415
|
-
var smallestDistanceFilter
|
|
963
|
+
var bestAccuracy: CLLocationAccuracy?
|
|
964
|
+
var smallestDistanceFilter: CLLocationDistance?
|
|
965
|
+
var activityType: CLActivityType?
|
|
966
|
+
var pausesLocationUpdatesAutomatically: Bool?
|
|
967
|
+
var showsBackgroundLocationIndicator = false
|
|
416
968
|
var shouldUseSignificantChanges = false
|
|
417
969
|
|
|
418
970
|
for (_, request) in pendingPositionRequests {
|
|
419
|
-
bestAccuracy =
|
|
420
|
-
smallestDistanceFilter =
|
|
971
|
+
bestAccuracy = mergeAccuracy(bestAccuracy, request.options.accuracy)
|
|
972
|
+
smallestDistanceFilter = mergeDistanceFilter(
|
|
973
|
+
smallestDistanceFilter,
|
|
974
|
+
request.options.distanceFilter
|
|
975
|
+
)
|
|
976
|
+
activityType = mergeActivityType(activityType, request.options.activityType)
|
|
977
|
+
pausesLocationUpdatesAutomatically = mergePausesLocationUpdatesAutomatically(
|
|
978
|
+
pausesLocationUpdatesAutomatically,
|
|
979
|
+
request.options.pausesLocationUpdatesAutomatically
|
|
980
|
+
)
|
|
981
|
+
showsBackgroundLocationIndicator = showsBackgroundLocationIndicator ||
|
|
982
|
+
(request.options.showsBackgroundLocationIndicator ?? false)
|
|
421
983
|
shouldUseSignificantChanges = shouldUseSignificantChanges || request.options.useSignificantChanges
|
|
422
984
|
}
|
|
423
985
|
|
|
424
986
|
for (_, subscription) in watchSubscriptions {
|
|
425
|
-
bestAccuracy =
|
|
426
|
-
smallestDistanceFilter =
|
|
987
|
+
bestAccuracy = mergeAccuracy(bestAccuracy, subscription.options.accuracy)
|
|
988
|
+
smallestDistanceFilter = mergeDistanceFilter(
|
|
989
|
+
smallestDistanceFilter,
|
|
990
|
+
subscription.options.distanceFilter
|
|
991
|
+
)
|
|
992
|
+
activityType = mergeActivityType(activityType, subscription.options.activityType)
|
|
993
|
+
pausesLocationUpdatesAutomatically = mergePausesLocationUpdatesAutomatically(
|
|
994
|
+
pausesLocationUpdatesAutomatically,
|
|
995
|
+
subscription.options.pausesLocationUpdatesAutomatically
|
|
996
|
+
)
|
|
997
|
+
showsBackgroundLocationIndicator = showsBackgroundLocationIndicator ||
|
|
998
|
+
(subscription.options.showsBackgroundLocationIndicator ?? false)
|
|
427
999
|
shouldUseSignificantChanges = shouldUseSignificantChanges || subscription.options.useSignificantChanges
|
|
428
1000
|
}
|
|
429
1001
|
|
|
430
|
-
manager.desiredAccuracy = bestAccuracy
|
|
431
|
-
manager.distanceFilter = smallestDistanceFilter
|
|
1002
|
+
manager.desiredAccuracy = bestAccuracy ?? kCLLocationAccuracyHundredMeters
|
|
1003
|
+
manager.distanceFilter = smallestDistanceFilter ?? kCLDistanceFilterNone
|
|
1004
|
+
manager.activityType = activityType ?? .other
|
|
1005
|
+
manager.pausesLocationUpdatesAutomatically = pausesLocationUpdatesAutomatically ?? true
|
|
1006
|
+
|
|
1007
|
+
if #available(iOS 11.0, *) {
|
|
1008
|
+
manager.showsBackgroundLocationIndicator = showsBackgroundLocationIndicator
|
|
1009
|
+
}
|
|
432
1010
|
|
|
433
1011
|
// Update significant changes mode if changed
|
|
434
1012
|
if shouldUseSignificantChanges != usingSignificantChanges {
|
|
@@ -438,6 +1016,79 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
438
1016
|
}
|
|
439
1017
|
}
|
|
440
1018
|
|
|
1019
|
+
private func mergeAccuracy(
|
|
1020
|
+
_ current: CLLocationAccuracy?,
|
|
1021
|
+
_ next: CLLocationAccuracy
|
|
1022
|
+
) -> CLLocationAccuracy {
|
|
1023
|
+
guard let current else {
|
|
1024
|
+
return next
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
return min(current, next)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private func mergeActivityType(
|
|
1031
|
+
_ current: CLActivityType?,
|
|
1032
|
+
_ next: CLActivityType?
|
|
1033
|
+
) -> CLActivityType? {
|
|
1034
|
+
guard let next else {
|
|
1035
|
+
return current
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
guard let current else {
|
|
1039
|
+
return next
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return activityTypeRank(next) > activityTypeRank(current) ? next : current
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private func activityTypeRank(_ activityType: CLActivityType) -> Int {
|
|
1046
|
+
switch activityType {
|
|
1047
|
+
case .other:
|
|
1048
|
+
return 0
|
|
1049
|
+
case .otherNavigation:
|
|
1050
|
+
return 1
|
|
1051
|
+
case .automotiveNavigation:
|
|
1052
|
+
return 2
|
|
1053
|
+
case .fitness:
|
|
1054
|
+
return 3
|
|
1055
|
+
case .airborne:
|
|
1056
|
+
return 4
|
|
1057
|
+
@unknown default:
|
|
1058
|
+
return 0
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
private func mergePausesLocationUpdatesAutomatically(
|
|
1063
|
+
_ current: Bool?,
|
|
1064
|
+
_ next: Bool?
|
|
1065
|
+
) -> Bool? {
|
|
1066
|
+
guard let next else {
|
|
1067
|
+
return current
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
guard let current else {
|
|
1071
|
+
return next
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return current && next
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
private func mergeDistanceFilter(
|
|
1078
|
+
_ current: CLLocationDistance?,
|
|
1079
|
+
_ next: CLLocationDistance
|
|
1080
|
+
) -> CLLocationDistance {
|
|
1081
|
+
guard let current else {
|
|
1082
|
+
return next
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if current == kCLDistanceFilterNone || next == kCLDistanceFilterNone {
|
|
1086
|
+
return kCLDistanceFilterNone
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
return min(current, next)
|
|
1090
|
+
}
|
|
1091
|
+
|
|
441
1092
|
private func startMonitoring() {
|
|
442
1093
|
if usingSignificantChanges {
|
|
443
1094
|
locationManager?.startMonitoringSignificantLocationChanges()
|
|
@@ -465,6 +1116,15 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
465
1116
|
return age < options.maximumAge
|
|
466
1117
|
}
|
|
467
1118
|
|
|
1119
|
+
private func getBestCachedLocation(options: ParsedOptions) -> CLLocation? {
|
|
1120
|
+
initializeLocationManagerIfNeeded()
|
|
1121
|
+
|
|
1122
|
+
return [lastLocation, locationManager?.location]
|
|
1123
|
+
.compactMap { $0 }
|
|
1124
|
+
.filter { isCachedLocationValid($0, options: options) }
|
|
1125
|
+
.max { $0.timestamp < $1.timestamp }
|
|
1126
|
+
}
|
|
1127
|
+
|
|
468
1128
|
private func handlePositionTimeout(requestId: UUID) {
|
|
469
1129
|
guard let request = pendingPositionRequests.removeValue(forKey: requestId) else {
|
|
470
1130
|
return
|
|
@@ -474,13 +1134,15 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
474
1134
|
|
|
475
1135
|
let timeoutSeconds = request.options.timeout / 1000.0
|
|
476
1136
|
let message = String(format: "Unable to fetch location within %.1fs.", timeoutSeconds)
|
|
477
|
-
let error =
|
|
1137
|
+
let error = createLocationError(code: TIMEOUT, message: message)
|
|
478
1138
|
|
|
479
|
-
request.
|
|
1139
|
+
request.error(error)
|
|
480
1140
|
|
|
481
1141
|
// Stop monitoring if no more watches or pending requests
|
|
482
1142
|
if watchSubscriptions.isEmpty && pendingPositionRequests.isEmpty {
|
|
483
1143
|
stopMonitoring()
|
|
1144
|
+
} else {
|
|
1145
|
+
updateLocationManagerConfiguration()
|
|
484
1146
|
}
|
|
485
1147
|
}
|
|
486
1148
|
|
|
@@ -553,25 +1215,161 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
553
1215
|
}
|
|
554
1216
|
}
|
|
555
1217
|
|
|
556
|
-
private func
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
let speed = location.speed >= 0 ? location.speed : 0.0
|
|
1218
|
+
private func getCurrentAccuracyAuthorization() -> AccuracyAuthorization {
|
|
1219
|
+
guard #available(iOS 14.0, *) else {
|
|
1220
|
+
return .unknown
|
|
1221
|
+
}
|
|
561
1222
|
|
|
1223
|
+
let manager = locationManager ?? CLLocationManager()
|
|
1224
|
+
switch manager.accuracyAuthorization {
|
|
1225
|
+
case .fullAccuracy:
|
|
1226
|
+
return .full
|
|
1227
|
+
case .reducedAccuracy:
|
|
1228
|
+
return .reduced
|
|
1229
|
+
@unknown default:
|
|
1230
|
+
return .unknown
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private func getCurrentAccuracyAuthorizationOnMain() -> AccuracyAuthorization {
|
|
1235
|
+
if Thread.isMainThread {
|
|
1236
|
+
return getCurrentAccuracyAuthorization()
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return DispatchQueue.main.sync {
|
|
1240
|
+
getCurrentAccuracyAuthorization()
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
private func locationToPosition(_ location: CLLocation) -> GeolocationResponse {
|
|
562
1245
|
let coords = GeolocationCoordinates(
|
|
563
1246
|
latitude: location.coordinate.latitude,
|
|
564
1247
|
longitude: location.coordinate.longitude,
|
|
565
|
-
altitude: .
|
|
1248
|
+
altitude: location.nitroGeolocationAltitude,
|
|
566
1249
|
accuracy: location.horizontalAccuracy,
|
|
567
|
-
altitudeAccuracy: .
|
|
568
|
-
heading: .
|
|
569
|
-
speed: .
|
|
1250
|
+
altitudeAccuracy: location.nitroGeolocationAltitudeAccuracy,
|
|
1251
|
+
heading: location.nitroGeolocationHeading,
|
|
1252
|
+
speed: location.nitroGeolocationSpeed
|
|
570
1253
|
)
|
|
571
1254
|
|
|
572
1255
|
return GeolocationResponse(
|
|
573
1256
|
coords: coords,
|
|
574
|
-
timestamp: location.timestamp.timeIntervalSince1970 * 1000
|
|
1257
|
+
timestamp: location.timestamp.timeIntervalSince1970 * 1000,
|
|
1258
|
+
mocked: location.nitroGeolocationMocked,
|
|
1259
|
+
provider: location.nitroGeolocationProvider
|
|
1260
|
+
)
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
private func placemarkToGeocodedLocation(_ placemark: CLPlacemark) -> GeocodedLocation? {
|
|
1264
|
+
guard let location = placemark.location else {
|
|
1265
|
+
return nil
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
let accuracy = location.horizontalAccuracy >= 0
|
|
1269
|
+
? location.horizontalAccuracy
|
|
1270
|
+
: nil
|
|
1271
|
+
|
|
1272
|
+
return GeocodedLocation(
|
|
1273
|
+
latitude: location.coordinate.latitude,
|
|
1274
|
+
longitude: location.coordinate.longitude,
|
|
1275
|
+
accuracy: accuracy
|
|
1276
|
+
)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
private func placemarkToReverseGeocodedAddress(_ placemark: CLPlacemark) -> ReverseGeocodedAddress {
|
|
1280
|
+
return ReverseGeocodedAddress(
|
|
1281
|
+
country: placemark.country.nonEmptyTrimmed,
|
|
1282
|
+
region: placemark.administrativeArea.nonEmptyTrimmed,
|
|
1283
|
+
city: placemark.locality.nonEmptyTrimmed,
|
|
1284
|
+
district: placemark.subLocality.nonEmptyTrimmed,
|
|
1285
|
+
street: formatStreet(placemark),
|
|
1286
|
+
postalCode: placemark.postalCode.nonEmptyTrimmed,
|
|
1287
|
+
formattedAddress: formatAddress(placemark)
|
|
1288
|
+
)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
private func formatStreet(_ placemark: CLPlacemark) -> String? {
|
|
1292
|
+
return [
|
|
1293
|
+
placemark.subThoroughfare.nonEmptyTrimmed,
|
|
1294
|
+
placemark.thoroughfare.nonEmptyTrimmed
|
|
1295
|
+
]
|
|
1296
|
+
.compactMap { $0 }
|
|
1297
|
+
.joined(separator: " ")
|
|
1298
|
+
.nonEmptyTrimmed
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
private func formatAddress(_ placemark: CLPlacemark) -> String? {
|
|
1302
|
+
var parts: [String] = []
|
|
1303
|
+
|
|
1304
|
+
appendDistinct(placemark.name.nonEmptyTrimmed, to: &parts)
|
|
1305
|
+
appendDistinct(formatStreet(placemark), to: &parts)
|
|
1306
|
+
appendDistinct(placemark.subLocality.nonEmptyTrimmed, to: &parts)
|
|
1307
|
+
appendDistinct(placemark.locality.nonEmptyTrimmed, to: &parts)
|
|
1308
|
+
appendDistinct(placemark.administrativeArea.nonEmptyTrimmed, to: &parts)
|
|
1309
|
+
appendDistinct(placemark.postalCode.nonEmptyTrimmed, to: &parts)
|
|
1310
|
+
appendDistinct(placemark.country.nonEmptyTrimmed, to: &parts)
|
|
1311
|
+
|
|
1312
|
+
return parts.joined(separator: ", ").nonEmptyTrimmed
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
private func appendDistinct(_ value: String?, to parts: inout [String]) {
|
|
1316
|
+
guard let value, !parts.contains(value) else {
|
|
1317
|
+
return
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
parts.append(value)
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
private func validateGeocodingCoordinates(_ coords: GeocodingCoordinates) -> LocationError? {
|
|
1324
|
+
if !coords.latitude.isFinite || coords.latitude < -90 || coords.latitude > 90 {
|
|
1325
|
+
return createLocationError(
|
|
1326
|
+
code: INTERNAL_ERROR,
|
|
1327
|
+
message: "latitude must be a finite number between -90 and 90."
|
|
1328
|
+
)
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
if !coords.longitude.isFinite || coords.longitude < -180 || coords.longitude > 180 {
|
|
1332
|
+
return createLocationError(
|
|
1333
|
+
code: INTERNAL_ERROR,
|
|
1334
|
+
message: "longitude must be a finite number between -180 and 180."
|
|
1335
|
+
)
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
return nil
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
private func isNoGeocoderResult(_ error: Error) -> Bool {
|
|
1342
|
+
guard let clError = error as? CLError else {
|
|
1343
|
+
return false
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
return clError.code == .geocodeFoundNoResult
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
private func createGeocoderError(_ error: Error, messagePrefix: String) -> LocationError {
|
|
1350
|
+
if let clError = error as? CLError {
|
|
1351
|
+
switch clError.code {
|
|
1352
|
+
case .denied:
|
|
1353
|
+
return createLocationError(
|
|
1354
|
+
code: PERMISSION_DENIED,
|
|
1355
|
+
message: "\(messagePrefix): geocoder access denied."
|
|
1356
|
+
)
|
|
1357
|
+
case .network:
|
|
1358
|
+
return createLocationError(
|
|
1359
|
+
code: POSITION_UNAVAILABLE,
|
|
1360
|
+
message: "\(messagePrefix): network unavailable."
|
|
1361
|
+
)
|
|
1362
|
+
default:
|
|
1363
|
+
return createLocationError(
|
|
1364
|
+
code: POSITION_UNAVAILABLE,
|
|
1365
|
+
message: "\(messagePrefix): \(error.localizedDescription)"
|
|
1366
|
+
)
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
return createLocationError(
|
|
1371
|
+
code: POSITION_UNAVAILABLE,
|
|
1372
|
+
message: "\(messagePrefix): \(error.localizedDescription)"
|
|
575
1373
|
)
|
|
576
1374
|
}
|
|
577
1375
|
|
|
@@ -581,4 +1379,45 @@ class NitroGeolocation: HybridNitroGeolocationSpec {
|
|
|
581
1379
|
message: message
|
|
582
1380
|
)
|
|
583
1381
|
}
|
|
1382
|
+
|
|
1383
|
+
private func createLocationProviderStatus() -> LocationProviderStatus {
|
|
1384
|
+
return LocationProviderStatus(
|
|
1385
|
+
locationServicesEnabled: CLLocationManager.locationServicesEnabled(),
|
|
1386
|
+
backgroundModeEnabled: isLocationBackgroundModeEnabled(),
|
|
1387
|
+
gpsAvailable: nil,
|
|
1388
|
+
networkAvailable: nil,
|
|
1389
|
+
passiveAvailable: nil,
|
|
1390
|
+
googleLocationAccuracyEnabled: nil
|
|
1391
|
+
)
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
private func isLocationBackgroundModeEnabled() -> Bool {
|
|
1395
|
+
guard let backgroundModes = Bundle.main.object(
|
|
1396
|
+
forInfoDictionaryKey: "UIBackgroundModes"
|
|
1397
|
+
) as? [String] else {
|
|
1398
|
+
return false
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
return backgroundModes.contains("location")
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
private extension Optional where Wrapped == String {
|
|
1406
|
+
var nonEmptyTrimmed: String? {
|
|
1407
|
+
guard let trimmed = self?.trimmingCharacters(in: .whitespacesAndNewlines) else {
|
|
1408
|
+
return nil
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
return trimmed.isEmpty ? nil : trimmed
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
private extension String {
|
|
1416
|
+
var nonEmptyTrimmed: String? {
|
|
1417
|
+
return trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
var nilIfEmpty: String? {
|
|
1421
|
+
return isEmpty ? nil : self
|
|
1422
|
+
}
|
|
584
1423
|
}
|