696 lines
22 KiB
C
696 lines
22 KiB
C
#include "ui.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "display.h"
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "ili9341.h"
|
|
#include "lvgl.h"
|
|
#include "mnemonic.h"
|
|
#include "touch.h"
|
|
|
|
#define UI_TICK_MS 2
|
|
#define UI_DRAW_LINES 20
|
|
|
|
static const char *TAG = "ui_lvgl";
|
|
|
|
static bool s_lvgl_inited = false;
|
|
static lv_disp_draw_buf_t s_draw_buf;
|
|
static lv_color_t s_lv_draw_buf[DISPLAY_WIDTH * UI_DRAW_LINES];
|
|
static uint8_t s_flush_bytes[DISPLAY_WIDTH * UI_DRAW_LINES * 2];
|
|
static esp_timer_handle_t s_lv_tick_timer;
|
|
|
|
static bool s_done = false;
|
|
static bool s_have_mnemonic = false;
|
|
static char s_selected_mnemonic[256];
|
|
static lv_obj_t *s_status_label = NULL;
|
|
static lv_obj_t *s_entry_ta = NULL;
|
|
|
|
static ui_approval_decision_t s_approval_decision = UI_APPROVAL_TIMEOUT;
|
|
static lv_obj_t *s_approval_countdown = NULL;
|
|
static int64_t s_approval_deadline_us = 0;
|
|
|
|
#define UI_EVENT_LOG_MAX 8
|
|
#define UI_EVENT_LINE_MAX 96
|
|
static char s_event_lines[UI_EVENT_LOG_MAX][UI_EVENT_LINE_MAX];
|
|
static int s_event_count = 0;
|
|
|
|
static void style_screen(lv_obj_t *scr)
|
|
{
|
|
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_bg_color(scr, lv_color_hex(0x000000), 0);
|
|
}
|
|
|
|
static void style_button(lv_obj_t *btn)
|
|
{
|
|
lv_obj_set_style_radius(btn, 6, 0);
|
|
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_bg_color(btn, lv_color_hex(0x000000), 0);
|
|
lv_obj_set_style_border_width(btn, 2, 0);
|
|
lv_obj_set_style_border_color(btn, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_set_style_text_color(btn, lv_color_hex(0xFFFFFF), 0);
|
|
|
|
lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED);
|
|
lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_PRESSED);
|
|
lv_obj_set_style_border_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
|
|
lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
|
|
}
|
|
|
|
static void style_text_area(lv_obj_t *ta)
|
|
{
|
|
lv_obj_set_style_bg_opa(ta, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_bg_color(ta, lv_color_hex(0x000000), 0);
|
|
lv_obj_set_style_border_width(ta, 2, 0);
|
|
lv_obj_set_style_border_color(ta, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_set_style_text_color(ta, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_set_style_text_color(ta, lv_color_hex(0xFF0000), LV_PART_CURSOR);
|
|
}
|
|
|
|
static void style_keyboard(lv_obj_t *kb)
|
|
{
|
|
lv_obj_set_style_bg_opa(kb, LV_OPA_COVER, 0);
|
|
lv_obj_set_style_bg_color(kb, lv_color_hex(0x000000), 0);
|
|
lv_obj_set_style_border_width(kb, 1, 0);
|
|
lv_obj_set_style_border_color(kb, lv_color_hex(0xFFFFFF), 0);
|
|
|
|
lv_obj_set_style_bg_opa(kb, LV_OPA_COVER, LV_PART_ITEMS);
|
|
lv_obj_set_style_bg_color(kb, lv_color_hex(0x000000), LV_PART_ITEMS);
|
|
lv_obj_set_style_border_color(kb, lv_color_hex(0xFFFFFF), LV_PART_ITEMS);
|
|
lv_obj_set_style_border_width(kb, 1, LV_PART_ITEMS);
|
|
lv_obj_set_style_text_color(kb, lv_color_hex(0xFFFFFF), LV_PART_ITEMS);
|
|
|
|
lv_obj_set_style_border_color(kb, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED);
|
|
lv_obj_set_style_text_color(kb, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED);
|
|
}
|
|
|
|
static void lvgl_tick_cb(void *arg)
|
|
{
|
|
(void)arg;
|
|
lv_tick_inc(UI_TICK_MS);
|
|
}
|
|
|
|
static void lvgl_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
|
|
{
|
|
const uint16_t w = (uint16_t)(area->x2 - area->x1 + 1);
|
|
const uint16_t h = (uint16_t)(area->y2 - area->y1 + 1);
|
|
const uint32_t px_count = (uint32_t)w * h;
|
|
|
|
if (px_count > (uint32_t)(DISPLAY_WIDTH * UI_DRAW_LINES)) {
|
|
lv_disp_flush_ready(disp_drv);
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < px_count; ++i) {
|
|
uint16_t c = color_p[i].full;
|
|
s_flush_bytes[i * 2 + 0] = (uint8_t)(c >> 8);
|
|
s_flush_bytes[i * 2 + 1] = (uint8_t)(c & 0xFF);
|
|
}
|
|
|
|
(void)ili9341_blit_rgb565((uint16_t)area->x1,
|
|
(uint16_t)area->y1,
|
|
w,
|
|
h,
|
|
s_flush_bytes);
|
|
|
|
lv_disp_flush_ready(disp_drv);
|
|
}
|
|
|
|
static void lvgl_touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
|
|
{
|
|
(void)drv;
|
|
|
|
int sx = 0;
|
|
int sy = 0;
|
|
uint16_t pressure = 0;
|
|
|
|
if (!touch_read_screen(&sx, &sy, &pressure)) {
|
|
data->state = LV_INDEV_STATE_RELEASED;
|
|
return;
|
|
}
|
|
|
|
data->state = LV_INDEV_STATE_PRESSED;
|
|
data->point.x = sx;
|
|
data->point.y = sy;
|
|
}
|
|
|
|
static esp_err_t ui_lvgl_init_once(void)
|
|
{
|
|
if (s_lvgl_inited) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
ESP_ERROR_CHECK(touch_init());
|
|
|
|
lv_init();
|
|
|
|
lv_disp_draw_buf_init(&s_draw_buf, s_lv_draw_buf, NULL, DISPLAY_WIDTH * UI_DRAW_LINES);
|
|
|
|
static lv_disp_drv_t disp_drv;
|
|
lv_disp_drv_init(&disp_drv);
|
|
disp_drv.hor_res = DISPLAY_WIDTH;
|
|
disp_drv.ver_res = DISPLAY_HEIGHT;
|
|
disp_drv.flush_cb = lvgl_flush_cb;
|
|
disp_drv.draw_buf = &s_draw_buf;
|
|
lv_disp_drv_register(&disp_drv);
|
|
|
|
static lv_indev_drv_t indev_drv;
|
|
lv_indev_drv_init(&indev_drv);
|
|
indev_drv.type = LV_INDEV_TYPE_POINTER;
|
|
indev_drv.read_cb = lvgl_touch_read_cb;
|
|
lv_indev_drv_register(&indev_drv);
|
|
|
|
const esp_timer_create_args_t lv_tick_timer_args = {
|
|
.callback = lvgl_tick_cb,
|
|
.name = "lv_tick",
|
|
};
|
|
ESP_ERROR_CHECK(esp_timer_create(&lv_tick_timer_args, &s_lv_tick_timer));
|
|
ESP_ERROR_CHECK(esp_timer_start_periodic(s_lv_tick_timer, UI_TICK_MS * 1000));
|
|
|
|
s_lvgl_inited = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void set_status(const char *text)
|
|
{
|
|
if (s_status_label != NULL) {
|
|
lv_label_set_text(s_status_label, text);
|
|
}
|
|
}
|
|
|
|
static void on_generate(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
|
|
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
|
|
if (generate_mnemonic_12(s_selected_mnemonic, sizeof(s_selected_mnemonic)) != 0) {
|
|
set_status("Failed to generate mnemonic");
|
|
return;
|
|
}
|
|
|
|
s_have_mnemonic = true;
|
|
s_done = true;
|
|
}
|
|
|
|
static void build_main_menu(void);
|
|
|
|
static void on_back_to_menu(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
build_main_menu();
|
|
}
|
|
|
|
static void on_use_entry(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
|
|
if (s_entry_ta == NULL) {
|
|
return;
|
|
}
|
|
|
|
const char *txt = lv_textarea_get_text(s_entry_ta);
|
|
if (txt == NULL || txt[0] == '\0') {
|
|
set_status("Mnemonic is empty");
|
|
return;
|
|
}
|
|
|
|
if (mnemonic_validate(txt) != 0) {
|
|
set_status("Invalid mnemonic");
|
|
return;
|
|
}
|
|
|
|
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
|
|
strncpy(s_selected_mnemonic, txt, sizeof(s_selected_mnemonic) - 1);
|
|
s_have_mnemonic = true;
|
|
s_done = true;
|
|
}
|
|
|
|
static void build_entry_screen(void)
|
|
{
|
|
lv_obj_t *scr = lv_scr_act();
|
|
lv_obj_clean(scr);
|
|
style_screen(scr);
|
|
|
|
lv_obj_t *title = lv_label_create(scr);
|
|
lv_label_set_text(title, "Enter Mnemonic");
|
|
lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 6);
|
|
|
|
s_entry_ta = lv_textarea_create(scr);
|
|
lv_obj_set_size(s_entry_ta, DISPLAY_WIDTH - 16, 70);
|
|
lv_obj_align(s_entry_ta, LV_ALIGN_TOP_MID, 0, 32);
|
|
lv_textarea_set_placeholder_text(s_entry_ta, "12 words separated by spaces");
|
|
lv_textarea_set_one_line(s_entry_ta, false);
|
|
style_text_area(s_entry_ta);
|
|
|
|
lv_obj_t *kb = lv_keyboard_create(scr);
|
|
lv_obj_set_size(kb, DISPLAY_WIDTH, 110);
|
|
lv_obj_align(kb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
|
lv_keyboard_set_textarea(kb, s_entry_ta);
|
|
style_keyboard(kb);
|
|
|
|
lv_obj_t *use_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(use_btn, 150, 30);
|
|
lv_obj_align(use_btn, LV_ALIGN_TOP_LEFT, 8, 110);
|
|
lv_obj_add_event_cb(use_btn, on_use_entry, LV_EVENT_RELEASED, NULL);
|
|
style_button(use_btn);
|
|
lv_obj_t *use_lbl = lv_label_create(use_btn);
|
|
lv_label_set_text(use_lbl, "Use Mnemonic");
|
|
lv_obj_center(use_lbl);
|
|
|
|
lv_obj_t *back_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(back_btn, 150, 30);
|
|
lv_obj_align(back_btn, LV_ALIGN_TOP_RIGHT, -8, 110);
|
|
lv_obj_add_event_cb(back_btn, on_back_to_menu, LV_EVENT_RELEASED, NULL);
|
|
style_button(back_btn);
|
|
lv_obj_t *back_lbl = lv_label_create(back_btn);
|
|
lv_label_set_text(back_lbl, "Back");
|
|
lv_obj_center(back_lbl);
|
|
|
|
s_status_label = lv_label_create(scr);
|
|
lv_label_set_text(s_status_label, "");
|
|
lv_obj_set_style_text_color(s_status_label, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(s_status_label, LV_ALIGN_TOP_LEFT, 8, 144);
|
|
}
|
|
|
|
static void on_enter(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
build_entry_screen();
|
|
}
|
|
|
|
static void on_approve_deny(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
s_approval_decision = UI_APPROVAL_DENY;
|
|
}
|
|
|
|
static void on_approve_once(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
s_approval_decision = UI_APPROVAL_ONCE;
|
|
}
|
|
|
|
static void on_approve_always(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
s_approval_decision = UI_APPROVAL_ALWAYS;
|
|
}
|
|
|
|
static void on_use_viewed(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
if (!s_have_mnemonic) {
|
|
set_status("No generated mnemonic yet");
|
|
return;
|
|
}
|
|
s_done = true;
|
|
}
|
|
|
|
static void build_view_screen(void)
|
|
{
|
|
lv_obj_t *scr = lv_scr_act();
|
|
lv_obj_clean(scr);
|
|
style_screen(scr);
|
|
|
|
lv_obj_t *title = lv_label_create(scr);
|
|
lv_label_set_text(title, "Mnemonic Preview");
|
|
lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 6);
|
|
|
|
lv_obj_t *mn = lv_label_create(scr);
|
|
lv_obj_set_width(mn, DISPLAY_WIDTH - 20);
|
|
lv_label_set_long_mode(mn, LV_LABEL_LONG_WRAP);
|
|
lv_label_set_text(mn, s_have_mnemonic ? s_selected_mnemonic : "No mnemonic generated yet");
|
|
lv_obj_align(mn, LV_ALIGN_TOP_LEFT, 10, 40);
|
|
lv_obj_set_style_text_color(mn, lv_color_hex(0xFFFFFF), 0);
|
|
|
|
lv_obj_t *use_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(use_btn, 150, 36);
|
|
lv_obj_align(use_btn, LV_ALIGN_BOTTOM_LEFT, 8, -8);
|
|
lv_obj_add_event_cb(use_btn, on_use_viewed, LV_EVENT_RELEASED, NULL);
|
|
style_button(use_btn);
|
|
lv_obj_t *use_lbl = lv_label_create(use_btn);
|
|
lv_label_set_text(use_lbl, "Use This");
|
|
lv_obj_center(use_lbl);
|
|
|
|
lv_obj_t *back_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(back_btn, 150, 36);
|
|
lv_obj_align(back_btn, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
|
|
lv_obj_add_event_cb(back_btn, on_back_to_menu, LV_EVENT_RELEASED, NULL);
|
|
style_button(back_btn);
|
|
lv_obj_t *back_lbl = lv_label_create(back_btn);
|
|
lv_label_set_text(back_lbl, "Back");
|
|
lv_obj_center(back_lbl);
|
|
|
|
s_status_label = NULL;
|
|
}
|
|
|
|
static void on_view(lv_event_t *e)
|
|
{
|
|
(void)e;
|
|
|
|
if (!s_have_mnemonic) {
|
|
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
|
|
if (generate_mnemonic_12(s_selected_mnemonic, sizeof(s_selected_mnemonic)) == 0) {
|
|
s_have_mnemonic = true;
|
|
}
|
|
}
|
|
|
|
build_view_screen();
|
|
}
|
|
|
|
static void build_main_menu(void)
|
|
{
|
|
lv_obj_t *scr = lv_scr_act();
|
|
lv_obj_clean(scr);
|
|
style_screen(scr);
|
|
|
|
lv_obj_t *title = lv_label_create(scr);
|
|
lv_label_set_text(title, "n_signer CYD");
|
|
lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
|
|
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
|
|
|
|
lv_obj_t *subtitle = lv_label_create(scr);
|
|
lv_label_set_text(subtitle, "Select startup mode");
|
|
lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 36);
|
|
lv_obj_set_style_text_color(subtitle, lv_color_hex(0xFFFFFF), 0);
|
|
|
|
lv_obj_t *btn_gen = lv_btn_create(scr);
|
|
lv_obj_set_size(btn_gen, DISPLAY_WIDTH - 40, 42);
|
|
lv_obj_align(btn_gen, LV_ALIGN_TOP_MID, 0, 64);
|
|
lv_obj_add_event_cb(btn_gen, on_generate, LV_EVENT_RELEASED, NULL);
|
|
style_button(btn_gen);
|
|
lv_obj_t *lbl_gen = lv_label_create(btn_gen);
|
|
lv_label_set_text(lbl_gen, "Generate New Mnemonic");
|
|
lv_obj_center(lbl_gen);
|
|
|
|
lv_obj_t *btn_enter = lv_btn_create(scr);
|
|
lv_obj_set_size(btn_enter, DISPLAY_WIDTH - 40, 42);
|
|
lv_obj_align(btn_enter, LV_ALIGN_TOP_MID, 0, 112);
|
|
lv_obj_add_event_cb(btn_enter, on_enter, LV_EVENT_RELEASED, NULL);
|
|
style_button(btn_enter);
|
|
lv_obj_t *lbl_enter = lv_label_create(btn_enter);
|
|
lv_label_set_text(lbl_enter, "Enter Existing Mnemonic");
|
|
lv_obj_center(lbl_enter);
|
|
|
|
lv_obj_t *btn_view = lv_btn_create(scr);
|
|
lv_obj_set_size(btn_view, DISPLAY_WIDTH - 40, 42);
|
|
lv_obj_align(btn_view, LV_ALIGN_TOP_MID, 0, 160);
|
|
lv_obj_add_event_cb(btn_view, on_view, LV_EVENT_RELEASED, NULL);
|
|
style_button(btn_view);
|
|
lv_obj_t *lbl_view = lv_label_create(btn_view);
|
|
lv_label_set_text(lbl_view, "View Current Mnemonic");
|
|
lv_obj_center(lbl_view);
|
|
|
|
s_status_label = lv_label_create(scr);
|
|
lv_label_set_text(s_status_label, "");
|
|
lv_obj_set_style_text_color(s_status_label, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(s_status_label, LV_ALIGN_BOTTOM_MID, 0, -10);
|
|
}
|
|
|
|
void ui_show_approval_prompt(const char *caller_pubkey_hex, int kind, const char *content, uint32_t timeout_ms)
|
|
{
|
|
if (!s_lvgl_inited) {
|
|
return;
|
|
}
|
|
|
|
lv_obj_t *scr = lv_scr_act();
|
|
lv_obj_clean(scr);
|
|
style_screen(scr);
|
|
|
|
char caller_line[64];
|
|
char kind_line[32];
|
|
char content_line[96];
|
|
|
|
if (caller_pubkey_hex != NULL && strlen(caller_pubkey_hex) >= 12) {
|
|
snprintf(caller_line, sizeof(caller_line), "from %.6s..%.4s",
|
|
caller_pubkey_hex, caller_pubkey_hex + strlen(caller_pubkey_hex) - 4);
|
|
} else {
|
|
snprintf(caller_line, sizeof(caller_line), "from %s",
|
|
caller_pubkey_hex ? caller_pubkey_hex : "anon");
|
|
}
|
|
|
|
snprintf(kind_line, sizeof(kind_line), "kind: %d", kind);
|
|
snprintf(content_line, sizeof(content_line), "%.80s", content ? content : "");
|
|
|
|
lv_obj_t *title = lv_label_create(scr);
|
|
lv_label_set_text(title, "Approve sign_event?");
|
|
lv_obj_set_style_text_color(title, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
|
|
|
|
lv_obj_t *caller_lbl = lv_label_create(scr);
|
|
lv_label_set_text(caller_lbl, caller_line);
|
|
lv_obj_set_style_text_color(caller_lbl, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(caller_lbl, LV_ALIGN_TOP_LEFT, 8, 34);
|
|
|
|
lv_obj_t *kind_lbl = lv_label_create(scr);
|
|
lv_label_set_text(kind_lbl, kind_line);
|
|
lv_obj_set_style_text_color(kind_lbl, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(kind_lbl, LV_ALIGN_TOP_LEFT, 8, 52);
|
|
|
|
lv_obj_t *content_lbl = lv_label_create(scr);
|
|
lv_obj_set_width(content_lbl, DISPLAY_WIDTH - 16);
|
|
lv_label_set_long_mode(content_lbl, LV_LABEL_LONG_WRAP);
|
|
lv_label_set_text(content_lbl, content_line);
|
|
lv_obj_set_style_text_color(content_lbl, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(content_lbl, LV_ALIGN_TOP_LEFT, 8, 72);
|
|
|
|
lv_obj_t *deny_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(deny_btn, 98, 34);
|
|
lv_obj_align(deny_btn, LV_ALIGN_BOTTOM_LEFT, 8, -8);
|
|
lv_obj_add_event_cb(deny_btn, on_approve_deny, LV_EVENT_RELEASED, NULL);
|
|
style_button(deny_btn);
|
|
lv_obj_t *deny_lbl = lv_label_create(deny_btn);
|
|
lv_label_set_text(deny_lbl, "Deny");
|
|
lv_obj_center(deny_lbl);
|
|
|
|
lv_obj_t *once_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(once_btn, 98, 34);
|
|
lv_obj_align(once_btn, LV_ALIGN_BOTTOM_MID, 0, -8);
|
|
lv_obj_add_event_cb(once_btn, on_approve_once, LV_EVENT_RELEASED, NULL);
|
|
style_button(once_btn);
|
|
lv_obj_t *once_lbl = lv_label_create(once_btn);
|
|
lv_label_set_text(once_lbl, "Approve");
|
|
lv_obj_center(once_lbl);
|
|
|
|
lv_obj_t *always_btn = lv_btn_create(scr);
|
|
lv_obj_set_size(always_btn, 98, 34);
|
|
lv_obj_align(always_btn, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
|
|
lv_obj_add_event_cb(always_btn, on_approve_always, LV_EVENT_RELEASED, NULL);
|
|
style_button(always_btn);
|
|
lv_obj_t *always_lbl = lv_label_create(always_btn);
|
|
lv_label_set_text(always_lbl, "Always");
|
|
lv_obj_center(always_lbl);
|
|
|
|
s_approval_countdown = lv_label_create(scr);
|
|
lv_obj_set_style_text_color(s_approval_countdown, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(s_approval_countdown, LV_ALIGN_BOTTOM_MID, 0, -46);
|
|
|
|
s_approval_decision = UI_APPROVAL_TIMEOUT;
|
|
s_approval_deadline_us = esp_timer_get_time() + ((int64_t)timeout_ms * 1000);
|
|
}
|
|
|
|
ui_approval_decision_t ui_wait_for_approval(uint32_t timeout_ms)
|
|
{
|
|
if (!s_lvgl_inited) {
|
|
return UI_APPROVAL_TIMEOUT;
|
|
}
|
|
|
|
int64_t deadline_us = esp_timer_get_time() + ((int64_t)timeout_ms * 1000);
|
|
|
|
while (1) {
|
|
int64_t now = esp_timer_get_time();
|
|
if (s_approval_decision != UI_APPROVAL_TIMEOUT) {
|
|
return s_approval_decision;
|
|
}
|
|
|
|
if (now >= deadline_us) {
|
|
return UI_APPROVAL_TIMEOUT;
|
|
}
|
|
|
|
if (s_approval_countdown != NULL) {
|
|
int remain_s = (int)((deadline_us - now + 999999) / 1000000);
|
|
if (remain_s < 0) {
|
|
remain_s = 0;
|
|
}
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "timeout: %ds", remain_s);
|
|
lv_label_set_text(s_approval_countdown, buf);
|
|
}
|
|
|
|
lv_timer_handler();
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
}
|
|
}
|
|
|
|
static void show_event_log_screen(void)
|
|
{
|
|
lv_obj_t *scr = lv_scr_act();
|
|
lv_obj_clean(scr);
|
|
style_screen(scr);
|
|
|
|
lv_obj_t *title = lv_label_create(scr);
|
|
lv_label_set_text(title, "recent events");
|
|
lv_obj_set_style_text_color(title, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 6);
|
|
|
|
lv_obj_t *sub = lv_label_create(scr);
|
|
lv_label_set_text(sub, "newest first");
|
|
lv_obj_set_style_text_color(sub, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(sub, LV_ALIGN_TOP_RIGHT, -8, 8);
|
|
|
|
int y = 30;
|
|
for (int i = 0; i < s_event_count; ++i) {
|
|
lv_obj_t *line = lv_label_create(scr);
|
|
lv_obj_set_width(line, DISPLAY_WIDTH - 16);
|
|
lv_label_set_long_mode(line, LV_LABEL_LONG_DOT);
|
|
lv_label_set_text(line, s_event_lines[i]);
|
|
lv_obj_set_style_text_color(line, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(line, LV_ALIGN_TOP_LEFT, 8, y);
|
|
y += 24;
|
|
}
|
|
|
|
if (s_event_count == 0) {
|
|
lv_obj_t *empty = lv_label_create(scr);
|
|
lv_label_set_text(empty, "No events yet");
|
|
lv_obj_set_style_text_color(empty, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(empty, LV_ALIGN_TOP_LEFT, 8, 36);
|
|
}
|
|
|
|
lv_obj_t *ready = lv_label_create(scr);
|
|
lv_label_set_text(ready, "ready");
|
|
lv_obj_set_style_text_color(ready, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(ready, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
|
|
}
|
|
|
|
void ui_show_idle(const char *npub_full, const char *mnemonic_full, const char *version_text)
|
|
{
|
|
if (!s_lvgl_inited) {
|
|
return;
|
|
}
|
|
|
|
lv_obj_t *scr = lv_scr_act();
|
|
lv_obj_clean(scr);
|
|
style_screen(scr);
|
|
|
|
lv_obj_t *title = lv_label_create(scr);
|
|
lv_label_set_text(title, "nostr npub");
|
|
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 6);
|
|
|
|
lv_obj_t *npub = lv_label_create(scr);
|
|
lv_obj_set_width(npub, DISPLAY_WIDTH - 16);
|
|
lv_label_set_long_mode(npub, LV_LABEL_LONG_WRAP);
|
|
lv_label_set_text(npub, npub_full ? npub_full : "");
|
|
lv_obj_set_style_text_color(npub, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(npub, LV_ALIGN_TOP_LEFT, 8, 24);
|
|
|
|
lv_obj_t *mn_title = lv_label_create(scr);
|
|
lv_label_set_text(mn_title, "mnemonic");
|
|
lv_obj_set_style_text_color(mn_title, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align_to(mn_title, npub, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 8);
|
|
|
|
lv_obj_t *mn = lv_label_create(scr);
|
|
lv_obj_set_width(mn, DISPLAY_WIDTH - 16);
|
|
lv_label_set_long_mode(mn, LV_LABEL_LONG_WRAP);
|
|
lv_label_set_text(mn, mnemonic_full ? mnemonic_full : "");
|
|
lv_obj_set_style_text_color(mn, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align_to(mn, mn_title, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 4);
|
|
|
|
lv_obj_t *ver = lv_label_create(scr);
|
|
lv_label_set_text(ver, version_text ? version_text : "");
|
|
lv_obj_set_style_text_color(ver, lv_color_hex(0xFFFFFF), 0);
|
|
lv_obj_align(ver, LV_ALIGN_BOTTOM_LEFT, 8, -8);
|
|
|
|
lv_obj_t *ready = lv_label_create(scr);
|
|
lv_label_set_text(ready, "ready");
|
|
lv_obj_set_style_text_color(ready, lv_color_hex(0xFF0000), 0);
|
|
lv_obj_align(ready, LV_ALIGN_BOTTOM_RIGHT, -8, -8);
|
|
}
|
|
|
|
void ui_add_event(const char *action, const char *caller_pubkey_hex, const char *extra)
|
|
{
|
|
char caller_short[20] = "anon";
|
|
char line[UI_EVENT_LINE_MAX];
|
|
|
|
if (!s_lvgl_inited) {
|
|
return;
|
|
}
|
|
|
|
if (caller_pubkey_hex != NULL && strlen(caller_pubkey_hex) >= 12) {
|
|
snprintf(caller_short, sizeof(caller_short), "%.6s..%.4s",
|
|
caller_pubkey_hex, caller_pubkey_hex + strlen(caller_pubkey_hex) - 4);
|
|
} else if (caller_pubkey_hex != NULL && caller_pubkey_hex[0] != '\0') {
|
|
snprintf(caller_short, sizeof(caller_short), "%.16s", caller_pubkey_hex);
|
|
}
|
|
|
|
snprintf(line, sizeof(line), "%s %s %s",
|
|
action ? action : "event",
|
|
caller_short,
|
|
extra ? extra : "");
|
|
|
|
for (int i = (s_event_count < UI_EVENT_LOG_MAX ? s_event_count : UI_EVENT_LOG_MAX - 1); i > 0; --i) {
|
|
strncpy(s_event_lines[i], s_event_lines[i - 1], UI_EVENT_LINE_MAX - 1);
|
|
s_event_lines[i][UI_EVENT_LINE_MAX - 1] = '\0';
|
|
}
|
|
|
|
strncpy(s_event_lines[0], line, UI_EVENT_LINE_MAX - 1);
|
|
s_event_lines[0][UI_EVENT_LINE_MAX - 1] = '\0';
|
|
|
|
if (s_event_count < UI_EVENT_LOG_MAX) {
|
|
s_event_count++;
|
|
}
|
|
|
|
show_event_log_screen();
|
|
}
|
|
|
|
void ui_pump(void)
|
|
{
|
|
if (!s_lvgl_inited) {
|
|
return;
|
|
}
|
|
lv_timer_handler();
|
|
}
|
|
|
|
int ui_init_runtime(void)
|
|
{
|
|
return (ui_lvgl_init_once() == ESP_OK) ? 0 : -1;
|
|
}
|
|
|
|
int ui_run_until_working(char out_mnemonic[256])
|
|
{
|
|
if (out_mnemonic == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (ui_lvgl_init_once() != ESP_OK) {
|
|
return -1;
|
|
}
|
|
|
|
s_done = false;
|
|
s_have_mnemonic = false;
|
|
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
|
|
|
|
build_main_menu();
|
|
|
|
while (!s_done) {
|
|
lv_timer_handler();
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
}
|
|
|
|
memset(out_mnemonic, 0, 256);
|
|
strncpy(out_mnemonic, s_selected_mnemonic, 255);
|
|
|
|
ESP_LOGI(TAG, "Mnemonic selected via LVGL UI");
|
|
return 0;
|
|
}
|