@glassly/engine 0.1.0-dev.61 → 0.1.0-dev.89
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 +6 -2
- package/build/engine.d.ts +7 -2
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +3 -0
- package/build/engine.js.map +1 -1
- package/build/facades/display.d.ts +10 -3
- package/build/facades/display.d.ts.map +1 -1
- package/build/facades/display.js +11 -1
- package/build/facades/display.js.map +1 -1
- package/build/facades/displayMirror.d.ts +2 -6
- package/build/facades/displayMirror.d.ts.map +1 -1
- package/build/facades/displayMirror.js +7 -14
- package/build/facades/displayMirror.js.map +1 -1
- package/build/facades/glasses.d.ts +1 -0
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +2 -0
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/ota.d.ts +3 -1
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +4 -2
- package/build/facades/ota.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +12 -2
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +30 -10
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +6 -1
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +136 -14
- package/build/react/MentraLiveOtaFlow.js.map +1 -1
- package/build/react/useMentraLiveOta.d.ts +5 -1
- package/build/react/useMentraLiveOta.d.ts.map +1 -1
- package/build/react/useMentraLiveOta.js +135 -58
- package/build/react/useMentraLiveOta.js.map +1 -1
- package/build/services/CustomDashboardService.d.ts +11 -10
- package/build/services/CustomDashboardService.d.ts.map +1 -1
- package/build/services/CustomDashboardService.js +70 -69
- package/build/services/CustomDashboardService.js.map +1 -1
- package/build/services/DashboardCoordinator.d.ts +26 -0
- package/build/services/DashboardCoordinator.d.ts.map +1 -0
- package/build/services/DashboardCoordinator.js +125 -0
- package/build/services/DashboardCoordinator.js.map +1 -0
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +5 -7
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/DisplayProcessor.d.ts +3 -3
- package/build/services/DisplayProcessor.js +3 -3
- package/build/services/DisplayProcessor.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +12 -2
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +68 -14
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/OtaInstallCoordinator.d.ts +2 -0
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +21 -1
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/PhoneNotificationsSync.d.ts +13 -0
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +101 -9
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +1 -1
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +9 -6
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +5 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/StreamLifecycleController.d.ts +4 -0
- package/build/services/StreamLifecycleController.d.ts.map +1 -1
- package/build/services/StreamLifecycleController.js +4 -0
- package/build/services/StreamLifecycleController.js.map +1 -1
- package/build/services/effectiveCapabilities.d.ts +1 -1
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +9 -4
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/iosNotificationAppCatalog.d.ts +13 -0
- package/build/services/iosNotificationAppCatalog.d.ts.map +1 -0
- package/build/services/iosNotificationAppCatalog.js +28 -0
- package/build/services/iosNotificationAppCatalog.js.map +1 -0
- package/build/services/otaInstallPolicy.d.ts +4 -0
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +4 -0
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/stores/display.d.ts +4 -4
- package/build/stores/display.d.ts.map +1 -1
- package/build/stores/display.js +12 -36
- package/build/stores/display.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +2 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +53 -2
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g2.js +2 -2
- package/build/types/capabilities/even-realities-g2.js.map +1 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +1 -0
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +1 -1
- package/build/types/index.js.map +1 -1
- package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
- package/build/utils/display/composer/ColumnComposer.js +1 -1
- package/build/utils/display/composer/ColumnComposer.js.map +1 -1
- package/package.json +8 -7
- package/src/engine.ts +3 -0
- package/src/facades/display.ts +11 -1
- package/src/facades/displayMirror.ts +7 -15
- package/src/facades/glasses.ts +2 -0
- package/src/facades/ota.ts +4 -2
- package/src/facades/phoneNotifications.ts +32 -10
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +2 -1
- package/src/react/MentraLiveOtaFlow.tsx +226 -41
- package/src/react/useMentraLiveOta.ts +146 -60
- package/src/services/CustomDashboardService.ts +67 -66
- package/src/services/DashboardCoordinator.ts +121 -0
- package/src/services/DeviceEventRouter.ts +5 -7
- package/src/services/DisplayProcessor.ts +3 -3
- package/src/services/LocalDisplayManager.ts +68 -16
- package/src/services/OtaInstallCoordinator.ts +21 -0
- package/src/services/PhoneNotificationsSync.ts +125 -9
- package/src/services/PhonePhotoCoordinator.ts +1 -1
- package/src/services/PhoneStreamCoordinator.ts +10 -6
- package/src/services/SceneRenderer.ts +4 -0
- package/src/services/StreamLifecycleController.ts +4 -0
- package/src/services/effectiveCapabilities.ts +9 -4
- package/src/services/iosNotificationAppCatalog.ts +39 -0
- package/src/services/otaInstallPolicy.ts +4 -0
- package/src/stores/display.ts +16 -48
- package/src/stores/glasses.ts +2 -0
- package/src/stores/settings.ts +53 -2
- package/src/types/applet.ts +1 -1
- package/src/types/capabilities/even-realities-g2.ts +3 -2
- package/src/types/hardware.ts +1 -0
- package/src/types/index.ts +1 -1
- package/src/utils/display/composer/ColumnComposer.ts +1 -1
package/build/types/index.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* are re-exported from @glassly/miniapp, their canonical owner. */
|
|
3
3
|
// Enums (runtime values)
|
|
4
4
|
export { HardwareType, HardwareRequirementLevel, DeviceTypes, ControllerTypes } from "./enums";
|
|
5
|
-
export { HARDWARE_CAPABILITIES, getModelCapabilities, simulatedGlasses, evenRealitiesG1, evenRealitiesG2, mentraLive, vuzixZ100, mentraDisplay,
|
|
5
|
+
export { HARDWARE_CAPABILITIES, getModelCapabilities, simulatedGlasses, evenRealitiesG1, evenRealitiesG2, mentraLive, nimo, vuzixZ100, mentraDisplay, } from "./hardware";
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
package/build/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;mEACmE;AAEnE,yBAAyB;AACzB,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,eAAe,EAAC,MAAM,SAAS,CAAA;AAgB5F,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;mEACmE;AAEnE,yBAAyB;AACzB,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAE,WAAW,EAAE,eAAe,EAAC,MAAM,SAAS,CAAA;AAgB5F,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,UAAU,EACV,IAAI,EACJ,SAAS,EACT,aAAa,GACd,MAAM,YAAY,CAAA","sourcesContent":["/** Engine capability types and profiles. Miniapp manifest requirement types\n * are re-exported from @glassly/miniapp, their canonical owner. */\n\n// Enums (runtime values)\nexport {HardwareType, HardwareRequirementLevel, DeviceTypes, ControllerTypes} from \"./enums\"\n\n// Hardware types\nexport type {\n HardwareRequirement,\n CameraCapabilities,\n DisplayCapabilities,\n MicrophoneCapabilities,\n SpeakerCapabilities,\n IMUCapabilities,\n ButtonCapabilities,\n LightCapabilities,\n PowerCapabilities,\n Capabilities,\n} from \"./hardware\"\n\nexport {\n HARDWARE_CAPABILITIES,\n getModelCapabilities,\n simulatedGlasses,\n evenRealitiesG1,\n evenRealitiesG2,\n mentraLive,\n nimo,\n vuzixZ100,\n mentraDisplay,\n} from \"./hardware\"\n\n// Applet types\nexport type {AppletType, AppPermissionType, AppletPermission, AppletInterface, ClientApp} from \"./applet\"\n"]}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 2. Merges columns line-by-line with pixel-precise space padding
|
|
12
12
|
* 3. Returns a single pre-composed string ready for native to chunk & send
|
|
13
13
|
*
|
|
14
|
-
* @see
|
|
14
|
+
* @see notes/miniapp-display-api-redesign.md for the current display design
|
|
15
15
|
*/
|
|
16
16
|
import type { DisplayProfile } from "../profiles/types";
|
|
17
17
|
import type { BreakMode } from "../wrapper/types";
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 2. Merges columns line-by-line with pixel-precise space padding
|
|
12
12
|
* 3. Returns a single pre-composed string ready for native to chunk & send
|
|
13
13
|
*
|
|
14
|
-
* @see
|
|
14
|
+
* @see notes/miniapp-display-api-redesign.md for the current display design
|
|
15
15
|
*/
|
|
16
16
|
import { TextMeasurer } from "../measurer/TextMeasurer";
|
|
17
17
|
import { TextWrapper } from "../wrapper/TextWrapper";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ColumnComposer.js","sourceRoot":"","sources":["../../../../src/utils/display/composer/ColumnComposer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ColumnComposer\n *\n * Handles composition of multi-column layouts like double_text_wall.\n * This is the single source of truth for column layout logic, replacing\n * the duplicate implementations in native iOS (G1Text.swift) and\n * Android (G1Text.kt, G1.java).\n *\n * The composer:\n * 1. Wraps each column's text to its allocated width\n * 2. Merges columns line-by-line with pixel-precise space padding\n * 3. Returns a single pre-composed string ready for native to chunk & send\n *\n * @see cloud/issues/026-mobile-display-processor for design docs\n */\n\nimport type {DisplayProfile} from \"../profiles/types\"\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {WrapOptions, BreakMode} from \"../wrapper/types\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Configuration for column layout\n */\nexport interface ColumnConfig {\n /** Width of the left column in pixels */\n leftColumnWidthPx: number\n /** X position where the right column starts in pixels */\n rightColumnStartPx: number\n /** Width of the right column in pixels (calculated from start position) */\n rightColumnWidthPx: number\n /** Maximum number of lines to display */\n maxLines: number\n /** Left margin in spaces (for indentation) */\n leftMarginSpaces?: number\n}\n\n/**\n * Options for composing double text wall\n */\nexport interface ComposeOptions {\n /** Break mode for text wrapping */\n breakMode?: BreakMode\n /** Custom column configuration (overrides profile defaults) */\n columnConfig?: Partial<ColumnConfig>\n}\n\n/**\n * Result of column composition\n */\nexport interface ComposeResult {\n /** The fully composed text with both columns merged */\n composedText: string\n /** Lines from the left column (for debugging/preview) */\n leftLines: string[]\n /** Lines from the right column (for debugging/preview) */\n rightLines: string[]\n /** The column configuration that was used */\n config: ColumnConfig\n}\n\n// =============================================================================\n// ColumnComposer Class\n// =============================================================================\n\n/**\n * Composes multi-column layouts for smart glasses displays.\n *\n * Usage:\n * ```typescript\n * const composer = new ColumnComposer(G1_PROFILE)\n * const result = composer.composeDoubleTextWall(\"Left content\", \"Right content\")\n * // result.composedText is ready to send to native SGC\n * ```\n */\nexport class ColumnComposer {\n private profile: DisplayProfile\n private measurer: TextMeasurer\n private wrapper: TextWrapper\n private spaceWidthPx: number\n\n constructor(profile: DisplayProfile, breakMode: BreakMode = \"character-no-hyphen\") {\n this.profile = profile\n this.measurer = new TextMeasurer(profile)\n this.wrapper = new TextWrapper(this.measurer, {\n breakMode,\n })\n\n // Cache space width for alignment calculations\n this.spaceWidthPx = this.measurer.measureText(\" \")\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Compose a double text wall layout.\n *\n * Takes left and right text, wraps each to their column width,\n * and merges them with pixel-precise space alignment.\n *\n * @param leftText - Text for the left column\n * @param rightText - Text for the right column\n * @param options - Optional composition settings\n * @returns Composed result with merged text and metadata\n */\n public composeDoubleTextWall(leftText: string, rightText: string, options: ComposeOptions = {}): ComposeResult {\n const config = this.getColumnConfig(options.columnConfig)\n\n // Wrap each column independently\n const leftWrapOptions: WrapOptions = {\n maxWidthPx: config.leftColumnWidthPx,\n maxLines: config.maxLines,\n }\n\n const rightWrapOptions: WrapOptions = {\n maxWidthPx: config.rightColumnWidthPx,\n maxLines: config.maxLines,\n }\n\n const leftResult = this.wrapper.wrap(leftText || \"\", leftWrapOptions)\n const rightResult = this.wrapper.wrap(rightText || \"\", rightWrapOptions)\n\n // Pad arrays to have exactly maxLines entries\n const leftLines = this.padLines(leftResult.lines, config.maxLines)\n const rightLines = this.padLines(rightResult.lines, config.maxLines)\n\n // Merge columns with space alignment\n const composedText = this.mergeColumns(leftLines, rightLines, config)\n\n return {\n composedText,\n leftLines,\n rightLines,\n config,\n }\n }\n\n /**\n * Get the default column configuration for the current profile.\n */\n public getDefaultColumnConfig(): ColumnConfig {\n return this.getColumnConfig()\n }\n\n /**\n * Update the break mode for text wrapping.\n */\n public setBreakMode(breakMode: BreakMode): void {\n this.wrapper = new TextWrapper(this.measurer, {\n breakMode,\n })\n }\n\n // ===========================================================================\n // Private Methods\n // ===========================================================================\n\n /**\n * Get column configuration, merging defaults with overrides.\n */\n private getColumnConfig(overrides?: Partial<ColumnConfig>): ColumnConfig {\n const displayWidth = this.profile.displayWidthPx\n\n // Default layout: 50% for left column, right starts at 55%\n // This matches the native G1Text implementations\n const defaults: ColumnConfig = {\n leftColumnWidthPx: Math.floor(displayWidth * 0.5),\n rightColumnStartPx: Math.floor(displayWidth * 0.55),\n rightColumnWidthPx: displayWidth - Math.floor(displayWidth * 0.55),\n maxLines: this.profile.maxLines,\n leftMarginSpaces: 0,\n }\n\n return {\n ...defaults,\n ...overrides,\n }\n }\n\n /**\n * Pad lines array to exactly `count` entries.\n */\n private padLines(lines: string[], count: number): string[] {\n const result = [...lines]\n while (result.length < count) {\n result.push(\"\")\n }\n return result.slice(0, count)\n }\n\n /**\n * Merge left and right columns with pixel-precise space alignment.\n *\n * For each line:\n * 1. Take the left column text\n * 2. Calculate its pixel width\n * 3. Calculate how many spaces needed to reach rightColumnStartPx\n * 4. Append spaces and right column text\n */\n private mergeColumns(leftLines: string[], rightLines: string[], config: ColumnConfig): string {\n const lines: string[] = []\n\n for (let i = 0; i < config.maxLines; i++) {\n const leftText = this.cleanEnspaces(leftLines[i] || \"\")\n const rightText = this.cleanEnspaces(rightLines[i] || \"\")\n\n // Calculate pixel width of left text\n const leftWidthPx = this.measurer.measureText(leftText)\n\n // Calculate spaces needed to reach the right column start\n const spacesNeeded = this.calculateSpacesForAlignment(leftWidthPx, config.rightColumnStartPx)\n\n // Build the merged line\n let line = \"\"\n\n // Add left margin if configured\n if (config.leftMarginSpaces && config.leftMarginSpaces > 0) {\n line += \" \".repeat(config.leftMarginSpaces)\n }\n\n line += leftText\n line += \" \".repeat(spacesNeeded)\n line += rightText\n\n lines.push(line)\n }\n\n return lines.join(\"\\n\")\n }\n\n /**\n * Calculate the number of spaces needed to align to a target position.\n *\n * @param currentWidthPx - Current position in pixels\n * @param targetPositionPx - Target position in pixels\n * @returns Number of space characters needed\n */\n private calculateSpacesForAlignment(currentWidthPx: number, targetPositionPx: number): number {\n const pixelsNeeded = targetPositionPx - currentWidthPx\n\n // Ensure at least one space for separation\n if (pixelsNeeded <= 0) {\n return 1\n }\n\n // Calculate spaces needed. Use Math.floor (not Math.ceil) to ensure the\n // right column never starts past rightColumnStartPx. Rounding up caused\n // the right column to start 1–5px further right than planned, shrinking\n // its usable width and causing the last character to wrap onto the next\n // line's left position.\n const spaces = Math.max(1, Math.floor(pixelsNeeded / this.spaceWidthPx))\n\n // Cap at a reasonable maximum to prevent runaway spacing\n return Math.min(spaces, 100)\n }\n\n /**\n * Remove en-space characters (U+2002) that may have been added during wrapping.\n * Native implementations do this cleanup before merging.\n */\n private cleanEnspaces(text: string): string {\n return text.replace(/\\u2002/g, \"\")\n }\n}\n\n// =============================================================================\n// Factory Function\n// =============================================================================\n\n/**\n * Create a ColumnComposer for a given profile.\n *\n * @param profile - Display profile to use\n * @param breakMode - Break mode for text wrapping (default: \"word\")\n * @returns Configured ColumnComposer instance\n */\nexport function createColumnComposer(profile: DisplayProfile, breakMode: BreakMode = \"word\"): ColumnComposer {\n return new ColumnComposer(profile, breakMode)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ColumnComposer.js","sourceRoot":"","sources":["../../../../src/utils/display/composer/ColumnComposer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA;AA+ClD,gFAAgF;AAChF,uBAAuB;AACvB,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,OAAO,cAAc;IACjB,OAAO,CAAgB;IACvB,QAAQ,CAAc;IACtB,OAAO,CAAa;IACpB,YAAY,CAAQ;IAE5B,YAAY,OAAuB,EAAE,YAAuB,qBAAqB;QAC/E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,CAAA;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC5C,SAAS;SACV,CAAC,CAAA;QAEF,+CAA+C;QAC/C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACpD,CAAC;IAED,8EAA8E;IAC9E,aAAa;IACb,8EAA8E;IAE9E;;;;;;;;;;OAUG;IACI,qBAAqB,CAAC,QAAgB,EAAE,SAAiB,EAAE,UAA0B,EAAE;QAC5F,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QAEzD,iCAAiC;QACjC,MAAM,eAAe,GAAgB;YACnC,UAAU,EAAE,MAAM,CAAC,iBAAiB;YACpC,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAA;QAED,MAAM,gBAAgB,GAAgB;YACpC,UAAU,EAAE,MAAM,CAAC,kBAAkB;YACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAA;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,eAAe,CAAC,CAAA;QACrE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,EAAE,gBAAgB,CAAC,CAAA;QAExE,8CAA8C;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;QAClE,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;QAEpE,qCAAqC;QACrC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,CAAA;QAErE,OAAO;YACL,YAAY;YACZ,SAAS;YACT,UAAU;YACV,MAAM;SACP,CAAA;IACH,CAAC;IAED;;OAEG;IACI,sBAAsB;QAC3B,OAAO,IAAI,CAAC,eAAe,EAAE,CAAA;IAC/B,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,SAAoB;QACtC,IAAI,CAAC,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC5C,SAAS;SACV,CAAC,CAAA;IACJ,CAAC;IAED,8EAA8E;IAC9E,kBAAkB;IAClB,8EAA8E;IAE9E;;OAEG;IACK,eAAe,CAAC,SAAiC;QACvD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAA;QAEhD,2DAA2D;QAC3D,iDAAiD;QACjD,MAAM,QAAQ,GAAiB;YAC7B,iBAAiB,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC;YACjD,kBAAkB,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;YACnD,kBAAkB,EAAE,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;YAClE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAC/B,gBAAgB,EAAE,CAAC;SACpB,CAAA;QAED,OAAO;YACL,GAAG,QAAQ;YACX,GAAG,SAAS;SACb,CAAA;IACH,CAAC;IAED;;OAEG;IACK,QAAQ,CAAC,KAAe,EAAE,KAAa;QAC7C,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;QACzB,OAAO,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACjB,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IAC/B,CAAC;IAED;;;;;;;;OAQG;IACK,YAAY,CAAC,SAAmB,EAAE,UAAoB,EAAE,MAAoB;QAClF,MAAM,KAAK,GAAa,EAAE,CAAA;QAE1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;YACvD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;YAEzD,qCAAqC;YACrC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;YAEvD,0DAA0D;YAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,2BAA2B,CAAC,WAAW,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAA;YAE7F,wBAAwB;YACxB,IAAI,IAAI,GAAG,EAAE,CAAA;YAEb,gCAAgC;YAChC,IAAI,MAAM,CAAC,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;gBAC3D,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;YAC7C,CAAC;YAED,IAAI,IAAI,QAAQ,CAAA;YAChB,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;YAChC,IAAI,IAAI,SAAS,CAAA;YAEjB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAED;;;;;;OAMG;IACK,2BAA2B,CAAC,cAAsB,EAAE,gBAAwB;QAClF,MAAM,YAAY,GAAG,gBAAgB,GAAG,cAAc,CAAA;QAEtD,2CAA2C;QAC3C,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,CAAA;QACV,CAAC;QAED,wEAAwE;QACxE,wEAAwE;QACxE,wEAAwE;QACxE,wEAAwE;QACxE,wBAAwB;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;QAExE,yDAAyD;QACzD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED;;;OAGG;IACK,aAAa,CAAC,IAAY;QAChC,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACpC,CAAC;CACF;AAED,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAuB,EAAE,YAAuB,MAAM;IACzF,OAAO,IAAI,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;AAC/C,CAAC","sourcesContent":["/**\n * ColumnComposer\n *\n * Handles composition of multi-column layouts like double_text_wall.\n * This is the single source of truth for column layout logic, replacing\n * the duplicate implementations in native iOS (G1Text.swift) and\n * Android (G1Text.kt, G1.java).\n *\n * The composer:\n * 1. Wraps each column's text to its allocated width\n * 2. Merges columns line-by-line with pixel-precise space padding\n * 3. Returns a single pre-composed string ready for native to chunk & send\n *\n * @see notes/miniapp-display-api-redesign.md for the current display design\n */\n\nimport type {DisplayProfile} from \"../profiles/types\"\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {WrapOptions, BreakMode} from \"../wrapper/types\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Configuration for column layout\n */\nexport interface ColumnConfig {\n /** Width of the left column in pixels */\n leftColumnWidthPx: number\n /** X position where the right column starts in pixels */\n rightColumnStartPx: number\n /** Width of the right column in pixels (calculated from start position) */\n rightColumnWidthPx: number\n /** Maximum number of lines to display */\n maxLines: number\n /** Left margin in spaces (for indentation) */\n leftMarginSpaces?: number\n}\n\n/**\n * Options for composing double text wall\n */\nexport interface ComposeOptions {\n /** Break mode for text wrapping */\n breakMode?: BreakMode\n /** Custom column configuration (overrides profile defaults) */\n columnConfig?: Partial<ColumnConfig>\n}\n\n/**\n * Result of column composition\n */\nexport interface ComposeResult {\n /** The fully composed text with both columns merged */\n composedText: string\n /** Lines from the left column (for debugging/preview) */\n leftLines: string[]\n /** Lines from the right column (for debugging/preview) */\n rightLines: string[]\n /** The column configuration that was used */\n config: ColumnConfig\n}\n\n// =============================================================================\n// ColumnComposer Class\n// =============================================================================\n\n/**\n * Composes multi-column layouts for smart glasses displays.\n *\n * Usage:\n * ```typescript\n * const composer = new ColumnComposer(G1_PROFILE)\n * const result = composer.composeDoubleTextWall(\"Left content\", \"Right content\")\n * // result.composedText is ready to send to native SGC\n * ```\n */\nexport class ColumnComposer {\n private profile: DisplayProfile\n private measurer: TextMeasurer\n private wrapper: TextWrapper\n private spaceWidthPx: number\n\n constructor(profile: DisplayProfile, breakMode: BreakMode = \"character-no-hyphen\") {\n this.profile = profile\n this.measurer = new TextMeasurer(profile)\n this.wrapper = new TextWrapper(this.measurer, {\n breakMode,\n })\n\n // Cache space width for alignment calculations\n this.spaceWidthPx = this.measurer.measureText(\" \")\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Compose a double text wall layout.\n *\n * Takes left and right text, wraps each to their column width,\n * and merges them with pixel-precise space alignment.\n *\n * @param leftText - Text for the left column\n * @param rightText - Text for the right column\n * @param options - Optional composition settings\n * @returns Composed result with merged text and metadata\n */\n public composeDoubleTextWall(leftText: string, rightText: string, options: ComposeOptions = {}): ComposeResult {\n const config = this.getColumnConfig(options.columnConfig)\n\n // Wrap each column independently\n const leftWrapOptions: WrapOptions = {\n maxWidthPx: config.leftColumnWidthPx,\n maxLines: config.maxLines,\n }\n\n const rightWrapOptions: WrapOptions = {\n maxWidthPx: config.rightColumnWidthPx,\n maxLines: config.maxLines,\n }\n\n const leftResult = this.wrapper.wrap(leftText || \"\", leftWrapOptions)\n const rightResult = this.wrapper.wrap(rightText || \"\", rightWrapOptions)\n\n // Pad arrays to have exactly maxLines entries\n const leftLines = this.padLines(leftResult.lines, config.maxLines)\n const rightLines = this.padLines(rightResult.lines, config.maxLines)\n\n // Merge columns with space alignment\n const composedText = this.mergeColumns(leftLines, rightLines, config)\n\n return {\n composedText,\n leftLines,\n rightLines,\n config,\n }\n }\n\n /**\n * Get the default column configuration for the current profile.\n */\n public getDefaultColumnConfig(): ColumnConfig {\n return this.getColumnConfig()\n }\n\n /**\n * Update the break mode for text wrapping.\n */\n public setBreakMode(breakMode: BreakMode): void {\n this.wrapper = new TextWrapper(this.measurer, {\n breakMode,\n })\n }\n\n // ===========================================================================\n // Private Methods\n // ===========================================================================\n\n /**\n * Get column configuration, merging defaults with overrides.\n */\n private getColumnConfig(overrides?: Partial<ColumnConfig>): ColumnConfig {\n const displayWidth = this.profile.displayWidthPx\n\n // Default layout: 50% for left column, right starts at 55%\n // This matches the native G1Text implementations\n const defaults: ColumnConfig = {\n leftColumnWidthPx: Math.floor(displayWidth * 0.5),\n rightColumnStartPx: Math.floor(displayWidth * 0.55),\n rightColumnWidthPx: displayWidth - Math.floor(displayWidth * 0.55),\n maxLines: this.profile.maxLines,\n leftMarginSpaces: 0,\n }\n\n return {\n ...defaults,\n ...overrides,\n }\n }\n\n /**\n * Pad lines array to exactly `count` entries.\n */\n private padLines(lines: string[], count: number): string[] {\n const result = [...lines]\n while (result.length < count) {\n result.push(\"\")\n }\n return result.slice(0, count)\n }\n\n /**\n * Merge left and right columns with pixel-precise space alignment.\n *\n * For each line:\n * 1. Take the left column text\n * 2. Calculate its pixel width\n * 3. Calculate how many spaces needed to reach rightColumnStartPx\n * 4. Append spaces and right column text\n */\n private mergeColumns(leftLines: string[], rightLines: string[], config: ColumnConfig): string {\n const lines: string[] = []\n\n for (let i = 0; i < config.maxLines; i++) {\n const leftText = this.cleanEnspaces(leftLines[i] || \"\")\n const rightText = this.cleanEnspaces(rightLines[i] || \"\")\n\n // Calculate pixel width of left text\n const leftWidthPx = this.measurer.measureText(leftText)\n\n // Calculate spaces needed to reach the right column start\n const spacesNeeded = this.calculateSpacesForAlignment(leftWidthPx, config.rightColumnStartPx)\n\n // Build the merged line\n let line = \"\"\n\n // Add left margin if configured\n if (config.leftMarginSpaces && config.leftMarginSpaces > 0) {\n line += \" \".repeat(config.leftMarginSpaces)\n }\n\n line += leftText\n line += \" \".repeat(spacesNeeded)\n line += rightText\n\n lines.push(line)\n }\n\n return lines.join(\"\\n\")\n }\n\n /**\n * Calculate the number of spaces needed to align to a target position.\n *\n * @param currentWidthPx - Current position in pixels\n * @param targetPositionPx - Target position in pixels\n * @returns Number of space characters needed\n */\n private calculateSpacesForAlignment(currentWidthPx: number, targetPositionPx: number): number {\n const pixelsNeeded = targetPositionPx - currentWidthPx\n\n // Ensure at least one space for separation\n if (pixelsNeeded <= 0) {\n return 1\n }\n\n // Calculate spaces needed. Use Math.floor (not Math.ceil) to ensure the\n // right column never starts past rightColumnStartPx. Rounding up caused\n // the right column to start 1–5px further right than planned, shrinking\n // its usable width and causing the last character to wrap onto the next\n // line's left position.\n const spaces = Math.max(1, Math.floor(pixelsNeeded / this.spaceWidthPx))\n\n // Cap at a reasonable maximum to prevent runaway spacing\n return Math.min(spaces, 100)\n }\n\n /**\n * Remove en-space characters (U+2002) that may have been added during wrapping.\n * Native implementations do this cleanup before merging.\n */\n private cleanEnspaces(text: string): string {\n return text.replace(/\\u2002/g, \"\")\n }\n}\n\n// =============================================================================\n// Factory Function\n// =============================================================================\n\n/**\n * Create a ColumnComposer for a given profile.\n *\n * @param profile - Display profile to use\n * @param breakMode - Break mode for text wrapping (default: \"word\")\n * @returns Configured ColumnComposer instance\n */\nexport function createColumnComposer(profile: DisplayProfile, breakMode: BreakMode = \"word\"): ColumnComposer {\n return new ColumnComposer(profile, breakMode)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.89",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,16 +97,17 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.1.0-dev.
|
|
101
|
-
"@glassly/cloud-client": "0.1.0-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.1.0-dev.
|
|
103
|
-
"@glassly/crust": "0.1.0-dev.
|
|
104
|
-
"@glassly/miniapp": "0.1.0-dev.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.1.0-dev.89",
|
|
101
|
+
"@glassly/cloud-client": "0.1.0-dev.89",
|
|
102
|
+
"@glassly/cloud-protocol": "0.1.0-dev.89",
|
|
103
|
+
"@glassly/crust": "0.1.0-dev.89",
|
|
104
|
+
"@glassly/miniapp": "0.1.0-dev.89",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
|
+
"react-native-marked": "8.1.1",
|
|
107
108
|
"semver": "^7.7.4",
|
|
108
109
|
"typesafe-ts": "^1.7.1",
|
|
109
|
-
"@glassly/llm-providers": "0.1.0-dev.
|
|
110
|
+
"@glassly/llm-providers": "0.1.0-dev.89"
|
|
110
111
|
},
|
|
111
112
|
"devDependencies": {
|
|
112
113
|
"@glassly/llm-providers": "workspace:*",
|
package/src/engine.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {speakers} from "./facades/speakers"
|
|
|
46
46
|
import {providerKeys} from "./facades/providerKeys"
|
|
47
47
|
import {logBuffer} from "./utils/devLogging"
|
|
48
48
|
import customDashboardService from "./services/CustomDashboardService"
|
|
49
|
+
import dashboardCoordinator from "./services/DashboardCoordinator"
|
|
49
50
|
import gestureDotsService from "./services/GestureDotsService"
|
|
50
51
|
|
|
51
52
|
export const engine = {
|
|
@@ -69,6 +70,7 @@ export const engine = {
|
|
|
69
70
|
// syncs below react to them.
|
|
70
71
|
startGlassesStatusProjection((changed) => localMiniappRuntime.forwardEvent("glasses_connection_state", changed))
|
|
71
72
|
customDashboardService.start()
|
|
73
|
+
dashboardCoordinator.start()
|
|
72
74
|
gestureDotsService.start()
|
|
73
75
|
// Route the rest of the inbound device events (wifi/hotspot/gallery -> stores+bus,
|
|
74
76
|
// photo/stream -> coordinators, button/touch/accel/head -> miniapps, save_setting ->
|
|
@@ -151,6 +153,7 @@ export const engine = {
|
|
|
151
153
|
await safely("glasses settings sync", stopGlassesSettingsSync)
|
|
152
154
|
await safely("glasses status projection", stopGlassesStatusProjection)
|
|
153
155
|
await safely("device event router", stopDeviceEventRouter)
|
|
156
|
+
await safely("dashboard coordinator", () => dashboardCoordinator.stop())
|
|
154
157
|
await safely("custom dashboard service", () => customDashboardService.stop())
|
|
155
158
|
await safely("gesture dots service", () => gestureDotsService.stop())
|
|
156
159
|
await safely("ota service", stopOtaService)
|
package/src/facades/display.ts
CHANGED
|
@@ -5,11 +5,21 @@
|
|
|
5
5
|
* the engine DisplayProcessor / layout system, not these).
|
|
6
6
|
*/
|
|
7
7
|
import BluetoothSdk from "@glassly/bluetooth-sdk"
|
|
8
|
+
import dashboardCoordinator from "../services/DashboardCoordinator"
|
|
8
9
|
import {displayMirror} from "./displayMirror"
|
|
9
10
|
|
|
10
11
|
export const display = {
|
|
11
|
-
/** Mirrored-view read-model (current/onMirror
|
|
12
|
+
/** Mirrored-view read-model (current/onMirror). */
|
|
12
13
|
mirror: displayMirror,
|
|
14
|
+
/** Phone-composed dashboard controls (simulated head-up, dev tools). */
|
|
15
|
+
dashboard: {
|
|
16
|
+
isOpen: (): boolean => dashboardCoordinator.isOpen,
|
|
17
|
+
/** Open/close as if the head was raised/lowered (respects contextual_dashboard). */
|
|
18
|
+
setHeadUp: (up: boolean): void => dashboardCoordinator.setHeadUp(up),
|
|
19
|
+
/** Force open/close, ignoring contextual_dashboard (simulator button). */
|
|
20
|
+
open: (): void => dashboardCoordinator.open(),
|
|
21
|
+
close: (): void => dashboardCoordinator.close(),
|
|
22
|
+
},
|
|
13
23
|
/** Write raw text to the glasses display at (x, y) with a font size. */
|
|
14
24
|
text: (...args: Parameters<typeof BluetoothSdk.displayText>) => BluetoothSdk.displayText(...args),
|
|
15
25
|
/** Clear the glasses display. */
|
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* glasses display mirror — the typed read facade over the (now engine-owned)
|
|
3
3
|
* display store, for a phone-side preview of the glasses screen
|
|
4
|
-
* (`engine.display.mirror`). The store
|
|
5
|
-
* (
|
|
6
|
-
* read side: `current()` (snapshot) + `onMirror(cb)` (subscribe).
|
|
7
|
-
* also exposed as `engine.displayStore` (the Glassly-app
|
|
4
|
+
* (`engine.display.mirror`). The store holds the single frame currently on the
|
|
5
|
+
* glasses (dashboard switching happens upstream in DashboardCoordinator); this
|
|
6
|
+
* facade is the read side: `current()` (snapshot) + `onMirror(cb)` (subscribe).
|
|
7
|
+
* The raw store is also exposed as `engine.displayStore` (the Glassly-app
|
|
8
|
+
* escape hatch).
|
|
8
9
|
*/
|
|
9
10
|
import {useDisplayStore} from "../stores/display"
|
|
10
11
|
|
|
11
12
|
export type DisplayMirrorEvent = Record<string, unknown> & {view?: string; layout?: any}
|
|
12
13
|
|
|
13
14
|
export const displayMirror = {
|
|
14
|
-
/** The display event currently
|
|
15
|
+
/** The display event currently on the glasses (snapshot). */
|
|
15
16
|
current(): DisplayMirrorEvent {
|
|
16
17
|
// Copy: the snapshot must not hand callers a mutable reference into the store.
|
|
17
18
|
return {...useDisplayStore.getState().currentEvent}
|
|
18
19
|
},
|
|
19
20
|
|
|
20
|
-
/** Subscribe to changes of the
|
|
21
|
+
/** Subscribe to changes of the mirrored display event; returns an unsubscribe. */
|
|
21
22
|
onMirror(cb: (event: DisplayMirrorEvent) => void): () => void {
|
|
22
23
|
return useDisplayStore.subscribe((s) => s.currentEvent, cb)
|
|
23
24
|
},
|
|
24
|
-
|
|
25
|
-
/** Which view is previewed — the main screen or the dashboard. */
|
|
26
|
-
view(): string {
|
|
27
|
-
return useDisplayStore.getState().view
|
|
28
|
-
},
|
|
29
|
-
/** Switch the previewed view between `"main"` and `"dashboard"`. */
|
|
30
|
-
setView(view: "main" | "dashboard"): void {
|
|
31
|
-
useDisplayStore.getState().setView(view)
|
|
32
|
-
},
|
|
33
25
|
}
|
package/src/facades/glasses.ts
CHANGED
|
@@ -35,6 +35,8 @@ function projectStatus() {
|
|
|
35
35
|
micEnabled: s.micEnabled,
|
|
36
36
|
vadEnabled: s.voiceActivityDetectionEnabled,
|
|
37
37
|
btClassic: s.bluetoothClassicConnected,
|
|
38
|
+
// iOS: glasses may read the phone's ANCS stream ("Share System Notifications").
|
|
39
|
+
ancsAuthorized: Boolean(s.ancsAuthorized),
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
package/src/facades/ota.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
OtaUpdateInfo,
|
|
18
18
|
ReleaseChangelog,
|
|
19
19
|
} from "@glassly/bluetooth-sdk"
|
|
20
|
-
import {isGlassesConnected, useGlassesStore} from "../stores/glasses"
|
|
20
|
+
import {isGlassesConnected, isGlassesReady, useGlassesStore} from "../stores/glasses"
|
|
21
21
|
import {resolveOtaManifestUrl} from "../services/otaManifestUrl"
|
|
22
22
|
import {otaInstallCoordinator, type OtaInstallSnapshot} from "../services/OtaInstallCoordinator"
|
|
23
23
|
import {startGlassesStatusProjection} from "../services/GlassesStatusProjection"
|
|
@@ -32,10 +32,12 @@ function projectSnapshot() {
|
|
|
32
32
|
const s = useGlassesStore.getState()
|
|
33
33
|
return {
|
|
34
34
|
connected: isGlassesConnected(s.connection),
|
|
35
|
+
ready: isGlassesReady(s.connection),
|
|
35
36
|
buildNumber: s.buildNumber || null,
|
|
36
37
|
appVersion: s.appVersion || null,
|
|
37
38
|
mtkFirmwareVersion: s.mtkFirmwareVersion || null,
|
|
38
39
|
besFirmwareVersion: s.besFirmwareVersion || null,
|
|
40
|
+
batteryLevel: s.batteryLevel >= 0 ? s.batteryLevel : null,
|
|
39
41
|
hotspotOtaVersion: s.hotspotOtaVersion ?? 0,
|
|
40
42
|
wifiConnected: s.wifi.state === "connected",
|
|
41
43
|
wifiStatusKnown: s.wifiStatusKnown,
|
|
@@ -64,7 +66,7 @@ export const ota = {
|
|
|
64
66
|
// --- actions ---
|
|
65
67
|
/**
|
|
66
68
|
* Start the device-status and OTA projections without starting the authenticated
|
|
67
|
-
* cloud
|
|
69
|
+
* cloud connection or miniapp runtime. This is the explicit entry point for Bluetooth-only hosts.
|
|
68
70
|
* Idempotent and safe when the full engine runtime is already running.
|
|
69
71
|
*/
|
|
70
72
|
initialize: async () => {
|
|
@@ -1,25 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* phoneNotifications facade — `engine.phoneNotifications`: forward phone
|
|
3
|
-
* notifications to the glasses, with a per-app blocklist.
|
|
4
|
-
* NotificationListenerService); the getters return sensible defaults on iOS.
|
|
3
|
+
* notifications to the glasses, with a per-app blocklist.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Android: apps come from the NotificationListenerService's installed-apps
|
|
6
|
+
* enumeration and the blocklist filters capture phone-side.
|
|
7
|
+
* iOS: apps are the seeded catalog plus what the glasses' ANCS client has
|
|
8
|
+
* reported posting notifications (`ios_notification_seen_apps`); the blocklist
|
|
9
|
+
* is mapped onto the G2's on-glass allowlist by PhoneNotificationsSync.
|
|
10
|
+
* Capture itself is controlled by Android notification access / iOS ANCS
|
|
11
|
+
* authorization (`glasses.ancsAuthorized`), not by this facade.
|
|
10
12
|
*/
|
|
11
13
|
import {Platform} from "react-native"
|
|
12
14
|
import CrustModule from "@glassly/crust"
|
|
13
15
|
import {useSettingsStore, SETTINGS} from "../stores/settings"
|
|
16
|
+
import {useGlassesStore} from "../stores/glasses"
|
|
17
|
+
import {getKnownNotificationApps} from "../services/PhoneNotificationsSync"
|
|
14
18
|
|
|
15
19
|
export const phoneNotifications = {
|
|
16
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Apps to show in the notification filter UI (package/bundle id, name, icon).
|
|
22
|
+
* Android: installed apps. iOS: seeded catalog ∪ glasses-reported apps
|
|
23
|
+
* (icons unavailable — iOS offers no cross-app icon lookup).
|
|
24
|
+
*/
|
|
17
25
|
installedApps: (): Promise<Array<{packageName: string; appName: string; icon: string | null}>> => {
|
|
18
|
-
if (Platform.OS
|
|
19
|
-
return
|
|
26
|
+
if (Platform.OS === "android") return CrustModule.getInstalledApps()
|
|
27
|
+
if (Platform.OS !== "ios") return Promise.resolve([])
|
|
28
|
+
return Promise.resolve(
|
|
29
|
+
getKnownNotificationApps().map((app) => ({
|
|
30
|
+
packageName: app.appId,
|
|
31
|
+
appName: app.displayName,
|
|
32
|
+
icon: null,
|
|
33
|
+
})),
|
|
34
|
+
)
|
|
20
35
|
},
|
|
21
36
|
|
|
22
|
-
/** The per-app blocklist (package
|
|
37
|
+
/** The per-app blocklist (package/bundle ids whose notifications are NOT forwarded). */
|
|
23
38
|
blocklist: (): string[] => {
|
|
24
39
|
const b = useSettingsStore.getState().getSetting(SETTINGS.notifications_blocklist.key)
|
|
25
40
|
return Array.isArray(b) ? b : []
|
|
@@ -38,4 +53,11 @@ export const phoneNotifications = {
|
|
|
38
53
|
if (Platform.OS !== "android") return
|
|
39
54
|
return CrustModule.openNotificationListenerSettings()
|
|
40
55
|
},
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* iOS: whether the connected glasses may read the phone's ANCS notification
|
|
59
|
+
* stream (the "Share System Notifications" Bluetooth consent). Always false
|
|
60
|
+
* on Android — Android capture is the notification listener instead.
|
|
61
|
+
*/
|
|
62
|
+
ancsAuthorized: (): boolean => Boolean(useGlassesStore.getState().ancsAuthorized),
|
|
41
63
|
}
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.1.0",
|
|
15
|
-
"releaseIdentity": "0.1.0-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.1.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.1.0-dev.89",
|
|
16
|
+
"releaseSetId": "glassly-0.1.0-dev.89",
|
|
17
|
+
"sourceCommit": "c66a621d66d70251e257f73c9a79ee1f8e079be9",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
package/src/index.ts
CHANGED
|
@@ -109,6 +109,7 @@ export {
|
|
|
109
109
|
GLOBAL_OTA_TIMEOUT_MS,
|
|
110
110
|
PING_INTERVAL_MS,
|
|
111
111
|
QUERY_REPLY_TIMEOUT_MS,
|
|
112
|
+
BES_RESTART_TIMEOUT_MS,
|
|
112
113
|
OtaProgressMessages,
|
|
113
114
|
} from "./services/otaInstallPolicy"
|
|
114
115
|
export {deriveDisplayState} from "./services/otaDisplayState"
|
|
@@ -216,9 +217,9 @@ export {
|
|
|
216
217
|
evenRealitiesG1,
|
|
217
218
|
evenRealitiesG2,
|
|
218
219
|
mentraLive,
|
|
220
|
+
nimo,
|
|
219
221
|
vuzixZ100,
|
|
220
222
|
mentraDisplay,
|
|
221
|
-
nimo,
|
|
222
223
|
} from "./types"
|
|
223
224
|
export type {
|
|
224
225
|
HardwareRequirement,
|