@mentra/crust 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/android/build.gradle +146 -0
- package/android/src/internal/AndroidManifest.xml +9 -0
- package/android/src/internal/java/com/mentra/crust/receivers/CaptionsTesterIncidentReceiver.kt +46 -0
- package/android/src/main/AndroidManifest.xml +19 -0
- package/android/src/main/java/com/mentra/crust/CrustModule.kt +1042 -0
- package/android/src/main/java/com/mentra/crust/CrustView.kt +30 -0
- package/android/src/main/java/com/mentra/crust/heading/HeadingManager.kt +150 -0
- package/android/src/main/java/com/mentra/crust/jsc/JSCDispatcher.kt +189 -0
- package/android/src/main/java/com/mentra/crust/jsc/JSCPolyfillBridge.kt +287 -0
- package/android/src/main/java/com/mentra/crust/jsc/JSCRuntime.kt +593 -0
- package/android/src/main/java/com/mentra/crust/navigation/NavigationManager.kt +1445 -0
- package/android/src/main/java/com/mentra/crust/services/NotificationListener.kt +319 -0
- package/android/src/main/java/com/mentra/crust/utils/ImageProcessor.java +452 -0
- package/android/src/main/java/com/mentra/crust/utils/VideoStabilizer.kt +556 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/app.plugin.js +3 -0
- package/build/Crust.types.d.ts +148 -0
- package/build/Crust.types.d.ts.map +1 -0
- package/build/Crust.types.js +2 -0
- package/build/Crust.types.js.map +1 -0
- package/build/CrustModule.d.ts +182 -0
- package/build/CrustModule.d.ts.map +1 -0
- package/build/CrustModule.js +4 -0
- package/build/CrustModule.js.map +1 -0
- package/build/CrustModule.web.d.ts +34 -0
- package/build/CrustModule.web.d.ts.map +1 -0
- package/build/CrustModule.web.js +63 -0
- package/build/CrustModule.web.js.map +1 -0
- package/build/CrustView.d.ts +4 -0
- package/build/CrustView.d.ts.map +1 -0
- package/build/CrustView.js +7 -0
- package/build/CrustView.js.map +1 -0
- package/build/CrustView.web.d.ts +4 -0
- package/build/CrustView.web.d.ts.map +1 -0
- package/build/CrustView.web.js +7 -0
- package/build/CrustView.web.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +6 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Crust.podspec +65 -0
- package/ios/CrustModule.swift +781 -0
- package/ios/CrustView.swift +38 -0
- package/ios/Resources/startup.js +814 -0
- package/ios/Source/JSCDispatcher.swift +226 -0
- package/ios/Source/JSCPolyfillBridge.swift +378 -0
- package/ios/Source/JSCRuntime.swift +673 -0
- package/ios/Source/utils/ImageProcessor.swift +392 -0
- package/ios/Source/utils/SystemGestures.swift +53 -0
- package/ios/Source/utils/VideoStabilizer.swift +374 -0
- package/ios/heading/HeadingManager.swift +74 -0
- package/ios/navigation/NavPayloads.swift +62 -0
- package/ios/navigation/NavigationManager.swift +720 -0
- package/package.json +69 -0
- package/plugin/build/index.d.ts +19 -0
- package/plugin/build/index.js +23 -0
- package/plugin/build/withAndroid.d.ts +2 -0
- package/plugin/build/withAndroid.js +78 -0
- package/src/Crust.types.ts +157 -0
- package/src/CrustModule.ts +191 -0
- package/src/CrustModule.web.ts +66 -0
- package/src/CrustView.tsx +10 -0
- package/src/CrustView.web.tsx +11 -0
- package/src/index.ts +5 -0
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mentra/crust",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Mentra Native Module",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"app.plugin": "app.plugin.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "expo-module build && expo-module build plugin",
|
|
10
|
+
"clean": "expo-module clean",
|
|
11
|
+
"lint": "expo-module lint",
|
|
12
|
+
"test": "expo-module test",
|
|
13
|
+
"prepare": "expo-module prepare && EXPO_NONINTERACTIVE=1 expo-module build plugin",
|
|
14
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
15
|
+
"expo-module": "expo-module",
|
|
16
|
+
"open:ios": "xed example/ios",
|
|
17
|
+
"open:android": "open -a \"Android Studio\" example/android",
|
|
18
|
+
"build:plugin": "expo-module build plugin"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"react-native",
|
|
22
|
+
"expo",
|
|
23
|
+
"crust",
|
|
24
|
+
"Crust"
|
|
25
|
+
],
|
|
26
|
+
"repository": "https://github.com/fossephate/crust",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/fossephate/crust/issues"
|
|
29
|
+
},
|
|
30
|
+
"author": "fossephate <6175367+fossephate@users.noreply.github.com> (https://github.com/fossephate)",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"homepage": "https://github.com/fossephate/crust#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@mentra/jspolyfill": "^0.1.0-beta.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@expo/config-plugins": ">=8.0.0",
|
|
38
|
+
"expo-module-scripts": "^55.0.2",
|
|
39
|
+
"typescript": "~5.9.2"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@types/react": "*",
|
|
43
|
+
"expo": "*",
|
|
44
|
+
"react": "*",
|
|
45
|
+
"react-native": "*"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"android",
|
|
52
|
+
"!android/.gradle",
|
|
53
|
+
"!android/.gradle/**",
|
|
54
|
+
"!android/build",
|
|
55
|
+
"!android/build/**",
|
|
56
|
+
"ios",
|
|
57
|
+
"!ios/build",
|
|
58
|
+
"!ios/build/**",
|
|
59
|
+
"!ios/Pods",
|
|
60
|
+
"!ios/Pods/**",
|
|
61
|
+
"app.plugin.js",
|
|
62
|
+
"build",
|
|
63
|
+
"plugin/build",
|
|
64
|
+
"expo-module.config.json",
|
|
65
|
+
"src",
|
|
66
|
+
"!src/__tests__",
|
|
67
|
+
"README.md"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ConfigPlugin } from "expo/config-plugins";
|
|
2
|
+
/**
|
|
3
|
+
* @mentra/crust config plugin — the module's own Android build contract, so
|
|
4
|
+
* every host that embeds crust (the Mentra app, the example OEM app, real OEM
|
|
5
|
+
* hosts) inherits it by listing "@mentra/crust" in `app.json` plugins instead
|
|
6
|
+
* of hand-mirroring gradle edits:
|
|
7
|
+
*
|
|
8
|
+
* - the authenticated Mapbox Downloads Maven repo (crust bundles the Mapbox
|
|
9
|
+
* Navigation SDK, whose artifacts exist only in Mapbox's private registry;
|
|
10
|
+
* the MAPBOX_DOWNLOADS_TOKEN env / gradle property authenticates it at
|
|
11
|
+
* dependency-resolution time — build-time only, never in the APK)
|
|
12
|
+
* - a global protobuf-javalite exclusion (the bluetooth-sdk/crust native stack
|
|
13
|
+
* needs protobuf-java; Mapbox drags protobuf-javalite transitively; shipping
|
|
14
|
+
* both fails the release build with duplicate classes)
|
|
15
|
+
* - core-library desugaring (crust's AAR metadata requires it of embedding
|
|
16
|
+
* apps — the Nav SDK uses newer core libs)
|
|
17
|
+
*/
|
|
18
|
+
declare const withCrust: ConfigPlugin;
|
|
19
|
+
export default withCrust;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const withAndroid_1 = require("./withAndroid");
|
|
4
|
+
/**
|
|
5
|
+
* @mentra/crust config plugin — the module's own Android build contract, so
|
|
6
|
+
* every host that embeds crust (the Mentra app, the example OEM app, real OEM
|
|
7
|
+
* hosts) inherits it by listing "@mentra/crust" in `app.json` plugins instead
|
|
8
|
+
* of hand-mirroring gradle edits:
|
|
9
|
+
*
|
|
10
|
+
* - the authenticated Mapbox Downloads Maven repo (crust bundles the Mapbox
|
|
11
|
+
* Navigation SDK, whose artifacts exist only in Mapbox's private registry;
|
|
12
|
+
* the MAPBOX_DOWNLOADS_TOKEN env / gradle property authenticates it at
|
|
13
|
+
* dependency-resolution time — build-time only, never in the APK)
|
|
14
|
+
* - a global protobuf-javalite exclusion (the bluetooth-sdk/crust native stack
|
|
15
|
+
* needs protobuf-java; Mapbox drags protobuf-javalite transitively; shipping
|
|
16
|
+
* both fails the release build with duplicate classes)
|
|
17
|
+
* - core-library desugaring (crust's AAR metadata requires it of embedding
|
|
18
|
+
* apps — the Nav SDK uses newer core libs)
|
|
19
|
+
*/
|
|
20
|
+
const withCrust = (config) => {
|
|
21
|
+
return (0, withAndroid_1.withCrustAndroidBuildContract)(config);
|
|
22
|
+
};
|
|
23
|
+
exports.default = withCrust;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withCrustAndroidBuildContract = void 0;
|
|
4
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
5
|
+
// Marker comments double as idempotency checks: each injection looks for its
|
|
6
|
+
// OWN unique marker (never a shared substring — a marker one block contains
|
|
7
|
+
// can silently mask another block's check).
|
|
8
|
+
const MAPBOX_REPO_MARKER = "mapbox: navigation sdk downloads repo (injected by @mentra/crust)";
|
|
9
|
+
const MAPBOX_REPO = [
|
|
10
|
+
" maven {",
|
|
11
|
+
` // ${MAPBOX_REPO_MARKER}`,
|
|
12
|
+
" url 'https://api.mapbox.com/downloads/v2/releases/maven'",
|
|
13
|
+
" authentication { basic(BasicAuthentication) }",
|
|
14
|
+
" credentials {",
|
|
15
|
+
" username = 'mapbox'",
|
|
16
|
+
" password = System.getenv('MAPBOX_DOWNLOADS_TOKEN') ?: (findProperty('MAPBOX_DOWNLOADS_TOKEN') ?: '')",
|
|
17
|
+
" }",
|
|
18
|
+
" }",
|
|
19
|
+
].join("\n");
|
|
20
|
+
const PROTOBUF_EXCLUDE = "exclude group: 'com.google.protobuf', module: 'protobuf-javalite'";
|
|
21
|
+
function withCrustProjectGradle(config) {
|
|
22
|
+
return (0, config_plugins_1.withProjectBuildGradle)(config, (cfg) => {
|
|
23
|
+
let gradle = cfg.modResults.contents;
|
|
24
|
+
// Mapbox Downloads repo, inserted at the top of allprojects.repositories.
|
|
25
|
+
if (!gradle.includes(MAPBOX_REPO_MARKER)) {
|
|
26
|
+
const reposMatch = gradle.match(/allprojects\s*\{[\s\S]*?repositories\s*\{/);
|
|
27
|
+
if (reposMatch) {
|
|
28
|
+
const idx = (reposMatch.index ?? 0) + reposMatch[0].length;
|
|
29
|
+
gradle = gradle.slice(0, idx) + "\n" + MAPBOX_REPO + gradle.slice(idx);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
gradle += `\nallprojects {\n repositories {\n${MAPBOX_REPO}\n }\n}\n`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// protobuf-javalite exclusion, appended as its own allprojects block so
|
|
36
|
+
// placement never depends on the shape of existing gradle content (other
|
|
37
|
+
// plugins add maven{} entries that break brace-counting regexes).
|
|
38
|
+
if (!gradle.includes(PROTOBUF_EXCLUDE)) {
|
|
39
|
+
gradle += `
|
|
40
|
+
// Exclude protobuf-javalite in every project to avoid duplicate classes with
|
|
41
|
+
// protobuf-java (injected by @mentra/crust).
|
|
42
|
+
allprojects {
|
|
43
|
+
configurations.all {
|
|
44
|
+
${PROTOBUF_EXCLUDE}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
cfg.modResults.contents = gradle;
|
|
50
|
+
return cfg;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function withCrustAppGradle(config) {
|
|
54
|
+
return (0, config_plugins_1.withAppBuildGradle)(config, (cfg) => {
|
|
55
|
+
let gradle = cfg.modResults.contents;
|
|
56
|
+
if (!gradle.includes("coreLibraryDesugaringEnabled")) {
|
|
57
|
+
gradle = gradle.replace(/(namespace\s+['"][^'"]+['"])/, `$1
|
|
58
|
+
compileOptions {
|
|
59
|
+
coreLibraryDesugaringEnabled true
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
62
|
+
}`);
|
|
63
|
+
}
|
|
64
|
+
if (!gradle.includes("coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs")) {
|
|
65
|
+
gradle = gradle.replace(/(implementation\("com\.facebook\.react:react-android"\))/, `$1
|
|
66
|
+
|
|
67
|
+
// Required by :mentra-crust (Mapbox Navigation SDK uses newer core libs).
|
|
68
|
+
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'`);
|
|
69
|
+
}
|
|
70
|
+
cfg.modResults.contents = gradle;
|
|
71
|
+
return cfg;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const withCrustAndroidBuildContract = (config) => {
|
|
75
|
+
config = withCrustProjectGradle(config);
|
|
76
|
+
return withCrustAppGradle(config);
|
|
77
|
+
};
|
|
78
|
+
exports.withCrustAndroidBuildContract = withCrustAndroidBuildContract;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type {StyleProp, ViewStyle} from "react-native"
|
|
2
|
+
|
|
3
|
+
export type OnLoadEventPayload = {
|
|
4
|
+
url: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type CrustModuleEvents = {
|
|
8
|
+
onChange: (params: ChangeEventPayload) => void
|
|
9
|
+
onNavManeuver: (params: NavManeuverPayload) => void
|
|
10
|
+
onNavRerouting: (params: Record<string, never>) => void
|
|
11
|
+
onNavArrived: (params: Record<string, never>) => void
|
|
12
|
+
onNavError: (params: NavErrorPayload) => void
|
|
13
|
+
onNavLocation: (params: NavLocationPayload) => void
|
|
14
|
+
onNavRoute: (params: NavRoutePayload) => void
|
|
15
|
+
onNavOffRoute: (params: NavOffRoutePayload) => void
|
|
16
|
+
onHeading: (params: HeadingPayload) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type NavOffRoutePayload = {
|
|
20
|
+
/** Approximate perpendicular distance in meters from the route. */
|
|
21
|
+
offRouteDistanceMeters: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type HeadingPayload = {
|
|
25
|
+
/** Compass heading in degrees, 0 = north, 90 = east. */
|
|
26
|
+
degrees: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type NavRoutePayload = {
|
|
30
|
+
points: Array<{lat: number; lng: number}>
|
|
31
|
+
/**
|
|
32
|
+
* Ordered step list along the route, when supplied by the host. Each
|
|
33
|
+
* step is one navigable segment — typically one straight stretch of
|
|
34
|
+
* road ending in a maneuver. Optional for back-compat with older
|
|
35
|
+
* hosts that only ship the polyline. The SDK's pivot module consumes
|
|
36
|
+
* this to enrich pivots with `fromRoad` / `toRoad` metadata.
|
|
37
|
+
*/
|
|
38
|
+
steps?: NavRouteStepPayload[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type NavRouteStepPayload = {
|
|
42
|
+
/** Coordinate where this step begins (== end of the previous step). */
|
|
43
|
+
lat: number
|
|
44
|
+
lng: number
|
|
45
|
+
/**
|
|
46
|
+
* Index into `NavRoutePayload.points[]` where this step starts.
|
|
47
|
+
* Lets consumers correlate step boundaries with polyline geometry.
|
|
48
|
+
*/
|
|
49
|
+
routeIndex: number
|
|
50
|
+
/** Name of the road traversed during this step. Null when the engine has no name. */
|
|
51
|
+
road?: string | null
|
|
52
|
+
/**
|
|
53
|
+
* Categorical maneuver performed at the END of this step (i.e. how
|
|
54
|
+
* the user exits this step). Same vocabulary as `NavManeuverPayload.maneuverType`.
|
|
55
|
+
*/
|
|
56
|
+
maneuver: string
|
|
57
|
+
/** Length of this step in meters. */
|
|
58
|
+
distanceMeters: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type NavLocationPayload = {
|
|
62
|
+
lat: number
|
|
63
|
+
lng: number
|
|
64
|
+
/** Horizontal accuracy in meters, if reported by the platform. */
|
|
65
|
+
accuracy: number | null
|
|
66
|
+
/** Unix ms timestamp of the fix. */
|
|
67
|
+
timestamp: number
|
|
68
|
+
phone_notification: (event: PhoneNotificationEvent) => void
|
|
69
|
+
phone_notification_dismissed: (event: PhoneNotificationDismissedEvent) => void
|
|
70
|
+
captions_tester_incident: (event: CaptionsTesterIncidentEvent) => void
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type ChangeEventPayload = {
|
|
74
|
+
value: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type NavManeuverPayload = {
|
|
78
|
+
/**
|
|
79
|
+
* Categorical type of the upcoming maneuver. One of: STRAIGHT,
|
|
80
|
+
* SLIGHT_LEFT, SLIGHT_RIGHT, TURN_LEFT, TURN_RIGHT, SHARP_LEFT,
|
|
81
|
+
* SHARP_RIGHT, U_TURN, ARRIVE.
|
|
82
|
+
*/
|
|
83
|
+
maneuverType: string
|
|
84
|
+
/** Distance in meters from the user's current position to that maneuver. -1 if unknown. */
|
|
85
|
+
distanceMeters: number
|
|
86
|
+
/** Road the user is currently on, per the Nav SDK. Null if unavailable. */
|
|
87
|
+
fromRoad?: string | null
|
|
88
|
+
/**
|
|
89
|
+
* Legacy "next road" field — historically populated from the same
|
|
90
|
+
* source as `fromRoad` (the current step's road, NOT the road
|
|
91
|
+
* after the upcoming turn). Kept for back-compat. New consumers
|
|
92
|
+
* should read `nextStepRoad` instead.
|
|
93
|
+
*/
|
|
94
|
+
toRoad?: string | null
|
|
95
|
+
/**
|
|
96
|
+
* Road the user will be on AFTER the upcoming maneuver, sourced
|
|
97
|
+
* from the Nav SDK's `remainingSteps[0]`. This is the value to
|
|
98
|
+
* show as the "next street" headline on the maneuver card. Null
|
|
99
|
+
* when the SDK hasn't surfaced a remaining step yet (final leg,
|
|
100
|
+
* pre-first-NavInfo, etc.).
|
|
101
|
+
*/
|
|
102
|
+
nextStepRoad?: string | null
|
|
103
|
+
/** Total remaining distance to final destination, meters. -1 if unknown. */
|
|
104
|
+
distanceToDestinationMeters?: number
|
|
105
|
+
/** Total remaining travel time, seconds. -1 if unknown. */
|
|
106
|
+
timeToDestinationSeconds?: number
|
|
107
|
+
/** Current speed in m/s. Null if unavailable. */
|
|
108
|
+
currentSpeedMps?: number | null
|
|
109
|
+
/** Speed limit on the current road segment in m/s. Null if unknown / not regulated. */
|
|
110
|
+
speedLimitMps?: number | null
|
|
111
|
+
/** Bearing along the route at the user's current position, 0–360. Null if unknown. */
|
|
112
|
+
routeHeadingDeg?: number | null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type NavErrorPayload = {
|
|
116
|
+
message: string
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type InstalledApp = {
|
|
120
|
+
packageName: string
|
|
121
|
+
appName: string
|
|
122
|
+
isBlocked: boolean
|
|
123
|
+
icon: string | null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type PhoneNotificationEvent = {
|
|
127
|
+
notificationId: string
|
|
128
|
+
app: string
|
|
129
|
+
title: string
|
|
130
|
+
content: string
|
|
131
|
+
priority: string
|
|
132
|
+
timestamp: number
|
|
133
|
+
packageName: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type PhoneNotificationDismissedEvent = {
|
|
137
|
+
notificationKey: string
|
|
138
|
+
packageName: string
|
|
139
|
+
notificationId: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export type CaptionsTesterIncidentEvent = {
|
|
143
|
+
action?: string
|
|
144
|
+
timestamp?: number
|
|
145
|
+
failure_code?: string
|
|
146
|
+
failure_message?: string
|
|
147
|
+
test_run_id?: string
|
|
148
|
+
scenario_name?: string
|
|
149
|
+
source?: string
|
|
150
|
+
[key: string]: unknown
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type CrustViewProps = {
|
|
154
|
+
url: string
|
|
155
|
+
onLoad: (event: {nativeEvent: OnLoadEventPayload}) => void
|
|
156
|
+
style?: StyleProp<ViewStyle>
|
|
157
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {NativeModule, requireNativeModule} from "expo"
|
|
2
|
+
|
|
3
|
+
import {CrustModuleEvents, InstalledApp} from "./Crust.types"
|
|
4
|
+
|
|
5
|
+
declare class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
6
|
+
PI: number
|
|
7
|
+
hello(): string
|
|
8
|
+
setValueAsync(value: string): Promise<void>
|
|
9
|
+
nativeHttpRequest(
|
|
10
|
+
method: string,
|
|
11
|
+
url: string,
|
|
12
|
+
headers: Record<string, string>,
|
|
13
|
+
body?: string | null,
|
|
14
|
+
): Promise<{status: number; statusText: string; headers: Record<string, string>; body: string}>
|
|
15
|
+
showAVRoutePicker(tintColor?: string | null): void
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* iOS: configure `preferredScreenEdgesDeferringSystemGestures`. When an
|
|
19
|
+
* edge is deferred, the first swipe across that edge is consumed by the
|
|
20
|
+
* app and the system gesture (Control Center, Notification Center, Home
|
|
21
|
+
* indicator) only fires on a second swipe — i.e. a two-swipe-to-exit UX.
|
|
22
|
+
*
|
|
23
|
+
* Pass `[]` to restore default behavior. Android: no-op (Android has no
|
|
24
|
+
* per-app equivalent; system gestures are configured at the OS level).
|
|
25
|
+
*/
|
|
26
|
+
setDeferredSystemGestures(edges: Array<"top" | "bottom" | "left" | "right" | "all">): Promise<void>
|
|
27
|
+
|
|
28
|
+
// MentraOS Notification Commands
|
|
29
|
+
setNotificationConfig(enabled: boolean, blocklist: string[]): Promise<void>
|
|
30
|
+
getInstalledApps(): Promise<InstalledApp[]>
|
|
31
|
+
getInstalledAppsForNotifications(): Promise<InstalledApp[]>
|
|
32
|
+
hasNotificationListenerPermission(): Promise<boolean>
|
|
33
|
+
openNotificationListenerSettings(): Promise<boolean>
|
|
34
|
+
isBetaBuild(): Promise<boolean>
|
|
35
|
+
// location services commands
|
|
36
|
+
showLocationServicesDialog(): Promise<boolean>
|
|
37
|
+
isLocationServicesEnabled(): Promise<boolean>
|
|
38
|
+
openLocationSettings(): Promise<boolean>
|
|
39
|
+
openAppSettings(): Promise<boolean>
|
|
40
|
+
openBluetoothSettings(): Promise<boolean>
|
|
41
|
+
|
|
42
|
+
// Image Processing Commands
|
|
43
|
+
processGalleryImage(
|
|
44
|
+
inputPath: string,
|
|
45
|
+
outputPath: string,
|
|
46
|
+
options: {
|
|
47
|
+
lensCorrection?: boolean
|
|
48
|
+
colorCorrection?: boolean
|
|
49
|
+
},
|
|
50
|
+
): Promise<{
|
|
51
|
+
success: boolean
|
|
52
|
+
outputPath?: string
|
|
53
|
+
processingTimeMs?: number
|
|
54
|
+
error?: string
|
|
55
|
+
}>
|
|
56
|
+
|
|
57
|
+
mergeHdrBrackets(
|
|
58
|
+
underPath: string,
|
|
59
|
+
normalPath: string,
|
|
60
|
+
overPath: string,
|
|
61
|
+
outputPath: string,
|
|
62
|
+
): Promise<{
|
|
63
|
+
success: boolean
|
|
64
|
+
outputPath?: string
|
|
65
|
+
processingTimeMs?: number
|
|
66
|
+
error?: string
|
|
67
|
+
}>
|
|
68
|
+
|
|
69
|
+
stabilizeVideo(
|
|
70
|
+
inputPath: string,
|
|
71
|
+
imuPath: string,
|
|
72
|
+
outputPath: string,
|
|
73
|
+
): Promise<{
|
|
74
|
+
success: boolean
|
|
75
|
+
outputPath?: string
|
|
76
|
+
processingTimeMs?: number
|
|
77
|
+
error?: string
|
|
78
|
+
}>
|
|
79
|
+
|
|
80
|
+
// Media Library Commands
|
|
81
|
+
saveToGalleryWithDate(
|
|
82
|
+
filePath: string,
|
|
83
|
+
captureTimeMillis?: number,
|
|
84
|
+
displayName?: string,
|
|
85
|
+
): Promise<{
|
|
86
|
+
success: boolean
|
|
87
|
+
uri?: string
|
|
88
|
+
identifier?: string
|
|
89
|
+
existing?: boolean
|
|
90
|
+
error?: string
|
|
91
|
+
}>
|
|
92
|
+
|
|
93
|
+
// Navigation (Android only — iOS stubs return error)
|
|
94
|
+
startNavigation(
|
|
95
|
+
lat: number,
|
|
96
|
+
lng: number,
|
|
97
|
+
options?: {
|
|
98
|
+
simulate?: boolean
|
|
99
|
+
speedMultiplier?: number
|
|
100
|
+
/** Optional multi-stop list. When present takes precedence over lat/lng. Last entry is the final destination. */
|
|
101
|
+
stops?: Array<{lat: number; lng: number}>
|
|
102
|
+
/** "walking" | "driving" | "cycling" | "two_wheeler". Defaults driving. */
|
|
103
|
+
mode?: string
|
|
104
|
+
avoid?: {highways?: boolean; tolls?: boolean; ferries?: boolean}
|
|
105
|
+
/** Force a reroute when the user is more than N meters past a pivot they didn't take. */
|
|
106
|
+
missedTurnRerouteMeters?: number
|
|
107
|
+
},
|
|
108
|
+
): Promise<{ok: boolean; error?: string}>
|
|
109
|
+
stopNavigation(): Promise<{ok: boolean; error?: string}>
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Show the Google Nav SDK Terms & Conditions dialog if not already
|
|
113
|
+
* accepted. Idempotent — resolves immediately with `{accepted: true}`
|
|
114
|
+
* when the user has already accepted (cached in-process / on-disk /
|
|
115
|
+
* inside the SDK).
|
|
116
|
+
*/
|
|
117
|
+
requestNavigationPermission(): Promise<{ok: boolean; accepted: boolean; error?: string}>
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Dev-only: clear cached T&C acceptance (SDK flag + on-disk pref +
|
|
121
|
+
* in-process flag) so the next requestNavigationPermission() re-shows
|
|
122
|
+
* the dialog. Android only; iOS returns {ok: false, error: "not
|
|
123
|
+
* supported on iOS"}.
|
|
124
|
+
*/
|
|
125
|
+
resetNavigationPermission(): Promise<{ok: boolean; error?: string}>
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Dev-only: nudge the simulated user position ~offsetMeters perpendicular
|
|
129
|
+
* to the route so the Nav SDK reroutes. Default 20m. Android only.
|
|
130
|
+
*/
|
|
131
|
+
simulateDeviation(offsetMeters?: number): Promise<{ok: boolean; error?: string}>
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Dev toggle. When enabled, the native NavigationManager shifts every
|
|
135
|
+
* reported location ~8m perpendicular to the route bearing, simulating
|
|
136
|
+
* a pedestrian walking on the wrong sidewalk. Only meaningful in
|
|
137
|
+
* simulate mode; lets us verify the SDK's along-path pivot trigger
|
|
138
|
+
* fires even when the user never crosses the 7m pivot point radius.
|
|
139
|
+
* Android-only today; iOS is a no-op stub.
|
|
140
|
+
*/
|
|
141
|
+
setWrongSidewalkOffset(enabled: boolean): Promise<{ok: boolean; error?: string}>
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Dev toggle. When enabled, the native NavigationManager takes over
|
|
145
|
+
* from the Google simulator and walks the user along a modified
|
|
146
|
+
* polyline that omits crossing micro-steps — reproducing the
|
|
147
|
+
* wrong-sidewalk-then-missed-the-turn scenario. Android-only today;
|
|
148
|
+
* iOS is a no-op stub.
|
|
149
|
+
*/
|
|
150
|
+
setSkipCrossings(enabled: boolean): Promise<{ok: boolean; error?: string}>
|
|
151
|
+
|
|
152
|
+
// Heading / compass (Android only)
|
|
153
|
+
startHeading(): Promise<{ok: boolean; error?: string}>
|
|
154
|
+
stopHeading(): Promise<{ok: boolean; error?: string}>
|
|
155
|
+
|
|
156
|
+
// MentraJS Runtime — per-miniapp JSContext lifecycle.
|
|
157
|
+
/**
|
|
158
|
+
* Spawn a per-miniapp JS context. Re-spawn is allowed: a live context
|
|
159
|
+
* with the same packageName is killed first. Returns true if the
|
|
160
|
+
* polyfill bundle + miniapp source evaluated without throwing.
|
|
161
|
+
*
|
|
162
|
+
* The polyfill bundle is `mobile/modules/mentrajs-runtime/dist/startup.js`
|
|
163
|
+
* (shipped inside the host binary). It installs window-style globals
|
|
164
|
+
* (console, timers, fetch, localStorage, crypto) atop the JSC runtime.
|
|
165
|
+
*/
|
|
166
|
+
mentraJsSpawn(packageName: string, polyfillBundle: string, miniappJs: string): Promise<boolean>
|
|
167
|
+
mentraJsEvaluate(packageName: string, source: string): Promise<unknown>
|
|
168
|
+
mentraJsKill(packageName: string): Promise<void>
|
|
169
|
+
/**
|
|
170
|
+
* Push a `{kind: "event"|"response", …}` envelope into the named
|
|
171
|
+
* context's globalThis.__deliver. Returns when the underlying
|
|
172
|
+
* evaluateScript completes (the JS handler runs synchronously on the
|
|
173
|
+
* context's queue).
|
|
174
|
+
*/
|
|
175
|
+
mentraJsDispatchToJs(packageName: string, envelope: Record<string, unknown>): Promise<void>
|
|
176
|
+
mentraJsSetManifest(packageName: string, permissions: string[]): Promise<void>
|
|
177
|
+
/** Diagnostic — returns the packageNames of every live JSContext. */
|
|
178
|
+
mentraJsAlivePackages(): string[]
|
|
179
|
+
/** Diagnostic — force a GC cycle on the named context. */
|
|
180
|
+
mentraJsDebugForceGC(packageName: string): Promise<boolean>
|
|
181
|
+
/**
|
|
182
|
+
* Read the bundled MentraJS polyfill (startup.js) shipped inside the
|
|
183
|
+
* host binary. Synchronous — host RN code calls this once on app boot
|
|
184
|
+
* and caches the string, then passes it to every mentraJsSpawn so
|
|
185
|
+
* every JSContext starts with the same polyfill ABI.
|
|
186
|
+
*/
|
|
187
|
+
mentraJsLoadPolyfillBundle(): string
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// This call loads the native module object from the JSI.
|
|
191
|
+
export default requireNativeModule<CrustModule>("Crust")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {registerWebModule, NativeModule} from "expo"
|
|
2
|
+
|
|
3
|
+
import {CrustModuleEvents} from "./Crust.types"
|
|
4
|
+
|
|
5
|
+
class CrustModule extends NativeModule<CrustModuleEvents> {
|
|
6
|
+
PI = Math.PI
|
|
7
|
+
async setValueAsync(value: string): Promise<void> {
|
|
8
|
+
this.emit("onChange", {value})
|
|
9
|
+
}
|
|
10
|
+
async nativeHttpRequest(method: string, url: string, headers: Record<string, string>, body?: string | null) {
|
|
11
|
+
const response = await fetch(url, {method, headers, body})
|
|
12
|
+
return {
|
|
13
|
+
status: response.status,
|
|
14
|
+
statusText: response.statusText,
|
|
15
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
16
|
+
body: await response.text(),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
hello() {
|
|
20
|
+
return "Hello world! 👋"
|
|
21
|
+
}
|
|
22
|
+
showAVRoutePicker(_tintColor?: string | null) {}
|
|
23
|
+
async setDeferredSystemGestures(_edges: string[]): Promise<void> {}
|
|
24
|
+
async setNotificationConfig(_enabled: boolean, _blocklist: string[]): Promise<void> {}
|
|
25
|
+
async getInstalledApps() {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
async getInstalledAppsForNotifications() {
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
async hasNotificationListenerPermission() {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
async openNotificationListenerSettings() {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
async isBetaBuild() {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
async mentraJsSpawn(_pkg: string, _polyfill: string, _miniappJs: string) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
async mentraJsEvaluate(_pkg: string, _src: string) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
async mentraJsKill(_pkg: string) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
async mentraJsDispatchToJs(_pkg: string, _env: Record<string, unknown>) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
async mentraJsSetManifest(_pkg: string, _perms: string[]) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
mentraJsAlivePackages() {
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
async mentraJsDebugForceGC(_pkg: string) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
mentraJsLoadPolyfillBundle() {
|
|
62
|
+
return ""
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default registerWebModule(CrustModule, "CrustModule")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {requireNativeView} from "expo"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
|
|
4
|
+
import {CrustViewProps} from "./Crust.types"
|
|
5
|
+
|
|
6
|
+
const NativeView: React.ComponentType<CrustViewProps> = requireNativeView("Crust")
|
|
7
|
+
|
|
8
|
+
export default function CrustView(props: CrustViewProps) {
|
|
9
|
+
return <NativeView {...props} />
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import {CrustViewProps} from "./Crust.types"
|
|
4
|
+
|
|
5
|
+
export default function CrustView(props: CrustViewProps) {
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<iframe style={{flex: 1}} src={props.url} onLoad={() => props.onLoad({nativeEvent: {url: props.url}})} />
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
package/src/index.ts
ADDED