@mentra/engine 3.1.0-dev.20 → 3.1.0-dev.22

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 (69) hide show
  1. package/README.md +19 -5
  2. package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
  3. package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/ota-transport.js +2 -0
  5. package/build/bluetooth-sdk/ota-transport.js.map +1 -0
  6. package/build/engine.d.ts +2 -2
  7. package/build/facades/ota.d.ts +4 -4
  8. package/build/facades/ota.d.ts.map +1 -1
  9. package/build/facades/ota.js +3 -4
  10. package/build/facades/ota.js.map +1 -1
  11. package/build/generated/releaseMetadata.js +4 -4
  12. package/build/generated/releaseMetadata.js.map +1 -1
  13. package/build/react/MentraLiveOtaFlow.d.ts +3 -2
  14. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
  15. package/build/react/MentraLiveOtaFlow.js +68 -345
  16. package/build/react/MentraLiveOtaFlow.js.map +1 -1
  17. package/build/react/index.d.ts +1 -0
  18. package/build/react/index.d.ts.map +1 -1
  19. package/build/react/index.js +1 -0
  20. package/build/react/index.js.map +1 -1
  21. package/build/react/useMentraLiveOta.d.ts +69 -0
  22. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  23. package/build/react/useMentraLiveOta.js +485 -0
  24. package/build/react/useMentraLiveOta.js.map +1 -0
  25. package/build/services/GlassesStatusProjection.d.ts +15 -2
  26. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  27. package/build/services/GlassesStatusProjection.js +3 -3
  28. package/build/services/GlassesStatusProjection.js.map +1 -1
  29. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  30. package/build/services/HotspotOtaTransport.js +8 -7
  31. package/build/services/HotspotOtaTransport.js.map +1 -1
  32. package/build/services/OtaInstallCoordinator.d.ts +24 -3
  33. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  34. package/build/services/OtaInstallCoordinator.js +14 -12
  35. package/build/services/OtaInstallCoordinator.js.map +1 -1
  36. package/build/services/OtaService.d.ts.map +1 -1
  37. package/build/services/OtaService.js +1 -1
  38. package/build/services/OtaService.js.map +1 -1
  39. package/build/services/OtaUpdateCheckService.d.ts +1 -1
  40. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  41. package/build/services/OtaUpdateCheckService.js +6 -2
  42. package/build/services/OtaUpdateCheckService.js.map +1 -1
  43. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  44. package/build/services/asg/localNetworkTransport.js +13 -12
  45. package/build/services/asg/localNetworkTransport.js.map +1 -1
  46. package/build/services/glassesClockSync.js +1 -1
  47. package/build/services/glassesClockSync.js.map +1 -1
  48. package/build/stores/core.d.ts +3 -3
  49. package/build/stores/core.d.ts.map +1 -1
  50. package/build/stores/core.js.map +1 -1
  51. package/build/stores/glasses.d.ts +10 -4
  52. package/build/stores/glasses.d.ts.map +1 -1
  53. package/build/stores/glasses.js.map +1 -1
  54. package/package.json +12 -6
  55. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  56. package/src/facades/ota.ts +3 -4
  57. package/src/generated/releaseMetadata.ts +4 -4
  58. package/src/react/MentraLiveOtaFlow.tsx +116 -466
  59. package/src/react/index.ts +13 -0
  60. package/src/react/useMentraLiveOta.ts +619 -0
  61. package/src/services/GlassesStatusProjection.ts +7 -6
  62. package/src/services/HotspotOtaTransport.ts +8 -7
  63. package/src/services/OtaInstallCoordinator.ts +16 -13
  64. package/src/services/OtaService.ts +1 -2
  65. package/src/services/OtaUpdateCheckService.ts +8 -5
  66. package/src/services/asg/localNetworkTransport.ts +20 -19
  67. package/src/services/glassesClockSync.ts +1 -1
  68. package/src/stores/core.ts +4 -4
  69. package/src/stores/glasses.ts +13 -6
@@ -1,13 +1,9 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ /* eslint-disable react-native/no-raw-text -- BodyText and PercentText are local Text wrappers. */
2
+ import React, { useMemo } from "react";
2
3
  import { ActivityIndicator, Pressable, StyleSheet, Text, View } from "react-native";
3
4
  import { SafeAreaView } from "react-native-safe-area-context";
4
5
  import Svg, { Path, Rect } from "react-native-svg";
5
- import { ota } from "../facades/ota";
6
- import { beginOtaAutoChain, clearOtaAutoChainReconnectWait, isOtaAutoChainActive, otaAutoChainFingerprint, otaAutoChainReconnectWaitRemaining, stopOtaAutoChain, tryAdvanceOtaAutoChain, } from "../services/OtaAutoChain";
7
- import { BES_INSTALL_RESTART_MESSAGE, getOtaErrorMessage, shouldRequireGlassesRebootForBesFailure, shouldShowChangeWifiForOtaDownloadFailure, } from "../services/OtaErrorMapping";
8
- import { useEngineSnapshot } from "./useEngineSnapshot";
9
- const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000;
10
- const AUTO_CHAIN_COMPLETE_DELAY_MS = 750;
6
+ import { useMentraLiveOta } from "./useMentraLiveOta";
11
7
  const DEFAULT_THEME = {
12
8
  background: "#FFFFFF",
13
9
  border: "#D7DFDA",
@@ -55,403 +51,130 @@ function defaultTranslate(key, options) {
55
51
  }
56
52
  export function MentraLiveOtaFlow({ allowDevSkip = typeof __DEV__ !== "undefined" && __DEV__, deviceName = "Mentra Live", initialPage = "check", initializeRuntime = true, onFinished, onFirmwareRestartingChange, onOpenWifiSetup, style, superMode = false, theme, translate = defaultTranslate, }) {
57
53
  const colors = useMemo(() => ({ ...DEFAULT_THEME, ...theme }), [theme]);
58
- const [page, setPage] = useState(initialPage);
59
- const [checkGeneration, setCheckGeneration] = useState(0);
60
- const [runtimeReady, setRuntimeReady] = useState(!initializeRuntime);
61
- useEffect(() => {
62
- if (!initializeRuntime) {
63
- setRuntimeReady(true);
64
- return;
65
- }
66
- let cancelled = false;
67
- setRuntimeReady(false);
68
- void ota.initialize().finally(() => {
69
- if (!cancelled)
70
- setRuntimeReady(true);
71
- });
72
- return () => {
73
- cancelled = true;
74
- };
75
- }, [initializeRuntime]);
76
- const showCheck = useCallback(() => {
77
- setPage("check");
78
- setCheckGeneration((generation) => generation + 1);
79
- }, []);
54
+ const controller = useMentraLiveOta({
55
+ initialPage,
56
+ initializeRuntime,
57
+ onFinished,
58
+ onFirmwareRestartingChange,
59
+ onOpenWifiSetup,
60
+ });
80
61
  return (<SafeAreaView style={[styles.safeArea, { backgroundColor: colors.background }, style]}>
81
62
  <View style={styles.header}>
82
63
  <View />
83
64
  <MentraMark color={colors.primary}/>
84
65
  </View>
85
- {!runtimeReady ? (<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
86
- <ActivityIndicator size="large" color={colors.foreground}/>
87
- </FlowPage>) : page === "check" ? (<OtaCheckPage allowDevSkip={allowDevSkip} colors={colors} deviceName={deviceName} generation={checkGeneration} onFinished={onFinished} onOpenWifiSetup={onOpenWifiSetup} onStartProgress={() => setPage("progress")} translate={translate}/>) : (<OtaProgressPage colors={colors} onFirmwareRestartingChange={onFirmwareRestartingChange} onOpenWifiSetup={onOpenWifiSetup} onReturnToCheck={showCheck} superMode={superMode} translate={translate}/>)}
66
+ <OtaFlowContent allowDevSkip={allowDevSkip} colors={colors} controller={controller} deviceName={deviceName} superMode={superMode} translate={translate}/>
88
67
  </SafeAreaView>);
89
68
  }
90
- function OtaCheckPage({ allowDevSkip, colors, deviceName, generation, onFinished, onOpenWifiSetup, onStartProgress, translate, }) {
91
- const otaSnapshot = useEngineSnapshot(ota.snapshot, ota.onSnapshot);
92
- const [checkState, setCheckState] = useState("checking");
93
- const [isUpdateRequired, setIsUpdateRequired] = useState(true);
94
- const [isDowngradeUpdate, setIsDowngradeUpdate] = useState(false);
95
- const [errorKind, setErrorKind] = useState("network");
96
- const [updateFingerprint, setUpdateFingerprint] = useState(null);
97
- const [retryGeneration, setRetryGeneration] = useState(0);
98
- const performCheckGenerationRef = useRef(0);
99
- const activeCheckKeyRef = useRef(null);
100
- const checkStartedRef = useRef(false);
101
- const checkCompletedRef = useRef(false);
102
- const selectedCheckResultRef = useRef(null);
103
- const onFinishedRef = useRef(onFinished);
104
- const onStartProgressRef = useRef(onStartProgress);
105
- onFinishedRef.current = onFinished;
106
- onStartProgressRef.current = onStartProgress;
107
- const glassesWifiConnected = otaSnapshot.wifiConnected;
108
- const glassesWifiStatusKnown = otaSnapshot.wifiStatusKnown;
109
- const hotspotOtaSupported = otaSnapshot.hotspotOtaVersion === 1;
110
- const canInstallUpdate = glassesWifiStatusKnown && (glassesWifiConnected || hotspotOtaSupported);
111
- const checkKey = `${generation}:${retryGeneration}`;
112
- const navigateToProgress = useCallback(() => {
113
- ota.clearProgress();
114
- onStartProgressRef.current();
115
- }, []);
116
- useEffect(() => {
117
- const MIN_DISPLAY_TIME_MS = 1100;
118
- const MAX_WAIT_FOR_VERSION_INFO_MS = 10_000;
119
- if (activeCheckKeyRef.current !== checkKey) {
120
- activeCheckKeyRef.current = checkKey;
121
- checkStartedRef.current = false;
122
- checkCompletedRef.current = false;
123
- setCheckState("checking");
124
- }
125
- const myGeneration = ++performCheckGenerationRef.current;
126
- let cancelled = false;
127
- let reconnectTimeout = null;
128
- const performCheck = async () => {
129
- if (checkCompletedRef.current)
130
- return;
131
- if (!otaSnapshot.connected) {
132
- if (isOtaAutoChainActive()) {
133
- const remainingMs = otaAutoChainReconnectWaitRemaining();
134
- if (remainingMs === null)
135
- return;
136
- reconnectTimeout = setTimeout(() => {
137
- if (cancelled || myGeneration !== performCheckGenerationRef.current || ota.snapshot().connected)
138
- return;
139
- stopOtaAutoChain();
140
- checkCompletedRef.current = true;
141
- setCheckState("error");
142
- }, remainingMs);
143
- return;
144
- }
145
- if (checkStartedRef.current) {
146
- stopOtaAutoChain();
147
- checkCompletedRef.current = true;
148
- setCheckState("error");
149
- return;
150
- }
151
- checkCompletedRef.current = true;
152
- onFinishedRef.current();
153
- return;
154
- }
155
- clearOtaAutoChainReconnectWait();
156
- checkStartedRef.current = true;
157
- const startTime = Date.now();
158
- try {
159
- let result = await ota.checkForUpdates({
160
- waitForBuildNumberMs: MAX_WAIT_FOR_VERSION_INFO_MS,
161
- waitForBesVersionMs: 5000,
162
- waitForMtkVersionMs: 2000,
163
- refreshVersionInfo: true,
164
- fixClockBeforeCheck: false,
165
- });
166
- if (cancelled || myGeneration !== performCheckGenerationRef.current)
167
- return;
168
- if (!result.hasCheckCompleted && result.checkFailureReason === "network" && isOtaAutoChainActive()) {
169
- await new Promise((resolve) => setTimeout(resolve, AUTO_CHAIN_NETWORK_RETRY_DELAY_MS));
170
- if (cancelled || myGeneration !== performCheckGenerationRef.current)
171
- return;
172
- result = await ota.checkForUpdates({
173
- waitForBuildNumberMs: MAX_WAIT_FOR_VERSION_INFO_MS,
174
- waitForBesVersionMs: 5000,
175
- waitForMtkVersionMs: 2000,
176
- refreshVersionInfo: true,
177
- fixClockBeforeCheck: false,
178
- });
179
- }
180
- if (cancelled || myGeneration !== performCheckGenerationRef.current)
181
- return;
182
- selectedCheckResultRef.current = result;
183
- await new Promise((resolve) => setTimeout(resolve, Math.max(0, MIN_DISPLAY_TIME_MS - (Date.now() - startTime))));
184
- if (cancelled || myGeneration !== performCheckGenerationRef.current)
185
- return;
186
- if (result.skippedReason === "disconnected") {
187
- stopOtaAutoChain();
188
- checkCompletedRef.current = true;
189
- setCheckState("error");
190
- return;
191
- }
192
- if (result.skippedReason === "missing_build") {
193
- checkCompletedRef.current = true;
194
- if (isOtaAutoChainActive()) {
195
- stopOtaAutoChain();
196
- setCheckState("error");
197
- }
198
- else {
199
- onFinishedRef.current();
200
- }
201
- return;
202
- }
203
- if (result.skippedReason === "dev_build") {
204
- stopOtaAutoChain();
205
- checkCompletedRef.current = true;
206
- ota.clearUpdateAvailable();
207
- setCheckState("dev_build");
208
- return;
209
- }
210
- if (!result.hasCheckCompleted) {
211
- stopOtaAutoChain();
212
- checkCompletedRef.current = true;
213
- setErrorKind(result.checkFailureReason === "pin_unavailable" ? "pin_unavailable" : "network");
214
- setCheckState("error");
215
- return;
216
- }
217
- if (result.updateAvailable && result.updateInfo) {
218
- setIsUpdateRequired(result.isRequired);
219
- setIsDowngradeUpdate(result.updateInfo.isDowngrade === true);
220
- const fingerprint = otaAutoChainFingerprint(result);
221
- setUpdateFingerprint(fingerprint);
222
- if (isOtaAutoChainActive()) {
223
- const snapshot = ota.snapshot();
224
- if (!snapshot.wifiStatusKnown)
225
- return;
226
- if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
227
- stopOtaAutoChain();
228
- }
229
- else {
230
- const admission = tryAdvanceOtaAutoChain(fingerprint, result.updateInfo.isDowngrade === true);
231
- if (admission.advance) {
232
- checkCompletedRef.current = true;
233
- ota.installSession.prepare(result);
234
- navigateToProgress();
235
- return;
236
- }
237
- }
238
- }
239
- checkCompletedRef.current = true;
240
- setCheckState("update_available");
241
- return;
242
- }
243
- stopOtaAutoChain();
244
- checkCompletedRef.current = true;
245
- ota.clearUpdateAvailable();
246
- setCheckState("no_update");
247
- }
248
- catch (error) {
249
- console.error("OTA check failed:", error);
250
- await new Promise((resolve) => setTimeout(resolve, Math.max(0, MIN_DISPLAY_TIME_MS - (Date.now() - startTime))));
251
- if (cancelled || myGeneration !== performCheckGenerationRef.current)
252
- return;
253
- stopOtaAutoChain();
254
- checkCompletedRef.current = true;
255
- setCheckState("error");
256
- }
257
- };
258
- void performCheck();
259
- return () => {
260
- cancelled = true;
261
- if (reconnectTimeout)
262
- clearTimeout(reconnectTimeout);
263
- };
264
- }, [
265
- checkKey,
266
- otaSnapshot.connected,
267
- otaSnapshot.wifiStatusKnown,
268
- navigateToProgress,
269
- ]);
270
- const retry = () => {
271
- setCheckState("checking");
272
- setRetryGeneration((value) => value + 1);
273
- };
274
- const updateNow = () => {
275
- const result = selectedCheckResultRef.current;
276
- if (!result) {
277
- setCheckState("error");
278
- return;
279
- }
280
- const snapshot = ota.snapshot();
281
- if (!snapshot.wifiStatusKnown) {
282
- retry();
283
- return;
284
- }
285
- if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
286
- onOpenWifiSetup();
287
- return;
288
- }
289
- ota.installSession.prepare(result);
290
- if (updateFingerprint)
291
- beginOtaAutoChain(updateFingerprint, isDowngradeUpdate);
292
- navigateToProgress();
293
- };
294
- if (checkState === "checking") {
69
+ function OtaFlowContent({ allowDevSkip, colors, controller, deviceName, superMode, translate, }) {
70
+ const { state } = controller;
71
+ if (state.screen === "initializing") {
72
+ return (<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
73
+ <ActivityIndicator size="large" color={colors.foreground}/>
74
+ </FlowPage>);
75
+ }
76
+ if (state.screen === "checking") {
295
77
  return (<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
296
78
  <BodyText colors={colors}>{translate("ota:checkingForUpdatesMessage")}</BodyText>
297
79
  <ActivityIndicator size="large" color={colors.foreground}/>
298
80
  </FlowPage>);
299
81
  }
300
- if (checkState === "update_available") {
301
- return (<FlowPage colors={colors} icon="download" title={translate(isDowngradeUpdate ? "ota:downgradeAvailable" : "ota:updateAvailable", { deviceName })} actions={<>
302
- <FlowButton colors={colors} disabled={!glassesWifiStatusKnown} label={translate(!glassesWifiStatusKnown || canInstallUpdate ? "ota:updateNow" : "ota:setupWifi")} onPress={updateNow}/>
303
- {!isUpdateRequired ? (<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={onFinished} secondary/>) : null}
304
- {allowDevSkip && isUpdateRequired ? (<FlowButton colors={colors} label="Skip (dev only)" onPress={onFinished} secondary/>) : null}
82
+ if (state.screen === "update_available" || state.screen === "wifi_required") {
83
+ return (<FlowPage colors={colors} icon="download" title={translate(state.versionChange ? "ota:downgradeAvailable" : "ota:updateAvailable", { deviceName })} actions={<>
84
+ <FlowButton colors={colors} disabled={!state.wifiStatusKnown} label={translate(state.screen === "wifi_required" ? "ota:setupWifi" : "ota:updateNow")} onPress={state.screen === "wifi_required" ? controller.openWifiSetup : controller.install}/>
85
+ {state.canDismiss ? (<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary/>) : null}
86
+ {allowDevSkip && state.updateRequired ? (<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary/>) : null}
305
87
  </>}>
306
88
  <BodyText colors={colors}>
307
- {canInstallUpdate
308
- ? translate(isDowngradeUpdate ? "ota:downgradeDescription" : "ota:updateDescription")
309
- : translate("ota:updateConnectWifi", { deviceName })}
89
+ {state.screen === "wifi_required"
90
+ ? translate("ota:updateConnectWifi", { deviceName })
91
+ : translate(state.versionChange ? "ota:downgradeDescription" : "ota:updateDescription")}
310
92
  </BodyText>
311
93
  </FlowPage>);
312
94
  }
313
- if (checkState === "dev_build") {
314
- return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={onFinished}/>} colors={colors} icon="settings" title={translate("ota:devBuild")}>
95
+ if (state.screen === "dev_build") {
96
+ return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish}/>} colors={colors} icon="settings" title={translate("ota:devBuild")}>
315
97
  <BodyText colors={colors}>{translate("ota:devBuildNoOta")}</BodyText>
316
98
  </FlowPage>);
317
99
  }
318
- if (checkState === "no_update") {
319
- return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={onFinished}/>} colors={colors} icon="check" title={translate("ota:upToDate")}>
100
+ if (state.screen === "up_to_date") {
101
+ return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish}/>} colors={colors} icon="check" title={translate("ota:upToDate")}>
320
102
  <BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
321
103
  </FlowPage>);
322
104
  }
323
- if (errorKind === "pin_unavailable") {
324
- return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={onFinished}/>} colors={colors} icon="alert" title={translate("ota:updateInfoUnavailable")}>
105
+ if (state.screen === "update_info_unavailable") {
106
+ return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish}/>} colors={colors} icon="alert" title={translate("ota:updateInfoUnavailable")}>
325
107
  <BodyText colors={colors}>{translate("ota:updateInfoUnavailableMessage")}</BodyText>
326
108
  </FlowPage>);
327
109
  }
328
- return (<FlowPage actions={<>
329
- <FlowButton colors={colors} label="Retry" onPress={retry}/>
330
- {allowDevSkip ? <FlowButton colors={colors} label="Skip (dev only)" onPress={onFinished} secondary/> : null}
331
- </>} colors={colors} icon="alert" title={translate("ota:checkFailed")}>
332
- <BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
333
- </FlowPage>);
334
- }
335
- function OtaProgressPage({ colors, onFirmwareRestartingChange, onOpenWifiSetup, onReturnToCheck, superMode, translate, }) {
336
- const install = useEngineSnapshot(ota.installSession.snapshot, ota.installSession.onSnapshot);
337
- const autoChainAdvancedRef = useRef(false);
338
- const { connected, continueButtonDisabled, displayState, errorMsg, hotspotArtifactPercent, hotspotPhase, isVersionChange, mtkInstallStallSimulatedPercent, otaProgress, otaStatus, versionChangeConverged, versionChangePhase, } = install;
339
- const firmwareRestarting = (!connected && displayState === "restarting") || versionChangePhase === "restarting";
340
- useEffect(() => {
341
- onFirmwareRestartingChange?.(firmwareRestarting, true);
342
- }, [firmwareRestarting, onFirmwareRestartingChange]);
343
- useEffect(() => () => {
344
- onFirmwareRestartingChange?.(false, false);
345
- }, [onFirmwareRestartingChange]);
346
- useEffect(() => {
347
- ota.installSession.attach();
348
- return () => ota.installSession.detach();
349
- }, []);
350
- useEffect(() => {
351
- if (displayState !== "complete" || !connected || !isOtaAutoChainActive() || autoChainAdvancedRef.current)
352
- return;
353
- const timeout = setTimeout(() => {
354
- if (!isOtaAutoChainActive())
355
- return;
356
- autoChainAdvancedRef.current = true;
357
- ota.installSession.finish();
358
- onReturnToCheck();
359
- }, AUTO_CHAIN_COMPLETE_DELAY_MS);
360
- return () => clearTimeout(timeout);
361
- }, [connected, displayState, onReturnToCheck]);
362
- const finishAndCheck = () => {
363
- ota.installSession.finish();
364
- onReturnToCheck();
365
- };
366
- const stopAndCheck = () => {
367
- stopOtaAutoChain();
368
- finishAndCheck();
369
- };
370
- const retryInstall = () => {
371
- onFirmwareRestartingChange?.(false, true);
372
- ota.installSession.retry();
373
- };
374
- if (versionChangePhase === "restarting" || versionChangePhase === "verifying") {
375
- return (<FlowPage colors={colors} icon="download" title={translate(versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting")}>
110
+ if (state.screen === "check_failed") {
111
+ return (<FlowPage actions={<>
112
+ <FlowButton colors={colors} label="Retry" onPress={controller.retryCheck}/>
113
+ {allowDevSkip ? (<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary/>) : null}
114
+ </>} colors={colors} icon="alert" title={translate("ota:checkFailed")}>
115
+ <BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
116
+ </FlowPage>);
117
+ }
118
+ if (state.versionChangePhase === "restarting" || state.versionChangePhase === "verifying") {
119
+ return (<FlowPage colors={colors} icon="download" title={translate(state.versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting")}>
376
120
  <ActivityIndicator size="large" color={colors.foreground}/>
377
121
  <BodyText colors={colors}>{translate("ota:versionChangeKeepNearby")}</BodyText>
378
122
  <BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
379
123
  </FlowPage>);
380
124
  }
381
- if (displayState === "starting") {
382
- const hotspotTitle = hotspotPhase === "downloading"
125
+ if (state.screen === "starting" || state.screen === "preparing_hotspot") {
126
+ const title = state.hotspotPhase === "downloading"
383
127
  ? "Downloading update to phone..."
384
- : hotspotPhase === "starting_hotspot"
128
+ : state.hotspotPhase === "starting_hotspot"
385
129
  ? "Starting glasses hotspot..."
386
- : hotspotPhase === "joining_hotspot"
130
+ : state.hotspotPhase === "joining_hotspot"
387
131
  ? "Connecting phone to glasses..."
388
132
  : "Starting update...";
389
- return (<FlowPage colors={colors} icon="download" title={hotspotTitle}>
390
- {hotspotPhase === "downloading" && hotspotArtifactPercent !== null ? (<PercentText colors={colors} percent={hotspotArtifactPercent}/>) : null}
133
+ return (<FlowPage colors={colors} icon="download" title={title}>
134
+ {state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (<PercentText colors={colors} percent={state.hotspotArtifactPercent}/>) : null}
391
135
  <ActivityIndicator size="large" color={colors.foreground}/>
392
136
  <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
393
137
  </FlowPage>);
394
138
  }
395
- if (displayState === "updating") {
396
- const isDownload = otaStatus?.phase === "download";
397
- const totalSteps = otaStatus?.totalSteps ?? 1;
398
- const isApkOnlyInstalling = otaStatus?.stepType === "apk" && otaStatus.phase === "install" && totalSteps === 1;
399
- const rawPercent = isDownload
400
- ? otaStatus?.stepPercent ?? 0
401
- : totalSteps >= 2
402
- ? otaStatus?.overallPercent ?? 0
403
- : otaStatus?.stepPercent ?? 0;
404
- const percent = Math.min(Math.max(rawPercent, mtkInstallStallSimulatedPercent ?? 0, 0), 100);
405
- return (<FlowPage colors={colors} icon="download" title={isDownload ? "Downloading..." : "Installing..."}>
406
- {isApkOnlyInstalling ? (<ActivityIndicator size="large" color={colors.foreground}/>) : (<>
407
- <PercentText colors={colors} percent={percent}/>
139
+ if (state.screen === "updating") {
140
+ return (<FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
141
+ {state.installingApkOnly ? (<ActivityIndicator size="large" color={colors.foreground}/>) : (<>
142
+ <PercentText colors={colors} percent={state.progress ?? 0}/>
408
143
  <View style={[styles.progressTrack, { backgroundColor: colors.border }]}>
409
- <View style={[styles.progressFill, { backgroundColor: colors.primary, width: `${percent}%` }]}/>
144
+ <View style={[styles.progressFill, { backgroundColor: colors.primary, width: `${state.progress ?? 0}%` }]}/>
410
145
  </View>
411
146
  </>)}
412
147
  <BodyText colors={colors}>Do not disconnect your glasses</BodyText>
413
- {isVersionChange && otaStatus?.phase === "install" ? (<BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>) : null}
148
+ {state.versionChange && state.phase === "install" ? (<BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>) : null}
414
149
  </FlowPage>);
415
150
  }
416
- if (displayState === "restarting") {
417
- return (<FlowPage actions={<FlowButton colors={colors} disabled={continueButtonDisabled} label="Continue" onPress={stopAndCheck}/>} colors={colors} icon="check" title="Update Installed"/>);
151
+ if (state.screen === "restarting") {
152
+ return (<FlowPage actions={<FlowButton colors={colors} disabled={state.continueDisabled} label="Continue" onPress={controller.finish}/>} colors={colors} icon="check" title="Update Installed"/>);
418
153
  }
419
- if (displayState === "complete") {
420
- const title = versionChangeConverged
154
+ if (state.screen === "complete") {
155
+ const title = state.versionChangeConverged
421
156
  ? translate("ota:versionChangeComplete")
422
- : isVersionChange
157
+ : state.versionChange
423
158
  ? translate("ota:versionChangeFirmwarePassComplete")
424
159
  : "Update complete!";
425
- const message = versionChangeConverged
160
+ const message = state.versionChangeConverged
426
161
  ? translate("ota:versionChangeCompleteMessage")
427
- : isVersionChange
162
+ : state.versionChange
428
163
  ? translate("ota:versionChangeFirmwarePassCompleteMessage")
429
164
  : "Your glasses are up to date.";
430
- return (<FlowPage actions={<FlowButton colors={colors} label={isVersionChange && !versionChangeConverged ? "Continue" : "Done"} onPress={finishAndCheck}/>} colors={colors} icon="check" title={title}>
165
+ return (<FlowPage actions={<FlowButton colors={colors} label={state.versionChange && !state.versionChangeConverged ? "Continue" : "Done"} onPress={controller.finish}/>} colors={colors} icon="check" title={title}>
431
166
  <BodyText colors={colors}>{message}</BodyText>
432
167
  </FlowPage>);
433
168
  }
434
- if (displayState === "failed") {
435
- const requiresGlassesReboot = shouldRequireGlassesRebootForBesFailure(otaStatus, otaProgress, errorMsg);
436
- const displayedError = requiresGlassesReboot
437
- ? BES_INSTALL_RESTART_MESSAGE
438
- : errorMsg || getOtaErrorMessage(otaStatus?.error);
439
- const showChangeWifi = shouldShowChangeWifiForOtaDownloadFailure(otaStatus, otaProgress, errorMsg);
169
+ if (state.screen === "failed") {
440
170
  return (<FlowPage actions={<>
441
- <FlowButton colors={colors} label={requiresGlassesReboot ? "Done" : "Retry"} onPress={requiresGlassesReboot ? stopAndCheck : retryInstall}/>
442
- {showChangeWifi ? (<FlowButton colors={colors} label="Change WiFi" onPress={() => {
443
- stopOtaAutoChain();
444
- onOpenWifiSetup();
445
- }} secondary/>) : null}
171
+ <FlowButton colors={colors} label={state.canRetry ? "Retry" : "Done"} onPress={state.canRetry ? controller.retryInstall : controller.finish}/>
172
+ {state.canOpenWifiSetup ? (<FlowButton colors={colors} label="Change WiFi" onPress={controller.openWifiSetup} secondary/>) : null}
446
173
  </>} colors={colors} icon="alert" title="Update Failed">
447
- <BodyText colors={colors}>{displayedError}</BodyText>
174
+ <BodyText colors={colors}>{state.error?.message}</BodyText>
448
175
  </FlowPage>);
449
176
  }
450
- return (<FlowPage actions={superMode ? (<FlowButton colors={colors} label="Skip (super)" onPress={() => {
451
- stopOtaAutoChain();
452
- ota.installSession.discard();
453
- onReturnToCheck();
454
- }} secondary/>) : undefined} colors={colors} icon="bluetooth" title="Glasses disconnected">
177
+ return (<FlowPage actions={superMode ? (<FlowButton colors={colors} label="Skip (super)" onPress={controller.discard} secondary/>) : undefined} colors={colors} icon="bluetooth" title="Glasses disconnected">
455
178
  <BodyText colors={colors}>Reconnecting...</BodyText>
456
179
  <ActivityIndicator size="large" color={colors.foreground}/>
457
180
  </FlowPage>);