// Author: cirakg & claude ai // Summary: "Phone changes": lists the changes to locked entries waiting for the PIN, and confirms or rejects them (README 7.9). // // Confirming always asks for the PIN, even while the watch is unlocked: // 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, 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 { // 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) 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)); for (var i = 0; i < items.size(); i++) { menu.addItem(new WatchUi.MenuItem(items[i], null, i, null)); } return menu; } } // 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, "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; private var _keys as Array; function initialize(items as Array, keys as Array) { Menu2InputDelegate.initialize(); _items = items; _keys = keys; } function onSelect(item as WatchUi.MenuItem) as Void { AppState.activity(); var id = item.getId(); var vault = AppState.theVault(); if (id == :confirm) { 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; 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). if (id < _items.size()) { WatchUi.pushView(new InfoView("Change", [_items[id]] as Array, "change"), new InfoDelegate(), WatchUi.SLIDE_LEFT); } } } } // After the PIN: apply the waiting changes, then show what happened. 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; // keys: the items the menu showed (DescribeJob.keys). function initialize(keys as Array) { _keys = keys; } function run() as Void { var job = new ChangeQueueJob(AppState.theVault(), _keys); _job = job; WatchUi.switchToView(new InfoView("Results", ["Applying the changes…"] as Array, "applying"), new InfoDelegate(), WatchUi.SLIDE_LEFT); var r = new Jobs.JobRunner(job, 1, method(:onDone)); runner = r; r.start(); } function onDone() as Void { var job = _job as ChangeQueueJob; _job = null; runner = null; AppState.theCodes().clear(); Results.save(job.lines); WatchUi.switchToView(new InfoView("Results", job.lines, "results"), new InfoDelegate(), WatchUi.SLIDE_IMMEDIATE); Results.markSeen(); AppState.processSettings(); // refresh the catalog on the phone } }