@mentra/engine 3.1.0-dev.4 → 3.1.0-dev.42
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 +19 -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 +9 -6
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +6 -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 +3 -2
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -1
- package/build/react/MentraLiveOtaFlow.js +113 -352
- 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 +80 -0
- package/build/react/useMentraLiveOta.d.ts.map +1 -0
- package/build/react/useMentraLiveOta.js +550 -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 +12 -6
- package/src/bluetooth-sdk/ota-transport.ts +1 -0
- package/src/facades/ota.ts +16 -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 +178 -465
- package/src/react/index.ts +14 -0
- package/src/react/useMentraLiveOta.ts +707 -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,21 @@
|
|
|
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"
|
|
3
13
|
import {SafeAreaView} from "react-native-safe-area-context"
|
|
4
14
|
import Svg, {Path, Rect} from "react-native-svg"
|
|
5
15
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
beginOtaAutoChain,
|
|
9
|
-
clearOtaAutoChainReconnectWait,
|
|
10
|
-
isOtaAutoChainActive,
|
|
11
|
-
otaAutoChainFingerprint,
|
|
12
|
-
otaAutoChainReconnectWaitRemaining,
|
|
13
|
-
stopOtaAutoChain,
|
|
14
|
-
tryAdvanceOtaAutoChain,
|
|
15
|
-
} from "../services/OtaAutoChain"
|
|
16
|
-
import {
|
|
17
|
-
BES_INSTALL_RESTART_MESSAGE,
|
|
18
|
-
getOtaErrorMessage,
|
|
19
|
-
shouldRequireGlassesRebootForBesFailure,
|
|
20
|
-
shouldShowChangeWifiForOtaDownloadFailure,
|
|
21
|
-
} from "../services/OtaErrorMapping"
|
|
22
|
-
import {useEngineSnapshot} from "./useEngineSnapshot"
|
|
16
|
+
import {useMentraLiveOta, type MentraLiveOtaController, type MentraLiveOtaFlowPage} from "./useMentraLiveOta"
|
|
23
17
|
|
|
24
|
-
export type MentraLiveOtaFlowPage
|
|
18
|
+
export type {MentraLiveOtaFlowPage} from "./useMentraLiveOta"
|
|
25
19
|
|
|
26
20
|
export type MentraLiveOtaFlowTheme = {
|
|
27
21
|
background: string
|
|
@@ -40,7 +34,7 @@ export type MentraLiveOtaFlowProps = {
|
|
|
40
34
|
deviceName?: string
|
|
41
35
|
/** Entry page. `progress` exists for recovery/deep-link compatibility. */
|
|
42
36
|
initialPage?: MentraLiveOtaFlowPage
|
|
43
|
-
/** Start the OTA-only projections. Full
|
|
37
|
+
/** Start the OTA-only projections. Full Engine hosts should pass false. */
|
|
44
38
|
initializeRuntime?: boolean
|
|
45
39
|
/** Called after the final check or when the user leaves an optional update. */
|
|
46
40
|
onFinished: () => void
|
|
@@ -59,11 +53,6 @@ export type MentraLiveOtaFlowProps = {
|
|
|
59
53
|
style?: StyleProp<ViewStyle>
|
|
60
54
|
}
|
|
61
55
|
|
|
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
56
|
const DEFAULT_THEME: MentraLiveOtaFlowTheme = {
|
|
68
57
|
background: "#FFFFFF",
|
|
69
58
|
border: "#D7DFDA",
|
|
@@ -83,6 +72,9 @@ const ENGLISH_COPY: Record<string, string> = {
|
|
|
83
72
|
"ota:updateConnectWifi": "Connect your {{deviceName}} to WiFi to install the update.",
|
|
84
73
|
"ota:updateDescription":
|
|
85
74
|
"A new update is available for your glasses. We recommend updating now for the best experience.",
|
|
75
|
+
"ota:releaseTransition": "{{fromVersion}} → {{toVersion}}",
|
|
76
|
+
"ota:releaseTransitionUnknown": "Current version unknown → {{toVersion}}",
|
|
77
|
+
"ota:updatedToVersion": "Updated to {{version}}",
|
|
86
78
|
"ota:downgradeAvailable": "{{deviceName}} Version Change Required",
|
|
87
79
|
"ota:downgradeDescription":
|
|
88
80
|
"This app requires an earlier glasses software version. Your photos and videos will be preserved, but glasses settings will be reset and restored automatically after the change.",
|
|
@@ -133,29 +125,13 @@ export function MentraLiveOtaFlow({
|
|
|
133
125
|
translate = defaultTranslate,
|
|
134
126
|
}: MentraLiveOtaFlowProps) {
|
|
135
127
|
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
|
-
}, [])
|
|
128
|
+
const controller = useMentraLiveOta({
|
|
129
|
+
initialPage,
|
|
130
|
+
initializeRuntime,
|
|
131
|
+
onFinished,
|
|
132
|
+
onFirmwareRestartingChange,
|
|
133
|
+
onOpenWifiSetup,
|
|
134
|
+
})
|
|
159
135
|
|
|
160
136
|
return (
|
|
161
137
|
<SafeAreaView style={[styles.safeArea, {backgroundColor: colors.background}, style]}>
|
|
@@ -163,257 +139,44 @@ export function MentraLiveOtaFlow({
|
|
|
163
139
|
<View />
|
|
164
140
|
<MentraMark color={colors.primary} />
|
|
165
141
|
</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
|
-
)}
|
|
142
|
+
<OtaFlowContent
|
|
143
|
+
allowDevSkip={allowDevSkip}
|
|
144
|
+
colors={colors}
|
|
145
|
+
controller={controller}
|
|
146
|
+
deviceName={deviceName}
|
|
147
|
+
superMode={superMode}
|
|
148
|
+
translate={translate}
|
|
149
|
+
/>
|
|
191
150
|
</SafeAreaView>
|
|
192
151
|
)
|
|
193
152
|
}
|
|
194
153
|
|
|
195
|
-
|
|
196
|
-
allowDevSkip: boolean
|
|
197
|
-
colors: MentraLiveOtaFlowTheme
|
|
198
|
-
deviceName: string
|
|
199
|
-
generation: number
|
|
200
|
-
onFinished: () => void
|
|
201
|
-
onOpenWifiSetup: () => void
|
|
202
|
-
onStartProgress: () => void
|
|
203
|
-
translate: MentraLiveOtaFlowTranslate
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function OtaCheckPage({
|
|
154
|
+
function OtaFlowContent({
|
|
207
155
|
allowDevSkip,
|
|
208
156
|
colors,
|
|
157
|
+
controller,
|
|
209
158
|
deviceName,
|
|
210
|
-
|
|
211
|
-
onFinished,
|
|
212
|
-
onOpenWifiSetup,
|
|
213
|
-
onStartProgress,
|
|
159
|
+
superMode,
|
|
214
160
|
translate,
|
|
215
|
-
}:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
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
|
-
}
|
|
282
|
-
|
|
283
|
-
clearOtaAutoChainReconnectWait()
|
|
284
|
-
checkStartedRef.current = true
|
|
285
|
-
const startTime = Date.now()
|
|
286
|
-
try {
|
|
287
|
-
let result = await ota.checkForUpdates({
|
|
288
|
-
waitForBuildNumberMs: MAX_WAIT_FOR_VERSION_INFO_MS,
|
|
289
|
-
waitForBesVersionMs: 5000,
|
|
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)
|
|
394
|
-
}
|
|
161
|
+
}: {
|
|
162
|
+
allowDevSkip: boolean
|
|
163
|
+
colors: MentraLiveOtaFlowTheme
|
|
164
|
+
controller: MentraLiveOtaController
|
|
165
|
+
deviceName: string
|
|
166
|
+
superMode: boolean
|
|
167
|
+
translate: MentraLiveOtaFlowTranslate
|
|
168
|
+
}) {
|
|
169
|
+
const {state} = controller
|
|
395
170
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const snapshot = ota.snapshot()
|
|
403
|
-
if (!snapshot.wifiStatusKnown) {
|
|
404
|
-
retry()
|
|
405
|
-
return
|
|
406
|
-
}
|
|
407
|
-
if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
|
|
408
|
-
onOpenWifiSetup()
|
|
409
|
-
return
|
|
410
|
-
}
|
|
411
|
-
ota.installSession.prepare(result)
|
|
412
|
-
if (updateFingerprint) beginOtaAutoChain(updateFingerprint, isDowngradeUpdate)
|
|
413
|
-
navigateToProgress()
|
|
171
|
+
if (state.screen === "initializing") {
|
|
172
|
+
return (
|
|
173
|
+
<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
|
|
174
|
+
<ActivityIndicator size="large" color={colors.foreground} />
|
|
175
|
+
</FlowPage>
|
|
176
|
+
)
|
|
414
177
|
}
|
|
415
178
|
|
|
416
|
-
if (
|
|
179
|
+
if (state.screen === "checking") {
|
|
417
180
|
return (
|
|
418
181
|
<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
|
|
419
182
|
<BodyText colors={colors}>{translate("ota:checkingForUpdatesMessage")}</BodyText>
|
|
@@ -422,41 +185,51 @@ function OtaCheckPage({
|
|
|
422
185
|
)
|
|
423
186
|
}
|
|
424
187
|
|
|
425
|
-
if (
|
|
188
|
+
if (state.screen === "update_available" || state.screen === "wifi_required") {
|
|
426
189
|
return (
|
|
427
190
|
<FlowPage
|
|
428
191
|
colors={colors}
|
|
429
192
|
icon="download"
|
|
430
|
-
title={translate(
|
|
193
|
+
title={translate(state.versionChange ? "ota:downgradeAvailable" : "ota:updateAvailable", {deviceName})}
|
|
431
194
|
actions={
|
|
432
195
|
<>
|
|
433
196
|
<FlowButton
|
|
434
197
|
colors={colors}
|
|
435
|
-
disabled={!
|
|
436
|
-
label={translate(
|
|
437
|
-
onPress={
|
|
198
|
+
disabled={!state.wifiStatusKnown}
|
|
199
|
+
label={translate(state.screen === "wifi_required" ? "ota:setupWifi" : "ota:updateNow")}
|
|
200
|
+
onPress={state.screen === "wifi_required" ? controller.openWifiSetup : controller.install}
|
|
438
201
|
/>
|
|
439
|
-
{
|
|
440
|
-
<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={
|
|
202
|
+
{state.canDismiss ? (
|
|
203
|
+
<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary />
|
|
441
204
|
) : null}
|
|
442
|
-
{allowDevSkip &&
|
|
443
|
-
<FlowButton colors={colors} label="Skip (dev only)" onPress={
|
|
205
|
+
{allowDevSkip && state.updateRequired ? (
|
|
206
|
+
<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
|
|
444
207
|
) : null}
|
|
445
208
|
</>
|
|
446
209
|
}>
|
|
210
|
+
{state.releaseTransition ? (
|
|
211
|
+
<BodyText colors={colors}>
|
|
212
|
+
{state.releaseTransition.fromVersion
|
|
213
|
+
? translate("ota:releaseTransition", {
|
|
214
|
+
fromVersion: state.releaseTransition.fromVersion,
|
|
215
|
+
toVersion: state.releaseTransition.toVersion,
|
|
216
|
+
})
|
|
217
|
+
: translate("ota:releaseTransitionUnknown", {toVersion: state.releaseTransition.toVersion})}
|
|
218
|
+
</BodyText>
|
|
219
|
+
) : null}
|
|
447
220
|
<BodyText colors={colors}>
|
|
448
|
-
{
|
|
449
|
-
? translate(
|
|
450
|
-
: translate("ota:
|
|
221
|
+
{state.screen === "wifi_required"
|
|
222
|
+
? translate("ota:updateConnectWifi", {deviceName})
|
|
223
|
+
: translate(state.versionChange ? "ota:downgradeDescription" : "ota:updateDescription")}
|
|
451
224
|
</BodyText>
|
|
452
225
|
</FlowPage>
|
|
453
226
|
)
|
|
454
227
|
}
|
|
455
228
|
|
|
456
|
-
if (
|
|
229
|
+
if (state.screen === "dev_build") {
|
|
457
230
|
return (
|
|
458
231
|
<FlowPage
|
|
459
|
-
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
232
|
+
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
|
|
460
233
|
colors={colors}
|
|
461
234
|
icon="settings"
|
|
462
235
|
title={translate("ota:devBuild")}>
|
|
@@ -465,22 +238,28 @@ function OtaCheckPage({
|
|
|
465
238
|
)
|
|
466
239
|
}
|
|
467
240
|
|
|
468
|
-
if (
|
|
241
|
+
if (state.screen === "up_to_date") {
|
|
469
242
|
return (
|
|
470
243
|
<FlowPage
|
|
471
|
-
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
244
|
+
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
|
|
472
245
|
colors={colors}
|
|
473
246
|
icon="check"
|
|
474
247
|
title={translate("ota:upToDate")}>
|
|
475
248
|
<BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
|
|
249
|
+
{state.releaseTransition ? (
|
|
250
|
+
<BodyText colors={colors}>
|
|
251
|
+
{translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
|
|
252
|
+
</BodyText>
|
|
253
|
+
) : null}
|
|
254
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} />
|
|
476
255
|
</FlowPage>
|
|
477
256
|
)
|
|
478
257
|
}
|
|
479
258
|
|
|
480
|
-
if (
|
|
259
|
+
if (state.screen === "update_info_unavailable") {
|
|
481
260
|
return (
|
|
482
261
|
<FlowPage
|
|
483
|
-
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
262
|
+
actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish} />}
|
|
484
263
|
colors={colors}
|
|
485
264
|
icon="alert"
|
|
486
265
|
title={translate("ota:updateInfoUnavailable")}>
|
|
@@ -489,104 +268,32 @@ function OtaCheckPage({
|
|
|
489
268
|
)
|
|
490
269
|
}
|
|
491
270
|
|
|
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()
|
|
271
|
+
if (state.screen === "check_failed") {
|
|
272
|
+
return (
|
|
273
|
+
<FlowPage
|
|
274
|
+
actions={
|
|
275
|
+
<>
|
|
276
|
+
<FlowButton colors={colors} label="Retry" onPress={controller.retryCheck} />
|
|
277
|
+
{allowDevSkip ? (
|
|
278
|
+
<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary />
|
|
279
|
+
) : null}
|
|
280
|
+
</>
|
|
281
|
+
}
|
|
282
|
+
colors={colors}
|
|
283
|
+
icon="alert"
|
|
284
|
+
title={translate("ota:checkFailed")}>
|
|
285
|
+
<BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
|
|
286
|
+
</FlowPage>
|
|
287
|
+
)
|
|
581
288
|
}
|
|
582
289
|
|
|
583
|
-
if (versionChangePhase === "restarting" || versionChangePhase === "verifying") {
|
|
290
|
+
if (state.versionChangePhase === "restarting" || state.versionChangePhase === "verifying") {
|
|
584
291
|
return (
|
|
585
292
|
<FlowPage
|
|
586
293
|
colors={colors}
|
|
587
294
|
icon="download"
|
|
588
295
|
title={translate(
|
|
589
|
-
versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting",
|
|
296
|
+
state.versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting",
|
|
590
297
|
)}>
|
|
591
298
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
592
299
|
<BodyText colors={colors}>{translate("ota:versionChangeKeepNearby")}</BodyText>
|
|
@@ -595,19 +302,19 @@ function OtaProgressPage({
|
|
|
595
302
|
)
|
|
596
303
|
}
|
|
597
304
|
|
|
598
|
-
if (
|
|
599
|
-
const
|
|
600
|
-
hotspotPhase === "downloading"
|
|
305
|
+
if (state.screen === "starting" || state.screen === "preparing_hotspot") {
|
|
306
|
+
const title =
|
|
307
|
+
state.hotspotPhase === "downloading"
|
|
601
308
|
? "Downloading update to phone..."
|
|
602
|
-
: hotspotPhase === "starting_hotspot"
|
|
309
|
+
: state.hotspotPhase === "starting_hotspot"
|
|
603
310
|
? "Starting glasses hotspot..."
|
|
604
|
-
: hotspotPhase === "joining_hotspot"
|
|
311
|
+
: state.hotspotPhase === "joining_hotspot"
|
|
605
312
|
? "Connecting phone to glasses..."
|
|
606
313
|
: "Starting update..."
|
|
607
314
|
return (
|
|
608
|
-
<FlowPage colors={colors} icon="download" title={
|
|
609
|
-
{hotspotPhase === "downloading" && hotspotArtifactPercent !== null ? (
|
|
610
|
-
<PercentText colors={colors} percent={hotspotArtifactPercent} />
|
|
315
|
+
<FlowPage colors={colors} icon="download" title={title}>
|
|
316
|
+
{state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
|
|
317
|
+
<PercentText colors={colors} percent={state.hotspotArtifactPercent} />
|
|
611
318
|
) : null}
|
|
612
319
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
613
320
|
<BodyText colors={colors}>Do not disconnect your glasses</BodyText>
|
|
@@ -615,41 +322,34 @@ function OtaProgressPage({
|
|
|
615
322
|
)
|
|
616
323
|
}
|
|
617
324
|
|
|
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)
|
|
325
|
+
if (state.screen === "updating") {
|
|
628
326
|
return (
|
|
629
|
-
<FlowPage colors={colors} icon="download" title={
|
|
630
|
-
{
|
|
327
|
+
<FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
|
|
328
|
+
{state.installingApkOnly ? (
|
|
631
329
|
<ActivityIndicator size="large" color={colors.foreground} />
|
|
632
330
|
) : (
|
|
633
331
|
<>
|
|
634
|
-
<PercentText colors={colors} percent={
|
|
332
|
+
<PercentText colors={colors} percent={state.progress ?? 0} />
|
|
635
333
|
<View style={[styles.progressTrack, {backgroundColor: colors.border}]}>
|
|
636
|
-
<View
|
|
334
|
+
<View
|
|
335
|
+
style={[styles.progressFill, {backgroundColor: colors.primary, width: `${state.progress ?? 0}%`}]}
|
|
336
|
+
/>
|
|
637
337
|
</View>
|
|
638
338
|
</>
|
|
639
339
|
)}
|
|
640
340
|
<BodyText colors={colors}>Do not disconnect your glasses</BodyText>
|
|
641
|
-
{
|
|
341
|
+
{state.versionChange && state.phase === "install" ? (
|
|
642
342
|
<BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
|
|
643
343
|
) : null}
|
|
644
344
|
</FlowPage>
|
|
645
345
|
)
|
|
646
346
|
}
|
|
647
347
|
|
|
648
|
-
if (
|
|
348
|
+
if (state.screen === "restarting") {
|
|
649
349
|
return (
|
|
650
350
|
<FlowPage
|
|
651
351
|
actions={
|
|
652
|
-
<FlowButton colors={colors} disabled={
|
|
352
|
+
<FlowButton colors={colors} disabled={state.continueDisabled} label="Continue" onPress={controller.finish} />
|
|
653
353
|
}
|
|
654
354
|
colors={colors}
|
|
655
355
|
icon="check"
|
|
@@ -658,15 +358,15 @@ function OtaProgressPage({
|
|
|
658
358
|
)
|
|
659
359
|
}
|
|
660
360
|
|
|
661
|
-
if (
|
|
662
|
-
const title = versionChangeConverged
|
|
361
|
+
if (state.screen === "complete") {
|
|
362
|
+
const title = state.versionChangeConverged
|
|
663
363
|
? translate("ota:versionChangeComplete")
|
|
664
|
-
:
|
|
364
|
+
: state.versionChange
|
|
665
365
|
? translate("ota:versionChangeFirmwarePassComplete")
|
|
666
366
|
: "Update complete!"
|
|
667
|
-
const message = versionChangeConverged
|
|
367
|
+
const message = state.versionChangeConverged
|
|
668
368
|
? translate("ota:versionChangeCompleteMessage")
|
|
669
|
-
:
|
|
369
|
+
: state.versionChange
|
|
670
370
|
? translate("ota:versionChangeFirmwarePassCompleteMessage")
|
|
671
371
|
: "Your glasses are up to date."
|
|
672
372
|
return (
|
|
@@ -674,50 +374,43 @@ function OtaProgressPage({
|
|
|
674
374
|
actions={
|
|
675
375
|
<FlowButton
|
|
676
376
|
colors={colors}
|
|
677
|
-
label={
|
|
678
|
-
onPress={
|
|
377
|
+
label={state.versionChange && !state.versionChangeConverged ? "Continue" : "Done"}
|
|
378
|
+
onPress={controller.finish}
|
|
679
379
|
/>
|
|
680
380
|
}
|
|
681
381
|
colors={colors}
|
|
682
382
|
icon="check"
|
|
683
383
|
title={title}>
|
|
684
384
|
<BodyText colors={colors}>{message}</BodyText>
|
|
385
|
+
{state.releaseTransition ? (
|
|
386
|
+
<BodyText colors={colors}>
|
|
387
|
+
{translate("ota:updatedToVersion", {version: state.releaseTransition.toVersion})}
|
|
388
|
+
</BodyText>
|
|
389
|
+
) : null}
|
|
390
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} />
|
|
685
391
|
</FlowPage>
|
|
686
392
|
)
|
|
687
393
|
}
|
|
688
394
|
|
|
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)
|
|
395
|
+
if (state.screen === "failed") {
|
|
695
396
|
return (
|
|
696
397
|
<FlowPage
|
|
697
398
|
actions={
|
|
698
399
|
<>
|
|
699
400
|
<FlowButton
|
|
700
401
|
colors={colors}
|
|
701
|
-
label={
|
|
702
|
-
onPress={
|
|
402
|
+
label={state.canRetry ? "Retry" : "Done"}
|
|
403
|
+
onPress={state.canRetry ? controller.retryInstall : controller.finish}
|
|
703
404
|
/>
|
|
704
|
-
{
|
|
705
|
-
<FlowButton
|
|
706
|
-
colors={colors}
|
|
707
|
-
label="Change WiFi"
|
|
708
|
-
onPress={() => {
|
|
709
|
-
stopOtaAutoChain()
|
|
710
|
-
onOpenWifiSetup()
|
|
711
|
-
}}
|
|
712
|
-
secondary
|
|
713
|
-
/>
|
|
405
|
+
{state.canOpenWifiSetup ? (
|
|
406
|
+
<FlowButton colors={colors} label="Change WiFi" onPress={controller.openWifiSetup} secondary />
|
|
714
407
|
) : null}
|
|
715
408
|
</>
|
|
716
409
|
}
|
|
717
410
|
colors={colors}
|
|
718
411
|
icon="alert"
|
|
719
412
|
title="Update Failed">
|
|
720
|
-
<BodyText colors={colors}>{
|
|
413
|
+
<BodyText colors={colors}>{state.error?.message}</BodyText>
|
|
721
414
|
</FlowPage>
|
|
722
415
|
)
|
|
723
416
|
}
|
|
@@ -726,16 +419,7 @@ function OtaProgressPage({
|
|
|
726
419
|
<FlowPage
|
|
727
420
|
actions={
|
|
728
421
|
superMode ? (
|
|
729
|
-
<FlowButton
|
|
730
|
-
colors={colors}
|
|
731
|
-
label="Skip (super)"
|
|
732
|
-
onPress={() => {
|
|
733
|
-
stopOtaAutoChain()
|
|
734
|
-
ota.installSession.discard()
|
|
735
|
-
onReturnToCheck()
|
|
736
|
-
}}
|
|
737
|
-
secondary
|
|
738
|
-
/>
|
|
422
|
+
<FlowButton colors={colors} label="Skip (super)" onPress={controller.discard} secondary />
|
|
739
423
|
) : undefined
|
|
740
424
|
}
|
|
741
425
|
colors={colors}
|
|
@@ -776,6 +460,30 @@ function PercentText({colors, percent}: {colors: MentraLiveOtaFlowTheme; percent
|
|
|
776
460
|
return <Text style={[styles.percent, {color: colors.primary}]}>{Math.round(percent)}%</Text>
|
|
777
461
|
}
|
|
778
462
|
|
|
463
|
+
function ChangelogList({
|
|
464
|
+
changelogs,
|
|
465
|
+
colors,
|
|
466
|
+
}: {
|
|
467
|
+
changelogs: MentraLiveOtaController["state"]["changelogs"]
|
|
468
|
+
colors: MentraLiveOtaFlowTheme
|
|
469
|
+
}) {
|
|
470
|
+
if (changelogs.length === 0) return null
|
|
471
|
+
return (
|
|
472
|
+
<ScrollView contentContainerStyle={styles.changelogContent} style={styles.changelogList}>
|
|
473
|
+
{changelogs.map((entry) => (
|
|
474
|
+
<View key={entry.version} style={styles.changelogEntry}>
|
|
475
|
+
<Text selectable style={[styles.changelogVersion, {color: colors.foreground}]}>
|
|
476
|
+
{entry.version}
|
|
477
|
+
</Text>
|
|
478
|
+
<Text selectable style={[styles.changelogMarkdown, {color: colors.textDim}]}>
|
|
479
|
+
{entry.markdown}
|
|
480
|
+
</Text>
|
|
481
|
+
</View>
|
|
482
|
+
))}
|
|
483
|
+
</ScrollView>
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
|
|
779
487
|
function FlowButton({
|
|
780
488
|
colors,
|
|
781
489
|
disabled = false,
|
|
@@ -797,13 +505,13 @@ function FlowButton({
|
|
|
797
505
|
style={({pressed}) => [
|
|
798
506
|
styles.button,
|
|
799
507
|
{
|
|
800
|
-
backgroundColor: secondary ? colors.background : colors.
|
|
801
|
-
borderColor: secondary ? colors.border : colors.
|
|
508
|
+
backgroundColor: secondary ? colors.background : colors.foreground,
|
|
509
|
+
borderColor: secondary ? colors.border : colors.foreground,
|
|
802
510
|
opacity: disabled ? 0.45 : pressed ? 0.75 : 1,
|
|
803
511
|
},
|
|
804
512
|
]}
|
|
805
513
|
testID={`button-${label}`}>
|
|
806
|
-
<Text style={[styles.buttonText, {color: secondary ? colors.foreground : colors.
|
|
514
|
+
<Text style={[styles.buttonText, {color: secondary ? colors.foreground : colors.background}]}>{label}</Text>
|
|
807
515
|
</Pressable>
|
|
808
516
|
)
|
|
809
517
|
}
|
|
@@ -844,13 +552,18 @@ const styles = StyleSheet.create({
|
|
|
844
552
|
percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
|
|
845
553
|
progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
|
|
846
554
|
progressFill: {borderRadius: 4, height: 8},
|
|
555
|
+
changelogList: {flexShrink: 1, maxHeight: 260, maxWidth: 420, width: "100%"},
|
|
556
|
+
changelogContent: {gap: 18, paddingVertical: 4},
|
|
557
|
+
changelogEntry: {gap: 8},
|
|
558
|
+
changelogVersion: {fontSize: 16, fontWeight: "600"},
|
|
559
|
+
changelogMarkdown: {fontSize: 14, lineHeight: 20},
|
|
847
560
|
button: {
|
|
848
561
|
alignItems: "center",
|
|
849
|
-
borderRadius:
|
|
562
|
+
borderRadius: 50,
|
|
850
563
|
borderWidth: 1,
|
|
851
564
|
justifyContent: "center",
|
|
852
|
-
minHeight:
|
|
565
|
+
minHeight: 44,
|
|
853
566
|
paddingHorizontal: 20,
|
|
854
567
|
},
|
|
855
|
-
buttonText: {fontSize:
|
|
568
|
+
buttonText: {fontSize: 14, fontWeight: "500"},
|
|
856
569
|
})
|