diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-25 23:51:52 +0100 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-25 23:51:52 +0100 |
| commit | b8addc87f804789368612bf8e1be31a2bf28efe1 (patch) | |
| tree | bece4051eb6f009eba05ffdd585420232741a7d7 /source/ui/MainMenu.mc | |
| parent | 140a6660f63f88d05ba9aae0bf1c4b401bb6574d (diff) | |
test(ui): second UI scenario, and the fixes it found
A second UI-test build (uitest-extra.jungle) covers the legacy PIN and
the forced new PIN, Change and Reset PIN, a queued Require PIN, UPC-A,
Code 39, the code error screen, the encoding progress, auto-lock on a
barcode, the light theme and the glance without a code; the main flow
adds the Steam and Library details and the menu's text screens.
Fixed from what it found: Entry IDs tripped the watchdog with 110
entries; auto-lock didn't close a locked barcode; a queued Require PIN
showed "Confirm in menu"; locked barcodes lacked the padlock; the PIN
keypad's bottom row, the Reset PIN question, text-screen bottoms, the
glance title and the light-theme warning colour.
Goldens approved on all seven watches; only the Forerunner 70 was
re-verified after the version bump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/ui/MainMenu.mc')
| -rw-r--r-- | source/ui/MainMenu.mc | 113 |
1 files changed, 94 insertions, 19 deletions
diff --git a/source/ui/MainMenu.mc b/source/ui/MainMenu.mc index 8863c6d..db59f4a 100644 --- a/source/ui/MainMenu.mc +++ b/source/ui/MainMenu.mc @@ -18,7 +18,10 @@ module MainMenu { var menu = new WatchUi.Menu2({:title => "OTP Wallet"}); var waiting = ChangeQueue.count(vault); if (waiting > 0) { - menu.addItem(new WatchUi.MenuItem("Phone changes", waiting + " to confirm", :queue, null)); + // Only "Require PIN" waiting: it applies at the next PIN anyway. + var toConfirm = ChangeQueue.countToConfirm(vault); + menu.addItem(new WatchUi.MenuItem("Phone changes", toConfirm > 0 ? toConfirm + " to confirm" : "Apply at next PIN", + :queue, null)); } if (vault.session.isUnlocked()) { menu.addItem(new WatchUi.MenuItem("Lock now", null, :lock, null)); @@ -32,28 +35,29 @@ module MainMenu { menu.addItem(new WatchUi.MenuItem("Clock status", null, :clock, null)); menu.addItem(new WatchUi.MenuItem("About", null, :about, null)); DebugMenu.addItems(menu); + logItems(menu); return menu; } - function entryIdLines() as Array<String> { - var vault = AppState.theVault(); - var ids = vault.ids(); - var lines = [] as Array<String>; - for (var i = 0; i < ids.size(); i++) { - var header = vault.header(ids[i]); - var name = header == null ? "damaged" : (header.name.length() > 0 ? header.name : "Locked entry"); - lines.add(ids[i] + " " + name); - } - if (lines.size() == 0) { - lines.add("No entries yet."); - } - var pending = vault.pendingIds().size(); - if (pending > 0) { - lines.add(pending + " locked entries wait for your PIN."); + // Debug builds: the item labels in order, so UI scripts can find an + // item on touch-only watches, whose menus have no focus to move. + (:debug) + function logItems(menu as WatchUi.Menu2) as Void { + var labels = ""; + for (var i = 0; menu.getItem(i) != null; i++) { + labels += (i > 0 ? "|" : "") + (menu.getItem(i) as WatchUi.MenuItem).getLabel(); } - return lines; + Diag.data("menu", "items=" + labels); + } + + (:release) + function logItems(menu as WatchUi.Menu2) as Void { } + // The Entry IDs screen: "Reading…" while EntryIdsJob reads the + // headers on a timer, then the list (kept so the runner isn't collected). + var idsScreen as EntryIdsScreen? = null; + function clockLines() as Array<String> { var lines = [] as Array<String>; var watch = Clock.nowUnix(0); @@ -118,7 +122,9 @@ class MainMenuDelegate extends WatchUi.Menu2InputDelegate { WatchUi.switchToView(new PinView(PinView.MODE_UNLOCK, then.method(:run)), new PinDelegate(), WatchUi.SLIDE_LEFT); } } else if (id == :reset) { - WatchUi.pushView(new WatchUi.Confirmation("Erase locked entries and set a new PIN?"), new ResetPinConfirmation(), + // Two short lines (the item said "Reset PIN"): the native dialog shows + // two lines and cuts longer text mid-word. + WatchUi.pushView(new WatchUi.Confirmation("Erase locked\nentries?"), new ResetPinConfirmation(), WatchUi.SLIDE_LEFT); } else if (id == :results) { WatchUi.switchToView(new InfoView("Results", Results.lines(), "results"), new InfoDelegate(), @@ -127,7 +133,9 @@ class MainMenuDelegate extends WatchUi.Menu2InputDelegate { } else if (id == :queue) { WatchUi.switchToView(QueueMenu.build(), new QueueMenuDelegate(), WatchUi.SLIDE_LEFT); } else if (id == :ids) { - WatchUi.switchToView(new InfoView("Entry IDs", MainMenu.entryIdLines(), "ids"), new InfoDelegate(), WatchUi.SLIDE_LEFT); + var screen = new EntryIdsScreen(); + MainMenu.idsScreen = screen; + screen.start(); } else if (id == :clock) { WatchUi.switchToView(new InfoView("Clock status", MainMenu.clockLines(), "clock"), new InfoDelegate(), WatchUi.SLIDE_LEFT); } else if (id == :about) { @@ -138,6 +146,73 @@ class MainMenuDelegate extends WatchUi.Menu2InputDelegate { } } +// Reads one header per entry, a few per timer step: reading 110 in one +// event tripped the watchdog. Ends with the lines of the Entry IDs screen. +class EntryIdsJob extends Jobs.Job { + + static const HEADERS_PER_STEP = 10; + + var lines as Array<String> = [] as Array<String>; + private var _ids as Array<String>; + private var _at as Number = 0; + + function initialize() { + Job.initialize(); + _ids = AppState.theVault().ids(); + } + + function step(budget as Number) as Boolean { + var vault = AppState.theVault(); + var stop = _at + HEADERS_PER_STEP * budget; + stop = stop < _ids.size() ? stop : _ids.size(); + for (; _at < stop; _at++) { + var id = _ids[_at]; + var header = vault.header(id); + var name = header == null ? "damaged" : (header.name.length() > 0 ? header.name : "Locked entry"); + lines.add(id + " " + name); + } + if (_at < _ids.size()) { + return false; + } + if (lines.size() == 0) { + lines.add("No entries yet."); + } + var pending = vault.pendingIds().size(); + if (pending > 0) { + lines.add(pending + " locked entries wait for your PIN."); + } + return true; + } +} + +// Shows "Reading…" (replacing the menu), runs EntryIdsJob, then replaces +// that screen with the IDs, unless the user left it meanwhile. +class EntryIdsScreen { + + private var _job as EntryIdsJob = new EntryIdsJob(); + // The timer driver (kept so it isn't collected). + var runner as Jobs.JobRunner? = null; + private var _reading as InfoView = new InfoView("Entry IDs", ["Reading…"] as Array<String>, "ids_reading"); + + function initialize() { + } + + function start() as Void { + WatchUi.switchToView(_reading, new InfoDelegate(), WatchUi.SLIDE_LEFT); + var r = new Jobs.JobRunner(_job, 1, method(:done)); + runner = r; + r.start(); + } + + function done() as Void { + runner = null; + MainMenu.idsScreen = null; + if (WatchUi.getCurrentView()[0] == _reading) { + WatchUi.switchToView(new InfoView("Entry IDs", _job.lines, "ids"), new InfoDelegate(), WatchUi.SLIDE_IMMEDIATE); + } + } +} + // After unlocking from the menu's "Change PIN": ask for the new PIN. class ChangePinAfterUnlock { |
