cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
ui_admin.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 * Copyright (c) 2026 Cryptnox SA
4 */
5
11
12#include "ui_internal.h"
13
14/******************************************************************
15 * 7b. Settings — full-screen page (swipe up from the bottom edge)
16 ******************************************************************/
17/* Brightness only: the one setting this page changes. The fee caps are written
18 * by the config page. */
22
23static void brightness_event_cb(lv_event_t *e) {
24 lv_obj_t *sl = lv_event_get_target(e);
25 lv_obj_t *lbl = static_cast<lv_obj_t *>(lv_event_get_user_data(e));
26 s_brightness = static_cast<uint8_t>(lv_slider_get_value(sl));
28 if (lbl != NULL) {
29 char b[8];
30 snprintf(b, sizeof(b), "%u%%", static_cast<unsigned>(s_brightness));
31 lv_label_set_text(lbl, b);
32 }
33}
34
35/* Show the Reset button (and shrink Close) only on the About tab. Driven by the
36 * tab index rather than by an event, so the rebuild path can call it directly
37 * instead of faking a tab change. */
38static void settings_bottom_bar(uint16_t tab) {
39 const bool about = (tab == TAB_ABOUT);
40 if (s_reset_btn != NULL) {
41 if (about) { lv_obj_clear_flag(s_reset_btn, LV_OBJ_FLAG_HIDDEN); }
42 else { lv_obj_add_flag(s_reset_btn, LV_OBJ_FLAG_HIDDEN); }
43 }
44 if (s_close_btn != NULL) {
45 if (about) {
46 lv_obj_set_width(s_close_btn, 108);
47 lv_obj_align(s_close_btn, LV_ALIGN_BOTTOM_RIGHT, -10, ACT_BTN_Y);
48 } else {
49 lv_obj_set_width(s_close_btn, 232);
50 lv_obj_align(s_close_btn, LV_ALIGN_BOTTOM_MID, 0, ACT_BTN_Y);
51 }
52 }
53}
54
55static void tab_change_cb(lv_event_t *e) {
56 lv_obj_t *tabbar = lv_event_get_target(e);
57 /* Only a real press names a button; anything else reports
58 * LV_BTNMATRIX_BTN_NONE (0xFFFF), which must not be mistaken for a tab. */
59 uint16_t sel = lv_btnmatrix_get_selected_btn(tabbar);
60 if (sel >= SETTINGS_TAB_COUNT) { return; }
61 s_settings_tab = sel;
63}
64void build_settings(void) {
65 clear_screen(); /* white, full screen — see the note by paint_page() */
66
67 lv_obj_t *tv = lv_tabview_create(lv_scr_act(), LV_DIR_TOP, 42);
68 lv_obj_set_size(tv, SCR_W, SCR_H - 54);
69 lv_obj_align(tv, LV_ALIGN_TOP_MID, 0, 0);
70 /* Tabview and tab bar are styled by the theme, which paints them COL_BG.
71 * The tabview, tab bar and each page go transparent here (not in the theme,
72 * so other tabviews stay white) to show the ramp behind them. */
73 lv_obj_set_style_bg_opa(tv, LV_OPA_TRANSP, LV_PART_MAIN);
74 lv_obj_set_style_bg_opa(lv_tabview_get_tab_btns(tv), LV_OPA_TRANSP,
75 LV_PART_MAIN);
76
77 lv_obj_t *t_screen = lv_tabview_add_tab(tv, "Screen");
78 lv_obj_t *t_wifi = lv_tabview_add_tab(tv, "Wi-Fi");
79 lv_obj_t *t_tx = lv_tabview_add_tab(tv, "Tx");
80 lv_obj_t *t_about = lv_tabview_add_tab(tv, "About");
81 lv_obj_t *pages[4] = { t_screen, t_wifi, t_tx, t_about };
82 for (int i = 0; i < 4; i++) {
83 lv_obj_set_style_bg_opa(pages[i], LV_OPA_TRANSP, LV_PART_MAIN);
84 lv_obj_set_style_border_width(pages[i], 0, LV_PART_MAIN);
85 lv_obj_set_style_pad_all(pages[i], TAB_PAD, LV_PART_MAIN);
86 lv_obj_clear_flag(pages[i], LV_OBJ_FLAG_SCROLLABLE);
87 }
88 /* About and Tx can overflow — let them scroll vertically with a scrollbar. */
89 lv_obj_t *scrollable[2] = { t_about, t_tx };
90 for (int i = 0; i < 2; i++) {
91 lv_obj_add_flag(scrollable[i], LV_OBJ_FLAG_SCROLLABLE);
92 lv_obj_set_scroll_dir(scrollable[i], LV_DIR_VER);
93 lv_obj_set_scrollbar_mode(scrollable[i], LV_SCROLLBAR_MODE_AUTO);
94 }
95 /* Three of the four tabs are a stack of rows, so they are flex columns and
96 * every row is appended rather than placed: conditional and wrapping rows
97 * push the rest down by themselves. Screen is a layout, not a stack. */
98 lv_obj_t *columns[3] = { t_wifi, t_tx, t_about };
99 for (int i = 0; i < 3; i++) {
100 lv_obj_set_flex_flow(columns[i], LV_FLEX_FLOW_COLUMN);
101 lv_obj_set_style_pad_row(columns[i], 14, LV_PART_MAIN);
102 }
103 /* About is centred on its logo; the other two read left. */
104 lv_obj_set_flex_align(t_about, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
105 LV_FLEX_ALIGN_CENTER);
106
107 /* ── Screen tab: brightness ── */
108 make_label(t_screen, "Brightness", COL_TEXT, &font_inter_14_medium,
109 LV_ALIGN_TOP_LEFT, 0, 0);
110 lv_obj_t *pct = make_label(t_screen, "", COL_DIM, &font_inter_14,
111 LV_ALIGN_TOP_RIGHT, 0, 0);
112 lv_obj_t *sl = lv_slider_create(t_screen);
113 lv_obj_set_width(sl, 196);
114 lv_obj_align(sl, LV_ALIGN_TOP_MID, 0, 32);
115 lv_slider_set_range(sl, 10, 100); /* never fully dark */
116 lv_slider_set_value(sl, s_brightness, LV_ANIM_OFF);
117 lv_obj_set_style_bg_color(sl, COL_BORDER, LV_PART_MAIN);
118 lv_obj_set_style_bg_color(sl, COL_ACCENT, LV_PART_INDICATOR);
119 lv_obj_set_style_bg_color(sl, COL_ACCENT, LV_PART_KNOB);
120 lv_obj_add_event_cb(sl, brightness_event_cb, LV_EVENT_VALUE_CHANGED, pct);
121
122 char b[8];
123 snprintf(b, sizeof(b), "%u%%", static_cast<unsigned>(s_brightness));
124 lv_label_set_text(pct, b);
125
126 /* Resistive overlays vary board to board, so the operator can calibrate
127 * without a rebuild. */
128 lv_obj_t *calpill = make_pill(t_screen, "Touch", "Calibrate the panel",
129 TAB_W, 76, ACT_TOUCH_CAL);
130 lv_obj_align(make_glyph_disc(calpill, LV_SYMBOL_EDIT, COL_ACCENT, COIN_SZ),
131 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
132
133 /* ── Wi-Fi tab: show the currently configured network, then a Scan button ── */
134 char cur_ssid[33] = {0};
135 char cur_pass[65];
136 bool have_wifi = settings_get_wifi(cur_ssid, sizeof(cur_ssid),
137 cur_pass, sizeof(cur_pass));
138 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(cur_pass), sizeof(cur_pass));
139
140 /* Read-only caption/value pair, not a tappable pill: the network is set from
141 * the Configure page, so nobody types a passphrase on a resistive panel. The
142 * on-device picker appears only when the terminal cannot re-join (wifi_picker). */
143 lv_obj_t *wl = make_field(t_wifi, "Network",
144 have_wifi ? cur_ssid : "Not configured");
145 /* An SSID is 32 arbitrary characters; elide it on real glyph widths. */
146 lv_label_set_long_mode(wl, LV_LABEL_LONG_DOT);
147
148 /* Link quality of the live association (snapshot at settings open),
149 * as a caption/value pair like every other field. */
150 int8_t rssi = 0;
151 if (net_wifi_rssi(&rssi)) {
152 const char *qual = (rssi >= -50) ? "Excellent" :
153 (rssi >= -60) ? "Good" :
154 (rssi >= -70) ? "Fair" : "Weak";
155 char sig[32];
156 snprintf(sig, sizeof(sig), "%s (%d dBm)", qual,
157 static_cast<int>(rssi));
158 (void)make_field(t_wifi, "Signal", sig);
159 }
160
161 /* Everything else lives in a browser: this row raises the config page and
162 * shows a QR code to scan (the same action as About's Update row). The
163 * subtitle stays under make_pill's 140px cap, which dot-elides the rest. */
164 lv_obj_t *cpill = make_pill(t_wifi, "Configure", "Open in a browser",
165 TAB_W, 0, ACT_PORTAL);
166 lv_obj_align(make_glyph_disc(cpill, LV_SYMBOL_SETTINGS, COL_ACCENT, COIN_SZ),
167 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
168
169 /* ── Transaction tab: which asset, what it will call, where the funds go and
170 * what gas it will pay.
171 *
172 * All read-only: settings are proposed from the config page and accepted on
173 * this panel, since a resistive screen is the wrong place to retype an
174 * address. The selector picks which asset's rows to show (s_view_chain). ── */
175 const bool tron = pos_chain_is_tron(s_view_chain);
176 ui_refresh_addresses_for(static_cast<uint8_t>(s_view_chain));
177 /* Ticker over network, split across the pill's two lines so both fit
178 * make_pill's 140px cap at full length. */
179 lv_obj_t *apill = make_pill(t_tx, asset_name(s_view_chain),
181 TAB_W, 0, ACT_NET_PICK);
182 lv_obj_align(make_asset_badge(apill, s_view_chain),
183 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
184
186 (s_addr_usdc != NULL) ? s_addr_usdc : "-");
187 (void)make_field(t_tx, "Send to",
188 (s_addr_dest != NULL) ? s_addr_dest : "-");
189
190 /* Say out loud when the recipient is the compile-time one rather than an
191 * address somebody chose: this is the row an operator checks to answer "where
192 * does my money go". The sale is refused at the confirm step, so say that. */
193 if (!settings_has_payout(tron)) {
194 lv_obj_t *w = make_label(t_tx, "Not configured - this terminal cannot "
195 "take payments. Set it from the "
196 "Configure page.",
198 LV_ALIGN_DEFAULT, 0, 0);
199 lv_obj_set_width(w, TAB_W);
200 lv_label_set_long_mode(w, LV_LABEL_LONG_WRAP);
201 }
202
203 /* The gas the terminal is willing to pay, as two read-only rows. Tron has no
204 * such setting (bandwidth, compile-time energy cap), so the rows are absent. */
205 if (!tron) {
206 char fee[16];
207 snprintf(fee, sizeof(fee), "%u",
208 static_cast<unsigned>(settings_get_max_fee_gwei()));
209 s_fee_max_lbl = make_field(t_tx, "Max fee (Gwei)", fee);
210
211 snprintf(fee, sizeof(fee), "%u",
212 static_cast<unsigned>(settings_get_priority_fee_gwei()));
213 s_fee_prio_lbl = make_field(t_tx, "Priority fee (Gwei)", fee);
214 }
215
216 /* The way out of a read-only tab: the config page, one tap from the rows it
217 * sets (same action as the Wi-Fi and About rows). The subtitle names the fees
218 * where there are any; both forms fit make_pill's 140px subtitle cap. */
219 lv_obj_t *tpill = make_pill(t_tx, "Configure",
220 tron ? "Open in a browser" : "Fees in a browser",
221 TAB_W, 0, ACT_PORTAL);
222 lv_obj_align(make_glyph_disc(tpill, LV_SYMBOL_SETTINGS, COL_ACCENT, COIN_SZ),
223 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
224
225 /* ── About tab: small C logo, name, version, info ── */
226 lv_obj_t *blogo = lv_img_create(t_about);
227 lv_img_set_src(blogo, &logo_small); /* 40px dedicated image */
228
229 make_label(t_about, "cryptnox-pos", COL_TEXT, &font_pjs_20_medium,
230 LV_ALIGN_DEFAULT, 0, 0);
231 /* Straight out of the running image's header rather than a #define, so that
232 * after an update this reads as the firmware that is actually executing. */
233 char about_ver[OTA_VERSION_SHOWN_MAX];
234 make_label(t_about,
236 sizeof(about_ver)),
237 COL_DIM, &font_inter_14, LV_ALIGN_DEFAULT, 0, 0);
238
239 /* An update that booted and was then reverted would leave this tab on the
240 * old version with no explanation, so say so in one red line under it. It
241 * clears itself when the next update overwrites the slot it is read from. */
243 make_label(t_about, "Last update rolled back", COL_DANGER,
244 &font_inter_14, LV_ALIGN_DEFAULT, 0, 0);
245 }
246
247 /* The update row: opens the config page with a QR code to scan, so it sits
248 * behind the admin code with the rest of the settings. Subtitle kept under
249 * make_pill's 140px cap, which dot-elides the rest. */
250 lv_obj_t *upill = make_pill(t_about, "Update", "From a browser",
251 TAB_W, 0, ACT_PORTAL);
252 lv_obj_align(make_glyph_disc(upill, LV_SYMBOL_DOWNLOAD, COL_ACCENT, COIN_SZ),
253 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
254
255 /* Deliberately says nothing about which assets or networks: that list goes
256 * stale with the firmware, and the Tx tab answers it from live settings. */
257 lv_obj_t *about = make_label(t_about,
258 "Crypto payment terminal\n"
259 "for Cryptnox cards\n\n"
260 "Based on cryptnox-sdk-esp32 1.0.0\n"
261 "(c) Cryptnox 2026 - Educational use only\n\n"
262 "Licensed under LGPL-3.0-or-later\n\n"
263 "Third-party: ESP-IDF (Apache-2.0),\n"
264 "LVGL (MIT), TFT_eSPI (FreeBSD/MIT),\n"
265 "XPT2046_Touchscreen (MIT)",
267 LV_ALIGN_DEFAULT, 0, 0);
268 lv_label_set_long_mode(about, LV_LABEL_LONG_WRAP);
269 lv_obj_set_width(about, 210);
270 lv_obj_set_style_text_align(about, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
271
272 /* Bottom bar: Close always; on the About tab a Reset joins it on the same
273 * line (Reset left, Close right). On other tabs Close is full-width. */
274 s_close_btn = make_button(lv_scr_act(), "Close", COL_ACTION, COL_BG, 232, ACT_BTN_H,
275 LV_ALIGN_BOTTOM_MID, 0, ACT_BTN_Y, ACT_CLOSE,
277 s_reset_btn = make_button(lv_scr_act(), "Reset", COL_DANGER, COL_TEXT, 108, ACT_BTN_H,
278 LV_ALIGN_BOTTOM_LEFT, 10, ACT_BTN_Y, ACT_RESET,
280 /* Shape comes from make_button (BTN_RADIUS), like every other button. */
281 lv_obj_add_event_cb(lv_tabview_get_tab_btns(tv), tab_change_cb,
282 LV_EVENT_VALUE_CHANGED, NULL);
283
284 /* Back to the tab the operator was on: a rebuild is how this page applies a
285 * changed asset, and it should not look like the page reopened. */
286 if (s_settings_tab != 0U) {
287 lv_tabview_set_act(tv, s_settings_tab, LV_ANIM_OFF);
288 }
290}
291
292/******************************************************************
293 * 7c. Touch calibration
294 *
295 * Resistive overlays vary unit to unit. Two targets, then the operator confirms
296 * the result by tapping a button drawn with it.
297 ******************************************************************/
298
299
300/* Crosshair rather than a filled dot: the target is the centre, and a dot
301 * hides it under the finger that is aiming at it. */
302static void cal_target(lv_coord_t cx, lv_coord_t cy) {
303 const lv_coord_t arm = 12;
304 static lv_point_t h[2], v[2];
305 h[0] = { (lv_coord_t)(cx - arm), cy }; h[1] = { (lv_coord_t)(cx + arm), cy };
306 v[0] = { cx, (lv_coord_t)(cy - arm) }; v[1] = { cx, (lv_coord_t)(cy + arm) };
307 for (int i = 0; i < 2; i++) {
308 lv_obj_t *l = lv_line_create(lv_scr_act());
309 lv_line_set_points(l, (i == 0) ? h : v, 2);
310 lv_obj_set_style_line_width(l, 2, LV_PART_MAIN);
311 lv_obj_set_style_line_color(l, COL_DANGER, LV_PART_MAIN);
312 lv_obj_set_pos(l, 0, 0);
313 }
314}
315
316/* Turn the two captured corners into the edge-to-edge range touch_to_screen()
317 * maps against, and put it in force without storing it. Returns false on a
318 * pair too close together to be two deliberate taps — which is what a stuck
319 * panel or an impatient double-tap on one spot looks like. */
320static bool cal_apply(void) {
321 touch_cal_t c;
323 s_cal_raw[1][0], s_cal_raw[1][1],
324 CAL_INSET, SCR_W, SCR_H, &c)) {
325 return false;
326 }
329 return true;
330}
331void build_touch_cal(void) {
332 clear_screen();
333
334 if (s_cal_step < 2U) {
335 const bool first = (s_cal_step == 0U);
336 /* No header on these two: the targets sit in the corners, and the one
337 * at the top left lands under a title bar's divider. Nothing is drawn
338 * near a corner except the cross being aimed at. */
339 lv_obj_t *ask = make_label(lv_scr_act(),
340 first ? "Tap the cross\nat the top left"
341 : "Now the one at\nthe bottom right",
342 COL_TEXT, &font_pjs_20_medium, LV_ALIGN_CENTER, 0, -10);
343 lv_obj_set_style_text_align(ask, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
344 lv_obj_align(ask, LV_ALIGN_CENTER, 0, -10);
345 lv_obj_t *hint = make_label(lv_scr_act(),
346 "Use a stylus or a fingernail - the centre "
347 "of the cross, not near it.",
349 LV_ALIGN_CENTER, 0, 60);
350 lv_obj_set_width(hint, 200);
351 lv_label_set_long_mode(hint, LV_LABEL_LONG_WRAP);
352 lv_obj_set_style_text_align(hint, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
353 lv_obj_align(hint, LV_ALIGN_CENTER, 0, 60);
354
355 cal_target(first ? CAL_INSET : (SCR_W - 1 - CAL_INSET),
356 first ? CAL_INSET : (SCR_H - 1 - CAL_INSET));
357 return;
358 }
359
360 /* Both corners captured. */
361 build_header("Calibrate touch");
362 if (!cal_apply()) {
363 lv_obj_t *no = make_label(lv_scr_act(),
364 "Those two taps were\ntoo close together.\n\n"
365 "Nothing was changed.",
366 COL_DANGER, &font_pjs_20_medium, LV_ALIGN_CENTER, 0, -20);
367 lv_obj_set_style_text_align(no, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
368 lv_obj_align(no, LV_ALIGN_CENTER, 0, -20);
369 make_button(lv_scr_act(), "Back", COL_ACTION, COL_BG, 232, ACT_BTN_H,
370 LV_ALIGN_BOTTOM_MID, 0, ACT_BTN_Y, ACT_CAL_CANCEL,
372 return;
373 }
374
375 /* The new map is live from here. Tapping Keep with it IS the test. */
376 lv_obj_t *q = make_label(lv_scr_act(), "Keep this\ncalibration?", COL_TEXT,
377 &font_pjs_20_medium, LV_ALIGN_TOP_MID, 0, 70);
378 lv_obj_set_style_text_align(q, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
379 lv_obj_align(q, LV_ALIGN_TOP_MID, 0, 70);
380 s_cal_countdown = make_label(lv_scr_act(), "", COL_DIM,
382 LV_ALIGN_TOP_MID, 0, 140);
383 s_cal_deadline = lv_tick_get() + CAL_VERIFY_MS;
384
385 make_button(lv_scr_act(), "Discard", COL_ACTION, COL_BG, 104, ACT_BTN_H,
386 LV_ALIGN_BOTTOM_LEFT, 10, ACT_BTN_Y, ACT_CAL_CANCEL,
388 make_button(lv_scr_act(), "Keep", COL_ACTION, COL_BG, 104, ACT_BTN_H,
389 LV_ALIGN_BOTTOM_RIGHT, -10, ACT_BTN_Y, ACT_CAL_SAVE,
391}
392
393/* Sampling runs on the UI task, outside LVGL's input device: the calibration
394 * screen is the one place that must read the panel before the mapping it is
395 * measuring. The sample taken is the last one before release — a resistive
396 * panel's first reading as the finger lands is its worst. */
397void touch_cal_poll(void) {
398 if (s_cal_step >= 2U) { return; }
399
400 int16_t rx, ry;
401 if (touch_raw(&rx, &ry)) {
402 s_cal_pressed = true;
403 s_cal_last_x = rx;
404 s_cal_last_y = ry;
405 return;
406 }
407 if (!s_cal_pressed) { return; }
408
409 s_cal_pressed = false;
412 s_cal_step++;
413 request_screen(UI_SCREEN_TOUCH_CAL); /* next target, then the verify */
414}
415
416/* Modal overlays (factory-reset confirmation, network picker). One at a time:
417 * both are full-screen and both are opened from the settings page. */
418static lv_obj_t *s_modal = NULL;
419
420/* Whether the modal on screen belongs to the config portal. The portal closes
421 * itself after PROV_WINDOW_MIN, and a leftover overlay swallows every touch and
422 * would block payments until a power cycle, so the UI task clears it then. */
423bool s_portal_modal = false;
424void close_modal(void) {
425 if (s_modal != NULL) {
426 lv_obj_del(s_modal);
427 s_modal = NULL;
428 }
429 s_portal_modal = false;
430}
431
432/* Dimmed overlay + centred card on the top layer, so it floats above the
433 * settings page. Returns the card for the caller to fill. */
434lv_obj_t *open_modal(lv_coord_t w, lv_coord_t h) {
435 close_modal();
436
437 s_modal = lv_obj_create(lv_layer_top());
438 lv_obj_remove_style_all(s_modal);
439 lv_obj_set_size(s_modal, SCR_W, SCR_H);
440 lv_obj_set_style_bg_color(s_modal, lv_color_black(), LV_PART_MAIN);
441 lv_obj_set_style_bg_opa(s_modal, LV_OPA_70, LV_PART_MAIN);
442 lv_obj_clear_flag(s_modal, LV_OBJ_FLAG_SCROLLABLE);
443 lv_obj_add_flag(s_modal, LV_OBJ_FLAG_CLICKABLE);
444
445 lv_obj_t *card = lv_obj_create(s_modal);
446 lv_obj_set_size(card, w, h);
447 lv_obj_center(card);
448 lv_obj_set_style_bg_color(card, COL_BG, LV_PART_MAIN);
449 lv_obj_set_style_radius(card, 14, LV_PART_MAIN);
450 lv_obj_set_style_border_width(card, 1, LV_PART_MAIN);
451 lv_obj_set_style_border_color(card, COL_BORDER, LV_PART_MAIN);
452 lv_obj_set_style_pad_all(card, 8, LV_PART_MAIN);
453 lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);
454 lv_obj_add_flag(card, LV_OBJ_FLAG_CLICKABLE);
455 return card;
456}
458 lv_obj_t *card = open_modal(224, 210);
459
460 lv_obj_t *msg = make_label(card,
461 "Erase all settings\n"
462 "(Wi-Fi, brightness, fees)\n"
463 "and reboot?",
465 LV_ALIGN_TOP_MID, 0, 12);
466 lv_obj_set_style_text_align(msg, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
467
468 make_button(card, "Erase", COL_DANGER, COL_TEXT, 196, 40,
469 LV_ALIGN_BOTTOM_MID, 0, -50, ACT_RESET_CONFIRM, &font_pjs_20_semibold);
470 make_button(card, "Cancel", COL_ACTION, COL_BG, 196, 40,
471 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_MODAL_CLOSE, &font_pjs_20_semibold);
472}
473#define OTA_GAP 8
474#define OTA_CARD_PAD 8 /* open_modal()'s pad_all */
475
477lv_obj_t *ota_text(lv_obj_t *card, lv_obj_t *above, const char *txt,
478 lv_color_t col, const lv_font_t *font) {
479 lv_obj_t *l = make_label(card, txt, col, font, LV_ALIGN_TOP_MID, 0, 0);
480 lv_obj_set_width(l, OTA_TEXT_W);
481 lv_label_set_long_mode(l, LV_LABEL_LONG_WRAP);
482 lv_obj_set_style_text_align(l, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
483 if (above == NULL) {
484 lv_obj_align(l, LV_ALIGN_TOP_MID, 0, 4);
485 } else {
486 /* align_to() reads current coordinates, and a freshly sized label has
487 * not been laid out yet: without this its wrapped height is stale. */
488 lv_obj_update_layout(card);
489 lv_obj_align_to(l, above, LV_ALIGN_OUT_BOTTOM_MID, 0, OTA_GAP);
490 }
491 return l;
492}
493
502void ota_fit_card(lv_obj_t *card, lv_obj_t *last, lv_coord_t buttons_h) {
503 lv_obj_update_layout(card);
504 lv_coord_t need = lv_obj_get_y(last) + lv_obj_get_height(last)
505 + OTA_GAP + buttons_h + (2 * OTA_CARD_PAD);
506 if (need > (SCR_H - 16)) { need = SCR_H - 16; }
507 lv_obj_set_height(card, need);
508 lv_obj_center(card);
509}
511 /* Started here, on the UI task. The event callback is handed over so a
512 * submission can come back to the panel. */
513 const bool up = prov_start(PROV_MODE_ADMIN, s_cb);
514
515 if (!up) {
516 lv_obj_t *card = open_modal(OTA_CARD_W, 200);
517 lv_obj_t *m = ota_text(card, NULL,
518 "The terminal's own Wi-Fi would not come up, so there is "
519 "nothing for a phone to join.\n\nRestart and try again.",
521 ota_fit_card(card, m, 42);
522 make_button(card, "Close", COL_ACTION, COL_BG, OTA_TEXT_W, 40,
523 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_MODAL_CLOSE,
525 return;
526 }
527
528 lv_obj_t *card = open_modal(OTA_CARD_W, 300);
529
530 /* A "WIFI:..." join code: the page is on the terminal's own AP, so joining
531 * is the whole journey. 104 px is 26 modules at 4 px. The white quiet zone
532 * is not optional: many scanners miss a code drawn hard against an edge. */
533 lv_obj_t *qr = lv_qrcode_create(card, 104, COL_TEXT, COL_BG);
534 if (qr != NULL) {
535 const char *payload = prov_qr_payload();
536 (void)lv_qrcode_update(qr, payload, strlen(payload));
537 lv_obj_set_style_border_color(qr, COL_BG, LV_PART_MAIN);
538 lv_obj_set_style_border_width(qr, 4, LV_PART_MAIN);
539 /* Top of the card, the same 4px inset ota_text() gives a first block. */
540 lv_obj_align(qr, LV_ALIGN_TOP_MID, 0, 4);
541 }
542
543 /* The credentials in text too — a laptop has no camera to point, and a code
544 * that will not scan in a dim bar still leaves ten characters to type. */
545 char creds[80];
546 snprintf(creds, sizeof(creds), "SSID: %s\nPassword: %s",
548 lv_obj_t *url = ota_text(card, qr, creds, COL_TEXT, &font_inter_14_medium);
549
550 /* Three hand-broken lines, each under OTA_TEXT_W: under ota_fit_card's
551 * ceiling the code, credentials and Done button leave room for no more, so
552 * anything added here must replace one. The address is for a real browser:
553 * the Wi-Fi sign-in window cannot pick a firmware file. */
554 char note[96];
555 snprintf(note, sizeof(note),
556 "Or open 192.168.4.1\nVenue Wi-Fi off while open\nCloses in %u min",
558 lv_obj_t *n = ota_text(card, url, note, COL_DIM, &font_inter_14);
559 ota_fit_card(card, n, 42);
560
561 make_button(card, "Done", COL_ACTION, COL_BG, OTA_TEXT_W, 40,
562 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_PORTAL_CLOSE,
564
565 s_portal_modal = true; /* set last: open_modal() cleared it */
566}
567
581void open_ota_gone(void) {
582 lv_obj_t *card = open_modal(OTA_CARD_W, 200);
583 lv_obj_t *m = ota_text(card, NULL,
584 "That firmware is no longer waiting to be installed.\n\n"
585 "Nothing changed - this terminal is still on the version it was. "
586 "Open Update again and send the file once more.",
588 ota_fit_card(card, m, 42);
589 make_button(card, "Close", COL_ACTION, COL_BG, OTA_TEXT_W, 40,
590 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_MODAL_CLOSE,
592}
594 char version[40] = "?";
595 bool older = false;
596 if (!ota_staged(version, sizeof(version), &older)) { return; }
597
598 lv_obj_t *card = open_modal(OTA_CARD_W, 252);
599
600 /* The downgrade warning is the caption, one red line above the version, so
601 * the body and the card's height stay the same either way. */
602 lv_obj_t *cap = ota_text(card, NULL,
603 older ? "This is an OLDER version" : "New firmware",
604 older ? COL_DANGER : COL_DIM,
606 /* 28 pt fits about twelve characters (the 'v' included); a longer
607 * `git describe` string steps down a size rather than wrapping. */
608 char staged_ver[OTA_VERSION_SHOWN_MAX];
609 (void)ota_version_display(version, staged_ver, sizeof(staged_ver));
610 lv_obj_t *ver = ota_text(card, cap, staged_ver, COL_TEXT,
611 (strlen(staged_ver) > 12U) ? &font_pjs_20_medium
613
614 char running_ver[OTA_VERSION_SHOWN_MAX];
615 char body[128];
616 snprintf(body, sizeof(body),
617 "Running %s.\n\nThe terminal restarts now. Not during a payment.",
619 sizeof(running_ver)));
620 lv_obj_t *m = ota_text(card, ver, body, COL_DIM, &font_inter_14);
621 ota_fit_card(card, m, 86);
622
623 make_button(card, "Install", COL_ACTION, COL_BG, OTA_TEXT_W, 40,
624 LV_ALIGN_BOTTOM_MID, 0, -46, ACT_OTA_OK, &font_pjs_20_semibold);
625 make_button(card, "Discard", COL_ACTION, COL_BG, OTA_TEXT_W, 40,
626 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_OTA_NO, &font_pjs_20_semibold);
627
628 s_portal_modal = true;
629}
630
631/* Asset selection, in two steps: the network, then the coin on it, since a chain
632 * is a (network, coin) pair. The card is 228 wide (212 inside the 8px pad), so
633 * the pills run to 206. */
634#define PICK_W 206
635
642static void pill_disable(lv_obj_t *p) {
643 lv_obj_clear_flag(p, LV_OBJ_FLAG_CLICKABLE);
644 lv_obj_set_style_bg_opa(p, LV_OPA_50, LV_PART_MAIN);
645 for (uint32_t i = 0; i < lv_obj_get_child_cnt(p); i++) {
646 lv_obj_set_style_text_color(lv_obj_get_child(p, i), COL_DIM, LV_PART_MAIN);
647 }
648}
649
651/* Which action opens step 2 for a network, in pos_net_t order. Kept here
652 * rather than in assets.h because an action is a UI concept. */
657 /* Same growth rule as step 2 — header + rows + the Cancel button — so a
658 * fourth network added to assets.h widens the card rather than clipping. */
659 lv_obj_t *card = open_modal(228,
660 static_cast<lv_coord_t>(86 + (POS_NET__COUNT * (PILL_H + 2))));
661
662 make_label(card, "Network", COL_DIM, &font_inter_14,
663 LV_ALIGN_TOP_MID, 0, 2);
664
665 for (int i = 0; i < (int)POS_NET__COUNT; i++) {
666 const pos_net_info_t *ni = pos_net_info(static_cast<pos_net_t>(i));
667 /* Read here, not cached: the subtitle names the deployment, which is a
668 * runtime setting. */
669 const char *sub = settings_net_str(ni->sub_test, ni->sub_main);
670 /* A network with no payout address of its own is not offered. Otherwise a
671 * terminal set up for Ethereum and interrupted before Tron would quietly
672 * take Tron payments to the compile-time recipient — somebody else's
673 * address — and look entirely normal doing it. Polygon shares the
674 * Ethereum payout address (same EVM account). */
675 const bool have = settings_has_payout(i == (int)POS_NET_TRON);
676 lv_coord_t y = static_cast<lv_coord_t>(22 + (i * (PILL_H + 2)));
677 /* Kept under this pill's 130px subtitle cap
678 * (PICK_W - PILL_TEXT_X - PILL_TEXT_PAD_R) so it is not elided. */
679 lv_obj_t *p = make_pill(card, ni->name, have ? sub : "No payout set",
680 PICK_W, y, NET_ACT[i]);
681 lv_obj_align(make_net_badge(p, static_cast<pos_net_t>(i)),
682 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
683 if (!have) { pill_disable(p); }
684 }
685
686 make_button(card, "Cancel", COL_SURFACE, COL_TEXT, PICK_W, 40,
687 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_MODAL_CLOSE,
689}
690
693 /* The rows are assets.h's, filtered on the network, in table order (grouped
694 * by network, native coin first). The chain IS the action (ACT_CHAIN_OF). */
695 size_t n = 0;
696 for (size_t i = 0U; i < POS_ASSET_COUNT; i++) {
697 if (POS_ASSETS[i].net == net) { n++; }
698 }
699
700 char title[24];
701 (void)snprintf(title, sizeof(title), "Coin on %s", pos_net_info(net)->name);
702
703 /* Card grows with the row count: header + rows + the Back button. */
704 lv_obj_t *card = open_modal(228,
705 static_cast<lv_coord_t>(86 + (n * (PILL_H + 2))));
706
707 make_label(card, title, COL_DIM,
708 &font_inter_14, LV_ALIGN_TOP_MID, 0, 2);
709
710 size_t row = 0;
711 for (size_t i = 0U; i < POS_ASSET_COUNT; i++) {
712 const pos_asset_t *a = &POS_ASSETS[i];
713 if (a->net != net) { continue; }
714 lv_coord_t y = static_cast<lv_coord_t>(22 + (row * (PILL_H + 2)));
715 lv_obj_t *p = make_pill(card, a->ticker, a->standard, PICK_W, y,
716 ACT_CHAIN_OF(a->chain), true /* leaf */);
717 lv_obj_align(make_asset_badge(p, a->chain),
718 LV_ALIGN_LEFT_MID, PILL_ICON_X, 0);
719 row++;
720 }
721
722 /* Back, not Cancel: step 2 of two, so the way out is step 1. */
723 make_button(card, "Back", COL_SURFACE, COL_TEXT, PICK_W, 40,
724 LV_ALIGN_BOTTOM_MID, 0, -2, ACT_NET_PICK,
726}
727
738#define ADMIN_PENALTY_MAX_S 3600U
739uint32_t admin_penalty_ms(uint8_t fails) {
740 if (fails < 3U) { return 0U; }
741 uint32_t shift = static_cast<uint32_t>(fails) - 3U;
742 if (shift > 12U) { shift = 12U; } /* 4096 s: past the cap, no overflow */
743 uint32_t secs = 1U << shift;
744 if (secs > ADMIN_PENALTY_MAX_S) { secs = ADMIN_PENALTY_MAX_S; }
745 return secs * 1000U;
746}
747
748/* "45s" or "12 min": the penalty runs to an hour, and nobody at a counter wants
749 * to divide "wait 3417s" by sixty. */
750static void wait_text(char *out, size_t n, uint32_t secs) {
751 if (secs < 120U) { (void)snprintf(out, n, "%us", static_cast<unsigned>(secs)); }
752 else { (void)snprintf(out, n, "%u min", static_cast<unsigned>((secs + 59U) / 60U)); }
753}
754
755/* Seconds left on the penalty, 0 once it has elapsed. */
756static uint32_t admin_lock_remaining_s(void) {
757 if (s_admin_lock_ms == 0U) { return 0U; }
758 const uint32_t elapsed = lv_tick_elaps(s_admin_lock_start);
759 if (elapsed >= s_admin_lock_ms) {
760 s_admin_lock_ms = 0U;
761 return 0U;
762 }
763 return ((s_admin_lock_ms - elapsed) + 999U) / 1000U;
764}
765
766static void admin_set_note(const char *msg) {
767 strncpy(s_admin_note, (msg != NULL) ? msg : "", sizeof(s_admin_note) - 1);
768 s_admin_note[sizeof(s_admin_note) - 1] = '\0';
769 if (s_admin_note_lbl != NULL) {
770 lv_label_set_text(s_admin_note_lbl, s_admin_note);
771 }
772}
773
774static void admin_submit(void) {
775 if (s_admin_ta == NULL) { return; }
776
777 char code[ADMIN_CODE_MAX + 1] = {0};
778 const char *txt = lv_textarea_get_text(s_admin_ta);
779 strncpy(code, (txt != NULL) ? txt : "", sizeof(code) - 1);
780 code[sizeof(code) - 1] = '\0';
781
783 if (!s_admin_confirming) {
784 if (strlen(code) < ADMIN_CODE_MIN) {
785 char msg[sizeof(s_admin_note)];
786 (void)snprintf(msg, sizeof(msg), "At least %d digits",
788 admin_set_note(msg); /* built, so it cannot drift from the limit */
789 } else {
790 strncpy(s_admin_first, code, sizeof(s_admin_first) - 1);
791 s_admin_first[sizeof(s_admin_first) - 1] = '\0';
792 s_admin_confirming = true;
793 admin_set_note("");
794 request_screen(UI_SCREEN_ADMIN_SET); /* rebuild, confirm pass */
795 }
796 } else if (strcmp(code, s_admin_first) == 0) {
797 if (!settings_set_admin_code(code)) {
798 /* Stay put and say so. Reporting success here would send main on
799 * to Wi-Fi setup and leave a terminal whose menu — factory reset
800 * included — no code can ever open. */
801 admin_set_note("Storage error - try again");
802 lv_textarea_set_text(s_admin_ta, "");
803 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(code),
804 sizeof(code));
805 return;
806 }
807 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(s_admin_first),
808 sizeof(s_admin_first));
809 s_admin_confirming = false;
810 admin_set_note("");
811 /* Not the amount screen: on this first-run path main raises the setup
812 * access point next, so say that rather than flash the keypad. */
813 set_wifi_progress("Starting setup", NULL);
815 if (s_cb != NULL) { s_cb(UI_EVENT_ADMIN_SET, 0); }
816 } else {
817 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(s_admin_first),
818 sizeof(s_admin_first));
819 s_admin_confirming = false;
820 admin_set_note("Codes did not match");
822 }
823 } else {
824 const uint32_t wait_s = admin_lock_remaining_s();
825 char msg[sizeof(s_admin_note)];
826 if (strlen(code) < ADMIN_CODE_MIN_STORED) {
827 /* Too short to be any stored code, so don't spend an attempt on it.
828 * Otherwise a few stray taps on OK push the counter into the penalty
829 * and the merchant waits a minute for a menu nobody attacked. */
830 admin_set_note("Enter your code");
831 } else if (wait_s > 0U) {
832 char w[16];
833 wait_text(w, sizeof(w), wait_s);
834 (void)snprintf(msg, sizeof(msg), "Too many tries - wait %s", w);
835 admin_set_note(msg);
836 } else if (settings_check_admin_code(code)) {
837 s_admin_lock_ms = 0U;
838 if (s_admin_for_portal) {
839 /* This is the whole authorisation mechanism: the code was typed
840 * here, so the browser is let in without it ever having been on
841 * the network.
842 *
843 * Admin mode returns to the settings page (the code was just
844 * verified); the setup screen would show AP credentials that only
845 * exist during setup. */
846 s_admin_for_portal = false;
848 prov_auth_resolve(true);
851 } else {
853 }
854 } else {
855 const uint32_t penalty = admin_penalty_ms(settings_admin_fail_count());
856 if (penalty > 0U) {
857 s_admin_lock_start = lv_tick_get();
858 s_admin_lock_ms = penalty;
859 char w[16];
860 wait_text(w, sizeof(w), penalty / 1000U);
861 (void)snprintf(msg, sizeof(msg), "Wrong code - wait %s", w);
862 admin_set_note(msg);
863 } else {
864 admin_set_note("Wrong code");
865 }
866 lv_textarea_set_text(s_admin_ta, "");
867 }
868 }
869 CW_Utils::secure_wipe(reinterpret_cast<uint8_t *>(code), sizeof(code));
870}
871
872static void admin_kbd_cb(lv_event_t *e) {
873 lv_obj_t *bm = lv_event_get_target(e);
874 uint32_t id = lv_btnmatrix_get_selected_btn(bm);
875 const char *txt = lv_btnmatrix_get_btn_text(bm, id);
876 if ((txt == NULL) || (s_admin_ta == NULL)) { return; }
877
878 if (strcmp(txt, LV_SYMBOL_OK) == 0) {
879 admin_submit();
880 } else if (strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) {
881 lv_textarea_del_char(s_admin_ta);
882 } else if ((txt[0] >= '0') && (txt[0] <= '9') && (txt[1] == '\0')) {
883 lv_textarea_add_char(s_admin_ta, static_cast<uint32_t>(txt[0]));
884 }
885}
886
887/* Shared body of both admin screens; only the title and the way out differ. */
888static void build_admin_screen(const char *title, bool allow_cancel,
889 const char *hint) {
890 /* Into the rising sheet when there is one, WITHOUT clearing the screen: the
891 * sale screen underneath is what the sheet slides over. Otherwise a normal
892 * rebuild. Either way it takes the card PIN's look (card on the ramp). */
893 lv_obj_t *host;
894 if (s_sheet != NULL) {
896 host = make_card(s_sheet);
897 } else {
898 host = build_page(PAY_STEP_NONE, false);
899 }
900
901 (void)make_title(title, allow_cancel, host);
902 if (allow_cancel) {
903 (void)make_icon_button(LV_SYMBOL_LEFT, ACT_ADMIN_CANCEL, host);
904 }
905
906 /* The card is 262 tall, not 320, so the note tucks under the field
907 * (44..76) and the keypad gives up height, as the PIN screen's does. */
908 lv_obj_t *kb = make_numeric_keypad(admin_kbd_cb, host, CODE_KBD_W,
909 160);
910
911 /* The reveal eye, as on the setup PIN: the code is typed blind on a resistive
912 * panel, and getting it wrong costs a doubling lockout on the only door to
913 * the factory reset. */
916
917 /* Note band above the keypad: wrong code, mismatch, or the remaining wait. */
919 &font_inter_14, LV_ALIGN_TOP_MID, 0,
920 78);
921}
922void build_admin_set(void) {
923 /* No way out: first-run setup is mandatory, since the whole menu — including
924 * the factory reset — hides behind this code. */
925 build_admin_screen(s_admin_confirming ? "Confirm code" : "Set admin code",
926 false,
927 s_admin_confirming ? "Type it again" : "New admin code");
928}
930 /* Same screen either way (lockout, note band, keypad); the title says which
931 * door, the hint says the key is the admin code. For a browser, the title
932 * carries its pairing code — the page shows the same four digits, so the
933 * operator lets in the phone in their hand and not whichever one on the
934 * access point asked first. */
935 static char title[24];
936 char code[8];
937 if (s_admin_for_portal && prov_pair_code(code, sizeof(code))) {
938 (void)snprintf(title, sizeof(title), "Browser %s", code);
939 } else {
940 (void)snprintf(title, sizeof(title), "%s",
941 s_admin_for_portal ? "Authorize browser" : "Control panel");
942 }
943 build_admin_screen(title, true, "Admin code");
944}
pos_net_t
A network, as the picker's first step offers it.
Definition assets.h:51
@ POS_NET_TRON
Definition assets.h:54
@ POS_NET__COUNT
Definition assets.h:55
static bool pos_chain_is_tron(pos_chain_t c)
true for the Tron selections; every other one is EVM.
Definition assets.h:165
static const pos_asset_t POS_ASSETS[]
Definition assets.h:102
#define POS_ASSET_COUNT
Definition assets.h:127
static const pos_net_info_t * pos_net_info(pos_net_t net)
Names for net, or Ethereum's for a value out of range.
Definition assets.h:190
lv_font_t font_inter_14
lv_font_t font_inter_14_medium
lv_font_t font_pjs_20_medium
lv_font_t font_pjs_20_semibold
lv_font_t font_pjs_28_semibold
bool net_wifi_rssi(int8_t *rssi_out)
Read the RSSI of the currently associated access point.
Definition net.cpp:448
const char * ota_running_version(void)
The running firmware's version, from the image header.
Definition ota.cpp:263
bool ota_staged(char *version, size_t version_n, bool *older)
Fetch the version of an image that has been received and verified but not yet installed.
Definition ota.cpp:275
bool ota_last_update_failed(void)
Whether the last update was installed and then thrown away.
Definition ota.cpp:221
#define OTA_VERSION_SHOWN_MAX
Bytes needed by ota_version_display: the version, a 'v', a NUL.
static const char * ota_version_display(const char *v, char *buf, size_t n)
The display form of a version: 1.0.0 reads as v1.0.0 on screen.
void ui_refresh_addresses_for(uint8_t c)
Point the UI's address rows at the selected chain.
Definition pay.cpp:155
void prov_auth_resolve(bool grant)
Answer a pending authorisation request from the panel.
bool prov_pair_code(char *out, size_t out_size)
The 4-digit pairing code of the browser asking to be let in.
const char * prov_ap_pass(void)
AP passphrase, or "" while no portal is up.
const char * prov_qr_payload(void)
What the panel's QR code should carry.
const char * prov_ap_ssid(void)
AP SSID, or "" while no portal is up.
static ui_event_cb_t s_cb
prov_mode_t prov_mode(void)
Which mode is running, or PROV_MODE_OFF.
bool prov_start(prov_mode_t mode, ui_event_cb_t cb)
Raise the portal.
unsigned prov_window_left_min(void)
Minutes left before the portal closes itself, 0 once it has.
@ PROV_MODE_WIZARD
Definition provision.h:106
@ PROV_MODE_ADMIN
Definition provision.h:107
bool settings_check_admin_code(const char *code)
Check a candidate code, maintaining the failure counter.
Definition settings.cpp:414
bool settings_get_wifi(char *ssid, size_t ssid_n, char *pass, size_t pass_n)
Read the stored Wi-Fi credentials.
Definition settings.cpp:298
const char * settings_net_str(const char *testnet, const char *mainnet)
Pick the string belonging to the network the terminal is on.
Definition settings.cpp:210
pos_chain_t settings_get_chain(void)
Selected chain, or POS_CHAIN_ETH_USDC if never set.
Definition settings.cpp:165
uint32_t settings_get_priority_fee_gwei(void)
EIP-1559 max priority fee (tip) per gas, in Gwei.
Definition settings.cpp:352
bool settings_set_admin_code(const char *code)
Store a new admin code, with a fresh random salt.
Definition settings.cpp:385
uint8_t settings_admin_fail_count(void)
Consecutive failed unlock attempts, persisted.
Definition settings.cpp:448
void settings_set_brightness(uint8_t pct)
Persist the backlight level (0..100).
Definition settings.cpp:220
bool settings_has_payout(bool tron)
Whether an operator has actually set the payout address for a network.
Definition settings.cpp:534
uint32_t settings_get_max_fee_gwei(void)
EIP-1559 max fee per gas, in Gwei.
Definition settings.cpp:347
One selectable asset.
Definition assets.h:66
pos_net_t net
Definition assets.h:78
const char * standard
Definition assets.h:69
pos_chain_t chain
Definition assets.h:67
const char * ticker
Definition assets.h:68
Names for one network, in both deployments.
Definition assets.h:92
const char * name
Definition assets.h:93
const char * sub_main
Definition assets.h:97
const char * sub_test
Definition assets.h:96
uint16_t y_max
Definition touch_cal.h:37
uint16_t x_min
Definition touch_cal.h:34
uint16_t x_max
Definition touch_cal.h:35
uint16_t y_min
Definition touch_cal.h:36
static bool touch_cal_from_corners(int16_t r0x, int16_t r0y, int16_t r1x, int16_t r1y, int16_t inset, int16_t w, int16_t h, touch_cal_t *out)
Definition touch_cal.h:75
lv_obj_t * s_sheet
Definition ui.cpp:62
const char * asset_caption(pos_chain_t c)
Definition ui.cpp:210
bool s_admin_confirming
Definition ui.cpp:149
void set_wifi_progress(const char *caption, const char *name)
Definition ui.cpp:247
uint32_t s_admin_lock_start
Definition ui.cpp:155
char s_admin_note[48]
Definition ui.cpp:148
lv_obj_t * s_fee_max_lbl
Definition ui.cpp:126
int16_t s_cal_last_x
Definition ui.cpp:53
char s_admin_first[ADMIN_CODE_MAX+1]
Definition ui.cpp:147
bool s_cal_pressed
Definition ui.cpp:52
const char * s_addr_dest
Definition ui.cpp:168
void request_screen(ui_screen_t s)
Definition ui.cpp:241
lv_obj_t * s_cal_countdown
Definition ui.cpp:57
pos_chain_t s_view_chain
Definition ui.cpp:186
lv_obj_t * s_fee_prio_lbl
Definition ui.cpp:127
uint8_t s_cal_step
Definition ui.cpp:50
lv_obj_t * s_reset_btn
Definition ui.cpp:171
int16_t s_cal_raw[2][2]
Definition ui.cpp:51
const char * asset_network(pos_chain_t c)
Definition ui.cpp:204
int16_t s_cal_last_y
Definition ui.cpp:54
lv_obj_t * s_admin_ta
Definition ui.cpp:145
volatile ui_screen_t s_req_screen
Definition ui.cpp:34
const char * asset_name(pos_chain_t c)
Definition ui.cpp:194
uint16_t s_settings_tab
Definition ui.cpp:180
lv_obj_t * s_admin_eye_lbl
Definition ui.cpp:109
uint32_t s_admin_lock_ms
Definition ui.cpp:156
lv_obj_t * s_close_btn
Definition ui.cpp:172
lv_obj_t * s_admin_note_lbl
Definition ui.cpp:146
bool s_admin_for_portal
Definition ui.cpp:159
const char * s_addr_usdc
Definition ui.cpp:167
uint32_t s_cal_deadline
Definition ui.cpp:56
@ UI_SCREEN_TOUCH_CAL
Definition ui.h:51
@ UI_SCREEN_ADMIN_SET
Definition ui.h:46
@ UI_SCREEN_SETTINGS
Definition ui.h:43
@ UI_SCREEN_PROV
Definition ui.h:49
@ UI_SCREEN_WIFI_CONNECTING
Definition ui.h:42
@ UI_EVENT_ADMIN_SET
Definition ui.h:64
void build_admin_set(void)
Definition ui_admin.cpp:922
static uint32_t admin_lock_remaining_s(void)
Definition ui_admin.cpp:756
static bool cal_apply(void)
Definition ui_admin.cpp:320
void open_network_picker(void)
Definition ui_admin.cpp:656
#define ADMIN_PENALTY_MAX_S
Wait imposed after repeated wrong admin codes.
Definition ui_admin.cpp:738
static void settings_bottom_bar(uint16_t tab)
Definition ui_admin.cpp:38
bool s_portal_modal
Definition ui_admin.cpp:423
void build_admin_unlock(void)
Definition ui_admin.cpp:929
#define OTA_CARD_PAD
Definition ui_admin.cpp:474
static void admin_set_note(const char *msg)
Definition ui_admin.cpp:766
uint32_t admin_penalty_ms(uint8_t fails)
Definition ui_admin.cpp:739
void settings_persist(void)
Definition ui_admin.cpp:19
static void build_admin_screen(const char *title, bool allow_cancel, const char *hint)
Definition ui_admin.cpp:888
#define OTA_GAP
Definition ui_admin.cpp:473
void touch_cal_poll(void)
Definition ui_admin.cpp:397
static void wait_text(char *out, size_t n, uint32_t secs)
Definition ui_admin.cpp:750
static const BtnAction NET_ACT[POS_NET__COUNT]
Definition ui_admin.cpp:653
void close_modal(void)
Definition ui_admin.cpp:424
static void admin_kbd_cb(lv_event_t *e)
Definition ui_admin.cpp:872
void build_settings(void)
Definition ui_admin.cpp:64
void open_coin_picker(pos_net_t net)
Definition ui_admin.cpp:692
static void cal_target(lv_coord_t cx, lv_coord_t cy)
Definition ui_admin.cpp:302
lv_obj_t * open_modal(lv_coord_t w, lv_coord_t h)
Definition ui_admin.cpp:434
static void brightness_event_cb(lv_event_t *e)
Definition ui_admin.cpp:23
static lv_obj_t * s_modal
Definition ui_admin.cpp:418
static void tab_change_cb(lv_event_t *e)
Definition ui_admin.cpp:55
lv_obj_t * ota_text(lv_obj_t *card, lv_obj_t *above, const char *txt, lv_color_t col, const lv_font_t *font)
Definition ui_admin.cpp:477
static void pill_disable(lv_obj_t *p)
Definition ui_admin.cpp:642
#define PICK_W
Definition ui_admin.cpp:634
void build_ota_confirm(void)
Definition ui_admin.cpp:593
static void admin_submit(void)
Definition ui_admin.cpp:774
void ota_fit_card(lv_obj_t *card, lv_obj_t *last, lv_coord_t buttons_h)
Definition ui_admin.cpp:502
void build_touch_cal(void)
Definition ui_admin.cpp:331
void open_reset_confirm(void)
Definition ui_admin.cpp:457
void open_portal_window(void)
Definition ui_admin.cpp:510
void open_ota_gone(void)
Definition ui_admin.cpp:581
bool touch_raw(int16_t *rx, int16_t *ry)
Definition ui_hal.cpp:49
void backlight_set_pct(uint8_t pct)
Definition ui_hal.cpp:180
uint16_t s_cal_ymax
Definition ui_hal.cpp:42
uint8_t s_brightness
Definition ui_hal.cpp:179
uint16_t s_cal_xmin
Definition ui_hal.cpp:39
uint16_t s_cal_ymin
Definition ui_hal.cpp:41
uint16_t s_cal_xmax
Definition ui_hal.cpp:40
Private to the ui*.cpp files: the state and helpers they share.
lv_obj_t * make_glyph_disc(lv_obj_t *parent, const char *sym, lv_color_t bg, lv_coord_t sz)
@ PAY_STEP_NONE
lv_obj_t * build_page(int step, bool band=true)
#define ACT_BTN_Y
#define ADMIN_CODE_MIN_STORED
lv_obj_t * make_asset_badge(lv_obj_t *parent, pos_chain_t chain)
#define ADMIN_CODE_MIN
#define TAB_PAD
#define COL_DANGER
#define CODE_KBD_W
lv_obj_t * make_button(lv_obj_t *parent, const char *label, lv_color_t bg, lv_color_t fg, lv_coord_t w, lv_coord_t h, lv_align_t align, lv_coord_t x, lv_coord_t y, BtnAction act, const lv_font_t *font)
lv_obj_t * make_code_row(lv_obj_t *host, lv_obj_t *kb, uint32_t max_len, const char *hint, BtnAction eye_act, lv_obj_t **eye_lbl)
Definition ui_sale.cpp:615
lv_obj_t * make_icon_button(const char *sym, BtnAction act, lv_obj_t *parent=NULL)
#define OTA_CARD_W
#define CAL_INSET
lv_obj_t * make_net_badge(lv_obj_t *parent, pos_net_t net)
#define ADMIN_CODE_MAX
#define SCR_H
lv_obj_t * make_label(lv_obj_t *parent, const char *txt, lv_color_t color, const lv_font_t *font, lv_align_t align, lv_coord_t x, lv_coord_t y)
#define COL_SURFACE
#define COL_DIM
lv_obj_t * make_card(lv_obj_t *parent)
#define SCR_W
#define TAB_ABOUT
#define PILL_ICON_X
#define OTA_TEXT_W
#define COL_TEXT
#define COL_ACCENT
#define CAL_VERIFY_MS
#define ACT_BTN_H
lv_obj_t * make_numeric_keypad(lv_event_cb_t cb, lv_obj_t *parent=NULL, lv_coord_t w=CODE_KBD_W, lv_coord_t h=210)
Definition ui_sale.cpp:576
#define PILL_H
void paint_page(lv_obj_t *obj)
Lay the sale-flow chrome and return the white card to build into.
void build_header(const char *title)
#define ACT_CHAIN_OF(chain)
#define TAB_W
lv_obj_t * make_pill(lv_obj_t *parent, const char *title, const char *sub, lv_coord_t w, lv_coord_t y, BtnAction act, bool leaf=false)
void clear_screen(void)
BtnAction
@ ACT_OTA_NO
@ ACT_CAL_CANCEL
@ ACT_NET_PICK
@ ACT_ADMIN_REVEAL
@ ACT_MODAL_CLOSE
@ ACT_RESET
@ ACT_NET_POLY
@ ACT_NET_ETH
@ ACT_CAL_SAVE
@ ACT_RESET_CONFIRM
@ ACT_ADMIN_CANCEL
@ ACT_PORTAL
@ ACT_OTA_OK
@ ACT_TOUCH_CAL
@ ACT_CLOSE
@ ACT_PORTAL_CLOSE
@ ACT_NET_TRON
#define COIN_SZ
lv_obj_t * make_title(const char *txt, bool has_icon, lv_obj_t *parent=NULL)
Screen title, centred but never underneath the top-left icon button.
#define COL_BG
lv_obj_t * make_field(lv_obj_t *parent, const char *caption, const char *value, lv_color_t col=COL_TEXT)
#define SETTINGS_TAB_COUNT
#define COL_ACTION
#define COL_BORDER