aboutsummaryrefslogtreecommitdiff
path: root/source/ui/QueueMenu.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-27 01:01:18 +0100
committercirakg <cirakg@cirakg.xyz>2026-10-27 01:01:18 +0100
commitbc4cc19df5f53519ca1aabd3cf161b6d78ffc16c (patch)
tree3242a63f603882d723bd0d718d043c30a9f11784 /source/ui/QueueMenu.mc
parent34daecaa25e5bdd995179a2b4b9d922f8bbb3b54 (diff)
fix(pin): the work after a PIN and the change queue run in steps
After a correct or new PIN, promoting pending entries and applying waiting "Require PIN" changes ran in the event of the last PBKDF2 step; 16 waiting changes tripped the watchdog on the Forerunner 70. ChangeQueue.LockJob now does it in steps while the PIN screen shows "Checking...", saves what it did to the results and refreshes the glance after Set PIN too. The list title decrypted every waiting change at each start. Queue items are now [action, box] (aad OTPW-Q-2 + action code), so they are counted without decrypting; 0.9.0 items are still read. A replacement is checked when it arrives and its material is sealed apart (qm.<n>). Large buffers are wiped and UTF-8-checked in pieces (Jobs.Wiper, Bytes.utf8Prefix, SealedBox.openKeep/sealKeep). "Phone changes" reads behind "Reading...", and Confirm all / Reject all act only on the changes shown. Storage full no longer stops the app at the PIN. Found by the review of b8addc8/096b5a4 and a new make watchdog test; the diff was reviewed again and its findings fixed. fr70: make lint, make test (176), make watchdog (19 cases at two steps per event), make screenshots pass; list_locked and list_reset goldens show "Results ready (menu)". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/ui/QueueMenu.mc')
-rw-r--r--source/ui/QueueMenu.mc78
1 files changed, 65 insertions, 13 deletions
diff --git a/source/ui/QueueMenu.mc b/source/ui/QueueMenu.mc
index 3f9161d..0ec9daf 100644
--- a/source/ui/QueueMenu.mc
+++ b/source/ui/QueueMenu.mc
@@ -5,20 +5,32 @@
// the point is that a person at the watch agrees, not that a key is
// available. (Before any PIN exists only "Require PIN" can wait here, and
// confirming creates the PIN. "Require PIN" items are applied by the PIN
-// screen itself, ChangeQueue.applyLocks, and listed in the results.) The changes are then applied by a
-// ChangeQueueJob on a timer and the results screen shows what happened.
+// screen itself, LockJob, and listed in the results.) The changes are then
+// applied by a ChangeQueueJob on a timer and the results screen shows what
+// happened. The menu is built from DescribeJob's lines, read behind a short
+// "Reading…" screen (opening 16 items in one event trips the watchdog).
import Toybox.Lang;
import Toybox.WatchUi;
module QueueMenu {
- function build() as WatchUi.Menu2 {
+ // The "Reading…" screen while it runs (kept so the runner isn't collected).
+ var screen as QueueMenuScreen? = null;
+
+ // Open the menu (replacing the current view).
+ function open() as Void {
+ var s = new QueueMenuScreen();
+ screen = s;
+ s.start();
+ }
+
+ // The menu for these descriptions (DescribeJob.lines).
+ function build(items as Array<String>) as WatchUi.Menu2 {
var vault = AppState.theVault();
var menu = new WatchUi.Menu2({:title => "Phone changes"});
menu.addItem(new WatchUi.MenuItem("Confirm all", vault.hasPin() ? "Asks for your PIN" : "Sets a PIN first", :confirm, null));
menu.addItem(new WatchUi.MenuItem("Reject all", null, :reject, null));
- var items = ChangeQueue.descriptions(vault);
for (var i = 0; i < items.size(); i++) {
menu.addItem(new WatchUi.MenuItem(items[i], null, i, null));
}
@@ -26,10 +38,44 @@ module QueueMenu {
}
}
-class QueueMenuDelegate extends WatchUi.Menu2InputDelegate {
+// Shows "Reading…", runs DescribeJob, then replaces that screen with the
+// menu, unless the user left it meanwhile.
+class QueueMenuScreen {
+
+ var runner as Jobs.JobRunner? = null;
+ private var _job as DescribeJob = new DescribeJob(AppState.theVault());
+ private var _reading as InfoView = new InfoView("Phone changes", ["Reading…"] as Array<String>, "queue_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;
+ QueueMenu.screen = null;
+ if (WatchUi.getCurrentView()[0] == _reading) {
+ WatchUi.switchToView(QueueMenu.build(_job.lines), new QueueMenuDelegate(_job.lines, _job.keys), WatchUi.SLIDE_IMMEDIATE);
+ }
+ }
+}
+
+class QueueMenuDelegate extends WatchUi.Menu2InputDelegate {
+
+ // The descriptions the menu shows, and the keys of the items read
+ // (DescribeJob): confirming or rejecting acts on exactly those.
+ private var _items as Array<String>;
+ private var _keys as Array<String>;
+
+ function initialize(items as Array<String>, keys as Array<String>) {
Menu2InputDelegate.initialize();
+ _items = items;
+ _keys = keys;
}
function onSelect(item as WatchUi.MenuItem) as Void {
@@ -37,22 +83,25 @@ class QueueMenuDelegate extends WatchUi.Menu2InputDelegate {
var id = item.getId();
var vault = AppState.theVault();
if (id == :confirm) {
- var then = new ApplyQueueAfterPin();
+ var then = new ApplyQueueAfterPin(_keys);
var mode = vault.hasPin() ? PinView.MODE_UNLOCK : PinView.MODE_CREATE;
WatchUi.switchToView(new PinView(mode, then.method(:run)), new PinDelegate(), WatchUi.SLIDE_LEFT);
} else if (id == :reject) {
var lines = ["Rejected these changes from the phone:"] as Array<String>;
- lines.addAll(ChangeQueue.descriptions(vault));
- ChangeQueue.clear(vault);
+ lines.addAll(_items);
+ var late = ChangeQueue.remove(vault, _keys);
+ if (late > 0) {
+ lines.add(late + (late == 1 ? " change" : " changes") + " arrived meanwhile and still "
+ + (late == 1 ? "waits" : "wait") + " (menu, Phone changes)");
+ }
Results.save(lines);
WatchUi.switchToView(new InfoView("Results", lines, "results"), new InfoDelegate(), WatchUi.SLIDE_LEFT);
Results.markSeen();
AppState.processSettings(); // the About fields mention waiting changes
} else if (id instanceof Number) {
// The full text of one change (menu labels are cut short).
- var items = ChangeQueue.descriptions(vault);
- if (id < items.size()) {
- WatchUi.pushView(new InfoView("Change", [items[id]] as Array<String>, "change"), new InfoDelegate(),
+ if (id < _items.size()) {
+ WatchUi.pushView(new InfoView("Change", [_items[id]] as Array<String>, "change"), new InfoDelegate(),
WatchUi.SLIDE_LEFT);
}
}
@@ -65,12 +114,15 @@ class ApplyQueueAfterPin {
// The job's timer driver (kept so it isn't collected while it runs).
var runner as Jobs.JobRunner? = null;
private var _job as ChangeQueueJob? = null;
+ private var _keys as Array<String>;
- function initialize() {
+ // keys: the items the menu showed (DescribeJob.keys).
+ function initialize(keys as Array<String>) {
+ _keys = keys;
}
function run() as Void {
- var job = new ChangeQueueJob(AppState.theVault());
+ var job = new ChangeQueueJob(AppState.theVault(), _keys);
_job = job;
WatchUi.switchToView(new InfoView("Results", ["Applying the changes…"] as Array<String>, "applying"), new InfoDelegate(),
WatchUi.SLIDE_LEFT);