react-native-nitro-geolocation 0.0.1 → 0.1.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 (79) hide show
  1. package/LICENSE +4 -1
  2. package/README.md +598 -0
  3. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/GetCurrentPosition.kt +341 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +76 -5
  5. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/RequestAuthorization.kt +164 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/WatchPosition.kt +228 -0
  7. package/ios/LocationManager.swift +529 -0
  8. package/ios/NitroGeolocation.swift +96 -2
  9. package/nitrogen/generated/.gitattributes +1 -0
  10. package/nitrogen/generated/android/c++/JAuthorizationLevelInternal.hpp +62 -0
  11. package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
  12. package/nitrogen/generated/android/c++/JFunc_void_GeolocationError.hpp +77 -0
  13. package/nitrogen/generated/android/c++/JFunc_void_GeolocationResponse.hpp +79 -0
  14. package/nitrogen/generated/android/c++/JGeolocationCoordinates.hpp +77 -0
  15. package/nitrogen/generated/android/c++/JGeolocationError.hpp +69 -0
  16. package/nitrogen/generated/android/c++/JGeolocationOptions.hpp +77 -0
  17. package/nitrogen/generated/android/c++/JGeolocationResponse.hpp +59 -0
  18. package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.cpp +98 -0
  19. package/nitrogen/generated/android/c++/JHybridNitroGeolocationSpec.hpp +69 -0
  20. package/nitrogen/generated/android/c++/JLocationProviderInternal.hpp +62 -0
  21. package/nitrogen/generated/android/c++/JRNConfigurationInternal.hpp +69 -0
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AuthorizationLevelInternal.kt +22 -0
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void.kt +81 -0
  24. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_GeolocationError.kt +81 -0
  25. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_GeolocationResponse.kt +81 -0
  26. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationCoordinates.kt +47 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationError.kt +41 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationOptions.kt +47 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeolocationResponse.kt +32 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroGeolocationSpec.kt +87 -0
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderInternal.kt +22 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/RNConfigurationInternal.kt +38 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/nitrogeolocationOnLoad.kt +35 -0
  34. package/nitrogen/generated/android/nitrogeolocation+autolinking.cmake +81 -0
  35. package/nitrogen/generated/android/nitrogeolocation+autolinking.gradle +27 -0
  36. package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +50 -0
  37. package/nitrogen/generated/android/nitrogeolocationOnLoad.hpp +25 -0
  38. package/nitrogen/generated/ios/NitroGeolocation+autolinking.rb +60 -0
  39. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +56 -0
  40. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +252 -0
  41. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +67 -0
  42. package/nitrogen/generated/ios/NitroGeolocationAutolinking.mm +33 -0
  43. package/nitrogen/generated/ios/NitroGeolocationAutolinking.swift +25 -0
  44. package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.cpp +11 -0
  45. package/nitrogen/generated/ios/c++/HybridNitroGeolocationSpecSwift.hpp +125 -0
  46. package/nitrogen/generated/ios/swift/AuthorizationLevelInternal.swift +44 -0
  47. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  48. package/nitrogen/generated/ios/swift/Func_void_GeolocationError.swift +47 -0
  49. package/nitrogen/generated/ios/swift/Func_void_GeolocationResponse.swift +47 -0
  50. package/nitrogen/generated/ios/swift/GeolocationCoordinates.swift +149 -0
  51. package/nitrogen/generated/ios/swift/GeolocationError.swift +79 -0
  52. package/nitrogen/generated/ios/swift/GeolocationOptions.swift +185 -0
  53. package/nitrogen/generated/ios/swift/GeolocationResponse.swift +46 -0
  54. package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec.swift +54 -0
  55. package/nitrogen/generated/ios/swift/HybridNitroGeolocationSpec_cxx.swift +236 -0
  56. package/nitrogen/generated/ios/swift/LocationProviderInternal.swift +44 -0
  57. package/nitrogen/generated/ios/swift/RNConfigurationInternal.swift +104 -0
  58. package/nitrogen/generated/shared/c++/AuthorizationLevelInternal.hpp +80 -0
  59. package/nitrogen/generated/shared/c++/GeolocationCoordinates.hpp +91 -0
  60. package/nitrogen/generated/shared/c++/GeolocationError.hpp +83 -0
  61. package/nitrogen/generated/shared/c++/GeolocationOptions.hpp +91 -0
  62. package/nitrogen/generated/shared/c++/GeolocationResponse.hpp +72 -0
  63. package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.cpp +26 -0
  64. package/nitrogen/generated/shared/c++/HybridNitroGeolocationSpec.hpp +79 -0
  65. package/nitrogen/generated/shared/c++/LocationProviderInternal.hpp +80 -0
  66. package/nitrogen/generated/shared/c++/RNConfigurationInternal.hpp +84 -0
  67. package/package.json +34 -10
  68. package/src/NitroGeolocation.nitro.ts +38 -3
  69. package/src/NitroGeolocationModule.ts +5 -0
  70. package/src/clearWatch.ts +13 -0
  71. package/src/getCurrentPosition.ts +14 -0
  72. package/src/index.tsx +32 -7
  73. package/src/requestAuthorization.ts +9 -0
  74. package/src/setRNConfiguration.ts +22 -0
  75. package/src/stopObserving.ts +12 -0
  76. package/src/types.ts +43 -0
  77. package/src/watchPosition.ts +26 -0
  78. package/nitro.json +0 -17
  79. package/turbo.json +0 -42
@@ -0,0 +1,529 @@
1
+ import CoreLocation
2
+
3
+ class LocationManager: NSObject, CLLocationManagerDelegate {
4
+ // MARK: - Types
5
+
6
+ enum AuthorizationType {
7
+ case always
8
+ case whenInUse
9
+ case none
10
+ }
11
+
12
+ private struct LocationRequest {
13
+ let success: (GeolocationResponse) -> Void
14
+ let error: ((GeolocationError) -> Void)?
15
+ let options: ParsedOptions
16
+ var timer: Timer?
17
+ }
18
+
19
+ private struct WatchSubscription {
20
+ let success: (GeolocationResponse) -> Void
21
+ let error: ((GeolocationError) -> Void)?
22
+ let options: ParsedOptions
23
+ }
24
+
25
+ private struct ParsedOptions {
26
+ let timeout: Double
27
+ let maximumAge: Double
28
+ let accuracy: CLLocationAccuracy
29
+ let distanceFilter: CLLocationDistance
30
+ let useSignificantChanges: Bool
31
+
32
+ static func parse(from options: GeolocationOptions?) -> ParsedOptions {
33
+ let timeout = options?.timeout ?? DEFAULT_TIMEOUT
34
+ let maximumAge = options?.maximumAge ?? DEFAULT_MAXIMUM_AGE
35
+ let enableHighAccuracy = options?.enableHighAccuracy ?? false
36
+ let accuracy =
37
+ enableHighAccuracy ? kCLLocationAccuracyBest : kCLLocationAccuracyHundredMeters
38
+ let distanceFilter = options?.distanceFilter ?? kCLDistanceFilterNone
39
+ let useSignificantChanges = options?.useSignificantChanges ?? false
40
+
41
+ return ParsedOptions(
42
+ timeout: timeout,
43
+ maximumAge: maximumAge,
44
+ accuracy: accuracy,
45
+ distanceFilter: distanceFilter,
46
+ useSignificantChanges: useSignificantChanges
47
+ )
48
+ }
49
+ }
50
+
51
+ // MARK: - Properties
52
+
53
+ private var locationManager: CLLocationManager?
54
+ private var lastLocation: CLLocation?
55
+ private var usingSignificantChanges: Bool = false
56
+
57
+ // Authorization
58
+ private var queuedAuthorizationCallbacks:
59
+ [(success: (() -> Void)?, error: ((GeolocationError) -> Void)?)] = []
60
+
61
+ // getCurrentPosition
62
+ private var pendingRequests: [LocationRequest] = []
63
+
64
+ // watchPosition
65
+ private var activeWatches: [Double: WatchSubscription] = [:]
66
+ private var nextWatchId: Double = 1
67
+
68
+ // Error codes
69
+ private let PERMISSION_DENIED = 1
70
+ private let POSITION_UNAVAILABLE = 2
71
+ private let TIMEOUT = 3
72
+
73
+ // MARK: - Constants
74
+
75
+ private static let DEFAULT_TIMEOUT: Double = 10 * 60 * 1000 // 10 minutes in ms
76
+ private static let DEFAULT_MAXIMUM_AGE: Double = Double.infinity
77
+
78
+ // MARK: - Authorization
79
+
80
+ func requestAuthorization(
81
+ authType: AuthorizationType,
82
+ skipPermissionRequests: Bool,
83
+ enableBackgroundLocationUpdates: Bool,
84
+ success: (() -> Void)?,
85
+ error: ((GeolocationError) -> Void)?
86
+ ) {
87
+ DispatchQueue.main.async { [weak self] in
88
+ guard let self = self else { return }
89
+
90
+ self.initializeLocationManagerIfNeeded()
91
+ self.enqueueAuthorizationCallbacks(success: success, error: error)
92
+
93
+ // Skip permission requests if configured
94
+ if skipPermissionRequests {
95
+ if enableBackgroundLocationUpdates {
96
+ self.enableBackgroundLocationUpdatesIfNeeded()
97
+ }
98
+ self.handleAuthorizationSuccess()
99
+ return
100
+ }
101
+
102
+ // Check if already authorized
103
+ let currentStatus = CLLocationManager.authorizationStatus()
104
+ if currentStatus == .authorizedAlways || currentStatus == .authorizedWhenInUse {
105
+ self.handleAuthorizationSuccess()
106
+ return
107
+ }
108
+
109
+ if currentStatus == .denied || currentStatus == .restricted {
110
+ self.handleAuthorizationError(for: currentStatus)
111
+ return
112
+ }
113
+
114
+ // Not determined yet, request permission
115
+ self.requestPermission(for: authType)
116
+ }
117
+ }
118
+
119
+ private func enqueueAuthorizationCallbacks(
120
+ success: (() -> Void)?, error: ((GeolocationError) -> Void)?
121
+ ) {
122
+ guard success != nil || error != nil else { return }
123
+ queuedAuthorizationCallbacks.append((success: success, error: error))
124
+ }
125
+
126
+ private func requestPermission(for type: AuthorizationType) {
127
+ switch type {
128
+ case .always:
129
+ locationManager?.requestAlwaysAuthorization()
130
+ enableBackgroundLocationUpdatesIfNeeded()
131
+ case .whenInUse:
132
+ locationManager?.requestWhenInUseAuthorization()
133
+ case .none:
134
+ break
135
+ }
136
+ }
137
+
138
+ private func enableBackgroundLocationUpdatesIfNeeded() {
139
+ guard
140
+ let backgroundModes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes")
141
+ as? [String],
142
+ backgroundModes.contains("location")
143
+ else {
144
+ return
145
+ }
146
+ locationManager?.allowsBackgroundLocationUpdates = true
147
+ }
148
+
149
+ // MARK: - Get Current Position
150
+
151
+ func getCurrentPosition(
152
+ success: @escaping (GeolocationResponse) -> Void,
153
+ error: ((GeolocationError) -> Void)?,
154
+ options: GeolocationOptions?
155
+ ) {
156
+ DispatchQueue.main.async { [weak self] in
157
+ guard let self = self else { return }
158
+
159
+ let parsedOptions = ParsedOptions.parse(from: options)
160
+
161
+ // Check authorization
162
+ let status = CLLocationManager.authorizationStatus()
163
+ if status == .denied || status == .restricted {
164
+ let message =
165
+ status == .restricted
166
+ ? "This application is not authorized to use location services"
167
+ : "User denied access to location services."
168
+ error?(self.createError(code: self.PERMISSION_DENIED, message: message))
169
+ return
170
+ }
171
+
172
+ if !CLLocationManager.locationServicesEnabled() {
173
+ error?(
174
+ self.createError(
175
+ code: self.POSITION_UNAVAILABLE, message: "Location services disabled."))
176
+ return
177
+ }
178
+
179
+ // Check cached location
180
+ if let cached = self.lastLocation,
181
+ self.isCachedLocationValid(cached, options: parsedOptions)
182
+ {
183
+ success(self.locationToPosition(cached))
184
+ return
185
+ }
186
+
187
+ self.initializeLocationManagerIfNeeded()
188
+
189
+ // Configure location manager (use best accuracy from all pending requests)
190
+ self.updateLocationManagerConfiguration()
191
+
192
+ // Create request
193
+ var request = LocationRequest(
194
+ success: success,
195
+ error: error,
196
+ options: parsedOptions,
197
+ timer: nil
198
+ )
199
+
200
+ // Setup timeout
201
+ let timer = Timer.scheduledTimer(
202
+ withTimeInterval: parsedOptions.timeout / 1000.0, repeats: false
203
+ ) { [weak self] timer in
204
+ self?.handleTimeout(for: timer)
205
+ }
206
+ request.timer = timer
207
+
208
+ self.pendingRequests.append(request)
209
+
210
+ // Start location updates
211
+ self.startMonitoring()
212
+ }
213
+ }
214
+
215
+ // MARK: - Watch Position
216
+
217
+ func watchPosition(
218
+ success: @escaping (GeolocationResponse) -> Void,
219
+ error: ((GeolocationError) -> Void)?,
220
+ options: GeolocationOptions?
221
+ ) -> Double {
222
+ var resultWatchId: Double = 0
223
+
224
+ DispatchQueue.main.sync { [weak self] in
225
+ guard let self = self else { return }
226
+
227
+ let parsedOptions = ParsedOptions.parse(from: options)
228
+ let watchId = self.nextWatchId
229
+ self.nextWatchId += 1
230
+
231
+ let subscription = WatchSubscription(
232
+ success: success,
233
+ error: error,
234
+ options: parsedOptions
235
+ )
236
+
237
+ self.activeWatches[watchId] = subscription
238
+
239
+ self.initializeLocationManagerIfNeeded()
240
+ self.updateLocationManagerConfiguration()
241
+ self.startMonitoring()
242
+
243
+ resultWatchId = watchId
244
+ }
245
+
246
+ return resultWatchId
247
+ }
248
+
249
+ func clearWatch(watchId: Double) {
250
+ DispatchQueue.main.async { [weak self] in
251
+ guard let self = self else { return }
252
+
253
+ self.activeWatches.removeValue(forKey: watchId)
254
+
255
+ // Stop monitoring if no more watches or pending requests
256
+ if self.activeWatches.isEmpty && self.pendingRequests.isEmpty {
257
+ self.stopMonitoring()
258
+ }
259
+ }
260
+ }
261
+
262
+ func stopObserving() {
263
+ DispatchQueue.main.async { [weak self] in
264
+ guard let self = self else { return }
265
+
266
+ self.activeWatches.removeAll()
267
+
268
+ // Stop monitoring if no pending requests
269
+ if self.pendingRequests.isEmpty {
270
+ self.stopMonitoring()
271
+ }
272
+ }
273
+ }
274
+
275
+ // MARK: - CLLocationManagerDelegate
276
+
277
+ func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
278
+ let status = getCurrentAuthorizationStatus(from: manager)
279
+
280
+ switch status {
281
+ case .authorizedAlways, .authorizedWhenInUse:
282
+ handleAuthorizationSuccess()
283
+ startMonitoring()
284
+ case .denied, .restricted:
285
+ handleAuthorizationError(for: status)
286
+ case .notDetermined:
287
+ break
288
+ @unknown default:
289
+ break
290
+ }
291
+ }
292
+
293
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
294
+ guard let location = locations.last else { return }
295
+
296
+ lastLocation = location
297
+ let position = locationToPosition(location)
298
+
299
+ // 1. Fire all pending getCurrentPosition requests
300
+ for request in pendingRequests {
301
+ request.timer?.invalidate()
302
+ request.success(position)
303
+ }
304
+ pendingRequests.removeAll()
305
+
306
+ // 2. Fire all active watchPosition subscriptions
307
+ for (_, watch) in activeWatches {
308
+ watch.success(position)
309
+ }
310
+
311
+ // 3. Stop monitoring if no more watches or pending requests
312
+ if activeWatches.isEmpty && pendingRequests.isEmpty {
313
+ stopMonitoring()
314
+ }
315
+ }
316
+
317
+ func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
318
+ let geoError: GeolocationError
319
+
320
+ if let clError = error as? CLError {
321
+ switch clError.code {
322
+ case .denied:
323
+ geoError = createError(
324
+ code: PERMISSION_DENIED, message: "User denied access to location services.")
325
+ case .locationUnknown:
326
+ // Location is temporarily unavailable, keep trying
327
+ return
328
+ default:
329
+ geoError = createError(
330
+ code: POSITION_UNAVAILABLE,
331
+ message: "Unable to retrieve location: \(error.localizedDescription)")
332
+ }
333
+ } else {
334
+ geoError = createError(
335
+ code: POSITION_UNAVAILABLE,
336
+ message: "Unable to retrieve location: \(error.localizedDescription)")
337
+ }
338
+
339
+ // Fire all pending requests with error
340
+ for request in pendingRequests {
341
+ request.timer?.invalidate()
342
+ request.error?(geoError)
343
+ }
344
+ pendingRequests.removeAll()
345
+
346
+ // Fire all active watches with error
347
+ for (_, watch) in activeWatches {
348
+ watch.error?(geoError)
349
+ }
350
+
351
+ stopMonitoring()
352
+ }
353
+
354
+ // MARK: - Helper Functions
355
+
356
+ private func initializeLocationManagerIfNeeded() {
357
+ guard locationManager == nil else { return }
358
+ locationManager = CLLocationManager()
359
+ locationManager?.delegate = self
360
+ }
361
+
362
+ private func updateLocationManagerConfiguration() {
363
+ guard let manager = locationManager else { return }
364
+
365
+ // Find the best (most accurate) settings from all pending requests and active watches
366
+ var bestAccuracy = kCLLocationAccuracyHundredMeters
367
+ var smallestDistanceFilter = kCLDistanceFilterNone
368
+ var shouldUseSignificantChanges = false
369
+
370
+ for request in pendingRequests {
371
+ bestAccuracy = min(bestAccuracy, request.options.accuracy)
372
+ smallestDistanceFilter = min(smallestDistanceFilter, request.options.distanceFilter)
373
+ shouldUseSignificantChanges =
374
+ shouldUseSignificantChanges || request.options.useSignificantChanges
375
+ }
376
+
377
+ for (_, watch) in activeWatches {
378
+ bestAccuracy = min(bestAccuracy, watch.options.accuracy)
379
+ smallestDistanceFilter = min(smallestDistanceFilter, watch.options.distanceFilter)
380
+ shouldUseSignificantChanges =
381
+ shouldUseSignificantChanges || watch.options.useSignificantChanges
382
+ }
383
+
384
+ manager.desiredAccuracy = bestAccuracy
385
+ manager.distanceFilter = smallestDistanceFilter
386
+
387
+ // Update significant changes mode if changed
388
+ if shouldUseSignificantChanges != usingSignificantChanges {
389
+ stopMonitoring()
390
+ usingSignificantChanges = shouldUseSignificantChanges
391
+ startMonitoring()
392
+ }
393
+ }
394
+
395
+ private func startMonitoring() {
396
+ if usingSignificantChanges {
397
+ locationManager?.startMonitoringSignificantLocationChanges()
398
+ } else {
399
+ locationManager?.startUpdatingLocation()
400
+ }
401
+ }
402
+
403
+ private func stopMonitoring() {
404
+ if usingSignificantChanges {
405
+ locationManager?.stopMonitoringSignificantLocationChanges()
406
+ } else {
407
+ locationManager?.stopUpdatingLocation()
408
+ }
409
+ }
410
+
411
+ private func isCachedLocationValid(_ location: CLLocation, options: ParsedOptions) -> Bool {
412
+ // Check if maximumAge is infinity
413
+ if options.maximumAge.isInfinite {
414
+ return true
415
+ }
416
+
417
+ // Check age
418
+ let age = Date().timeIntervalSince(location.timestamp) * 1000 // convert to ms
419
+ guard age < options.maximumAge else {
420
+ return false
421
+ }
422
+
423
+ // Check accuracy
424
+ guard location.horizontalAccuracy <= options.accuracy else {
425
+ return false
426
+ }
427
+
428
+ return true
429
+ }
430
+
431
+ private func handleTimeout(for timer: Timer) {
432
+ // Find and remove the request with this timer
433
+ if let index = pendingRequests.firstIndex(where: { $0.timer === timer }) {
434
+ let request = pendingRequests[index]
435
+ pendingRequests.remove(at: index)
436
+
437
+ // Always return timeout error
438
+ let timeoutSeconds = request.options.timeout / 1000.0
439
+ let message = String(format: "Unable to fetch location within %.1fs.", timeoutSeconds)
440
+ request.error?(createError(code: TIMEOUT, message: message))
441
+
442
+ // Stop monitoring if no more watches or pending requests
443
+ if activeWatches.isEmpty && pendingRequests.isEmpty {
444
+ stopMonitoring()
445
+ }
446
+ }
447
+ }
448
+
449
+ private func getCurrentAuthorizationStatus(from manager: CLLocationManager)
450
+ -> CLAuthorizationStatus
451
+ {
452
+ if #available(iOS 14.0, *) {
453
+ return manager.authorizationStatus
454
+ } else {
455
+ return CLLocationManager.authorizationStatus()
456
+ }
457
+ }
458
+
459
+ private func handleAuthorizationSuccess() {
460
+ invokeQueuedAuthorizationCallbacks(success: true)
461
+ }
462
+
463
+ private func handleAuthorizationError(for status: CLAuthorizationStatus) {
464
+ let error = createGeolocationError(for: status)
465
+ invokeQueuedAuthorizationCallbacks(error: error)
466
+ }
467
+
468
+ private func createGeolocationError(for status: CLAuthorizationStatus) -> GeolocationError {
469
+ let message =
470
+ status == .restricted
471
+ ? "This application is not authorized to use location services"
472
+ : "User denied access to location services."
473
+
474
+ return GeolocationError(
475
+ code: Double(PERMISSION_DENIED),
476
+ message: message,
477
+ PERMISSION_DENIED: Double(PERMISSION_DENIED),
478
+ POSITION_UNAVAILABLE: Double(POSITION_UNAVAILABLE),
479
+ TIMEOUT: Double(TIMEOUT)
480
+ )
481
+ }
482
+
483
+ private func invokeQueuedAuthorizationCallbacks(
484
+ success: Bool = false, error: GeolocationError? = nil
485
+ ) {
486
+ for callback in queuedAuthorizationCallbacks {
487
+ if success {
488
+ callback.success?()
489
+ } else if let error = error {
490
+ callback.error?(error)
491
+ }
492
+ }
493
+ queuedAuthorizationCallbacks.removeAll()
494
+ }
495
+
496
+ private func locationToPosition(_ location: CLLocation) -> GeolocationResponse {
497
+ let altitude = location.verticalAccuracy < 0 ? 0.0 : location.altitude
498
+ let altitudeAccuracy = location.verticalAccuracy < 0 ? 0.0 : location.verticalAccuracy
499
+ let heading = location.course >= 0 ? location.course : -1.0
500
+ let speed = location.speed >= 0 ? location.speed : 0.0
501
+
502
+ let coordsObj = GeolocationCoordinates(
503
+ latitude: location.coordinate.latitude,
504
+ longitude: location.coordinate.longitude,
505
+ altitude: altitude,
506
+ accuracy: location.horizontalAccuracy,
507
+ altitudeAccuracy: altitudeAccuracy,
508
+ heading: heading,
509
+ speed: speed
510
+ )
511
+
512
+ let position = GeolocationResponse(
513
+ coords: coordsObj,
514
+ timestamp: location.timestamp.timeIntervalSince1970 * 1000
515
+ )
516
+
517
+ return position
518
+ }
519
+
520
+ private func createError(code: Int, message: String) -> GeolocationError {
521
+ return GeolocationError(
522
+ code: Double(code),
523
+ message: message,
524
+ PERMISSION_DENIED: Double(PERMISSION_DENIED),
525
+ POSITION_UNAVAILABLE: Double(POSITION_UNAVAILABLE),
526
+ TIMEOUT: Double(TIMEOUT)
527
+ )
528
+ }
529
+ }
@@ -1,5 +1,99 @@
1
+ import CoreLocation
2
+
1
3
  class NitroGeolocation: HybridNitroGeolocationSpec {
2
- public func multiply(a: Double, b: Double) throws -> Double {
3
- return a * b
4
+ // MARK: - Properties
5
+
6
+ private var configuration: RNConfigurationInternal = RNConfigurationInternal(
7
+ skipPermissionRequests: false,
8
+ authorizationLevel: nil,
9
+ enableBackgroundLocationUpdates: nil,
10
+ locationProvider: nil
11
+ )
12
+
13
+ private let locationManager = LocationManager()
14
+
15
+ // MARK: - Public API
16
+
17
+ public func setRNConfiguration(config: RNConfigurationInternal) throws {
18
+ configuration = config
19
+ }
20
+
21
+ public func requestAuthorization(success: (() -> Void)?, error: ((GeolocationError) -> Void)?)
22
+ throws
23
+ {
24
+ let authType = determineAuthorizationType()
25
+ let skipPermissionRequests = configuration.skipPermissionRequests
26
+ let enableBackgroundLocationUpdates = configuration.enableBackgroundLocationUpdates ?? false
27
+
28
+ locationManager.requestAuthorization(
29
+ authType: authType,
30
+ skipPermissionRequests: skipPermissionRequests,
31
+ enableBackgroundLocationUpdates: enableBackgroundLocationUpdates,
32
+ success: success,
33
+ error: error
34
+ )
35
+ }
36
+
37
+ public func getCurrentPosition(
38
+ success: @escaping (GeolocationResponse) -> Void, error: ((GeolocationError) -> Void)?,
39
+ options: GeolocationOptions?
40
+ ) throws {
41
+ locationManager.getCurrentPosition(success: success, error: error, options: options)
42
+ }
43
+
44
+ public func watchPosition(
45
+ success: @escaping (GeolocationResponse) -> Void, error: ((GeolocationError) -> Void)?,
46
+ options: GeolocationOptions?
47
+ ) throws -> Double {
48
+ return locationManager.watchPosition(success: success, error: error, options: options)
49
+ }
50
+
51
+ public func clearWatch(watchId: Double) throws {
52
+ locationManager.clearWatch(watchId: watchId)
53
+ }
54
+
55
+ public func stopObserving() throws {
56
+ locationManager.stopObserving()
57
+ }
58
+
59
+ // MARK: - Authorization Helpers
60
+
61
+ private func determineAuthorizationType() -> LocationManager.AuthorizationType {
62
+ guard let authLevel = configuration.authorizationLevel else {
63
+ return determineAuthorizationTypeFromInfoPlist()
64
+ }
65
+
66
+ switch authLevel {
67
+ case .always:
68
+ return .always
69
+ case .wheninuse:
70
+ return .whenInUse
71
+ case .auto:
72
+ return determineAuthorizationTypeFromInfoPlist()
73
+ }
74
+ }
75
+
76
+ private func determineAuthorizationTypeFromInfoPlist() -> LocationManager.AuthorizationType {
77
+ if hasInfoPlistKey(for: .always) {
78
+ return .always
79
+ } else if hasInfoPlistKey(for: .whenInUse) {
80
+ return .whenInUse
81
+ }
82
+ return .none
83
+ }
84
+
85
+ private func hasInfoPlistKey(for type: LocationManager.AuthorizationType) -> Bool {
86
+ switch type {
87
+ case .always:
88
+ return Bundle.main.object(forInfoDictionaryKey: "NSLocationAlwaysUsageDescription")
89
+ != nil
90
+ || Bundle.main.object(
91
+ forInfoDictionaryKey: "NSLocationAlwaysAndWhenInUseUsageDescription") != nil
92
+ case .whenInUse:
93
+ return Bundle.main.object(forInfoDictionaryKey: "NSLocationWhenInUseUsageDescription")
94
+ != nil
95
+ case .none:
96
+ return false
97
+ }
4
98
  }
5
99
  }
@@ -0,0 +1 @@
1
+ ** linguist-generated=true
@@ -0,0 +1,62 @@
1
+ ///
2
+ /// JAuthorizationLevelInternal.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © 2025 Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #include <fbjni/fbjni.h>
11
+ #include "AuthorizationLevelInternal.hpp"
12
+
13
+ namespace margelo::nitro::nitrogeolocation {
14
+
15
+ using namespace facebook;
16
+
17
+ /**
18
+ * The C++ JNI bridge between the C++ enum "AuthorizationLevelInternal" and the the Kotlin enum "AuthorizationLevelInternal".
19
+ */
20
+ struct JAuthorizationLevelInternal final: public jni::JavaClass<JAuthorizationLevelInternal> {
21
+ public:
22
+ static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/nitrogeolocation/AuthorizationLevelInternal;";
23
+
24
+ public:
25
+ /**
26
+ * Convert this Java/Kotlin-based enum to the C++ enum AuthorizationLevelInternal.
27
+ */
28
+ [[maybe_unused]]
29
+ [[nodiscard]]
30
+ AuthorizationLevelInternal toCpp() const {
31
+ static const auto clazz = javaClassStatic();
32
+ static const auto fieldOrdinal = clazz->getField<int>("value");
33
+ int ordinal = this->getFieldValue(fieldOrdinal);
34
+ return static_cast<AuthorizationLevelInternal>(ordinal);
35
+ }
36
+
37
+ public:
38
+ /**
39
+ * Create a Java/Kotlin-based enum with the given C++ enum's value.
40
+ */
41
+ [[maybe_unused]]
42
+ static jni::alias_ref<JAuthorizationLevelInternal> fromCpp(AuthorizationLevelInternal value) {
43
+ static const auto clazz = javaClassStatic();
44
+ static const auto fieldALWAYS = clazz->getStaticField<JAuthorizationLevelInternal>("ALWAYS");
45
+ static const auto fieldWHENINUSE = clazz->getStaticField<JAuthorizationLevelInternal>("WHENINUSE");
46
+ static const auto fieldAUTO = clazz->getStaticField<JAuthorizationLevelInternal>("AUTO");
47
+
48
+ switch (value) {
49
+ case AuthorizationLevelInternal::ALWAYS:
50
+ return clazz->getStaticFieldValue(fieldALWAYS);
51
+ case AuthorizationLevelInternal::WHENINUSE:
52
+ return clazz->getStaticFieldValue(fieldWHENINUSE);
53
+ case AuthorizationLevelInternal::AUTO:
54
+ return clazz->getStaticFieldValue(fieldAUTO);
55
+ default:
56
+ std::string stringValue = std::to_string(static_cast<int>(value));
57
+ throw std::invalid_argument("Invalid enum value (" + stringValue + "!");
58
+ }
59
+ }
60
+ };
61
+
62
+ } // namespace margelo::nitro::nitrogeolocation