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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Omar Sukarieh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NitroCompass"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 }
|
|
14
|
+
s.source = { :git => "https://github.com/omarsdev/react-native-nitro-compass.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
# Implementation (Swift)
|
|
18
|
+
"ios/**/*.{swift}",
|
|
19
|
+
# Autolinking/Registration (Objective-C++)
|
|
20
|
+
"ios/**/*.{m,mm}",
|
|
21
|
+
# Implementation (C++ objects)
|
|
22
|
+
"cpp/**/*.{hpp,cpp}",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
load 'nitrogen/generated/ios/NitroCompass+autolinking.rb'
|
|
26
|
+
add_nitrogen_files(s)
|
|
27
|
+
|
|
28
|
+
s.dependency 'React-jsi'
|
|
29
|
+
s.dependency 'React-callinvoker'
|
|
30
|
+
install_modules_dependencies(s)
|
|
31
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# react-native-nitro-compass
|
|
2
|
+
|
|
3
|
+
Fast, accurate compass heading for React Native, powered by [Nitro Modules](https://github.com/mrousavy/nitro).
|
|
4
|
+
|
|
5
|
+
- **Android**: `Sensor.TYPE_ROTATION_VECTOR` (gyro + accel + magnetometer sensor fusion), with `TYPE_GEOMAGNETIC_ROTATION_VECTOR` fallback for gyroless devices. Sensor delivery on a dedicated `HandlerThread` — never blocks the UI thread. Heading accuracy taken from `event.values[4]` of the rotation vector.
|
|
6
|
+
- **iOS**: `CLLocationManager` heading via `CLHeading.magneticHeading`. Apple's stack already handles sensor fusion natively.
|
|
7
|
+
- **JS API**: type-safe Nitro callbacks — no `NativeEventEmitter`, no string event names.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Most React Native compass libraries use the legacy `accelerometer + magnetometer + getRotationMatrix` Android approach, which is laggy, noisy, and requires a figure-8 calibration on every session. This library uses Android's modern fused rotation-vector sensor (recommended by Google since 2013), giving you stable headings without calibration on virtually any modern device.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- React Native 0.76.0 or higher
|
|
16
|
+
- Node 18.0.0 or higher
|
|
17
|
+
- `react-native-nitro-modules` peer dependency
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install react-native-nitro-compass react-native-nitro-modules
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
iOS:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cd ios && pod install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { NitroCompass } from 'react-native-nitro-compass'
|
|
35
|
+
|
|
36
|
+
if (NitroCompass.hasCompass()) {
|
|
37
|
+
NitroCompass.start(1, ({ heading, accuracy }) => {
|
|
38
|
+
console.log(`heading: ${heading.toFixed(1)}°, accuracy: ±${accuracy}°`)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// later…
|
|
43
|
+
NitroCompass.stop()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### API
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
NitroCompass.start(filterDegrees: number, onHeading: (sample: CompassSample) => void): void
|
|
50
|
+
NitroCompass.stop(): void
|
|
51
|
+
NitroCompass.isStarted(): boolean
|
|
52
|
+
NitroCompass.hasCompass(): boolean
|
|
53
|
+
|
|
54
|
+
NitroCompass.setFilter(degrees: number): void
|
|
55
|
+
NitroCompass.getCurrentHeading(): CompassSample | undefined
|
|
56
|
+
NitroCompass.getDiagnostics(): SensorDiagnostics | undefined
|
|
57
|
+
NitroCompass.setDeclination(degrees: number): void
|
|
58
|
+
NitroCompass.setOnCalibrationNeeded(onChange: (quality: AccuracyQuality) => void): void
|
|
59
|
+
NitroCompass.setOnInterferenceDetected(onChange: (interferenceDetected: boolean) => void): void
|
|
60
|
+
NitroCompass.setPauseOnBackground(enabled: boolean): void
|
|
61
|
+
|
|
62
|
+
interface CompassSample {
|
|
63
|
+
heading: number // degrees, [0, 360); magnetic by default, true-north if setDeclination was called
|
|
64
|
+
accuracy: number // degrees, smaller is better; -1 if unknown
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type AccuracyQuality = 'high' | 'medium' | 'low' | 'unreliable'
|
|
68
|
+
type SensorKind = 'rotationVector' | 'geomagneticRotationVector' | 'coreLocation'
|
|
69
|
+
interface SensorDiagnostics { sensor: SensorKind }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `filterDegrees` — minimum change between successive samples before the next one is delivered. Pass `0` for "every event"; typical UI values are `1`–`3`. Use `setFilter()` to change live without tearing down the subscription.
|
|
73
|
+
- `start()` is idempotent in the destructive sense — calling it while already started silently replaces the previous subscription with the new callback. `stop()` is idempotent and safe from inside the `onHeading` callback.
|
|
74
|
+
- `getDiagnostics()` reports which sensor would produce headings on this device — useful for explaining quality differences (e.g. a phone falling back to `geomagneticRotationVector` will be more susceptible to magnetic interference than one using `rotationVector`). Safe to call before `start()`.
|
|
75
|
+
- `accuracy` is a numeric uncertainty (degrees). On iOS it comes from `CLHeading.headingAccuracy` directly. On Android it comes from `event.values[4]` of the rotation-vector sensor; if the sensor stack does not publish that (rare), the module falls back to a coarse degree estimate from `SensorManager.SENSOR_STATUS_*` (`HIGH→5°`, `MEDIUM→15°`, `LOW→30°`).
|
|
76
|
+
- `getCurrentHeading()` returns the most recently emitted sample (with declination already applied), or `undefined` if not started yet or no sample has arrived.
|
|
77
|
+
|
|
78
|
+
### Calibration
|
|
79
|
+
|
|
80
|
+
`setOnCalibrationNeeded(cb)` registers a callback fired whenever the calibration bucket transitions. Buckets are derived from numeric accuracy on both platforms using the same thresholds, so values agree across iOS and Android: `<5°` → `'high'`, `<15°` → `'medium'`, `<30°` → `'low'`, otherwise `'unreliable'`. On iOS, the system's "wave the device in a figure-8" prompt is suppressed and reported to your callback as `'unreliable'` — show your own UI when you receive that bucket.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
NitroCompass.setOnCalibrationNeeded((q) => {
|
|
84
|
+
if (q === 'unreliable') showCalibrationToast()
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Magnetic interference
|
|
89
|
+
|
|
90
|
+
`setOnInterferenceDetected(cb)` fires `true` when the raw magnetic field magnitude leaves the normal Earth band (~20–70 µT) and `false` when it returns. Typical sources are laptops, monitors, car engines, and large steel structures — these can skew heading by tens of degrees while the calibration bucket still reads `'medium'` or better, so this is complementary to the calibration callback rather than a replacement.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
NitroCompass.setOnInterferenceDetected((interfering) => {
|
|
94
|
+
if (interfering) showInterferenceWarning()
|
|
95
|
+
else hideInterferenceWarning()
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Android uses `Sensor.TYPE_MAGNETIC_FIELD` at ~5 Hz. iOS uses `CMDeviceMotion.magneticField` (calibrated, with the device's own hard-iron bias subtracted in real time) — note that no transitions are reported on iOS until CoreMotion's bias estimate converges, typically a second or two of normal device movement. Only triggered while `start()` is active; no debounce, so brief excursions still fire.
|
|
100
|
+
|
|
101
|
+
### Magnetic vs true north
|
|
102
|
+
|
|
103
|
+
Headings are **magnetic** by default. You can either apply declination in JS, or let the native side do it once via `setDeclination(deg)` so every emitted sample (and `getCurrentHeading()`) is true-north.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import geomagnetism from 'geomagnetism'
|
|
107
|
+
|
|
108
|
+
const declination = geomagnetism.model().point([lat, lon]).decl
|
|
109
|
+
|
|
110
|
+
// Option A — JS-side
|
|
111
|
+
const trueHeading = (heading + declination + 360) % 360
|
|
112
|
+
|
|
113
|
+
// Option B — native-side (subsequent samples are true-north)
|
|
114
|
+
NitroCompass.setDeclination(declination)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pass `0` to revert to magnetic. Declination survives `stop()`/`start()` cycles.
|
|
118
|
+
|
|
119
|
+
### Background pause
|
|
120
|
+
|
|
121
|
+
By default the underlying sensor / location-manager subscription is silently paused while the app is backgrounded and resumed when it returns to the foreground; the JS callback and any declination set via `setDeclination` are preserved across the pause. To opt out (e.g. for a fitness tracker that needs heading while screen-off):
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
NitroCompass.setPauseOnBackground(false)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `useCompass()` hook
|
|
128
|
+
|
|
129
|
+
For React consumers, the bundled hook wraps the entire surface — subscription lifecycle, calibration/interference callbacks, and the live-tuneable knobs — into one ergonomic call. Multiple `useCompass()` mounts safely share the same underlying native subscription via JS-side fan-out, so two screens can both consume heading without clobbering each other.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { useCompass } from 'react-native-nitro-compass'
|
|
133
|
+
|
|
134
|
+
function CompassView() {
|
|
135
|
+
const { reading, quality, interfering, hasCompass } = useCompass({
|
|
136
|
+
filterDegrees: 1,
|
|
137
|
+
declination: 0,
|
|
138
|
+
pauseOnBackground: true,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (!hasCompass) return <Text>No compass on this device.</Text>
|
|
142
|
+
if (!reading) return <Text>Acquiring heading…</Text>
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<View>
|
|
146
|
+
<Text>{reading.heading.toFixed(0)}° (±{reading.accuracy.toFixed(0)}°)</Text>
|
|
147
|
+
{quality === 'unreliable' && <Text>Calibration needed</Text>}
|
|
148
|
+
{interfering && <Text>Magnetic interference</Text>}
|
|
149
|
+
</View>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`filterDegrees`, `declination`, and `pauseOnBackground` are global state on `NitroCompass` — multiple hooks setting them are last-write-wins. Pass `enabled: false` to pause a hook's heading subscription without unmounting (calibration / interference observation continues).
|
|
155
|
+
|
|
156
|
+
For non-React state managers, lower-level `addHeadingListener(cb): () => void`, `addCalibrationListener(cb): () => void`, and `addInterferenceListener(cb): () => void` are also exported. They are reference-counted: the first heading listener calls `start()`, the last unsubscribe calls `stop()`. Mixing these helpers with direct `NitroCompass.start()` / `setOnCalibrationNeeded()` / `setOnInterferenceDetected()` will clobber the multiplex's internal callback slot — pick one path.
|
|
157
|
+
|
|
158
|
+
## Permissions
|
|
159
|
+
|
|
160
|
+
- **iOS**: requires `NSLocationWhenInUseUsageDescription` in `Info.plist`. `CLLocationManager` only emits headings when location permission is granted.
|
|
161
|
+
- **Android**: no permission required for the rotation-vector sensor.
|
|
162
|
+
|
|
163
|
+
## Example app
|
|
164
|
+
|
|
165
|
+
A bare React Native CLI app under [example/](./example) (RN 0.84.1, New Arch enabled) consumes the library via a local symlink. Use it to test changes on a real device — the iOS Simulator has no compass and the Android emulator's magnetometer is faked.
|
|
166
|
+
|
|
167
|
+
First-time setup:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
cd example
|
|
171
|
+
npm install # symlinks ../ as react-native-nitro-compass
|
|
172
|
+
cd ios && bundle install && bundle exec pod install && cd ..
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Run on a device:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
# Terminal 1 — Metro
|
|
179
|
+
npm start
|
|
180
|
+
|
|
181
|
+
# Terminal 2 — build & launch
|
|
182
|
+
npm run ios -- --device # physical iPhone
|
|
183
|
+
npm run android # physical device or emulator
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
If you change the Nitrogen spec or any native source, regenerate and rebuild:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
# from the repo root
|
|
190
|
+
npm run codegen
|
|
191
|
+
# then in example/
|
|
192
|
+
cd ios && bundle exec pod install && cd .. # iOS only
|
|
193
|
+
npm run ios # or npm run android
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The example imports `NitroCompass` directly from the workspace `src/` (via Metro `watchFolders`), so editing TypeScript only requires a Metro reload.
|
|
197
|
+
|
|
198
|
+
## Acknowledgments
|
|
199
|
+
|
|
200
|
+
The Android rotation-vector pattern (sensor fusion, surface-rotation remapping, `getOrientation` extraction) is adapted from the MIT-licensed [Andromeda](https://github.com/kylecorry31/andromeda) sensor library by Kyle Corry, which powers the [Trail Sense](https://github.com/kylecorry31/Trail-Sense) wilderness navigation app.
|
|
201
|
+
|
|
202
|
+
Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
project(NitroCompass)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set (PACKAGE_NAME NitroCompass)
|
|
5
|
+
set (CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set (CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Enable Raw Props parsing in react-native (for Nitro Views)
|
|
9
|
+
add_compile_options(-DRN_SERIALIZABLE_STATE=1)
|
|
10
|
+
|
|
11
|
+
# Define C++ library and add all sources
|
|
12
|
+
add_library(${PACKAGE_NAME} SHARED
|
|
13
|
+
src/main/cpp/cpp-adapter.cpp
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Add Nitrogen specs :)
|
|
17
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroCompass+autolinking.cmake)
|
|
18
|
+
|
|
19
|
+
# Set up local includes
|
|
20
|
+
include_directories(
|
|
21
|
+
"src/main/cpp"
|
|
22
|
+
"../cpp"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
find_library(LOG_LIB log)
|
|
26
|
+
|
|
27
|
+
# Link all libraries together
|
|
28
|
+
target_link_libraries(
|
|
29
|
+
${PACKAGE_NAME}
|
|
30
|
+
${LOG_LIB}
|
|
31
|
+
android # <-- Android core
|
|
32
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:8.8.0"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def reactNativeArchitectures() {
|
|
13
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
14
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def isNewArchitectureEnabled() {
|
|
18
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
apply plugin: "com.android.library"
|
|
22
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
23
|
+
apply from: '../nitrogen/generated/android/NitroCompass+autolinking.gradle'
|
|
24
|
+
apply from: "./fix-prefab.gradle"
|
|
25
|
+
|
|
26
|
+
if (isNewArchitectureEnabled()) {
|
|
27
|
+
apply plugin: "com.facebook.react"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def getExtOrDefault(name) {
|
|
31
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroCompass_" + name]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def getExtOrIntegerDefault(name) {
|
|
35
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroCompass_" + name]).toInteger()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
android {
|
|
39
|
+
namespace "com.nitrocompass"
|
|
40
|
+
|
|
41
|
+
ndkVersion getExtOrDefault("ndkVersion")
|
|
42
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
43
|
+
|
|
44
|
+
defaultConfig {
|
|
45
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
46
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
47
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
48
|
+
|
|
49
|
+
externalNativeBuild {
|
|
50
|
+
cmake {
|
|
51
|
+
cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
|
|
52
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
53
|
+
abiFilters (*reactNativeArchitectures())
|
|
54
|
+
|
|
55
|
+
buildTypes {
|
|
56
|
+
debug {
|
|
57
|
+
cppFlags "-O1 -g"
|
|
58
|
+
}
|
|
59
|
+
release {
|
|
60
|
+
cppFlags "-O2"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
externalNativeBuild {
|
|
68
|
+
cmake {
|
|
69
|
+
path "CMakeLists.txt"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
packagingOptions {
|
|
74
|
+
excludes = [
|
|
75
|
+
"META-INF",
|
|
76
|
+
"META-INF/**",
|
|
77
|
+
"**/libc++_shared.so",
|
|
78
|
+
"**/libfbjni.so",
|
|
79
|
+
"**/libjsi.so",
|
|
80
|
+
"**/libfolly_json.so",
|
|
81
|
+
"**/libfolly_runtime.so",
|
|
82
|
+
"**/libglog.so",
|
|
83
|
+
"**/libhermes.so",
|
|
84
|
+
"**/libhermes-executor-debug.so",
|
|
85
|
+
"**/libhermes_executor.so",
|
|
86
|
+
"**/libreactnative.so",
|
|
87
|
+
"**/libreactnativejni.so",
|
|
88
|
+
"**/libturbomodulejsijni.so",
|
|
89
|
+
"**/libreact_nativemodule_core.so",
|
|
90
|
+
"**/libjscexecutor.so"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
buildFeatures {
|
|
95
|
+
buildConfig true
|
|
96
|
+
prefab true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
buildTypes {
|
|
100
|
+
release {
|
|
101
|
+
minifyEnabled false
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
lintOptions {
|
|
106
|
+
disable "GradleCompatible"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
compileOptions {
|
|
110
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
111
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
sourceSets {
|
|
115
|
+
main {
|
|
116
|
+
if (isNewArchitectureEnabled()) {
|
|
117
|
+
java.srcDirs += [
|
|
118
|
+
// React Codegen files
|
|
119
|
+
"${project.buildDir}/generated/source/codegen/java"
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
repositories {
|
|
127
|
+
mavenCentral()
|
|
128
|
+
google()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
dependencies {
|
|
133
|
+
// For < 0.71, this will be from the local maven repo
|
|
134
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
135
|
+
//noinspection GradleDynamicVersion
|
|
136
|
+
implementation "com.facebook.react:react-native:+"
|
|
137
|
+
|
|
138
|
+
// Add a dependency on NitroModules
|
|
139
|
+
implementation project(":react-native-nitro-modules")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (isNewArchitectureEnabled()) {
|
|
143
|
+
react {
|
|
144
|
+
jsRootDir = file("../src/")
|
|
145
|
+
libraryName = "NitroCompass"
|
|
146
|
+
codegenJavaPackageName = "com.nitrocompass"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
tasks.configureEach { task ->
|
|
2
|
+
// Make sure that we generate our prefab publication file only after having built the native library
|
|
3
|
+
// so that not a header publication file, but a full configuration publication will be generated, which
|
|
4
|
+
// will include the .so file
|
|
5
|
+
|
|
6
|
+
def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
|
|
7
|
+
def matcher = task.name =~ prefabConfigurePattern
|
|
8
|
+
if (matcher.matches()) {
|
|
9
|
+
def variantName = matcher[0][1]
|
|
10
|
+
task.outputs.upToDateWhen { false }
|
|
11
|
+
task.dependsOn("externalNativeBuild${variantName}")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
afterEvaluate {
|
|
16
|
+
def abis = reactNativeArchitectures()
|
|
17
|
+
rootProject.allprojects.each { proj ->
|
|
18
|
+
if (proj === rootProject) return
|
|
19
|
+
|
|
20
|
+
def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
|
|
21
|
+
config.dependencies.any { dep ->
|
|
22
|
+
dep.group == project.group && dep.name == project.name
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!dependsOnThisLib && proj != project) return
|
|
26
|
+
|
|
27
|
+
if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
|
|
32
|
+
// Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
|
|
33
|
+
// generate a libnameConfig.cmake file that will contain our native library (.so).
|
|
34
|
+
// See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
|
|
35
|
+
variants.all { variant ->
|
|
36
|
+
def variantName = variant.name
|
|
37
|
+
abis.each { abi ->
|
|
38
|
+
def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
|
|
39
|
+
if (!searchDir.exists()) return
|
|
40
|
+
def matches = []
|
|
41
|
+
searchDir.eachDir { randomDir ->
|
|
42
|
+
def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
|
|
43
|
+
if (prefabFile.exists()) matches << prefabFile
|
|
44
|
+
}
|
|
45
|
+
matches.each { prefabConfig ->
|
|
46
|
+
prefabConfig.setLastModified(System.currentTimeMillis())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|