@mentra/engine 3.1.0-dev.8 → 3.1.0-dev.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -5
- package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
- package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
- package/build/bluetooth-sdk/ota-transport.js +2 -0
- package/build/bluetooth-sdk/ota-transport.js.map +1 -0
- package/build/engine.d.ts +5 -2
- package/build/engine.d.ts.map +1 -1
- package/build/facades/ota.d.ts +10 -6
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +7 -4
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +4 -0
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +81 -7
- package/build/facades/pairing.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +8 -2
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +249 -354
- package/build/react/MentraLiveOtaFlow.js.map +1 -1
- package/build/react/index.d.ts +1 -0
- package/build/react/index.d.ts.map +1 -1
- package/build/react/index.js +1 -0
- package/build/react/index.js.map +1 -1
- package/build/react/useMentraLiveOta.d.ts +84 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -0
- package/build/react/useMentraLiveOta.js +616 -0
- package/build/react/useMentraLiveOta.js.map +1 -0
- package/build/services/BlobStore.d.ts +2 -0
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +63 -10
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/GlassesStatusProjection.d.ts +15 -2
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +3 -3
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts.map +1 -1
- package/build/services/HotspotOtaTransport.js +8 -7
- package/build/services/HotspotOtaTransport.js.map +1 -1
- package/build/services/OtaAutoChain.d.ts +9 -2
- package/build/services/OtaAutoChain.d.ts.map +1 -1
- package/build/services/OtaAutoChain.js +10 -2
- package/build/services/OtaAutoChain.js.map +1 -1
- package/build/services/OtaInstallCoordinator.d.ts +24 -3
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +14 -12
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +1 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +4 -1
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +21 -5
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
- package/build/services/asg/localNetworkTransport.js +13 -12
- package/build/services/asg/localNetworkTransport.js.map +1 -1
- package/build/services/glassesClockSync.js +1 -1
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/otaReleaseVersion.d.ts +10 -0
- package/build/services/otaReleaseVersion.d.ts.map +1 -0
- package/build/services/otaReleaseVersion.js +14 -0
- package/build/services/otaReleaseVersion.js.map +1 -0
- package/build/stores/appVisibility.d.ts +3 -0
- package/build/stores/appVisibility.d.ts.map +1 -0
- package/build/stores/appVisibility.js +3 -0
- package/build/stores/appVisibility.js.map +1 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +7 -1
- package/build/stores/apps.js.map +1 -1
- package/build/stores/core.d.ts +3 -3
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.js.map +1 -1
- package/build/stores/glasses.d.ts +10 -4
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js.map +1 -1
- package/package.json +13 -6
- package/src/bluetooth-sdk/ota-transport.ts +1 -0
- package/src/facades/ota.ts +17 -5
- package/src/facades/pairing.ts +88 -12
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +1 -0
- package/src/react/MentraLiveOtaFlow.tsx +370 -473
- package/src/react/index.ts +14 -0
- package/src/react/useMentraLiveOta.ts +781 -0
- package/src/services/BlobStore.ts +57 -10
- package/src/services/GlassesStatusProjection.ts +7 -6
- package/src/services/HotspotOtaTransport.ts +8 -7
- package/src/services/OtaAutoChain.ts +26 -2
- package/src/services/OtaInstallCoordinator.ts +16 -13
- package/src/services/OtaService.ts +1 -2
- package/src/services/OtaUpdateCheckService.ts +26 -8
- package/src/services/asg/localNetworkTransport.ts +20 -19
- package/src/services/glassesClockSync.ts +1 -1
- package/src/services/otaReleaseVersion.ts +22 -0
- package/src/stores/appVisibility.ts +3 -0
- package/src/stores/apps.ts +7 -1
- package/src/stores/core.ts +4 -4
- package/src/stores/glasses.ts +13 -6
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
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"
|
|
3
14
|
import {SafeAreaView} from "react-native-safe-area-context"
|
|
4
15
|
import Svg, {Path, Rect} from "react-native-svg"
|
|
5
16
|
|
|
6
|
-
import {ota} from "../facades/ota"
|
|
7
|
-
import {
|
|
8
|
-
beginOtaAutoChain,
|
|
9
|
-
clearOtaAutoChainReconnectWait,
|
|
10
|
-
isOtaAutoChainActive,
|
|
11
|
-
otaAutoChainFingerprint,
|
|
12
|
-
otaAutoChainReconnectWaitRemaining,
|
|
13
|
-
stopOtaAutoChain,
|
|
14
|
-
tryAdvanceOtaAutoChain,
|
|
15
|
-
} from "../services/OtaAutoChain"
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "
|
|
22
|
-
import {useEngineSnapshot} from "./useEngineSnapshot"
|
|
18
|
+
MINIMUM_OTA_BATTERY_LEVEL,
|
|
19
|
+
useMentraLiveOta,
|
|
20
|
+
type MentraLiveOtaController,
|
|
21
|
+
type MentraLiveOtaFlowPage,
|
|
22
|
+
} from "./useMentraLiveOta"
|
|
23
23
|
|
|
24
|
-
export type MentraLiveOtaFlowPage
|
|
24
|
+
export type {MentraLiveOtaFlowPage} from "./useMentraLiveOta"
|
|
25
25
|
|
|
26
26
|
export type MentraLiveOtaFlowTheme = {
|
|
27
27
|
background: string
|
|
@@ -40,7 +40,7 @@ export type MentraLiveOtaFlowProps = {
|
|
|
40
40
|
deviceName?: string
|
|
41
41
|
/** Entry page. `progress` exists for recovery/deep-link compatibility. */
|
|
42
42
|
initialPage?: MentraLiveOtaFlowPage
|
|
43
|
-
/** Start the OTA-only projections. Full
|
|
43
|
+
/** Start the OTA-only projections. Full Engine hosts should pass false. */
|
|
44
44
|
initializeRuntime?: boolean
|
|
45
45
|
/** Called after the final check or when the user leaves an optional update. */
|
|
46
46
|
onFinished: () => void
|
|
@@ -59,11 +59,6 @@ export type MentraLiveOtaFlowProps = {
|
|
|
59
59
|
style?: StyleProp<ViewStyle>
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
type CheckState = "checking" | "update_available" | "no_update" | "dev_build" | "error"
|
|
63
|
-
|
|
64
|
-
const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000
|
|
65
|
-
const AUTO_CHAIN_COMPLETE_DELAY_MS = 750
|
|
66
|
-
|
|
67
62
|
const DEFAULT_THEME: MentraLiveOtaFlowTheme = {
|
|
68
63
|
background: "#FFFFFF",
|
|
69
64
|
border: "#D7DFDA",
|
|
@@ -76,19 +71,34 @@ const DEFAULT_THEME: MentraLiveOtaFlowTheme = {
|
|
|
76
71
|
|
|
77
72
|
const ENGLISH_COPY: Record<string, string> = {
|
|
78
73
|
"common:continue": "Continue",
|
|
74
|
+
"common:done": "Done",
|
|
79
75
|
"ota:checkingForUpdates": "Checking for updates",
|
|
80
76
|
"ota:checkingForUpdatesMessage":
|
|
81
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.",
|
|
82
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.",
|
|
83
85
|
"ota:updateConnectWifi": "Connect your {{deviceName}} to WiFi to install the update.",
|
|
86
|
+
"ota:wifiRequiredTitle": "WiFi Needed for Update",
|
|
84
87
|
"ota:updateDescription":
|
|
85
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}}",
|
|
86
94
|
"ota:downgradeAvailable": "{{deviceName}} Version Change Required",
|
|
87
95
|
"ota:downgradeDescription":
|
|
88
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.",
|
|
89
97
|
"ota:updateNow": "Update Now",
|
|
90
98
|
"ota:setupWifi": "Setup WiFi",
|
|
91
99
|
"ota:updateLater": "Later",
|
|
100
|
+
"ota:updateComplete": "Update complete",
|
|
101
|
+
"ota:whatsNew": "What's new",
|
|
92
102
|
"ota:upToDate": "Up To Date",
|
|
93
103
|
"ota:devBuild": "Development Build",
|
|
94
104
|
"ota:devBuildNoOta":
|
|
@@ -133,29 +143,13 @@ export function MentraLiveOtaFlow({
|
|
|
133
143
|
translate = defaultTranslate,
|
|
134
144
|
}: MentraLiveOtaFlowProps) {
|
|
135
145
|
const colors = useMemo(() => ({...DEFAULT_THEME, ...theme}), [theme])
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
let cancelled = false
|
|
146
|
-
setRuntimeReady(false)
|
|
147
|
-
void ota.initialize().finally(() => {
|
|
148
|
-
if (!cancelled) setRuntimeReady(true)
|
|
149
|
-
})
|
|
150
|
-
return () => {
|
|
151
|
-
cancelled = true
|
|
152
|
-
}
|
|
153
|
-
}, [initializeRuntime])
|
|
154
|
-
|
|
155
|
-
const showCheck = useCallback(() => {
|
|
156
|
-
setPage("check")
|
|
157
|
-
setCheckGeneration((generation) => generation + 1)
|
|
158
|
-
}, [])
|
|
146
|
+
const controller = useMentraLiveOta({
|
|
147
|
+
initialPage,
|
|
148
|
+
initializeRuntime,
|
|
149
|
+
onFinished,
|
|
150
|
+
onFirmwareRestartingChange,
|
|
151
|
+
onOpenWifiSetup,
|
|
152
|
+
})
|
|
159
153
|
|
|
160
154
|
return (
|
|
161
155
|
<SafeAreaView style={[styles.safeArea, {backgroundColor: colors.background}, style]}>
|
|
@@ -163,300 +157,139 @@ export function MentraLiveOtaFlow({
|
|
|
163
157
|
<View />
|
|
164
158
|
<MentraMark color={colors.primary} />
|
|
165
159
|
</View>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
deviceName={deviceName}
|
|
175
|
-
generation={checkGeneration}
|
|
176
|
-
onFinished={onFinished}
|
|
177
|
-
onOpenWifiSetup={onOpenWifiSetup}
|
|
178
|
-
onStartProgress={() => setPage("progress")}
|
|
179
|
-
translate={translate}
|
|
180
|
-
/>
|
|
181
|
-
) : (
|
|
182
|
-
<OtaProgressPage
|
|
183
|
-
colors={colors}
|
|
184
|
-
onFirmwareRestartingChange={onFirmwareRestartingChange}
|
|
185
|
-
onOpenWifiSetup={onOpenWifiSetup}
|
|
186
|
-
onReturnToCheck={showCheck}
|
|
187
|
-
superMode={superMode}
|
|
188
|
-
translate={translate}
|
|
189
|
-
/>
|
|
190
|
-
)}
|
|
160
|
+
<OtaFlowContent
|
|
161
|
+
allowDevSkip={allowDevSkip}
|
|
162
|
+
colors={colors}
|
|
163
|
+
controller={controller}
|
|
164
|
+
deviceName={deviceName}
|
|
165
|
+
superMode={superMode}
|
|
166
|
+
translate={translate}
|
|
167
|
+
/>
|
|
191
168
|
</SafeAreaView>
|
|
192
169
|
)
|
|
193
170
|
}
|
|
194
171
|
|
|
195
|
-
|
|
172
|
+
function OtaFlowContent({
|
|
173
|
+
allowDevSkip,
|
|
174
|
+
colors,
|
|
175
|
+
controller,
|
|
176
|
+
deviceName,
|
|
177
|
+
superMode,
|
|
178
|
+
translate,
|
|
179
|
+
}: {
|
|
196
180
|
allowDevSkip: boolean
|
|
197
181
|
colors: MentraLiveOtaFlowTheme
|
|
182
|
+
controller: MentraLiveOtaController
|
|
198
183
|
deviceName: string
|
|
199
|
-
|
|
200
|
-
onFinished: () => void
|
|
201
|
-
onOpenWifiSetup: () => void
|
|
202
|
-
onStartProgress: () => void
|
|
184
|
+
superMode: boolean
|
|
203
185
|
translate: MentraLiveOtaFlowTranslate
|
|
204
|
-
}
|
|
186
|
+
}) {
|
|
187
|
+
const {state} = controller
|
|
205
188
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
onStartProgress,
|
|
214
|
-
translate,
|
|
215
|
-
}: CheckPageProps) {
|
|
216
|
-
const otaSnapshot = useEngineSnapshot(ota.snapshot, ota.onSnapshot)
|
|
217
|
-
const [checkState, setCheckState] = useState<CheckState>("checking")
|
|
218
|
-
const [isUpdateRequired, setIsUpdateRequired] = useState(true)
|
|
219
|
-
const [isDowngradeUpdate, setIsDowngradeUpdate] = useState(false)
|
|
220
|
-
const [errorKind, setErrorKind] = useState<"network" | "pin_unavailable">("network")
|
|
221
|
-
const [updateFingerprint, setUpdateFingerprint] = useState<string | null>(null)
|
|
222
|
-
const [retryGeneration, setRetryGeneration] = useState(0)
|
|
223
|
-
const performCheckGenerationRef = useRef(0)
|
|
224
|
-
const activeCheckKeyRef = useRef<string | null>(null)
|
|
225
|
-
const checkStartedRef = useRef(false)
|
|
226
|
-
const checkCompletedRef = useRef(false)
|
|
227
|
-
const selectedCheckResultRef = useRef<Awaited<ReturnType<typeof ota.checkForUpdates>> | null>(null)
|
|
228
|
-
const onFinishedRef = useRef(onFinished)
|
|
229
|
-
const onStartProgressRef = useRef(onStartProgress)
|
|
230
|
-
onFinishedRef.current = onFinished
|
|
231
|
-
onStartProgressRef.current = onStartProgress
|
|
232
|
-
|
|
233
|
-
const glassesWifiConnected = otaSnapshot.wifiConnected
|
|
234
|
-
const glassesWifiStatusKnown = otaSnapshot.wifiStatusKnown
|
|
235
|
-
const hotspotOtaSupported = otaSnapshot.hotspotOtaVersion === 1
|
|
236
|
-
const canInstallUpdate = glassesWifiStatusKnown && (glassesWifiConnected || hotspotOtaSupported)
|
|
237
|
-
const checkKey = `${generation}:${retryGeneration}`
|
|
238
|
-
|
|
239
|
-
const navigateToProgress = useCallback(() => {
|
|
240
|
-
ota.clearProgress()
|
|
241
|
-
onStartProgressRef.current()
|
|
242
|
-
}, [])
|
|
243
|
-
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
const MIN_DISPLAY_TIME_MS = 1100
|
|
246
|
-
const MAX_WAIT_FOR_VERSION_INFO_MS = 10_000
|
|
247
|
-
if (activeCheckKeyRef.current !== checkKey) {
|
|
248
|
-
activeCheckKeyRef.current = checkKey
|
|
249
|
-
checkStartedRef.current = false
|
|
250
|
-
checkCompletedRef.current = false
|
|
251
|
-
setCheckState("checking")
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const myGeneration = ++performCheckGenerationRef.current
|
|
255
|
-
let cancelled = false
|
|
256
|
-
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null
|
|
257
|
-
|
|
258
|
-
const performCheck = async () => {
|
|
259
|
-
if (checkCompletedRef.current) return
|
|
260
|
-
if (!otaSnapshot.connected) {
|
|
261
|
-
if (isOtaAutoChainActive()) {
|
|
262
|
-
const remainingMs = otaAutoChainReconnectWaitRemaining()
|
|
263
|
-
if (remainingMs === null) return
|
|
264
|
-
reconnectTimeout = setTimeout(() => {
|
|
265
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current || ota.snapshot().connected) return
|
|
266
|
-
stopOtaAutoChain()
|
|
267
|
-
checkCompletedRef.current = true
|
|
268
|
-
setCheckState("error")
|
|
269
|
-
}, remainingMs)
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
if (checkStartedRef.current) {
|
|
273
|
-
stopOtaAutoChain()
|
|
274
|
-
checkCompletedRef.current = true
|
|
275
|
-
setCheckState("error")
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
checkCompletedRef.current = true
|
|
279
|
-
onFinishedRef.current()
|
|
280
|
-
return
|
|
281
|
-
}
|
|
189
|
+
if (state.screen === "initializing") {
|
|
190
|
+
return (
|
|
191
|
+
<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
|
|
192
|
+
<ActivityIndicator size="large" color={colors.foreground} />
|
|
193
|
+
</FlowPage>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
282
196
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
waitForMtkVersionMs: 2000,
|
|
291
|
-
refreshVersionInfo: true,
|
|
292
|
-
fixClockBeforeCheck: false,
|
|
293
|
-
})
|
|
294
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current) return
|
|
295
|
-
if (!result.hasCheckCompleted && result.checkFailureReason === "network" && isOtaAutoChainActive()) {
|
|
296
|
-
await new Promise((resolve) => setTimeout(resolve, AUTO_CHAIN_NETWORK_RETRY_DELAY_MS))
|
|
297
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current) return
|
|
298
|
-
result = await ota.checkForUpdates({
|
|
299
|
-
waitForBuildNumberMs: MAX_WAIT_FOR_VERSION_INFO_MS,
|
|
300
|
-
waitForBesVersionMs: 5000,
|
|
301
|
-
waitForMtkVersionMs: 2000,
|
|
302
|
-
refreshVersionInfo: true,
|
|
303
|
-
fixClockBeforeCheck: false,
|
|
304
|
-
})
|
|
305
|
-
}
|
|
306
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current) return
|
|
307
|
-
selectedCheckResultRef.current = result
|
|
308
|
-
await new Promise((resolve) => setTimeout(resolve, Math.max(0, MIN_DISPLAY_TIME_MS - (Date.now() - startTime))))
|
|
309
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current) return
|
|
310
|
-
|
|
311
|
-
if (result.skippedReason === "disconnected") {
|
|
312
|
-
stopOtaAutoChain()
|
|
313
|
-
checkCompletedRef.current = true
|
|
314
|
-
setCheckState("error")
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
if (result.skippedReason === "missing_build") {
|
|
318
|
-
checkCompletedRef.current = true
|
|
319
|
-
if (isOtaAutoChainActive()) {
|
|
320
|
-
stopOtaAutoChain()
|
|
321
|
-
setCheckState("error")
|
|
322
|
-
} else {
|
|
323
|
-
onFinishedRef.current()
|
|
324
|
-
}
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
if (result.skippedReason === "dev_build") {
|
|
328
|
-
stopOtaAutoChain()
|
|
329
|
-
checkCompletedRef.current = true
|
|
330
|
-
ota.clearUpdateAvailable()
|
|
331
|
-
setCheckState("dev_build")
|
|
332
|
-
return
|
|
333
|
-
}
|
|
334
|
-
if (!result.hasCheckCompleted) {
|
|
335
|
-
stopOtaAutoChain()
|
|
336
|
-
checkCompletedRef.current = true
|
|
337
|
-
setErrorKind(result.checkFailureReason === "pin_unavailable" ? "pin_unavailable" : "network")
|
|
338
|
-
setCheckState("error")
|
|
339
|
-
return
|
|
340
|
-
}
|
|
341
|
-
if (result.updateAvailable && result.updateInfo) {
|
|
342
|
-
setIsUpdateRequired(result.isRequired)
|
|
343
|
-
setIsDowngradeUpdate(result.updateInfo.isDowngrade === true)
|
|
344
|
-
const fingerprint = otaAutoChainFingerprint(result)
|
|
345
|
-
setUpdateFingerprint(fingerprint)
|
|
346
|
-
if (isOtaAutoChainActive()) {
|
|
347
|
-
const snapshot = ota.snapshot()
|
|
348
|
-
if (!snapshot.wifiStatusKnown) return
|
|
349
|
-
if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
|
|
350
|
-
stopOtaAutoChain()
|
|
351
|
-
} else {
|
|
352
|
-
const admission = tryAdvanceOtaAutoChain(fingerprint, result.updateInfo.isDowngrade === true)
|
|
353
|
-
if (admission.advance) {
|
|
354
|
-
checkCompletedRef.current = true
|
|
355
|
-
ota.installSession.prepare(result)
|
|
356
|
-
navigateToProgress()
|
|
357
|
-
return
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
checkCompletedRef.current = true
|
|
362
|
-
setCheckState("update_available")
|
|
363
|
-
return
|
|
364
|
-
}
|
|
365
|
-
stopOtaAutoChain()
|
|
366
|
-
checkCompletedRef.current = true
|
|
367
|
-
ota.clearUpdateAvailable()
|
|
368
|
-
setCheckState("no_update")
|
|
369
|
-
} catch (error) {
|
|
370
|
-
console.error("OTA check failed:", error)
|
|
371
|
-
await new Promise((resolve) => setTimeout(resolve, Math.max(0, MIN_DISPLAY_TIME_MS - (Date.now() - startTime))))
|
|
372
|
-
if (cancelled || myGeneration !== performCheckGenerationRef.current) return
|
|
373
|
-
stopOtaAutoChain()
|
|
374
|
-
checkCompletedRef.current = true
|
|
375
|
-
setCheckState("error")
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
void performCheck()
|
|
380
|
-
return () => {
|
|
381
|
-
cancelled = true
|
|
382
|
-
if (reconnectTimeout) clearTimeout(reconnectTimeout)
|
|
383
|
-
}
|
|
384
|
-
}, [
|
|
385
|
-
checkKey,
|
|
386
|
-
otaSnapshot.connected,
|
|
387
|
-
otaSnapshot.wifiStatusKnown,
|
|
388
|
-
navigateToProgress,
|
|
389
|
-
])
|
|
390
|
-
|
|
391
|
-
const retry = () => {
|
|
392
|
-
setCheckState("checking")
|
|
393
|
-
setRetryGeneration((value) => value + 1)
|
|
197
|
+
if (state.screen === "checking") {
|
|
198
|
+
return (
|
|
199
|
+
<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
|
|
200
|
+
<BodyText colors={colors}>{translate("ota:checkingForUpdatesMessage")}</BodyText>
|
|
201
|
+
<ActivityIndicator size="large" color={colors.foreground} />
|
|
202
|
+
</FlowPage>
|
|
203
|
+
)
|
|
394
204
|
}
|
|
395
205
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
206
|
+
if (state.screen === "battery_required") {
|
|
207
|
+
return (
|
|
208
|
+
<FlowPage
|
|
209
|
+
actions={
|
|
210
|
+
<>
|
|
211
|
+
<FlowButton colors={colors} disabled label={translate("ota:updateNow")} onPress={controller.install} />
|
|
212
|
+
{state.canDismiss ? (
|
|
213
|
+
<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary />
|
|
214
|
+
) : null}
|
|
215
|
+
</>
|
|
216
|
+
}
|
|
217
|
+
colors={colors}
|
|
218
|
+
icon="alert"
|
|
219
|
+
title={translate("ota:batteryRequiredTitle", {deviceName})}>
|
|
220
|
+
<BodyText colors={colors}>
|
|
221
|
+
{translate("ota:batteryRequiredMessage", {
|
|
222
|
+
batteryLevel: String(state.batteryLevel),
|
|
223
|
+
deviceName,
|
|
224
|
+
minimumBatteryLevel: String(MINIMUM_OTA_BATTERY_LEVEL),
|
|
225
|
+
})}
|
|
226
|
+
</BodyText>
|
|
227
|
+
<BodyText colors={colors}>{translate("ota:batteryRequiredLiveUpdate")}</BodyText>
|
|
228
|
+
</FlowPage>
|
|
229
|
+
)
|
|
414
230
|
}
|
|
415
231
|
|
|
416
|
-
if (
|
|
232
|
+
if (state.screen === "finishing") {
|
|
417
233
|
return (
|
|
418
|
-
<FlowPage colors={colors} icon="download" title={translate("ota:
|
|
419
|
-
<BodyText colors={colors}>{translate("ota:
|
|
234
|
+
<FlowPage colors={colors} icon="download" title={translate("ota:finishingUpdate")}>
|
|
235
|
+
<BodyText colors={colors}>{translate("ota:checkingAdditionalUpdates")}</BodyText>
|
|
420
236
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
421
237
|
</FlowPage>
|
|
422
238
|
)
|
|
423
239
|
}
|
|
424
240
|
|
|
425
|
-
if (
|
|
241
|
+
if (state.screen === "update_available" || state.screen === "wifi_required") {
|
|
242
|
+
const titleKey =
|
|
243
|
+
state.screen === "wifi_required"
|
|
244
|
+
? "ota:wifiRequiredTitle"
|
|
245
|
+
: state.versionChange
|
|
246
|
+
? "ota:downgradeAvailable"
|
|
247
|
+
: "ota:updateAvailable"
|
|
426
248
|
return (
|
|
427
249
|
<FlowPage
|
|
428
250
|
colors={colors}
|
|
429
251
|
icon="download"
|
|
430
|
-
title={translate(
|
|
252
|
+
title={translate(titleKey, {deviceName})}
|
|
431
253
|
actions={
|
|
432
254
|
<>
|
|
433
255
|
<FlowButton
|
|
434
256
|
colors={colors}
|
|
435
|
-
disabled={!
|
|
436
|
-
label={translate(
|
|
437
|
-
onPress={
|
|
257
|
+
disabled={!state.wifiStatusKnown}
|
|
258
|
+
label={translate(state.screen === "wifi_required" ? "ota:setupWifi" : "ota:updateNow")}
|
|
259
|
+
onPress={state.screen === "wifi_required" ? controller.openWifiSetup : controller.install}
|
|
438
260
|
/>
|
|
439
|
-
{
|
|
440
|
-
<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={
|
|
261
|
+
{state.canDismiss ? (
|
|
262
|
+
<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary />
|
|
441
263
|
) : null}
|
|
442
|
-
{allowDevSkip &&
|
|
443
|
-
<FlowButton colors={colors} label="Skip (dev only)" onPress={
|
|
264
|
+
{allowDevSkip && state.updateRequired ? (
|
|
265
|
+
<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
|
|
444
266
|
) : null}
|
|
445
267
|
</>
|
|
446
268
|
}>
|
|
269
|
+
{state.releaseTransition ? (
|
|
270
|
+
<BodyText colors={colors}>
|
|
271
|
+
{state.releaseTransition.fromVersion
|
|
272
|
+
? translate("ota:releaseTransition", {
|
|
273
|
+
fromVersion: state.releaseTransition.fromVersion,
|
|
274
|
+
toVersion: state.releaseTransition.toVersion,
|
|
275
|
+
})
|
|
276
|
+
: translate("ota:releaseTransitionUnknown", {toVersion: state.releaseTransition.toVersion})}
|
|
277
|
+
</BodyText>
|
|
278
|
+
) : null}
|
|
447
279
|
<BodyText colors={colors}>
|
|
448
|
-
{
|
|
449
|
-
? translate(
|
|
450
|
-
: translate("ota:
|
|
280
|
+
{state.screen === "wifi_required"
|
|
281
|
+
? translate("ota:updateConnectWifi", {deviceName})
|
|
282
|
+
: translate(state.versionChange ? "ota:downgradeDescription" : "ota:updateDescription")}
|
|
451
283
|
</BodyText>
|
|
284
|
+
<BodyText colors={colors}>{translate("ota:updateSequenceMessage")}</BodyText>
|
|
452
285
|
</FlowPage>
|
|
453
286
|
)
|
|
454
287
|
}
|
|
455
288
|
|
|
456
|
-
if (
|
|
289
|
+
if (state.screen === "dev_build") {
|
|
457
290
|
return (
|
|
458
291
|
<FlowPage
|
|
459
|
-
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
292
|
+
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
|
|
460
293
|
colors={colors}
|
|
461
294
|
icon="settings"
|
|
462
295
|
title={translate("ota:devBuild")}>
|
|
@@ -465,22 +298,35 @@ function OtaCheckPage({
|
|
|
465
298
|
)
|
|
466
299
|
}
|
|
467
300
|
|
|
468
|
-
if (
|
|
301
|
+
if (state.screen === "up_to_date") {
|
|
469
302
|
return (
|
|
470
303
|
<FlowPage
|
|
471
|
-
actions={
|
|
304
|
+
actions={
|
|
305
|
+
<FlowButton
|
|
306
|
+
colors={colors}
|
|
307
|
+
label={translate(state.completedUpdate ? "common:done" : "common:continue")}
|
|
308
|
+
onPress={controller.finish}
|
|
309
|
+
/>
|
|
310
|
+
}
|
|
472
311
|
colors={colors}
|
|
312
|
+
contentAlignment={state.changelogs.length > 0 ? "top" : "center"}
|
|
473
313
|
icon="check"
|
|
474
|
-
title={translate("ota:upToDate")}>
|
|
314
|
+
title={translate(state.completedUpdate ? "ota:updateComplete" : "ota:upToDate")}>
|
|
475
315
|
<BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
|
|
316
|
+
{state.releaseTransition ? (
|
|
317
|
+
<BodyText colors={colors}>
|
|
318
|
+
{translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
|
|
319
|
+
</BodyText>
|
|
320
|
+
) : null}
|
|
321
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} title={translate("ota:whatsNew")} />
|
|
476
322
|
</FlowPage>
|
|
477
323
|
)
|
|
478
324
|
}
|
|
479
325
|
|
|
480
|
-
if (
|
|
326
|
+
if (state.screen === "update_info_unavailable") {
|
|
481
327
|
return (
|
|
482
328
|
<FlowPage
|
|
483
|
-
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
329
|
+
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
|
|
484
330
|
colors={colors}
|
|
485
331
|
icon="alert"
|
|
486
332
|
title={translate("ota:updateInfoUnavailable")}>
|
|
@@ -489,104 +335,32 @@ function OtaCheckPage({
|
|
|
489
335
|
)
|
|
490
336
|
}
|
|
491
337
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
colors: MentraLiveOtaFlowTheme
|
|
510
|
-
onFirmwareRestartingChange?: (restarting: boolean, progressActive: boolean) => void
|
|
511
|
-
onOpenWifiSetup: () => void
|
|
512
|
-
onReturnToCheck: () => void
|
|
513
|
-
superMode: boolean
|
|
514
|
-
translate: MentraLiveOtaFlowTranslate
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function OtaProgressPage({
|
|
518
|
-
colors,
|
|
519
|
-
onFirmwareRestartingChange,
|
|
520
|
-
onOpenWifiSetup,
|
|
521
|
-
onReturnToCheck,
|
|
522
|
-
superMode,
|
|
523
|
-
translate,
|
|
524
|
-
}: ProgressPageProps) {
|
|
525
|
-
const install = useEngineSnapshot(ota.installSession.snapshot, ota.installSession.onSnapshot)
|
|
526
|
-
const autoChainAdvancedRef = useRef(false)
|
|
527
|
-
const {
|
|
528
|
-
connected,
|
|
529
|
-
continueButtonDisabled,
|
|
530
|
-
displayState,
|
|
531
|
-
errorMsg,
|
|
532
|
-
hotspotArtifactPercent,
|
|
533
|
-
hotspotPhase,
|
|
534
|
-
isVersionChange,
|
|
535
|
-
mtkInstallStallSimulatedPercent,
|
|
536
|
-
otaProgress,
|
|
537
|
-
otaStatus,
|
|
538
|
-
versionChangeConverged,
|
|
539
|
-
versionChangePhase,
|
|
540
|
-
} = install
|
|
541
|
-
const firmwareRestarting = (!connected && displayState === "restarting") || versionChangePhase === "restarting"
|
|
542
|
-
|
|
543
|
-
useEffect(() => {
|
|
544
|
-
onFirmwareRestartingChange?.(firmwareRestarting, true)
|
|
545
|
-
}, [firmwareRestarting, onFirmwareRestartingChange])
|
|
546
|
-
|
|
547
|
-
useEffect(
|
|
548
|
-
() => () => {
|
|
549
|
-
onFirmwareRestartingChange?.(false, false)
|
|
550
|
-
},
|
|
551
|
-
[onFirmwareRestartingChange],
|
|
552
|
-
)
|
|
553
|
-
|
|
554
|
-
useEffect(() => {
|
|
555
|
-
ota.installSession.attach()
|
|
556
|
-
return () => ota.installSession.detach()
|
|
557
|
-
}, [])
|
|
558
|
-
|
|
559
|
-
useEffect(() => {
|
|
560
|
-
if (displayState !== "complete" || !connected || !isOtaAutoChainActive() || autoChainAdvancedRef.current) return
|
|
561
|
-
const timeout = setTimeout(() => {
|
|
562
|
-
if (!isOtaAutoChainActive()) return
|
|
563
|
-
autoChainAdvancedRef.current = true
|
|
564
|
-
ota.installSession.finish()
|
|
565
|
-
onReturnToCheck()
|
|
566
|
-
}, AUTO_CHAIN_COMPLETE_DELAY_MS)
|
|
567
|
-
return () => clearTimeout(timeout)
|
|
568
|
-
}, [connected, displayState, onReturnToCheck])
|
|
569
|
-
|
|
570
|
-
const finishAndCheck = () => {
|
|
571
|
-
ota.installSession.finish()
|
|
572
|
-
onReturnToCheck()
|
|
573
|
-
}
|
|
574
|
-
const stopAndCheck = () => {
|
|
575
|
-
stopOtaAutoChain()
|
|
576
|
-
finishAndCheck()
|
|
577
|
-
}
|
|
578
|
-
const retryInstall = () => {
|
|
579
|
-
onFirmwareRestartingChange?.(false, true)
|
|
580
|
-
ota.installSession.retry()
|
|
338
|
+
if (state.screen === "check_failed") {
|
|
339
|
+
return (
|
|
340
|
+
<FlowPage
|
|
341
|
+
actions={
|
|
342
|
+
<>
|
|
343
|
+
<FlowButton colors={colors} label="Retry" onPress={controller.retryCheck} />
|
|
344
|
+
{allowDevSkip ? (
|
|
345
|
+
<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
|
|
346
|
+
) : null}
|
|
347
|
+
</>
|
|
348
|
+
}
|
|
349
|
+
colors={colors}
|
|
350
|
+
icon="alert"
|
|
351
|
+
title={translate("ota:checkFailed")}>
|
|
352
|
+
<BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
|
|
353
|
+
</FlowPage>
|
|
354
|
+
)
|
|
581
355
|
}
|
|
582
356
|
|
|
583
|
-
if (versionChangePhase === "restarting" || versionChangePhase === "verifying") {
|
|
357
|
+
if (state.versionChangePhase === "restarting" || state.versionChangePhase === "verifying") {
|
|
584
358
|
return (
|
|
585
359
|
<FlowPage
|
|
586
360
|
colors={colors}
|
|
587
361
|
icon="download"
|
|
588
362
|
title={translate(
|
|
589
|
-
versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting",
|
|
363
|
+
state.versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting",
|
|
590
364
|
)}>
|
|
591
365
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
592
366
|
<BodyText colors={colors}>{translate("ota:versionChangeKeepNearby")}</BodyText>
|
|
@@ -595,19 +369,19 @@ function OtaProgressPage({
|
|
|
595
369
|
)
|
|
596
370
|
}
|
|
597
371
|
|
|
598
|
-
if (
|
|
599
|
-
const
|
|
600
|
-
hotspotPhase === "downloading"
|
|
372
|
+
if (state.screen === "starting" || state.screen === "preparing_hotspot") {
|
|
373
|
+
const title =
|
|
374
|
+
state.hotspotPhase === "downloading"
|
|
601
375
|
? "Downloading update to phone..."
|
|
602
|
-
: hotspotPhase === "starting_hotspot"
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
376
|
+
: state.hotspotPhase === "starting_hotspot"
|
|
377
|
+
? "Starting glasses hotspot..."
|
|
378
|
+
: state.hotspotPhase === "joining_hotspot"
|
|
379
|
+
? "Connecting phone to glasses..."
|
|
380
|
+
: "Starting update..."
|
|
607
381
|
return (
|
|
608
|
-
<FlowPage colors={colors} icon="download" title={
|
|
609
|
-
{hotspotPhase === "downloading" && hotspotArtifactPercent !== null ? (
|
|
610
|
-
<PercentText colors={colors} percent={hotspotArtifactPercent} />
|
|
382
|
+
<FlowPage colors={colors} icon="download" title={title}>
|
|
383
|
+
{state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
|
|
384
|
+
<PercentText colors={colors} percent={state.hotspotArtifactPercent} />
|
|
611
385
|
) : null}
|
|
612
386
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
613
387
|
<BodyText colors={colors}>Do not disconnect your glasses</BodyText>
|
|
@@ -615,41 +389,34 @@ function OtaProgressPage({
|
|
|
615
389
|
)
|
|
616
390
|
}
|
|
617
391
|
|
|
618
|
-
if (
|
|
619
|
-
const isDownload = otaStatus?.phase === "download"
|
|
620
|
-
const totalSteps = otaStatus?.totalSteps ?? 1
|
|
621
|
-
const isApkOnlyInstalling = otaStatus?.stepType === "apk" && otaStatus.phase === "install" && totalSteps === 1
|
|
622
|
-
const rawPercent = isDownload
|
|
623
|
-
? otaStatus?.stepPercent ?? 0
|
|
624
|
-
: totalSteps >= 2
|
|
625
|
-
? otaStatus?.overallPercent ?? 0
|
|
626
|
-
: otaStatus?.stepPercent ?? 0
|
|
627
|
-
const percent = Math.min(Math.max(rawPercent, mtkInstallStallSimulatedPercent ?? 0, 0), 100)
|
|
392
|
+
if (state.screen === "updating") {
|
|
628
393
|
return (
|
|
629
|
-
<FlowPage colors={colors} icon="download" title={
|
|
630
|
-
{
|
|
394
|
+
<FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
|
|
395
|
+
{state.installingApkOnly ? (
|
|
631
396
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
632
397
|
) : (
|
|
633
398
|
<>
|
|
634
|
-
<PercentText colors={colors} percent={
|
|
399
|
+
<PercentText colors={colors} percent={state.progress ?? 0} />
|
|
635
400
|
<View style={[styles.progressTrack, {backgroundColor: colors.border}]}>
|
|
636
|
-
<View
|
|
401
|
+
<View
|
|
402
|
+
style={[styles.progressFill, {backgroundColor: colors.primary, width: `${state.progress ?? 0}%`}]}
|
|
403
|
+
/>
|
|
637
404
|
</View>
|
|
638
405
|
</>
|
|
639
406
|
)}
|
|
640
407
|
<BodyText colors={colors}>Do not disconnect your glasses</BodyText>
|
|
641
|
-
{
|
|
408
|
+
{state.versionChange && state.phase === "install" ? (
|
|
642
409
|
<BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
|
|
643
410
|
) : null}
|
|
644
411
|
</FlowPage>
|
|
645
412
|
)
|
|
646
413
|
}
|
|
647
414
|
|
|
648
|
-
if (
|
|
415
|
+
if (state.screen === "restarting") {
|
|
649
416
|
return (
|
|
650
417
|
<FlowPage
|
|
651
418
|
actions={
|
|
652
|
-
<FlowButton colors={colors} disabled={
|
|
419
|
+
<FlowButton colors={colors} disabled={state.continueDisabled} label="Continue" onPress={controller.finish} />
|
|
653
420
|
}
|
|
654
421
|
colors={colors}
|
|
655
422
|
icon="check"
|
|
@@ -658,66 +425,60 @@ function OtaProgressPage({
|
|
|
658
425
|
)
|
|
659
426
|
}
|
|
660
427
|
|
|
661
|
-
if (
|
|
662
|
-
const title = versionChangeConverged
|
|
428
|
+
if (state.screen === "complete") {
|
|
429
|
+
const title = state.versionChangeConverged
|
|
663
430
|
? translate("ota:versionChangeComplete")
|
|
664
|
-
:
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const message = versionChangeConverged
|
|
431
|
+
: state.versionChange
|
|
432
|
+
? translate("ota:versionChangeFirmwarePassComplete")
|
|
433
|
+
: "Update complete!"
|
|
434
|
+
const message = state.versionChangeConverged
|
|
668
435
|
? translate("ota:versionChangeCompleteMessage")
|
|
669
|
-
:
|
|
670
|
-
|
|
671
|
-
|
|
436
|
+
: state.versionChange
|
|
437
|
+
? translate("ota:versionChangeFirmwarePassCompleteMessage")
|
|
438
|
+
: "Your glasses are up to date."
|
|
672
439
|
return (
|
|
673
440
|
<FlowPage
|
|
674
441
|
actions={
|
|
675
442
|
<FlowButton
|
|
676
443
|
colors={colors}
|
|
677
|
-
label={
|
|
678
|
-
onPress={
|
|
444
|
+
label={state.versionChange && !state.versionChangeConverged ? "Continue" : "Done"}
|
|
445
|
+
onPress={controller.finish}
|
|
679
446
|
/>
|
|
680
447
|
}
|
|
681
448
|
colors={colors}
|
|
449
|
+
contentAlignment={state.changelogs.length > 0 ? "top" : "center"}
|
|
682
450
|
icon="check"
|
|
683
451
|
title={title}>
|
|
684
452
|
<BodyText colors={colors}>{message}</BodyText>
|
|
453
|
+
{state.releaseTransition ? (
|
|
454
|
+
<BodyText colors={colors}>
|
|
455
|
+
{translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
|
|
456
|
+
</BodyText>
|
|
457
|
+
) : null}
|
|
458
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} title={translate("ota:whatsNew")} />
|
|
685
459
|
</FlowPage>
|
|
686
460
|
)
|
|
687
461
|
}
|
|
688
462
|
|
|
689
|
-
if (
|
|
690
|
-
const requiresGlassesReboot = shouldRequireGlassesRebootForBesFailure(otaStatus, otaProgress, errorMsg)
|
|
691
|
-
const displayedError = requiresGlassesReboot
|
|
692
|
-
? BES_INSTALL_RESTART_MESSAGE
|
|
693
|
-
: errorMsg || getOtaErrorMessage(otaStatus?.error)
|
|
694
|
-
const showChangeWifi = shouldShowChangeWifiForOtaDownloadFailure(otaStatus, otaProgress, errorMsg)
|
|
463
|
+
if (state.screen === "failed") {
|
|
695
464
|
return (
|
|
696
465
|
<FlowPage
|
|
697
466
|
actions={
|
|
698
467
|
<>
|
|
699
468
|
<FlowButton
|
|
700
469
|
colors={colors}
|
|
701
|
-
label={
|
|
702
|
-
onPress={
|
|
470
|
+
label={state.canRetry ? "Retry" : "Done"}
|
|
471
|
+
onPress={state.canRetry ? controller.retryInstall : controller.finish}
|
|
703
472
|
/>
|
|
704
|
-
{
|
|
705
|
-
<FlowButton
|
|
706
|
-
colors={colors}
|
|
707
|
-
label="Change WiFi"
|
|
708
|
-
onPress={() => {
|
|
709
|
-
stopOtaAutoChain()
|
|
710
|
-
onOpenWifiSetup()
|
|
711
|
-
}}
|
|
712
|
-
secondary
|
|
713
|
-
/>
|
|
473
|
+
{state.canOpenWifiSetup ? (
|
|
474
|
+
<FlowButton colors={colors} label="Change WiFi" onPress={controller.openWifiSetup} secondary />
|
|
714
475
|
) : null}
|
|
715
476
|
</>
|
|
716
477
|
}
|
|
717
478
|
colors={colors}
|
|
718
479
|
icon="alert"
|
|
719
480
|
title="Update Failed">
|
|
720
|
-
<BodyText colors={colors}>{
|
|
481
|
+
<BodyText colors={colors}>{state.error?.message}</BodyText>
|
|
721
482
|
</FlowPage>
|
|
722
483
|
)
|
|
723
484
|
}
|
|
@@ -726,16 +487,7 @@ function OtaProgressPage({
|
|
|
726
487
|
<FlowPage
|
|
727
488
|
actions={
|
|
728
489
|
superMode ? (
|
|
729
|
-
<FlowButton
|
|
730
|
-
colors={colors}
|
|
731
|
-
label="Skip (super)"
|
|
732
|
-
onPress={() => {
|
|
733
|
-
stopOtaAutoChain()
|
|
734
|
-
ota.installSession.discard()
|
|
735
|
-
onReturnToCheck()
|
|
736
|
-
}}
|
|
737
|
-
secondary
|
|
738
|
-
/>
|
|
490
|
+
<FlowButton colors={colors} label="Skip (super)" onPress={controller.discard} secondary />
|
|
739
491
|
) : undefined
|
|
740
492
|
}
|
|
741
493
|
colors={colors}
|
|
@@ -751,14 +503,15 @@ type FlowPageProps = {
|
|
|
751
503
|
actions?: React.ReactNode
|
|
752
504
|
children?: React.ReactNode
|
|
753
505
|
colors: MentraLiveOtaFlowTheme
|
|
506
|
+
contentAlignment?: "center" | "top"
|
|
754
507
|
icon: "alert" | "bluetooth" | "check" | "download" | "settings"
|
|
755
508
|
title: string
|
|
756
509
|
}
|
|
757
510
|
|
|
758
|
-
function FlowPage({actions, children, colors, icon, title}: FlowPageProps) {
|
|
511
|
+
function FlowPage({actions, children, colors, contentAlignment = "center", icon, title}: FlowPageProps) {
|
|
759
512
|
return (
|
|
760
513
|
<View style={styles.page} testID="mentra-live-ota-flow">
|
|
761
|
-
<View style={styles.centerContent}>
|
|
514
|
+
<View style={[styles.centerContent, contentAlignment === "top" && styles.topContent]}>
|
|
762
515
|
<FlowIcon colors={colors} name={icon} />
|
|
763
516
|
<Text style={[styles.title, {color: colors.foreground}]}>{title}</Text>
|
|
764
517
|
{children}
|
|
@@ -776,6 +529,132 @@ function PercentText({colors, percent}: {colors: MentraLiveOtaFlowTheme; percent
|
|
|
776
529
|
return <Text style={[styles.percent, {color: colors.primary}]}>{Math.round(percent)}%</Text>
|
|
777
530
|
}
|
|
778
531
|
|
|
532
|
+
function ChangelogMarkdown({colors, markdown}: {colors: MentraLiveOtaFlowTheme; markdown: string}) {
|
|
533
|
+
const markdownStyles = useMemo<MarkedStyles>(
|
|
534
|
+
() => ({
|
|
535
|
+
blockquote: {
|
|
536
|
+
borderLeftColor: colors.primary,
|
|
537
|
+
borderLeftWidth: 3,
|
|
538
|
+
marginVertical: 2,
|
|
539
|
+
opacity: 1,
|
|
540
|
+
paddingLeft: 12,
|
|
541
|
+
},
|
|
542
|
+
code: {
|
|
543
|
+
backgroundColor: colors.background,
|
|
544
|
+
borderColor: colors.border,
|
|
545
|
+
borderRadius: 8,
|
|
546
|
+
borderWidth: 1,
|
|
547
|
+
padding: 12,
|
|
548
|
+
},
|
|
549
|
+
codespan: {
|
|
550
|
+
backgroundColor: colors.border,
|
|
551
|
+
color: colors.foreground,
|
|
552
|
+
fontFamily: "monospace",
|
|
553
|
+
fontSize: 13,
|
|
554
|
+
fontStyle: "normal",
|
|
555
|
+
fontWeight: "400",
|
|
556
|
+
},
|
|
557
|
+
em: {color: colors.textDim, fontSize: 14, lineHeight: 20},
|
|
558
|
+
h1: {
|
|
559
|
+
borderBottomWidth: 0,
|
|
560
|
+
color: colors.foreground,
|
|
561
|
+
fontSize: 18,
|
|
562
|
+
fontWeight: "700",
|
|
563
|
+
lineHeight: 24,
|
|
564
|
+
marginVertical: 0,
|
|
565
|
+
paddingBottom: 0,
|
|
566
|
+
},
|
|
567
|
+
h2: {
|
|
568
|
+
borderBottomWidth: 0,
|
|
569
|
+
color: colors.foreground,
|
|
570
|
+
fontSize: 17,
|
|
571
|
+
fontWeight: "700",
|
|
572
|
+
lineHeight: 23,
|
|
573
|
+
marginVertical: 0,
|
|
574
|
+
paddingBottom: 0,
|
|
575
|
+
},
|
|
576
|
+
h3: {color: colors.foreground, fontSize: 16, fontWeight: "700", lineHeight: 22, marginVertical: 0},
|
|
577
|
+
h4: {color: colors.foreground, fontSize: 15, fontWeight: "700", lineHeight: 21, marginVertical: 0},
|
|
578
|
+
h5: {color: colors.foreground, fontSize: 14, fontWeight: "700", lineHeight: 20, marginVertical: 0},
|
|
579
|
+
h6: {color: colors.textDim, fontSize: 14, fontWeight: "600", lineHeight: 20, marginVertical: 0},
|
|
580
|
+
hr: {borderBottomColor: colors.border, marginVertical: 2},
|
|
581
|
+
image: {borderRadius: 8},
|
|
582
|
+
li: {color: colors.textDim, fontSize: 14, lineHeight: 20},
|
|
583
|
+
link: {
|
|
584
|
+
color: colors.primary,
|
|
585
|
+
fontSize: 14,
|
|
586
|
+
fontStyle: "normal",
|
|
587
|
+
lineHeight: 20,
|
|
588
|
+
textDecorationLine: "underline",
|
|
589
|
+
},
|
|
590
|
+
paragraph: {paddingVertical: 0},
|
|
591
|
+
strikethrough: {color: colors.textDim, fontSize: 14, lineHeight: 20},
|
|
592
|
+
strong: {color: colors.foreground, fontSize: 14, fontWeight: "700", lineHeight: 20},
|
|
593
|
+
table: {borderColor: colors.border, borderRadius: 8},
|
|
594
|
+
tableCell: {padding: 8},
|
|
595
|
+
text: {color: colors.textDim, fontSize: 14, lineHeight: 20},
|
|
596
|
+
}),
|
|
597
|
+
[colors],
|
|
598
|
+
)
|
|
599
|
+
const markdownTheme = useMemo<NonNullable<useMarkdownHookOptions["theme"]>>(
|
|
600
|
+
() => ({
|
|
601
|
+
colors: {
|
|
602
|
+
background: colors.background,
|
|
603
|
+
border: colors.border,
|
|
604
|
+
code: colors.border,
|
|
605
|
+
link: colors.primary,
|
|
606
|
+
text: colors.textDim,
|
|
607
|
+
},
|
|
608
|
+
}),
|
|
609
|
+
[colors],
|
|
610
|
+
)
|
|
611
|
+
const elements = useMarkdown(markdown, {colorScheme: "light", styles: markdownStyles, theme: markdownTheme})
|
|
612
|
+
|
|
613
|
+
return (
|
|
614
|
+
<View style={styles.changelogMarkdownContent} testID="ota-changelog-markdown">
|
|
615
|
+
{elements}
|
|
616
|
+
</View>
|
|
617
|
+
)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export function ChangelogList({
|
|
621
|
+
changelogs,
|
|
622
|
+
colors,
|
|
623
|
+
title,
|
|
624
|
+
}: {
|
|
625
|
+
changelogs: MentraLiveOtaController["state"]["changelogs"]
|
|
626
|
+
colors: MentraLiveOtaFlowTheme
|
|
627
|
+
title: string
|
|
628
|
+
}) {
|
|
629
|
+
if (changelogs.length === 0) return null
|
|
630
|
+
return (
|
|
631
|
+
<View style={[styles.changelogCard, {borderColor: colors.border}]} testID="ota-changelog-card">
|
|
632
|
+
<Text style={[styles.changelogTitle, {color: colors.foreground}]}>{title}</Text>
|
|
633
|
+
<ScrollView
|
|
634
|
+
contentContainerStyle={styles.changelogContent}
|
|
635
|
+
nestedScrollEnabled
|
|
636
|
+
showsVerticalScrollIndicator
|
|
637
|
+
style={styles.changelogList}
|
|
638
|
+
testID="ota-changelog-scroll">
|
|
639
|
+
{changelogs.map((entry, index) => (
|
|
640
|
+
<View
|
|
641
|
+
key={entry.version}
|
|
642
|
+
style={[
|
|
643
|
+
styles.changelogEntry,
|
|
644
|
+
index > 0 && styles.changelogEntryDivider,
|
|
645
|
+
index > 0 && {borderTopColor: colors.border},
|
|
646
|
+
]}>
|
|
647
|
+
<Text selectable style={[styles.changelogVersion, {color: colors.foreground}]}>
|
|
648
|
+
{entry.version}
|
|
649
|
+
</Text>
|
|
650
|
+
<ChangelogMarkdown colors={colors} markdown={entry.markdown} />
|
|
651
|
+
</View>
|
|
652
|
+
))}
|
|
653
|
+
</ScrollView>
|
|
654
|
+
</View>
|
|
655
|
+
)
|
|
656
|
+
}
|
|
657
|
+
|
|
779
658
|
function FlowButton({
|
|
780
659
|
colors,
|
|
781
660
|
disabled = false,
|
|
@@ -797,13 +676,13 @@ function FlowButton({
|
|
|
797
676
|
style={({pressed}) => [
|
|
798
677
|
styles.button,
|
|
799
678
|
{
|
|
800
|
-
backgroundColor: secondary ? colors.background : colors.
|
|
801
|
-
borderColor: secondary ? colors.border : colors.
|
|
679
|
+
backgroundColor: secondary ? colors.background : colors.foreground,
|
|
680
|
+
borderColor: secondary ? colors.border : colors.foreground,
|
|
802
681
|
opacity: disabled ? 0.45 : pressed ? 0.75 : 1,
|
|
803
682
|
},
|
|
804
683
|
]}
|
|
805
684
|
testID={`button-${label}`}>
|
|
806
|
-
<Text style={[styles.buttonText, {color: secondary ? colors.foreground : colors.
|
|
685
|
+
<Text style={[styles.buttonText, {color: secondary ? colors.foreground : colors.background}]}>{label}</Text>
|
|
807
686
|
</Pressable>
|
|
808
687
|
)
|
|
809
688
|
}
|
|
@@ -836,6 +715,7 @@ const styles = StyleSheet.create({
|
|
|
836
715
|
},
|
|
837
716
|
page: {flex: 1, paddingBottom: 24, paddingHorizontal: 24},
|
|
838
717
|
centerContent: {alignItems: "center", flex: 1, gap: 16, justifyContent: "center"},
|
|
718
|
+
topContent: {justifyContent: "flex-start", paddingTop: 12},
|
|
839
719
|
actionSpacer: {height: 48},
|
|
840
720
|
actions: {gap: 12},
|
|
841
721
|
icon: {fontSize: 64, fontWeight: "500", lineHeight: 72, textAlign: "center"},
|
|
@@ -844,13 +724,30 @@ const styles = StyleSheet.create({
|
|
|
844
724
|
percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
|
|
845
725
|
progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
|
|
846
726
|
progressFill: {borderRadius: 4, height: 8},
|
|
727
|
+
changelogCard: {
|
|
728
|
+
borderRadius: 16,
|
|
729
|
+
borderWidth: 1,
|
|
730
|
+
flexShrink: 1,
|
|
731
|
+
gap: 12,
|
|
732
|
+
maxHeight: 300,
|
|
733
|
+
maxWidth: 420,
|
|
734
|
+
padding: 16,
|
|
735
|
+
width: "100%",
|
|
736
|
+
},
|
|
737
|
+
changelogTitle: {fontSize: 16, fontWeight: "700"},
|
|
738
|
+
changelogList: {flexShrink: 1, maxHeight: 232, width: "100%"},
|
|
739
|
+
changelogContent: {gap: 20, paddingBottom: 2},
|
|
740
|
+
changelogEntry: {gap: 8},
|
|
741
|
+
changelogEntryDivider: {borderTopWidth: StyleSheet.hairlineWidth, paddingTop: 20},
|
|
742
|
+
changelogVersion: {fontSize: 14, fontWeight: "600"},
|
|
743
|
+
changelogMarkdownContent: {gap: 10},
|
|
847
744
|
button: {
|
|
848
745
|
alignItems: "center",
|
|
849
|
-
borderRadius:
|
|
746
|
+
borderRadius: 50,
|
|
850
747
|
borderWidth: 1,
|
|
851
748
|
justifyContent: "center",
|
|
852
|
-
minHeight:
|
|
749
|
+
minHeight: 44,
|
|
853
750
|
paddingHorizontal: 20,
|
|
854
751
|
},
|
|
855
|
-
buttonText: {fontSize:
|
|
752
|
+
buttonText: {fontSize: 14, fontWeight: "500"},
|
|
856
753
|
})
|