@glassly/engine 0.1.0-dev.0 → 0.1.0-dev.26

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.
Files changed (277) hide show
  1. package/README.md +53 -19
  2. package/build/bluetooth-sdk/debug.d.ts +2 -0
  3. package/build/bluetooth-sdk/debug.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/debug.js +2 -0
  5. package/build/bluetooth-sdk/debug.js.map +1 -0
  6. package/build/bluetooth-sdk/index.d.ts +3 -0
  7. package/build/bluetooth-sdk/index.d.ts.map +1 -0
  8. package/build/bluetooth-sdk/index.js +3 -0
  9. package/build/bluetooth-sdk/index.js.map +1 -0
  10. package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
  11. package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
  12. package/build/bluetooth-sdk/ota-transport.js +2 -0
  13. package/build/bluetooth-sdk/ota-transport.js.map +1 -0
  14. package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
  15. package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
  16. package/build/bluetooth-sdk/photo-receiver.js +3 -0
  17. package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
  18. package/build/bluetooth-sdk/react.d.ts +2 -0
  19. package/build/bluetooth-sdk/react.d.ts.map +1 -0
  20. package/build/bluetooth-sdk/react.js +2 -0
  21. package/build/bluetooth-sdk/react.js.map +1 -0
  22. package/build/bluetooth-sdk/types.d.ts +2 -0
  23. package/build/bluetooth-sdk/types.d.ts.map +1 -0
  24. package/build/bluetooth-sdk/types.js +2 -0
  25. package/build/bluetooth-sdk/types.js.map +1 -0
  26. package/build/engine.d.ts +23 -16
  27. package/build/engine.d.ts.map +1 -1
  28. package/build/engine.js +9 -3
  29. package/build/engine.js.map +1 -1
  30. package/build/facades/dev.d.ts +1 -1
  31. package/build/facades/display.d.ts +1 -1
  32. package/build/facades/display.d.ts.map +1 -1
  33. package/build/facades/display.js +1 -1
  34. package/build/facades/display.js.map +1 -1
  35. package/build/facades/glasses.d.ts +5 -13
  36. package/build/facades/glasses.d.ts.map +1 -1
  37. package/build/facades/glasses.js +5 -3
  38. package/build/facades/glasses.js.map +1 -1
  39. package/build/facades/ota.d.ts +19 -21
  40. package/build/facades/ota.d.ts.map +1 -1
  41. package/build/facades/ota.js +23 -9
  42. package/build/facades/ota.js.map +1 -1
  43. package/build/facades/pairing.d.ts +11 -5
  44. package/build/facades/pairing.d.ts.map +1 -1
  45. package/build/facades/pairing.js +93 -17
  46. package/build/facades/pairing.js.map +1 -1
  47. package/build/facades/speakers.d.ts +1 -1
  48. package/build/facades/speakers.d.ts.map +1 -1
  49. package/build/facades/speakers.js.map +1 -1
  50. package/build/facades/speech.d.ts +7 -6
  51. package/build/facades/speech.d.ts.map +1 -1
  52. package/build/facades/speech.js +1 -1
  53. package/build/facades/speech.js.map +1 -1
  54. package/build/generated/releaseMetadata.d.ts +12 -0
  55. package/build/generated/releaseMetadata.d.ts.map +1 -0
  56. package/build/generated/releaseMetadata.js +10 -0
  57. package/build/generated/releaseMetadata.js.map +1 -0
  58. package/build/index.d.ts +7 -4
  59. package/build/index.d.ts.map +1 -1
  60. package/build/index.js +5 -3
  61. package/build/index.js.map +1 -1
  62. package/build/react/MentraLiveOtaFlow.d.ts +39 -0
  63. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  64. package/build/react/MentraLiveOtaFlow.js +200 -0
  65. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  66. package/build/react/index.d.ts +3 -0
  67. package/build/react/index.d.ts.map +1 -0
  68. package/build/react/index.js +3 -0
  69. package/build/react/index.js.map +1 -0
  70. package/build/react/useEngineSnapshot.d.ts +2 -0
  71. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  72. package/build/react/useEngineSnapshot.js +14 -0
  73. package/build/react/useEngineSnapshot.js.map +1 -0
  74. package/build/react/useMentraLiveOta.d.ts +80 -0
  75. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  76. package/build/react/useMentraLiveOta.js +550 -0
  77. package/build/react/useMentraLiveOta.js.map +1 -0
  78. package/build/services/AppRegistry.d.ts +8 -0
  79. package/build/services/AppRegistry.d.ts.map +1 -1
  80. package/build/services/AppRegistry.js +15 -0
  81. package/build/services/AppRegistry.js.map +1 -1
  82. package/build/services/BlobStore.d.ts +2 -0
  83. package/build/services/BlobStore.d.ts.map +1 -1
  84. package/build/services/BlobStore.js +63 -10
  85. package/build/services/BlobStore.js.map +1 -1
  86. package/build/services/CustomDashboardModel.d.ts +48 -0
  87. package/build/services/CustomDashboardModel.d.ts.map +1 -0
  88. package/build/services/CustomDashboardModel.js +73 -0
  89. package/build/services/CustomDashboardModel.js.map +1 -0
  90. package/build/services/CustomDashboardService.d.ts +61 -0
  91. package/build/services/CustomDashboardService.d.ts.map +1 -0
  92. package/build/services/CustomDashboardService.js +552 -0
  93. package/build/services/CustomDashboardService.js.map +1 -0
  94. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  95. package/build/services/DeviceEventRouter.js +44 -3
  96. package/build/services/DeviceEventRouter.js.map +1 -1
  97. package/build/services/GestureDotsService.d.ts +35 -0
  98. package/build/services/GestureDotsService.d.ts.map +1 -0
  99. package/build/services/GestureDotsService.js +149 -0
  100. package/build/services/GestureDotsService.js.map +1 -0
  101. package/build/services/GlassesStatusProjection.d.ts +15 -1
  102. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  103. package/build/services/GlassesStatusProjection.js +18 -11
  104. package/build/services/GlassesStatusProjection.js.map +1 -1
  105. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  106. package/build/services/HotspotOtaTransport.js +8 -7
  107. package/build/services/HotspotOtaTransport.js.map +1 -1
  108. package/build/services/LocalDisplayManager.d.ts +43 -1
  109. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  110. package/build/services/LocalDisplayManager.js +128 -2
  111. package/build/services/LocalDisplayManager.js.map +1 -1
  112. package/build/services/LocalMiniappRuntime.d.ts +13 -0
  113. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  114. package/build/services/LocalMiniappRuntime.js +32 -0
  115. package/build/services/LocalMiniappRuntime.js.map +1 -1
  116. package/build/services/MiniappExitGesture.d.ts +33 -0
  117. package/build/services/MiniappExitGesture.d.ts.map +1 -0
  118. package/build/services/MiniappExitGesture.js +54 -0
  119. package/build/services/MiniappExitGesture.js.map +1 -0
  120. package/build/services/OtaAutoChain.d.ts +41 -0
  121. package/build/services/OtaAutoChain.d.ts.map +1 -0
  122. package/build/services/OtaAutoChain.js +91 -0
  123. package/build/services/OtaAutoChain.js.map +1 -0
  124. package/build/services/OtaErrorMapping.d.ts +18 -0
  125. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  126. package/build/services/OtaErrorMapping.js +63 -0
  127. package/build/services/OtaErrorMapping.js.map +1 -0
  128. package/build/services/OtaInstallCoordinator.d.ts +24 -3
  129. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  130. package/build/services/OtaInstallCoordinator.js +17 -12
  131. package/build/services/OtaInstallCoordinator.js.map +1 -1
  132. package/build/services/OtaService.d.ts.map +1 -1
  133. package/build/services/OtaService.js +1 -1
  134. package/build/services/OtaService.js.map +1 -1
  135. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  136. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  137. package/build/services/OtaUpdateCheckService.js +28 -16
  138. package/build/services/OtaUpdateCheckService.js.map +1 -1
  139. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  140. package/build/services/asg/localNetworkTransport.js +13 -12
  141. package/build/services/asg/localNetworkTransport.js.map +1 -1
  142. package/build/services/gestureClaims.d.ts +18 -0
  143. package/build/services/gestureClaims.d.ts.map +1 -0
  144. package/build/services/gestureClaims.js +24 -0
  145. package/build/services/gestureClaims.js.map +1 -0
  146. package/build/services/gestureDotGlyphs.d.ts +8 -0
  147. package/build/services/gestureDotGlyphs.d.ts.map +1 -0
  148. package/build/services/gestureDotGlyphs.js +12 -0
  149. package/build/services/gestureDotGlyphs.js.map +1 -0
  150. package/build/services/glassesClockSync.d.ts.map +1 -1
  151. package/build/services/glassesClockSync.js +5 -1
  152. package/build/services/glassesClockSync.js.map +1 -1
  153. package/build/services/otaDisplayState.d.ts +1 -1
  154. package/build/services/otaDisplayState.d.ts.map +1 -1
  155. package/build/services/otaDisplayState.js.map +1 -1
  156. package/build/services/otaInstallPolicy.d.ts +1 -1
  157. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  158. package/build/services/otaInstallPolicy.js.map +1 -1
  159. package/build/services/otaLegacyMapping.d.ts +1 -1
  160. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  161. package/build/services/otaLegacyMapping.js.map +1 -1
  162. package/build/services/otaManifestPolicy.d.ts +14 -0
  163. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  164. package/build/services/otaManifestPolicy.js +20 -0
  165. package/build/services/otaManifestPolicy.js.map +1 -0
  166. package/build/services/otaManifestUrl.d.ts +2 -1
  167. package/build/services/otaManifestUrl.d.ts.map +1 -1
  168. package/build/services/otaManifestUrl.js +24 -32
  169. package/build/services/otaManifestUrl.js.map +1 -1
  170. package/build/services/otaReleaseVersion.d.ts +10 -0
  171. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  172. package/build/services/otaReleaseVersion.js +14 -0
  173. package/build/services/otaReleaseVersion.js.map +1 -0
  174. package/build/stores/appVisibility.d.ts +3 -0
  175. package/build/stores/appVisibility.d.ts.map +1 -0
  176. package/build/stores/appVisibility.js +3 -0
  177. package/build/stores/appVisibility.js.map +1 -0
  178. package/build/stores/apps.d.ts.map +1 -1
  179. package/build/stores/apps.js +7 -1
  180. package/build/stores/apps.js.map +1 -1
  181. package/build/stores/core.d.ts +3 -3
  182. package/build/stores/core.d.ts.map +1 -1
  183. package/build/stores/core.js.map +1 -1
  184. package/build/stores/glasses.d.ts +10 -4
  185. package/build/stores/glasses.d.ts.map +1 -1
  186. package/build/stores/glasses.js.map +1 -1
  187. package/build/stores/settings.d.ts.map +1 -1
  188. package/build/stores/settings.js +87 -3
  189. package/build/stores/settings.js.map +1 -1
  190. package/build/types/applet.d.ts +2 -0
  191. package/build/types/applet.d.ts.map +1 -1
  192. package/build/types/applet.js +0 -3
  193. package/build/types/applet.js.map +1 -1
  194. package/build/types/enums.d.ts +1 -5
  195. package/build/types/enums.d.ts.map +1 -1
  196. package/build/types/enums.js +1 -8
  197. package/build/types/enums.js.map +1 -1
  198. package/build/types/hardware.d.ts +1 -1
  199. package/build/types/hardware.d.ts.map +1 -1
  200. package/build/types/hardware.js.map +1 -1
  201. package/build/types/index.d.ts +2 -6
  202. package/build/types/index.d.ts.map +1 -1
  203. package/build/types/index.js +2 -6
  204. package/build/types/index.js.map +1 -1
  205. package/build/utils/display/scene/process.d.ts.map +1 -1
  206. package/build/utils/display/scene/process.js +4 -3
  207. package/build/utils/display/scene/process.js.map +1 -1
  208. package/build/utils/display/scene/types.d.ts +6 -2
  209. package/build/utils/display/scene/types.d.ts.map +1 -1
  210. package/build/utils/display/scene/types.js.map +1 -1
  211. package/package.json +65 -33
  212. package/src/bluetooth-sdk/debug.ts +1 -0
  213. package/src/bluetooth-sdk/index.ts +2 -0
  214. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  215. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  216. package/src/bluetooth-sdk/react.ts +1 -0
  217. package/src/bluetooth-sdk/types.ts +1 -0
  218. package/src/engine.ts +9 -3
  219. package/src/facades/display.ts +1 -1
  220. package/src/facades/glasses.ts +6 -4
  221. package/src/facades/ota.ts +33 -10
  222. package/src/facades/pairing.ts +103 -23
  223. package/src/facades/speakers.ts +1 -1
  224. package/src/facades/speech.ts +21 -7
  225. package/src/generated/releaseMetadata.ts +20 -0
  226. package/src/index.ts +29 -4
  227. package/src/react/MentraLiveOtaFlow.tsx +569 -0
  228. package/src/react/index.ts +21 -0
  229. package/src/react/useEngineSnapshot.ts +16 -0
  230. package/src/react/useMentraLiveOta.ts +707 -0
  231. package/src/services/AppRegistry.ts +16 -0
  232. package/src/services/BlobStore.ts +57 -10
  233. package/src/services/CustomDashboardModel.ts +83 -0
  234. package/src/services/CustomDashboardService.ts +575 -0
  235. package/src/services/DeviceEventRouter.ts +47 -5
  236. package/src/services/GestureDotsService.ts +147 -0
  237. package/src/services/GlassesStatusProjection.ts +20 -11
  238. package/src/services/HotspotOtaTransport.ts +8 -7
  239. package/src/services/LocalDisplayManager.ts +130 -2
  240. package/src/services/LocalMiniappRuntime.ts +37 -0
  241. package/src/services/MiniappExitGesture.ts +76 -0
  242. package/src/services/OtaAutoChain.ts +130 -0
  243. package/src/services/OtaErrorMapping.ts +80 -0
  244. package/src/services/OtaInstallCoordinator.ts +19 -13
  245. package/src/services/OtaService.ts +1 -2
  246. package/src/services/OtaUpdateCheckService.ts +33 -19
  247. package/src/services/asg/localNetworkTransport.ts +20 -19
  248. package/src/services/gestureClaims.ts +28 -0
  249. package/src/services/gestureDotGlyphs.ts +14 -0
  250. package/src/services/glassesClockSync.ts +5 -1
  251. package/src/services/otaDisplayState.ts +1 -1
  252. package/src/services/otaInstallPolicy.ts +1 -1
  253. package/src/services/otaLegacyMapping.ts +1 -1
  254. package/src/services/otaManifestPolicy.ts +43 -0
  255. package/src/services/otaManifestUrl.ts +27 -36
  256. package/src/services/otaReleaseVersion.ts +22 -0
  257. package/src/stores/appVisibility.ts +3 -0
  258. package/src/stores/apps.ts +7 -1
  259. package/src/stores/core.ts +4 -4
  260. package/src/stores/glasses.ts +13 -6
  261. package/src/stores/settings.ts +87 -3
  262. package/src/types/applet.ts +2 -0
  263. package/src/types/enums.ts +1 -9
  264. package/src/types/hardware.ts +1 -1
  265. package/src/types/index.ts +2 -6
  266. package/src/utils/display/scene/process.ts +4 -3
  267. package/src/utils/display/scene/types.ts +0 -0
  268. package/build/devtools.d.ts +0 -11
  269. package/build/devtools.d.ts.map +0 -1
  270. package/build/devtools.js +0 -13
  271. package/build/devtools.js.map +0 -1
  272. package/build/internal.d.ts +0 -67
  273. package/build/internal.d.ts.map +0 -1
  274. package/build/internal.js +0 -90
  275. package/build/internal.js.map +0 -1
  276. package/src/devtools.ts +0 -13
  277. package/src/internal.ts +0 -155
@@ -0,0 +1,569 @@
1
+ /* eslint-disable react-native/no-raw-text -- BodyText and PercentText are local Text wrappers. */
2
+ import React, {useMemo} from "react"
3
+ import {
4
+ ActivityIndicator,
5
+ Pressable,
6
+ ScrollView,
7
+ StyleSheet,
8
+ Text,
9
+ View,
10
+ type StyleProp,
11
+ type ViewStyle,
12
+ } from "react-native"
13
+ import {SafeAreaView} from "react-native-safe-area-context"
14
+ import Svg, {Path, Rect} from "react-native-svg"
15
+
16
+ import {useMentraLiveOta, type MentraLiveOtaController, type MentraLiveOtaFlowPage} from "./useMentraLiveOta"
17
+
18
+ export type {MentraLiveOtaFlowPage} from "./useMentraLiveOta"
19
+
20
+ export type MentraLiveOtaFlowTheme = {
21
+ background: string
22
+ border: string
23
+ error: string
24
+ foreground: string
25
+ primary: string
26
+ primaryText: string
27
+ textDim: string
28
+ }
29
+
30
+ export type MentraLiveOtaFlowTranslate = (key: string, options?: Record<string, string>) => string
31
+
32
+ export type MentraLiveOtaFlowProps = {
33
+ /** Display name used in update copy. */
34
+ deviceName?: string
35
+ /** Entry page. `progress` exists for recovery/deep-link compatibility. */
36
+ initialPage?: MentraLiveOtaFlowPage
37
+ /** Start the OTA-only projections. Full Engine hosts should pass false. */
38
+ initializeRuntime?: boolean
39
+ /** Called after the final check or when the user leaves an optional update. */
40
+ onFinished: () => void
41
+ /** Host-owned Wi-Fi setup for glasses that do not support hotspot OTA. */
42
+ onOpenWifiSetup: () => void
43
+ /** Lets a host coordinate its global connection overlay with OTA progress and firmware restarts. */
44
+ onFirmwareRestartingChange?: (restarting: boolean, progressActive: boolean) => void
45
+ /** Enables the existing developer-only escape hatches. */
46
+ allowDevSkip?: boolean
47
+ /** Enables the existing super-mode interrupted-session escape hatch. */
48
+ superMode?: boolean
49
+ /** Optional host localization. Defaults to the Glassly App English OTA copy. */
50
+ translate?: MentraLiveOtaFlowTranslate
51
+ /** Optional host theme. Both hosts use the same layout and state machine. */
52
+ theme?: Partial<MentraLiveOtaFlowTheme>
53
+ style?: StyleProp<ViewStyle>
54
+ }
55
+
56
+ const DEFAULT_THEME: MentraLiveOtaFlowTheme = {
57
+ background: "#FFFFFF",
58
+ border: "#D7DFDA",
59
+ error: "#C43131",
60
+ foreground: "#0E2C1A",
61
+ primary: "#00B869",
62
+ primaryText: "#FFFFFF",
63
+ textDim: "#66736B",
64
+ }
65
+
66
+ const ENGLISH_COPY: Record<string, string> = {
67
+ "common:continue": "Continue",
68
+ "ota:checkingForUpdates": "Checking for updates",
69
+ "ota:checkingForUpdatesMessage":
70
+ "Connected devices will perform automatic updates. Automatic updates can be disabled in Device Settings",
71
+ "ota:updateAvailable": "{{deviceName}} Update Available",
72
+ "ota:updateConnectWifi": "Connect your {{deviceName}} to WiFi to install the update.",
73
+ "ota:updateDescription":
74
+ "A new update is available for your glasses. We recommend updating now for the best experience.",
75
+ "ota:releaseTransition": "{{fromVersion}} → {{toVersion}}",
76
+ "ota:releaseTransitionUnknown": "Current version unknown → {{toVersion}}",
77
+ "ota:updatedToVersion": "Updated to {{version}}",
78
+ "ota:downgradeAvailable": "{{deviceName}} Version Change Required",
79
+ "ota:downgradeDescription":
80
+ "This app requires an earlier glasses software version. Your photos and videos will be preserved, but glasses settings will be reset and restored automatically after the change.",
81
+ "ota:updateNow": "Update Now",
82
+ "ota:setupWifi": "Setup WiFi",
83
+ "ota:updateLater": "Later",
84
+ "ota:upToDate": "Up To Date",
85
+ "ota:devBuild": "Development Build",
86
+ "ota:devBuildNoOta":
87
+ "This mobile app is a development build, so automatic glasses updates are disabled. Use the developer settings manifest override to update them manually.",
88
+ "ota:noUpdatesAvailable": "Your glasses are running the latest version.",
89
+ "ota:checkFailed": "Check Failed",
90
+ "ota:checkFailedMessage": "Couldn't check for updates. Please check your connection and try again.",
91
+ "ota:updateInfoUnavailable": "Update Info Unavailable",
92
+ "ota:updateInfoUnavailableMessage":
93
+ "Update information for this version of the app is unavailable. Please check the app store for a newer version of the Glassly App.",
94
+ "ota:downgradeDuration": "Your glasses will restart twice — this may take up to 2 minutes.",
95
+ "ota:versionChangeRestarting": "Installing a different version…",
96
+ "ota:versionChangeVerifying": "Verifying your glasses…",
97
+ "ota:versionChangeKeepNearby": "Keep your glasses nearby and connected. They will restart on their own.",
98
+ "ota:versionChangeComplete": "Version Change Complete",
99
+ "ota:versionChangeCompleteMessage":
100
+ "Your glasses are now on the required version. Their settings were reset and are being restored automatically.",
101
+ "ota:versionChangeFirmwarePassComplete": "Firmware updated",
102
+ "ota:versionChangeFirmwarePassCompleteMessage":
103
+ "Your glasses restarted with new firmware. One more step: they'll now continue to the required version.",
104
+ }
105
+
106
+ function defaultTranslate(key: string, options?: Record<string, string>): string {
107
+ let value = ENGLISH_COPY[key] ?? key
108
+ for (const [name, replacement] of Object.entries(options ?? {})) {
109
+ value = value.replaceAll(`{{${name}}}`, replacement)
110
+ }
111
+ return value
112
+ }
113
+
114
+ export function MentraLiveOtaFlow({
115
+ allowDevSkip = typeof __DEV__ !== "undefined" && __DEV__,
116
+ deviceName = "Mentra Live",
117
+ initialPage = "check",
118
+ initializeRuntime = true,
119
+ onFinished,
120
+ onFirmwareRestartingChange,
121
+ onOpenWifiSetup,
122
+ style,
123
+ superMode = false,
124
+ theme,
125
+ translate = defaultTranslate,
126
+ }: MentraLiveOtaFlowProps) {
127
+ const colors = useMemo(() => ({...DEFAULT_THEME, ...theme}), [theme])
128
+ const controller = useMentraLiveOta({
129
+ initialPage,
130
+ initializeRuntime,
131
+ onFinished,
132
+ onFirmwareRestartingChange,
133
+ onOpenWifiSetup,
134
+ })
135
+
136
+ return (
137
+ <SafeAreaView style={[styles.safeArea, {backgroundColor: colors.background}, style]}>
138
+ <View style={styles.header}>
139
+ <View />
140
+ <GlasslyMark color={colors.primary} />
141
+ </View>
142
+ <OtaFlowContent
143
+ allowDevSkip={allowDevSkip}
144
+ colors={colors}
145
+ controller={controller}
146
+ deviceName={deviceName}
147
+ superMode={superMode}
148
+ translate={translate}
149
+ />
150
+ </SafeAreaView>
151
+ )
152
+ }
153
+
154
+ function OtaFlowContent({
155
+ allowDevSkip,
156
+ colors,
157
+ controller,
158
+ deviceName,
159
+ superMode,
160
+ translate,
161
+ }: {
162
+ allowDevSkip: boolean
163
+ colors: MentraLiveOtaFlowTheme
164
+ controller: MentraLiveOtaController
165
+ deviceName: string
166
+ superMode: boolean
167
+ translate: MentraLiveOtaFlowTranslate
168
+ }) {
169
+ const {state} = controller
170
+
171
+ if (state.screen === "initializing") {
172
+ return (
173
+ <FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
174
+ <ActivityIndicator size="large" color={colors.foreground} />
175
+ </FlowPage>
176
+ )
177
+ }
178
+
179
+ if (state.screen === "checking") {
180
+ return (
181
+ <FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
182
+ <BodyText colors={colors}>{translate("ota:checkingForUpdatesMessage")}</BodyText>
183
+ <ActivityIndicator size="large" color={colors.foreground} />
184
+ </FlowPage>
185
+ )
186
+ }
187
+
188
+ if (state.screen === "update_available" || state.screen === "wifi_required") {
189
+ return (
190
+ <FlowPage
191
+ colors={colors}
192
+ icon="download"
193
+ title={translate(state.versionChange ? "ota:downgradeAvailable" : "ota:updateAvailable", {deviceName})}
194
+ actions={
195
+ <>
196
+ <FlowButton
197
+ colors={colors}
198
+ disabled={!state.wifiStatusKnown}
199
+ label={translate(state.screen === "wifi_required" ? "ota:setupWifi" : "ota:updateNow")}
200
+ onPress={state.screen === "wifi_required" ? controller.openWifiSetup : controller.install}
201
+ />
202
+ {state.canDismiss ? (
203
+ <FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary />
204
+ ) : null}
205
+ {allowDevSkip && state.updateRequired ? (
206
+ <FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
207
+ ) : null}
208
+ </>
209
+ }>
210
+ {state.releaseTransition ? (
211
+ <BodyText colors={colors}>
212
+ {state.releaseTransition.fromVersion
213
+ ? translate("ota:releaseTransition", {
214
+ fromVersion: state.releaseTransition.fromVersion,
215
+ toVersion: state.releaseTransition.toVersion,
216
+ })
217
+ : translate("ota:releaseTransitionUnknown", {toVersion: state.releaseTransition.toVersion})}
218
+ </BodyText>
219
+ ) : null}
220
+ <BodyText colors={colors}>
221
+ {state.screen === "wifi_required"
222
+ ? translate("ota:updateConnectWifi", {deviceName})
223
+ : translate(state.versionChange ? "ota:downgradeDescription" : "ota:updateDescription")}
224
+ </BodyText>
225
+ </FlowPage>
226
+ )
227
+ }
228
+
229
+ if (state.screen === "dev_build") {
230
+ return (
231
+ <FlowPage
232
+ actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
233
+ colors={colors}
234
+ icon="settings"
235
+ title={translate("ota:devBuild")}>
236
+ <BodyText colors={colors}>{translate("ota:devBuildNoOta")}</BodyText>
237
+ </FlowPage>
238
+ )
239
+ }
240
+
241
+ if (state.screen === "up_to_date") {
242
+ return (
243
+ <FlowPage
244
+ actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
245
+ colors={colors}
246
+ icon="check"
247
+ title={translate("ota:upToDate")}>
248
+ <BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
249
+ {state.releaseTransition ? (
250
+ <BodyText colors={colors}>
251
+ {translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
252
+ </BodyText>
253
+ ) : null}
254
+ <ChangelogList changelogs={state.changelogs} colors={colors} />
255
+ </FlowPage>
256
+ )
257
+ }
258
+
259
+ if (state.screen === "update_info_unavailable") {
260
+ return (
261
+ <FlowPage
262
+ actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
263
+ colors={colors}
264
+ icon="alert"
265
+ title={translate("ota:updateInfoUnavailable")}>
266
+ <BodyText colors={colors}>{translate("ota:updateInfoUnavailableMessage")}</BodyText>
267
+ </FlowPage>
268
+ )
269
+ }
270
+
271
+ if (state.screen === "check_failed") {
272
+ return (
273
+ <FlowPage
274
+ actions={
275
+ <>
276
+ <FlowButton colors={colors} label="Retry" onPress={controller.retryCheck} />
277
+ {allowDevSkip ? (
278
+ <FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
279
+ ) : null}
280
+ </>
281
+ }
282
+ colors={colors}
283
+ icon="alert"
284
+ title={translate("ota:checkFailed")}>
285
+ <BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
286
+ </FlowPage>
287
+ )
288
+ }
289
+
290
+ if (state.versionChangePhase === "restarting" || state.versionChangePhase === "verifying") {
291
+ return (
292
+ <FlowPage
293
+ colors={colors}
294
+ icon="download"
295
+ title={translate(
296
+ state.versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting",
297
+ )}>
298
+ <ActivityIndicator size="large" color={colors.foreground} />
299
+ <BodyText colors={colors}>{translate("ota:versionChangeKeepNearby")}</BodyText>
300
+ <BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
301
+ </FlowPage>
302
+ )
303
+ }
304
+
305
+ if (state.screen === "starting" || state.screen === "preparing_hotspot") {
306
+ const title =
307
+ state.hotspotPhase === "downloading"
308
+ ? "Downloading update to phone..."
309
+ : state.hotspotPhase === "starting_hotspot"
310
+ ? "Starting glasses hotspot..."
311
+ : state.hotspotPhase === "joining_hotspot"
312
+ ? "Connecting phone to glasses..."
313
+ : "Starting update..."
314
+ return (
315
+ <FlowPage colors={colors} icon="download" title={title}>
316
+ {state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
317
+ <PercentText colors={colors} percent={state.hotspotArtifactPercent} />
318
+ ) : null}
319
+ <ActivityIndicator size="large" color={colors.foreground} />
320
+ <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
321
+ </FlowPage>
322
+ )
323
+ }
324
+
325
+ if (state.screen === "updating") {
326
+ return (
327
+ <FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
328
+ {state.installingApkOnly ? (
329
+ <ActivityIndicator size="large" color={colors.foreground} />
330
+ ) : (
331
+ <>
332
+ <PercentText colors={colors} percent={state.progress ?? 0} />
333
+ <View style={[styles.progressTrack, {backgroundColor: colors.border}]}>
334
+ <View
335
+ style={[styles.progressFill, {backgroundColor: colors.primary, width: `${state.progress ?? 0}%`}]}
336
+ />
337
+ </View>
338
+ </>
339
+ )}
340
+ <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
341
+ {state.versionChange && state.phase === "install" ? (
342
+ <BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
343
+ ) : null}
344
+ </FlowPage>
345
+ )
346
+ }
347
+
348
+ if (state.screen === "restarting") {
349
+ return (
350
+ <FlowPage
351
+ actions={
352
+ <FlowButton colors={colors} disabled={state.continueDisabled} label="Continue" onPress={controller.finish} />
353
+ }
354
+ colors={colors}
355
+ icon="check"
356
+ title="Update Installed"
357
+ />
358
+ )
359
+ }
360
+
361
+ if (state.screen === "complete") {
362
+ const title = state.versionChangeConverged
363
+ ? translate("ota:versionChangeComplete")
364
+ : state.versionChange
365
+ ? translate("ota:versionChangeFirmwarePassComplete")
366
+ : "Update complete!"
367
+ const message = state.versionChangeConverged
368
+ ? translate("ota:versionChangeCompleteMessage")
369
+ : state.versionChange
370
+ ? translate("ota:versionChangeFirmwarePassCompleteMessage")
371
+ : "Your glasses are up to date."
372
+ return (
373
+ <FlowPage
374
+ actions={
375
+ <FlowButton
376
+ colors={colors}
377
+ label={state.versionChange && !state.versionChangeConverged ? "Continue" : "Done"}
378
+ onPress={controller.finish}
379
+ />
380
+ }
381
+ colors={colors}
382
+ icon="check"
383
+ title={title}>
384
+ <BodyText colors={colors}>{message}</BodyText>
385
+ {state.releaseTransition ? (
386
+ <BodyText colors={colors}>
387
+ {translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
388
+ </BodyText>
389
+ ) : null}
390
+ <ChangelogList changelogs={state.changelogs} colors={colors} />
391
+ </FlowPage>
392
+ )
393
+ }
394
+
395
+ if (state.screen === "failed") {
396
+ return (
397
+ <FlowPage
398
+ actions={
399
+ <>
400
+ <FlowButton
401
+ colors={colors}
402
+ label={state.canRetry ? "Retry" : "Done"}
403
+ onPress={state.canRetry ? controller.retryInstall : controller.finish}
404
+ />
405
+ {state.canOpenWifiSetup ? (
406
+ <FlowButton colors={colors} label="Change WiFi" onPress={controller.openWifiSetup} secondary />
407
+ ) : null}
408
+ </>
409
+ }
410
+ colors={colors}
411
+ icon="alert"
412
+ title="Update Failed">
413
+ <BodyText colors={colors}>{state.error?.message}</BodyText>
414
+ </FlowPage>
415
+ )
416
+ }
417
+
418
+ return (
419
+ <FlowPage
420
+ actions={
421
+ superMode ? (
422
+ <FlowButton colors={colors} label="Skip (super)" onPress={controller.discard} secondary />
423
+ ) : undefined
424
+ }
425
+ colors={colors}
426
+ icon="bluetooth"
427
+ title="Glasses disconnected">
428
+ <BodyText colors={colors}>Reconnecting...</BodyText>
429
+ <ActivityIndicator size="large" color={colors.foreground} />
430
+ </FlowPage>
431
+ )
432
+ }
433
+
434
+ type FlowPageProps = {
435
+ actions?: React.ReactNode
436
+ children?: React.ReactNode
437
+ colors: MentraLiveOtaFlowTheme
438
+ icon: "alert" | "bluetooth" | "check" | "download" | "settings"
439
+ title: string
440
+ }
441
+
442
+ function FlowPage({actions, children, colors, icon, title}: FlowPageProps) {
443
+ return (
444
+ <View style={styles.page} testID="mentra-live-ota-flow">
445
+ <View style={styles.centerContent}>
446
+ <FlowIcon colors={colors} name={icon} />
447
+ <Text style={[styles.title, {color: colors.foreground}]}>{title}</Text>
448
+ {children}
449
+ </View>
450
+ {actions ? <View style={styles.actions}>{actions}</View> : <View style={styles.actionSpacer} />}
451
+ </View>
452
+ )
453
+ }
454
+
455
+ function BodyText({children, colors}: {children: React.ReactNode; colors: MentraLiveOtaFlowTheme}) {
456
+ return <Text style={[styles.body, {color: colors.textDim}]}>{children}</Text>
457
+ }
458
+
459
+ function PercentText({colors, percent}: {colors: MentraLiveOtaFlowTheme; percent: number}) {
460
+ return <Text style={[styles.percent, {color: colors.primary}]}>{Math.round(percent)}%</Text>
461
+ }
462
+
463
+ function ChangelogList({
464
+ changelogs,
465
+ colors,
466
+ }: {
467
+ changelogs: MentraLiveOtaController["state"]["changelogs"]
468
+ colors: MentraLiveOtaFlowTheme
469
+ }) {
470
+ if (changelogs.length === 0) return null
471
+ return (
472
+ <ScrollView contentContainerStyle={styles.changelogContent} style={styles.changelogList}>
473
+ {changelogs.map((entry) => (
474
+ <View key={entry.version} style={styles.changelogEntry}>
475
+ <Text selectable style={[styles.changelogVersion, {color: colors.foreground}]}>
476
+ {entry.version}
477
+ </Text>
478
+ <Text selectable style={[styles.changelogMarkdown, {color: colors.textDim}]}>
479
+ {entry.markdown}
480
+ </Text>
481
+ </View>
482
+ ))}
483
+ </ScrollView>
484
+ )
485
+ }
486
+
487
+ function FlowButton({
488
+ colors,
489
+ disabled = false,
490
+ label,
491
+ onPress,
492
+ secondary = false,
493
+ }: {
494
+ colors: MentraLiveOtaFlowTheme
495
+ disabled?: boolean
496
+ label: string
497
+ onPress: () => void
498
+ secondary?: boolean
499
+ }) {
500
+ return (
501
+ <Pressable
502
+ accessibilityRole="button"
503
+ disabled={disabled}
504
+ onPress={onPress}
505
+ style={({pressed}) => [
506
+ styles.button,
507
+ {
508
+ backgroundColor: secondary ? colors.background : colors.foreground,
509
+ borderColor: secondary ? colors.border : colors.foreground,
510
+ opacity: disabled ? 0.45 : pressed ? 0.75 : 1,
511
+ },
512
+ ]}
513
+ testID={`button-${label}`}>
514
+ <Text style={[styles.buttonText, {color: secondary ? colors.foreground : colors.background}]}>{label}</Text>
515
+ </Pressable>
516
+ )
517
+ }
518
+
519
+ function GlasslyMark({color}: {color: string}) {
520
+ return (
521
+ <Svg width={33} height={18} viewBox="0 0 50 27" fill="none">
522
+ <Rect y={14.8072} width={11.8457} height={11.8457} fill={color} />
523
+ <Path d="M9.36639 0L30.7163 14.8072V26.6529L9.36639 11.8457V0Z" fill={color} />
524
+ <Path d="M28.6501 0L50 14.8072V26.6529L28.6501 11.8457V0Z" fill={color} />
525
+ </Svg>
526
+ )
527
+ }
528
+
529
+ function FlowIcon({colors, name}: {colors: MentraLiveOtaFlowTheme; name: FlowPageProps["icon"]}) {
530
+ const color = name === "alert" || name === "bluetooth" ? colors.error : colors.primary
531
+ const glyph =
532
+ name === "check" ? "✓" : name === "alert" ? "!" : name === "settings" ? "⚙" : name === "bluetooth" ? "⌁" : "↓"
533
+ return <Text style={[styles.icon, {color}]}>{glyph}</Text>
534
+ }
535
+
536
+ const styles = StyleSheet.create({
537
+ safeArea: {flex: 1},
538
+ header: {
539
+ alignItems: "center",
540
+ flexDirection: "row",
541
+ height: 48,
542
+ justifyContent: "space-between",
543
+ paddingHorizontal: 20,
544
+ },
545
+ page: {flex: 1, paddingBottom: 24, paddingHorizontal: 24},
546
+ centerContent: {alignItems: "center", flex: 1, gap: 16, justifyContent: "center"},
547
+ actionSpacer: {height: 48},
548
+ actions: {gap: 12},
549
+ icon: {fontSize: 64, fontWeight: "500", lineHeight: 72, textAlign: "center"},
550
+ title: {fontSize: 20, fontWeight: "600", textAlign: "center"},
551
+ body: {fontSize: 14, lineHeight: 20, maxWidth: 420, textAlign: "center"},
552
+ percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
553
+ progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
554
+ progressFill: {borderRadius: 4, height: 8},
555
+ changelogList: {flexShrink: 1, maxHeight: 260, maxWidth: 420, width: "100%"},
556
+ changelogContent: {gap: 18, paddingVertical: 4},
557
+ changelogEntry: {gap: 8},
558
+ changelogVersion: {fontSize: 16, fontWeight: "600"},
559
+ changelogMarkdown: {fontSize: 14, lineHeight: 20},
560
+ button: {
561
+ alignItems: "center",
562
+ borderRadius: 50,
563
+ borderWidth: 1,
564
+ justifyContent: "center",
565
+ minHeight: 44,
566
+ paddingHorizontal: 20,
567
+ },
568
+ buttonText: {fontSize: 14, fontWeight: "500"},
569
+ })
@@ -0,0 +1,21 @@
1
+ export {
2
+ MentraLiveOtaFlow,
3
+ type MentraLiveOtaFlowPage,
4
+ type MentraLiveOtaFlowProps,
5
+ type MentraLiveOtaFlowTheme,
6
+ type MentraLiveOtaFlowTranslate,
7
+ } from "./MentraLiveOtaFlow"
8
+ export {
9
+ useMentraLiveOta,
10
+ type MentraLiveOtaController,
11
+ type MentraLiveOtaError,
12
+ type MentraLiveOtaErrorCode,
13
+ type MentraLiveOtaHotspotPhase,
14
+ type MentraLiveOtaInstallPhase,
15
+ type MentraLiveOtaReleaseTransition,
16
+ type MentraLiveOtaScreen,
17
+ type MentraLiveOtaState,
18
+ type MentraLiveOtaStep,
19
+ type MentraLiveOtaTransport,
20
+ type UseMentraLiveOtaOptions,
21
+ } from "./useMentraLiveOta"
@@ -0,0 +1,16 @@
1
+ import {useEffect, useRef, useState} from "react"
2
+
3
+ export function useEngineSnapshot<T>(getSnapshot: () => T, subscribe: (onChange: () => void) => () => void): T {
4
+ const getSnapshotRef = useRef(getSnapshot)
5
+ const subscribeRef = useRef(subscribe)
6
+ getSnapshotRef.current = getSnapshot
7
+ subscribeRef.current = subscribe
8
+ const [snapshot, setSnapshot] = useState(() => getSnapshot())
9
+
10
+ useEffect(() => {
11
+ setSnapshot(getSnapshotRef.current())
12
+ return subscribeRef.current(() => setSnapshot(getSnapshotRef.current()))
13
+ }, [])
14
+
15
+ return snapshot
16
+ }