v0.0.31 - Harden LVGL screen transitions with widget pointer resets and null guards
This commit is contained in:
@@ -277,6 +277,72 @@ size_t mnemonic_words_for_letter(char letter, size_t *out_start)
|
||||
return s_letter_count[idx];
|
||||
}
|
||||
|
||||
size_t mnemonic_words_with_prefix(const char *prefix, uint16_t *out_indices, size_t max_out)
|
||||
{
|
||||
size_t start = 0;
|
||||
size_t count = 0;
|
||||
size_t written = 0;
|
||||
|
||||
if (prefix == NULL || prefix[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
count = mnemonic_words_for_letter(prefix[0], &start);
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
size_t idx = start + i;
|
||||
const char *word = BIP39_WORDLIST[idx];
|
||||
if (strncmp(word, prefix, strlen(prefix)) == 0) {
|
||||
if (out_indices != NULL && written < max_out) {
|
||||
out_indices[written] = (uint16_t)idx;
|
||||
}
|
||||
written++;
|
||||
}
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
bool mnemonic_letter_extends_prefix(const char *prefix, size_t prefix_len, char next_letter)
|
||||
{
|
||||
char probe[16];
|
||||
size_t start = 0;
|
||||
size_t count = 0;
|
||||
|
||||
if (next_letter >= 'A' && next_letter <= 'Z') {
|
||||
next_letter = (char)(next_letter - 'A' + 'a');
|
||||
}
|
||||
|
||||
if (next_letter < 'a' || next_letter > 'z') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prefix == NULL || prefix_len >= sizeof(probe) - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(probe, prefix, prefix_len);
|
||||
probe[prefix_len] = next_letter;
|
||||
probe[prefix_len + 1] = '\0';
|
||||
|
||||
count = mnemonic_words_for_letter(probe[0], &start);
|
||||
if (count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const char *word = BIP39_WORDLIST[start + i];
|
||||
if (strncmp(word, probe, prefix_len + 1) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *mnemonic_word_at(size_t index)
|
||||
{
|
||||
if (index >= 2048) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -7,4 +8,6 @@ int generate_mnemonic_12(char *out, size_t out_len);
|
||||
int mnemonic_to_seed(const char *mnemonic, uint8_t seed[64]);
|
||||
int mnemonic_validate(const char *mnemonic);
|
||||
size_t mnemonic_words_for_letter(char letter, size_t *out_start);
|
||||
size_t mnemonic_words_with_prefix(const char *prefix, uint16_t *out_indices, size_t max_out);
|
||||
bool mnemonic_letter_extends_prefix(const char *prefix, size_t prefix_len, char next_letter);
|
||||
const char *mnemonic_word_at(size_t index);
|
||||
|
||||
@@ -13,10 +13,15 @@
|
||||
#include "ili9341.h"
|
||||
#include "lvgl.h"
|
||||
#include "mnemonic.h"
|
||||
#include "secure_mem.h"
|
||||
#include "touch.h"
|
||||
|
||||
#define UI_TICK_MS 2
|
||||
#define UI_DRAW_LINES 20
|
||||
#define UI_MNEMONIC_WORDS 12
|
||||
#define UI_BIP39_WORD_MAX 9
|
||||
#define UI_SUGGESTION_STORE_MAX 64
|
||||
#define UI_SUGGESTION_VISIBLE_MAX 32
|
||||
|
||||
static const char *TAG = "ui_lvgl";
|
||||
|
||||
@@ -30,7 +35,21 @@ 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 char s_committed_words[UI_MNEMONIC_WORDS][UI_BIP39_WORD_MAX];
|
||||
static size_t s_word_count = 0;
|
||||
static char s_prefix[UI_BIP39_WORD_MAX];
|
||||
static size_t s_prefix_len = 0;
|
||||
static bool s_checksum_override = false;
|
||||
|
||||
static lv_obj_t *s_entry_words_label = NULL;
|
||||
static lv_obj_t *s_entry_prefix_label = NULL;
|
||||
static lv_obj_t *s_suggestion_row = NULL;
|
||||
static lv_obj_t *s_key_matrix = NULL;
|
||||
static lv_obj_t *s_checksum_panel = NULL;
|
||||
|
||||
static uint16_t s_match_indices[UI_SUGGESTION_STORE_MAX];
|
||||
static size_t s_match_total = 0;
|
||||
|
||||
static ui_approval_decision_t s_approval_decision = UI_APPROVAL_TIMEOUT;
|
||||
static lv_obj_t *s_approval_countdown = NULL;
|
||||
@@ -41,6 +60,18 @@ static int64_t s_approval_deadline_us = 0;
|
||||
static char s_event_lines[UI_EVENT_LOG_MAX][UI_EVENT_LINE_MAX];
|
||||
static int s_event_count = 0;
|
||||
|
||||
static void reset_screen_widget_refs(void)
|
||||
{
|
||||
s_status_label = NULL;
|
||||
s_entry_words_label = NULL;
|
||||
s_entry_prefix_label = NULL;
|
||||
s_suggestion_row = NULL;
|
||||
s_key_matrix = NULL;
|
||||
s_checksum_panel = NULL;
|
||||
s_approval_countdown = NULL;
|
||||
s_match_total = 0;
|
||||
}
|
||||
|
||||
static void style_screen(lv_obj_t *scr)
|
||||
{
|
||||
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
|
||||
@@ -62,31 +93,24 @@ static void style_button(lv_obj_t *btn)
|
||||
lv_obj_set_style_text_color(btn, lv_color_hex(0xFF0000), LV_STATE_FOCUSED);
|
||||
}
|
||||
|
||||
static void style_text_area(lv_obj_t *ta)
|
||||
static void style_key_matrix(lv_obj_t *km)
|
||||
{
|
||||
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);
|
||||
}
|
||||
lv_obj_set_style_bg_opa(km, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_bg_color(km, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_border_width(km, 1, 0);
|
||||
lv_obj_set_style_border_color(km, lv_color_hex(0xFFFFFF), 0);
|
||||
|
||||
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(km, LV_OPA_COVER, LV_PART_ITEMS);
|
||||
lv_obj_set_style_bg_color(km, lv_color_hex(0x000000), LV_PART_ITEMS);
|
||||
lv_obj_set_style_border_color(km, lv_color_hex(0xFFFFFF), LV_PART_ITEMS);
|
||||
lv_obj_set_style_border_width(km, 1, LV_PART_ITEMS);
|
||||
lv_obj_set_style_text_color(km, lv_color_hex(0xFFFFFF), LV_PART_ITEMS);
|
||||
|
||||
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(km, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED);
|
||||
lv_obj_set_style_text_color(km, lv_color_hex(0xFF0000), LV_PART_ITEMS | LV_STATE_PRESSED);
|
||||
|
||||
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);
|
||||
lv_obj_set_style_text_color(km, lv_color_hex(0x505050), LV_PART_ITEMS | LV_STATE_DISABLED);
|
||||
lv_obj_set_style_border_color(km, lv_color_hex(0x303030), LV_PART_ITEMS | LV_STATE_DISABLED);
|
||||
}
|
||||
|
||||
static void lvgl_tick_cb(void *arg)
|
||||
@@ -198,85 +222,443 @@ static void on_generate(lv_event_t *e)
|
||||
}
|
||||
|
||||
static void build_main_menu(void);
|
||||
static void refresh_entry_ui(void);
|
||||
static bool verify_or_prompt_checksum(void);
|
||||
|
||||
static void on_back_to_menu(lv_event_t *e)
|
||||
static const char *s_key_map[] = {
|
||||
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
|
||||
"a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
|
||||
"z", "x", "c", "v", "b", "n", "m", "<", "",
|
||||
};
|
||||
|
||||
static void clear_entry_state(void)
|
||||
{
|
||||
(void)e;
|
||||
build_main_menu();
|
||||
secure_memzero(s_committed_words, sizeof(s_committed_words));
|
||||
secure_memzero(s_prefix, sizeof(s_prefix));
|
||||
s_word_count = 0;
|
||||
s_prefix_len = 0;
|
||||
s_checksum_override = false;
|
||||
s_match_total = 0;
|
||||
}
|
||||
|
||||
static void on_use_entry(lv_event_t *e)
|
||||
static bool join_committed_words(char *out, size_t out_len)
|
||||
{
|
||||
size_t cursor = 0;
|
||||
|
||||
if (out == NULL || out_len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out[0] = '\0';
|
||||
|
||||
for (size_t i = 0; i < s_word_count; ++i) {
|
||||
size_t wl = strlen(s_committed_words[i]);
|
||||
|
||||
if (cursor != 0) {
|
||||
if (cursor + 1 >= out_len) {
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
out[cursor++] = ' ';
|
||||
}
|
||||
|
||||
if (cursor + wl >= out_len) {
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(out + cursor, s_committed_words[i], wl);
|
||||
cursor += wl;
|
||||
out[cursor] = '\0';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void commit_word(const char *word)
|
||||
{
|
||||
if (word == NULL || s_word_count >= UI_MNEMONIC_WORDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(s_committed_words[s_word_count], 0, UI_BIP39_WORD_MAX);
|
||||
strncpy(s_committed_words[s_word_count], word, UI_BIP39_WORD_MAX - 1);
|
||||
s_word_count++;
|
||||
|
||||
secure_memzero(s_prefix, sizeof(s_prefix));
|
||||
s_prefix_len = 0;
|
||||
s_checksum_override = false;
|
||||
}
|
||||
|
||||
static void on_checksum_use_anyway(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");
|
||||
char joined[256];
|
||||
memset(joined, 0, sizeof(joined));
|
||||
if (!join_committed_words(joined, sizeof(joined))) {
|
||||
set_status("Mnemonic buffer error");
|
||||
secure_memzero(joined, sizeof(joined));
|
||||
return;
|
||||
}
|
||||
|
||||
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
|
||||
strncpy(s_selected_mnemonic, txt, sizeof(s_selected_mnemonic) - 1);
|
||||
strncpy(s_selected_mnemonic, joined, sizeof(s_selected_mnemonic) - 1);
|
||||
s_have_mnemonic = true;
|
||||
s_done = true;
|
||||
s_checksum_override = true;
|
||||
secure_memzero(joined, sizeof(joined));
|
||||
}
|
||||
|
||||
static void on_checksum_edit_words(lv_event_t *e)
|
||||
{
|
||||
(void)e;
|
||||
|
||||
if (s_checksum_panel != NULL) {
|
||||
lv_obj_del(s_checksum_panel);
|
||||
s_checksum_panel = NULL;
|
||||
}
|
||||
set_status("Checksum mismatch: edit or use anyway");
|
||||
refresh_entry_ui();
|
||||
}
|
||||
|
||||
static bool verify_or_prompt_checksum(void)
|
||||
{
|
||||
char joined[256];
|
||||
|
||||
if (s_key_matrix == NULL || s_entry_words_label == NULL || s_entry_prefix_label == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s_word_count != UI_MNEMONIC_WORDS || s_prefix_len != 0) {
|
||||
set_status("Need exactly 12 complete words");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(joined, 0, sizeof(joined));
|
||||
if (!join_committed_words(joined, sizeof(joined))) {
|
||||
set_status("Mnemonic buffer error");
|
||||
secure_memzero(joined, sizeof(joined));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mnemonic_validate(joined) == 0) {
|
||||
if (s_checksum_panel != NULL) {
|
||||
lv_obj_del(s_checksum_panel);
|
||||
s_checksum_panel = NULL;
|
||||
}
|
||||
set_status("Checksum valid");
|
||||
memset(s_selected_mnemonic, 0, sizeof(s_selected_mnemonic));
|
||||
strncpy(s_selected_mnemonic, joined, sizeof(s_selected_mnemonic) - 1);
|
||||
s_have_mnemonic = true;
|
||||
s_done = true;
|
||||
secure_memzero(joined, sizeof(joined));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (s_checksum_panel != NULL) {
|
||||
lv_obj_del(s_checksum_panel);
|
||||
s_checksum_panel = NULL;
|
||||
}
|
||||
|
||||
s_checksum_panel = lv_obj_create(lv_scr_act());
|
||||
lv_obj_set_size(s_checksum_panel, DISPLAY_WIDTH - 20, 82);
|
||||
lv_obj_align(s_checksum_panel, LV_ALIGN_TOP_MID, 0, 78);
|
||||
lv_obj_set_style_bg_color(s_checksum_panel, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_border_color(s_checksum_panel, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_border_width(s_checksum_panel, 2, 0);
|
||||
lv_obj_set_style_radius(s_checksum_panel, 6, 0);
|
||||
lv_obj_set_style_pad_all(s_checksum_panel, 6, 0);
|
||||
|
||||
lv_obj_t *txt = lv_label_create(s_checksum_panel);
|
||||
lv_label_set_text(txt, "Checksum invalid. Use anyway?");
|
||||
lv_obj_set_style_text_color(txt, lv_color_hex(0xFF0000), 0);
|
||||
lv_obj_align(txt, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t *use_btn = lv_btn_create(s_checksum_panel);
|
||||
lv_obj_set_size(use_btn, 128, 30);
|
||||
lv_obj_align(use_btn, LV_ALIGN_BOTTOM_LEFT, 4, -2);
|
||||
style_button(use_btn);
|
||||
lv_obj_set_style_border_color(use_btn, lv_color_hex(0xFF0000), 0);
|
||||
lv_obj_add_event_cb(use_btn, on_checksum_use_anyway, LV_EVENT_RELEASED, NULL);
|
||||
lv_obj_t *use_lbl = lv_label_create(use_btn);
|
||||
lv_label_set_text(use_lbl, "Use Anyway");
|
||||
lv_obj_center(use_lbl);
|
||||
|
||||
lv_obj_t *edit_btn = lv_btn_create(s_checksum_panel);
|
||||
lv_obj_set_size(edit_btn, 128, 30);
|
||||
lv_obj_align(edit_btn, LV_ALIGN_BOTTOM_RIGHT, -4, -2);
|
||||
style_button(edit_btn);
|
||||
lv_obj_add_event_cb(edit_btn, on_checksum_edit_words, LV_EVENT_RELEASED, NULL);
|
||||
lv_obj_t *edit_lbl = lv_label_create(edit_btn);
|
||||
lv_label_set_text(edit_lbl, "Edit Words");
|
||||
lv_obj_center(edit_lbl);
|
||||
|
||||
set_status("Checksum invalid");
|
||||
secure_memzero(joined, sizeof(joined));
|
||||
return false;
|
||||
}
|
||||
|
||||
static void on_back_to_menu(lv_event_t *e)
|
||||
{
|
||||
(void)e;
|
||||
if (s_checksum_panel != NULL) {
|
||||
lv_obj_del(s_checksum_panel);
|
||||
s_checksum_panel = NULL;
|
||||
}
|
||||
clear_entry_state();
|
||||
build_main_menu();
|
||||
}
|
||||
|
||||
static void on_suggestion_tap(lv_event_t *e)
|
||||
{
|
||||
const char *word = (const char *)lv_event_get_user_data(e);
|
||||
|
||||
if (s_suggestion_row == NULL || word == NULL || s_word_count >= UI_MNEMONIC_WORDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
commit_word(word);
|
||||
refresh_entry_ui();
|
||||
|
||||
if (s_word_count == UI_MNEMONIC_WORDS) {
|
||||
(void)verify_or_prompt_checksum();
|
||||
}
|
||||
}
|
||||
|
||||
static void entry_backspace_step(void)
|
||||
{
|
||||
if (s_prefix_len > 0) {
|
||||
s_prefix_len--;
|
||||
s_prefix[s_prefix_len] = '\0';
|
||||
} else if (s_word_count > 0) {
|
||||
s_word_count--;
|
||||
memset(s_prefix, 0, sizeof(s_prefix));
|
||||
strncpy(s_prefix, s_committed_words[s_word_count], sizeof(s_prefix) - 1);
|
||||
s_prefix_len = strlen(s_prefix);
|
||||
secure_memzero(s_committed_words[s_word_count], sizeof(s_committed_words[s_word_count]));
|
||||
}
|
||||
|
||||
s_checksum_override = false;
|
||||
if (s_checksum_panel != NULL) {
|
||||
lv_obj_del(s_checksum_panel);
|
||||
s_checksum_panel = NULL;
|
||||
}
|
||||
refresh_entry_ui();
|
||||
}
|
||||
|
||||
static void on_key_matrix(lv_event_t *e)
|
||||
{
|
||||
lv_obj_t *obj = lv_event_get_target(e);
|
||||
if (s_key_matrix == NULL || obj != s_key_matrix) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t idx = lv_btnmatrix_get_selected_btn(obj);
|
||||
const char *txt = lv_btnmatrix_get_btn_text(obj, idx);
|
||||
|
||||
if (txt == NULL || txt[0] == '\0' || txt[1] != '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (txt[0] == '<') {
|
||||
entry_backspace_step();
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_word_count >= UI_MNEMONIC_WORDS) {
|
||||
set_status("Already have 12 words");
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_prefix_len + 1 >= sizeof(s_prefix)) {
|
||||
set_status("Word too long");
|
||||
return;
|
||||
}
|
||||
|
||||
s_prefix[s_prefix_len++] = txt[0];
|
||||
s_prefix[s_prefix_len] = '\0';
|
||||
s_checksum_override = false;
|
||||
|
||||
refresh_entry_ui();
|
||||
|
||||
if (s_match_total == 1) {
|
||||
const char *word = mnemonic_word_at(s_match_indices[0]);
|
||||
if (word != NULL) {
|
||||
commit_word(word);
|
||||
refresh_entry_ui();
|
||||
if (s_word_count == UI_MNEMONIC_WORDS) {
|
||||
(void)verify_or_prompt_checksum();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void refresh_entry_ui(void)
|
||||
{
|
||||
size_t shown = 0;
|
||||
char words_line[256];
|
||||
char prefix_line[64];
|
||||
|
||||
if (s_checksum_panel != NULL) {
|
||||
lv_obj_del(s_checksum_panel);
|
||||
s_checksum_panel = NULL;
|
||||
}
|
||||
|
||||
memset(words_line, 0, sizeof(words_line));
|
||||
(void)join_committed_words(words_line, sizeof(words_line));
|
||||
|
||||
if (s_entry_words_label != NULL) {
|
||||
if (s_word_count == 0) {
|
||||
lv_label_set_text(s_entry_words_label, "(no words selected)");
|
||||
} else {
|
||||
lv_label_set_text(s_entry_words_label, words_line);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(prefix_line, sizeof(prefix_line), "Word %u/12 prefix: %s",
|
||||
(unsigned)(s_word_count + 1),
|
||||
(s_prefix_len > 0) ? s_prefix : "-");
|
||||
if (s_entry_prefix_label != NULL) {
|
||||
lv_label_set_text(s_entry_prefix_label, prefix_line);
|
||||
}
|
||||
|
||||
if (s_suggestion_row != NULL) {
|
||||
lv_obj_clean(s_suggestion_row);
|
||||
|
||||
if (s_prefix_len > 0 && s_word_count < UI_MNEMONIC_WORDS) {
|
||||
s_match_total = mnemonic_words_with_prefix(s_prefix, s_match_indices, UI_SUGGESTION_STORE_MAX);
|
||||
shown = (s_match_total > UI_SUGGESTION_VISIBLE_MAX) ? UI_SUGGESTION_VISIBLE_MAX : s_match_total;
|
||||
|
||||
for (size_t i = 0; i < shown; ++i) {
|
||||
const char *word = mnemonic_word_at(s_match_indices[i]);
|
||||
lv_obj_t *btn;
|
||||
lv_obj_t *lbl;
|
||||
if (word == NULL) {
|
||||
continue;
|
||||
}
|
||||
btn = lv_btn_create(s_suggestion_row);
|
||||
lv_obj_set_size(btn, 84, 26);
|
||||
style_button(btn);
|
||||
lv_obj_add_event_cb(btn, on_suggestion_tap, LV_EVENT_RELEASED, (void *)word);
|
||||
lbl = lv_label_create(btn);
|
||||
lv_label_set_text(lbl, word);
|
||||
lv_obj_center(lbl);
|
||||
}
|
||||
|
||||
if (s_match_total > shown) {
|
||||
char more[24];
|
||||
lv_obj_t *m = lv_label_create(s_suggestion_row);
|
||||
snprintf(more, sizeof(more), "+%u more", (unsigned)(s_match_total - shown));
|
||||
lv_label_set_text(m, more);
|
||||
lv_obj_set_style_text_color(m, lv_color_hex(0xFFFFFF), 0);
|
||||
}
|
||||
|
||||
if (s_match_total == 0) {
|
||||
set_status("No matches");
|
||||
} else {
|
||||
set_status("");
|
||||
}
|
||||
} else {
|
||||
s_match_total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (s_key_matrix != NULL) {
|
||||
const uint16_t matrix_btn_count = 27;
|
||||
for (uint16_t i = 0; i < matrix_btn_count; ++i) {
|
||||
const char *txt = lv_btnmatrix_get_btn_text(s_key_matrix, i);
|
||||
bool enabled = false;
|
||||
if (txt == NULL || txt[0] == '\0' || txt[1] != '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (txt[0] == '<') {
|
||||
enabled = (s_prefix_len > 0 || s_word_count > 0);
|
||||
} else if (s_word_count >= UI_MNEMONIC_WORDS) {
|
||||
enabled = false;
|
||||
} else if (s_prefix_len == 0) {
|
||||
size_t start = 0;
|
||||
enabled = (mnemonic_words_for_letter(txt[0], &start) > 0);
|
||||
} else {
|
||||
enabled = mnemonic_letter_extends_prefix(s_prefix, s_prefix_len, txt[0]);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
lv_btnmatrix_clear_btn_ctrl(s_key_matrix, i, LV_BTNMATRIX_CTRL_DISABLED);
|
||||
} else {
|
||||
lv_btnmatrix_set_btn_ctrl(s_key_matrix, i, LV_BTNMATRIX_CTRL_DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void build_entry_screen(void)
|
||||
{
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
lv_obj_t *title;
|
||||
lv_obj_t *close_btn;
|
||||
lv_obj_t *close_lbl;
|
||||
|
||||
reset_screen_widget_refs();
|
||||
lv_obj_clean(scr);
|
||||
style_screen(scr);
|
||||
clear_entry_state();
|
||||
|
||||
lv_obj_t *title = lv_label_create(scr);
|
||||
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);
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 4);
|
||||
|
||||
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);
|
||||
s_entry_words_label = lv_label_create(scr);
|
||||
lv_obj_set_width(s_entry_words_label, DISPLAY_WIDTH - 16);
|
||||
lv_label_set_long_mode(s_entry_words_label, LV_LABEL_LONG_WRAP);
|
||||
lv_label_set_text(s_entry_words_label, "(no words selected)");
|
||||
lv_obj_set_style_text_color(s_entry_words_label, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_align(s_entry_words_label, LV_ALIGN_TOP_LEFT, 8, 26);
|
||||
|
||||
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_entry_prefix_label = lv_label_create(scr);
|
||||
lv_label_set_text(s_entry_prefix_label, "Word 1/12 prefix: -");
|
||||
lv_obj_set_style_text_color(s_entry_prefix_label, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_align(s_entry_prefix_label, LV_ALIGN_TOP_LEFT, 8, 66);
|
||||
|
||||
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);
|
||||
lv_obj_align(s_status_label, LV_ALIGN_TOP_LEFT, 8, 82);
|
||||
|
||||
s_suggestion_row = lv_obj_create(scr);
|
||||
lv_obj_set_size(s_suggestion_row, DISPLAY_WIDTH - 12, 30);
|
||||
lv_obj_align(s_suggestion_row, LV_ALIGN_TOP_MID, 0, 96);
|
||||
lv_obj_set_style_bg_color(s_suggestion_row, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_border_color(s_suggestion_row, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_border_width(s_suggestion_row, 1, 0);
|
||||
lv_obj_set_style_pad_all(s_suggestion_row, 2, 0);
|
||||
lv_obj_set_scroll_dir(s_suggestion_row, LV_DIR_HOR);
|
||||
lv_obj_set_flex_flow(s_suggestion_row, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(s_suggestion_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
close_btn = lv_btn_create(scr);
|
||||
lv_obj_set_size(close_btn, 42, 30);
|
||||
lv_obj_align(close_btn, LV_ALIGN_TOP_RIGHT, -4, 2);
|
||||
style_button(close_btn);
|
||||
lv_obj_add_event_cb(close_btn, on_back_to_menu, LV_EVENT_CLICKED, NULL);
|
||||
close_lbl = lv_label_create(close_btn);
|
||||
lv_label_set_text(close_lbl, "X");
|
||||
lv_obj_center(close_lbl);
|
||||
|
||||
s_key_matrix = lv_btnmatrix_create(scr);
|
||||
lv_obj_set_size(s_key_matrix, DISPLAY_WIDTH - 16, 100);
|
||||
lv_obj_align(s_key_matrix, LV_ALIGN_TOP_MID, 0, 132);
|
||||
lv_btnmatrix_set_map(s_key_matrix, s_key_map);
|
||||
lv_obj_add_event_cb(s_key_matrix, on_key_matrix, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
style_key_matrix(s_key_matrix);
|
||||
lv_obj_set_style_pad_all(s_key_matrix, 0, 0);
|
||||
lv_obj_set_style_pad_gap(s_key_matrix, 0, LV_PART_ITEMS);
|
||||
lv_obj_set_style_border_width(s_key_matrix, 1, LV_PART_ITEMS);
|
||||
|
||||
refresh_entry_ui();
|
||||
}
|
||||
|
||||
static void on_enter(lv_event_t *e)
|
||||
@@ -316,6 +698,7 @@ static void on_use_viewed(lv_event_t *e)
|
||||
static void build_view_screen(void)
|
||||
{
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
reset_screen_widget_refs();
|
||||
lv_obj_clean(scr);
|
||||
style_screen(scr);
|
||||
|
||||
@@ -370,6 +753,7 @@ static void on_view(lv_event_t *e)
|
||||
static void build_main_menu(void)
|
||||
{
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
reset_screen_widget_refs();
|
||||
lv_obj_clean(scr);
|
||||
style_screen(scr);
|
||||
|
||||
@@ -424,6 +808,7 @@ void ui_show_approval_prompt(const char *caller_pubkey_hex, int kind, const char
|
||||
}
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
reset_screen_widget_refs();
|
||||
lv_obj_clean(scr);
|
||||
style_screen(scr);
|
||||
|
||||
@@ -535,6 +920,7 @@ ui_approval_decision_t ui_wait_for_approval(uint32_t timeout_ms)
|
||||
static void show_event_log_screen(void)
|
||||
{
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
reset_screen_widget_refs();
|
||||
lv_obj_clean(scr);
|
||||
style_screen(scr);
|
||||
|
||||
@@ -579,6 +965,7 @@ void ui_show_idle(const char *npub_full, const char *mnemonic_full, const char *
|
||||
}
|
||||
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
reset_screen_widget_refs();
|
||||
lv_obj_clean(scr);
|
||||
style_screen(scr);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user