This commit is contained in:
2026-09-12 03:40:10 +02:00
parent 029ceee69a
commit 8ac465229c
20 changed files with 220 additions and 422 deletions

View File

@@ -1,5 +1,6 @@
/*
* MTG RFID Companion - UI task with LVGL
*/
#include <string.h>
#include <stdlib.h>
@@ -36,7 +37,6 @@ static char s_uid[RFID_UID_STR_MAX_LEN] = {0};
static char s_card_name[128] = {0};
static bool s_waiting_fetch = false;
static uint32_t s_led_off_tick = 0;
static lv_obj_t *s_life_label = NULL;
static lv_img_dsc_t s_art_img_dsc;
static uint16_t *s_art_pixels = NULL;
@@ -241,39 +241,44 @@ static const char *get_json_str(cJSON *root, const char *key, const char *fallba
static void ui_clean_screen(void)
{
free_art_buffer();
s_life_label = NULL;
lv_obj_clean(lv_scr_act());
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0);
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);
}
/* ponytail: single shared header builder across all screens */
static void ui_draw_header(const char *title, const char *subtitle)
{
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(COLOR_GOLD), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
if (title != NULL) {
lv_obj_t *t = lv_label_create(lv_scr_act());
lv_label_set_text(t, title);
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(t, lv_color_hex(COLOR_GOLD), 0);
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, (subtitle != NULL) ? 10 : 14);
}
if (subtitle != NULL) {
lv_obj_t *sub = lv_label_create(lv_scr_act());
lv_label_set_text(sub, subtitle);
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(sub, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 34);
}
}
/* 1. Main Menu */
static void view_menu(void)
{
encoder_set_steps_per_detent(4);
lvgl_port_lock(0);
ui_clean_screen();
/* Top yellow accent bar */
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
/* Title */
lv_obj_t *title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "MTG COMPANION");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
/* Subtitle */
lv_obj_t *sub = lv_label_create(lv_scr_act());
lv_label_set_text(sub, "- MAIN MENU -");
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(sub, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 34);
ui_draw_header("MTG COMPANION", "- MAIN MENU -");
/* 7 Menu Items */
int start_y = 52;
@@ -407,33 +412,33 @@ static void view_life(void)
}
/* Main Big Number Display */
s_life_label = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(s_life_label, &lv_font_montserrat_48, 0);
lv_obj_align(s_life_label, LV_ALIGN_CENTER, 0, -20);
lv_obj_t *life_label = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(life_label, &lv_font_montserrat_48, 0);
lv_obj_align(life_label, LV_ALIGN_CENTER, 0, -20);
bool lethal = false;
if (s_life_mode == LIFE_MODE_TOTAL) {
int32_t life = app_get_life();
lv_label_set_text_fmt(s_life_label, "%ld", (long)life);
lv_label_set_text_fmt(life_label, "%ld", (long)life);
lv_color_t color = (life <= 0) ? lv_color_hex(0xFF1744) :
(life <= 10) ? lv_color_hex(0xFF9100) :
(life <= 15) ? lv_color_hex(0xFFCC00) :
lv_color_hex(0x00E676);
lv_obj_set_style_text_color(s_life_label, color, 0);
lv_obj_set_style_text_color(life_label, color, 0);
if (life <= 0) lethal = true;
} else if (s_life_mode == LIFE_MODE_POISON) {
lv_label_set_text_fmt(s_life_label, "%ld / 10", (long)s_poison);
lv_label_set_text_fmt(life_label, "%ld / 10", (long)s_poison);
lv_color_t color = (s_poison >= 10) ? lv_color_hex(0xFF1744) :
(s_poison >= 6) ? lv_color_hex(0xFF9100) :
lv_color_hex(0x00E5FF);
lv_obj_set_style_text_color(s_life_label, color, 0);
lv_obj_set_style_text_color(life_label, color, 0);
if (s_poison >= 10) lethal = true;
} else {
lv_label_set_text_fmt(s_life_label, "%ld / 21", (long)s_cmd_dmg);
lv_label_set_text_fmt(life_label, "%ld / 21", (long)s_cmd_dmg);
lv_color_t color = (s_cmd_dmg >= 21) ? lv_color_hex(0xFF1744) :
(s_cmd_dmg >= 15) ? lv_color_hex(0xFF9100) :
lv_color_hex(0xE040FB);
lv_obj_set_style_text_color(s_life_label, color, 0);
lv_obj_set_style_text_color(life_label, color, 0);
if (s_cmd_dmg >= 21) lethal = true;
}
@@ -481,12 +486,7 @@ static void view_dice(void)
lvgl_port_lock(0);
ui_clean_screen();
/* Top yellow accent bar */
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
ui_draw_header(NULL, NULL);
/* Selector tabs */
const char *tabs[3] = {"[ D20 ]", "[ D6 ]", "[ COIN ]"};
@@ -630,18 +630,7 @@ static void view_commander_tax(void)
lvgl_port_lock(0);
ui_clean_screen();
/* Top yellow accent bar */
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t *title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "COMMANDER TAX");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
ui_draw_header("COMMANDER TAX", NULL);
/* Casts Box */
lv_obj_t *cast_box = lv_obj_create(lv_scr_act());
@@ -722,19 +711,7 @@ static void view_token_spawner(void)
lvgl_port_lock(0);
ui_clean_screen();
/* Top yellow accent bar */
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
/* Title */
lv_obj_t *title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "TOKEN SPAWNER");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);
ui_draw_header("TOKEN SPAWNER", NULL);
/* Carousel indicator: < 1 OF 8 > */
lv_obj_t *idx_lbl = lv_label_create(lv_scr_act());
@@ -843,18 +820,7 @@ static void view_storage(void)
lvgl_port_lock(0);
ui_clean_screen();
/* Top yellow accent bar */
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t *title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "DECK & STORAGE");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 12);
ui_draw_header("DECK & STORAGE", NULL);
size_t total_b = 0, used_b = 0;
storage_manager_get_info(&total_b, &used_b);
@@ -1018,18 +984,7 @@ static void view_device_info(void)
lvgl_port_lock(0);
ui_clean_screen();
/* Top yellow accent bar */
lv_obj_t *bar = lv_obj_create(lv_scr_act());
lv_obj_set_size(bar, DISP_W, 4);
lv_obj_set_style_bg_color(bar, lv_color_hex(0xFFCC00), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t *title = lv_label_create(lv_scr_act());
lv_label_set_text(title, "DEVICE INFO");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFCC00), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15);
ui_draw_header("DEVICE INFO", NULL);
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 220);
@@ -1416,10 +1371,55 @@ static void handle_fetch_ready(void)
{
const char *ready_uid = scryfall_last_result_uid();
if (ready_uid != NULL && strcmp(ready_uid, s_uid) == 0) {
bool success = scryfall_last_result_success();
scryfall_consume_result();
s_waiting_fetch = false;
read_cached_color(s_uid);
view_card();
if (success) {
read_cached_color(s_uid);
view_card();
} else {
buzzer_chirp(220, 150);
led_manager_set_color(255, 0, 0);
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
lvgl_port_lock(0);
ui_clean_screen();
ui_draw_header("FETCH FAILED", NULL);
lv_obj_t *box = lv_obj_create(lv_scr_act());
lv_obj_set_size(box, DISP_W - 24, 180);
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
lv_obj_set_style_bg_color(box, lv_color_hex(0x281010), 0);
lv_obj_set_style_border_color(box, lv_color_hex(0xFF1744), 0);
lv_obj_set_style_border_width(box, 2, 0);
lv_obj_set_style_radius(box, 8, 0);
lv_obj_set_style_pad_all(box, 8, 0);
lv_obj_t *cname = lv_label_create(box);
lv_label_set_text(cname, s_card_name);
lv_obj_set_style_text_font(cname, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(cname, lv_color_hex(0xFFCC00), 0);
lv_label_set_long_mode(cname, LV_LABEL_LONG_DOT);
lv_obj_set_width(cname, DISP_W - 44);
lv_obj_align(cname, LV_ALIGN_TOP_MID, 0, 10);
lv_obj_t *sub = lv_label_create(box);
lv_label_set_text(sub, "Could not fetch card from\nScryfall. Check Wi-Fi STA\nor card spelling online.\n\nCLICK: RETRY");
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(sub, lv_color_white(), 0);
lv_obj_set_style_text_align(sub, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(sub, LV_ALIGN_CENTER, 0, 20);
lv_obj_t *hint = lv_label_create(lv_scr_act());
lv_label_set_text(hint, "HOLD: MAIN MENU");
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
lvgl_port_unlock();
}
} else {
scryfall_consume_result();
}
}
@@ -1528,6 +1528,17 @@ static void handle_encoder_click(void)
case APP_STATE_SCANNER:
case APP_STATE_CARD_VIEW:
case APP_STATE_REGISTRATION:
if (cur == APP_STATE_CARD_VIEW && s_card_name[0] != '\0' && !s_waiting_fetch) {
char jp[72];
snprintf(jp, sizeof(jp), STORAGE_CARDS_DIR "/%s.json", s_uid);
if (!storage_file_exists(jp)) {
/* Retry fetch */
s_waiting_fetch = true;
view_fetching(s_card_name);
scryfall_fetch_request(s_card_name, s_uid);
break;
}
}
app_set_view(APP_STATE_LIFE_COUNTER);
view_life();
break;
@@ -1719,8 +1730,4 @@ esp_err_t ui_task_start(void)
{
xTaskCreatePinnedToCore(ui_task, "ui_task", UI_TASK_STACK, NULL, UI_TASK_PRIO, &s_ui_task, UI_CORE);
return ESP_OK;
}
void ui_task_wait_boot(void)
{
}