@therealitycrafters/react-native-evskit 0.1.0 → 0.1.2

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.
@@ -28,6 +28,8 @@ import UIKit.app.data.ProximityActionType
28
28
  import UIKit.app.data.YprData
29
29
  import UIKit.app.data.QuaternionData
30
30
 
31
+ import UIKit.app.Viewport
32
+
31
33
  class DeviceInfoRecord : Record {
32
34
  @Field val deviceId: String = ""
33
35
  @Field val name: String = ""
@@ -44,6 +46,12 @@ class EvsKitModule : Module() {
44
46
  private var glassesEventsHandle: IEvsGlassesEvents? = null
45
47
  private var sensorsEventsHandle: IEvsSensorsEvents? = null
46
48
 
49
+ // Screens pushed via showScreen(id:), keyed by the JS-facing id so
50
+ // removeScreen(id:) can resolve the native instance. Viewport covers
51
+ // Screen, FullScreen and ArScreen.
52
+ private val activeScreens = mutableMapOf<String, Viewport>()
53
+ private val activeScreenOrder = mutableListOf<String>()
54
+
47
55
  override fun definition() = ModuleDefinition {
48
56
  Name("RNEvsKit")
49
57
 
@@ -61,6 +69,7 @@ class EvsKitModule : Module() {
61
69
 
62
70
  OnCreate {
63
71
  registerNativeListeners()
72
+ loadAppScreens()
64
73
  }
65
74
 
66
75
  OnDestroy {
@@ -97,6 +106,69 @@ class EvsKitModule : Module() {
97
106
  }
98
107
  }
99
108
 
109
+ // --- App-owned screens (IEvsScreenService) -------------------------------
110
+ //
111
+ // The bridge ships no Screen subclasses. Apps register their own via
112
+ // EvsScreenRegistry.register(id, factory) from app-owned Kotlin sources
113
+ // (linked at prebuild by the `screens` plugin option), then push them
114
+ // from JS with showScreen(id:).
115
+
116
+ AsyncFunction("showScreen") { id: String, promise: Promise ->
117
+ try {
118
+ val screen = EvsScreenRegistry.make(id)
119
+ ?: throw IllegalArgumentException(
120
+ "No screen registered for id '$id'. " +
121
+ "Register it via EvsScreenRegistry.register(id, factory) " +
122
+ "from app-owned Kotlin sources."
123
+ )
124
+ Evs.instance().screens().addScreen(screen)
125
+ if (!activeScreens.containsKey(id)) {
126
+ activeScreenOrder.add(id)
127
+ }
128
+ activeScreens[id] = screen
129
+ promise.resolve(null)
130
+ } catch (e: Exception) {
131
+ promise.reject("EVSKIT_SHOW_SCREEN_FAILED", e.message, e)
132
+ }
133
+ }
134
+
135
+ AsyncFunction("removeScreen") { id: String, promise: Promise ->
136
+ try {
137
+ activeScreens.remove(id)?.let { screen ->
138
+ Evs.instance().screens().removeScreen(screen)
139
+ activeScreenOrder.remove(id)
140
+ }
141
+ promise.resolve(null)
142
+ } catch (e: Exception) {
143
+ promise.reject("EVSKIT_REMOVE_SCREEN_FAILED", e.message, e)
144
+ }
145
+ }
146
+
147
+ AsyncFunction("removeTopmostScreen") { promise: Promise ->
148
+ try {
149
+ val removed = Evs.instance().screens().removeTopmostScreen()
150
+ val id = if (removed != null) {
151
+ activeScreens.entries.firstOrNull { it.value === removed }?.key?.also { found ->
152
+ activeScreens.remove(found)
153
+ activeScreenOrder.remove(found)
154
+ }
155
+ } else null
156
+ promise.resolve(id)
157
+ } catch (e: Exception) {
158
+ promise.reject("EVSKIT_REMOVE_TOPMOST_SCREEN_FAILED", e.message, e)
159
+ }
160
+ }
161
+
162
+ AsyncFunction("getTopmostScreen") { promise: Promise ->
163
+ try {
164
+ val topmost = Evs.instance().screens().getTopmostScreen()
165
+ val id = activeScreens.entries.firstOrNull { it.value === topmost }?.key
166
+ promise.resolve(id)
167
+ } catch (e: Exception) {
168
+ promise.reject("EVSKIT_GET_TOPMOST_SCREEN_FAILED", e.message, e)
169
+ }
170
+ }
171
+
100
172
  // --- Communication (IEvsCommunicationService) -----------------------------
101
173
 
102
174
  AsyncFunction("setDeviceInfo") { info: DeviceInfoRecord, promise: Promise ->
@@ -313,6 +385,17 @@ class EvsKitModule : Module() {
313
385
  // `addEvsKitListener` / `useEvsKitEvent` instead of touching native code.
314
386
  // ---------------------------------------------------------------------------
315
387
 
388
+ // Triggers the plugin-generated app-screens registrar, if the app
389
+ // configured `screens.register`. Class.forName initializes the Kotlin
390
+ // object, running its init block. Absent = no app screens; ignore.
391
+ private fun loadAppScreens() {
392
+ try {
393
+ Class.forName("evs.screens.generated.EvsAppScreensGenerated")
394
+ } catch (_: ClassNotFoundException) {
395
+ // No app-owned screens configured — nothing to register.
396
+ }
397
+ }
398
+
316
399
  private fun registerNativeListeners() {
317
400
  glassesEventsHandle = object : IEvsGlassesEvents {
318
401
  override fun onTouch(touch: TouchDirection) {
@@ -0,0 +1,25 @@
1
+ package expo.modules.evskit
2
+
3
+ import UIKit.app.Viewport
4
+
5
+ /**
6
+ * Generic app-owned screen registry.
7
+ *
8
+ * The bridge itself ships NO Screen subclasses. Each app registers its own
9
+ * Kotlin `Screen`/`FullScreen`/`ArScreen` subclasses under a string id
10
+ * (from app-owned sources linked at prebuild by the `screens` plugin
11
+ * option), then drives them from JS via `EvsScreens.show(id)`.
12
+ */
13
+ object EvsScreenRegistry {
14
+ private val factories = mutableMapOf<String, () -> Viewport>()
15
+
16
+ fun register(id: String, factory: () -> Viewport) {
17
+ factories[id] = factory
18
+ }
19
+
20
+ fun unregister(id: String) {
21
+ factories.remove(id)
22
+ }
23
+
24
+ fun make(id: String): Viewport? = factories[id]?.invoke()
25
+ }
package/app.plugin.js ADDED
@@ -0,0 +1,9 @@
1
+ const withEverysightBundleResources = require('./plugin/withEverysightBundleResources');
2
+ const withEvsScreens = require('./plugin/withEvsScreens');
3
+
4
+ module.exports = (config = {}, { bundleResources = [], screens = {} } = {}) => {
5
+ let result = config;
6
+ result = withEverysightBundleResources(result, { bundleResources });
7
+ result = withEvsScreens(result, { screens });
8
+ return result;
9
+ };
@@ -58,6 +58,18 @@ export interface EvsQuaternionSample extends EvsSensorSample<EvsQuaternionData>
58
58
  calibrationStatus: CalibrationStatus;
59
59
  }
60
60
  export type EvsStockUIScreen = "configure" | "adjust" | "calibrate";
61
+ /**
62
+ * String id of an app-owned native screen.
63
+ *
64
+ * The bridge ships no Screen subclasses. Each app registers its own
65
+ * Swift/Kotlin `Screen`/`FullScreen`/`ArScreen` subclasses via
66
+ * `EvsScreenRegistry.register(id, factory)` (iOS) /
67
+ * `EvsScreenRegistry.register(id, factory)` (Android) from app-owned
68
+ * sources — e.g. `<app>/evs-screens/ios/*.swift` linked at prebuild by the
69
+ * bridge config plugin's `screens` option — then pushes them with
70
+ * `EvsScreens.show(id)`.
71
+ */
72
+ export type EvsScreenId = string;
61
73
  export interface EvsBeginAuthEvent {
62
74
  serial: string;
63
75
  fwVersion: number;
@@ -1 +1 @@
1
- {"version":3,"file":"EvsKit.types.d.ts","sourceRoot":"","sources":["../src/EvsKit.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,kBAAkB,GAC5B,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAM3E,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAMlD,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,YAAY,GAAG,UAAU,CAAC;AAEzE,kGAAkG;AAClG,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,YAAa,SAAQ,eAAe,CAAC,UAAU,CAAC;IAC/D,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED,MAAM,WAAW,mBAAoB,SAAQ,eAAe,CAAC,iBAAiB,CAAC;IAC7E,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAMD,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAMpE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,MAAM,YAAY,GAAG;IACzB,wBAAwB,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9D,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,OAAO,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACpD,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC"}
1
+ {"version":3,"file":"EvsKit.types.d.ts","sourceRoot":"","sources":["../src/EvsKit.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,kBAAkB,GAC5B,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAM3E,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAMlD,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,YAAY,GAAG,UAAU,CAAC;AAEzE,kGAAkG;AAClG,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,YAAa,SAAQ,eAAe,CAAC,UAAU,CAAC;IAC/D,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED,MAAM,WAAW,mBAAoB,SAAQ,eAAe,CAAC,iBAAiB,CAAC;IAC7E,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAMD,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAMpE;;;;;;;;;;GAUG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAMjC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,MAAM,YAAY,GAAG;IACzB,wBAAwB,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9D,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,OAAO,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACpD,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"EvsKit.types.js","sourceRoot":"","sources":["../src/EvsKit.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG","sourcesContent":["/**\n * Type definitions mirroring Everysight's Maverick SDK interfaces\n * (IEvsCommunicationService, IEvsGlassesStateService, IEvsDisplayService,\n * IEvsSensorsService, IEvsAuthService).\n *\n * Signatures confirmed against the everysight-maverick/samples repo (v2.6.1)\n * and the MCP API reference.\n */\n\n// ---------------------------------------------------------------------------\n// Communication (IEvsCommunicationService)\n// ---------------------------------------------------------------------------\n\nexport interface EvsDeviceInfo {\n /** Bluetooth device address/id, e.g. from a BLE scan (EV0080 style name). */\n deviceId: string;\n /** Bluetooth device name. */\n name: string;\n}\n\nexport type EvsConnectionState =\n \"disconnected\" | \"connecting\" | \"connected\" | \"authenticating\" | \"error\";\n\n// ---------------------------------------------------------------------------\n// Glasses (IEvsGlassesStateService)\n// ---------------------------------------------------------------------------\n\nexport interface EvsGlassesInfo {\n /** Battery percentage 0-100 from IEvsGlassesStateService.batteryPercentage(). */\n batteryLevel: number;\n /** Battery level category (Unknown/Critical/Low/Medium/High) from batteryLevel(). */\n batteryStatus: string;\n /** Estimated remaining operation time in minutes. */\n estimatedRemainingTime: number;\n /** Firmware version number (Int from fwVersion()). */\n firmwareVersion: number;\n}\n\nexport type TouchDirection = \"backward\" | \"forward\" | \"tap\" | \"longTap\";\n\nexport type ProximityEvent = \"onFace\" | \"offFace\";\n\n// ---------------------------------------------------------------------------\n// Display (IEvsDisplayService)\n// ---------------------------------------------------------------------------\n\nexport interface EvsAutoBrightnessOptions {\n enabled: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Sensors (IEvsSensorsService)\n// ---------------------------------------------------------------------------\n\nexport type CalibrationStatus = \"calibrated\" | \"inProgress\" | \"required\";\n\n/** Currently only rate0 and rate1 are operational per the SDK docs; the rest are experimental. */\nexport type SensorRate = \"rate0\" | \"rate1\" | \"rate2\" | \"rate3\";\n\nexport interface EvsYprData {\n yaw: number;\n pitch: number;\n roll: number;\n}\n\nexport interface EvsQuaternionData {\n w: number;\n x: number;\n y: number;\n z: number;\n}\n\nexport interface EvsVector3 {\n x: number;\n y: number;\n z: number;\n}\n\nexport interface EvsSensorSample<T> {\n timestampMs: number;\n data: T;\n}\n\nexport interface EvsYprSample extends EvsSensorSample<EvsYprData> {\n calibrationStatus: CalibrationStatus;\n}\n\nexport interface EvsQuaternionSample extends EvsSensorSample<EvsQuaternionData> {\n calibrationStatus: CalibrationStatus;\n}\n\n// ---------------------------------------------------------------------------\n// Stock UI (Evs.instance().showUI(...))\n// ---------------------------------------------------------------------------\n\nexport type EvsStockUIScreen = \"configure\" | \"adjust\" | \"calibrate\";\n\n// ---------------------------------------------------------------------------\n// Auth (IEvsAuthService) / lifecycle events (IEvsAppEvents)\n// ---------------------------------------------------------------------------\n\nexport interface EvsBeginAuthEvent {\n serial: string;\n fwVersion: number;\n}\n\n// ---------------------------------------------------------------------------\n// Native event map — every event the native module can emit to JS via\n// EventEmitter. Keep this in sync with EvsKitModule.kt / EvsKitModule.swift.\n// ---------------------------------------------------------------------------\n\nexport type EvsKitEvents = {\n onConnectionStateChanged: (state: EvsConnectionState) => void;\n onBeginAuth: (event: EvsBeginAuthEvent) => void;\n onGlassesInfoChanged: (info: EvsGlassesInfo) => void;\n onTouch: (direction: TouchDirection) => void;\n onProximity: (event: ProximityEvent) => void;\n onAmbient: (lux: number) => void;\n onYpr: (sample: EvsYprSample) => void;\n onQuaternion: (sample: EvsQuaternionSample) => void;\n onError: (message: string) => void;\n};\n"]}
1
+ {"version":3,"file":"EvsKit.types.js","sourceRoot":"","sources":["../src/EvsKit.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG","sourcesContent":["/**\n * Type definitions mirroring Everysight's Maverick SDK interfaces\n * (IEvsCommunicationService, IEvsGlassesStateService, IEvsDisplayService,\n * IEvsSensorsService, IEvsAuthService).\n *\n * Signatures confirmed against the everysight-maverick/samples repo (v2.6.1)\n * and the MCP API reference.\n */\n\n// ---------------------------------------------------------------------------\n// Communication (IEvsCommunicationService)\n// ---------------------------------------------------------------------------\n\nexport interface EvsDeviceInfo {\n /** Bluetooth device address/id, e.g. from a BLE scan (EV0080 style name). */\n deviceId: string;\n /** Bluetooth device name. */\n name: string;\n}\n\nexport type EvsConnectionState =\n \"disconnected\" | \"connecting\" | \"connected\" | \"authenticating\" | \"error\";\n\n// ---------------------------------------------------------------------------\n// Glasses (IEvsGlassesStateService)\n// ---------------------------------------------------------------------------\n\nexport interface EvsGlassesInfo {\n /** Battery percentage 0-100 from IEvsGlassesStateService.batteryPercentage(). */\n batteryLevel: number;\n /** Battery level category (Unknown/Critical/Low/Medium/High) from batteryLevel(). */\n batteryStatus: string;\n /** Estimated remaining operation time in minutes. */\n estimatedRemainingTime: number;\n /** Firmware version number (Int from fwVersion()). */\n firmwareVersion: number;\n}\n\nexport type TouchDirection = \"backward\" | \"forward\" | \"tap\" | \"longTap\";\n\nexport type ProximityEvent = \"onFace\" | \"offFace\";\n\n// ---------------------------------------------------------------------------\n// Display (IEvsDisplayService)\n// ---------------------------------------------------------------------------\n\nexport interface EvsAutoBrightnessOptions {\n enabled: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Sensors (IEvsSensorsService)\n// ---------------------------------------------------------------------------\n\nexport type CalibrationStatus = \"calibrated\" | \"inProgress\" | \"required\";\n\n/** Currently only rate0 and rate1 are operational per the SDK docs; the rest are experimental. */\nexport type SensorRate = \"rate0\" | \"rate1\" | \"rate2\" | \"rate3\";\n\nexport interface EvsYprData {\n yaw: number;\n pitch: number;\n roll: number;\n}\n\nexport interface EvsQuaternionData {\n w: number;\n x: number;\n y: number;\n z: number;\n}\n\nexport interface EvsVector3 {\n x: number;\n y: number;\n z: number;\n}\n\nexport interface EvsSensorSample<T> {\n timestampMs: number;\n data: T;\n}\n\nexport interface EvsYprSample extends EvsSensorSample<EvsYprData> {\n calibrationStatus: CalibrationStatus;\n}\n\nexport interface EvsQuaternionSample extends EvsSensorSample<EvsQuaternionData> {\n calibrationStatus: CalibrationStatus;\n}\n\n// ---------------------------------------------------------------------------\n// Stock UI (Evs.instance().showUI(...))\n// ---------------------------------------------------------------------------\n\nexport type EvsStockUIScreen = \"configure\" | \"adjust\" | \"calibrate\";\n\n// ---------------------------------------------------------------------------\n// App-owned screens (IEvsScreenService via EvsScreenRegistry)\n// ---------------------------------------------------------------------------\n\n/**\n * String id of an app-owned native screen.\n *\n * The bridge ships no Screen subclasses. Each app registers its own\n * Swift/Kotlin `Screen`/`FullScreen`/`ArScreen` subclasses via\n * `EvsScreenRegistry.register(id, factory)` (iOS) /\n * `EvsScreenRegistry.register(id, factory)` (Android) from app-owned\n * sources — e.g. `<app>/evs-screens/ios/*.swift` linked at prebuild by the\n * bridge config plugin's `screens` option — then pushes them with\n * `EvsScreens.show(id)`.\n */\nexport type EvsScreenId = string;\n\n// ---------------------------------------------------------------------------\n// Auth (IEvsAuthService) / lifecycle events (IEvsAppEvents)\n// ---------------------------------------------------------------------------\n\nexport interface EvsBeginAuthEvent {\n serial: string;\n fwVersion: number;\n}\n\n// ---------------------------------------------------------------------------\n// Native event map — every event the native module can emit to JS via\n// EventEmitter. Keep this in sync with EvsKitModule.kt / EvsKitModule.swift.\n// ---------------------------------------------------------------------------\n\nexport type EvsKitEvents = {\n onConnectionStateChanged: (state: EvsConnectionState) => void;\n onBeginAuth: (event: EvsBeginAuthEvent) => void;\n onGlassesInfoChanged: (info: EvsGlassesInfo) => void;\n onTouch: (direction: TouchDirection) => void;\n onProximity: (event: ProximityEvent) => void;\n onAmbient: (lux: number) => void;\n onYpr: (sample: EvsYprSample) => void;\n onQuaternion: (sample: EvsQuaternionSample) => void;\n onError: (message: string) => void;\n};\n"]}
@@ -28,6 +28,18 @@ declare class EvsKitNativeModule extends NativeModule<EvsKitEvents> {
28
28
  setAutoBrightness(options: EvsAutoBrightnessOptions): Promise<void>;
29
29
  setRenderingCenterX(dx: number): Promise<void>;
30
30
  setRenderingCenterY(dy: number): Promise<void>;
31
+ /**
32
+ * Push an app-registered screen onto the glasses screen stack.
33
+ * Rejects with EVSKIT_SHOW_SCREEN_FAILED / EVSKIT_UNKNOWN_SCREEN when no
34
+ * factory is registered for `id`.
35
+ */
36
+ showScreen(id: string): Promise<void>;
37
+ /** Remove a previously shown screen by id. No-op when unknown. */
38
+ removeScreen(id: string): Promise<void>;
39
+ /** Pop the topmost screen; resolves with its id, or null when empty/untracked. */
40
+ removeTopmostScreen(): Promise<string | null>;
41
+ /** Resolve the topmost tracked screen id, or null. */
42
+ getTopmostScreen(): Promise<string | null>;
31
43
  enableTouch(enabled: boolean): Promise<void>;
32
44
  enableAmbient(enabled: boolean): Promise<void>;
33
45
  enableGyro(enabled: boolean): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"EvsKitModule.d.ts","sourceRoot":"","sources":["../src/EvsKitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EACV,wBAAwB,EACxB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB;;;;;;GAMG;AACH,OAAO,OAAO,kBAAmB,SAAQ,YAAY,CAAC,YAAY,CAAC;IAEjE,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAG7B,0EAA0E;IAC1E,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAG/C,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IACjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAC/B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAC3B,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC;IAGvC,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC;IACzC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAG/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAC9B,oCAAoC;IACpC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,yDAAyD;IACzD,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAChD,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAGnE,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9C,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5C,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACnD,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACpD,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAGpD,aAAa,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACxD,gCAAgC;IAChC,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAC5C;;AAED,wBAAmE"}
1
+ {"version":3,"file":"EvsKitModule.d.ts","sourceRoot":"","sources":["../src/EvsKitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EACV,wBAAwB,EACxB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB;;;;;;GAMG;AACH,OAAO,OAAO,kBAAmB,SAAQ,YAAY,CAAC,YAAY,CAAC;IAEjE,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAG7B,0EAA0E;IAC1E,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAG/C,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IACjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAC/B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAC3B,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC;IAGvC,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC;IACzC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAG/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAC9B,oCAAoC;IACpC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,yDAAyD;IACzD,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAChD,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAGnE,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9C,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAG9C;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACrC,kEAAkE;IAClE,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACvC,kFAAkF;IAClF,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAC7C,sDAAsD;IACtD,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAG1C,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5C,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9C,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3C,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACnD,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACpD,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAGpD,aAAa,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACxD,gCAAgC;IAChC,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAC5C;;AAED,wBAAmE"}
@@ -1 +1 @@
1
- {"version":3,"file":"EvsKitModule.js","sourceRoot":"","sources":["../src/EvsKitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAgEtE,eAAe,mBAAmB,CAAqB,UAAU,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo-modules-core\";\n\nimport type {\n EvsAutoBrightnessOptions,\n EvsDeviceInfo,\n EvsGlassesInfo,\n EvsKitEvents,\n EvsStockUIScreen,\n} from \"./EvsKit.types\";\n\n/**\n * Raw native surface. This intentionally mirrors the SDK's service methods\n * 1:1 (comm/glasses/display/sensors/auth) rather than inventing a different\n * shape, so the JS API stays easy to cross-reference against the Maverick\n * docs. Prefer using the higher-level classes exported from `src/index.ts`\n * over calling this module directly.\n */\ndeclare class EvsKitNativeModule extends NativeModule<EvsKitEvents> {\n // --- Lifecycle -----------------------------------------------------------\n /** Evs.init(context).start() on Android / Evs.instance().start() on iOS. */\n start(): Promise<void>;\n isStarted(): Promise<boolean>;\n\n // --- Stock UI --------------------------------------------------------------\n /** Evs.instance().showUI(name) — \"configure\" | \"adjust\" | \"calibrate\". */\n showUI(screen: EvsStockUIScreen): Promise<void>;\n\n // --- Communication (IEvsCommunicationService) -----------------------------\n setDeviceInfo(info: EvsDeviceInfo): Promise<void>;\n connect(): Promise<void>;\n connectSecured(): Promise<void>;\n disconnect(): Promise<void>;\n hasConfiguredDevice(): Promise<boolean>;\n\n // --- Glasses (IEvsGlassesStateService) -------------------------------------\n getGlassesInfo(): Promise<EvsGlassesInfo>;\n turnGlassesOff(): Promise<void>;\n\n // --- Display (IEvsDisplayService) ------------------------------------------\n turnDisplayOn(): Promise<void>;\n /** Continuous brightness, 1-255. */\n setBrightness(value: number): Promise<void>;\n /** Discrete, display-optimized brightness controller. */\n setBrightnessLevel(level: number): Promise<void>;\n setAutoBrightness(options: EvsAutoBrightnessOptions): Promise<void>;\n\n // --- Screens (rendering offset) ---------------------------------------------\n setRenderingCenterX(dx: number): Promise<void>;\n setRenderingCenterY(dy: number): Promise<void>;\n\n // --- Sensors (IEvsSensorsService) -------------------------------------------\n enableTouch(enabled: boolean): Promise<void>;\n enableAmbient(enabled: boolean): Promise<void>;\n enableGyro(enabled: boolean): Promise<void>;\n enableMagnetometer(enabled: boolean): Promise<void>;\n enableAccelerometer(enabled: boolean): Promise<void>;\n enableSensorsFusion(enabled: boolean): Promise<void>;\n\n // --- Auth (IEvsAuthService) --------------------------------------------------\n setApiKeyName(fileNameWithoutExt: string): Promise<void>;\n /** base64-encoded key bytes. */\n setApiKey(base64Key: string): Promise<void>;\n}\n\nexport default requireNativeModule<EvsKitNativeModule>(\"RNEvsKit\");\n"]}
1
+ {"version":3,"file":"EvsKitModule.js","sourceRoot":"","sources":["../src/EvsKitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA8EtE,eAAe,mBAAmB,CAAqB,UAAU,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo-modules-core\";\n\nimport type {\n EvsAutoBrightnessOptions,\n EvsDeviceInfo,\n EvsGlassesInfo,\n EvsKitEvents,\n EvsStockUIScreen,\n} from \"./EvsKit.types\";\n\n/**\n * Raw native surface. This intentionally mirrors the SDK's service methods\n * 1:1 (comm/glasses/display/sensors/auth) rather than inventing a different\n * shape, so the JS API stays easy to cross-reference against the Maverick\n * docs. Prefer using the higher-level classes exported from `src/index.ts`\n * over calling this module directly.\n */\ndeclare class EvsKitNativeModule extends NativeModule<EvsKitEvents> {\n // --- Lifecycle -----------------------------------------------------------\n /** Evs.init(context).start() on Android / Evs.instance().start() on iOS. */\n start(): Promise<void>;\n isStarted(): Promise<boolean>;\n\n // --- Stock UI --------------------------------------------------------------\n /** Evs.instance().showUI(name) — \"configure\" | \"adjust\" | \"calibrate\". */\n showUI(screen: EvsStockUIScreen): Promise<void>;\n\n // --- Communication (IEvsCommunicationService) -----------------------------\n setDeviceInfo(info: EvsDeviceInfo): Promise<void>;\n connect(): Promise<void>;\n connectSecured(): Promise<void>;\n disconnect(): Promise<void>;\n hasConfiguredDevice(): Promise<boolean>;\n\n // --- Glasses (IEvsGlassesStateService) -------------------------------------\n getGlassesInfo(): Promise<EvsGlassesInfo>;\n turnGlassesOff(): Promise<void>;\n\n // --- Display (IEvsDisplayService) ------------------------------------------\n turnDisplayOn(): Promise<void>;\n /** Continuous brightness, 1-255. */\n setBrightness(value: number): Promise<void>;\n /** Discrete, display-optimized brightness controller. */\n setBrightnessLevel(level: number): Promise<void>;\n setAutoBrightness(options: EvsAutoBrightnessOptions): Promise<void>;\n\n // --- Screens (rendering offset) ---------------------------------------------\n setRenderingCenterX(dx: number): Promise<void>;\n setRenderingCenterY(dy: number): Promise<void>;\n\n // --- App-owned screens (IEvsScreenService via EvsScreenRegistry) ------------\n /**\n * Push an app-registered screen onto the glasses screen stack.\n * Rejects with EVSKIT_SHOW_SCREEN_FAILED / EVSKIT_UNKNOWN_SCREEN when no\n * factory is registered for `id`.\n */\n showScreen(id: string): Promise<void>;\n /** Remove a previously shown screen by id. No-op when unknown. */\n removeScreen(id: string): Promise<void>;\n /** Pop the topmost screen; resolves with its id, or null when empty/untracked. */\n removeTopmostScreen(): Promise<string | null>;\n /** Resolve the topmost tracked screen id, or null. */\n getTopmostScreen(): Promise<string | null>;\n\n // --- Sensors (IEvsSensorsService) -------------------------------------------\n enableTouch(enabled: boolean): Promise<void>;\n enableAmbient(enabled: boolean): Promise<void>;\n enableGyro(enabled: boolean): Promise<void>;\n enableMagnetometer(enabled: boolean): Promise<void>;\n enableAccelerometer(enabled: boolean): Promise<void>;\n enableSensorsFusion(enabled: boolean): Promise<void>;\n\n // --- Auth (IEvsAuthService) --------------------------------------------------\n setApiKeyName(fileNameWithoutExt: string): Promise<void>;\n /** base64-encoded key bytes. */\n setApiKey(base64Key: string): Promise<void>;\n}\n\nexport default requireNativeModule<EvsKitNativeModule>(\"RNEvsKit\");\n"]}
package/build/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { EventSubscription } from "expo-modules-core";
2
- import type { EvsAutoBrightnessOptions, EvsDeviceInfo, EvsGlassesInfo, EvsKitEvents, EvsStockUIScreen } from "./EvsKit.types";
2
+ import type { EvsAutoBrightnessOptions, EvsDeviceInfo, EvsGlassesInfo, EvsKitEvents, EvsScreenId, EvsStockUIScreen } from "./EvsKit.types";
3
3
  export * from "./EvsKit.types";
4
4
  /**
5
5
  * Communication service — mirrors IEvsCommunicationService
@@ -58,6 +58,23 @@ export declare const EvsAuth: {
58
58
  export declare const EvsUI: {
59
59
  show(screen: EvsStockUIScreen): Promise<void>;
60
60
  };
61
+ /**
62
+ * App-owned glasses screens (`Evs.instance().screens()`).
63
+ *
64
+ * The bridge ships no Screen subclasses. Register yours natively per app —
65
+ * `EvsScreenRegistry.register(id, factory)` from app-owned Swift/Kotlin
66
+ * sources (linked at prebuild by the plugin's `screens` option) — then push
67
+ * them from JS:
68
+ *
69
+ * @example
70
+ * await EvsScreens.show("hello-world");
71
+ */
72
+ export declare const EvsScreens: {
73
+ show(id: EvsScreenId): Promise<void>;
74
+ remove(id: EvsScreenId): Promise<void>;
75
+ removeTopmost(): Promise<string | null>;
76
+ getTopmost(): Promise<string | null>;
77
+ };
61
78
  /** Top-level lifecycle, matching `Evs.init(context).start()` / `Evs.instance().start()`. */
62
79
  export declare const EvsKit: {
63
80
  start(): Promise<void>;
@@ -100,6 +117,12 @@ export declare const EvsKit: {
100
117
  ui: {
101
118
  show(screen: EvsStockUIScreen): Promise<void>;
102
119
  };
120
+ screens: {
121
+ show(id: EvsScreenId): Promise<void>;
122
+ remove(id: EvsScreenId): Promise<void>;
123
+ removeTopmost(): Promise<string | null>;
124
+ getTopmost(): Promise<string | null>;
125
+ };
103
126
  };
104
127
  export default EvsKit;
105
128
  /** Subscribe to a single EvsKit event; call the returned function to unsubscribe. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,KAAK,EACV,wBAAwB,EACxB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB,cAAc,gBAAgB,CAAC;AAE/B;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO;wBACE,aAAa;;IAMjC,kEAAkE;;;;CAUnE,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,UAAU;eACV,OAAO,CAAC,cAAc,CAAC;;CAMnC,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,UAAU;;IAIrB,2EAA2E;yBACtD,MAAM;IAG3B,4EAA4E;8BAClD,MAAM;+BAGL,wBAAwB;IAGnD,oFAAoF;2BAC7D,MAAM,MAAM,MAAM;CAM1C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU;yBACA,OAAO;2BAGL,OAAO;wBAGV,OAAO;gCAGC,OAAO;iCAGN,OAAO;IAGpC,mFAAmF;0BAC7D,OAAO;CAG9B,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,OAAO;sCACgB,MAAM;yBAGnB,MAAM;CAG5B,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,KAAK;iBACH,gBAAgB;CAG9B,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,MAAM;;;;4BAlGG,aAAa;;QAMjC,kEAAkE;;;;;;mBAcvD,OAAO,CAAC,cAAc,CAAC;;;;;QAalC,2EAA2E;6BACtD,MAAM;QAG3B,4EAA4E;kCAClD,MAAM;mCAGL,wBAAwB;QAGnD,oFAAoF;+BAC7D,MAAM,MAAM,MAAM;;;6BAcpB,OAAO;+BAGL,OAAO;4BAGV,OAAO;oCAGC,OAAO;qCAGN,OAAO;QAGpC,mFAAmF;8BAC7D,OAAO;;;0CAOK,MAAM;6BAGnB,MAAM;;;qBAOd,gBAAgB;;CAmB9B,CAAC;AAEF,eAAe,MAAM,CAAC;AAMtB,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC5D,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,GACxB,iBAAiB,CAEnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EACzD,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,QAO1B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,KAAK,EACV,wBAAwB,EACxB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAAW,EACX,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB,cAAc,gBAAgB,CAAC;AAE/B;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO;wBACE,aAAa;;IAMjC,kEAAkE;;;;CAUnE,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,UAAU;eACV,OAAO,CAAC,cAAc,CAAC;;CAMnC,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,UAAU;;IAIrB,2EAA2E;yBACtD,MAAM;IAG3B,4EAA4E;8BAClD,MAAM;+BAGL,wBAAwB;IAGnD,oFAAoF;2BAC7D,MAAM,MAAM,MAAM;CAM1C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU;yBACA,OAAO;2BAGL,OAAO;wBAGV,OAAO;gCAGC,OAAO;iCAGN,OAAO;IAGpC,mFAAmF;0BAC7D,OAAO;CAG9B,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,OAAO;sCACgB,MAAM;yBAGnB,MAAM;CAG5B,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,KAAK;iBACH,gBAAgB;CAG9B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU;aACZ,WAAW;eAGT,WAAW;;;CASvB,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,MAAM;;;;4BA5HG,aAAa;;QAMjC,kEAAkE;;;;;;mBAcvD,OAAO,CAAC,cAAc,CAAC;;;;;QAalC,2EAA2E;6BACtD,MAAM;QAG3B,4EAA4E;kCAClD,MAAM;mCAGL,wBAAwB;QAGnD,oFAAoF;+BAC7D,MAAM,MAAM,MAAM;;;6BAcpB,OAAO;+BAGL,OAAO;4BAGV,OAAO;oCAGC,OAAO;qCAGN,OAAO;QAGpC,mFAAmF;8BAC7D,OAAO;;;0CAOK,MAAM;6BAGnB,MAAM;;;qBAOd,gBAAgB;;;iBAiBpB,WAAW;mBAGT,WAAW;;;;CA0BvB,CAAC;AAEF,eAAe,MAAM,CAAC;AAMtB,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC5D,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,GACxB,iBAAiB,CAEnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EACzD,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,QAO1B"}
package/build/index.js CHANGED
@@ -103,6 +103,31 @@ export const EvsUI = {
103
103
  return EvsKitNative.showUI(screen);
104
104
  },
105
105
  };
106
+ /**
107
+ * App-owned glasses screens (`Evs.instance().screens()`).
108
+ *
109
+ * The bridge ships no Screen subclasses. Register yours natively per app —
110
+ * `EvsScreenRegistry.register(id, factory)` from app-owned Swift/Kotlin
111
+ * sources (linked at prebuild by the plugin's `screens` option) — then push
112
+ * them from JS:
113
+ *
114
+ * @example
115
+ * await EvsScreens.show("hello-world");
116
+ */
117
+ export const EvsScreens = {
118
+ show(id) {
119
+ return EvsKitNative.showScreen(id);
120
+ },
121
+ remove(id) {
122
+ return EvsKitNative.removeScreen(id);
123
+ },
124
+ removeTopmost() {
125
+ return EvsKitNative.removeTopmostScreen();
126
+ },
127
+ getTopmost() {
128
+ return EvsKitNative.getTopmostScreen();
129
+ },
130
+ };
106
131
  /** Top-level lifecycle, matching `Evs.init(context).start()` / `Evs.instance().start()`. */
107
132
  export const EvsKit = {
108
133
  start() {
@@ -117,6 +142,7 @@ export const EvsKit = {
117
142
  sensors: EvsSensors,
118
143
  auth: EvsAuth,
119
144
  ui: EvsUI,
145
+ screens: EvsScreens,
120
146
  };
121
147
  export default EvsKit;
122
148
  // ---------------------------------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport { useEffect } from \"react\";\n\nimport type {\n EvsAutoBrightnessOptions,\n EvsDeviceInfo,\n EvsGlassesInfo,\n EvsKitEvents,\n EvsStockUIScreen,\n} from \"./EvsKit.types\";\nimport EvsKitNative from \"./EvsKitModule\";\n\nexport * from \"./EvsKit.types\";\n\n/**\n * Communication service — mirrors IEvsCommunicationService\n * (`Evs.instance().comm()`).\n *\n * Scanning for glasses is NOT handled here: the SDK docs specify scanning\n * is done with the platform's standard BLE APIs (CBCentralManager /\n * BluetoothLeScanner) against `BTConstants.serviceUUID`, filtering for the\n * \"EV####\" name pattern. Pair that with a BLE library (e.g. react-native-ble-plx)\n * or just use `EvsUI.show('configure')` for the SDK's built-in scan/pair flow.\n */\nexport const EvsComm = {\n setDeviceInfo(info: EvsDeviceInfo) {\n return EvsKitNative.setDeviceInfo(info);\n },\n connect() {\n return EvsKitNative.connect();\n },\n /** Use instead of connect() when the app requires BLE pairing. */\n connectSecured() {\n return EvsKitNative.connectSecured();\n },\n disconnect() {\n return EvsKitNative.disconnect();\n },\n hasConfiguredDevice() {\n return EvsKitNative.hasConfiguredDevice();\n },\n};\n\n/** Mirrors IEvsGlassesStateService (`Evs.instance().glasses()`). */\nexport const EvsGlasses = {\n getInfo(): Promise<EvsGlassesInfo> {\n return EvsKitNative.getGlassesInfo();\n },\n turnOff() {\n return EvsKitNative.turnGlassesOff();\n },\n};\n\n/** Mirrors IEvsDisplayService (`Evs.instance().display()`) plus screen offset controls. */\nexport const EvsDisplay = {\n turnOn() {\n return EvsKitNative.turnDisplayOn();\n },\n /** Continuous scale, 1-255. Prefer setBrightnessLevel() where possible. */\n setBrightness(value: number) {\n return EvsKitNative.setBrightness(value);\n },\n /** Discrete brightness level via the SDK's display-optimized controller. */\n setBrightnessLevel(level: number) {\n return EvsKitNative.setBrightnessLevel(level);\n },\n setAutoBrightness(options: EvsAutoBrightnessOptions) {\n return EvsKitNative.setAutoBrightness(options);\n },\n /** Adjust the virtual rendering area's center offset within the 640x400 display. */\n setRenderingCenter(dx: number, dy: number) {\n return Promise.all([\n EvsKitNative.setRenderingCenterX(dx),\n EvsKitNative.setRenderingCenterY(dy),\n ]);\n },\n};\n\n/**\n * Mirrors IEvsSensorsService (`Evs.instance().sensors()`).\n * Remember to disable sensors and unsubscribe events when no longer needed —\n * the SDK docs call this out explicitly for battery/bandwidth reasons.\n */\nexport const EvsSensors = {\n enableTouch(enabled: boolean) {\n return EvsKitNative.enableTouch(enabled);\n },\n enableAmbient(enabled: boolean) {\n return EvsKitNative.enableAmbient(enabled);\n },\n enableGyro(enabled: boolean) {\n return EvsKitNative.enableGyro(enabled);\n },\n enableMagnetometer(enabled: boolean) {\n return EvsKitNative.enableMagnetometer(enabled);\n },\n enableAccelerometer(enabled: boolean) {\n return EvsKitNative.enableAccelerometer(enabled);\n },\n /** Enables Gyro + Magnetometer + Accelerometer and starts the fusion algorithm. */\n enableFusion(enabled: boolean) {\n return EvsKitNative.enableSensorsFusion(enabled);\n },\n};\n\n/** Mirrors IEvsAuthService (`Evs.instance().auth()`). Usually unnecessary — see API Keys docs. */\nexport const EvsAuth = {\n setApiKeyName(fileNameWithoutExt: string) {\n return EvsKitNative.setApiKeyName(fileNameWithoutExt);\n },\n setApiKey(base64Key: string) {\n return EvsKitNative.setApiKey(base64Key);\n },\n};\n\n/** Mirrors `Evs.instance().showUI(...)` — the SDK's built-in portrait UI screens. */\nexport const EvsUI = {\n show(screen: EvsStockUIScreen) {\n return EvsKitNative.showUI(screen);\n },\n};\n\n/** Top-level lifecycle, matching `Evs.init(context).start()` / `Evs.instance().start()`. */\nexport const EvsKit = {\n start() {\n return EvsKitNative.start();\n },\n isStarted() {\n return EvsKitNative.isStarted();\n },\n comm: EvsComm,\n glasses: EvsGlasses,\n display: EvsDisplay,\n sensors: EvsSensors,\n auth: EvsAuth,\n ui: EvsUI,\n};\n\nexport default EvsKit;\n\n// ---------------------------------------------------------------------------\n// Event subscription helpers\n// ---------------------------------------------------------------------------\n\n/** Subscribe to a single EvsKit event; call the returned function to unsubscribe. */\nexport function addEvsKitListener<K extends keyof EvsKitEvents>(\n eventName: K,\n listener: EvsKitEvents[K],\n): EventSubscription {\n return EvsKitNative.addListener(eventName, listener as any);\n}\n\n/**\n * React hook that subscribes to an EvsKit event for the lifetime of the\n * component. Pass a stable callback (e.g. via useCallback) to avoid\n * resubscribing every render.\n *\n * @example\n * useEvsKitEvent('onTouch', (direction) => {\n * if (direction === 'tap') showPopup();\n * });\n */\nexport function useEvsKitEvent<K extends keyof EvsKitEvents>(\n eventName: K,\n listener: EvsKitEvents[K],\n) {\n useEffect(() => {\n const subscription = addEvsKitListener(eventName, listener);\n return () => subscription.remove();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [eventName, listener]);\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport { useEffect } from \"react\";\n\nimport type {\n EvsAutoBrightnessOptions,\n EvsDeviceInfo,\n EvsGlassesInfo,\n EvsKitEvents,\n EvsScreenId,\n EvsStockUIScreen,\n} from \"./EvsKit.types\";\nimport EvsKitNative from \"./EvsKitModule\";\n\nexport * from \"./EvsKit.types\";\n\n/**\n * Communication service — mirrors IEvsCommunicationService\n * (`Evs.instance().comm()`).\n *\n * Scanning for glasses is NOT handled here: the SDK docs specify scanning\n * is done with the platform's standard BLE APIs (CBCentralManager /\n * BluetoothLeScanner) against `BTConstants.serviceUUID`, filtering for the\n * \"EV####\" name pattern. Pair that with a BLE library (e.g. react-native-ble-plx)\n * or just use `EvsUI.show('configure')` for the SDK's built-in scan/pair flow.\n */\nexport const EvsComm = {\n setDeviceInfo(info: EvsDeviceInfo) {\n return EvsKitNative.setDeviceInfo(info);\n },\n connect() {\n return EvsKitNative.connect();\n },\n /** Use instead of connect() when the app requires BLE pairing. */\n connectSecured() {\n return EvsKitNative.connectSecured();\n },\n disconnect() {\n return EvsKitNative.disconnect();\n },\n hasConfiguredDevice() {\n return EvsKitNative.hasConfiguredDevice();\n },\n};\n\n/** Mirrors IEvsGlassesStateService (`Evs.instance().glasses()`). */\nexport const EvsGlasses = {\n getInfo(): Promise<EvsGlassesInfo> {\n return EvsKitNative.getGlassesInfo();\n },\n turnOff() {\n return EvsKitNative.turnGlassesOff();\n },\n};\n\n/** Mirrors IEvsDisplayService (`Evs.instance().display()`) plus screen offset controls. */\nexport const EvsDisplay = {\n turnOn() {\n return EvsKitNative.turnDisplayOn();\n },\n /** Continuous scale, 1-255. Prefer setBrightnessLevel() where possible. */\n setBrightness(value: number) {\n return EvsKitNative.setBrightness(value);\n },\n /** Discrete brightness level via the SDK's display-optimized controller. */\n setBrightnessLevel(level: number) {\n return EvsKitNative.setBrightnessLevel(level);\n },\n setAutoBrightness(options: EvsAutoBrightnessOptions) {\n return EvsKitNative.setAutoBrightness(options);\n },\n /** Adjust the virtual rendering area's center offset within the 640x400 display. */\n setRenderingCenter(dx: number, dy: number) {\n return Promise.all([\n EvsKitNative.setRenderingCenterX(dx),\n EvsKitNative.setRenderingCenterY(dy),\n ]);\n },\n};\n\n/**\n * Mirrors IEvsSensorsService (`Evs.instance().sensors()`).\n * Remember to disable sensors and unsubscribe events when no longer needed —\n * the SDK docs call this out explicitly for battery/bandwidth reasons.\n */\nexport const EvsSensors = {\n enableTouch(enabled: boolean) {\n return EvsKitNative.enableTouch(enabled);\n },\n enableAmbient(enabled: boolean) {\n return EvsKitNative.enableAmbient(enabled);\n },\n enableGyro(enabled: boolean) {\n return EvsKitNative.enableGyro(enabled);\n },\n enableMagnetometer(enabled: boolean) {\n return EvsKitNative.enableMagnetometer(enabled);\n },\n enableAccelerometer(enabled: boolean) {\n return EvsKitNative.enableAccelerometer(enabled);\n },\n /** Enables Gyro + Magnetometer + Accelerometer and starts the fusion algorithm. */\n enableFusion(enabled: boolean) {\n return EvsKitNative.enableSensorsFusion(enabled);\n },\n};\n\n/** Mirrors IEvsAuthService (`Evs.instance().auth()`). Usually unnecessary — see API Keys docs. */\nexport const EvsAuth = {\n setApiKeyName(fileNameWithoutExt: string) {\n return EvsKitNative.setApiKeyName(fileNameWithoutExt);\n },\n setApiKey(base64Key: string) {\n return EvsKitNative.setApiKey(base64Key);\n },\n};\n\n/** Mirrors `Evs.instance().showUI(...)` — the SDK's built-in portrait UI screens. */\nexport const EvsUI = {\n show(screen: EvsStockUIScreen) {\n return EvsKitNative.showUI(screen);\n },\n};\n\n/**\n * App-owned glasses screens (`Evs.instance().screens()`).\n *\n * The bridge ships no Screen subclasses. Register yours natively per app —\n * `EvsScreenRegistry.register(id, factory)` from app-owned Swift/Kotlin\n * sources (linked at prebuild by the plugin's `screens` option) — then push\n * them from JS:\n *\n * @example\n * await EvsScreens.show(\"hello-world\");\n */\nexport const EvsScreens = {\n show(id: EvsScreenId) {\n return EvsKitNative.showScreen(id);\n },\n remove(id: EvsScreenId) {\n return EvsKitNative.removeScreen(id);\n },\n removeTopmost() {\n return EvsKitNative.removeTopmostScreen();\n },\n getTopmost() {\n return EvsKitNative.getTopmostScreen();\n },\n};\n\n/** Top-level lifecycle, matching `Evs.init(context).start()` / `Evs.instance().start()`. */\nexport const EvsKit = {\n start() {\n return EvsKitNative.start();\n },\n isStarted() {\n return EvsKitNative.isStarted();\n },\n comm: EvsComm,\n glasses: EvsGlasses,\n display: EvsDisplay,\n sensors: EvsSensors,\n auth: EvsAuth,\n ui: EvsUI,\n screens: EvsScreens,\n};\n\nexport default EvsKit;\n\n// ---------------------------------------------------------------------------\n// Event subscription helpers\n// ---------------------------------------------------------------------------\n\n/** Subscribe to a single EvsKit event; call the returned function to unsubscribe. */\nexport function addEvsKitListener<K extends keyof EvsKitEvents>(\n eventName: K,\n listener: EvsKitEvents[K],\n): EventSubscription {\n return EvsKitNative.addListener(eventName, listener as any);\n}\n\n/**\n * React hook that subscribes to an EvsKit event for the lifetime of the\n * component. Pass a stable callback (e.g. via useCallback) to avoid\n * resubscribing every render.\n *\n * @example\n * useEvsKitEvent('onTouch', (direction) => {\n * if (direction === 'tap') showPopup();\n * });\n */\nexport function useEvsKitEvent<K extends keyof EvsKitEvents>(\n eventName: K,\n listener: EvsKitEvents[K],\n) {\n useEffect(() => {\n const subscription = addEvsKitListener(eventName, listener);\n return () => subscription.remove();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [eventName, listener]);\n}\n"]}
@@ -23,6 +23,12 @@ public class EvsKitModule: Module {
23
23
  private var yprEventsHandle: YprEventsHandler?
24
24
  private var quaternionEventsHandle: QuaternionEventsHandler?
25
25
 
26
+ /// Screens pushed via showScreen(id:), keyed by the JS-facing id so
27
+ /// removeScreen(id:) can resolve the native instance. Viewport covers
28
+ /// Screen, FullScreen and ArScreen.
29
+ private var activeScreens: [String: Viewport] = [:]
30
+ private var activeScreenOrder: [String] = []
31
+
26
32
  public func definition() -> ModuleDefinition {
27
33
  Name("RNEvsKit")
28
34
 
@@ -71,6 +77,58 @@ public class EvsKitModule: Module {
71
77
  }
72
78
  }
73
79
 
80
+ // --- App-owned screens (IEvsScreenService) --------------------------------
81
+ //
82
+ // The bridge ships no Screen subclasses. Apps register their own via
83
+ // EvsScreenRegistry.register(id:factory:) from app-owned Swift sources
84
+ // (linked at prebuild by the `screens` plugin option), then push them
85
+ // from JS with showScreen(id:).
86
+
87
+ AsyncFunction("showScreen") { (id: String) -> Void in
88
+ await MainActor.run { () -> Void in
89
+ guard let screen = EvsScreenRegistry.make(id: id) else {
90
+ throw Exception(
91
+ name: "EVSKIT_UNKNOWN_SCREEN",
92
+ description: "No screen registered for id '\(id)'. "
93
+ + "Register it via EvsScreenRegistry.register(id:factory:) "
94
+ + "from app-owned Swift sources."
95
+ )
96
+ }
97
+ Evs.instance().screens().addScreen(screen: screen)
98
+ if self.activeScreens[id] == nil {
99
+ self.activeScreenOrder.append(id)
100
+ }
101
+ self.activeScreens[id] = screen
102
+ }
103
+ }
104
+
105
+ AsyncFunction("removeScreen") { (id: String) -> Void in
106
+ await MainActor.run { () -> Void in
107
+ guard let screen = self.activeScreens[id] else { return }
108
+ Evs.instance().screens().removeScreen(screen: screen)
109
+ self.activeScreens.removeValue(forKey: id)
110
+ self.activeScreenOrder.removeAll { $0 == id }
111
+ }
112
+ }
113
+
114
+ AsyncFunction("removeTopmostScreen") { () -> String? in
115
+ await MainActor.run { () -> String? in
116
+ let removed: Viewport? = Evs.instance().screens().removeTopmostScreen()
117
+ guard let removed else { return nil }
118
+ if let id = self.activeScreens.first(where: { $0.value === removed })?.key {
119
+ self.activeScreens.removeValue(forKey: id)
120
+ self.activeScreenOrder.removeAll { $0 == id }
121
+ return id
122
+ }
123
+ return nil
124
+ }
125
+ }
126
+
127
+ AsyncFunction("getTopmostScreen") { () -> String? in
128
+ let topmost = Evs.instance().screens().getTopmostScreen()
129
+ return self.activeScreens.first(where: { $0.value === topmost })?.key
130
+ }
131
+
74
132
  // --- Communication (IEvsCommunicationService) -----------------------------
75
133
 
76
134
  AsyncFunction("setDeviceInfo") { (info: DeviceInfoRecord) -> Void in
@@ -0,0 +1,26 @@
1
+ import NativeEvsKit
2
+
3
+ /// Generic app-owned screen registry.
4
+ ///
5
+ /// The bridge itself ships NO Screen subclasses. Each app registers its own
6
+ /// Swift `Screen`/`FullScreen`/`ArScreen` subclasses under a string id, then
7
+ /// drives them from JS via `EvsScreens.show(id)`.
8
+ ///
9
+ /// App screens live in app source (e.g. `<app>/evs-screens/ios/*.swift`),
10
+ /// linked into the app target at prebuild by the `screens` config-plugin
11
+ /// option — so wiping `ios/` and re-running `expo prebuild` loses nothing.
12
+ public enum EvsScreenRegistry {
13
+ public static var factories: [String: () -> Viewport] = [:]
14
+
15
+ public static func register(id: String, factory: @escaping () -> Viewport) {
16
+ factories[id] = factory
17
+ }
18
+
19
+ public static func unregister(id: String) {
20
+ factories.removeValue(forKey: id)
21
+ }
22
+
23
+ public static func make(id: String) -> Viewport? {
24
+ factories[id]?()
25
+ }
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@therealitycrafters/react-native-evskit",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React Native / Expo bridge for Everysight's Maverick SDK (EvsKit + NativeEvsKit)",
5
5
  "author": "Patrick Catanzariti",
6
6
  "homepage": "https://therealitycrafters.com",
@@ -17,6 +17,8 @@
17
17
  "src",
18
18
  "android",
19
19
  "ios",
20
+ "plugin",
21
+ "app.plugin.js",
20
22
  "expo-module.config.json"
21
23
  ],
22
24
  "scripts": {
@@ -0,0 +1,110 @@
1
+ const { withXcodeProject } = require("@expo/config-plugins");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * Config plugin that adds files (e.g. sdk.key, app.key) as Xcode bundle
6
+ * resources so they are included in the app binary.
7
+ *
8
+ * Usage in app.config.ts:
9
+ * plugins: [
10
+ * ['react-native-evskit', { bundleResources: ['./sdk.key'] }],
11
+ * ]
12
+ *
13
+ * Adds PBXBuildFile + PBXFileReference entries directly to the pbxproj
14
+ * objects, avoiding the pbxFile constructor which produces undefined values
15
+ * that CocoaPods cannot parse.
16
+ */
17
+ function withEverysightBundleResources(config, { bundleResources = [] } = {}) {
18
+ if (!bundleResources.length) {
19
+ return config;
20
+ }
21
+
22
+ return withXcodeProject(config, (config) => {
23
+ const project = config.modResults;
24
+ const objects = project.hash.project.objects;
25
+
26
+ // Initialize required object sections
27
+ objects.PBXBuildFile = objects.PBXBuildFile || {};
28
+ objects.PBXFileReference = objects.PBXFileReference || {};
29
+
30
+ for (const relativePath of bundleResources) {
31
+ const resolvedPath = path.join(
32
+ config.modRequest.projectRoot,
33
+ relativePath,
34
+ );
35
+ const fileName = path.basename(resolvedPath);
36
+
37
+ // Skip if already exists
38
+ const existingRef = Object.keys(objects.PBXFileReference).find(
39
+ (key) =>
40
+ !key.endsWith("_comment") &&
41
+ objects.PBXFileReference[key].name === fileName,
42
+ );
43
+ if (existingRef) continue;
44
+
45
+ // The Xcode project lives in <projectRoot>/ios, so the file reference
46
+ // path must be relative to that directory (e.g. "../sdk.key").
47
+ const iosDir = path.join(config.modRequest.projectRoot, "ios");
48
+ const relativeToIos = path.relative(iosDir, resolvedPath);
49
+
50
+ // Create PBXFileReference
51
+ const fileRefUuid = project.generateUuid();
52
+ objects.PBXFileReference[fileRefUuid] = {
53
+ isa: "PBXFileReference",
54
+ name: `"${fileName}"`,
55
+ path: `"${relativeToIos}"`,
56
+ // Quotes must be embedded in the string: the `xcode` lib's pbxWriter
57
+ // does not add quotes itself, and angle brackets (<>) are invalid
58
+ // unquoted in the pbxproj format (e.g. `sourceTree = "<group>";`).
59
+ sourceTree: '"<group>"',
60
+ };
61
+ objects.PBXFileReference[`${fileRefUuid}_comment`] = fileName;
62
+
63
+ // Create PBXBuildFile
64
+ const buildFileUuid = project.generateUuid();
65
+ objects.PBXBuildFile[buildFileUuid] = {
66
+ isa: "PBXBuildFile",
67
+ fileRef: fileRefUuid,
68
+ };
69
+ objects.PBXBuildFile[`${buildFileUuid}_comment`] =
70
+ `${fileName} in Resources`;
71
+
72
+ // Add to Resources build phase
73
+ const target = project.getFirstTarget();
74
+ const nativeTarget = objects.PBXNativeTarget[target.uuid];
75
+ const resourcesPhaseUuid = nativeTarget.buildPhases.find((phase) =>
76
+ /Resources/i.test(phase.comment || ""),
77
+ )?.value;
78
+ if (resourcesPhaseUuid && objects.PBXResourcesBuildPhase) {
79
+ const resourcesPhase =
80
+ objects.PBXResourcesBuildPhase[resourcesPhaseUuid];
81
+ if (resourcesPhase) {
82
+ resourcesPhase.files = resourcesPhase.files || [];
83
+ resourcesPhase.files.push({
84
+ value: buildFileUuid,
85
+ comment: `${fileName} in Resources`,
86
+ });
87
+ }
88
+ }
89
+
90
+ // Add to Resources group
91
+ const resourcesGroupKey = Object.keys(objects.PBXGroup || {}).find(
92
+ (key) =>
93
+ !key.endsWith("_comment") &&
94
+ objects.PBXGroup[key].name === "Resources",
95
+ );
96
+ if (resourcesGroupKey) {
97
+ objects.PBXGroup[resourcesGroupKey].children =
98
+ objects.PBXGroup[resourcesGroupKey].children || [];
99
+ objects.PBXGroup[resourcesGroupKey].children.push({
100
+ value: fileRefUuid,
101
+ comment: fileName,
102
+ });
103
+ }
104
+ }
105
+
106
+ return config;
107
+ });
108
+ }
109
+
110
+ module.exports = withEverysightBundleResources;
@@ -0,0 +1,276 @@
1
+ const { withXcodeProject } = require("@expo/config-plugins");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ /**
6
+ * Adds Everysight's Maverick SDK (EvsKit + NativeEvsKit) as a Swift Package
7
+ * Manager dependency of the iOS app target during `expo prebuild`.
8
+ *
9
+ * Why this exists: CocoaPods can't consume SPM packages, and the SDK is
10
+ * only distributed via SPM (github.com/everysight-maverick/m1-ios-spm),
11
+ * which itself is just a `Package.swift` pointing at two prebuilt
12
+ * `EvsKit.xcframework.zip` / `NativeEvsKit.xcframework.zip` binary targets
13
+ * (confirmed by reading that repo directly — there's no source in it).
14
+ * Without this plugin you'd have to re-add the package by hand in Xcode
15
+ * every time you run a clean prebuild, which defeats the point of managed
16
+ * config.
17
+ *
18
+ * Implementation note: `@expo/config-plugins` / the underlying `xcode` npm
19
+ * package has no first-class API for SPM package references (it predates
20
+ * SPM support landing in most versions of that library), so this writes the
21
+ * `XCRemoteSwiftPackageReference` / `XCSwiftPackageProductDependency`
22
+ * pbxproj objects directly. That's inherently more fragile than a proper
23
+ * API — it's been structured to be idempotent (safe to run on every
24
+ * prebuild) and defensive about missing sections, but if a future Xcode
25
+ * project format changes the relevant object shapes, this may need
26
+ * updating. If it ever misbehaves, the fallback is adding the package by
27
+ * hand via Xcode's File > Add Packages... using the same URL/version below,
28
+ * then removing this plugin.
29
+ *
30
+ * @param {import('@expo/config-plugins').ExportedConfig} config
31
+ * @param {{ version?: string }} [options]
32
+ */
33
+ function withEverysightSPM(config, options = {}) {
34
+ const packageURL = "https://github.com/everysight-maverick/m1-ios-spm.git";
35
+ const versionRequirement = options.version ?? "2.6.1";
36
+ const products = ["EvsKit", "NativeEvsKit"];
37
+
38
+ config = withXcodeProject(config, (config) => {
39
+ const project = config.modResults;
40
+ addSwiftPackage(project, packageURL, versionRequirement, products);
41
+ addModuleSourcesToAppTarget(project, config.modRequest.projectRoot);
42
+ return config;
43
+ });
44
+
45
+ return config;
46
+ }
47
+
48
+ /**
49
+ * Registers a remote Swift package (with an exact-version requirement) and
50
+ * links the given product names against the app's first native target.
51
+ * Safe to call repeatedly — checks for an existing reference/dependency
52
+ * before adding a duplicate.
53
+ */
54
+ function addSwiftPackage(project, repositoryURL, exactVersion, productNames) {
55
+ const objects = project.hash.project.objects;
56
+ objects.XCRemoteSwiftPackageReference =
57
+ objects.XCRemoteSwiftPackageReference || {};
58
+ objects.XCSwiftPackageProductDependency =
59
+ objects.XCSwiftPackageProductDependency || {};
60
+
61
+ // --- 1. Find or create the XCRemoteSwiftPackageReference ------------------
62
+ let packageRefUuid = Object.keys(objects.XCRemoteSwiftPackageReference).find(
63
+ (key) =>
64
+ !key.endsWith("_comment") &&
65
+ objects.XCRemoteSwiftPackageReference[key].repositoryURL ===
66
+ JSON.stringify(repositoryURL),
67
+ );
68
+
69
+ if (!packageRefUuid) {
70
+ packageRefUuid = project.generateUuid();
71
+ objects.XCRemoteSwiftPackageReference[packageRefUuid] = {
72
+ isa: "XCRemoteSwiftPackageReference",
73
+ repositoryURL: JSON.stringify(repositoryURL),
74
+ requirement: {
75
+ kind: "exactVersion",
76
+ version: exactVersion,
77
+ },
78
+ };
79
+ objects.XCRemoteSwiftPackageReference[`${packageRefUuid}_comment`] =
80
+ 'XCRemoteSwiftPackageReference "m1-ios-spm"';
81
+
82
+ // Register the reference on the project object itself
83
+ // (Xcode reads packageReferences off PBXProject, not just the target).
84
+ const rootObjectUuid = project.hash.project.rootObject;
85
+ const rootObject = objects.PBXProject[rootObjectUuid];
86
+ rootObject.packageReferences = rootObject.packageReferences || [];
87
+ if (
88
+ !rootObject.packageReferences.some((ref) => ref.value === packageRefUuid)
89
+ ) {
90
+ rootObject.packageReferences.push({
91
+ value: packageRefUuid,
92
+ comment: 'XCRemoteSwiftPackageReference "m1-ios-spm"',
93
+ });
94
+ }
95
+ }
96
+
97
+ // --- 2. Find or create a XCSwiftPackageProductDependency per product ------
98
+ const target = project.getFirstTarget();
99
+ const nativeTarget = objects.PBXNativeTarget[target.uuid];
100
+ nativeTarget.packageProductDependencies =
101
+ nativeTarget.packageProductDependencies || [];
102
+
103
+ // Also link SPM products to the pod target (RNEvsKit) so that the Swift
104
+ // module can import EvsKit and NativeEvsKit. The pod is compiled as a
105
+ // separate target and needs the SPM products linked to it.
106
+ const podTargetUuid = Object.keys(objects.PBXNativeTarget).find(
107
+ (key) =>
108
+ !key.endsWith("_comment") &&
109
+ objects.PBXNativeTarget[key].productReference &&
110
+ objects.PBXNativeTarget[key].buildConfigurationList,
111
+ );
112
+ const podTarget = podTargetUuid
113
+ ? objects.PBXNativeTarget[podTargetUuid]
114
+ : null;
115
+
116
+ const frameworksBuildPhaseUuid = nativeTarget.buildPhases.find((phase) =>
117
+ /Frameworks/i.test(phase.comment || ""),
118
+ )?.value;
119
+ const frameworksPhase = frameworksBuildPhaseUuid
120
+ ? objects.PBXFrameworksBuildPhase[frameworksBuildPhaseUuid]
121
+ : undefined;
122
+
123
+ for (const productName of productNames) {
124
+ let productDepUuid = Object.keys(
125
+ objects.XCSwiftPackageProductDependency,
126
+ ).find(
127
+ (key) =>
128
+ !key.endsWith("_comment") &&
129
+ objects.XCSwiftPackageProductDependency[key].productName ===
130
+ productName,
131
+ );
132
+
133
+ if (!productDepUuid) {
134
+ productDepUuid = project.generateUuid();
135
+ objects.XCSwiftPackageProductDependency[productDepUuid] = {
136
+ isa: "XCSwiftPackageProductDependency",
137
+ package: packageRefUuid,
138
+ productName,
139
+ };
140
+ objects.XCSwiftPackageProductDependency[`${productDepUuid}_comment`] =
141
+ productName;
142
+ }
143
+
144
+ // Add to app target
145
+ if (
146
+ !nativeTarget.packageProductDependencies.some(
147
+ (dep) => dep.value === productDepUuid,
148
+ )
149
+ ) {
150
+ nativeTarget.packageProductDependencies.push({
151
+ value: productDepUuid,
152
+ comment: productName,
153
+ });
154
+ }
155
+
156
+ // Also add to pod target (RNEvsKit) so the Swift module can import these
157
+ if (podTarget) {
158
+ podTarget.packageProductDependencies =
159
+ podTarget.packageProductDependencies || [];
160
+ if (
161
+ !podTarget.packageProductDependencies.some(
162
+ (dep) => dep.value === productDepUuid,
163
+ )
164
+ ) {
165
+ podTarget.packageProductDependencies.push({
166
+ value: productDepUuid,
167
+ comment: productName,
168
+ });
169
+ }
170
+ }
171
+
172
+ // Link the product into the app binary via the Frameworks build phase,
173
+ // same as Xcode would if you added it through the UI.
174
+ if (frameworksPhase) {
175
+ objects.PBXBuildFile = objects.PBXBuildFile || {};
176
+ const alreadyLinked = Object.keys(objects.PBXBuildFile).some(
177
+ (key) =>
178
+ !key.endsWith("_comment") &&
179
+ objects.PBXBuildFile[key].productRef === productDepUuid,
180
+ );
181
+
182
+ if (!alreadyLinked) {
183
+ const buildFileUuid = project.generateUuid();
184
+ objects.PBXBuildFile[buildFileUuid] = {
185
+ isa: "PBXBuildFile",
186
+ productRef: productDepUuid,
187
+ productRef_comment: productName,
188
+ };
189
+ objects.PBXBuildFile[`${buildFileUuid}_comment`] =
190
+ `${productName} in Frameworks`;
191
+
192
+ frameworksPhase.files = frameworksPhase.files || [];
193
+ frameworksPhase.files.push({
194
+ value: buildFileUuid,
195
+ comment: `${productName} in Frameworks`,
196
+ });
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Adds the EvsKitModule.swift source files directly to the app target's
204
+ * Sources build phase so they can import SPM products (EvsKit, NativeEvsKit).
205
+ * CocoaPods targets cannot consume SPM packages, so the podspec has empty
206
+ * source_files and the actual sources are compiled via the app target instead.
207
+ */
208
+ function addModuleSourcesToAppTarget(project, projectRoot) {
209
+ const objects = project.hash.project.objects;
210
+ const target = project.getFirstTarget();
211
+ const nativeTarget = objects.PBXNativeTarget[target.uuid];
212
+
213
+ // Find the Sources build phase
214
+ const sourcesPhaseUuid = nativeTarget.buildPhases.find((phase) =>
215
+ /Sources/i.test(phase.comment || ""),
216
+ )?.value;
217
+ const sourcesPhase = sourcesPhaseUuid
218
+ ? objects.PBXSourcesBuildPhase[sourcesPhaseUuid]
219
+ : undefined;
220
+ if (!sourcesPhase) return;
221
+
222
+ const moduleDir = path.join(path.dirname(__dirname), "ios");
223
+ const sourceFiles = fs
224
+ .readdirSync(moduleDir)
225
+ .filter((f) => f.endsWith(".swift"));
226
+
227
+ // The Xcode project lives in <projectRoot>/ios, so the file reference path
228
+ // must be relative to that directory (e.g. "../../react-native-evskit/ios").
229
+ const iosDir = path.join(projectRoot, "ios");
230
+ const moduleDirRelativeToIos = path.relative(iosDir, moduleDir);
231
+
232
+ for (const fileName of sourceFiles) {
233
+ // Check if already added
234
+ const alreadyAdded = Object.keys(objects.PBXBuildFile || {}).some(
235
+ (key) =>
236
+ !key.endsWith("_comment") &&
237
+ objects.PBXBuildFile[key].fileRef &&
238
+ objects.PBXBuildFile[key].fileRef_comment === fileName,
239
+ );
240
+ if (alreadyAdded) continue;
241
+
242
+ // Add file reference
243
+ const fileRefUuid = project.generateUuid();
244
+ objects.PBXFileReference = objects.PBXFileReference || {};
245
+ objects.PBXFileReference[fileRefUuid] = {
246
+ isa: "PBXFileReference",
247
+ explicitFileType: "sourcecode.swift",
248
+ fileEncoding: 4,
249
+ name: fileName,
250
+ path: path.join(moduleDirRelativeToIos, fileName),
251
+ // Quotes must be embedded in the string: the `xcode` lib's pbxWriter
252
+ // does not add quotes itself, and angle brackets (<>) are invalid
253
+ // unquoted in the pbxproj format (e.g. `sourceTree = "<group>";`).
254
+ sourceTree: '"<group>"',
255
+ };
256
+ objects.PBXFileReference[`${fileRefUuid}_comment`] = fileName;
257
+
258
+ // Add build file
259
+ const buildFileUuid = project.generateUuid();
260
+ objects.PBXBuildFile = objects.PBXBuildFile || {};
261
+ objects.PBXBuildFile[buildFileUuid] = {
262
+ isa: "PBXBuildFile",
263
+ fileRef: fileRefUuid,
264
+ };
265
+ objects.PBXBuildFile[`${buildFileUuid}_comment`] = `${fileName} in Sources`;
266
+
267
+ // Add to Sources build phase
268
+ sourcesPhase.files = sourcesPhase.files || [];
269
+ sourcesPhase.files.push({
270
+ value: buildFileUuid,
271
+ comment: `${fileName} in Sources`,
272
+ });
273
+ }
274
+ }
275
+
276
+ module.exports = withEverysightSPM;
@@ -0,0 +1,256 @@
1
+ const { withAppBuildGradle, withXcodeProject } = require("@expo/config-plugins");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Links app-owned Everysight glasses screens into the native build and
7
+ // generates their registration with the bridge's EvsScreenRegistry.
8
+ //
9
+ // App screens live in app source (committed), e.g.:
10
+ //
11
+ // <app>/evs-screens/ios/HelloWorldScreen.swift
12
+ // <app>/evs-screens/android/HelloWorldScreen.kt
13
+ //
14
+ // Usage in app.config.ts:
15
+ //
16
+ // plugins: [
17
+ // ['react-native-evskit', {
18
+ // bundleResources: ['./sdk.key'],
19
+ // screens: {
20
+ // ios: './evs-screens/ios',
21
+ // android: './evs-screens/android',
22
+ // // id -> class name. Swift: class name in the app target.
23
+ // // Kotlin: fully-qualified class name.
24
+ // register: {
25
+ // 'hello-world': {
26
+ // ios: 'HelloWorldScreen',
27
+ // android: 'com.sampleevskitproject.screens.HelloWorldScreen',
28
+ // },
29
+ // },
30
+ // },
31
+ // }],
32
+ // ]
33
+ //
34
+ // On `expo prebuild` this:
35
+ // - iOS: adds each app-owned .swift file to the app target's Sources phase
36
+ // (path relative to ios/, e.g. "../evs-screens/ios/HelloWorldScreen.swift"),
37
+ // and writes a generated `ios/EvsAppScreens.generated.swift` that registers
38
+ // every id. The generated file self-registers via top-level statements, so
39
+ // no AppDelegate changes are needed.
40
+ // - Android: registers the app-owned dir (plus a generated dir) as extra
41
+ // sourceSets java srcDirs, and writes a generated registrar object. The
42
+ // bridge loads it via Class.forName on startup (no-op when absent).
43
+ //
44
+ // The source of truth is the committed evs-screens/ folder plus the config,
45
+ // and the plugin re-runs on every prebuild, so wiping ios/ + android/ loses
46
+ // nothing. Push from JS with `await EvsScreens.show("hello-world")`.
47
+ // ---------------------------------------------------------------------------
48
+ const GENERATED_IOS_FILE = "EvsAppScreens.generated.swift";
49
+ const GENERATED_ANDROID_DIR = "evs-screens-generated";
50
+ const GENERATED_ANDROID_PACKAGE = "evs.screens.generated";
51
+ const GENERATED_ANDROID_CLASS = "EvsAppScreensGenerated";
52
+
53
+ function withEvsScreens(config, { screens = {} } = {}) {
54
+ const { ios: iosDir, android: androidDir, register = {} } = screens;
55
+ if (!iosDir && !androidDir) {
56
+ return config;
57
+ }
58
+ if (iosDir) {
59
+ config = withEvsIosScreens(config, iosDir, register);
60
+ }
61
+ if (androidDir) {
62
+ config = withEvsAndroidScreens(config, androidDir, register);
63
+ }
64
+ return config;
65
+ }
66
+
67
+ function listSourceFiles(absDir, ext) {
68
+ if (!fs.existsSync(absDir) || !fs.statSync(absDir).isDirectory()) {
69
+ return [];
70
+ }
71
+ return fs.readdirSync(absDir).filter((f) => f.endsWith(ext)).sort();
72
+ }
73
+
74
+ function toPosix(p) {
75
+ return p.split(path.sep).join("/");
76
+ }
77
+
78
+ function sanitize(id) {
79
+ return id.replace(/[^A-Za-z0-9_]/g, "_");
80
+ }
81
+
82
+ module.exports = withEvsScreens;
83
+ function generatedIosContents(register) {
84
+ const lines = [
85
+ "// Generated by react-native-evskit — do not edit.",
86
+ "// Registers app-owned screens (see `screens.register` in app.config).",
87
+ "",
88
+ "import NativeEvsKit",
89
+ "import RNEvsKit",
90
+ "",
91
+ ];
92
+ for (const [id, entry] of Object.entries(register)) {
93
+ const className = typeof entry === "string" ? entry : entry.ios;
94
+ if (!className) {
95
+ continue;
96
+ }
97
+ lines.push(
98
+ `private let _evsRegister_${sanitize(id)}: Void = {`,
99
+ ` EvsScreenRegistry.register(id: ${JSON.stringify(id)}) { ${className}() }`,
100
+ `}()`,
101
+ "",
102
+ );
103
+ }
104
+ if (!Object.keys(register).length) {
105
+ lines.push("// No screens registered.", "");
106
+ }
107
+ return lines.join("\n");
108
+ }
109
+
110
+ function addSwiftFileToSources(project, objects, sourcesPhase, fileName, relativePath) {
111
+ const alreadyAdded = Object.keys(objects.PBXBuildFile).some(
112
+ (key) =>
113
+ !key.endsWith("_comment") &&
114
+ objects.PBXBuildFile[key].fileRef_comment === fileName,
115
+ );
116
+ if (alreadyAdded) {
117
+ return;
118
+ }
119
+ const fileRefUuid = project.generateUuid();
120
+ objects.PBXFileReference[fileRefUuid] = {
121
+ isa: "PBXFileReference",
122
+ explicitFileType: "sourcecode.swift",
123
+ fileEncoding: 4,
124
+ name: fileName,
125
+ path: relativePath,
126
+ // Quotes must be embedded: the `xcode` lib's pbxWriter does not add
127
+ // quotes itself, and angle brackets (<>) are invalid unquoted.
128
+ sourceTree: '"<group>"',
129
+ };
130
+ objects.PBXFileReference[`${fileRefUuid}_comment`] = fileName;
131
+ const buildFileUuid = project.generateUuid();
132
+ objects.PBXBuildFile[buildFileUuid] = {
133
+ isa: "PBXBuildFile",
134
+ fileRef: fileRefUuid,
135
+ fileRef_comment: fileName,
136
+ };
137
+ objects.PBXBuildFile[`${buildFileUuid}_comment`] =
138
+ `${fileName} in Sources`;
139
+ sourcesPhase.files = sourcesPhase.files || [];
140
+ sourcesPhase.files.push({
141
+ value: buildFileUuid,
142
+ comment: `${fileName} in Sources`,
143
+ });
144
+ }
145
+
146
+ function withEvsIosScreens(config, iosScreensDir, register) {
147
+ return withXcodeProject(config, (config) => {
148
+ const projectRoot = config.modRequest.projectRoot;
149
+ const absDir = path.join(projectRoot, iosScreensDir);
150
+ const files = listSourceFiles(absDir, ".swift");
151
+ const project = config.modResults;
152
+ const objects = project.hash.project.objects;
153
+ objects.PBXBuildFile = objects.PBXBuildFile || {};
154
+ objects.PBXFileReference = objects.PBXFileReference || {};
155
+ const target = project.getFirstTarget();
156
+ const nativeTarget = objects.PBXNativeTarget[target.uuid];
157
+ const sourcesPhaseUuid = nativeTarget.buildPhases.find((phase) =>
158
+ /Sources/i.test(phase.comment || ""),
159
+ )?.value;
160
+ const sourcesPhase = sourcesPhaseUuid
161
+ ? objects.PBXSourcesBuildPhase[sourcesPhaseUuid]
162
+ : undefined;
163
+ if (!sourcesPhase) {
164
+ return config;
165
+ }
166
+ // The Xcode project lives in <projectRoot>/ios, so file reference paths
167
+ // must be relative to that directory (e.g. "../evs-screens/ios/X.swift").
168
+ const iosDir = path.join(projectRoot, "ios");
169
+ for (const fileName of files) {
170
+ addSwiftFileToSources(
171
+ project,
172
+ objects,
173
+ sourcesPhase,
174
+ fileName,
175
+ toPosix(path.join(path.relative(iosDir, absDir), fileName)),
176
+ );
177
+ }
178
+ // Generated registrar — written into the (regenerable) ios/ dir and
179
+ // linked the same way. Top-level `let` runs at binary load, so no
180
+ // AppDelegate changes are needed.
181
+ if (Object.keys(register).length) {
182
+ const generatedPath = path.join(iosDir, GENERATED_IOS_FILE);
183
+ fs.writeFileSync(generatedPath, generatedIosContents(register));
184
+ addSwiftFileToSources(
185
+ project,
186
+ objects,
187
+ sourcesPhase,
188
+ GENERATED_IOS_FILE,
189
+ GENERATED_IOS_FILE,
190
+ );
191
+ }
192
+ return config;
193
+ });
194
+ }
195
+ function generatedAndroidContents(register) {
196
+ const lines = [
197
+ `package ${GENERATED_ANDROID_PACKAGE}`,
198
+ "",
199
+ "// Generated by react-native-evskit — do not edit.",
200
+ "// Registers app-owned screens (see `screens.register` in app.config).",
201
+ "// Loaded via Class.forName from the bridge module's OnCreate.",
202
+ "",
203
+ "import expo.modules.evskit.EvsScreenRegistry",
204
+ "",
205
+ `object ${GENERATED_ANDROID_CLASS} {`,
206
+ " init {",
207
+ ];
208
+ for (const [id, entry] of Object.entries(register)) {
209
+ const fqcn = typeof entry === "string" ? entry : entry.android;
210
+ if (!fqcn) {
211
+ continue;
212
+ }
213
+ const simple = fqcn.split(".").pop();
214
+ lines.push(`import ${fqcn}`);
215
+ lines.push(` EvsScreenRegistry.register(${JSON.stringify(id)}) { ${simple}() }`);
216
+ }
217
+ lines.push(" }", "}");
218
+ return lines.join("\n");
219
+ }
220
+
221
+ function withEvsAndroidScreens(config, androidScreensDir, register) {
222
+ const projectRoot = config.modRequest.projectRoot;
223
+ // Write the generated registrar into a disposable dir under android/ and
224
+ // compile it via an extra srcDir (same mechanism as the app-owned dir).
225
+ if (Object.keys(register).length) {
226
+ const genDir = path.join(projectRoot, "android", GENERATED_ANDROID_DIR);
227
+ fs.mkdirSync(genDir, { recursive: true });
228
+ fs.writeFileSync(
229
+ path.join(genDir, `${GENERATED_ANDROID_CLASS}.kt`),
230
+ generatedAndroidContents(register),
231
+ );
232
+ }
233
+ // Register the app-owned screens dir (+ generated dir) as extra java
234
+ // srcDirs so their .kt files compile into the app.
235
+ config = withAppBuildGradle(config, (config) => {
236
+ const marker = "// react-native-evskit: app-owned glasses screens";
237
+ if (config.modResults.contents.includes(marker)) {
238
+ return config;
239
+ }
240
+ // Paths relative to <projectRoot>/android/app.
241
+ const appDir = path.join(projectRoot, "android", "app");
242
+ const srcDirs = [toPosix(path.relative(appDir, path.join(projectRoot, androidScreensDir)))];
243
+ if (Object.keys(register).length) {
244
+ srcDirs.push(
245
+ toPosix(path.relative(appDir, path.join(projectRoot, "android", GENERATED_ANDROID_DIR))),
246
+ );
247
+ }
248
+ const srcDirsList = srcDirs.map((d) => ` '${d}'`).join(",\n");
249
+ config.modResults.contents = config.modResults.contents.replace(
250
+ /android\s*\{/,
251
+ `android {\n${marker}\n sourceSets {\n main {\n java.srcDirs += [\n${srcDirsList}\n ]\n }\n }`,
252
+ );
253
+ return config;
254
+ });
255
+ return config;
256
+ }
@@ -95,6 +95,23 @@ export interface EvsQuaternionSample extends EvsSensorSample<EvsQuaternionData>
95
95
 
96
96
  export type EvsStockUIScreen = "configure" | "adjust" | "calibrate";
97
97
 
98
+ // ---------------------------------------------------------------------------
99
+ // App-owned screens (IEvsScreenService via EvsScreenRegistry)
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * String id of an app-owned native screen.
104
+ *
105
+ * The bridge ships no Screen subclasses. Each app registers its own
106
+ * Swift/Kotlin `Screen`/`FullScreen`/`ArScreen` subclasses via
107
+ * `EvsScreenRegistry.register(id, factory)` (iOS) /
108
+ * `EvsScreenRegistry.register(id, factory)` (Android) from app-owned
109
+ * sources — e.g. `<app>/evs-screens/ios/*.swift` linked at prebuild by the
110
+ * bridge config plugin's `screens` option — then pushes them with
111
+ * `EvsScreens.show(id)`.
112
+ */
113
+ export type EvsScreenId = string;
114
+
98
115
  // ---------------------------------------------------------------------------
99
116
  // Auth (IEvsAuthService) / lifecycle events (IEvsAppEvents)
100
117
  // ---------------------------------------------------------------------------
@@ -48,6 +48,20 @@ declare class EvsKitNativeModule extends NativeModule<EvsKitEvents> {
48
48
  setRenderingCenterX(dx: number): Promise<void>;
49
49
  setRenderingCenterY(dy: number): Promise<void>;
50
50
 
51
+ // --- App-owned screens (IEvsScreenService via EvsScreenRegistry) ------------
52
+ /**
53
+ * Push an app-registered screen onto the glasses screen stack.
54
+ * Rejects with EVSKIT_SHOW_SCREEN_FAILED / EVSKIT_UNKNOWN_SCREEN when no
55
+ * factory is registered for `id`.
56
+ */
57
+ showScreen(id: string): Promise<void>;
58
+ /** Remove a previously shown screen by id. No-op when unknown. */
59
+ removeScreen(id: string): Promise<void>;
60
+ /** Pop the topmost screen; resolves with its id, or null when empty/untracked. */
61
+ removeTopmostScreen(): Promise<string | null>;
62
+ /** Resolve the topmost tracked screen id, or null. */
63
+ getTopmostScreen(): Promise<string | null>;
64
+
51
65
  // --- Sensors (IEvsSensorsService) -------------------------------------------
52
66
  enableTouch(enabled: boolean): Promise<void>;
53
67
  enableAmbient(enabled: boolean): Promise<void>;
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  EvsDeviceInfo,
7
7
  EvsGlassesInfo,
8
8
  EvsKitEvents,
9
+ EvsScreenId,
9
10
  EvsStockUIScreen,
10
11
  } from "./EvsKit.types";
11
12
  import EvsKitNative from "./EvsKitModule";
@@ -120,6 +121,32 @@ export const EvsUI = {
120
121
  },
121
122
  };
122
123
 
124
+ /**
125
+ * App-owned glasses screens (`Evs.instance().screens()`).
126
+ *
127
+ * The bridge ships no Screen subclasses. Register yours natively per app —
128
+ * `EvsScreenRegistry.register(id, factory)` from app-owned Swift/Kotlin
129
+ * sources (linked at prebuild by the plugin's `screens` option) — then push
130
+ * them from JS:
131
+ *
132
+ * @example
133
+ * await EvsScreens.show("hello-world");
134
+ */
135
+ export const EvsScreens = {
136
+ show(id: EvsScreenId) {
137
+ return EvsKitNative.showScreen(id);
138
+ },
139
+ remove(id: EvsScreenId) {
140
+ return EvsKitNative.removeScreen(id);
141
+ },
142
+ removeTopmost() {
143
+ return EvsKitNative.removeTopmostScreen();
144
+ },
145
+ getTopmost() {
146
+ return EvsKitNative.getTopmostScreen();
147
+ },
148
+ };
149
+
123
150
  /** Top-level lifecycle, matching `Evs.init(context).start()` / `Evs.instance().start()`. */
124
151
  export const EvsKit = {
125
152
  start() {
@@ -134,6 +161,7 @@ export const EvsKit = {
134
161
  sensors: EvsSensors,
135
162
  auth: EvsAuth,
136
163
  ui: EvsUI,
164
+ screens: EvsScreens,
137
165
  };
138
166
 
139
167
  export default EvsKit;