@mentra/engine 3.1.0-dev.31 → 3.1.0-dev.33

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.
@@ -1,6 +1,15 @@
1
1
  /* eslint-disable react-native/no-raw-text -- BodyText and PercentText are local Text wrappers. */
2
2
  import React, {useMemo} from "react"
3
- import {ActivityIndicator, Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle} from "react-native"
3
+ import {
4
+ ActivityIndicator,
5
+ Pressable,
6
+ ScrollView,
7
+ StyleSheet,
8
+ Text,
9
+ View,
10
+ type StyleProp,
11
+ type ViewStyle,
12
+ } from "react-native"
4
13
  import {SafeAreaView} from "react-native-safe-area-context"
5
14
  import Svg, {Path, Rect} from "react-native-svg"
6
15
 
@@ -224,6 +233,7 @@ function OtaFlowContent({
224
233
  icon="check"
225
234
  title={translate("ota:upToDate")}>
226
235
  <BodyText colors={colors}>{translate("ota:noUpdatesAvailable")}</BodyText>
236
+ <ChangelogList changelogs={state.changelogs} colors={colors} />
227
237
  </FlowPage>
228
238
  )
229
239
  }
@@ -279,10 +289,10 @@ function OtaFlowContent({
279
289
  state.hotspotPhase === "downloading"
280
290
  ? "Downloading update to phone..."
281
291
  : state.hotspotPhase === "starting_hotspot"
282
- ? "Starting glasses hotspot..."
283
- : state.hotspotPhase === "joining_hotspot"
284
- ? "Connecting phone to glasses..."
285
- : "Starting update..."
292
+ ? "Starting glasses hotspot..."
293
+ : state.hotspotPhase === "joining_hotspot"
294
+ ? "Connecting phone to glasses..."
295
+ : "Starting update..."
286
296
  return (
287
297
  <FlowPage colors={colors} icon="download" title={title}>
288
298
  {state.hotspotPhase === "downloading" && state.hotspotArtifactPercent !== null ? (
@@ -334,13 +344,13 @@ function OtaFlowContent({
334
344
  const title = state.versionChangeConverged
335
345
  ? translate("ota:versionChangeComplete")
336
346
  : state.versionChange
337
- ? translate("ota:versionChangeFirmwarePassComplete")
338
- : "Update complete!"
347
+ ? translate("ota:versionChangeFirmwarePassComplete")
348
+ : "Update complete!"
339
349
  const message = state.versionChangeConverged
340
350
  ? translate("ota:versionChangeCompleteMessage")
341
351
  : state.versionChange
342
- ? translate("ota:versionChangeFirmwarePassCompleteMessage")
343
- : "Your glasses are up to date."
352
+ ? translate("ota:versionChangeFirmwarePassCompleteMessage")
353
+ : "Your glasses are up to date."
344
354
  return (
345
355
  <FlowPage
346
356
  actions={
@@ -354,6 +364,7 @@ function OtaFlowContent({
354
364
  icon="check"
355
365
  title={title}>
356
366
  <BodyText colors={colors}>{message}</BodyText>
367
+ <ChangelogList changelogs={state.changelogs} colors={colors} />
357
368
  </FlowPage>
358
369
  )
359
370
  }
@@ -426,6 +437,30 @@ function PercentText({colors, percent}: {colors: MentraLiveOtaFlowTheme; percent
426
437
  return <Text style={[styles.percent, {color: colors.primary}]}>{Math.round(percent)}%</Text>
427
438
  }
428
439
 
440
+ function ChangelogList({
441
+ changelogs,
442
+ colors,
443
+ }: {
444
+ changelogs: MentraLiveOtaController["state"]["changelogs"]
445
+ colors: MentraLiveOtaFlowTheme
446
+ }) {
447
+ if (changelogs.length === 0) return null
448
+ return (
449
+ <ScrollView contentContainerStyle={styles.changelogContent} style={styles.changelogList}>
450
+ {changelogs.map((entry) => (
451
+ <View key={entry.version} style={styles.changelogEntry}>
452
+ <Text selectable style={[styles.changelogVersion, {color: colors.foreground}]}>
453
+ {entry.version}
454
+ </Text>
455
+ <Text selectable style={[styles.changelogMarkdown, {color: colors.textDim}]}>
456
+ {entry.markdown}
457
+ </Text>
458
+ </View>
459
+ ))}
460
+ </ScrollView>
461
+ )
462
+ }
463
+
429
464
  function FlowButton({
430
465
  colors,
431
466
  disabled = false,
@@ -494,6 +529,11 @@ const styles = StyleSheet.create({
494
529
  percent: {fontSize: 30, fontVariant: ["tabular-nums"], fontWeight: "700"},
495
530
  progressTrack: {borderRadius: 4, height: 8, maxWidth: 420, overflow: "hidden", width: "100%"},
496
531
  progressFill: {borderRadius: 4, height: 8},
532
+ changelogList: {flexShrink: 1, maxHeight: 260, maxWidth: 420, width: "100%"},
533
+ changelogContent: {gap: 18, paddingVertical: 4},
534
+ changelogEntry: {gap: 8},
535
+ changelogVersion: {fontSize: 16, fontWeight: "600"},
536
+ changelogMarkdown: {fontSize: 14, lineHeight: 20},
497
537
  button: {
498
538
  alignItems: "center",
499
539
  borderRadius: 50,
@@ -6,6 +6,7 @@ import {
6
6
  clearOtaAutoChainReconnectWait,
7
7
  isOtaAutoChainActive,
8
8
  otaAutoChainFingerprint,
9
+ otaAutoChainReleaseRange,
9
10
  otaAutoChainReconnectWaitRemaining,
10
11
  stopOtaAutoChain,
11
12
  tryAdvanceOtaAutoChain,
@@ -18,6 +19,7 @@ import {
18
19
  } from "../services/OtaErrorMapping"
19
20
  import type {OtaInstallSnapshot} from "../services/OtaInstallCoordinator"
20
21
  import type {OtaCheckCurrentGlassesResult} from "../services/OtaUpdateCheckService"
22
+ import type {ReleaseChangelog} from "../facades/ota"
21
23
  import {useEngineSnapshot} from "./useEngineSnapshot"
22
24
 
23
25
  export type MentraLiveOtaFlowPage = "check" | "progress"
@@ -84,6 +86,8 @@ export type MentraLiveOtaState = {
84
86
  canDiscard: boolean
85
87
  canOpenWifiSetup: boolean
86
88
  continueDisabled: boolean
89
+ /** Release notes crossed by this update, newest first. Populated on completion. */
90
+ changelogs: ReleaseChangelog[]
87
91
  }
88
92
 
89
93
  export type UseMentraLiveOtaOptions = {
@@ -115,16 +119,34 @@ type CheckState = "checking" | "update_available" | "no_update" | "dev_build" |
115
119
  const AUTO_CHAIN_NETWORK_RETRY_DELAY_MS = 5000
116
120
  const AUTO_CHAIN_COMPLETE_DELAY_MS = 750
117
121
 
122
+ function targetVersion(result: OtaCheckCurrentGlassesResult): string | null {
123
+ return result.updateInfo?.versionName ?? result.latestVersionInfo?.versionName ?? null
124
+ }
125
+
126
+ function releaseChangelogsForActiveChain(): ReleaseChangelog[] {
127
+ const range = otaAutoChainReleaseRange()
128
+ if (!range?.toVersion) return []
129
+ try {
130
+ return ota.getReleaseChangelogs(range.fromVersion, range.toVersion)
131
+ } catch {
132
+ try {
133
+ return ota.getReleaseChangelogs(null, range.toVersion)
134
+ } catch {
135
+ return []
136
+ }
137
+ }
138
+ }
139
+
118
140
  function installProgress(snapshot: OtaInstallSnapshot): number | null {
119
141
  if (snapshot.displayState !== "updating") return null
120
142
  const {otaStatus} = snapshot
121
143
  const isDownload = otaStatus?.phase === "download"
122
144
  const totalSteps = otaStatus?.totalSteps ?? 1
123
145
  const rawPercent = isDownload
124
- ? (otaStatus?.stepPercent ?? 0)
146
+ ? otaStatus?.stepPercent ?? 0
125
147
  : totalSteps >= 2
126
- ? (otaStatus?.overallPercent ?? 0)
127
- : (otaStatus?.stepPercent ?? 0)
148
+ ? otaStatus?.overallPercent ?? 0
149
+ : otaStatus?.stepPercent ?? 0
128
150
  return Math.min(Math.max(rawPercent, snapshot.mtkInstallStallSimulatedPercent ?? 0, 0), 100)
129
151
  }
130
152
 
@@ -169,6 +191,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
169
191
  const [isVersionChange, setIsVersionChange] = useState(false)
170
192
  const [errorKind, setErrorKind] = useState<"network" | "pin_unavailable">("network")
171
193
  const [updateFingerprint, setUpdateFingerprint] = useState<string | null>(null)
194
+ const [completedChangelogs, setCompletedChangelogs] = useState<ReleaseChangelog[]>([])
172
195
  const [checkGeneration, setCheckGeneration] = useState(0)
173
196
  const performCheckGenerationRef = useRef(0)
174
197
  const activeCheckKeyRef = useRef<string | null>(null)
@@ -319,7 +342,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
319
342
  if (!snapshot.wifiConnected && snapshot.hotspotOtaVersion !== 1) {
320
343
  stopOtaAutoChain()
321
344
  } else {
322
- const admission = tryAdvanceOtaAutoChain(fingerprint, result.updateInfo.isDowngrade === true)
345
+ const admission = tryAdvanceOtaAutoChain(
346
+ fingerprint,
347
+ result.updateInfo.isDowngrade === true,
348
+ targetVersion(result),
349
+ )
323
350
  if (admission.advance) {
324
351
  checkCompletedRef.current = true
325
352
  ota.installSession.prepare(result)
@@ -332,6 +359,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
332
359
  setCheckState("update_available")
333
360
  return
334
361
  }
362
+ if (isOtaAutoChainActive()) setCompletedChangelogs(releaseChangelogsForActiveChain())
335
363
  stopOtaAutoChain()
336
364
  checkCompletedRef.current = true
337
365
  ota.clearUpdateAvailable()
@@ -397,6 +425,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
397
425
  }, [installSnapshot.connected, installSnapshot.displayState, page, returnToCheck])
398
426
 
399
427
  const check = useCallback(() => {
428
+ setCompletedChangelogs([])
400
429
  setPage("check")
401
430
  setCheckState("checking")
402
431
  setCheckGeneration((generation) => generation + 1)
@@ -420,7 +449,13 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
420
449
  return
421
450
  }
422
451
  ota.installSession.prepare(result)
423
- if (updateFingerprint) beginOtaAutoChain(updateFingerprint, isVersionChange)
452
+ setCompletedChangelogs([])
453
+ if (updateFingerprint) {
454
+ beginOtaAutoChain(updateFingerprint, isVersionChange, {
455
+ fromVersion: snapshot.appVersion,
456
+ toVersion: targetVersion(result),
457
+ })
458
+ }
424
459
  navigateToProgress()
425
460
  }, [check, isVersionChange, navigateToProgress, page, updateFingerprint])
426
461
 
@@ -490,6 +525,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
490
525
  canDiscard: false,
491
526
  canOpenWifiSetup: false,
492
527
  continueDisabled: false,
528
+ changelogs: [],
493
529
  }
494
530
  }
495
531
 
@@ -499,22 +535,22 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
499
535
  checkState === "checking"
500
536
  ? "checking"
501
537
  : checkState === "update_available"
502
- ? wifiRequired
503
- ? "wifi_required"
504
- : "update_available"
505
- : checkState === "no_update"
506
- ? "up_to_date"
507
- : checkState === "dev_build"
508
- ? "dev_build"
509
- : errorKind === "pin_unavailable"
510
- ? "update_info_unavailable"
511
- : "check_failed"
538
+ ? wifiRequired
539
+ ? "wifi_required"
540
+ : "update_available"
541
+ : checkState === "no_update"
542
+ ? "up_to_date"
543
+ : checkState === "dev_build"
544
+ ? "dev_build"
545
+ : errorKind === "pin_unavailable"
546
+ ? "update_info_unavailable"
547
+ : "check_failed"
512
548
  const error: MentraLiveOtaError | null =
513
549
  screen === "check_failed"
514
550
  ? {code: "check_failed", message: "Couldn't check for updates. Please check your connection and try again."}
515
551
  : screen === "update_info_unavailable"
516
- ? {code: "update_info_unavailable", message: "Update information for this app version is unavailable."}
517
- : null
552
+ ? {code: "update_info_unavailable", message: "Update information for this app version is unavailable."}
553
+ : null
518
554
  return {
519
555
  screen,
520
556
  connected: otaSnapshot.connected,
@@ -543,6 +579,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
543
579
  canDiscard: false,
544
580
  canOpenWifiSetup: screen === "wifi_required",
545
581
  continueDisabled: false,
582
+ changelogs: screen === "up_to_date" ? completedChangelogs : [],
546
583
  }
547
584
  }
548
585
 
@@ -568,6 +605,7 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
568
605
  }
569
606
  : null
570
607
  const totalSteps = installSnapshot.otaStatus?.totalSteps ?? null
608
+ const changelogs = screen === "complete" ? releaseChangelogsForActiveChain() : []
571
609
  return {
572
610
  screen,
573
611
  connected: installSnapshot.connected,
@@ -599,9 +637,11 @@ export function useMentraLiveOta(options: UseMentraLiveOtaOptions = {}): MentraL
599
637
  canDiscard: screen === "disconnected",
600
638
  canOpenWifiSetup: screen === "failed" && showChangeWifi,
601
639
  continueDisabled: installSnapshot.continueButtonDisabled,
640
+ changelogs,
602
641
  }
603
642
  }, [
604
643
  checkState,
644
+ completedChangelogs,
605
645
  errorKind,
606
646
  firmwareRestarting,
607
647
  installSnapshot,
@@ -6,10 +6,16 @@ export const OTA_AUTO_CHAIN_RECONNECT_TIMEOUT_MS = 120_000
6
6
  type OtaAutoChainSession = {
7
7
  approvedDowngrade: boolean
8
8
  passCount: number
9
+ releaseRange: OtaAutoChainReleaseRange
9
10
  reconnectDeadline: number | null
10
11
  seenFingerprints: Set<string>
11
12
  }
12
13
 
14
+ export type OtaAutoChainReleaseRange = {
15
+ fromVersion: string | null
16
+ toVersion: string | null
17
+ }
18
+
13
19
  export type OtaAutoChainAdvanceResult =
14
20
  | {advance: true; passCount: number}
15
21
  | {advance: false; reason: "inactive" | "duplicate" | "max_passes" | "downgrade_not_approved"}
@@ -42,10 +48,15 @@ export function otaAutoChainFingerprint(result: OtaCheckCurrentGlassesResult): s
42
48
  }
43
49
 
44
50
  /** Start a chain after the user explicitly approves its first update pass. */
45
- export function beginOtaAutoChain(initialFingerprint: string, approvedDowngrade: boolean): void {
51
+ export function beginOtaAutoChain(
52
+ initialFingerprint: string,
53
+ approvedDowngrade: boolean,
54
+ releaseRange: OtaAutoChainReleaseRange,
55
+ ): void {
46
56
  session = {
47
57
  approvedDowngrade,
48
58
  passCount: 1,
59
+ releaseRange: {...releaseRange},
49
60
  reconnectDeadline: null,
50
61
  seenFingerprints: new Set([initialFingerprint]),
51
62
  }
@@ -55,6 +66,10 @@ export function isOtaAutoChainActive(): boolean {
55
66
  return session !== null
56
67
  }
57
68
 
69
+ export function otaAutoChainReleaseRange(): OtaAutoChainReleaseRange | null {
70
+ return session ? {...session.releaseRange} : null
71
+ }
72
+
58
73
  /** End the current chain. Safe to call when no chain is active. */
59
74
  export function stopOtaAutoChain(): void {
60
75
  session = null
@@ -80,7 +95,11 @@ export function clearOtaAutoChainReconnectWait(): void {
80
95
  * Admit the next pass of an active chain. Repeated offers, unexpectedly
81
96
  * destructive downgrades, and runaway chains fail closed and end automation.
82
97
  */
83
- export function tryAdvanceOtaAutoChain(fingerprint: string, isDowngrade: boolean): OtaAutoChainAdvanceResult {
98
+ export function tryAdvanceOtaAutoChain(
99
+ fingerprint: string,
100
+ isDowngrade: boolean,
101
+ targetVersion: string | null,
102
+ ): OtaAutoChainAdvanceResult {
84
103
  if (!session) {
85
104
  return {advance: false, reason: "inactive"}
86
105
  }
@@ -102,5 +121,6 @@ export function tryAdvanceOtaAutoChain(fingerprint: string, isDowngrade: boolean
102
121
 
103
122
  session.seenFingerprints.add(fingerprint)
104
123
  session.passCount += 1
124
+ if (targetVersion) session.releaseRange.toVersion = targetVersion
105
125
  return {advance: true, passCount: session.passCount}
106
126
  }