@mentra/engine 3.1.0-dev.9 → 3.1.0-dev.94
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 +11 -6
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +9 -5
- 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 +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +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 +256 -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 +627 -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 +26 -3
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +35 -13
- 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/otaInstallPolicy.d.ts +4 -0
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +4 -0
- package/build/services/otaInstallPolicy.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 +19 -6
- package/src/facades/pairing.ts +88 -12
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +2 -0
- package/src/react/MentraLiveOtaFlow.tsx +378 -480
- package/src/react/index.ts +14 -0
- package/src/react/useMentraLiveOta.ts +793 -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 +37 -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/otaInstallPolicy.ts +4 -0
- 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,13 +1,10 @@
|
|
|
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 { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View, } from "react-native";
|
|
4
|
+
import { useMarkdown } from "react-native-marked";
|
|
3
5
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
4
6
|
import Svg, { Path, Rect } from "react-native-svg";
|
|
5
|
-
import {
|
|
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;
|
|
7
|
+
import { MINIMUM_OTA_BATTERY_LEVEL, useMentraLiveOta, } from "./useMentraLiveOta";
|
|
11
8
|
const DEFAULT_THEME = {
|
|
12
9
|
background: "#FFFFFF",
|
|
13
10
|
border: "#D7DFDA",
|
|
@@ -19,16 +16,29 @@ const DEFAULT_THEME = {
|
|
|
19
16
|
};
|
|
20
17
|
const ENGLISH_COPY = {
|
|
21
18
|
"common:continue": "Continue",
|
|
19
|
+
"common:done": "Done",
|
|
22
20
|
"ota:checkingForUpdates": "Checking for updates",
|
|
23
21
|
"ota:checkingForUpdatesMessage": "Connected devices will perform automatic updates. Automatic updates can be disabled in Device Settings",
|
|
22
|
+
"ota:finishingUpdate": "Finishing your update",
|
|
23
|
+
"ota:checkingAdditionalUpdates": "Checking whether your glasses need any additional updates.",
|
|
24
24
|
"ota:updateAvailable": "{{deviceName}} Update Available",
|
|
25
|
+
"ota:batteryRequiredTitle": "Charge {{deviceName}} to Update",
|
|
26
|
+
"ota:batteryRequiredMessage": "{{deviceName}} is currently at {{batteryLevel}}%. Charge it to at least {{minimumBatteryLevel}}% before updating.",
|
|
27
|
+
"ota:batteryRequiredLiveUpdate": "This screen will update automatically as the battery charges.",
|
|
25
28
|
"ota:updateConnectWifi": "Connect your {{deviceName}} to WiFi to install the update.",
|
|
29
|
+
"ota:wifiRequiredTitle": "WiFi Needed for Update",
|
|
26
30
|
"ota:updateDescription": "A new update is available for your glasses. We recommend updating now for the best experience.",
|
|
31
|
+
"ota:updateSequenceMessage": "Your glasses may install more than one update and restart several times. Keep them nearby until finished.",
|
|
32
|
+
"ota:releaseTransition": "{{fromVersion}} → {{toVersion}}",
|
|
33
|
+
"ota:releaseTransitionUnknown": "Current version unknown → {{toVersion}}",
|
|
34
|
+
"ota:updatedToVersion": "Updated to {{version}}",
|
|
27
35
|
"ota:downgradeAvailable": "{{deviceName}} Version Change Required",
|
|
28
36
|
"ota:downgradeDescription": "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.",
|
|
29
37
|
"ota:updateNow": "Update Now",
|
|
30
38
|
"ota:setupWifi": "Setup WiFi",
|
|
31
39
|
"ota:updateLater": "Later",
|
|
40
|
+
"ota:updateComplete": "Update complete",
|
|
41
|
+
"ota:whatsNew": "What's new",
|
|
32
42
|
"ota:upToDate": "Up To Date",
|
|
33
43
|
"ota:devBuild": "Development Build",
|
|
34
44
|
"ota:devBuildNoOta": "This mobile app is a development build, so automatic glasses updates are disabled. Use the developer settings manifest override to update them manually.",
|
|
@@ -41,6 +51,9 @@ const ENGLISH_COPY = {
|
|
|
41
51
|
"ota:versionChangeRestarting": "Installing a different version…",
|
|
42
52
|
"ota:versionChangeVerifying": "Verifying your glasses…",
|
|
43
53
|
"ota:versionChangeKeepNearby": "Keep your glasses nearby and connected. They will restart on their own.",
|
|
54
|
+
"ota:restartingGlasses": "Restarting {{deviceName}}…",
|
|
55
|
+
"ota:restartingGlassesMessage": "The update is installed. Keep your glasses nearby and leave this screen open while they finish starting.",
|
|
56
|
+
"ota:restartingGlassesAutomatic": "We'll continue automatically when they're ready.",
|
|
44
57
|
"ota:versionChangeComplete": "Version Change Complete",
|
|
45
58
|
"ota:versionChangeCompleteMessage": "Your glasses are now on the required version. Their settings were reset and are being restored automatically.",
|
|
46
59
|
"ota:versionChangeFirmwarePassComplete": "Firmware updated",
|
|
@@ -55,410 +68,184 @@ function defaultTranslate(key, options) {
|
|
|
55
68
|
}
|
|
56
69
|
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
70
|
const colors = useMemo(() => ({ ...DEFAULT_THEME, ...theme }), [theme]);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
}, []);
|
|
71
|
+
const controller = useMentraLiveOta({
|
|
72
|
+
initialPage,
|
|
73
|
+
initializeRuntime,
|
|
74
|
+
onFinished,
|
|
75
|
+
onFirmwareRestartingChange,
|
|
76
|
+
onOpenWifiSetup,
|
|
77
|
+
});
|
|
80
78
|
return (<SafeAreaView style={[styles.safeArea, { backgroundColor: colors.background }, style]}>
|
|
81
79
|
<View style={styles.header}>
|
|
82
80
|
<View />
|
|
83
81
|
<MentraMark color={colors.primary}/>
|
|
84
82
|
</View>
|
|
85
|
-
{
|
|
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}/>)}
|
|
83
|
+
<OtaFlowContent allowDevSkip={allowDevSkip} colors={colors} controller={controller} deviceName={deviceName} superMode={superMode} translate={translate}/>
|
|
88
84
|
</SafeAreaView>);
|
|
89
85
|
}
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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") {
|
|
86
|
+
function OtaFlowContent({ allowDevSkip, colors, controller, deviceName, superMode, translate, }) {
|
|
87
|
+
const { state } = controller;
|
|
88
|
+
if (state.screen === "initializing") {
|
|
89
|
+
return (<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
|
|
90
|
+
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
91
|
+
</FlowPage>);
|
|
92
|
+
}
|
|
93
|
+
if (state.screen === "checking") {
|
|
295
94
|
return (<FlowPage colors={colors} icon="download" title={translate("ota:checkingForUpdates")}>
|
|
296
95
|
<BodyText colors={colors}>{translate("ota:checkingForUpdatesMessage")}</BodyText>
|
|
297
96
|
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
298
97
|
</FlowPage>);
|
|
299
98
|
}
|
|
300
|
-
if (
|
|
301
|
-
return (<FlowPage
|
|
302
|
-
<FlowButton colors={colors} disabled
|
|
303
|
-
{
|
|
304
|
-
|
|
99
|
+
if (state.screen === "battery_required") {
|
|
100
|
+
return (<FlowPage actions={<>
|
|
101
|
+
<FlowButton colors={colors} disabled label={translate("ota:updateNow")} onPress={controller.install}/>
|
|
102
|
+
{state.canDismiss ? (<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary/>) : null}
|
|
103
|
+
</>} colors={colors} icon="alert" title={translate("ota:batteryRequiredTitle", { deviceName })}>
|
|
104
|
+
<BodyText colors={colors}>
|
|
105
|
+
{translate("ota:batteryRequiredMessage", {
|
|
106
|
+
batteryLevel: String(state.batteryLevel),
|
|
107
|
+
deviceName,
|
|
108
|
+
minimumBatteryLevel: String(MINIMUM_OTA_BATTERY_LEVEL),
|
|
109
|
+
})}
|
|
110
|
+
</BodyText>
|
|
111
|
+
<BodyText colors={colors}>{translate("ota:batteryRequiredLiveUpdate")}</BodyText>
|
|
112
|
+
</FlowPage>);
|
|
113
|
+
}
|
|
114
|
+
if (state.screen === "finishing") {
|
|
115
|
+
return (<FlowPage colors={colors} icon="download" title={translate("ota:finishingUpdate")}>
|
|
116
|
+
<BodyText colors={colors}>{translate("ota:checkingAdditionalUpdates")}</BodyText>
|
|
117
|
+
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
118
|
+
</FlowPage>);
|
|
119
|
+
}
|
|
120
|
+
if (state.screen === "update_available" || state.screen === "wifi_required") {
|
|
121
|
+
const titleKey = state.screen === "wifi_required"
|
|
122
|
+
? "ota:wifiRequiredTitle"
|
|
123
|
+
: state.versionChange
|
|
124
|
+
? "ota:downgradeAvailable"
|
|
125
|
+
: "ota:updateAvailable";
|
|
126
|
+
return (<FlowPage colors={colors} icon="download" title={translate(titleKey, { deviceName })} actions={<>
|
|
127
|
+
<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}/>
|
|
128
|
+
{state.canDismiss ? (<FlowButton colors={colors} label={translate("ota:updateLater")} onPress={controller.finish} secondary/>) : null}
|
|
129
|
+
{allowDevSkip && state.updateRequired ? (<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary/>) : null}
|
|
305
130
|
</>}>
|
|
131
|
+
{state.releaseTransition ? (<BodyText colors={colors}>
|
|
132
|
+
{state.releaseTransition.fromVersion
|
|
133
|
+
? translate("ota:releaseTransition", {
|
|
134
|
+
fromVersion: state.releaseTransition.fromVersion,
|
|
135
|
+
toVersion: state.releaseTransition.toVersion,
|
|
136
|
+
})
|
|
137
|
+
: translate("ota:releaseTransitionUnknown", { toVersion: state.releaseTransition.toVersion })}
|
|
138
|
+
</BodyText>) : null}
|
|
306
139
|
<BodyText colors={colors}>
|
|
307
|
-
{
|
|
308
|
-
? translate(
|
|
309
|
-
: translate("ota:
|
|
140
|
+
{state.screen === "wifi_required"
|
|
141
|
+
? translate("ota:updateConnectWifi", { deviceName })
|
|
142
|
+
: translate(state.versionChange ? "ota:downgradeDescription" : "ota:updateDescription")}
|
|
310
143
|
</BodyText>
|
|
144
|
+
<BodyText colors={colors}>{translate("ota:updateSequenceMessage")}</BodyText>
|
|
311
145
|
</FlowPage>);
|
|
312
146
|
}
|
|
313
|
-
if (
|
|
314
|
-
return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
147
|
+
if (state.screen === "dev_build") {
|
|
148
|
+
return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish}/>} colors={colors} icon="settings" title={translate("ota:devBuild")}>
|
|
315
149
|
<BodyText colors={colors}>{translate("ota:devBuildNoOta")}</BodyText>
|
|
316
150
|
</FlowPage>);
|
|
317
151
|
}
|
|
318
|
-
if (
|
|
319
|
-
return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
152
|
+
if (state.screen === "up_to_date") {
|
|
153
|
+
return (<FlowPage actions={<FlowButton colors={colors} label={translate(state.completedUpdate ? "common:done" : "common:continue")} onPress={controller.finish}/>} colors={colors} contentAlignment={state.changelogs.length > 0 ? "top" : "center"} icon="check" title={translate(state.completedUpdate ? "ota:updateComplete" : "ota:upToDate")}>
|
|
320
154
|
<BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
|
|
155
|
+
{state.releaseTransition ? (<BodyText colors={colors}>
|
|
156
|
+
{translate("ota:updatedToVersion", { version: state.releaseTransition.toVersion })}
|
|
157
|
+
</BodyText>) : null}
|
|
158
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} title={translate("ota:whatsNew")}/>
|
|
321
159
|
</FlowPage>);
|
|
322
160
|
}
|
|
323
|
-
if (
|
|
324
|
-
return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={
|
|
161
|
+
if (state.screen === "update_info_unavailable") {
|
|
162
|
+
return (<FlowPage actions={<FlowButton colors={colors} label={translate("common:continue")} onPress={controller.finish}/>} colors={colors} icon="alert" title={translate("ota:updateInfoUnavailable")}>
|
|
325
163
|
<BodyText colors={colors}>{translate("ota:updateInfoUnavailableMessage")}</BodyText>
|
|
326
164
|
</FlowPage>);
|
|
327
165
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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")}>
|
|
166
|
+
if (state.screen === "check_failed") {
|
|
167
|
+
return (<FlowPage actions={<>
|
|
168
|
+
<FlowButton colors={colors} label="Retry" onPress={controller.retryCheck}/>
|
|
169
|
+
{allowDevSkip ? (<FlowButton colors={colors} label="Skip (dev only)" onPress={controller.finish} secondary/>) : null}
|
|
170
|
+
</>} colors={colors} icon="alert" title={translate("ota:checkFailed")}>
|
|
171
|
+
<BodyText colors={colors}>{translate("ota:checkFailedMessage")}</BodyText>
|
|
172
|
+
</FlowPage>);
|
|
173
|
+
}
|
|
174
|
+
if (state.versionChangePhase === "restarting" || state.versionChangePhase === "verifying") {
|
|
175
|
+
return (<FlowPage colors={colors} icon="download" title={translate(state.versionChangePhase === "verifying" ? "ota:versionChangeVerifying" : "ota:versionChangeRestarting")}>
|
|
376
176
|
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
377
177
|
<BodyText colors={colors}>{translate("ota:versionChangeKeepNearby")}</BodyText>
|
|
378
178
|
<BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>
|
|
379
179
|
</FlowPage>);
|
|
380
180
|
}
|
|
381
|
-
if (
|
|
382
|
-
const
|
|
181
|
+
if (state.screen === "starting" || state.screen === "preparing_hotspot") {
|
|
182
|
+
const title = state.hotspotPhase === "downloading"
|
|
383
183
|
? "Downloading update to phone..."
|
|
384
|
-
: hotspotPhase === "starting_hotspot"
|
|
184
|
+
: state.hotspotPhase === "starting_hotspot"
|
|
385
185
|
? "Starting glasses hotspot..."
|
|
386
|
-
: hotspotPhase === "joining_hotspot"
|
|
186
|
+
: state.hotspotPhase === "joining_hotspot"
|
|
387
187
|
? "Connecting phone to glasses..."
|
|
388
188
|
: "Starting update...";
|
|
389
|
-
return (<FlowPage colors={colors} icon="download" title={
|
|
390
|
-
{hotspotPhase === "downloading" && hotspotArtifactPercent !== null ? (<PercentText colors={colors} percent={hotspotArtifactPercent}/>) : null}
|
|
189
|
+
return (<FlowPage colors={colors} icon="download" title={title}>
|
|
190
|
+
{state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (<PercentText colors={colors} percent={state.hotspotArtifactPercent}/>) : null}
|
|
391
191
|
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
392
192
|
<BodyText colors={colors}>Do not disconnect your glasses</BodyText>
|
|
393
193
|
</FlowPage>);
|
|
394
194
|
}
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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}/>
|
|
195
|
+
if (state.screen === "updating") {
|
|
196
|
+
return (<FlowPage colors={colors} icon="download" title={state.phase === "download" ? "Downloading..." : "Installing..."}>
|
|
197
|
+
{state.installingApkOnly ? (<ActivityIndicator size="large" color={colors.foreground}/>) : (<>
|
|
198
|
+
<PercentText colors={colors} percent={state.progress ?? 0}/>
|
|
408
199
|
<View style={[styles.progressTrack, { backgroundColor: colors.border }]}>
|
|
409
|
-
<View style={[styles.progressFill, { backgroundColor: colors.primary, width: `${
|
|
200
|
+
<View style={[styles.progressFill, { backgroundColor: colors.primary, width: `${state.progress ?? 0}%` }]}/>
|
|
410
201
|
</View>
|
|
411
202
|
</>)}
|
|
412
203
|
<BodyText colors={colors}>Do not disconnect your glasses</BodyText>
|
|
413
|
-
{
|
|
204
|
+
{state.versionChange && state.phase === "install" ? (<BodyText colors={colors}>{translate("ota:downgradeDuration")}</BodyText>) : null}
|
|
414
205
|
</FlowPage>);
|
|
415
206
|
}
|
|
416
|
-
if (
|
|
417
|
-
return (<FlowPage
|
|
207
|
+
if (state.screen === "restarting") {
|
|
208
|
+
return (<FlowPage colors={colors} icon="download" title={translate("ota:restartingGlasses", { deviceName })}>
|
|
209
|
+
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
210
|
+
<BodyText colors={colors}>{translate("ota:restartingGlassesMessage")}</BodyText>
|
|
211
|
+
<BodyText colors={colors}>{translate("ota:restartingGlassesAutomatic")}</BodyText>
|
|
212
|
+
</FlowPage>);
|
|
418
213
|
}
|
|
419
|
-
if (
|
|
420
|
-
const title = versionChangeConverged
|
|
214
|
+
if (state.screen === "complete") {
|
|
215
|
+
const title = state.versionChangeConverged
|
|
421
216
|
? translate("ota:versionChangeComplete")
|
|
422
|
-
:
|
|
217
|
+
: state.versionChange
|
|
423
218
|
? translate("ota:versionChangeFirmwarePassComplete")
|
|
424
219
|
: "Update complete!";
|
|
425
|
-
const message = versionChangeConverged
|
|
220
|
+
const message = state.versionChangeConverged
|
|
426
221
|
? translate("ota:versionChangeCompleteMessage")
|
|
427
|
-
:
|
|
222
|
+
: state.versionChange
|
|
428
223
|
? translate("ota:versionChangeFirmwarePassCompleteMessage")
|
|
429
224
|
: "Your glasses are up to date.";
|
|
430
|
-
return (<FlowPage actions={<FlowButton colors={colors} label={
|
|
225
|
+
return (<FlowPage actions={<FlowButton colors={colors} label={state.versionChange && !state.versionChangeConverged ? "Continue" : "Done"} onPress={controller.finish}/>} colors={colors} contentAlignment={state.changelogs.length > 0 ? "top" : "center"} icon="check" title={title}>
|
|
431
226
|
<BodyText colors={colors}>{message}</BodyText>
|
|
227
|
+
{state.releaseTransition ? (<BodyText colors={colors}>
|
|
228
|
+
{translate("ota:updatedToVersion", { version: state.releaseTransition.toVersion })}
|
|
229
|
+
</BodyText>) : null}
|
|
230
|
+
<ChangelogList changelogs={state.changelogs} colors={colors} title={translate("ota:whatsNew")}/>
|
|
432
231
|
</FlowPage>);
|
|
433
232
|
}
|
|
434
|
-
if (
|
|
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);
|
|
233
|
+
if (state.screen === "failed") {
|
|
440
234
|
return (<FlowPage actions={<>
|
|
441
|
-
<FlowButton colors={colors} label={
|
|
442
|
-
{
|
|
443
|
-
stopOtaAutoChain();
|
|
444
|
-
onOpenWifiSetup();
|
|
445
|
-
}} secondary/>) : null}
|
|
235
|
+
<FlowButton colors={colors} label={state.canRetry ? "Retry" : "Done"} onPress={state.canRetry ? controller.retryInstall : controller.finish}/>
|
|
236
|
+
{state.canOpenWifiSetup ? (<FlowButton colors={colors} label="Change WiFi" onPress={controller.openWifiSetup} secondary/>) : null}
|
|
446
237
|
</>} colors={colors} icon="alert" title="Update Failed">
|
|
447
|
-
<BodyText colors={colors}>{
|
|
238
|
+
<BodyText colors={colors}>{state.error?.message}</BodyText>
|
|
448
239
|
</FlowPage>);
|
|
449
240
|
}
|
|
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">
|
|
241
|
+
return (<FlowPage actions={superMode ? (<FlowButton colors={colors} label="Skip (super)" onPress={controller.discard} secondary/>) : undefined} colors={colors} icon="bluetooth" title="Glasses disconnected">
|
|
455
242
|
<BodyText colors={colors}>Reconnecting...</BodyText>
|
|
456
243
|
<ActivityIndicator size="large" color={colors.foreground}/>
|
|
457
244
|
</FlowPage>);
|
|
458
245
|
}
|
|
459
|
-
function FlowPage({ actions, children, colors, icon, title }) {
|
|
246
|
+
function FlowPage({ actions, children, colors, contentAlignment = "center", icon, title }) {
|
|
460
247
|
return (<View style={styles.page} testID="mentra-live-ota-flow">
|
|
461
|
-
<View style={styles.centerContent}>
|
|
248
|
+
<View style={[styles.centerContent, contentAlignment === "top" && styles.topContent]}>
|
|
462
249
|
<FlowIcon colors={colors} name={icon}/>
|
|
463
250
|
<Text style={[styles.title, { color: colors.foreground }]}>{title}</Text>
|
|
464
251
|
{children}
|
|
@@ -472,16 +259,113 @@ function BodyText({ children, colors }) {
|
|
|
472
259
|
function PercentText({ colors, percent }) {
|
|
473
260
|
return <Text style={[styles.percent, { color: colors.primary }]}>{Math.round(percent)}%</Text>;
|
|
474
261
|
}
|
|
262
|
+
function ChangelogMarkdown({ colors, markdown }) {
|
|
263
|
+
const markdownStyles = useMemo(() => ({
|
|
264
|
+
blockquote: {
|
|
265
|
+
borderLeftColor: colors.primary,
|
|
266
|
+
borderLeftWidth: 3,
|
|
267
|
+
marginVertical: 2,
|
|
268
|
+
opacity: 1,
|
|
269
|
+
paddingLeft: 12,
|
|
270
|
+
},
|
|
271
|
+
code: {
|
|
272
|
+
backgroundColor: colors.background,
|
|
273
|
+
borderColor: colors.border,
|
|
274
|
+
borderRadius: 8,
|
|
275
|
+
borderWidth: 1,
|
|
276
|
+
padding: 12,
|
|
277
|
+
},
|
|
278
|
+
codespan: {
|
|
279
|
+
backgroundColor: colors.border,
|
|
280
|
+
color: colors.foreground,
|
|
281
|
+
fontFamily: "monospace",
|
|
282
|
+
fontSize: 13,
|
|
283
|
+
fontStyle: "normal",
|
|
284
|
+
fontWeight: "400",
|
|
285
|
+
},
|
|
286
|
+
em: { color: colors.textDim, fontSize: 14, lineHeight: 20 },
|
|
287
|
+
h1: {
|
|
288
|
+
borderBottomWidth: 0,
|
|
289
|
+
color: colors.foreground,
|
|
290
|
+
fontSize: 18,
|
|
291
|
+
fontWeight: "700",
|
|
292
|
+
lineHeight: 24,
|
|
293
|
+
marginVertical: 0,
|
|
294
|
+
paddingBottom: 0,
|
|
295
|
+
},
|
|
296
|
+
h2: {
|
|
297
|
+
borderBottomWidth: 0,
|
|
298
|
+
color: colors.foreground,
|
|
299
|
+
fontSize: 17,
|
|
300
|
+
fontWeight: "700",
|
|
301
|
+
lineHeight: 23,
|
|
302
|
+
marginVertical: 0,
|
|
303
|
+
paddingBottom: 0,
|
|
304
|
+
},
|
|
305
|
+
h3: { color: colors.foreground, fontSize: 16, fontWeight: "700", lineHeight: 22, marginVertical: 0 },
|
|
306
|
+
h4: { color: colors.foreground, fontSize: 15, fontWeight: "700", lineHeight: 21, marginVertical: 0 },
|
|
307
|
+
h5: { color: colors.foreground, fontSize: 14, fontWeight: "700", lineHeight: 20, marginVertical: 0 },
|
|
308
|
+
h6: { color: colors.textDim, fontSize: 14, fontWeight: "600", lineHeight: 20, marginVertical: 0 },
|
|
309
|
+
hr: { borderBottomColor: colors.border, marginVertical: 2 },
|
|
310
|
+
image: { borderRadius: 8 },
|
|
311
|
+
li: { color: colors.textDim, fontSize: 14, lineHeight: 20 },
|
|
312
|
+
link: {
|
|
313
|
+
color: colors.primary,
|
|
314
|
+
fontSize: 14,
|
|
315
|
+
fontStyle: "normal",
|
|
316
|
+
lineHeight: 20,
|
|
317
|
+
textDecorationLine: "underline",
|
|
318
|
+
},
|
|
319
|
+
paragraph: { paddingVertical: 0 },
|
|
320
|
+
strikethrough: { color: colors.textDim, fontSize: 14, lineHeight: 20 },
|
|
321
|
+
strong: { color: colors.foreground, fontSize: 14, fontWeight: "700", lineHeight: 20 },
|
|
322
|
+
table: { borderColor: colors.border, borderRadius: 8 },
|
|
323
|
+
tableCell: { padding: 8 },
|
|
324
|
+
text: { color: colors.textDim, fontSize: 14, lineHeight: 20 },
|
|
325
|
+
}), [colors]);
|
|
326
|
+
const markdownTheme = useMemo(() => ({
|
|
327
|
+
colors: {
|
|
328
|
+
background: colors.background,
|
|
329
|
+
border: colors.border,
|
|
330
|
+
code: colors.border,
|
|
331
|
+
link: colors.primary,
|
|
332
|
+
text: colors.textDim,
|
|
333
|
+
},
|
|
334
|
+
}), [colors]);
|
|
335
|
+
const elements = useMarkdown(markdown, { colorScheme: "light", styles: markdownStyles, theme: markdownTheme });
|
|
336
|
+
return (<View style={styles.changelogMarkdownContent} testID="ota-changelog-markdown">
|
|
337
|
+
{elements}
|
|
338
|
+
</View>);
|
|
339
|
+
}
|
|
340
|
+
export function ChangelogList({ changelogs, colors, title, }) {
|
|
341
|
+
if (changelogs.length === 0)
|
|
342
|
+
return null;
|
|
343
|
+
return (<View style={[styles.changelogCard, { borderColor: colors.border }]} testID="ota-changelog-card">
|
|
344
|
+
<Text style={[styles.changelogTitle, { color: colors.foreground }]}>{title}</Text>
|
|
345
|
+
<ScrollView contentContainerStyle={styles.changelogContent} nestedScrollEnabled showsVerticalScrollIndicator style={styles.changelogList} testID="ota-changelog-scroll">
|
|
346
|
+
{changelogs.map((entry, index) => (<View key={entry.version} style={[
|
|
347
|
+
styles.changelogEntry,
|
|
348
|
+
index > 0 && styles.changelogEntryDivider,
|
|
349
|
+
index > 0 && { borderTopColor: colors.border },
|
|
350
|
+
]}>
|
|
351
|
+
<Text selectable style={[styles.changelogVersion, { color: colors.foreground }]}>
|
|
352
|
+
{entry.version}
|
|
353
|
+
</Text>
|
|
354
|
+
<ChangelogMarkdown colors={colors} markdown={entry.markdown}/>
|
|
355
|
+
</View>))}
|
|
356
|
+
</ScrollView>
|
|
357
|
+
</View>);
|
|
358
|
+
}
|
|
475
359
|
function FlowButton({ colors, disabled = false, label, onPress, secondary = false, }) {
|
|
476
360
|
return (<Pressable accessibilityRole="button" disabled={disabled} onPress={onPress} style={({ pressed }) => [
|
|
477
361
|
styles.button,
|
|
478
362
|
{
|
|
479
|
-
backgroundColor: secondary ? colors.background : colors.
|
|
480
|
-
borderColor: secondary ? colors.border : colors.
|
|
363
|
+
backgroundColor: secondary ? colors.background : colors.foreground,
|
|
364
|
+
borderColor: secondary ? colors.border : colors.foreground,
|
|
481
365
|
opacity: disabled ? 0.45 : pressed ? 0.75 : 1,
|
|
482
366
|
},
|
|
483
367
|
]} testID={`button-${label}`}>
|
|
484
|
-
<Text style={[styles.buttonText, { color: secondary ? colors.foreground : colors.
|
|
368
|
+
<Text style={[styles.buttonText, { color: secondary ? colors.foreground : colors.background }]}>{label}</Text>
|
|
485
369
|
</Pressable>);
|
|
486
370
|
}
|
|
487
371
|
function MentraMark({ color }) {
|
|
@@ -507,6 +391,7 @@ const styles = StyleSheet.create({
|
|
|
507
391
|
},
|
|
508
392
|
page: { flex: 1, paddingBottom: 24, paddingHorizontal: 24 },
|
|
509
393
|
centerContent: { alignItems: "center", flex: 1, gap: 16, justifyContent: "center" },
|
|
394
|
+
topContent: { justifyContent: "flex-start", paddingTop: 12 },
|
|
510
395
|
actionSpacer: { height: 48 },
|
|
511
396
|
actions: { gap: 12 },
|
|
512
397
|
icon: { fontSize: 64, fontWeight: "500", lineHeight: 72, textAlign: "center" },
|
|
@@ -515,14 +400,31 @@ const styles = StyleSheet.create({
|
|
|
515
400
|
percent: { fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700" },
|
|
516
401
|
progressTrack: { borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%" },
|
|
517
402
|
progressFill: { borderRadius: 4, height: 8 },
|
|
403
|
+
changelogCard: {
|
|
404
|
+
borderRadius: 16,
|
|
405
|
+
borderWidth: 1,
|
|
406
|
+
flexShrink: 1,
|
|
407
|
+
gap: 12,
|
|
408
|
+
maxHeight: 300,
|
|
409
|
+
maxWidth: 420,
|
|
410
|
+
padding: 16,
|
|
411
|
+
width: "100%",
|
|
412
|
+
},
|
|
413
|
+
changelogTitle: { fontSize: 16, fontWeight: "700" },
|
|
414
|
+
changelogList: { flexShrink: 1, maxHeight: 232, width: "100%" },
|
|
415
|
+
changelogContent: { gap: 20, paddingBottom: 2 },
|
|
416
|
+
changelogEntry: { gap: 8 },
|
|
417
|
+
changelogEntryDivider: { borderTopWidth: StyleSheet.hairlineWidth, paddingTop: 20 },
|
|
418
|
+
changelogVersion: { fontSize: 14, fontWeight: "600" },
|
|
419
|
+
changelogMarkdownContent: { gap: 10 },
|
|
518
420
|
button: {
|
|
519
421
|
alignItems: "center",
|
|
520
|
-
borderRadius:
|
|
422
|
+
borderRadius: 50,
|
|
521
423
|
borderWidth: 1,
|
|
522
424
|
justifyContent: "center",
|
|
523
|
-
minHeight:
|
|
425
|
+
minHeight: 44,
|
|
524
426
|
paddingHorizontal: 20,
|
|
525
427
|
},
|
|
526
|
-
buttonText: { fontSize:
|
|
428
|
+
buttonText: { fontSize: 14, fontWeight: "500" },
|
|
527
429
|
});
|
|
528
430
|
//# sourceMappingURL=MentraLiveOtaFlow.js.map
|