@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.101

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 (343) hide show
  1. package/README.md +57 -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 +30 -18
  27. package/build/engine.d.ts.map +1 -1
  28. package/build/engine.js +12 -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 +11 -4
  32. package/build/facades/display.d.ts.map +1 -1
  33. package/build/facades/display.js +12 -2
  34. package/build/facades/display.js.map +1 -1
  35. package/build/facades/displayMirror.d.ts +2 -6
  36. package/build/facades/displayMirror.d.ts.map +1 -1
  37. package/build/facades/displayMirror.js +7 -14
  38. package/build/facades/displayMirror.js.map +1 -1
  39. package/build/facades/glasses.d.ts +6 -13
  40. package/build/facades/glasses.d.ts.map +1 -1
  41. package/build/facades/glasses.js +7 -3
  42. package/build/facades/glasses.js.map +1 -1
  43. package/build/facades/ota.d.ts +21 -21
  44. package/build/facades/ota.d.ts.map +1 -1
  45. package/build/facades/ota.js +26 -10
  46. package/build/facades/ota.js.map +1 -1
  47. package/build/facades/pairing.d.ts +11 -5
  48. package/build/facades/pairing.d.ts.map +1 -1
  49. package/build/facades/pairing.js +93 -17
  50. package/build/facades/pairing.js.map +1 -1
  51. package/build/facades/phoneNotifications.d.ts +12 -2
  52. package/build/facades/phoneNotifications.d.ts.map +1 -1
  53. package/build/facades/phoneNotifications.js +30 -10
  54. package/build/facades/phoneNotifications.js.map +1 -1
  55. package/build/facades/speakers.d.ts +1 -1
  56. package/build/facades/speakers.d.ts.map +1 -1
  57. package/build/facades/speakers.js.map +1 -1
  58. package/build/facades/speech.d.ts +7 -6
  59. package/build/facades/speech.d.ts.map +1 -1
  60. package/build/facades/speech.js +1 -1
  61. package/build/facades/speech.js.map +1 -1
  62. package/build/generated/releaseMetadata.d.ts +12 -0
  63. package/build/generated/releaseMetadata.d.ts.map +1 -0
  64. package/build/generated/releaseMetadata.js +10 -0
  65. package/build/generated/releaseMetadata.js.map +1 -0
  66. package/build/index.d.ts +9 -6
  67. package/build/index.d.ts.map +1 -1
  68. package/build/index.js +7 -5
  69. package/build/index.js.map +1 -1
  70. package/build/react/MentraLiveOtaFlow.d.ts +44 -0
  71. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  72. package/build/react/MentraLiveOtaFlow.js +322 -0
  73. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  74. package/build/react/index.d.ts +3 -0
  75. package/build/react/index.d.ts.map +1 -0
  76. package/build/react/index.js +3 -0
  77. package/build/react/index.js.map +1 -0
  78. package/build/react/useEngineSnapshot.d.ts +2 -0
  79. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  80. package/build/react/useEngineSnapshot.js +14 -0
  81. package/build/react/useEngineSnapshot.js.map +1 -0
  82. package/build/react/useMentraLiveOta.d.ts +84 -0
  83. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  84. package/build/react/useMentraLiveOta.js +627 -0
  85. package/build/react/useMentraLiveOta.js.map +1 -0
  86. package/build/services/AppRegistry.d.ts +8 -0
  87. package/build/services/AppRegistry.d.ts.map +1 -1
  88. package/build/services/AppRegistry.js +15 -0
  89. package/build/services/AppRegistry.js.map +1 -1
  90. package/build/services/BlobStore.d.ts +2 -0
  91. package/build/services/BlobStore.d.ts.map +1 -1
  92. package/build/services/BlobStore.js +63 -10
  93. package/build/services/BlobStore.js.map +1 -1
  94. package/build/services/CustomDashboardModel.d.ts +48 -0
  95. package/build/services/CustomDashboardModel.d.ts.map +1 -0
  96. package/build/services/CustomDashboardModel.js +73 -0
  97. package/build/services/CustomDashboardModel.js.map +1 -0
  98. package/build/services/CustomDashboardService.d.ts +55 -0
  99. package/build/services/CustomDashboardService.d.ts.map +1 -0
  100. package/build/services/CustomDashboardService.js +520 -0
  101. package/build/services/CustomDashboardService.js.map +1 -0
  102. package/build/services/DashboardCoordinator.d.ts +26 -0
  103. package/build/services/DashboardCoordinator.d.ts.map +1 -0
  104. package/build/services/DashboardCoordinator.js +125 -0
  105. package/build/services/DashboardCoordinator.js.map +1 -0
  106. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  107. package/build/services/DeviceEventRouter.js +45 -6
  108. package/build/services/DeviceEventRouter.js.map +1 -1
  109. package/build/services/DisplayProcessor.d.ts +3 -3
  110. package/build/services/DisplayProcessor.js +3 -3
  111. package/build/services/DisplayProcessor.js.map +1 -1
  112. package/build/services/GestureDotsService.d.ts +40 -0
  113. package/build/services/GestureDotsService.d.ts.map +1 -0
  114. package/build/services/GestureDotsService.js +167 -0
  115. package/build/services/GestureDotsService.js.map +1 -0
  116. package/build/services/GlassesStatusProjection.d.ts +15 -1
  117. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  118. package/build/services/GlassesStatusProjection.js +18 -11
  119. package/build/services/GlassesStatusProjection.js.map +1 -1
  120. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  121. package/build/services/HotspotOtaTransport.js +8 -7
  122. package/build/services/HotspotOtaTransport.js.map +1 -1
  123. package/build/services/LocalDisplayManager.d.ts +55 -3
  124. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  125. package/build/services/LocalDisplayManager.js +196 -16
  126. package/build/services/LocalDisplayManager.js.map +1 -1
  127. package/build/services/LocalMiniappRuntime.d.ts +13 -0
  128. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  129. package/build/services/LocalMiniappRuntime.js +32 -0
  130. package/build/services/LocalMiniappRuntime.js.map +1 -1
  131. package/build/services/MiniappExitGesture.d.ts +33 -0
  132. package/build/services/MiniappExitGesture.d.ts.map +1 -0
  133. package/build/services/MiniappExitGesture.js +54 -0
  134. package/build/services/MiniappExitGesture.js.map +1 -0
  135. package/build/services/OtaAutoChain.d.ts +41 -0
  136. package/build/services/OtaAutoChain.d.ts.map +1 -0
  137. package/build/services/OtaAutoChain.js +91 -0
  138. package/build/services/OtaAutoChain.js.map +1 -0
  139. package/build/services/OtaErrorMapping.d.ts +18 -0
  140. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  141. package/build/services/OtaErrorMapping.js +63 -0
  142. package/build/services/OtaErrorMapping.js.map +1 -0
  143. package/build/services/OtaInstallCoordinator.d.ts +26 -3
  144. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  145. package/build/services/OtaInstallCoordinator.js +38 -13
  146. package/build/services/OtaInstallCoordinator.js.map +1 -1
  147. package/build/services/OtaService.d.ts.map +1 -1
  148. package/build/services/OtaService.js +1 -1
  149. package/build/services/OtaService.js.map +1 -1
  150. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  151. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  152. package/build/services/OtaUpdateCheckService.js +28 -16
  153. package/build/services/OtaUpdateCheckService.js.map +1 -1
  154. package/build/services/PhoneNotificationsSync.d.ts +13 -0
  155. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  156. package/build/services/PhoneNotificationsSync.js +101 -9
  157. package/build/services/PhoneNotificationsSync.js.map +1 -1
  158. package/build/services/PhonePhotoCoordinator.js +1 -1
  159. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  160. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  161. package/build/services/PhoneStreamCoordinator.js +9 -6
  162. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  163. package/build/services/SceneRenderer.d.ts.map +1 -1
  164. package/build/services/SceneRenderer.js +5 -0
  165. package/build/services/SceneRenderer.js.map +1 -1
  166. package/build/services/StreamLifecycleController.d.ts +4 -0
  167. package/build/services/StreamLifecycleController.d.ts.map +1 -1
  168. package/build/services/StreamLifecycleController.js +4 -0
  169. package/build/services/StreamLifecycleController.js.map +1 -1
  170. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  171. package/build/services/asg/localNetworkTransport.js +13 -12
  172. package/build/services/asg/localNetworkTransport.js.map +1 -1
  173. package/build/services/effectiveCapabilities.d.ts +1 -1
  174. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  175. package/build/services/effectiveCapabilities.js +26 -7
  176. package/build/services/effectiveCapabilities.js.map +1 -1
  177. package/build/services/gestureClaims.d.ts +18 -0
  178. package/build/services/gestureClaims.d.ts.map +1 -0
  179. package/build/services/gestureClaims.js +24 -0
  180. package/build/services/gestureClaims.js.map +1 -0
  181. package/build/services/gestureDotGlyphs.d.ts +8 -0
  182. package/build/services/gestureDotGlyphs.d.ts.map +1 -0
  183. package/build/services/gestureDotGlyphs.js +13 -0
  184. package/build/services/gestureDotGlyphs.js.map +1 -0
  185. package/build/services/glassesClockSync.d.ts.map +1 -1
  186. package/build/services/glassesClockSync.js +5 -1
  187. package/build/services/glassesClockSync.js.map +1 -1
  188. package/build/services/iosNotificationAppCatalog.d.ts +13 -0
  189. package/build/services/iosNotificationAppCatalog.d.ts.map +1 -0
  190. package/build/services/iosNotificationAppCatalog.js +28 -0
  191. package/build/services/iosNotificationAppCatalog.js.map +1 -0
  192. package/build/services/otaDisplayState.d.ts +1 -1
  193. package/build/services/otaDisplayState.d.ts.map +1 -1
  194. package/build/services/otaDisplayState.js.map +1 -1
  195. package/build/services/otaInstallPolicy.d.ts +5 -1
  196. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  197. package/build/services/otaInstallPolicy.js +4 -0
  198. package/build/services/otaInstallPolicy.js.map +1 -1
  199. package/build/services/otaLegacyMapping.d.ts +1 -1
  200. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  201. package/build/services/otaLegacyMapping.js.map +1 -1
  202. package/build/services/otaManifestPolicy.d.ts +14 -0
  203. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  204. package/build/services/otaManifestPolicy.js +20 -0
  205. package/build/services/otaManifestPolicy.js.map +1 -0
  206. package/build/services/otaManifestUrl.d.ts +2 -1
  207. package/build/services/otaManifestUrl.d.ts.map +1 -1
  208. package/build/services/otaManifestUrl.js +24 -32
  209. package/build/services/otaManifestUrl.js.map +1 -1
  210. package/build/services/otaReleaseVersion.d.ts +10 -0
  211. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  212. package/build/services/otaReleaseVersion.js +14 -0
  213. package/build/services/otaReleaseVersion.js.map +1 -0
  214. package/build/stores/appVisibility.d.ts +3 -0
  215. package/build/stores/appVisibility.d.ts.map +1 -0
  216. package/build/stores/appVisibility.js +3 -0
  217. package/build/stores/appVisibility.js.map +1 -0
  218. package/build/stores/apps.d.ts.map +1 -1
  219. package/build/stores/apps.js +7 -1
  220. package/build/stores/apps.js.map +1 -1
  221. package/build/stores/core.d.ts +3 -3
  222. package/build/stores/core.d.ts.map +1 -1
  223. package/build/stores/core.js.map +1 -1
  224. package/build/stores/display.d.ts +4 -4
  225. package/build/stores/display.d.ts.map +1 -1
  226. package/build/stores/display.js +12 -36
  227. package/build/stores/display.js.map +1 -1
  228. package/build/stores/glasses.d.ts +10 -4
  229. package/build/stores/glasses.d.ts.map +1 -1
  230. package/build/stores/glasses.js +2 -0
  231. package/build/stores/glasses.js.map +1 -1
  232. package/build/stores/settings.d.ts.map +1 -1
  233. package/build/stores/settings.js +140 -5
  234. package/build/stores/settings.js.map +1 -1
  235. package/build/types/applet.d.ts +3 -1
  236. package/build/types/applet.d.ts.map +1 -1
  237. package/build/types/applet.js +0 -3
  238. package/build/types/applet.js.map +1 -1
  239. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  240. package/build/types/capabilities/even-realities-g2.js +2 -2
  241. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  242. package/build/types/enums.d.ts +1 -5
  243. package/build/types/enums.d.ts.map +1 -1
  244. package/build/types/enums.js +1 -8
  245. package/build/types/enums.js.map +1 -1
  246. package/build/types/hardware.d.ts +1 -1
  247. package/build/types/hardware.d.ts.map +1 -1
  248. package/build/types/hardware.js +1 -0
  249. package/build/types/hardware.js.map +1 -1
  250. package/build/types/index.d.ts +3 -7
  251. package/build/types/index.d.ts.map +1 -1
  252. package/build/types/index.js +3 -7
  253. package/build/types/index.js.map +1 -1
  254. package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
  255. package/build/utils/display/composer/ColumnComposer.js +1 -1
  256. package/build/utils/display/composer/ColumnComposer.js.map +1 -1
  257. package/build/utils/display/scene/process.d.ts.map +1 -1
  258. package/build/utils/display/scene/process.js +4 -3
  259. package/build/utils/display/scene/process.js.map +1 -1
  260. package/build/utils/display/scene/types.d.ts +6 -2
  261. package/build/utils/display/scene/types.d.ts.map +1 -1
  262. package/build/utils/display/scene/types.js.map +1 -1
  263. package/package.json +69 -35
  264. package/src/bluetooth-sdk/debug.ts +1 -0
  265. package/src/bluetooth-sdk/index.ts +2 -0
  266. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  267. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  268. package/src/bluetooth-sdk/react.ts +1 -0
  269. package/src/bluetooth-sdk/types.ts +1 -0
  270. package/src/engine.ts +12 -3
  271. package/src/facades/display.ts +12 -2
  272. package/src/facades/displayMirror.ts +7 -15
  273. package/src/facades/glasses.ts +8 -4
  274. package/src/facades/ota.ts +36 -11
  275. package/src/facades/pairing.ts +103 -23
  276. package/src/facades/phoneNotifications.ts +32 -10
  277. package/src/facades/speakers.ts +1 -1
  278. package/src/facades/speech.ts +21 -7
  279. package/src/generated/releaseMetadata.ts +20 -0
  280. package/src/index.ts +31 -5
  281. package/src/react/MentraLiveOtaFlow.tsx +754 -0
  282. package/src/react/index.ts +21 -0
  283. package/src/react/useEngineSnapshot.ts +16 -0
  284. package/src/react/useMentraLiveOta.ts +793 -0
  285. package/src/services/AppRegistry.ts +16 -0
  286. package/src/services/BlobStore.ts +57 -10
  287. package/src/services/CustomDashboardModel.ts +83 -0
  288. package/src/services/CustomDashboardService.ts +544 -0
  289. package/src/services/DashboardCoordinator.ts +121 -0
  290. package/src/services/DeviceEventRouter.ts +48 -8
  291. package/src/services/DisplayProcessor.ts +3 -3
  292. package/src/services/GestureDotsService.ts +164 -0
  293. package/src/services/GlassesStatusProjection.ts +20 -11
  294. package/src/services/HotspotOtaTransport.ts +8 -7
  295. package/src/services/LocalDisplayManager.ts +198 -18
  296. package/src/services/LocalMiniappRuntime.ts +37 -0
  297. package/src/services/MiniappExitGesture.ts +76 -0
  298. package/src/services/OtaAutoChain.ts +130 -0
  299. package/src/services/OtaErrorMapping.ts +80 -0
  300. package/src/services/OtaInstallCoordinator.ts +40 -13
  301. package/src/services/OtaService.ts +1 -2
  302. package/src/services/OtaUpdateCheckService.ts +33 -19
  303. package/src/services/PhoneNotificationsSync.ts +125 -9
  304. package/src/services/PhonePhotoCoordinator.ts +1 -1
  305. package/src/services/PhoneStreamCoordinator.ts +10 -6
  306. package/src/services/SceneRenderer.ts +4 -0
  307. package/src/services/StreamLifecycleController.ts +4 -0
  308. package/src/services/asg/localNetworkTransport.ts +20 -19
  309. package/src/services/effectiveCapabilities.ts +27 -7
  310. package/src/services/gestureClaims.ts +28 -0
  311. package/src/services/gestureDotGlyphs.ts +15 -0
  312. package/src/services/glassesClockSync.ts +5 -1
  313. package/src/services/iosNotificationAppCatalog.ts +39 -0
  314. package/src/services/otaDisplayState.ts +1 -1
  315. package/src/services/otaInstallPolicy.ts +5 -1
  316. package/src/services/otaLegacyMapping.ts +1 -1
  317. package/src/services/otaManifestPolicy.ts +43 -0
  318. package/src/services/otaManifestUrl.ts +27 -36
  319. package/src/services/otaReleaseVersion.ts +22 -0
  320. package/src/stores/appVisibility.ts +3 -0
  321. package/src/stores/apps.ts +7 -1
  322. package/src/stores/core.ts +4 -4
  323. package/src/stores/display.ts +16 -48
  324. package/src/stores/glasses.ts +15 -6
  325. package/src/stores/settings.ts +140 -5
  326. package/src/types/applet.ts +3 -1
  327. package/src/types/capabilities/even-realities-g2.ts +3 -2
  328. package/src/types/enums.ts +1 -9
  329. package/src/types/hardware.ts +2 -1
  330. package/src/types/index.ts +3 -7
  331. package/src/utils/display/composer/ColumnComposer.ts +1 -1
  332. package/src/utils/display/scene/process.ts +4 -3
  333. package/src/utils/display/scene/types.ts +0 -0
  334. package/build/devtools.d.ts +0 -11
  335. package/build/devtools.d.ts.map +0 -1
  336. package/build/devtools.js +0 -13
  337. package/build/devtools.js.map +0 -1
  338. package/build/internal.d.ts +0 -67
  339. package/build/internal.d.ts.map +0 -1
  340. package/build/internal.js +0 -90
  341. package/build/internal.js.map +0 -1
  342. package/src/devtools.ts +0 -13
  343. package/src/internal.ts +0 -155
@@ -0,0 +1,754 @@
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 {useMarkdown, type MarkedStyles, type useMarkdownHookOptions} from "react-native-marked"
14
+ import {SafeAreaView} from "react-native-safe-area-context"
15
+ import Svg, {Path, Rect} from "react-native-svg"
16
+
17
+ import {
18
+ MINIMUM_OTA_BATTERY_LEVEL,
19
+ useMentraLiveOta,
20
+ type MentraLiveOtaController,
21
+ type MentraLiveOtaFlowPage,
22
+ } from "./useMentraLiveOta"
23
+
24
+ export type {MentraLiveOtaFlowPage} from "./useMentraLiveOta"
25
+
26
+ export type MentraLiveOtaFlowTheme = {
27
+ background: string
28
+ border: string
29
+ error: string
30
+ foreground: string
31
+ primary: string
32
+ primaryText: string
33
+ textDim: string
34
+ }
35
+
36
+ export type MentraLiveOtaFlowTranslate = (key: string, options?: Record<string, string>) => string
37
+
38
+ export type MentraLiveOtaFlowProps = {
39
+ /** Display name used in update copy. */
40
+ deviceName?: string
41
+ /** Entry page. `progress` exists for recovery/deep-link compatibility. */
42
+ initialPage?: MentraLiveOtaFlowPage
43
+ /** Start the OTA-only projections. Full Engine hosts should pass false. */
44
+ initializeRuntime?: boolean
45
+ /** Called after the final check or when the user leaves an optional update. */
46
+ onFinished: () => void
47
+ /** Host-owned Wi-Fi setup for glasses that do not support hotspot OTA. */
48
+ onOpenWifiSetup: () => void
49
+ /** Lets a host coordinate its global connection overlay with OTA progress and firmware restarts. */
50
+ onFirmwareRestartingChange?: (restarting: boolean, progressActive: boolean) => void
51
+ /** Enables the existing developer-only escape hatches. */
52
+ allowDevSkip?: boolean
53
+ /** Enables the existing super-mode interrupted-session escape hatch. */
54
+ superMode?: boolean
55
+ /** Optional host localization. Defaults to the Glassly App English OTA copy. */
56
+ translate?: MentraLiveOtaFlowTranslate
57
+ /** Optional host theme. Both hosts use the same layout and state machine. */
58
+ theme?: Partial<MentraLiveOtaFlowTheme>
59
+ style?: StyleProp<ViewStyle>
60
+ }
61
+
62
+ const DEFAULT_THEME: MentraLiveOtaFlowTheme = {
63
+ background: "#FFFFFF",
64
+ border: "#D7DFDA",
65
+ error: "#C43131",
66
+ foreground: "#0E2C1A",
67
+ primary: "#00B869",
68
+ primaryText: "#FFFFFF",
69
+ textDim: "#66736B",
70
+ }
71
+
72
+ const ENGLISH_COPY: Record<string, string> = {
73
+ "common:continue": "Continue",
74
+ "common:done": "Done",
75
+ "ota:checkingForUpdates": "Checking for updates",
76
+ "ota:checkingForUpdatesMessage":
77
+ "Connected devices will perform automatic updates. Automatic updates can be disabled in Device Settings",
78
+ "ota:finishingUpdate": "Finishing your update",
79
+ "ota:checkingAdditionalUpdates": "Checking whether your glasses need any additional updates.",
80
+ "ota:updateAvailable": "{{deviceName}} Update Available",
81
+ "ota:batteryRequiredTitle": "Charge {{deviceName}} to Update",
82
+ "ota:batteryRequiredMessage":
83
+ "{{deviceName}} is currently at {{batteryLevel}}%. Charge it to at least {{minimumBatteryLevel}}% before updating.",
84
+ "ota:batteryRequiredLiveUpdate": "This screen will update automatically as the battery charges.",
85
+ "ota:updateConnectWifi": "Connect your {{deviceName}} to WiFi to install the update.",
86
+ "ota:wifiRequiredTitle": "WiFi Needed for Update",
87
+ "ota:updateDescription":
88
+ "A new update is available for your glasses. We recommend updating now for the best experience.",
89
+ "ota:updateSequenceMessage":
90
+ "Your glasses may install more than one update and restart several times. Keep them nearby until finished.",
91
+ "ota:releaseTransition": "{{fromVersion}} → {{toVersion}}",
92
+ "ota:releaseTransitionUnknown": "Current version unknown → {{toVersion}}",
93
+ "ota:updatedToVersion": "Updated to {{version}}",
94
+ "ota:downgradeAvailable": "{{deviceName}} Version Change Required",
95
+ "ota:downgradeDescription":
96
+ "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.",
97
+ "ota:updateNow": "Update Now",
98
+ "ota:setupWifi": "Setup WiFi",
99
+ "ota:updateLater": "Later",
100
+ "ota:updateComplete": "Update complete",
101
+ "ota:whatsNew": "What's new",
102
+ "ota:upToDate": "Up To Date",
103
+ "ota:devBuild": "Development Build",
104
+ "ota:devBuildNoOta":
105
+ "This mobile app is a development build, so automatic glasses updates are disabled. Use the developer settings manifest override to update them manually.",
106
+ "ota:noUpdatesAvailable": "Your glasses are running the latest version.",
107
+ "ota:checkFailed": "Check Failed",
108
+ "ota:checkFailedMessage": "Couldn't check for updates. Please check your connection and try again.",
109
+ "ota:updateInfoUnavailable": "Update Info Unavailable",
110
+ "ota:updateInfoUnavailableMessage":
111
+ "Update information for this version of the app is unavailable. Please check the app store for a newer version of the Glassly App.",
112
+ "ota:downgradeDuration": "Your glasses will restart twice — this may take up to 2 minutes.",
113
+ "ota:versionChangeRestarting": "Installing a different version…",
114
+ "ota:versionChangeVerifying": "Verifying your glasses…",
115
+ "ota:versionChangeKeepNearby": "Keep your glasses nearby and connected. They will restart on their own.",
116
+ "ota:restartingGlasses": "Restarting {{deviceName}}…",
117
+ "ota:restartingGlassesMessage":
118
+ "The update is installed. Keep your glasses nearby and leave this screen open while they finish starting.",
119
+ "ota:restartingGlassesAutomatic": "We'll continue automatically when they're ready.",
120
+ "ota:versionChangeComplete": "Version Change Complete",
121
+ "ota:versionChangeCompleteMessage":
122
+ "Your glasses are now on the required version. Their settings were reset and are being restored automatically.",
123
+ "ota:versionChangeFirmwarePassComplete": "Firmware updated",
124
+ "ota:versionChangeFirmwarePassCompleteMessage":
125
+ "Your glasses restarted with new firmware. One more step: they'll now continue to the required version.",
126
+ }
127
+
128
+ function defaultTranslate(key: string, options?: Record<string, string>): string {
129
+ let value = ENGLISH_COPY[key] ?? key
130
+ for (const [name, replacement] of Object.entries(options ?? {})) {
131
+ value = value.replaceAll(`{{${name}}}`, replacement)
132
+ }
133
+ return value
134
+ }
135
+
136
+ export function MentraLiveOtaFlow({
137
+ allowDevSkip = typeof __DEV__ !== "undefined" && __DEV__,
138
+ deviceName = "Mentra Live",
139
+ initialPage = "check",
140
+ initializeRuntime = true,
141
+ onFinished,
142
+ onFirmwareRestartingChange,
143
+ onOpenWifiSetup,
144
+ style,
145
+ superMode = false,
146
+ theme,
147
+ translate = defaultTranslate,
148
+ }: MentraLiveOtaFlowProps) {
149
+ const colors = useMemo(() => ({...DEFAULT_THEME, ...theme}), [theme])
150
+ const controller = useMentraLiveOta({
151
+ initialPage,
152
+ initializeRuntime,
153
+ onFinished,
154
+ onFirmwareRestartingChange,
155
+ onOpenWifiSetup,
156
+ })
157
+
158
+ return (
159
+ <SafeAreaView style={[styles.safeArea, {backgroundColor: colors.background}, style]}>
160
+ <View style={styles.header}>
161
+ <View />
162
+ <GlasslyMark color={colors.primary} />
163
+ </View>
164
+ <OtaFlowContent
165
+ allowDevSkip={allowDevSkip}
166
+ colors={colors}
167
+ controller={controller}
168
+ deviceName={deviceName}
169
+ superMode={superMode}
170
+ translate={translate}
171
+ />
172
+ </SafeAreaView>
173
+ )
174
+ }
175
+
176
+ function OtaFlowContent({
177
+ allowDevSkip,
178
+ colors,
179
+ controller,
180
+ deviceName,
181
+ superMode,
182
+ translate,
183
+ }: {
184
+ allowDevSkip: boolean
185
+ colors: MentraLiveOtaFlowTheme
186
+ controller: MentraLiveOtaController
187
+ deviceName: string
188
+ superMode: boolean
189
+ translate: MentraLiveOtaFlowTranslate
190
+ }) {
191
+ const {state} = controller
192
+
193
+ if (state.screen === "initializing") {
194
+ return (
195
+ <FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
196
+ <ActivityIndicator size="large" color={colors.foreground} />
197
+ </FlowPage>
198
+ )
199
+ }
200
+
201
+ if (state.screen === "checking") {
202
+ return (
203
+ <FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
204
+ <BodyText colors={colors}>{translate("ota:checkingForUpdatesMessage")}</BodyText>
205
+ <ActivityIndicator size="large" color={colors.foreground} />
206
+ </FlowPage>
207
+ )
208
+ }
209
+
210
+ if (state.screen === "battery_required") {
211
+ return (
212
+ <FlowPage
213
+ actions={
214
+ <>
215
+ <FlowButton colors={colors} disabled label={translate("ota:updateNow")} onPress={controller.install} />
216
+ {state.canDismiss ? (
217
+ <FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary />
218
+ ) : null}
219
+ </>
220
+ }
221
+ colors={colors}
222
+ icon="alert"
223
+ title={translate("ota:batteryRequiredTitle", {deviceName})}>
224
+ <BodyText colors={colors}>
225
+ {translate("ota:batteryRequiredMessage", {
226
+ batteryLevel: String(state.batteryLevel),
227
+ deviceName,
228
+ minimumBatteryLevel: String(MINIMUM_OTA_BATTERY_LEVEL),
229
+ })}
230
+ </BodyText>
231
+ <BodyText colors={colors}>{translate("ota:batteryRequiredLiveUpdate")}</BodyText>
232
+ </FlowPage>
233
+ )
234
+ }
235
+
236
+ if (state.screen === "finishing") {
237
+ return (
238
+ <FlowPage colors={colors} icon="download" title={translate("ota:finishingUpdate")}>
239
+ <BodyText colors={colors}>{translate("ota:checkingAdditionalUpdates")}</BodyText>
240
+ <ActivityIndicator size="large" color={colors.foreground} />
241
+ </FlowPage>
242
+ )
243
+ }
244
+
245
+ if (state.screen === "update_available" || state.screen === "wifi_required") {
246
+ const titleKey =
247
+ state.screen === "wifi_required"
248
+ ? "ota:wifiRequiredTitle"
249
+ : state.versionChange
250
+ ? "ota:downgradeAvailable"
251
+ : "ota:updateAvailable"
252
+ return (
253
+ <FlowPage
254
+ colors={colors}
255
+ icon="download"
256
+ title={translate(titleKey, {deviceName})}
257
+ actions={
258
+ <>
259
+ <FlowButton
260
+ colors={colors}
261
+ disabled={!state.wifiStatusKnown}
262
+ label={translate(state.screen === "wifi_required" ? "ota:setupWifi" : "ota:updateNow")}
263
+ onPress={state.screen === "wifi_required" ? controller.openWifiSetup : controller.install}
264
+ />
265
+ {state.canDismiss ? (
266
+ <FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary />
267
+ ) : null}
268
+ {allowDevSkip && state.updateRequired ? (
269
+ <FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
270
+ ) : null}
271
+ </>
272
+ }>
273
+ {state.releaseTransition ? (
274
+ <BodyText colors={colors}>
275
+ {state.releaseTransition.fromVersion
276
+ ? translate("ota:releaseTransition", {
277
+ fromVersion: state.releaseTransition.fromVersion,
278
+ toVersion: state.releaseTransition.toVersion,
279
+ })
280
+ : translate("ota:releaseTransitionUnknown", {toVersion: state.releaseTransition.toVersion})}
281
+ </BodyText>
282
+ ) : null}
283
+ <BodyText colors={colors}>
284
+ {state.screen === "wifi_required"
285
+ ? translate("ota:updateConnectWifi", {deviceName})
286
+ : translate(state.versionChange ? "ota:downgradeDescription" : "ota:updateDescription")}
287
+ </BodyText>
288
+ <BodyText colors={colors}>{translate("ota:updateSequenceMessage")}</BodyText>
289
+ </FlowPage>
290
+ )
291
+ }
292
+
293
+ if (state.screen === "dev_build") {
294
+ return (
295
+ <FlowPage
296
+ actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
297
+ colors={colors}
298
+ icon="settings"
299
+ title={translate("ota:devBuild")}>
300
+ <BodyText colors={colors}>{translate("ota:devBuildNoOta")}</BodyText>
301
+ </FlowPage>
302
+ )
303
+ }
304
+
305
+ if (state.screen === "up_to_date") {
306
+ return (
307
+ <FlowPage
308
+ actions={
309
+ <FlowButton
310
+ colors={colors}
311
+ label={translate(state.completedUpdate ? "common:done" : "common:continue")}
312
+ onPress={controller.finish}
313
+ />
314
+ }
315
+ colors={colors}
316
+ contentAlignment={state.changelogs.length > 0 ? "top" : "center"}
317
+ icon="check"
318
+ title={translate(state.completedUpdate ? "ota:updateComplete" : "ota:upToDate")}>
319
+ <BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
320
+ {state.releaseTransition ? (
321
+ <BodyText colors={colors}>
322
+ {translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
323
+ </BodyText>
324
+ ) : null}
325
+ <ChangelogList changelogs={state.changelogs} colors={colors} title={translate("ota:whatsNew")} />
326
+ </FlowPage>
327
+ )
328
+ }
329
+
330
+ if (state.screen === "update_info_unavailable") {
331
+ return (
332
+ <FlowPage
333
+ actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
334
+ colors={colors}
335
+ icon="alert"
336
+ title={translate("ota:updateInfoUnavailable")}>
337
+ <BodyText colors={colors}>{translate("ota:updateInfoUnavailableMessage")}</BodyText>
338
+ </FlowPage>
339
+ )
340
+ }
341
+
342
+ if (state.screen === "check_failed") {
343
+ return (
344
+ <FlowPage
345
+ actions={
346
+ <>
347
+ <FlowButton colors={colors} label="Retry" onPress={controller.retryCheck} />
348
+ {allowDevSkip ? (
349
+ <FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
350
+ ) : null}
351
+ </>
352
+ }
353
+ colors={colors}
354
+ icon="alert"
355
+ title={translate("ota:checkFailed")}>
356
+ <BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
357
+ </FlowPage>
358
+ )
359
+ }
360
+
361
+ if (state.versionChangePhase === "restarting" || state.versionChangePhase === "verifying") {
362
+ return (
363
+ <FlowPage
364
+ colors={colors}
365
+ icon="download"
366
+ title={translate(
367
+ state.versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting",
368
+ )}>
369
+ <ActivityIndicator size="large" color={colors.foreground} />
370
+ <BodyText colors={colors}>{translate("ota:versionChangeKeepNearby")}</BodyText>
371
+ <BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
372
+ </FlowPage>
373
+ )
374
+ }
375
+
376
+ if (state.screen === "starting" || state.screen === "preparing_hotspot") {
377
+ const title =
378
+ state.hotspotPhase === "downloading"
379
+ ? "Downloading update to phone..."
380
+ : state.hotspotPhase === "starting_hotspot"
381
+ ? "Starting glasses hotspot..."
382
+ : state.hotspotPhase === "joining_hotspot"
383
+ ? "Connecting phone to glasses..."
384
+ : "Starting update..."
385
+ return (
386
+ <FlowPage colors={colors} icon="download" title={title}>
387
+ {state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
388
+ <PercentText colors={colors} percent={state.hotspotArtifactPercent} />
389
+ ) : null}
390
+ <ActivityIndicator size="large" color={colors.foreground} />
391
+ <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
392
+ </FlowPage>
393
+ )
394
+ }
395
+
396
+ if (state.screen === "updating") {
397
+ return (
398
+ <FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
399
+ {state.installingApkOnly ? (
400
+ <ActivityIndicator size="large" color={colors.foreground} />
401
+ ) : (
402
+ <>
403
+ <PercentText colors={colors} percent={state.progress ?? 0} />
404
+ <View style={[styles.progressTrack, {backgroundColor: colors.border}]}>
405
+ <View
406
+ style={[styles.progressFill, {backgroundColor: colors.primary, width: `${state.progress ?? 0}%`}]}
407
+ />
408
+ </View>
409
+ </>
410
+ )}
411
+ <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
412
+ {state.versionChange && state.phase === "install" ? (
413
+ <BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
414
+ ) : null}
415
+ </FlowPage>
416
+ )
417
+ }
418
+
419
+ if (state.screen === "restarting") {
420
+ return (
421
+ <FlowPage colors={colors} icon="download" title={translate("ota:restartingGlasses", {deviceName})}>
422
+ <ActivityIndicator size="large" color={colors.foreground} />
423
+ <BodyText colors={colors}>{translate("ota:restartingGlassesMessage")}</BodyText>
424
+ <BodyText colors={colors}>{translate("ota:restartingGlassesAutomatic")}</BodyText>
425
+ </FlowPage>
426
+ )
427
+ }
428
+
429
+ if (state.screen === "complete") {
430
+ const title = state.versionChangeConverged
431
+ ? translate("ota:versionChangeComplete")
432
+ : state.versionChange
433
+ ? translate("ota:versionChangeFirmwarePassComplete")
434
+ : "Update complete!"
435
+ const message = state.versionChangeConverged
436
+ ? translate("ota:versionChangeCompleteMessage")
437
+ : state.versionChange
438
+ ? translate("ota:versionChangeFirmwarePassCompleteMessage")
439
+ : "Your glasses are up to date."
440
+ return (
441
+ <FlowPage
442
+ actions={
443
+ <FlowButton
444
+ colors={colors}
445
+ label={state.versionChange && !state.versionChangeConverged ? "Continue" : "Done"}
446
+ onPress={controller.finish}
447
+ />
448
+ }
449
+ colors={colors}
450
+ contentAlignment={state.changelogs.length > 0 ? "top" : "center"}
451
+ icon="check"
452
+ title={title}>
453
+ <BodyText colors={colors}>{message}</BodyText>
454
+ {state.releaseTransition ? (
455
+ <BodyText colors={colors}>
456
+ {translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
457
+ </BodyText>
458
+ ) : null}
459
+ <ChangelogList changelogs={state.changelogs} colors={colors} title={translate("ota:whatsNew")} />
460
+ </FlowPage>
461
+ )
462
+ }
463
+
464
+ if (state.screen === "failed") {
465
+ return (
466
+ <FlowPage
467
+ actions={
468
+ <>
469
+ <FlowButton
470
+ colors={colors}
471
+ label={state.canRetry ? "Retry" : "Done"}
472
+ onPress={state.canRetry ? controller.retryInstall : controller.finish}
473
+ />
474
+ {state.canOpenWifiSetup ? (
475
+ <FlowButton colors={colors} label="Change WiFi" onPress={controller.openWifiSetup} secondary />
476
+ ) : null}
477
+ </>
478
+ }
479
+ colors={colors}
480
+ icon="alert"
481
+ title="Update Failed">
482
+ <BodyText colors={colors}>{state.error?.message}</BodyText>
483
+ </FlowPage>
484
+ )
485
+ }
486
+
487
+ return (
488
+ <FlowPage
489
+ actions={
490
+ superMode ? (
491
+ <FlowButton colors={colors} label="Skip (super)" onPress={controller.discard} secondary />
492
+ ) : undefined
493
+ }
494
+ colors={colors}
495
+ icon="bluetooth"
496
+ title="Glasses disconnected">
497
+ <BodyText colors={colors}>Reconnecting...</BodyText>
498
+ <ActivityIndicator size="large" color={colors.foreground} />
499
+ </FlowPage>
500
+ )
501
+ }
502
+
503
+ type FlowPageProps = {
504
+ actions?: React.ReactNode
505
+ children?: React.ReactNode
506
+ colors: MentraLiveOtaFlowTheme
507
+ contentAlignment?: "center" | "top"
508
+ icon: "alert" | "bluetooth" | "check" | "download" | "settings"
509
+ title: string
510
+ }
511
+
512
+ function FlowPage({actions, children, colors, contentAlignment = "center", icon, title}: FlowPageProps) {
513
+ return (
514
+ <View style={styles.page} testID="mentra-live-ota-flow">
515
+ <View style={[styles.centerContent, contentAlignment === "top" && styles.topContent]}>
516
+ <FlowIcon colors={colors} name={icon} />
517
+ <Text style={[styles.title, {color: colors.foreground}]}>{title}</Text>
518
+ {children}
519
+ </View>
520
+ {actions ? <View style={styles.actions}>{actions}</View> : <View style={styles.actionSpacer} />}
521
+ </View>
522
+ )
523
+ }
524
+
525
+ function BodyText({children, colors}: {children: React.ReactNode; colors: MentraLiveOtaFlowTheme}) {
526
+ return <Text style={[styles.body, {color: colors.textDim}]}>{children}</Text>
527
+ }
528
+
529
+ function PercentText({colors, percent}: {colors: MentraLiveOtaFlowTheme; percent: number}) {
530
+ return <Text style={[styles.percent, {color: colors.primary}]}>{Math.round(percent)}%</Text>
531
+ }
532
+
533
+ function ChangelogMarkdown({colors, markdown}: {colors: MentraLiveOtaFlowTheme; markdown: string}) {
534
+ const markdownStyles = useMemo<MarkedStyles>(
535
+ () => ({
536
+ blockquote: {
537
+ borderLeftColor: colors.primary,
538
+ borderLeftWidth: 3,
539
+ marginVertical: 2,
540
+ opacity: 1,
541
+ paddingLeft: 12,
542
+ },
543
+ code: {
544
+ backgroundColor: colors.background,
545
+ borderColor: colors.border,
546
+ borderRadius: 8,
547
+ borderWidth: 1,
548
+ padding: 12,
549
+ },
550
+ codespan: {
551
+ backgroundColor: colors.border,
552
+ color: colors.foreground,
553
+ fontFamily: "monospace",
554
+ fontSize: 13,
555
+ fontStyle: "normal",
556
+ fontWeight: "400",
557
+ },
558
+ em: {color: colors.textDim, fontSize: 14, lineHeight: 20},
559
+ h1: {
560
+ borderBottomWidth: 0,
561
+ color: colors.foreground,
562
+ fontSize: 18,
563
+ fontWeight: "700",
564
+ lineHeight: 24,
565
+ marginVertical: 0,
566
+ paddingBottom: 0,
567
+ },
568
+ h2: {
569
+ borderBottomWidth: 0,
570
+ color: colors.foreground,
571
+ fontSize: 17,
572
+ fontWeight: "700",
573
+ lineHeight: 23,
574
+ marginVertical: 0,
575
+ paddingBottom: 0,
576
+ },
577
+ h3: {color: colors.foreground, fontSize: 16, fontWeight: "700", lineHeight: 22, marginVertical: 0},
578
+ h4: {color: colors.foreground, fontSize: 15, fontWeight: "700", lineHeight: 21, marginVertical: 0},
579
+ h5: {color: colors.foreground, fontSize: 14, fontWeight: "700", lineHeight: 20, marginVertical: 0},
580
+ h6: {color: colors.textDim, fontSize: 14, fontWeight: "600", lineHeight: 20, marginVertical: 0},
581
+ hr: {borderBottomColor: colors.border, marginVertical: 2},
582
+ image: {borderRadius: 8},
583
+ li: {color: colors.textDim, fontSize: 14, lineHeight: 20},
584
+ link: {
585
+ color: colors.primary,
586
+ fontSize: 14,
587
+ fontStyle: "normal",
588
+ lineHeight: 20,
589
+ textDecorationLine: "underline",
590
+ },
591
+ paragraph: {paddingVertical: 0},
592
+ strikethrough: {color: colors.textDim, fontSize: 14, lineHeight: 20},
593
+ strong: {color: colors.foreground, fontSize: 14, fontWeight: "700", lineHeight: 20},
594
+ table: {borderColor: colors.border, borderRadius: 8},
595
+ tableCell: {padding: 8},
596
+ text: {color: colors.textDim, fontSize: 14, lineHeight: 20},
597
+ }),
598
+ [colors],
599
+ )
600
+ const markdownTheme = useMemo<NonNullable<useMarkdownHookOptions["theme"]>>(
601
+ () => ({
602
+ colors: {
603
+ background: colors.background,
604
+ border: colors.border,
605
+ code: colors.border,
606
+ link: colors.primary,
607
+ text: colors.textDim,
608
+ },
609
+ }),
610
+ [colors],
611
+ )
612
+ const elements = useMarkdown(markdown, {colorScheme: "light", styles: markdownStyles, theme: markdownTheme})
613
+
614
+ return (
615
+ <View style={styles.changelogMarkdownContent} testID="ota-changelog-markdown">
616
+ {elements}
617
+ </View>
618
+ )
619
+ }
620
+
621
+ export function ChangelogList({
622
+ changelogs,
623
+ colors,
624
+ title,
625
+ }: {
626
+ changelogs: MentraLiveOtaController["state"]["changelogs"]
627
+ colors: MentraLiveOtaFlowTheme
628
+ title: string
629
+ }) {
630
+ if (changelogs.length === 0) return null
631
+ return (
632
+ <View style={[styles.changelogCard, {borderColor: colors.border}]} testID="ota-changelog-card">
633
+ <Text style={[styles.changelogTitle, {color: colors.foreground}]}>{title}</Text>
634
+ <ScrollView
635
+ contentContainerStyle={styles.changelogContent}
636
+ nestedScrollEnabled
637
+ showsVerticalScrollIndicator
638
+ style={styles.changelogList}
639
+ testID="ota-changelog-scroll">
640
+ {changelogs.map((entry, index) => (
641
+ <View
642
+ key={entry.version}
643
+ style={[
644
+ styles.changelogEntry,
645
+ index > 0 && styles.changelogEntryDivider,
646
+ index > 0 && {borderTopColor: colors.border},
647
+ ]}>
648
+ <Text selectable style={[styles.changelogVersion, {color: colors.foreground}]}>
649
+ {entry.version}
650
+ </Text>
651
+ <ChangelogMarkdown colors={colors} markdown={entry.markdown} />
652
+ </View>
653
+ ))}
654
+ </ScrollView>
655
+ </View>
656
+ )
657
+ }
658
+
659
+ function FlowButton({
660
+ colors,
661
+ disabled = false,
662
+ label,
663
+ onPress,
664
+ secondary = false,
665
+ }: {
666
+ colors: MentraLiveOtaFlowTheme
667
+ disabled?: boolean
668
+ label: string
669
+ onPress: () => void
670
+ secondary?: boolean
671
+ }) {
672
+ return (
673
+ <Pressable
674
+ accessibilityRole="button"
675
+ disabled={disabled}
676
+ onPress={onPress}
677
+ style={({pressed}) => [
678
+ styles.button,
679
+ {
680
+ backgroundColor: secondary ? colors.background : colors.foreground,
681
+ borderColor: secondary ? colors.border : colors.foreground,
682
+ opacity: disabled ? 0.45 : pressed ? 0.75 : 1,
683
+ },
684
+ ]}
685
+ testID={`button-${label}`}>
686
+ <Text style={[styles.buttonText, {color: secondary ? colors.foreground : colors.background}]}>{label}</Text>
687
+ </Pressable>
688
+ )
689
+ }
690
+
691
+ function GlasslyMark({color}: {color: string}) {
692
+ return (
693
+ <Svg width={33} height={18} viewBox="0 0 50 27" fill="none">
694
+ <Rect y={14.8072} width={11.8457} height={11.8457} fill={color} />
695
+ <Path d="M9.36639 0L30.7163 14.8072V26.6529L9.36639 11.8457V0Z" fill={color} />
696
+ <Path d="M28.6501 0L50 14.8072V26.6529L28.6501 11.8457V0Z" fill={color} />
697
+ </Svg>
698
+ )
699
+ }
700
+
701
+ function FlowIcon({colors, name}: {colors: MentraLiveOtaFlowTheme; name: FlowPageProps["icon"]}) {
702
+ const color = name === "alert" || name === "bluetooth" ? colors.error : colors.primary
703
+ const glyph =
704
+ name === "check" ? "✓" : name === "alert" ? "!" : name === "settings" ? "⚙" : name === "bluetooth" ? "⌁" : "↓"
705
+ return <Text style={[styles.icon, {color}]}>{glyph}</Text>
706
+ }
707
+
708
+ const styles = StyleSheet.create({
709
+ safeArea: {flex: 1},
710
+ header: {
711
+ alignItems: "center",
712
+ flexDirection: "row",
713
+ height: 48,
714
+ justifyContent: "space-between",
715
+ paddingHorizontal: 20,
716
+ },
717
+ page: {flex: 1, paddingBottom: 24, paddingHorizontal: 24},
718
+ centerContent: {alignItems: "center", flex: 1, gap: 16, justifyContent: "center"},
719
+ topContent: {justifyContent: "flex-start", paddingTop: 12},
720
+ actionSpacer: {height: 48},
721
+ actions: {gap: 12},
722
+ icon: {fontSize: 64, fontWeight: "500", lineHeight: 72, textAlign: "center"},
723
+ title: {fontSize: 20, fontWeight: "600", textAlign: "center"},
724
+ body: {fontSize: 14, lineHeight: 20, maxWidth: 420, textAlign: "center"},
725
+ percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
726
+ progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
727
+ progressFill: {borderRadius: 4, height: 8},
728
+ changelogCard: {
729
+ borderRadius: 16,
730
+ borderWidth: 1,
731
+ flexShrink: 1,
732
+ gap: 12,
733
+ maxHeight: 300,
734
+ maxWidth: 420,
735
+ padding: 16,
736
+ width: "100%",
737
+ },
738
+ changelogTitle: {fontSize: 16, fontWeight: "700"},
739
+ changelogList: {flexShrink: 1, maxHeight: 232, width: "100%"},
740
+ changelogContent: {gap: 20, paddingBottom: 2},
741
+ changelogEntry: {gap: 8},
742
+ changelogEntryDivider: {borderTopWidth: StyleSheet.hairlineWidth, paddingTop: 20},
743
+ changelogVersion: {fontSize: 14, fontWeight: "600"},
744
+ changelogMarkdownContent: {gap: 10},
745
+ button: {
746
+ alignItems: "center",
747
+ borderRadius: 50,
748
+ borderWidth: 1,
749
+ justifyContent: "center",
750
+ minHeight: 44,
751
+ paddingHorizontal: 20,
752
+ },
753
+ buttonText: {fontSize: 14, fontWeight: "500"},
754
+ })