1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
// 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<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));
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<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 {
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<String>;
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<String>, "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<String>;
// 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(), _keys);
_job = job;
WatchUi.switchToView(new InfoView("Results", ["Applying the changes…"] as Array<String>, "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
}
}
|