react-native-nitro-geolocation 1.1.4 → 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.
Files changed (186) hide show
  1. package/README.md +74 -8
  2. package/android/build.gradle +6 -0
  3. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidAccuracy.kt +105 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidHeadingManager.kt +313 -0
  5. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidLocationSettings.kt +313 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +46 -45
  7. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/LocationMetadata.kt +26 -0
  8. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/LocationValues.kt +31 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +1025 -140
  10. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocationCompat.kt +11 -11
  11. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/RequestAuthorization.kt +6 -6
  12. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +46 -45
  13. package/ios/CLLocation+GeolocationMetadata.swift +32 -0
  14. package/ios/LocationManager.swift +205 -51
  15. package/ios/NitroGeolocation.swift +949 -110
  16. package/ios/NitroGeolocationCompat.swift +7 -7
  17. package/nitrogen/generated/android/c++/JAccuracyAuthorization.hpp +61 -0
  18. package/nitrogen/generated/android/c++/JAndroidAccuracyPreset.hpp +64 -0
  19. package/nitrogen/generated/android/c++/JAndroidGranularity.hpp +61 -0
  20. package/nitrogen/generated/android/c++/{JRNConfigurationInternal.hpp → JCompatGeolocationConfigurationInternal.hpp} +10 -10
  21. package/nitrogen/generated/android/c++/{JGeolocationError.hpp → JCompatGeolocationError.hpp} +10 -10
  22. package/nitrogen/generated/android/c++/JCompatGeolocationOptions.hpp +105 -0
  23. package/nitrogen/generated/android/c++/JCompatGeolocationResponse.hpp +67 -0
  24. package/nitrogen/generated/android/c++/JFunc_void_AccuracyAuthorization.hpp +77 -0
  25. package/nitrogen/generated/android/c++/JFunc_void_CompatGeolocationError.hpp +78 -0
  26. package/nitrogen/generated/android/c++/JFunc_void_CompatGeolocationResponse.hpp +84 -0
  27. package/nitrogen/generated/android/c++/JFunc_void_GeolocationResponse.hpp +2 -0
  28. package/nitrogen/generated/android/c++/JFunc_void_Heading.hpp +78 -0
  29. package/nitrogen/generated/android/c++/JFunc_void_LocationProviderStatus.hpp +78 -0
  30. package/nitrogen/generated/android/c++/JFunc_void_PermissionStatus.hpp +77 -0
  31. package/nitrogen/generated/android/c++/JFunc_void_std__vector_GeocodedLocation_.hpp +97 -0
  32. package/nitrogen/generated/android/c++/JFunc_void_std__vector_ReverseGeocodedAddress_.hpp +98 -0
  33. package/nitrogen/generated/android/c++/JGeocodedLocation.hpp +65 -0
  34. package/nitrogen/generated/android/c++/JGeocodingCoordinates.hpp +61 -0
  35. package/nitrogen/generated/android/c++/{JModernGeolocationConfiguration.hpp → JGeolocationConfiguration.hpp} +10 -10
  36. package/nitrogen/generated/android/c++/JGeolocationResponse.hpp +13 -3
  37. package/nitrogen/generated/android/c++/JHeading.hpp +69 -0
  38. package/nitrogen/generated/android/c++/JHeadingOptions.hpp +57 -0
  39. package/nitrogen/generated/android/c++/JHybridNitroGeolocationCompatSpec.cpp +46 -30
  40. package/nitrogen/generated/android/c++/JHybridNitroGeolocationCompatSpec.hpp +4 -4
  41. package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.cpp +169 -33
  42. package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.hpp +14 -3
  43. package/nitrogen/generated/android/c++/JIOSAccuracyPreset.hpp +73 -0
  44. package/nitrogen/generated/android/c++/JIOSActivityType.hpp +67 -0
  45. package/nitrogen/generated/android/c++/JLocationAccuracyOptions.hpp +65 -0
  46. package/nitrogen/generated/android/c++/JLocationAvailability.hpp +62 -0
  47. package/nitrogen/generated/android/c++/JLocationProviderStatus.hpp +77 -0
  48. package/nitrogen/generated/android/c++/JLocationProviderUsed.hpp +67 -0
  49. package/nitrogen/generated/android/c++/JLocationRequestOptions.hpp +49 -3
  50. package/nitrogen/generated/android/c++/{JGeolocationOptions.hpp → JLocationSettingsOptions.hpp} +28 -22
  51. package/nitrogen/generated/android/c++/JReverseGeocodedAddress.hpp +82 -0
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AccuracyAuthorization.kt +24 -0
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidAccuracyPreset.kt +25 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidGranularity.kt +24 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{RNConfigurationInternal.kt → CompatGeolocationConfigurationInternal.kt} +5 -5
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{GeolocationError.kt → CompatGeolocationError.kt} +5 -5
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/CompatGeolocationOptions.kt +68 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/CompatGeolocationResponse.kt +41 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_AccuracyAuthorization.kt +80 -0
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{Func_void_GeolocationError.kt → Func_void_CompatGeolocationError.kt} +9 -9
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_CompatGeolocationResponse.kt +80 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_Heading.kt +80 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_LocationProviderStatus.kt +80 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_PermissionStatus.kt +80 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_std__vector_GeocodedLocation_.kt +80 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_std__vector_ReverseGeocodedAddress_.kt +80 -0
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeocodedLocation.kt +44 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeocodingCoordinates.kt +41 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{ModernGeolocationConfiguration.kt → GeolocationConfiguration.kt} +5 -5
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationResponse.kt +9 -3
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Heading.kt +47 -0
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HeadingOptions.kt +38 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationCompatSpec.kt +7 -7
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationSpec.kt +92 -3
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSAccuracyPreset.kt +28 -0
  76. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSActivityType.kt +26 -0
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationAccuracyOptions.kt +41 -0
  78. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationAvailability.kt +41 -0
  79. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderStatus.kt +53 -0
  80. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderUsed.kt +26 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationRequestOptions.kt +30 -3
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/{GeolocationOptions.kt → LocationSettingsOptions.kt} +11 -11
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/ReverseGeocodedAddress.kt +56 -0
  84. package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +18 -4
  85. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +76 -12
  86. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +519 -77
  87. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +61 -12
  88. package/nitrogen/generated/ios/c++/HybridNitroGeolocationCompatSpecSwift.hpp +28 -16
  89. package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.hpp +131 -13
  90. package/nitrogen/generated/ios/swift/AccuracyAuthorization.swift +44 -0
  91. package/nitrogen/generated/ios/swift/AndroidAccuracyPreset.swift +48 -0
  92. package/nitrogen/generated/ios/swift/AndroidGranularity.swift +44 -0
  93. package/nitrogen/generated/ios/swift/{RNConfigurationInternal.swift → CompatGeolocationConfigurationInternal.swift} +5 -5
  94. package/nitrogen/generated/ios/swift/{GeolocationError.swift → CompatGeolocationError.swift} +5 -5
  95. package/nitrogen/generated/ios/swift/CompatGeolocationOptions.swift +208 -0
  96. package/nitrogen/generated/ios/swift/CompatGeolocationResponse.swift +34 -0
  97. package/nitrogen/generated/ios/swift/Func_void_AccuracyAuthorization.swift +46 -0
  98. package/nitrogen/generated/ios/swift/Func_void_CompatGeolocationError.swift +46 -0
  99. package/nitrogen/generated/ios/swift/Func_void_CompatGeolocationResponse.swift +46 -0
  100. package/nitrogen/generated/ios/swift/{Func_void_GeolocationError.swift → Func_void_Heading.swift} +11 -11
  101. package/nitrogen/generated/ios/swift/Func_void_LocationAvailability.swift +46 -0
  102. package/nitrogen/generated/ios/swift/Func_void_LocationProviderStatus.swift +46 -0
  103. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  104. package/nitrogen/generated/ios/swift/Func_void_std__vector_GeocodedLocation_.swift +46 -0
  105. package/nitrogen/generated/ios/swift/Func_void_std__vector_ReverseGeocodedAddress_.swift +46 -0
  106. package/nitrogen/generated/ios/swift/GeocodedLocation.swift +52 -0
  107. package/nitrogen/generated/ios/swift/GeocodingCoordinates.swift +34 -0
  108. package/nitrogen/generated/ios/swift/{ModernGeolocationConfiguration.swift → GeolocationConfiguration.swift} +5 -5
  109. package/nitrogen/generated/ios/swift/GeolocationResponse.swift +31 -2
  110. package/nitrogen/generated/ios/swift/Heading.swift +70 -0
  111. package/nitrogen/generated/ios/swift/HeadingOptions.swift +42 -0
  112. package/nitrogen/generated/ios/swift/HybridNitroGeolocationCompatSpec.swift +4 -4
  113. package/nitrogen/generated/ios/swift/HybridNitroGeolocationCompatSpec_cxx.swift +28 -28
  114. package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec.swift +14 -3
  115. package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec_cxx.swift +318 -15
  116. package/nitrogen/generated/ios/swift/IOSAccuracyPreset.swift +60 -0
  117. package/nitrogen/generated/ios/swift/IOSActivityType.swift +52 -0
  118. package/nitrogen/generated/ios/swift/LocationAccuracyOptions.swift +46 -0
  119. package/nitrogen/generated/ios/swift/LocationAvailability.swift +47 -0
  120. package/nitrogen/generated/ios/swift/LocationProviderStatus.swift +106 -0
  121. package/nitrogen/generated/ios/swift/LocationProviderUsed.swift +52 -0
  122. package/nitrogen/generated/ios/swift/LocationRequestOptions.swift +142 -1
  123. package/nitrogen/generated/ios/swift/{GeolocationOptions.swift → LocationSettingsOptions.swift} +39 -46
  124. package/nitrogen/generated/ios/swift/ReverseGeocodedAddress.swift +150 -0
  125. package/nitrogen/generated/shared/c++/AccuracyAuthorization.hpp +80 -0
  126. package/nitrogen/generated/shared/c++/AndroidAccuracyPreset.hpp +84 -0
  127. package/nitrogen/generated/shared/c++/AndroidGranularity.hpp +80 -0
  128. package/nitrogen/generated/shared/c++/{RNConfigurationInternal.hpp → CompatGeolocationConfigurationInternal.hpp} +11 -11
  129. package/nitrogen/generated/shared/c++/{GeolocationError.hpp → CompatGeolocationError.hpp} +11 -11
  130. package/nitrogen/generated/shared/c++/CompatGeolocationOptions.hpp +128 -0
  131. package/nitrogen/generated/shared/c++/CompatGeolocationResponse.hpp +88 -0
  132. package/nitrogen/generated/shared/c++/GeocodedLocation.hpp +91 -0
  133. package/nitrogen/generated/shared/c++/GeocodingCoordinates.hpp +87 -0
  134. package/nitrogen/generated/shared/c++/{ModernGeolocationConfiguration.hpp → GeolocationConfiguration.hpp} +11 -11
  135. package/nitrogen/generated/shared/c++/GeolocationResponse.hpp +14 -2
  136. package/nitrogen/generated/shared/c++/Heading.hpp +95 -0
  137. package/nitrogen/generated/shared/c++/HeadingOptions.hpp +83 -0
  138. package/nitrogen/generated/shared/c++/HybridNitroGeolocationCompatSpec.hpp +16 -16
  139. package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.cpp +11 -0
  140. package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.hpp +51 -12
  141. package/nitrogen/generated/shared/c++/IOSAccuracyPreset.hpp +96 -0
  142. package/nitrogen/generated/shared/c++/IOSActivityType.hpp +88 -0
  143. package/nitrogen/generated/shared/c++/LocationAccuracyOptions.hpp +92 -0
  144. package/nitrogen/generated/shared/c++/LocationAvailability.hpp +88 -0
  145. package/nitrogen/generated/shared/c++/LocationProviderStatus.hpp +103 -0
  146. package/nitrogen/generated/shared/c++/LocationProviderUsed.hpp +88 -0
  147. package/nitrogen/generated/shared/c++/LocationRequestOptions.hpp +47 -3
  148. package/nitrogen/generated/shared/c++/{GeolocationOptions.hpp → LocationSettingsOptions.hpp} +26 -24
  149. package/nitrogen/generated/shared/c++/ReverseGeocodedAddress.hpp +108 -0
  150. package/package.json +1 -1
  151. package/src/NitroGeolocation.nitro.ts +291 -17
  152. package/src/NitroGeolocationCompat.nitro.ts +12 -12
  153. package/src/api/geocode.ts +18 -0
  154. package/src/api/getAccuracyAuthorization.ts +12 -0
  155. package/src/api/getCurrentPosition.ts +5 -3
  156. package/src/api/getHeading.ts +13 -0
  157. package/src/api/getLastKnownPosition.ts +28 -0
  158. package/src/api/getLocationAvailability.ts +11 -0
  159. package/src/api/getProviderStatus.ts +16 -0
  160. package/src/api/hasServicesEnabled.ts +13 -0
  161. package/src/api/index.ts +11 -0
  162. package/src/api/requestLocationSettings.ts +29 -0
  163. package/src/api/requestPermission.ts +3 -1
  164. package/src/api/requestTemporaryFullAccuracy.ts +21 -0
  165. package/src/api/reverseGeocode.ts +23 -0
  166. package/src/api/setConfiguration.ts +8 -4
  167. package/src/api/watchHeading.ts +19 -0
  168. package/src/api/watchPosition.ts +2 -2
  169. package/src/compat/getCurrentPosition.ts +7 -7
  170. package/src/compat/index.tsx +5 -5
  171. package/src/compat/requestAuthorization.ts +2 -2
  172. package/src/compat/setRNConfiguration.ts +7 -5
  173. package/src/compat/watchPosition.ts +7 -7
  174. package/src/devtools/getCurrentPosition.ts +5 -3
  175. package/src/devtools/index.ts +1 -1
  176. package/src/devtools/watchPosition.ts +6 -7
  177. package/src/hooks/useWatchPosition.ts +2 -2
  178. package/src/index.tsx +35 -6
  179. package/src/publicTypes.ts +96 -0
  180. package/src/types.ts +113 -37
  181. package/src/utils/errors.test.ts +65 -0
  182. package/src/utils/errors.ts +45 -18
  183. package/src/utils/index.ts +2 -2
  184. package/src/utils/provider.test.ts +172 -1
  185. package/src/utils/provider.ts +50 -5
  186. package/nitrogen/generated/android/c++/JFunc_void_GeolocationError.hpp +0 -78
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![NPM](https://img.shields.io/npm/v/react-native-nitro-geolocation)](https://www.npmjs.com/package/react-native-nitro-geolocation)
4
4
 
5
- **Simple and Modern Geolocation for React Native** — Powered by Nitro Modules with JSI
5
+ **Simple Native Geolocation for React Native** — Powered by Nitro Modules with JSI
6
6
 
7
7
  A complete reimplementation of [`@react-native-community/geolocation`](https://github.com/michalchudziak/react-native-geolocation) for the React Native New Architecture, featuring:
8
8
 
@@ -36,7 +36,17 @@ React Native Nitro Geolocation provides **two APIs** to fit your needs:
36
36
  import {
37
37
  setConfiguration,
38
38
  requestPermission,
39
+ requestLocationSettings,
40
+ getLocationAvailability,
39
41
  getCurrentPosition,
42
+ getLastKnownPosition,
43
+ geocode,
44
+ reverseGeocode,
45
+ getHeading,
46
+ watchHeading,
47
+ unwatch,
48
+ getAccuracyAuthorization,
49
+ requestTemporaryFullAccuracy,
40
50
  useWatchPosition,
41
51
  } from "react-native-nitro-geolocation";
42
52
 
@@ -49,16 +59,50 @@ setConfiguration({
49
59
  // Request permission
50
60
  const status = await requestPermission();
51
61
 
62
+ // Android, v1.2+: ask the user to enable settings required for accurate location
63
+ await requestLocationSettings({ accuracy: { android: "high" } });
64
+
65
+ // v1.2+: check whether the platform can currently provide locations
66
+ const availability = await getLocationAvailability();
67
+
52
68
  // Get current location
53
69
  const position = await getCurrentPosition({
54
- enableHighAccuracy: true,
70
+ accuracy: { android: "high", ios: "best" },
71
+ granularity: "permission",
72
+ waitForAccurateLocation: true,
73
+ });
74
+
75
+ // v1.2+: read cached location explicitly without starting a fresh request
76
+ const cached = await getLastKnownPosition({
77
+ maximumAge: 60_000,
78
+ accuracy: { android: "balanced", ios: "hundredMeters" },
79
+ });
80
+
81
+ // v1.2+: convert between addresses and coordinates with native geocoders
82
+ const locations = await geocode("City Hall, Seoul, South Korea");
83
+ const addresses = await reverseGeocode({
84
+ latitude: 37.5665,
85
+ longitude: 126.978,
86
+ });
87
+
88
+ // v1.2+: inspect precise/reduced accuracy authorization
89
+ const accuracyAuthorization = await getAccuracyAuthorization();
90
+ if (accuracyAuthorization === "reduced") {
91
+ await requestTemporaryFullAccuracy("TurnByTurnNavigation");
92
+ }
93
+
94
+ // v1.2+: read and watch compass heading
95
+ const heading = await getHeading();
96
+ const headingToken = watchHeading((nextHeading) => {
97
+ console.log(nextHeading.magneticHeading);
55
98
  });
99
+ unwatch(headingToken);
56
100
 
57
101
  // Continuous tracking with hook
58
102
  function LocationTracker() {
59
103
  const { position, error, isWatching } = useWatchPosition({
60
104
  enabled: true,
61
- enableHighAccuracy: true,
105
+ accuracy: { android: "high", ios: "bestForNavigation" },
62
106
  distanceFilter: 10,
63
107
  });
64
108
 
@@ -75,7 +119,7 @@ function LocationTracker() {
75
119
 
76
120
  **Benefits**:
77
121
 
78
- ### 2. Legacy API (Compatibility)
122
+ ### 2. Compat API (Compatibility)
79
123
 
80
124
  **Drop-in replacement** for `@react-native-community/geolocation`:
81
125
 
@@ -150,7 +194,7 @@ Optional (for background):
150
194
 
151
195
  > **Prerequisites**: Requires [Rozenite DevTools](https://github.com/rozenite/rozenite) to be installed.
152
196
  >
153
- > **API Compatibility**: Only works with the Modern API. Does not support the Legacy API (`/compat`).
197
+ > **API Compatibility**: Only works with the Modern API. Does not support the Compat API (`/compat`).
154
198
 
155
199
  Mock geolocation data during development with an interactive map interface:
156
200
 
@@ -200,16 +244,38 @@ function App() {
200
244
 
201
245
  ```tsx
202
246
  // Get current location
203
- const position = await getCurrentPosition({ enableHighAccuracy: true });
247
+ const position = await getCurrentPosition({
248
+ accuracy: { android: "high", ios: "best" },
249
+ });
204
250
 
205
251
  // Real-time tracking with hook
206
252
  const { position, error } = useWatchPosition({
207
253
  enabled: true,
254
+ accuracy: { android: "balanced", ios: "nearestTenMeters" },
208
255
  distanceFilter: 10
209
256
  });
210
257
  ```
211
258
 
212
- #### Legacy API (Compatibility)
259
+ Accuracy presets are available since `v1.2`.
260
+
261
+ `getLastKnownPosition(options?)`, `getLocationAvailability()`, `getHeading()`,
262
+ `watchHeading()`, `geocode(address)`, `reverseGeocode(coords)`, selected
263
+ Android request options (`granularity`, `waitForAccurateLocation`,
264
+ `maxUpdateAge`, `maxUpdateDelay`, and `maxUpdates`), iOS tuning options
265
+ (`activityType`, `pausesLocationUpdatesAutomatically`, and
266
+ `showsBackgroundLocationIndicator`), `getAccuracyAuthorization()`, and
267
+ `requestTemporaryFullAccuracy(purposeKey)` are available since `v1.2`.
268
+
269
+ `enableHighAccuracy` is deprecated in the Modern API and remains supported only
270
+ for v1 compatibility. Prefer `accuracy`; when `accuracy.android` or
271
+ `accuracy.ios` is provided for the current platform, that explicit preset takes
272
+ precedence over the boolean. `enableHighAccuracy` is expected to be removed from
273
+ the Modern API in v2.
274
+
275
+ The `/compat` API keeps `enableHighAccuracy` for drop-in compatibility with
276
+ `@react-native-community/geolocation`.
277
+
278
+ #### Compat API (Compatibility)
213
279
 
214
280
  ```tsx
215
281
  import Geolocation from "react-native-nitro-geolocation/compat";
@@ -238,7 +304,7 @@ Change the import to use `/compat` — 100% API compatible:
238
304
  - [Introduction](https://react-native-nitro-geolocation.pages.dev/guide/)
239
305
  - [Quick Start Guide](https://react-native-nitro-geolocation.pages.dev/guide/quick-start)
240
306
  - [Modern API Reference](https://react-native-nitro-geolocation.pages.dev/guide/modern-api)
241
- - [Legacy API Reference](https://react-native-nitro-geolocation.pages.dev/guide/legacy-api)
307
+ - [Compat API Reference](https://react-native-nitro-geolocation.pages.dev/guide/compat-api)
242
308
  - [DevTools Plugin Guide](https://react-native-nitro-geolocation.pages.dev/guide/devtools)
243
309
  - [Why Nitro Module?](https://react-native-nitro-geolocation.pages.dev/guide/why-nitro-module)
244
310
  - [Benchmark Results](https://react-native-nitro-geolocation.pages.dev/guide/benchmark)
@@ -30,6 +30,10 @@ def getExtOrIntegerDefault(name) {
30
30
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroGeolocation_" + name]).toInteger()
31
31
  }
32
32
 
33
+ def getExtOrDefaultValue(name, defaultValue) {
34
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroGeolocation_" + name] ?: defaultValue)
35
+ }
36
+
33
37
  android {
34
38
  namespace "com.margelo.nitro.nitrogeolocation"
35
39
 
@@ -123,6 +127,8 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
123
127
 
124
128
  dependencies {
125
129
  implementation "com.facebook.react:react-android"
130
+ implementation "com.google.android.gms:play-services-base:${getExtOrDefaultValue('playServicesBaseVersion', '18.5.0')}"
131
+ implementation "com.google.android.gms:play-services-location:${getExtOrDefaultValue('playServicesLocationVersion', '21.3.0')}"
126
132
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
127
133
  implementation project(":react-native-nitro-modules")
128
134
  }
@@ -0,0 +1,105 @@
1
+ package com.margelo.nitro.nitrogeolocation
2
+
3
+ import android.location.LocationManager
4
+ import com.google.android.gms.location.Granularity
5
+ import com.google.android.gms.location.Priority
6
+
7
+ internal enum class AndroidAccuracyMode {
8
+ HIGH,
9
+ BALANCED,
10
+ LOW,
11
+ PASSIVE
12
+ }
13
+
14
+ internal data class AndroidAccuracyResolution(
15
+ val mode: AndroidAccuracyMode,
16
+ val explicitPreset: AndroidAccuracyPreset?
17
+ )
18
+
19
+ internal fun resolveAndroidAccuracy(
20
+ accuracy: LocationAccuracyOptions?,
21
+ enableHighAccuracy: Boolean
22
+ ): AndroidAccuracyResolution {
23
+ val preset = accuracy?.android
24
+ val mode = when (preset) {
25
+ AndroidAccuracyPreset.HIGH -> AndroidAccuracyMode.HIGH
26
+ AndroidAccuracyPreset.BALANCED -> AndroidAccuracyMode.BALANCED
27
+ AndroidAccuracyPreset.LOW -> AndroidAccuracyMode.LOW
28
+ AndroidAccuracyPreset.PASSIVE -> AndroidAccuracyMode.PASSIVE
29
+ null -> if (enableHighAccuracy) AndroidAccuracyMode.HIGH else AndroidAccuracyMode.BALANCED
30
+ }
31
+
32
+ return AndroidAccuracyResolution(mode = mode, explicitPreset = preset)
33
+ }
34
+
35
+ internal fun AndroidAccuracyResolution.providerOrder(): List<String> {
36
+ return when (mode) {
37
+ AndroidAccuracyMode.HIGH -> listOf(
38
+ LocationManager.GPS_PROVIDER,
39
+ LocationManager.NETWORK_PROVIDER
40
+ )
41
+ AndroidAccuracyMode.BALANCED -> if (explicitPreset == null) {
42
+ listOf(LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER)
43
+ } else {
44
+ listOf(LocationManager.NETWORK_PROVIDER)
45
+ }
46
+ AndroidAccuracyMode.LOW -> listOf(
47
+ LocationManager.NETWORK_PROVIDER,
48
+ LocationManager.PASSIVE_PROVIDER
49
+ )
50
+ AndroidAccuracyMode.PASSIVE -> listOf(LocationManager.PASSIVE_PROVIDER)
51
+ }
52
+ }
53
+
54
+ internal fun AndroidAccuracyResolution.gmsPriority(): Int {
55
+ return when (mode) {
56
+ AndroidAccuracyMode.HIGH -> Priority.PRIORITY_HIGH_ACCURACY
57
+ AndroidAccuracyMode.BALANCED -> Priority.PRIORITY_BALANCED_POWER_ACCURACY
58
+ AndroidAccuracyMode.LOW -> Priority.PRIORITY_LOW_POWER
59
+ AndroidAccuracyMode.PASSIVE -> Priority.PRIORITY_PASSIVE
60
+ }
61
+ }
62
+
63
+ internal fun AndroidGranularity?.gmsGranularity(): Int {
64
+ return when (this) {
65
+ AndroidGranularity.COARSE -> Granularity.GRANULARITY_COARSE
66
+ AndroidGranularity.FINE -> Granularity.GRANULARITY_FINE
67
+ AndroidGranularity.PERMISSION,
68
+ null -> Granularity.GRANULARITY_PERMISSION_LEVEL
69
+ }
70
+ }
71
+
72
+ internal fun AndroidGranularity?.allowsProvider(provider: String): Boolean {
73
+ return when (this) {
74
+ AndroidGranularity.COARSE -> provider != LocationManager.GPS_PROVIDER
75
+ AndroidGranularity.FINE,
76
+ AndroidGranularity.PERMISSION,
77
+ null -> true
78
+ }
79
+ }
80
+
81
+ internal fun mostDemandingAndroidAccuracy(
82
+ current: AndroidAccuracyResolution?,
83
+ next: AndroidAccuracyResolution
84
+ ): AndroidAccuracyResolution {
85
+ if (current == null) return next
86
+
87
+ val currentRank = current.mode.demandRank()
88
+ val nextRank = next.mode.demandRank()
89
+
90
+ return when {
91
+ nextRank > currentRank -> next
92
+ nextRank < currentRank -> current
93
+ current.explicitPreset == null && next.explicitPreset != null -> next
94
+ else -> current
95
+ }
96
+ }
97
+
98
+ private fun AndroidAccuracyMode.demandRank(): Int {
99
+ return when (this) {
100
+ AndroidAccuracyMode.PASSIVE -> 0
101
+ AndroidAccuracyMode.LOW -> 1
102
+ AndroidAccuracyMode.BALANCED -> 2
103
+ AndroidAccuracyMode.HIGH -> 3
104
+ }
105
+ }
@@ -0,0 +1,313 @@
1
+ package com.margelo.nitro.nitrogeolocation
2
+
3
+ import android.content.Context
4
+ import android.hardware.GeomagneticField
5
+ import android.hardware.Sensor
6
+ import android.hardware.SensorEvent
7
+ import android.hardware.SensorEventListener
8
+ import android.hardware.SensorManager
9
+ import android.location.Location
10
+ import android.os.Handler
11
+ import android.os.Looper
12
+ import java.util.UUID
13
+ import java.util.concurrent.ConcurrentHashMap
14
+ import kotlin.math.abs
15
+
16
+ private const val DEFAULT_HEADING_TIMEOUT_MS = 10_000L
17
+
18
+ internal class AndroidHeadingManager(
19
+ context: Context,
20
+ private val createLocationError: (Double, String) -> LocationError,
21
+ private val getReferenceLocation: () -> Location?
22
+ ) {
23
+ private data class PendingHeadingRequest(
24
+ val id: UUID,
25
+ val success: (Heading) -> Unit,
26
+ val error: ((LocationError) -> Unit)?,
27
+ val timeoutRunnable: Runnable
28
+ )
29
+
30
+ private data class HeadingSubscription(
31
+ val token: String,
32
+ val success: (Heading) -> Unit,
33
+ val error: ((LocationError) -> Unit)?,
34
+ val headingFilter: Double,
35
+ var lastDeliveredHeading: Double?
36
+ )
37
+
38
+ private val sensorManager =
39
+ context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
40
+ private val mainHandler = Handler(Looper.getMainLooper())
41
+ private val pendingRequests = ConcurrentHashMap<UUID, PendingHeadingRequest>()
42
+ private val subscriptions = ConcurrentHashMap<String, HeadingSubscription>()
43
+ private val rotationVectorSensor: Sensor? =
44
+ sensorManager?.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
45
+ ?: sensorManager?.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)
46
+ private val accelerometerSensor: Sensor? =
47
+ sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
48
+ private val magneticFieldSensor: Sensor? =
49
+ sensorManager?.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
50
+
51
+ private var isListening = false
52
+ private var sensorAccuracy: Int? = null
53
+ private var gravityValues: FloatArray? = null
54
+ private var magneticValues: FloatArray? = null
55
+
56
+ private val sensorListener = object : SensorEventListener {
57
+ override fun onSensorChanged(event: SensorEvent) {
58
+ val magneticHeading = when (event.sensor.type) {
59
+ Sensor.TYPE_ROTATION_VECTOR,
60
+ Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR -> {
61
+ headingFromRotationVector(event.values)
62
+ }
63
+ Sensor.TYPE_ACCELEROMETER -> {
64
+ gravityValues = event.values.clone()
65
+ headingFromAccelerationAndMagnetometer()
66
+ }
67
+ Sensor.TYPE_MAGNETIC_FIELD -> {
68
+ magneticValues = event.values.clone()
69
+ headingFromAccelerationAndMagnetometer()
70
+ }
71
+ else -> null
72
+ } ?: return
73
+
74
+ emitHeading(createHeading(magneticHeading))
75
+ }
76
+
77
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
78
+ sensorAccuracy = accuracy
79
+ }
80
+ }
81
+
82
+ fun getHeading(
83
+ success: (Heading) -> Unit,
84
+ error: ((LocationError) -> Unit)?
85
+ ) {
86
+ if (!hasHeadingSensor()) {
87
+ error?.invoke(createUnavailableError())
88
+ return
89
+ }
90
+
91
+ val id = UUID.randomUUID()
92
+ val timeoutRunnable = Runnable {
93
+ pendingRequests.remove(id)?.error?.invoke(
94
+ createLocationError(
95
+ TIMEOUT,
96
+ "Unable to fetch heading within ${DEFAULT_HEADING_TIMEOUT_MS / 1000.0}s."
97
+ )
98
+ )
99
+ stopIfIdle()
100
+ }
101
+
102
+ pendingRequests[id] = PendingHeadingRequest(
103
+ id = id,
104
+ success = success,
105
+ error = error,
106
+ timeoutRunnable = timeoutRunnable
107
+ )
108
+ mainHandler.postDelayed(timeoutRunnable, DEFAULT_HEADING_TIMEOUT_MS)
109
+ startIfNeeded()
110
+ }
111
+
112
+ fun watchHeading(
113
+ success: (Heading) -> Unit,
114
+ error: ((LocationError) -> Unit)?,
115
+ options: HeadingOptions?
116
+ ): String {
117
+ val token = UUID.randomUUID().toString()
118
+ val headingFilter = options?.headingFilter ?: 0.0
119
+
120
+ if (!headingFilter.isFinite() || headingFilter < 0.0) {
121
+ error?.invoke(createLocationError(
122
+ INTERNAL_ERROR,
123
+ "headingFilter must be a finite number greater than or equal to 0."
124
+ ))
125
+ return token
126
+ }
127
+
128
+ if (!hasHeadingSensor()) {
129
+ error?.invoke(createUnavailableError())
130
+ return token
131
+ }
132
+
133
+ subscriptions[token] = HeadingSubscription(
134
+ token = token,
135
+ success = success,
136
+ error = error,
137
+ headingFilter = headingFilter,
138
+ lastDeliveredHeading = null
139
+ )
140
+ startIfNeeded()
141
+
142
+ return token
143
+ }
144
+
145
+ fun unwatch(token: String) {
146
+ subscriptions.remove(token)
147
+ stopIfIdle()
148
+ }
149
+
150
+ fun stopObserving() {
151
+ pendingRequests.values.forEach { request ->
152
+ mainHandler.removeCallbacks(request.timeoutRunnable)
153
+ }
154
+ pendingRequests.clear()
155
+ subscriptions.clear()
156
+ stopListening()
157
+ }
158
+
159
+ fun hasActiveWork(): Boolean {
160
+ return pendingRequests.isNotEmpty() || subscriptions.isNotEmpty()
161
+ }
162
+
163
+ private fun hasHeadingSensor(): Boolean {
164
+ return sensorManager != null &&
165
+ (rotationVectorSensor != null || (accelerometerSensor != null && magneticFieldSensor != null))
166
+ }
167
+
168
+ private fun startIfNeeded() {
169
+ if (isListening) return
170
+
171
+ val manager = sensorManager ?: return
172
+ val didRegister = if (rotationVectorSensor != null) {
173
+ manager.registerListener(
174
+ sensorListener,
175
+ rotationVectorSensor,
176
+ SensorManager.SENSOR_DELAY_UI,
177
+ mainHandler
178
+ )
179
+ } else {
180
+ val accelerometerRegistered = manager.registerListener(
181
+ sensorListener,
182
+ accelerometerSensor!!,
183
+ SensorManager.SENSOR_DELAY_UI,
184
+ mainHandler
185
+ )
186
+ val magneticRegistered = manager.registerListener(
187
+ sensorListener,
188
+ magneticFieldSensor!!,
189
+ SensorManager.SENSOR_DELAY_UI,
190
+ mainHandler
191
+ )
192
+ accelerometerRegistered && magneticRegistered
193
+ }
194
+
195
+ isListening = didRegister
196
+ if (!didRegister) {
197
+ val error = createUnavailableError()
198
+ pendingRequests.values.forEach { it.error?.invoke(error) }
199
+ subscriptions.values.forEach { it.error?.invoke(error) }
200
+ stopObserving()
201
+ }
202
+ }
203
+
204
+ private fun stopIfIdle() {
205
+ if (!hasActiveWork()) {
206
+ stopListening()
207
+ }
208
+ }
209
+
210
+ private fun stopListening() {
211
+ if (!isListening) return
212
+
213
+ try {
214
+ sensorManager?.unregisterListener(sensorListener)
215
+ } catch (_: Exception) {
216
+ // Ignore unregister races.
217
+ }
218
+ isListening = false
219
+ gravityValues = null
220
+ magneticValues = null
221
+ }
222
+
223
+ private fun headingFromRotationVector(values: FloatArray): Double? {
224
+ val rotationMatrix = FloatArray(9)
225
+ val orientation = FloatArray(3)
226
+ SensorManager.getRotationMatrixFromVector(rotationMatrix, values)
227
+ SensorManager.getOrientation(rotationMatrix, orientation)
228
+ return normalizeHeading(Math.toDegrees(orientation[0].toDouble()))
229
+ }
230
+
231
+ private fun headingFromAccelerationAndMagnetometer(): Double? {
232
+ val gravity = gravityValues ?: return null
233
+ val magnetic = magneticValues ?: return null
234
+ val rotationMatrix = FloatArray(9)
235
+ val inclinationMatrix = FloatArray(9)
236
+
237
+ if (!SensorManager.getRotationMatrix(rotationMatrix, inclinationMatrix, gravity, magnetic)) {
238
+ return null
239
+ }
240
+
241
+ val orientation = FloatArray(3)
242
+ SensorManager.getOrientation(rotationMatrix, orientation)
243
+ return normalizeHeading(Math.toDegrees(orientation[0].toDouble()))
244
+ }
245
+
246
+ private fun createHeading(magneticHeading: Double): Heading {
247
+ val referenceLocation = getReferenceLocation()
248
+ val trueHeading = referenceLocation?.let { location ->
249
+ val field = GeomagneticField(
250
+ location.latitude.toFloat(),
251
+ location.longitude.toFloat(),
252
+ if (location.hasAltitude()) location.altitude.toFloat() else 0f,
253
+ location.time
254
+ )
255
+ normalizeHeading(magneticHeading + field.declination)
256
+ }
257
+
258
+ return Heading(
259
+ magneticHeading = magneticHeading,
260
+ trueHeading = trueHeading,
261
+ accuracy = sensorAccuracy?.takeIf {
262
+ it != SensorManager.SENSOR_STATUS_UNRELIABLE
263
+ }?.toDouble(),
264
+ timestamp = System.currentTimeMillis().toDouble()
265
+ )
266
+ }
267
+
268
+ private fun emitHeading(heading: Heading) {
269
+ val pendingSnapshot = pendingRequests.values.toList()
270
+ pendingSnapshot.forEach { request ->
271
+ mainHandler.removeCallbacks(request.timeoutRunnable)
272
+ if (pendingRequests.remove(request.id) != null) {
273
+ request.success(heading)
274
+ }
275
+ }
276
+
277
+ subscriptions.values.forEach { subscription ->
278
+ val lastHeading = subscription.lastDeliveredHeading
279
+ if (
280
+ lastHeading == null ||
281
+ angularDistance(lastHeading, heading.magneticHeading) >= subscription.headingFilter
282
+ ) {
283
+ subscription.lastDeliveredHeading = heading.magneticHeading
284
+ subscription.success(heading)
285
+ }
286
+ }
287
+
288
+ stopIfIdle()
289
+ }
290
+
291
+ private fun angularDistance(first: Double, second: Double): Double {
292
+ val distance = abs(first - second) % 360.0
293
+ return if (distance > 180.0) 360.0 - distance else distance
294
+ }
295
+
296
+ private fun normalizeHeading(value: Double): Double {
297
+ val normalized = value % 360.0
298
+ return if (normalized < 0.0) normalized + 360.0 else normalized
299
+ }
300
+
301
+ private fun createUnavailableError(): LocationError {
302
+ return createLocationError(
303
+ POSITION_UNAVAILABLE,
304
+ "Heading sensor is not available."
305
+ )
306
+ }
307
+
308
+ private companion object {
309
+ private const val INTERNAL_ERROR = -1.0
310
+ private const val POSITION_UNAVAILABLE = 2.0
311
+ private const val TIMEOUT = 3.0
312
+ }
313
+ }