react-native-nitro-compass 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.
- package/LICENSE +21 -0
- package/NitroCompass.podspec +31 -0
- package/README.md +206 -0
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +148 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/margelo/nitro/nitrocompass/HybridNitroCompass.kt +481 -0
- package/android/src/main/java/com/margelo/nitro/nitrocompass/NitroCompassPackage.kt +18 -0
- package/app.plugin.js +16 -0
- package/ios/Bridge.h +8 -0
- package/ios/HybridNitroCompass.swift +473 -0
- package/lib/commonjs/hook.js +69 -0
- package/lib/commonjs/hook.js.map +1 -0
- package/lib/commonjs/index.js +39 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/multiplex.js +109 -0
- package/lib/commonjs/multiplex.js.map +1 -0
- package/lib/commonjs/native.js +9 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/NitroCompass.nitro.js +6 -0
- package/lib/commonjs/specs/NitroCompass.nitro.js.map +1 -0
- package/lib/module/hook.js +65 -0
- package/lib/module/hook.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/multiplex.js +103 -0
- package/lib/module/multiplex.js.map +1 -0
- package/lib/module/native.js +5 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/specs/NitroCompass.nitro.js +4 -0
- package/lib/module/specs/NitroCompass.nitro.js.map +1 -0
- package/lib/typescript/src/hook.d.ts +49 -0
- package/lib/typescript/src/hook.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +8 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/multiplex.d.ts +38 -0
- package/lib/typescript/src/multiplex.d.ts.map +1 -0
- package/lib/typescript/src/native.d.ts +3 -0
- package/lib/typescript/src/native.d.ts.map +1 -0
- package/lib/typescript/src/specs/NitroCompass.nitro.d.ts +176 -0
- package/lib/typescript/src/specs/NitroCompass.nitro.d.ts.map +1 -0
- package/nitro.json +30 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroCompass+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroCompass+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroCompassOnLoad.cpp +60 -0
- package/nitrogen/generated/android/NitroCompassOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JAccuracyQuality.hpp +64 -0
- package/nitrogen/generated/android/c++/JCompassSample.hpp +61 -0
- package/nitrogen/generated/android/c++/JFunc_void_AccuracyQuality.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_CompassSample.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.cpp +143 -0
- package/nitrogen/generated/android/c++/JHybridNitroCompassSpec.hpp +75 -0
- package/nitrogen/generated/android/c++/JPermissionStatus.hpp +61 -0
- package/nitrogen/generated/android/c++/JSensorDiagnostics.hpp +58 -0
- package/nitrogen/generated/android/c++/JSensorKind.hpp +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/AccuracyQuality.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/CompassSample.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/Func_void_AccuracyQuality.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/Func_void_CompassSample.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/HybridNitroCompassSpec.kt +118 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/NitroCompassOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/PermissionStatus.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/SensorDiagnostics.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrocompass/SensorKind.kt +24 -0
- package/nitrogen/generated/ios/NitroCompass+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Bridge.cpp +73 -0
- package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Bridge.hpp +267 -0
- package/nitrogen/generated/ios/NitroCompass-Swift-Cxx-Umbrella.hpp +61 -0
- package/nitrogen/generated/ios/NitroCompassAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroCompassAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridNitroCompassSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNitroCompassSpecSwift.hpp +180 -0
- package/nitrogen/generated/ios/swift/AccuracyQuality.swift +48 -0
- package/nitrogen/generated/ios/swift/CompassSample.swift +34 -0
- package/nitrogen/generated/ios/swift/Func_void_AccuracyQuality.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CompassSample.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_PermissionStatus.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNitroCompassSpec.swift +67 -0
- package/nitrogen/generated/ios/swift/HybridNitroCompassSpec_cxx.swift +309 -0
- package/nitrogen/generated/ios/swift/PermissionStatus.swift +44 -0
- package/nitrogen/generated/ios/swift/SensorDiagnostics.swift +29 -0
- package/nitrogen/generated/ios/swift/SensorKind.swift +44 -0
- package/nitrogen/generated/shared/c++/AccuracyQuality.hpp +84 -0
- package/nitrogen/generated/shared/c++/CompassSample.hpp +87 -0
- package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.cpp +33 -0
- package/nitrogen/generated/shared/c++/HybridNitroCompassSpec.hpp +87 -0
- package/nitrogen/generated/shared/c++/PermissionStatus.hpp +80 -0
- package/nitrogen/generated/shared/c++/SensorDiagnostics.hpp +84 -0
- package/nitrogen/generated/shared/c++/SensorKind.hpp +80 -0
- package/package.json +136 -0
- package/react-native.config.js +11 -0
- package/src/hook.ts +118 -0
- package/src/index.ts +28 -0
- package/src/multiplex.ts +117 -0
- package/src/native.ts +5 -0
- package/src/specs/NitroCompass.nitro.ts +193 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HybridNitroCompass.swift
|
|
3
|
+
// NitroCompass
|
|
4
|
+
//
|
|
5
|
+
// iOS implementation of the NitroCompass HybridObject. Uses
|
|
6
|
+
// CLLocationManager for the heading source — Apple's stack already does
|
|
7
|
+
// proper sensor fusion natively, so the Swift side stays simple.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import Foundation
|
|
11
|
+
import CoreLocation
|
|
12
|
+
import CoreMotion
|
|
13
|
+
import NitroModules
|
|
14
|
+
import UIKit
|
|
15
|
+
|
|
16
|
+
// Earth's magnetic field magnitude is typically 25–65 µT. Anything
|
|
17
|
+
// outside this band (with a small grace margin) is treated as
|
|
18
|
+
// external interference — laptops, monitors, car engines, and
|
|
19
|
+
// structural steel routinely push readings well above 100 µT.
|
|
20
|
+
private let earthFieldMinUT: Double = 20
|
|
21
|
+
private let earthFieldMaxUT: Double = 70
|
|
22
|
+
|
|
23
|
+
class HybridNitroCompass: HybridNitroCompassSpec {
|
|
24
|
+
// CLLocationManager must be created on a thread with an active run loop
|
|
25
|
+
// (typically main). Nitro can instantiate HybridObjects off-main, so we
|
|
26
|
+
// hop to main for construction and configuration.
|
|
27
|
+
private var manager: CLLocationManager!
|
|
28
|
+
private var delegateProxy: HeadingDelegate?
|
|
29
|
+
private var onSample: ((CompassSample) -> Void)?
|
|
30
|
+
private var orientationObserver: NSObjectProtocol?
|
|
31
|
+
private var backgroundObserver: NSObjectProtocol?
|
|
32
|
+
private var foregroundObserver: NSObjectProtocol?
|
|
33
|
+
private var declinationDeg: Double = 0
|
|
34
|
+
private var lastSample: CompassSample?
|
|
35
|
+
private var lastQuality: AccuracyQuality?
|
|
36
|
+
private var calibrationCb: ((AccuracyQuality) -> Void)?
|
|
37
|
+
private var interferenceCb: ((Bool) -> Void)?
|
|
38
|
+
private let motionManager = CMMotionManager()
|
|
39
|
+
private let motionQueue: OperationQueue = {
|
|
40
|
+
let q = OperationQueue()
|
|
41
|
+
q.name = "NitroCompass.motion"
|
|
42
|
+
q.maxConcurrentOperationCount = 1
|
|
43
|
+
return q
|
|
44
|
+
}()
|
|
45
|
+
private var lastInterference: Bool?
|
|
46
|
+
private var pauseOnBackground: Bool = true
|
|
47
|
+
private var started: Bool = false
|
|
48
|
+
private var isSubscribed: Bool = false
|
|
49
|
+
private var activeFilterDegrees: Double = 1
|
|
50
|
+
// Mirrors UIApplication.applicationState. Updated from background/foreground
|
|
51
|
+
// notification observers (delivered on main) so JS-thread callers don't
|
|
52
|
+
// have to hop to main to read it.
|
|
53
|
+
private var appIsBackgrounded: Bool = false
|
|
54
|
+
// Holds an in-flight permission request. CLLocationManager.delegate is
|
|
55
|
+
// weak, so we own the resolver here for the duration of the request.
|
|
56
|
+
private var authResolver: AuthRequestResolver?
|
|
57
|
+
|
|
58
|
+
override init() {
|
|
59
|
+
super.init()
|
|
60
|
+
let setup = {
|
|
61
|
+
let m = CLLocationManager()
|
|
62
|
+
m.headingFilter = 1
|
|
63
|
+
m.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
|
64
|
+
self.manager = m
|
|
65
|
+
}
|
|
66
|
+
if Thread.isMainThread {
|
|
67
|
+
setup()
|
|
68
|
+
} else {
|
|
69
|
+
DispatchQueue.main.sync(execute: setup)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
deinit {
|
|
74
|
+
stopInternal()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func start(filterDegrees: Double, onHeading: @escaping (_ sample: CompassSample) -> Void) throws {
|
|
78
|
+
guard CLLocationManager.headingAvailable() else {
|
|
79
|
+
throw NSError(
|
|
80
|
+
domain: "NitroCompass",
|
|
81
|
+
code: 1,
|
|
82
|
+
userInfo: [NSLocalizedDescriptionKey: "Heading unavailable on this device"]
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// CLLocationManager.startUpdatingHeading silently delivers nothing
|
|
87
|
+
// when location authorization is denied. Surface that explicitly so
|
|
88
|
+
// callers don't wait on a callback that will never fire.
|
|
89
|
+
// .notDetermined still proceeds — the host may request later.
|
|
90
|
+
let authStatus = manager.authorizationStatus
|
|
91
|
+
if authStatus == .denied || authStatus == .restricted {
|
|
92
|
+
throw NSError(
|
|
93
|
+
domain: "NitroCompass",
|
|
94
|
+
code: 2,
|
|
95
|
+
userInfo: [NSLocalizedDescriptionKey: "Location authorization denied — request authorization before calling start()"]
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
stopInternal()
|
|
100
|
+
|
|
101
|
+
onSample = onHeading
|
|
102
|
+
activeFilterDegrees = filterDegrees
|
|
103
|
+
started = true
|
|
104
|
+
|
|
105
|
+
let proxy = HeadingDelegate(
|
|
106
|
+
onSample: { [weak self] heading, accuracy in
|
|
107
|
+
self?.deliver(heading: heading, accuracy: accuracy)
|
|
108
|
+
},
|
|
109
|
+
onCalibrationOverride: { [weak self] in
|
|
110
|
+
self?.fireCalibration(.unreliable)
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
delegateProxy = proxy
|
|
114
|
+
manager.delegate = proxy
|
|
115
|
+
|
|
116
|
+
orientationObserver = NotificationCenter.default.addObserver(
|
|
117
|
+
forName: UIDevice.orientationDidChangeNotification,
|
|
118
|
+
object: nil,
|
|
119
|
+
queue: .main
|
|
120
|
+
) { [weak self] _ in
|
|
121
|
+
self?.applyHeadingOrientation()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
backgroundObserver = NotificationCenter.default.addObserver(
|
|
125
|
+
forName: UIApplication.didEnterBackgroundNotification,
|
|
126
|
+
object: nil,
|
|
127
|
+
queue: .main
|
|
128
|
+
) { [weak self] _ in
|
|
129
|
+
self?.handleBackground()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
foregroundObserver = NotificationCenter.default.addObserver(
|
|
133
|
+
forName: UIApplication.willEnterForegroundNotification,
|
|
134
|
+
object: nil,
|
|
135
|
+
queue: .main
|
|
136
|
+
) { [weak self] _ in
|
|
137
|
+
self?.handleForeground()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Read background state synchronously so a start() call from
|
|
141
|
+
// background correctly skips the initial subscribe under
|
|
142
|
+
// pauseOnBackground=true. Without this the lifecycle observers
|
|
143
|
+
// wouldn't fire (we're already in BG) and a useless subscription
|
|
144
|
+
// would sit there until the next FG↔BG cycle.
|
|
145
|
+
let backgroundedAtStart: Bool
|
|
146
|
+
if Thread.isMainThread {
|
|
147
|
+
backgroundedAtStart = UIApplication.shared.applicationState == .background
|
|
148
|
+
} else {
|
|
149
|
+
var bg = false
|
|
150
|
+
DispatchQueue.main.sync {
|
|
151
|
+
bg = UIApplication.shared.applicationState == .background
|
|
152
|
+
}
|
|
153
|
+
backgroundedAtStart = bg
|
|
154
|
+
}
|
|
155
|
+
appIsBackgrounded = backgroundedAtStart
|
|
156
|
+
|
|
157
|
+
DispatchQueue.main.async { [weak self] in
|
|
158
|
+
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
|
|
159
|
+
self?.applyHeadingOrientation()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if !(pauseOnBackground && backgroundedAtStart) {
|
|
163
|
+
subscribe()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func stop() throws {
|
|
168
|
+
stopInternal()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func hasCompass() throws -> Bool {
|
|
172
|
+
return CLLocationManager.headingAvailable()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func isStarted() throws -> Bool {
|
|
176
|
+
return started
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
func setFilter(degrees: Double) throws {
|
|
180
|
+
activeFilterDegrees = degrees
|
|
181
|
+
if isSubscribed {
|
|
182
|
+
manager.headingFilter = degrees == 0 ? kCLHeadingFilterNone : degrees
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func getDiagnostics() throws -> SensorDiagnostics? {
|
|
187
|
+
guard CLLocationManager.headingAvailable() else { return nil }
|
|
188
|
+
return SensorDiagnostics(sensor: .corelocation)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func getCurrentHeading() throws -> CompassSample? {
|
|
192
|
+
return lastSample
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func setDeclination(degrees: Double) throws {
|
|
196
|
+
declinationDeg = degrees
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func setOnCalibrationNeeded(onChange: @escaping (_ quality: AccuracyQuality) -> Void) throws {
|
|
200
|
+
calibrationCb = onChange
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
func setOnInterferenceDetected(onChange: @escaping (_ interferenceDetected: Bool) -> Void) throws {
|
|
204
|
+
interferenceCb = onChange
|
|
205
|
+
// Replay current state so a late-registering consumer sees the truth
|
|
206
|
+
// instead of waiting for the next transition (which may never come
|
|
207
|
+
// if the field stays stable).
|
|
208
|
+
if let last = lastInterference { onChange(last) }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func setPauseOnBackground(enabled: Bool) throws {
|
|
212
|
+
pauseOnBackground = enabled
|
|
213
|
+
if enabled, started, isSubscribed, appIsBackgrounded {
|
|
214
|
+
unsubscribe()
|
|
215
|
+
} else if !enabled, started, !isSubscribed {
|
|
216
|
+
subscribe()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
func getPermissionStatus() throws -> PermissionStatus {
|
|
221
|
+
return Self.mapAuthStatus(manager.authorizationStatus)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
func requestPermission() throws -> Promise<PermissionStatus> {
|
|
225
|
+
let current = manager.authorizationStatus
|
|
226
|
+
if current != .notDetermined {
|
|
227
|
+
return Promise.resolved(withResult: Self.mapAuthStatus(current))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let promise = Promise<PermissionStatus>()
|
|
231
|
+
|
|
232
|
+
DispatchQueue.main.async { [weak self] in
|
|
233
|
+
guard let self = self else {
|
|
234
|
+
promise.reject(withError: NSError(
|
|
235
|
+
domain: "NitroCompass",
|
|
236
|
+
code: 4,
|
|
237
|
+
userInfo: [NSLocalizedDescriptionKey: "Compass instance was deallocated"]
|
|
238
|
+
))
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Cancel any in-flight request — only one outstanding system
|
|
243
|
+
// prompt makes sense.
|
|
244
|
+
self.authResolver?.cancel()
|
|
245
|
+
|
|
246
|
+
let resolver = AuthRequestResolver(promise: promise)
|
|
247
|
+
// Save the existing delegate so heading delivery can resume after
|
|
248
|
+
// the auth callback fires. CLLocationManager.delegate is weak, so
|
|
249
|
+
// we have to keep our resolver alive on `self` for the duration.
|
|
250
|
+
resolver.savedDelegate = self.manager.delegate
|
|
251
|
+
resolver.onResolved = { [weak self] in self?.authResolver = nil }
|
|
252
|
+
self.authResolver = resolver
|
|
253
|
+
self.manager.delegate = resolver
|
|
254
|
+
self.manager.requestWhenInUseAuthorization()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return promise
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private static func mapAuthStatus(_ status: CLAuthorizationStatus) -> PermissionStatus {
|
|
261
|
+
switch status {
|
|
262
|
+
case .authorizedAlways, .authorizedWhenInUse:
|
|
263
|
+
return .granted
|
|
264
|
+
case .denied, .restricted:
|
|
265
|
+
return .denied
|
|
266
|
+
case .notDetermined:
|
|
267
|
+
return .unknown
|
|
268
|
+
@unknown default:
|
|
269
|
+
return .unknown
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// MARK: - Helpers
|
|
274
|
+
|
|
275
|
+
private func subscribe() {
|
|
276
|
+
guard !isSubscribed else { return }
|
|
277
|
+
manager.headingFilter = activeFilterDegrees == 0 ? kCLHeadingFilterNone : activeFilterDegrees
|
|
278
|
+
manager.startUpdatingHeading()
|
|
279
|
+
startMagnetometerIfAvailable()
|
|
280
|
+
isSubscribed = true
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private func unsubscribe() {
|
|
284
|
+
guard isSubscribed else { return }
|
|
285
|
+
manager.stopUpdatingHeading()
|
|
286
|
+
stopMagnetometerIfRunning()
|
|
287
|
+
isSubscribed = false
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private func startMagnetometerIfAvailable() {
|
|
291
|
+
guard motionManager.isDeviceMotionAvailable,
|
|
292
|
+
!motionManager.isDeviceMotionActive else { return }
|
|
293
|
+
motionManager.deviceMotionUpdateInterval = 0.2 // 5Hz
|
|
294
|
+
motionManager.startDeviceMotionUpdates(to: motionQueue) { [weak self] motion, _ in
|
|
295
|
+
guard let self = self, let m = motion else { return }
|
|
296
|
+
let cal = m.magneticField
|
|
297
|
+
if cal.accuracy == .uncalibrated { return }
|
|
298
|
+
let f = cal.field
|
|
299
|
+
let magnitude = sqrt(f.x * f.x + f.y * f.y + f.z * f.z)
|
|
300
|
+
self.evaluateInterference(magnitude: magnitude)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private func stopMagnetometerIfRunning() {
|
|
305
|
+
if motionManager.isDeviceMotionActive {
|
|
306
|
+
motionManager.stopDeviceMotionUpdates()
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private func evaluateInterference(magnitude: Double) {
|
|
311
|
+
let isInterference = magnitude < earthFieldMinUT || magnitude > earthFieldMaxUT
|
|
312
|
+
if lastInterference == isInterference { return }
|
|
313
|
+
lastInterference = isInterference
|
|
314
|
+
interferenceCb?(isInterference)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private func handleBackground() {
|
|
318
|
+
appIsBackgrounded = true
|
|
319
|
+
if pauseOnBackground, started, isSubscribed {
|
|
320
|
+
unsubscribe()
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private func handleForeground() {
|
|
325
|
+
appIsBackgrounded = false
|
|
326
|
+
if pauseOnBackground, started, !isSubscribed {
|
|
327
|
+
subscribe()
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private func stopInternal() {
|
|
332
|
+
started = false
|
|
333
|
+
unsubscribe()
|
|
334
|
+
if delegateProxy != nil {
|
|
335
|
+
manager.delegate = nil
|
|
336
|
+
delegateProxy = nil
|
|
337
|
+
}
|
|
338
|
+
if let observer = orientationObserver {
|
|
339
|
+
NotificationCenter.default.removeObserver(observer)
|
|
340
|
+
orientationObserver = nil
|
|
341
|
+
DispatchQueue.main.async {
|
|
342
|
+
UIDevice.current.endGeneratingDeviceOrientationNotifications()
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if let observer = backgroundObserver {
|
|
346
|
+
NotificationCenter.default.removeObserver(observer)
|
|
347
|
+
backgroundObserver = nil
|
|
348
|
+
}
|
|
349
|
+
if let observer = foregroundObserver {
|
|
350
|
+
NotificationCenter.default.removeObserver(observer)
|
|
351
|
+
foregroundObserver = nil
|
|
352
|
+
}
|
|
353
|
+
onSample = nil
|
|
354
|
+
lastSample = nil
|
|
355
|
+
lastQuality = nil
|
|
356
|
+
lastInterference = nil
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private func deliver(heading magnetic: Double, accuracy: Double) {
|
|
360
|
+
var heading = magnetic + declinationDeg
|
|
361
|
+
heading = heading.truncatingRemainder(dividingBy: 360)
|
|
362
|
+
if heading < 0 { heading += 360 }
|
|
363
|
+
let sample = CompassSample(heading: heading, accuracy: accuracy)
|
|
364
|
+
lastSample = sample
|
|
365
|
+
|
|
366
|
+
let quality: AccuracyQuality
|
|
367
|
+
if accuracy < 0 {
|
|
368
|
+
quality = .unreliable
|
|
369
|
+
} else if accuracy < 5 {
|
|
370
|
+
quality = .high
|
|
371
|
+
} else if accuracy < 15 {
|
|
372
|
+
quality = .medium
|
|
373
|
+
} else if accuracy < 30 {
|
|
374
|
+
quality = .low
|
|
375
|
+
} else {
|
|
376
|
+
quality = .unreliable
|
|
377
|
+
}
|
|
378
|
+
fireCalibration(quality)
|
|
379
|
+
|
|
380
|
+
onSample?(sample)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private func fireCalibration(_ quality: AccuracyQuality) {
|
|
384
|
+
guard quality != lastQuality else { return }
|
|
385
|
+
lastQuality = quality
|
|
386
|
+
calibrationCb?(quality)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private func applyHeadingOrientation() {
|
|
390
|
+
let scene = UIApplication.shared.connectedScenes
|
|
391
|
+
.compactMap { $0 as? UIWindowScene }
|
|
392
|
+
.first
|
|
393
|
+
let clOrientation: CLDeviceOrientation
|
|
394
|
+
switch scene?.interfaceOrientation {
|
|
395
|
+
case .landscapeLeft:
|
|
396
|
+
clOrientation = .landscapeRight
|
|
397
|
+
case .landscapeRight:
|
|
398
|
+
clOrientation = .landscapeLeft
|
|
399
|
+
case .portraitUpsideDown:
|
|
400
|
+
clOrientation = .portraitUpsideDown
|
|
401
|
+
default:
|
|
402
|
+
clOrientation = .portrait
|
|
403
|
+
}
|
|
404
|
+
manager.headingOrientation = clOrientation
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/// One-shot delegate that drives a `requestPermission()` call. It owns
|
|
409
|
+
/// the `Promise` until the system delivers the user's choice via
|
|
410
|
+
/// `locationManagerDidChangeAuthorization`, then restores the prior
|
|
411
|
+
/// delegate so heading delivery resumes if a subscription was active.
|
|
412
|
+
private class AuthRequestResolver: NSObject, CLLocationManagerDelegate {
|
|
413
|
+
private let promise: Promise<PermissionStatus>
|
|
414
|
+
weak var savedDelegate: CLLocationManagerDelegate?
|
|
415
|
+
var onResolved: (() -> Void)?
|
|
416
|
+
private var resolved = false
|
|
417
|
+
|
|
418
|
+
init(promise: Promise<PermissionStatus>) {
|
|
419
|
+
self.promise = promise
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
func cancel() {
|
|
423
|
+
guard !resolved else { return }
|
|
424
|
+
resolved = true
|
|
425
|
+
promise.reject(withError: NSError(
|
|
426
|
+
domain: "NitroCompass",
|
|
427
|
+
code: 3,
|
|
428
|
+
userInfo: [NSLocalizedDescriptionKey: "Permission request superseded"]
|
|
429
|
+
))
|
|
430
|
+
onResolved?()
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
434
|
+
let status = manager.authorizationStatus
|
|
435
|
+
if status == .notDetermined { return }
|
|
436
|
+
if resolved { return }
|
|
437
|
+
resolved = true
|
|
438
|
+
manager.delegate = savedDelegate
|
|
439
|
+
let mapped: PermissionStatus
|
|
440
|
+
switch status {
|
|
441
|
+
case .authorizedAlways, .authorizedWhenInUse: mapped = .granted
|
|
442
|
+
case .denied, .restricted: mapped = .denied
|
|
443
|
+
default: mapped = .unknown
|
|
444
|
+
}
|
|
445
|
+
promise.resolve(withResult: mapped)
|
|
446
|
+
onResolved?()
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/// CLLocationManager requires an NSObject delegate. Wrapping it lets the
|
|
451
|
+
/// HybridObject stay a pure Swift class.
|
|
452
|
+
private class HeadingDelegate: NSObject, CLLocationManagerDelegate {
|
|
453
|
+
private let onSample: (Double, Double) -> Void
|
|
454
|
+
private let onCalibrationOverride: () -> Void
|
|
455
|
+
|
|
456
|
+
init(
|
|
457
|
+
onSample: @escaping (Double, Double) -> Void,
|
|
458
|
+
onCalibrationOverride: @escaping () -> Void
|
|
459
|
+
) {
|
|
460
|
+
self.onSample = onSample
|
|
461
|
+
self.onCalibrationOverride = onCalibrationOverride
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
|
|
465
|
+
guard newHeading.headingAccuracy >= 0 else { return }
|
|
466
|
+
onSample(newHeading.magneticHeading, newHeading.headingAccuracy)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
func locationManagerShouldDisplayHeadingCalibration(_ manager: CLLocationManager) -> Bool {
|
|
470
|
+
onCalibrationOverride()
|
|
471
|
+
return false
|
|
472
|
+
}
|
|
473
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useCompass = useCompass;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _native = require("./native");
|
|
9
|
+
var _multiplex = require("./multiplex");
|
|
10
|
+
/**
|
|
11
|
+
* Ergonomic React wrapper for the NitroCompass surface. Handles
|
|
12
|
+
* subscription lifecycle, callback registration, and the live-tuneable
|
|
13
|
+
* knobs. Multiple instances mounted at once safely share the same
|
|
14
|
+
* underlying native subscription via the multi-listener primitives in
|
|
15
|
+
* `./multiplex`.
|
|
16
|
+
*/
|
|
17
|
+
function useCompass(options = {}) {
|
|
18
|
+
const {
|
|
19
|
+
filterDegrees = 1,
|
|
20
|
+
declination = 0,
|
|
21
|
+
pauseOnBackground = true,
|
|
22
|
+
enabled = true
|
|
23
|
+
} = options;
|
|
24
|
+
const [reading, setReading] = (0, _react.useState)(null);
|
|
25
|
+
const [quality, setQuality] = (0, _react.useState)(null);
|
|
26
|
+
const [interfering, setInterfering] = (0, _react.useState)(false);
|
|
27
|
+
const [hasCompass] = (0, _react.useState)(() => _native.NitroCompass.hasCompass());
|
|
28
|
+
const [diagnostics] = (0, _react.useState)(() => _native.NitroCompass.getDiagnostics());
|
|
29
|
+
|
|
30
|
+
// Tracked via ref so the heading-subscription effect can re-apply
|
|
31
|
+
// the user's filter after a stop/start cycle without restarting on
|
|
32
|
+
// every filterDegrees change.
|
|
33
|
+
const filterRef = (0, _react.useRef)(filterDegrees);
|
|
34
|
+
filterRef.current = filterDegrees;
|
|
35
|
+
(0, _react.useEffect)(() => {
|
|
36
|
+
_native.NitroCompass.setFilter(filterDegrees);
|
|
37
|
+
}, [filterDegrees]);
|
|
38
|
+
(0, _react.useEffect)(() => {
|
|
39
|
+
_native.NitroCompass.setDeclination(declination);
|
|
40
|
+
}, [declination]);
|
|
41
|
+
(0, _react.useEffect)(() => {
|
|
42
|
+
_native.NitroCompass.setPauseOnBackground(pauseOnBackground);
|
|
43
|
+
}, [pauseOnBackground]);
|
|
44
|
+
(0, _react.useEffect)(() => {
|
|
45
|
+
if (!hasCompass) return;
|
|
46
|
+
return (0, _multiplex.addCalibrationListener)(setQuality);
|
|
47
|
+
}, [hasCompass]);
|
|
48
|
+
(0, _react.useEffect)(() => {
|
|
49
|
+
if (!hasCompass) return;
|
|
50
|
+
return (0, _multiplex.addInterferenceListener)(setInterfering);
|
|
51
|
+
}, [hasCompass]);
|
|
52
|
+
(0, _react.useEffect)(() => {
|
|
53
|
+
if (!hasCompass || !enabled) return;
|
|
54
|
+
const off = (0, _multiplex.addHeadingListener)(setReading);
|
|
55
|
+
// Multiplex starts the sensor with a default filter; re-apply the
|
|
56
|
+
// current option after subscribing.
|
|
57
|
+
_native.NitroCompass.setFilter(filterRef.current);
|
|
58
|
+
return off;
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, [hasCompass, enabled]);
|
|
61
|
+
return {
|
|
62
|
+
reading,
|
|
63
|
+
quality,
|
|
64
|
+
interfering,
|
|
65
|
+
hasCompass,
|
|
66
|
+
diagnostics
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_react","require","_native","_multiplex","useCompass","options","filterDegrees","declination","pauseOnBackground","enabled","reading","setReading","useState","quality","setQuality","interfering","setInterfering","hasCompass","NitroCompass","diagnostics","getDiagnostics","filterRef","useRef","current","useEffect","setFilter","setDeclination","setPauseOnBackground","addCalibrationListener","addInterferenceListener","off","addHeadingListener"],"sourceRoot":"../../src","sources":["hook.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAMA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,UAAA,GAAAF,OAAA;AA+CA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASG,UAAUA,CACxBC,OAA0B,GAAG,CAAC,CAAC,EACb;EAClB,MAAM;IACJC,aAAa,GAAG,CAAC;IACjBC,WAAW,GAAG,CAAC;IACfC,iBAAiB,GAAG,IAAI;IACxBC,OAAO,GAAG;EACZ,CAAC,GAAGJ,OAAO;EAEX,MAAM,CAACK,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAC,eAAQ,EAAuB,IAAI,CAAC;EAClE,MAAM,CAACC,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAF,eAAQ,EAAyB,IAAI,CAAC;EACpE,MAAM,CAACG,WAAW,EAAEC,cAAc,CAAC,GAAG,IAAAJ,eAAQ,EAAC,KAAK,CAAC;EAErD,MAAM,CAACK,UAAU,CAAC,GAAG,IAAAL,eAAQ,EAAC,MAAMM,oBAAY,CAACD,UAAU,CAAC,CAAC,CAAC;EAC9D,MAAM,CAACE,WAAW,CAAC,GAAG,IAAAP,eAAQ,EAAC,MAAMM,oBAAY,CAACE,cAAc,CAAC,CAAC,CAAC;;EAEnE;EACA;EACA;EACA,MAAMC,SAAS,GAAG,IAAAC,aAAM,EAAChB,aAAa,CAAC;EACvCe,SAAS,CAACE,OAAO,GAAGjB,aAAa;EAEjC,IAAAkB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACO,SAAS,CAACnB,aAAa,CAAC;EACvC,CAAC,EAAE,CAACA,aAAa,CAAC,CAAC;EAEnB,IAAAkB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACQ,cAAc,CAACnB,WAAW,CAAC;EAC1C,CAAC,EAAE,CAACA,WAAW,CAAC,CAAC;EAEjB,IAAAiB,gBAAS,EAAC,MAAM;IACdN,oBAAY,CAACS,oBAAoB,CAACnB,iBAAiB,CAAC;EACtD,CAAC,EAAE,CAACA,iBAAiB,CAAC,CAAC;EAEvB,IAAAgB,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,EAAE;IACjB,OAAO,IAAAW,iCAAsB,EAACd,UAAU,CAAC;EAC3C,CAAC,EAAE,CAACG,UAAU,CAAC,CAAC;EAEhB,IAAAO,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,EAAE;IACjB,OAAO,IAAAY,kCAAuB,EAACb,cAAc,CAAC;EAChD,CAAC,EAAE,CAACC,UAAU,CAAC,CAAC;EAEhB,IAAAO,gBAAS,EAAC,MAAM;IACd,IAAI,CAACP,UAAU,IAAI,CAACR,OAAO,EAAE;IAC7B,MAAMqB,GAAG,GAAG,IAAAC,6BAAkB,EAACpB,UAAU,CAAC;IAC1C;IACA;IACAO,oBAAY,CAACO,SAAS,CAACJ,SAAS,CAACE,OAAO,CAAC;IACzC,OAAOO,GAAG;IACV;EACF,CAAC,EAAE,CAACb,UAAU,EAAER,OAAO,CAAC,CAAC;EAEzB,OAAO;IAAEC,OAAO;IAAEG,OAAO;IAAEE,WAAW;IAAEE,UAAU;IAAEE;EAAY,CAAC;AACnE","ignoreList":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "NitroCompass", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _native.NitroCompass;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "addCalibrationListener", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _multiplex.addCalibrationListener;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "addHeadingListener", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _multiplex.addHeadingListener;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "addInterferenceListener", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _multiplex.addInterferenceListener;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "useCompass", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return _hook.useCompass;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
var _native = require("./native");
|
|
37
|
+
var _multiplex = require("./multiplex");
|
|
38
|
+
var _hook = require("./hook");
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_native","require","_multiplex","_hook"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,IAAAA,OAAA,GAAAC,OAAA;AAWA,IAAAC,UAAA,GAAAD,OAAA;AAMA,IAAAE,KAAA,GAAAF,OAAA","ignoreList":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.addCalibrationListener = addCalibrationListener;
|
|
7
|
+
exports.addHeadingListener = addHeadingListener;
|
|
8
|
+
exports.addInterferenceListener = addInterferenceListener;
|
|
9
|
+
var _native = require("./native");
|
|
10
|
+
/**
|
|
11
|
+
* JS-side fan-out so multiple consumers can subscribe to the same
|
|
12
|
+
* compass stream without clobbering each other. The native API is
|
|
13
|
+
* single-callback by design (start, setOnCalibrationNeeded,
|
|
14
|
+
* setOnInterferenceDetected each own one slot); these helpers wrap
|
|
15
|
+
* that into multi-listener primitives with reference-counted
|
|
16
|
+
* lifecycle.
|
|
17
|
+
*
|
|
18
|
+
* Mixing direct `NitroCompass.start()` / `setOnCalibrationNeeded()` /
|
|
19
|
+
* `setOnInterferenceDetected()` calls with these helpers will
|
|
20
|
+
* clobber the multiplex's internal callback slot — pick one path.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const headingListeners = new Set();
|
|
24
|
+
const calibrationListeners = new Set();
|
|
25
|
+
const interferenceListeners = new Set();
|
|
26
|
+
let calibrationRegistered = false;
|
|
27
|
+
let interferenceRegistered = false;
|
|
28
|
+
const DEFAULT_FILTER_DEG = 1;
|
|
29
|
+
function dispatchHeading(sample) {
|
|
30
|
+
for (const cb of Array.from(headingListeners)) {
|
|
31
|
+
try {
|
|
32
|
+
cb(sample);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error('[NitroCompass] heading listener threw:', e);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function dispatchCalibration(quality) {
|
|
39
|
+
for (const cb of Array.from(calibrationListeners)) {
|
|
40
|
+
try {
|
|
41
|
+
cb(quality);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error('[NitroCompass] calibration listener threw:', e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function dispatchInterference(detected) {
|
|
48
|
+
for (const cb of Array.from(interferenceListeners)) {
|
|
49
|
+
try {
|
|
50
|
+
cb(detected);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('[NitroCompass] interference listener threw:', e);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to heading samples. The first listener implicitly calls
|
|
59
|
+
* `NitroCompass.start()`; the last `unsubscribe()` calls
|
|
60
|
+
* `NitroCompass.stop()`. Returns the unsubscribe function.
|
|
61
|
+
*
|
|
62
|
+
* Filter, declination, and pauseOnBackground remain global state on
|
|
63
|
+
* `NitroCompass` and are shared across all listeners — call
|
|
64
|
+
* `NitroCompass.setFilter()` etc. directly to tune them.
|
|
65
|
+
*/
|
|
66
|
+
function addHeadingListener(cb) {
|
|
67
|
+
const wasEmpty = headingListeners.size === 0;
|
|
68
|
+
headingListeners.add(cb);
|
|
69
|
+
if (wasEmpty) {
|
|
70
|
+
_native.NitroCompass.start(DEFAULT_FILTER_DEG, dispatchHeading);
|
|
71
|
+
}
|
|
72
|
+
return () => {
|
|
73
|
+
if (!headingListeners.delete(cb)) return;
|
|
74
|
+
if (headingListeners.size === 0) {
|
|
75
|
+
_native.NitroCompass.stop();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Subscribe to calibration-bucket transitions. Only fires while a
|
|
82
|
+
* heading subscription is active. Returns the unsubscribe function.
|
|
83
|
+
*/
|
|
84
|
+
function addCalibrationListener(cb) {
|
|
85
|
+
if (!calibrationRegistered) {
|
|
86
|
+
_native.NitroCompass.setOnCalibrationNeeded(dispatchCalibration);
|
|
87
|
+
calibrationRegistered = true;
|
|
88
|
+
}
|
|
89
|
+
calibrationListeners.add(cb);
|
|
90
|
+
return () => {
|
|
91
|
+
calibrationListeners.delete(cb);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Subscribe to magnetic-interference transitions. Only fires while a
|
|
97
|
+
* heading subscription is active. Returns the unsubscribe function.
|
|
98
|
+
*/
|
|
99
|
+
function addInterferenceListener(cb) {
|
|
100
|
+
if (!interferenceRegistered) {
|
|
101
|
+
_native.NitroCompass.setOnInterferenceDetected(dispatchInterference);
|
|
102
|
+
interferenceRegistered = true;
|
|
103
|
+
}
|
|
104
|
+
interferenceListeners.add(cb);
|
|
105
|
+
return () => {
|
|
106
|
+
interferenceListeners.delete(cb);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=multiplex.js.map
|