feat(bibleverse): editorial v6 - Georgia Italic serif pretty font, 15px lift, menu auto-show 10s, animated book picker, no black bar
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
- Pretty font: embedded LVGL C Georgia Italic 26/22/20/18/16 + regular 24 (lv_font_conv --lv-include lvgl.h) ~368KB src ~315KB PSRAM via CONFIG_ELF_LOADER_LOAD_PSRAM=y, heap 47KB stable on 192.168.68.129 0.8.0-dev - Downtier for readability: LARGE->22 (was 26), DEFAULT->18, SMALL->16, ultra <40c still 26, pads tighter 6/4/3/2/1 - Black bar fix: header 36px bg 0x0E0E14 TRANSP not 15151F COVER, border 0, ctrl TRANSP, vision 'no extra black bar overlapping' - Lift 15px up: center ALIGN_CENTER 0,-7 (was -22 30px, user corrected), balanced optical center, reference gap 10 - Menu auto-show: start set_chrome_visible(true) + chrome_auto_hide_timer 10000ms via lv_timer_get_user_data + toggle_ui re-arms, onHide cleanup - Book picker animation: book_y_anim_cb y 20->0 260ms ease_out + fade 20->COVER 220ms pop, name fade 60->255 on dial change, swipe 28px + slider fast scrub, [X left][slider flex][39/66 right], no card/no Go/no top bar, LARGE pretty name 50% bigger tappable - Fade transition verse 220ms ref 300ms + uppercase ref tracking 2 #9A9AA8 + warmer #F2F0E8 - Compat: no lv_arc_create (0.7 fleet), 0 missing LVGL syms undef=0, build 4.3M - Skill docs: SKILL.md v6 black-bar+downtier+auto-hide+anim, pretty-font.md v6, immersive-ui.md v6 Screenshots: Zephaniah 1:3 8683 bytes menu visible (top ref + bottom pills), after 6882 immersive, Habakkuk 2:9 no crop vision PASS Co-Authored-By: internal-model
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
@@ -12,6 +13,14 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "tactility/lvgl_fonts.h"
|
#include "tactility/lvgl_fonts.h"
|
||||||
|
|
||||||
|
// Embedded pretty serif fonts - Georgia Italic like reference white card
|
||||||
|
LV_FONT_DECLARE(georgia_italic_26)
|
||||||
|
LV_FONT_DECLARE(georgia_italic_22)
|
||||||
|
LV_FONT_DECLARE(georgia_italic_20)
|
||||||
|
LV_FONT_DECLARE(georgia_italic_18)
|
||||||
|
LV_FONT_DECLARE(georgia_italic_16)
|
||||||
|
LV_FONT_DECLARE(georgia_regular_24)
|
||||||
|
|
||||||
#define TAG "BibleVerse"
|
#define TAG "BibleVerse"
|
||||||
#define AUTO_ADV_MS 60000 // 1 minute
|
#define AUTO_ADV_MS 60000 // 1 minute
|
||||||
|
|
||||||
@@ -75,6 +84,7 @@ typedef struct {
|
|||||||
// UI
|
// UI
|
||||||
lv_obj_t* root;
|
lv_obj_t* root;
|
||||||
lv_obj_t* verse_wrapper; // full screen, clickable to toggle chrome
|
lv_obj_t* verse_wrapper; // full screen, clickable to toggle chrome
|
||||||
|
lv_obj_t* center_col; // editorial column for fade
|
||||||
lv_obj_t* lbl_verse;
|
lv_obj_t* lbl_verse;
|
||||||
lv_obj_t* lbl_ref; // small ref line below verse
|
lv_obj_t* lbl_ref; // small ref line below verse
|
||||||
lv_obj_t* header_bar;
|
lv_obj_t* header_bar;
|
||||||
@@ -86,9 +96,33 @@ typedef struct {
|
|||||||
lv_obj_t* btn_play_pause;
|
lv_obj_t* btn_play_pause;
|
||||||
lv_obj_t* btn_highlight;
|
lv_obj_t* btn_highlight;
|
||||||
lv_obj_t* btn_close;
|
lv_obj_t* btn_close;
|
||||||
|
lv_obj_t* btn_books;
|
||||||
|
|
||||||
|
// Pretty serif fonts (Georgia Italic .fnt bin)
|
||||||
|
// Will be linked as C arrays from assets/fonts via binfont -> replaced with embedded C now
|
||||||
|
// Keep pointers for compatibility
|
||||||
|
lv_font_t* font_serif_26;
|
||||||
|
lv_font_t* font_serif_22;
|
||||||
|
lv_font_t* font_serif_20;
|
||||||
|
lv_font_t* font_serif_18;
|
||||||
|
lv_font_t* font_serif_16;
|
||||||
|
lv_font_t* font_book_24;
|
||||||
|
bool fonts_loaded;
|
||||||
|
|
||||||
|
// Book browser - horizontal slide (0.7.0-dev compat, low RAM)
|
||||||
|
lv_obj_t* book_browser;
|
||||||
|
lv_obj_t* book_center_name;
|
||||||
|
lv_obj_t* book_center_num;
|
||||||
|
lv_obj_t* book_center_verses;
|
||||||
|
lv_obj_t* book_slider; // fast scrub
|
||||||
|
int book_dial_idx; // 0..book_count-1 currently selected
|
||||||
|
int book_drag_start_x;
|
||||||
|
bool book_dragging;
|
||||||
|
bool book_browser_visible;
|
||||||
|
|
||||||
// Timer
|
// Timer
|
||||||
lv_timer_t* auto_timer;
|
lv_timer_t* auto_timer;
|
||||||
|
lv_timer_t* chrome_auto_hide_timer; // menu auto-hide after 10s on start
|
||||||
bool auto_paused;
|
bool auto_paused;
|
||||||
bool ui_visible; // chrome visible?
|
bool ui_visible; // chrome visible?
|
||||||
} AppCtx;
|
} AppCtx;
|
||||||
@@ -101,6 +135,8 @@ static bool load_book(AppCtx* ctx, int bnumber);
|
|||||||
static void unload_book(AppCtx* ctx);
|
static void unload_book(AppCtx* ctx);
|
||||||
static void show_current_verse(AppCtx* ctx);
|
static void show_current_verse(AppCtx* ctx);
|
||||||
static void advance_verse(AppCtx* ctx, int delta);
|
static void advance_verse(AppCtx* ctx, int delta);
|
||||||
|
static void jump_to_book(AppCtx* ctx, int book_idx);
|
||||||
|
static void jump_to_global(AppCtx* ctx, int global_idx);
|
||||||
static void save_progress(AppCtx* ctx);
|
static void save_progress(AppCtx* ctx);
|
||||||
static bool load_progress(AppCtx* ctx);
|
static bool load_progress(AppCtx* ctx);
|
||||||
static void toggle_ui(AppCtx* ctx);
|
static void toggle_ui(AppCtx* ctx);
|
||||||
@@ -108,6 +144,9 @@ static void set_chrome_visible(AppCtx* ctx, bool visible);
|
|||||||
static bool is_favorite(AppCtx* ctx, int global_idx);
|
static bool is_favorite(AppCtx* ctx, int global_idx);
|
||||||
static void toggle_favorite(AppCtx* ctx);
|
static void toggle_favorite(AppCtx* ctx);
|
||||||
static void ensure_user_dir(AppCtx* ctx);
|
static void ensure_user_dir(AppCtx* ctx);
|
||||||
|
static void set_book_browser_visible(AppCtx* ctx, bool visible);
|
||||||
|
static void toggle_book_browser(AppCtx* ctx);
|
||||||
|
static void build_book_browser(AppCtx* ctx, lv_obj_t* parent);
|
||||||
|
|
||||||
/* ── Utilities ── */
|
/* ── Utilities ── */
|
||||||
static void ensure_user_dir(AppCtx* ctx) {
|
static void ensure_user_dir(AppCtx* ctx) {
|
||||||
@@ -516,16 +555,31 @@ static void toggle_favorite(AppCtx* ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── UI logic ── */
|
/* ── UI logic ── */
|
||||||
|
// Font resolution: pretty serif if loaded, else fallback to system
|
||||||
|
// Georgia Italic is wider/taller than Montserrat system -> map down one tier to avoid crop
|
||||||
|
static const lv_font_t* resolve_verse_font(AppCtx* ctx, enum LvglFontSize fallback_sz) {
|
||||||
|
if (!ctx->fonts_loaded) return lvgl_get_text_font(fallback_sz);
|
||||||
|
// Georgia 26 is visually larger than system LARGE, so use 22 for LARGE tier, 20 for DEFAULT, 16 for SMALL
|
||||||
|
if (fallback_sz == FONT_SIZE_LARGE && ctx->font_serif_22) return ctx->font_serif_22; // was 26, too big
|
||||||
|
if (fallback_sz == FONT_SIZE_DEFAULT && ctx->font_serif_18) return ctx->font_serif_18; // was 20
|
||||||
|
if (fallback_sz == FONT_SIZE_SMALL && ctx->font_serif_16) return ctx->font_serif_16;
|
||||||
|
// ultra-short special: still allow 26 for very tiny verses <40 chars
|
||||||
|
return lvgl_get_text_font(fallback_sz);
|
||||||
|
}
|
||||||
|
static const lv_font_t* resolve_verse_font_ultra(AppCtx* ctx) {
|
||||||
|
if (ctx->fonts_loaded && ctx->font_serif_26) return ctx->font_serif_26;
|
||||||
|
return lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||||
|
}
|
||||||
|
static const lv_font_t* resolve_book_font(AppCtx* ctx) {
|
||||||
|
if (ctx->font_book_24) return ctx->font_book_24;
|
||||||
|
if (ctx->font_serif_22) return ctx->font_serif_22;
|
||||||
|
return lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||||
|
}
|
||||||
static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
||||||
if (!ctx->lbl_verse) return;
|
if (!ctx->lbl_verse) return;
|
||||||
size_t len = strlen(verse);
|
size_t len = strlen(verse);
|
||||||
// Tiers tuned from real device screenshots (240x320 style)
|
|
||||||
// < 40 : ultra short e.g. "Jesus wept." -> giant, centered, lots of breathing
|
|
||||||
// 40-100 : short -> large
|
|
||||||
// 100-200: medium -> default/large borderline
|
|
||||||
// 200-350: long -> small/default
|
|
||||||
// 350+ : very long (Esther 8:9 etc) -> extra compact
|
|
||||||
enum LvglFontSize font_sz;
|
enum LvglFontSize font_sz;
|
||||||
|
bool is_ultra = false;
|
||||||
int line_space;
|
int line_space;
|
||||||
int pad_v;
|
int pad_v;
|
||||||
int pad_h;
|
int pad_h;
|
||||||
@@ -533,33 +587,35 @@ static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
|||||||
|
|
||||||
if (len < 40) {
|
if (len < 40) {
|
||||||
font_sz = FONT_SIZE_LARGE;
|
font_sz = FONT_SIZE_LARGE;
|
||||||
line_space = 14;
|
is_ultra = true;
|
||||||
pad_v = 32;
|
line_space = 12;
|
||||||
pad_h = 36;
|
pad_v = 6;
|
||||||
letter_space = 1;
|
pad_h = 20;
|
||||||
|
letter_space = 0;
|
||||||
} else if (len < 100) {
|
} else if (len < 100) {
|
||||||
font_sz = FONT_SIZE_LARGE;
|
font_sz = FONT_SIZE_LARGE;
|
||||||
line_space = 11;
|
line_space = 8;
|
||||||
pad_v = 24;
|
pad_v = 4;
|
||||||
pad_h = 28;
|
pad_h = 16;
|
||||||
} else if (len < 200) {
|
} else if (len < 200) {
|
||||||
font_sz = FONT_SIZE_DEFAULT;
|
font_sz = FONT_SIZE_DEFAULT;
|
||||||
line_space = 9;
|
line_space = 6;
|
||||||
pad_v = 18;
|
pad_v = 3;
|
||||||
pad_h = 22;
|
pad_h = 14;
|
||||||
} else if (len < 350) {
|
} else if (len < 350) {
|
||||||
font_sz = FONT_SIZE_SMALL;
|
font_sz = FONT_SIZE_SMALL;
|
||||||
line_space = 6;
|
line_space = 5;
|
||||||
pad_v = 12;
|
pad_v = 2;
|
||||||
pad_h = 18;
|
pad_h = 12;
|
||||||
} else {
|
} else {
|
||||||
font_sz = FONT_SIZE_SMALL;
|
font_sz = FONT_SIZE_SMALL;
|
||||||
line_space = 4;
|
line_space = 4;
|
||||||
pad_v = 8;
|
pad_v = 1;
|
||||||
pad_h = 14;
|
pad_h = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_obj_set_style_text_font(ctx->lbl_verse, lvgl_get_text_font(font_sz), 0);
|
const lv_font_t* f = is_ultra ? resolve_verse_font_ultra(ctx) : resolve_verse_font(ctx, font_sz);
|
||||||
|
lv_obj_set_style_text_font(ctx->lbl_verse, f, 0);
|
||||||
lv_obj_set_style_text_line_space(ctx->lbl_verse, line_space, 0);
|
lv_obj_set_style_text_line_space(ctx->lbl_verse, line_space, 0);
|
||||||
lv_obj_set_style_text_letter_space(ctx->lbl_verse, letter_space, 0);
|
lv_obj_set_style_text_letter_space(ctx->lbl_verse, letter_space, 0);
|
||||||
lv_obj_set_style_pad_top(ctx->lbl_verse, pad_v, 0);
|
lv_obj_set_style_pad_top(ctx->lbl_verse, pad_v, 0);
|
||||||
@@ -568,6 +624,34 @@ static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
|||||||
lv_obj_set_style_pad_right(ctx->lbl_verse, pad_h, 0);
|
lv_obj_set_style_pad_right(ctx->lbl_verse, pad_h, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fade transition for verse change
|
||||||
|
static void verse_fade_anim_cb(void* var, int32_t v) {
|
||||||
|
lv_obj_t* obj = (lv_obj_t*)var;
|
||||||
|
lv_obj_set_style_text_opa(obj, v, 0);
|
||||||
|
}
|
||||||
|
static void verse_fade_in(AppCtx* ctx) {
|
||||||
|
if (!ctx->lbl_verse || !ctx->center_col) return;
|
||||||
|
lv_anim_t a;
|
||||||
|
lv_anim_init(&a);
|
||||||
|
lv_anim_set_var(&a, ctx->lbl_verse);
|
||||||
|
lv_anim_set_values(&a, 40, 255);
|
||||||
|
lv_anim_set_duration(&a, 220);
|
||||||
|
lv_anim_set_exec_cb(&a, verse_fade_anim_cb);
|
||||||
|
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
|
||||||
|
lv_anim_start(&a);
|
||||||
|
// Also fade ref slightly delayed
|
||||||
|
if (ctx->lbl_ref) {
|
||||||
|
lv_anim_t b;
|
||||||
|
lv_anim_init(&b);
|
||||||
|
lv_anim_set_var(&b, ctx->lbl_ref);
|
||||||
|
lv_anim_set_values(&b, 30, 200);
|
||||||
|
lv_anim_set_duration(&b, 300);
|
||||||
|
lv_anim_set_exec_cb(&b, verse_fade_anim_cb);
|
||||||
|
lv_anim_set_path_cb(&b, lv_anim_path_ease_out);
|
||||||
|
lv_anim_start(&b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void show_current_verse(AppCtx* ctx) {
|
static void show_current_verse(AppCtx* ctx) {
|
||||||
if (ctx->book_count==0) return;
|
if (ctx->book_count==0) return;
|
||||||
global_to_book(ctx, ctx->cur_global);
|
global_to_book(ctx, ctx->cur_global);
|
||||||
@@ -595,14 +679,28 @@ static void show_current_verse(AppCtx* ctx) {
|
|||||||
char ref_buf[96];
|
char ref_buf[96];
|
||||||
snprintf(ref_buf, sizeof(ref_buf), "%s %d:%d", ctx->books[ctx->cur_book_idx].bname, vi->cnum, vi->vnum);
|
snprintf(ref_buf, sizeof(ref_buf), "%s %d:%d", ctx->books[ctx->cur_book_idx].bname, vi->cnum, vi->vnum);
|
||||||
|
|
||||||
char ref_pretty[96];
|
// Editorial reference style like your image: "PSALMS 23:1" with tracking
|
||||||
snprintf(ref_pretty, sizeof(ref_pretty), "— %s", ref_buf);
|
char ref_pretty[108];
|
||||||
|
// Use uppercase for book? Keep title for now but uppercase style for editorial match
|
||||||
|
// We'll format as "— PSALMS 23:1 —" feel? For dark mode keep minimal like ref image
|
||||||
|
// Reference image shows lines flanking citation - we do with letterspacing + dim
|
||||||
|
// For now: "PSALMS 23:1" style - uppercase via runtime? We'll uppercase book name
|
||||||
|
char bname_upper[48];
|
||||||
|
strncpy(bname_upper, ctx->books[ctx->cur_book_idx].bname, sizeof(bname_upper)-1);
|
||||||
|
bname_upper[sizeof(bname_upper)-1]='\0';
|
||||||
|
for (char* p=bname_upper; *p; ++p) *p = toupper((unsigned char)*p);
|
||||||
|
snprintf(ref_pretty, sizeof(ref_pretty), "%s %d:%d", bname_upper, vi->cnum, vi->vnum);
|
||||||
|
|
||||||
if (ctx->lbl_verse) {
|
if (ctx->lbl_verse) {
|
||||||
|
// start faded for transition
|
||||||
|
lv_obj_set_style_text_opa(ctx->lbl_verse, 60, 0);
|
||||||
lv_label_set_text(ctx->lbl_verse, verse_buf);
|
lv_label_set_text(ctx->lbl_verse, verse_buf);
|
||||||
apply_verse_scaling(ctx, verse_buf);
|
apply_verse_scaling(ctx, verse_buf);
|
||||||
}
|
}
|
||||||
if (ctx->lbl_ref) lv_label_set_text(ctx->lbl_ref, ref_pretty);
|
if (ctx->lbl_ref) {
|
||||||
|
lv_obj_set_style_text_opa(ctx->lbl_ref, 40, 0);
|
||||||
|
lv_label_set_text(ctx->lbl_ref, ref_pretty);
|
||||||
|
}
|
||||||
if (ctx->lbl_book_title) lv_label_set_text(ctx->lbl_book_title, ref_buf);
|
if (ctx->lbl_book_title) lv_label_set_text(ctx->lbl_book_title, ref_buf);
|
||||||
if (ctx->lbl_position) {
|
if (ctx->lbl_position) {
|
||||||
char pos_buf[40];
|
char pos_buf[40];
|
||||||
@@ -644,20 +742,38 @@ static void show_current_verse(AppCtx* ctx) {
|
|||||||
else lv_obj_clear_state(ctx->btn_next, LV_STATE_DISABLED);
|
else lv_obj_clear_state(ctx->btn_next, LV_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
save_progress(ctx);
|
save_progress(ctx);
|
||||||
|
verse_fade_in(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void advance_verse(AppCtx* ctx, int delta) {
|
static void advance_verse(AppCtx* ctx, int delta) {
|
||||||
int ng = ctx->cur_global + delta;
|
int ng = ctx->cur_global + delta;
|
||||||
if (ng < 0) ng = 0;
|
if (ng < 0) ng = 0;
|
||||||
if (ng >= ctx->total_verses) ng = ctx->total_verses-1; // stop at end, or wrap? we stop
|
if (ng >= ctx->total_verses) ng = ctx->total_verses-1;
|
||||||
// Optionally wrap to start after Revelation
|
|
||||||
// if (ng >= ctx->total_verses) ng = 0;
|
|
||||||
if (ng != ctx->cur_global) {
|
if (ng != ctx->cur_global) {
|
||||||
ctx->cur_global = ng;
|
ctx->cur_global = ng;
|
||||||
show_current_verse(ctx);
|
show_current_verse(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void jump_to_global(AppCtx* ctx, int global_idx) {
|
||||||
|
if (global_idx < 0) global_idx = 0;
|
||||||
|
if (global_idx >= ctx->total_verses) global_idx = ctx->total_verses-1;
|
||||||
|
ctx->cur_global = global_idx;
|
||||||
|
set_book_browser_visible(ctx, false);
|
||||||
|
set_chrome_visible(ctx, false);
|
||||||
|
show_current_verse(ctx);
|
||||||
|
// restart auto timer
|
||||||
|
if (ctx->auto_timer) lv_timer_reset(ctx->auto_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void jump_to_book(AppCtx* ctx, int book_idx) {
|
||||||
|
if (book_idx < 0 || book_idx >= ctx->book_count) return;
|
||||||
|
// compute global start of book
|
||||||
|
int global = 0;
|
||||||
|
for (int i=0;i<book_idx;i++) global += ctx->books[i].verse_count;
|
||||||
|
jump_to_global(ctx, global);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Timer ── */
|
/* ── Timer ── */
|
||||||
static void on_auto_timer(lv_timer_t* t) {
|
static void on_auto_timer(lv_timer_t* t) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_timer_get_user_data(t);
|
AppCtx* ctx = (AppCtx*)lv_timer_get_user_data(t);
|
||||||
@@ -673,14 +789,304 @@ static void on_auto_timer(lv_timer_t* t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Book browser: horizontal slide, center name updates (arc-free) ── */
|
||||||
|
static void book_y_anim_cb(void* var, int32_t v) {
|
||||||
|
lv_obj_t* obj = (lv_obj_t*)var;
|
||||||
|
// v is y offset
|
||||||
|
lv_obj_set_y(obj, v);
|
||||||
|
}
|
||||||
|
static void book_fade_anim_cb(void* var, int32_t v) {
|
||||||
|
lv_obj_t* obj = (lv_obj_t*)var;
|
||||||
|
lv_obj_set_style_text_opa(obj, v, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void book_dial_update_center(AppCtx* ctx) {
|
||||||
|
if (!ctx->book_center_name) return;
|
||||||
|
if (ctx->book_dial_idx <0) ctx->book_dial_idx=0;
|
||||||
|
if (ctx->book_dial_idx >= ctx->book_count) ctx->book_dial_idx = ctx->book_count-1;
|
||||||
|
BookInfo* b = &ctx->books[ctx->book_dial_idx];
|
||||||
|
|
||||||
|
// Animate name change: quick fade (40->255)
|
||||||
|
lv_obj_set_style_text_opa(ctx->book_center_name, 100, 0);
|
||||||
|
{
|
||||||
|
lv_anim_t an; lv_anim_init(&an);
|
||||||
|
lv_anim_set_var(&an, ctx->book_center_name);
|
||||||
|
lv_anim_set_values(&an, 60, 255);
|
||||||
|
lv_anim_set_duration(&an, 180);
|
||||||
|
lv_anim_set_exec_cb(&an, book_fade_anim_cb);
|
||||||
|
lv_anim_set_path_cb(&an, lv_anim_path_ease_out);
|
||||||
|
lv_anim_start(&an);
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_label_set_text(ctx->book_center_name, b->bname);
|
||||||
|
// Use pretty book font if loaded (50% larger already via LARGE vs DEFAULT fallback)
|
||||||
|
lv_obj_set_style_text_font(ctx->book_center_name, resolve_book_font(ctx), 0);
|
||||||
|
|
||||||
|
if (ctx->book_center_num) {
|
||||||
|
char nb[20]; snprintf(nb, sizeof(nb), "%2d / %d", b->bnumber, ctx->book_count);
|
||||||
|
lv_label_set_text(ctx->book_center_num, nb);
|
||||||
|
}
|
||||||
|
if (ctx->book_center_verses) {
|
||||||
|
char vb[24]; snprintf(vb, sizeof(vb), "%d verses", b->verse_count);
|
||||||
|
lv_label_set_text(ctx->book_center_verses, vb);
|
||||||
|
}
|
||||||
|
uint32_t col = (b->bnumber < 40) ? 0xE8E6E0 : 0x89B4FA;
|
||||||
|
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(col), 0);
|
||||||
|
|
||||||
|
if (ctx->book_slider) {
|
||||||
|
// avoid recursive event loop - set without anim, but we add subtle anim for drag
|
||||||
|
lv_slider_set_value(ctx->book_slider, ctx->book_dial_idx, LV_ANIM_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_book_browser_visible(AppCtx* ctx, bool visible) {
|
||||||
|
ctx->book_browser_visible = visible;
|
||||||
|
if (visible) {
|
||||||
|
ctx->book_dial_idx = ctx->cur_book_idx;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
if (ctx->book_browser) {
|
||||||
|
lv_obj_remove_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
// Anim: pop center mid_wrap y 20 -> 0, ease_out 260ms
|
||||||
|
if (ctx->book_center_name) {
|
||||||
|
lv_obj_t* parent = lv_obj_get_parent(ctx->book_center_name); // mid_wrap
|
||||||
|
lv_obj_set_y(parent, 20);
|
||||||
|
lv_anim_t ay; lv_anim_init(&ay);
|
||||||
|
lv_anim_set_var(&ay, parent);
|
||||||
|
lv_anim_set_values(&ay, 20, 0);
|
||||||
|
lv_anim_set_duration(&ay, 260);
|
||||||
|
lv_anim_set_exec_cb(&ay, book_y_anim_cb);
|
||||||
|
lv_anim_set_path_cb(&ay, lv_anim_path_ease_out);
|
||||||
|
lv_anim_start(&ay);
|
||||||
|
}
|
||||||
|
// Fade name
|
||||||
|
if (ctx->book_center_name) {
|
||||||
|
lv_obj_set_style_text_opa(ctx->book_center_name, LV_OPA_20, 0);
|
||||||
|
lv_anim_t af; lv_anim_init(&af);
|
||||||
|
lv_anim_set_var(&af, ctx->book_center_name);
|
||||||
|
lv_anim_set_values(&af, LV_OPA_20, LV_OPA_COVER);
|
||||||
|
lv_anim_set_duration(&af, 220);
|
||||||
|
lv_anim_set_exec_cb(&af, book_fade_anim_cb);
|
||||||
|
lv_anim_set_path_cb(&af, lv_anim_path_ease_out);
|
||||||
|
lv_anim_start(&af);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ctx->book_browser) lv_obj_add_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void toggle_book_browser(AppCtx* ctx) {
|
||||||
|
// reset chrome auto-hide when opening browser
|
||||||
|
if (ctx->chrome_auto_hide_timer) {
|
||||||
|
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||||
|
ctx->chrome_auto_hide_timer = NULL;
|
||||||
|
}
|
||||||
|
set_book_browser_visible(ctx, !ctx->book_browser_visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_books_close_click(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
set_book_browser_visible(ctx, false);
|
||||||
|
}
|
||||||
|
static void on_book_center_click(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
jump_to_book(ctx, ctx->book_dial_idx);
|
||||||
|
}
|
||||||
|
static void on_dial_prev_click(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
if (ctx->book_dial_idx>0) { ctx->book_dial_idx--; book_dial_update_center(ctx); }
|
||||||
|
}
|
||||||
|
static void on_dial_next_click(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
if (ctx->book_dial_idx < ctx->book_count-1) { ctx->book_dial_idx++; book_dial_update_center(ctx); }
|
||||||
|
}
|
||||||
|
static void on_slider_changed(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
int v = lv_slider_get_value(ctx->book_slider);
|
||||||
|
if (v!=ctx->book_dial_idx) { ctx->book_dial_idx=v; book_dial_update_center(ctx); }
|
||||||
|
}
|
||||||
|
static void on_swipe_area_event(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
lv_event_code_t code = lv_event_get_code(e);
|
||||||
|
lv_indev_t* indev = lv_indev_active();
|
||||||
|
if (!indev) return;
|
||||||
|
lv_point_t p; lv_indev_get_point(indev, &p);
|
||||||
|
if (code==LV_EVENT_PRESSED) {
|
||||||
|
ctx->book_drag_start_x = p.x;
|
||||||
|
ctx->book_dragging = true;
|
||||||
|
} else if (code==LV_EVENT_PRESSING && ctx->book_dragging) {
|
||||||
|
int dx = p.x - ctx->book_drag_start_x;
|
||||||
|
int thresh = 28; // px per book
|
||||||
|
if (abs(dx) >= thresh) {
|
||||||
|
int steps = dx / thresh;
|
||||||
|
int new_idx = ctx->book_dial_idx - steps; // swipe left -> next
|
||||||
|
if (new_idx<0) new_idx=0;
|
||||||
|
if (new_idx>=ctx->book_count) new_idx=ctx->book_count-1;
|
||||||
|
if (new_idx!=ctx->book_dial_idx) {
|
||||||
|
ctx->book_dial_idx = new_idx;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
}
|
||||||
|
ctx->book_drag_start_x = p.x; // reset for next step
|
||||||
|
}
|
||||||
|
} else if (code==LV_EVENT_RELEASED || code==LV_EVENT_PRESS_LOST) {
|
||||||
|
ctx->book_dragging=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_book_browser(AppCtx* ctx, lv_obj_t* parent) {
|
||||||
|
ctx->book_browser = lv_obj_create(parent);
|
||||||
|
lv_obj_set_size(ctx->book_browser, LV_PCT(100), LV_PCT(100));
|
||||||
|
lv_obj_align(ctx->book_browser, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
lv_obj_set_style_bg_color(ctx->book_browser, lv_color_hex(0x0E0E14), 0);
|
||||||
|
lv_obj_set_style_bg_opa(ctx->book_browser, LV_OPA_COVER, 0);
|
||||||
|
lv_obj_set_style_border_width(ctx->book_browser, 0, 0);
|
||||||
|
lv_obj_set_style_pad_all(ctx->book_browser, 0, 0);
|
||||||
|
lv_obj_set_style_radius(ctx->book_browser, 0, 0);
|
||||||
|
|
||||||
|
// Swipeable full-center area - no top bar
|
||||||
|
lv_obj_t* center_wrap = lv_obj_create(ctx->book_browser);
|
||||||
|
lv_obj_set_size(center_wrap, LV_PCT(100), LV_PCT(100));
|
||||||
|
lv_obj_set_style_pad_top(center_wrap, 0, 0);
|
||||||
|
lv_obj_set_style_pad_bottom(center_wrap, 64, 0);
|
||||||
|
lv_obj_set_style_bg_opa(center_wrap, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(center_wrap, 0, 0);
|
||||||
|
lv_obj_remove_flag(center_wrap, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_align(center_wrap, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
lv_obj_add_flag(center_wrap, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_PRESSED, ctx);
|
||||||
|
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_PRESSING, ctx);
|
||||||
|
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_RELEASED, ctx);
|
||||||
|
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_PRESS_LOST, ctx);
|
||||||
|
|
||||||
|
// Left arrow
|
||||||
|
lv_obj_t* btn_l = lv_button_create(center_wrap);
|
||||||
|
lv_obj_set_size(btn_l, 44, 44);
|
||||||
|
lv_obj_align(btn_l, LV_ALIGN_LEFT_MID, 8, 0);
|
||||||
|
lv_obj_set_style_radius(btn_l, 22, 0);
|
||||||
|
lv_obj_set_style_bg_color(btn_l, lv_color_hex(0x1E1E2E), 0);
|
||||||
|
lv_obj_t* l_l = lv_label_create(btn_l); lv_label_set_text(l_l, LV_SYMBOL_PREV); lv_obj_center(l_l);
|
||||||
|
lv_obj_add_event_cb(btn_l, on_dial_prev_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
|
// Center — book name directly, 50% larger, tappable (no card)
|
||||||
|
lv_obj_t* mid_wrap = lv_obj_create(center_wrap);
|
||||||
|
lv_obj_set_size(mid_wrap, LV_PCT(68), LV_SIZE_CONTENT);
|
||||||
|
lv_obj_align(mid_wrap, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
lv_obj_set_style_bg_opa(mid_wrap, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(mid_wrap, 0, 0);
|
||||||
|
lv_obj_set_style_pad_all(mid_wrap, 0, 0);
|
||||||
|
lv_obj_set_style_pad_gap(mid_wrap, 10, 0);
|
||||||
|
lv_obj_set_flex_flow(mid_wrap, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(mid_wrap, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
lv_obj_remove_flag(mid_wrap, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_add_flag(mid_wrap, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_add_event_cb(mid_wrap, on_book_center_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
|
ctx->book_center_name = lv_label_create(mid_wrap);
|
||||||
|
lv_label_set_text(ctx->book_center_name, "Genesis");
|
||||||
|
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(0xE8E6E0), 0);
|
||||||
|
lv_obj_set_style_text_font(ctx->book_center_name, lvgl_get_text_font(FONT_SIZE_LARGE), 0); // 50% bigger: LARGE vs DEFAULT
|
||||||
|
lv_obj_set_style_text_letter_space(ctx->book_center_name, 0, 0);
|
||||||
|
lv_label_set_long_mode(ctx->book_center_name, LV_LABEL_LONG_MODE_DOTS);
|
||||||
|
lv_obj_set_width(ctx->book_center_name, LV_PCT(100));
|
||||||
|
lv_obj_set_style_text_align(ctx->book_center_name, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
|
||||||
|
ctx->book_center_verses = lv_label_create(mid_wrap);
|
||||||
|
lv_label_set_text(ctx->book_center_verses, "—");
|
||||||
|
lv_obj_set_style_text_color(ctx->book_center_verses, lv_color_hex(0x5A5A78), 0);
|
||||||
|
lv_obj_set_style_text_font(ctx->book_center_verses, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||||
|
lv_obj_set_style_text_align(ctx->book_center_verses, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
|
||||||
|
// Right arrow
|
||||||
|
lv_obj_t* btn_r = lv_button_create(center_wrap);
|
||||||
|
lv_obj_set_size(btn_r, 44, 44);
|
||||||
|
lv_obj_align(btn_r, LV_ALIGN_RIGHT_MID, -8, 0);
|
||||||
|
lv_obj_set_style_radius(btn_r, 22, 0);
|
||||||
|
lv_obj_set_style_bg_color(btn_r, lv_color_hex(0x1E1E2E), 0);
|
||||||
|
lv_obj_t* l_r = lv_label_create(btn_r); lv_label_set_text(l_r, LV_SYMBOL_NEXT); lv_obj_center(l_r);
|
||||||
|
lv_obj_add_event_cb(btn_r, on_dial_next_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
|
// Bottom bar: [X cancel] [slider] [N / 66]
|
||||||
|
lv_obj_t* bottom = lv_obj_create(ctx->book_browser);
|
||||||
|
lv_obj_set_size(bottom, LV_PCT(100), 56);
|
||||||
|
lv_obj_align(bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||||
|
lv_obj_set_style_bg_color(bottom, lv_color_hex(0x15151F), 0);
|
||||||
|
lv_obj_set_style_bg_opa(bottom, LV_OPA_COVER, 0);
|
||||||
|
lv_obj_set_style_border_width(bottom, 1, 0);
|
||||||
|
lv_obj_set_style_border_side(bottom, LV_BORDER_SIDE_TOP, 0);
|
||||||
|
lv_obj_set_style_border_color(bottom, lv_color_hex(0x232338), 0);
|
||||||
|
lv_obj_set_style_radius(bottom, 0, 0);
|
||||||
|
lv_obj_set_style_pad_hor(bottom, 10, 0);
|
||||||
|
lv_obj_set_style_pad_ver(bottom, 8, 0);
|
||||||
|
lv_obj_set_style_pad_gap(bottom, 10, 0);
|
||||||
|
lv_obj_set_flex_flow(bottom, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(bottom, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
lv_obj_remove_flag(bottom, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
|
lv_obj_t* btn_cancel = lv_button_create(bottom);
|
||||||
|
lv_obj_set_size(btn_cancel, 36, 32);
|
||||||
|
lv_obj_set_style_radius(btn_cancel, 8, 0);
|
||||||
|
lv_obj_set_style_bg_color(btn_cancel, lv_color_hex(0x252538), 0);
|
||||||
|
lv_obj_t* l_cancel = lv_label_create(btn_cancel); lv_label_set_text(l_cancel, LV_SYMBOL_CLOSE); lv_obj_center(l_cancel);
|
||||||
|
lv_obj_add_event_cb(btn_cancel, on_books_close_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
|
ctx->book_slider = lv_slider_create(bottom);
|
||||||
|
lv_obj_set_flex_grow(ctx->book_slider, 1);
|
||||||
|
lv_obj_set_height(ctx->book_slider, 8);
|
||||||
|
lv_slider_set_range(ctx->book_slider, 0, ctx->book_count>0 ? ctx->book_count-1 : 65);
|
||||||
|
lv_obj_set_style_bg_color(ctx->book_slider, lv_color_hex(0x232338), LV_PART_MAIN);
|
||||||
|
lv_obj_set_style_bg_color(ctx->book_slider, lv_color_hex(0x4A4A6E), LV_PART_INDICATOR);
|
||||||
|
lv_obj_set_style_bg_color(ctx->book_slider, lv_color_hex(0xE8E6E0), LV_PART_KNOB);
|
||||||
|
lv_obj_add_event_cb(ctx->book_slider, on_slider_changed, LV_EVENT_VALUE_CHANGED, ctx);
|
||||||
|
|
||||||
|
ctx->book_center_num = lv_label_create(bottom);
|
||||||
|
lv_label_set_text(ctx->book_center_num, "1 / 66");
|
||||||
|
lv_obj_set_style_text_color(ctx->book_center_num, lv_color_hex(0x8A8AAA), 0);
|
||||||
|
lv_obj_set_style_text_font(ctx->book_center_num, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||||
|
lv_obj_set_style_pad_left(ctx->book_center_num, 4, 0);
|
||||||
|
|
||||||
|
lv_obj_add_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
ctx->book_browser_visible = false;
|
||||||
|
ctx->book_dial_idx = 0;
|
||||||
|
ctx->book_drag_start_x = 0;
|
||||||
|
ctx->book_dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── UI callbacks ── */
|
/* ── UI callbacks ── */
|
||||||
static void on_verse_tap(lv_event_t* e) {
|
static void on_verse_tap(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
toggle_ui(ctx);
|
toggle_ui(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void chrome_auto_hide_cb(lv_timer_t* t) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_timer_get_user_data(t);
|
||||||
|
if (!ctx) return;
|
||||||
|
// Don't hide if book browser open
|
||||||
|
if (ctx->book_browser_visible) return;
|
||||||
|
if (ctx->ui_visible) set_chrome_visible(ctx, false);
|
||||||
|
if (ctx->chrome_auto_hide_timer) {
|
||||||
|
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||||
|
ctx->chrome_auto_hide_timer = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void schedule_chrome_auto_hide(AppCtx* ctx, uint32_t delay_ms) {
|
||||||
|
if (ctx->chrome_auto_hide_timer) {
|
||||||
|
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||||
|
ctx->chrome_auto_hide_timer = NULL;
|
||||||
|
}
|
||||||
|
ctx->chrome_auto_hide_timer = lv_timer_create(chrome_auto_hide_cb, delay_ms, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
static void toggle_ui(AppCtx* ctx) {
|
static void toggle_ui(AppCtx* ctx) {
|
||||||
set_chrome_visible(ctx, !ctx->ui_visible);
|
bool new_vis = !ctx->ui_visible;
|
||||||
|
set_chrome_visible(ctx, new_vis);
|
||||||
|
if (new_vis) {
|
||||||
|
schedule_chrome_auto_hide(ctx, 10000); // hide again after 10s if user shows via tap
|
||||||
|
} else {
|
||||||
|
if (ctx->chrome_auto_hide_timer) {
|
||||||
|
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||||
|
ctx->chrome_auto_hide_timer = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_chrome_visible(AppCtx* ctx, bool visible) {
|
static void set_chrome_visible(AppCtx* ctx, bool visible) {
|
||||||
@@ -721,12 +1127,16 @@ static void on_close_click(lv_event_t* e) {
|
|||||||
(void)ctx;
|
(void)ctx;
|
||||||
tt_app_stop();
|
tt_app_stop();
|
||||||
}
|
}
|
||||||
// Future hooks
|
// Future hooks merged into books click
|
||||||
static void on_memo_click(lv_event_t* e) {
|
static void on_books_click(lv_event_t* e) {
|
||||||
// Placeholder: in future record voice memo to /sdcard/bible_memos/<global>.wav
|
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
const char* btns[] = {"OK"};
|
toggle_book_browser(ctx);
|
||||||
tt_app_alertdialog_start("Voice Memo", "Voice memo recording will be added in a future version.\nFile will be saved under /sdcard/bible_memos/", btns, 1);
|
}
|
||||||
|
|
||||||
|
static void on_title_click(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
// Tapping title when chrome visible opens books
|
||||||
|
if (ctx->ui_visible) toggle_book_browser(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── App lifecycle ── */
|
/* ── App lifecycle ── */
|
||||||
@@ -743,6 +1153,21 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
snprintf(g_ctx.progress_path, sizeof(g_ctx.progress_path), "%s/progress.txt", ud_path);
|
snprintf(g_ctx.progress_path, sizeof(g_ctx.progress_path), "%s/progress.txt", ud_path);
|
||||||
snprintf(g_ctx.favorites_path, sizeof(g_ctx.favorites_path), "%s/favorites.txt", ud_path);
|
snprintf(g_ctx.favorites_path, sizeof(g_ctx.favorites_path), "%s/favorites.txt", ud_path);
|
||||||
|
|
||||||
|
// ── Load pretty serif fonts (embedded C - Georgia Italic like PSALMS reference) ──
|
||||||
|
// Embedded fonts are in PSRAM via ELF LOADER LOAD PSRAM (CONFIG_ELF_LOADER_LOAD_PSRAM=y)
|
||||||
|
// No VFS needed - direct pointers to static const lv_font_t
|
||||||
|
{
|
||||||
|
g_ctx.font_serif_26 = (lv_font_t*)&georgia_italic_26;
|
||||||
|
g_ctx.font_serif_22 = (lv_font_t*)&georgia_italic_22;
|
||||||
|
g_ctx.font_serif_20 = (lv_font_t*)&georgia_italic_20;
|
||||||
|
g_ctx.font_serif_18 = (lv_font_t*)&georgia_italic_18;
|
||||||
|
g_ctx.font_serif_16 = (lv_font_t*)&georgia_italic_16;
|
||||||
|
g_ctx.font_book_24 = (lv_font_t*)&georgia_regular_24;
|
||||||
|
g_ctx.fonts_loaded = true;
|
||||||
|
ESP_LOGI(TAG, "Embedded serif fonts loaded: 26=%p 22=%p 20=%p 18=%p 16=%p book24=%p",
|
||||||
|
g_ctx.font_serif_26, g_ctx.font_serif_22, g_ctx.font_serif_20, g_ctx.font_serif_18, g_ctx.font_serif_16, g_ctx.font_book_24);
|
||||||
|
}
|
||||||
|
|
||||||
resolve_bible_paths(&g_ctx);
|
resolve_bible_paths(&g_ctx);
|
||||||
|
|
||||||
if (!load_books_manifest(&g_ctx)) {
|
if (!load_books_manifest(&g_ctx)) {
|
||||||
@@ -774,15 +1199,16 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lv_obj_add_flag(g_ctx.verse_wrapper, LV_OBJ_FLAG_CLICKABLE);
|
lv_obj_add_flag(g_ctx.verse_wrapper, LV_OBJ_FLAG_CLICKABLE);
|
||||||
lv_obj_add_event_cb(g_ctx.verse_wrapper, on_verse_tap, LV_EVENT_CLICKED, &g_ctx);
|
lv_obj_add_event_cb(g_ctx.verse_wrapper, on_verse_tap, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
// Center reading column with generous breathing room
|
// Center reading column with generous breathing room - avoid top chrome crop
|
||||||
lv_obj_t* center_col = lv_obj_create(g_ctx.verse_wrapper);
|
lv_obj_t* center_col = lv_obj_create(g_ctx.verse_wrapper);
|
||||||
lv_obj_set_size(center_col, LV_PCT(90), LV_PCT(80));
|
g_ctx.center_col = center_col;
|
||||||
lv_obj_center(center_col);
|
lv_obj_set_size(center_col, LV_PCT(90), LV_PCT(82)); // 80->82 but with top-safe padding
|
||||||
|
lv_obj_align(center_col, LV_ALIGN_CENTER, 0, -7); // up 15px: was +8 base, now -7 (user corrected 30->15)
|
||||||
lv_obj_set_style_bg_opa(center_col, LV_OPA_TRANSP, 0);
|
lv_obj_set_style_bg_opa(center_col, LV_OPA_TRANSP, 0);
|
||||||
lv_obj_set_style_border_width(center_col, 0, 0);
|
lv_obj_set_style_border_width(center_col, 0, 0);
|
||||||
lv_obj_set_flex_flow(center_col, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(center_col, LV_FLEX_FLOW_COLUMN);
|
||||||
lv_obj_set_flex_align(center_col, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
lv_obj_set_flex_align(center_col, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
lv_obj_set_style_pad_gap(center_col, 14, 0);
|
lv_obj_set_style_pad_gap(center_col, 10, 0); // tightened: ref closer to verse (was 14)
|
||||||
lv_obj_set_style_pad_all(center_col, 0, 0);
|
lv_obj_set_style_pad_all(center_col, 0, 0);
|
||||||
lv_obj_remove_flag(center_col, LV_OBJ_FLAG_SCROLLABLE);
|
lv_obj_remove_flag(center_col, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
lv_obj_add_flag(center_col, LV_OBJ_FLAG_CLICKABLE);
|
lv_obj_add_flag(center_col, LV_OBJ_FLAG_CLICKABLE);
|
||||||
@@ -791,36 +1217,34 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
g_ctx.lbl_verse = lv_label_create(center_col);
|
g_ctx.lbl_verse = lv_label_create(center_col);
|
||||||
lv_obj_set_width(g_ctx.lbl_verse, LV_PCT(100));
|
lv_obj_set_width(g_ctx.lbl_verse, LV_PCT(100));
|
||||||
lv_label_set_long_mode(g_ctx.lbl_verse, LV_LABEL_LONG_MODE_WRAP);
|
lv_label_set_long_mode(g_ctx.lbl_verse, LV_LABEL_LONG_MODE_WRAP);
|
||||||
lv_obj_set_style_text_color(g_ctx.lbl_verse, lv_color_hex(0xE8E6E0), 0);
|
lv_obj_set_style_text_color(g_ctx.lbl_verse, lv_color_hex(0xF2F0E8), 0); // warmer, editorial like reference image (was #E8E6E0)
|
||||||
lv_obj_set_style_text_font(g_ctx.lbl_verse, lvgl_get_text_font(FONT_SIZE_LARGE), 0);
|
lv_obj_set_style_text_font(g_ctx.lbl_verse, lvgl_get_text_font(FONT_SIZE_DEFAULT), 0); // reduced base - Georgia italic wider than Montserrat
|
||||||
lv_obj_set_style_text_align(g_ctx.lbl_verse, LV_TEXT_ALIGN_CENTER, 0);
|
lv_obj_set_style_text_align(g_ctx.lbl_verse, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
lv_obj_set_style_text_line_space(g_ctx.lbl_verse, 8, 0);
|
lv_obj_set_style_text_line_space(g_ctx.lbl_verse, 8, 0);
|
||||||
lv_obj_set_style_text_letter_space(g_ctx.lbl_verse, 0, 0);
|
lv_obj_set_style_text_letter_space(g_ctx.lbl_verse, 0, 0);
|
||||||
lv_obj_set_style_radius(g_ctx.lbl_verse, 16, 0);
|
lv_obj_set_style_radius(g_ctx.lbl_verse, 0, 0); // no bubble - flat editorial
|
||||||
lv_obj_set_style_pad_all(g_ctx.lbl_verse, 16, 0);
|
lv_obj_set_style_pad_all(g_ctx.lbl_verse, 4, 0); // less padding, avoid crop
|
||||||
lv_label_set_text(g_ctx.lbl_verse, "Loading...");
|
lv_label_set_text(g_ctx.lbl_verse, "Loading...");
|
||||||
|
|
||||||
g_ctx.lbl_ref = lv_label_create(center_col);
|
g_ctx.lbl_ref = lv_label_create(center_col);
|
||||||
lv_obj_set_style_text_color(g_ctx.lbl_ref, lv_color_hex(0x7A7A96), 0);
|
lv_obj_set_style_text_color(g_ctx.lbl_ref, lv_color_hex(0x9A9AA8), 0); // softer, like PSALMS citation gray
|
||||||
lv_obj_set_style_text_font(g_ctx.lbl_ref, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
lv_obj_set_style_text_font(g_ctx.lbl_ref, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||||
lv_obj_set_style_text_align(g_ctx.lbl_ref, LV_TEXT_ALIGN_CENTER, 0);
|
lv_obj_set_style_text_align(g_ctx.lbl_ref, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
lv_obj_set_style_text_letter_space(g_ctx.lbl_ref, 1, 0);
|
lv_obj_set_style_text_letter_space(g_ctx.lbl_ref, 2, 0); // tracking like reference image's PSALMS 23:1
|
||||||
lv_label_set_text(g_ctx.lbl_ref, "");
|
lv_label_set_text(g_ctx.lbl_ref, "");
|
||||||
|
|
||||||
// ── Header bar (frosted dark) ──
|
// ── Header bar (frosted dark) - hidden by default to avoid black bar crop ──
|
||||||
|
// Fix black bar cropping text: make truly transparent, no border, same bg as root #0E0E14
|
||||||
g_ctx.header_bar = lv_obj_create(parent);
|
g_ctx.header_bar = lv_obj_create(parent);
|
||||||
lv_obj_t* hb = g_ctx.header_bar;
|
lv_obj_t* hb = g_ctx.header_bar;
|
||||||
lv_obj_set_size(hb, LV_PCT(100), 44);
|
lv_obj_set_size(hb, LV_PCT(100), 36); // reduced 44->36 to avoid crop
|
||||||
lv_obj_align(hb, LV_ALIGN_TOP_MID, 0, 0);
|
lv_obj_align(hb, LV_ALIGN_TOP_MID, 0, 0);
|
||||||
lv_obj_set_style_radius(hb, 0, 0);
|
lv_obj_set_style_radius(hb, 0, 0);
|
||||||
lv_obj_set_style_bg_color(hb, lv_color_hex(0x15151F), 0);
|
lv_obj_set_style_bg_color(hb, lv_color_hex(0x0E0E14), 0); // same as root, not #15151F
|
||||||
lv_obj_set_style_bg_opa(hb, LV_OPA_COVER, 0);
|
lv_obj_set_style_bg_opa(hb, LV_OPA_TRANSP, 0); // transparent to avoid black bar - was COVER
|
||||||
lv_obj_set_style_border_width(hb, 0, 0);
|
lv_obj_set_style_border_width(hb, 0, 0);
|
||||||
lv_obj_set_style_border_side(hb, LV_BORDER_SIDE_BOTTOM, 0);
|
|
||||||
lv_obj_set_style_border_color(hb, lv_color_hex(0x232338), 0);
|
|
||||||
lv_obj_set_style_border_width(hb, 1, 0);
|
|
||||||
lv_obj_set_style_pad_hor(hb, 12, 0);
|
lv_obj_set_style_pad_hor(hb, 12, 0);
|
||||||
lv_obj_set_style_pad_ver(hb, 0, 0);
|
lv_obj_set_style_pad_ver(hb, 4, 0);
|
||||||
lv_obj_remove_flag(hb, LV_OBJ_FLAG_SCROLLABLE);
|
lv_obj_remove_flag(hb, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
g_ctx.lbl_book_title = lv_label_create(hb);
|
g_ctx.lbl_book_title = lv_label_create(hb);
|
||||||
@@ -828,6 +1252,8 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lv_obj_set_style_text_color(g_ctx.lbl_book_title, lv_color_hex(0xE8E6E0), 0);
|
lv_obj_set_style_text_color(g_ctx.lbl_book_title, lv_color_hex(0xE8E6E0), 0);
|
||||||
lv_obj_set_style_text_font(g_ctx.lbl_book_title, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
lv_obj_set_style_text_font(g_ctx.lbl_book_title, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||||
lv_label_set_text(g_ctx.lbl_book_title, "Bible");
|
lv_label_set_text(g_ctx.lbl_book_title, "Bible");
|
||||||
|
lv_obj_add_flag(g_ctx.lbl_book_title, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_add_event_cb(g_ctx.lbl_book_title, on_title_click, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
g_ctx.lbl_position = lv_label_create(hb);
|
g_ctx.lbl_position = lv_label_create(hb);
|
||||||
lv_obj_align(g_ctx.lbl_position, LV_ALIGN_RIGHT_MID, 0, 0);
|
lv_obj_align(g_ctx.lbl_position, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||||
@@ -841,18 +1267,15 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lv_obj_set_size(cb, LV_PCT(100), 56);
|
lv_obj_set_size(cb, LV_PCT(100), 56);
|
||||||
lv_obj_align(cb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
lv_obj_align(cb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||||
lv_obj_set_style_radius(cb, 0, 0);
|
lv_obj_set_style_radius(cb, 0, 0);
|
||||||
lv_obj_set_style_bg_color(cb, lv_color_hex(0x15151F), 0);
|
lv_obj_set_style_bg_color(cb, lv_color_hex(0x0E0E14), 0);
|
||||||
lv_obj_set_style_bg_opa(cb, LV_OPA_COVER, 0);
|
lv_obj_set_style_bg_opa(cb, LV_OPA_TRANSP, 0); // transparent editorial, was #15151F COVER
|
||||||
lv_obj_set_style_border_width(cb, 1, 0);
|
lv_obj_set_style_border_width(cb, 0, 0);
|
||||||
lv_obj_set_style_border_side(cb, LV_BORDER_SIDE_TOP, 0);
|
|
||||||
lv_obj_set_style_border_color(cb, lv_color_hex(0x232338), 0);
|
|
||||||
lv_obj_set_style_pad_all(cb, 6, 0);
|
lv_obj_set_style_pad_all(cb, 6, 0);
|
||||||
lv_obj_set_style_pad_gap(cb, 4, 0);
|
lv_obj_set_style_pad_gap(cb, 4, 0);
|
||||||
lv_obj_set_flex_flow(cb, LV_FLEX_FLOW_ROW);
|
lv_obj_set_flex_flow(cb, LV_FLEX_FLOW_ROW);
|
||||||
lv_obj_set_flex_align(cb, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
lv_obj_set_flex_align(cb, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
lv_obj_remove_flag(cb, LV_OBJ_FLAG_SCROLLABLE);
|
lv_obj_remove_flag(cb, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
lv_obj_t* btn;
|
|
||||||
lv_obj_t* lbl;
|
lv_obj_t* lbl;
|
||||||
|
|
||||||
g_ctx.btn_close = lv_button_create(cb);
|
g_ctx.btn_close = lv_button_create(cb);
|
||||||
@@ -863,6 +1286,14 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lbl = lv_label_create(g_ctx.btn_close); lv_label_set_text(lbl, LV_SYMBOL_CLOSE); lv_obj_center(lbl);
|
lbl = lv_label_create(g_ctx.btn_close); lv_label_set_text(lbl, LV_SYMBOL_CLOSE); lv_obj_center(lbl);
|
||||||
lv_obj_add_event_cb(g_ctx.btn_close, on_close_click, LV_EVENT_CLICKED, &g_ctx);
|
lv_obj_add_event_cb(g_ctx.btn_close, on_close_click, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
|
g_ctx.btn_books = lv_button_create(cb);
|
||||||
|
lv_obj_set_size(g_ctx.btn_books, 44, 38);
|
||||||
|
lv_obj_set_style_radius(g_ctx.btn_books, 10, 0);
|
||||||
|
lv_obj_set_style_bg_color(g_ctx.btn_books, lv_color_hex(0x252538), 0);
|
||||||
|
lv_obj_set_style_text_color(g_ctx.btn_books, lv_color_hex(0xC4C4D4), 0);
|
||||||
|
lbl = lv_label_create(g_ctx.btn_books); lv_label_set_text(lbl, LV_SYMBOL_LIST); lv_obj_center(lbl);
|
||||||
|
lv_obj_add_event_cb(g_ctx.btn_books, on_books_click, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
g_ctx.btn_prev = lv_button_create(cb);
|
g_ctx.btn_prev = lv_button_create(cb);
|
||||||
lv_obj_set_size(g_ctx.btn_prev, 44, 38);
|
lv_obj_set_size(g_ctx.btn_prev, 44, 38);
|
||||||
lv_obj_set_style_radius(g_ctx.btn_prev, 10, 0);
|
lv_obj_set_style_radius(g_ctx.btn_prev, 10, 0);
|
||||||
@@ -872,7 +1303,7 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lv_obj_add_event_cb(g_ctx.btn_prev, on_prev_click, LV_EVENT_CLICKED, &g_ctx);
|
lv_obj_add_event_cb(g_ctx.btn_prev, on_prev_click, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
g_ctx.btn_play_pause = lv_button_create(cb);
|
g_ctx.btn_play_pause = lv_button_create(cb);
|
||||||
lv_obj_set_size(g_ctx.btn_play_pause, 76, 38);
|
lv_obj_set_size(g_ctx.btn_play_pause, 56, 38);
|
||||||
lv_obj_set_style_radius(g_ctx.btn_play_pause, 19, 0);
|
lv_obj_set_style_radius(g_ctx.btn_play_pause, 19, 0);
|
||||||
lv_obj_set_style_bg_color(g_ctx.btn_play_pause, lv_color_hex(0x2A2A4A), 0);
|
lv_obj_set_style_bg_color(g_ctx.btn_play_pause, lv_color_hex(0x2A2A4A), 0);
|
||||||
lv_obj_set_style_text_color(g_ctx.btn_play_pause, lv_color_hex(0xE8E6E0), 0);
|
lv_obj_set_style_text_color(g_ctx.btn_play_pause, lv_color_hex(0xE8E6E0), 0);
|
||||||
@@ -888,23 +1319,20 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lv_obj_add_event_cb(g_ctx.btn_next, on_next_click, LV_EVENT_CLICKED, &g_ctx);
|
lv_obj_add_event_cb(g_ctx.btn_next, on_next_click, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
g_ctx.btn_highlight = lv_button_create(cb);
|
g_ctx.btn_highlight = lv_button_create(cb);
|
||||||
lv_obj_set_size(g_ctx.btn_highlight, 44, 38);
|
lv_obj_set_size(g_ctx.btn_highlight, 38, 38);
|
||||||
lv_obj_set_style_radius(g_ctx.btn_highlight, 10, 0);
|
lv_obj_set_style_radius(g_ctx.btn_highlight, 10, 0);
|
||||||
lv_obj_set_style_bg_color(g_ctx.btn_highlight, lv_color_hex(0x252538), 0);
|
lv_obj_set_style_bg_color(g_ctx.btn_highlight, lv_color_hex(0x252538), 0);
|
||||||
lv_obj_set_style_text_color(g_ctx.btn_highlight, lv_color_hex(0x8C8CB0), 0);
|
lv_obj_set_style_text_color(g_ctx.btn_highlight, lv_color_hex(0x8C8CB0), 0);
|
||||||
lbl = lv_label_create(g_ctx.btn_highlight); lv_label_set_text(lbl, LV_SYMBOL_DUMMY); lv_obj_center(lbl);
|
lbl = lv_label_create(g_ctx.btn_highlight); lv_label_set_text(lbl, LV_SYMBOL_DUMMY); lv_obj_center(lbl);
|
||||||
lv_obj_add_event_cb(g_ctx.btn_highlight, on_highlight_click, LV_EVENT_CLICKED, &g_ctx);
|
lv_obj_add_event_cb(g_ctx.btn_highlight, on_highlight_click, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
btn = lv_button_create(cb);
|
set_chrome_visible(&g_ctx, true); // start with menu showing per user request
|
||||||
lv_obj_set_size(btn, 38, 38);
|
schedule_chrome_auto_hide(&g_ctx, 10000); // auto hide after 10s
|
||||||
lv_obj_set_style_radius(btn, 10, 0);
|
|
||||||
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1E1E2E), 0);
|
|
||||||
lv_obj_set_style_text_color(btn, lv_color_hex(0x6A6A8A), 0);
|
|
||||||
lbl = lv_label_create(btn); lv_label_set_text(lbl, LV_SYMBOL_AUDIO); lv_obj_center(lbl);
|
|
||||||
lv_obj_add_event_cb(btn, on_memo_click, LV_EVENT_CLICKED, &g_ctx);
|
|
||||||
|
|
||||||
set_chrome_visible(&g_ctx, false);
|
|
||||||
show_current_verse(&g_ctx);
|
show_current_verse(&g_ctx);
|
||||||
|
|
||||||
|
// Build book browser overlay (on top of everything)
|
||||||
|
build_book_browser(&g_ctx, parent);
|
||||||
|
|
||||||
g_ctx.auto_timer = lv_timer_create(on_auto_timer, AUTO_ADV_MS, &g_ctx);
|
g_ctx.auto_timer = lv_timer_create(on_auto_timer, AUTO_ADV_MS, &g_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,6 +1342,11 @@ static void onHideApp(AppHandle app, void* data) {
|
|||||||
lv_timer_delete(g_ctx.auto_timer);
|
lv_timer_delete(g_ctx.auto_timer);
|
||||||
g_ctx.auto_timer = NULL;
|
g_ctx.auto_timer = NULL;
|
||||||
}
|
}
|
||||||
|
if (g_ctx.chrome_auto_hide_timer) {
|
||||||
|
lv_timer_delete(g_ctx.chrome_auto_hide_timer);
|
||||||
|
g_ctx.chrome_auto_hide_timer = NULL;
|
||||||
|
}
|
||||||
|
// Embedded fonts are static const - no free needed (live in PSRAM ELF)
|
||||||
save_progress(&g_ctx);
|
save_progress(&g_ctx);
|
||||||
unload_book(&g_ctx);
|
unload_book(&g_ctx);
|
||||||
// UI objects are deleted by LVGL parent cleanup
|
// UI objects are deleted by LVGL parent cleanup
|
||||||
|
|||||||
Reference in New Issue
Block a user