latest change on laptop Bible and SDDownload
This commit is contained in:
@@ -31,6 +31,7 @@ LV_FONT_DECLARE(georgia_regular_24)
|
|||||||
// books.json : array of {bnumber, bname, verses, chapters}
|
// books.json : array of {bnumber, bname, verses, chapters}
|
||||||
|
|
||||||
#define MAX_BIBLE_BOOKS 66
|
#define MAX_BIBLE_BOOKS 66
|
||||||
|
#define MAX_FAVORITES 512
|
||||||
#define MAX_BOOK_NAME 40
|
#define MAX_BOOK_NAME 40
|
||||||
#define MAX_VERSE_TEXT 2048
|
#define MAX_VERSE_TEXT 2048
|
||||||
#define MAX_PATH 128
|
#define MAX_PATH 128
|
||||||
@@ -115,11 +116,23 @@ typedef struct {
|
|||||||
lv_obj_t* book_center_num;
|
lv_obj_t* book_center_num;
|
||||||
lv_obj_t* book_center_verses;
|
lv_obj_t* book_center_verses;
|
||||||
lv_obj_t* book_slider; // fast scrub
|
lv_obj_t* book_slider; // fast scrub
|
||||||
|
lv_obj_t* btn_favorites;
|
||||||
int book_dial_idx; // 0..book_count-1 currently selected
|
int book_dial_idx; // 0..book_count-1 currently selected
|
||||||
|
int favorite_indices[MAX_FAVORITES];
|
||||||
|
int favorite_count;
|
||||||
|
int favorite_dial_idx;
|
||||||
|
bool browsing_favorites;
|
||||||
|
bool favorite_navigation_active;
|
||||||
int book_drag_start_x;
|
int book_drag_start_x;
|
||||||
bool book_dragging;
|
bool book_dragging;
|
||||||
bool book_browser_visible;
|
bool book_browser_visible;
|
||||||
|
|
||||||
|
// Reading-view swipe tracking
|
||||||
|
int verse_drag_start_x;
|
||||||
|
int verse_drag_start_y;
|
||||||
|
bool verse_dragging;
|
||||||
|
bool verse_swipe_consumed;
|
||||||
|
|
||||||
// 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
|
lv_timer_t* chrome_auto_hide_timer; // menu auto-hide after 10s on start
|
||||||
@@ -135,6 +148,7 @@ 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 manually_advance_verse(AppCtx* ctx, int delta);
|
||||||
static void jump_to_book(AppCtx* ctx, int book_idx);
|
static void jump_to_book(AppCtx* ctx, int book_idx);
|
||||||
static void jump_to_global(AppCtx* ctx, int global_idx);
|
static void jump_to_global(AppCtx* ctx, int global_idx);
|
||||||
static void save_progress(AppCtx* ctx);
|
static void save_progress(AppCtx* ctx);
|
||||||
@@ -142,6 +156,7 @@ static bool load_progress(AppCtx* ctx);
|
|||||||
static void toggle_ui(AppCtx* ctx);
|
static void toggle_ui(AppCtx* ctx);
|
||||||
static void set_chrome_visible(AppCtx* ctx, bool visible);
|
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 load_favorites(AppCtx* ctx);
|
||||||
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 set_book_browser_visible(AppCtx* ctx, bool visible);
|
||||||
@@ -524,13 +539,33 @@ static bool is_favorite(AppCtx* ctx, int global_idx) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void load_favorites(AppCtx* ctx) {
|
||||||
|
ctx->favorite_count = 0;
|
||||||
|
FILE* f = fopen(ctx->favorites_path, "r");
|
||||||
|
if (!f) return;
|
||||||
|
int global_idx;
|
||||||
|
while (ctx->favorite_count < MAX_FAVORITES && fscanf(f, "%d", &global_idx) == 1) {
|
||||||
|
if (global_idx >= 0 && global_idx < ctx->total_verses) {
|
||||||
|
ctx->favorite_indices[ctx->favorite_count++] = global_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_favorite_position(AppCtx* ctx, int global_idx) {
|
||||||
|
for (int i = 0; i < ctx->favorite_count; i++) {
|
||||||
|
if (ctx->favorite_indices[i] == global_idx) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
static void toggle_favorite(AppCtx* ctx) {
|
static void toggle_favorite(AppCtx* ctx) {
|
||||||
// load all
|
// load all
|
||||||
int vals[512];
|
int vals[MAX_FAVORITES];
|
||||||
int cnt=0;
|
int cnt=0;
|
||||||
FILE* f = fopen(ctx->favorites_path, "r");
|
FILE* f = fopen(ctx->favorites_path, "r");
|
||||||
if (f) {
|
if (f) {
|
||||||
while (cnt<512 && fscanf(f, "%d", &vals[cnt])==1) cnt++;
|
while (cnt<MAX_FAVORITES && fscanf(f, "%d", &vals[cnt])==1) cnt++;
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
// check existence
|
// check existence
|
||||||
@@ -542,7 +577,7 @@ static void toggle_favorite(AppCtx* ctx) {
|
|||||||
cnt--;
|
cnt--;
|
||||||
ctx->is_highlighted=false;
|
ctx->is_highlighted=false;
|
||||||
} else {
|
} else {
|
||||||
if (cnt<512) {
|
if (cnt<MAX_FAVORITES) {
|
||||||
vals[cnt++]=ctx->cur_global;
|
vals[cnt++]=ctx->cur_global;
|
||||||
ctx->is_highlighted=true;
|
ctx->is_highlighted=true;
|
||||||
}
|
}
|
||||||
@@ -552,6 +587,7 @@ static void toggle_favorite(AppCtx* ctx) {
|
|||||||
if (!f) return;
|
if (!f) return;
|
||||||
for (int i=0;i<cnt;i++) fprintf(f, "%d\n", vals[i]);
|
for (int i=0;i<cnt;i++) fprintf(f, "%d\n", vals[i]);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
load_favorites(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── UI logic ── */
|
/* ── UI logic ── */
|
||||||
@@ -709,9 +745,24 @@ static void show_current_verse(AppCtx* ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx->is_highlighted = is_favorite(ctx, ctx->cur_global);
|
ctx->is_highlighted = is_favorite(ctx, ctx->cur_global);
|
||||||
|
if (ctx->favorite_navigation_active) {
|
||||||
|
load_favorites(ctx);
|
||||||
|
int favorite_pos = find_favorite_position(ctx, ctx->cur_global);
|
||||||
|
if (favorite_pos >= 0) {
|
||||||
|
ctx->favorite_dial_idx = favorite_pos;
|
||||||
|
if (ctx->lbl_position) {
|
||||||
|
char favorite_pos_buf[40];
|
||||||
|
snprintf(favorite_pos_buf, sizeof(favorite_pos_buf), "Fav %d / %d",
|
||||||
|
favorite_pos + 1, ctx->favorite_count);
|
||||||
|
lv_label_set_text(ctx->lbl_position, favorite_pos_buf);
|
||||||
|
}
|
||||||
|
} else if (ctx->favorite_count == 0) {
|
||||||
|
ctx->favorite_navigation_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ctx->btn_highlight) {
|
if (ctx->btn_highlight) {
|
||||||
lv_obj_t* lbl = lv_obj_get_child(ctx->btn_highlight, 0);
|
lv_obj_t* lbl = lv_obj_get_child(ctx->btn_highlight, 0);
|
||||||
if (lbl) lv_label_set_text(lbl, ctx->is_highlighted ? LV_SYMBOL_OK : LV_SYMBOL_DUMMY);
|
if (lbl) lv_label_set_text(lbl, ctx->is_highlighted ? LV_SYMBOL_OK : LV_SYMBOL_PLUS);
|
||||||
if (ctx->is_highlighted) {
|
if (ctx->is_highlighted) {
|
||||||
lv_obj_set_style_bg_color(ctx->btn_highlight, lv_color_hex(0xF5C16C), 0);
|
lv_obj_set_style_bg_color(ctx->btn_highlight, lv_color_hex(0xF5C16C), 0);
|
||||||
lv_obj_set_style_text_color(ctx->btn_highlight, lv_color_hex(0x1E1E2E), 0);
|
lv_obj_set_style_text_color(ctx->btn_highlight, lv_color_hex(0x1E1E2E), 0);
|
||||||
@@ -734,11 +785,17 @@ static void show_current_verse(AppCtx* ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->btn_prev) {
|
if (ctx->btn_prev) {
|
||||||
if (ctx->cur_global<=0) lv_obj_add_state(ctx->btn_prev, LV_STATE_DISABLED);
|
bool at_start = ctx->favorite_navigation_active
|
||||||
|
? ctx->favorite_dial_idx <= 0
|
||||||
|
: ctx->cur_global <= 0;
|
||||||
|
if (at_start) lv_obj_add_state(ctx->btn_prev, LV_STATE_DISABLED);
|
||||||
else lv_obj_clear_state(ctx->btn_prev, LV_STATE_DISABLED);
|
else lv_obj_clear_state(ctx->btn_prev, LV_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
if (ctx->btn_next) {
|
if (ctx->btn_next) {
|
||||||
if (ctx->cur_global >= ctx->total_verses-1) lv_obj_add_state(ctx->btn_next, LV_STATE_DISABLED);
|
bool at_end = ctx->favorite_navigation_active
|
||||||
|
? ctx->favorite_dial_idx >= ctx->favorite_count - 1
|
||||||
|
: ctx->cur_global >= ctx->total_verses - 1;
|
||||||
|
if (at_end) lv_obj_add_state(ctx->btn_next, LV_STATE_DISABLED);
|
||||||
else lv_obj_clear_state(ctx->btn_next, LV_STATE_DISABLED);
|
else lv_obj_clear_state(ctx->btn_next, LV_STATE_DISABLED);
|
||||||
}
|
}
|
||||||
free(verse_buf);
|
free(verse_buf);
|
||||||
@@ -747,6 +804,28 @@ static void show_current_verse(AppCtx* ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void advance_verse(AppCtx* ctx, int delta) {
|
static void advance_verse(AppCtx* ctx, int delta) {
|
||||||
|
if (ctx->favorite_navigation_active) {
|
||||||
|
load_favorites(ctx);
|
||||||
|
if (ctx->favorite_count <= 0) {
|
||||||
|
ctx->favorite_navigation_active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int current_pos = find_favorite_position(ctx, ctx->cur_global);
|
||||||
|
if (current_pos < 0) current_pos = ctx->favorite_dial_idx;
|
||||||
|
if (current_pos < 0) current_pos = 0;
|
||||||
|
if (current_pos >= ctx->favorite_count) current_pos = ctx->favorite_count - 1;
|
||||||
|
int next_pos = current_pos + delta;
|
||||||
|
if (next_pos < 0) next_pos = 0;
|
||||||
|
if (next_pos >= ctx->favorite_count) next_pos = ctx->favorite_count - 1;
|
||||||
|
ctx->favorite_dial_idx = next_pos;
|
||||||
|
int next_global = ctx->favorite_indices[next_pos];
|
||||||
|
if (next_global != ctx->cur_global) {
|
||||||
|
ctx->cur_global = next_global;
|
||||||
|
show_current_verse(ctx);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
if (ng >= ctx->total_verses) ng = ctx->total_verses-1;
|
||||||
@@ -756,6 +835,11 @@ static void advance_verse(AppCtx* ctx, int delta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void manually_advance_verse(AppCtx* ctx, int delta) {
|
||||||
|
advance_verse(ctx, delta);
|
||||||
|
if (ctx->auto_timer) lv_timer_reset(ctx->auto_timer);
|
||||||
|
}
|
||||||
|
|
||||||
static void jump_to_global(AppCtx* ctx, int global_idx) {
|
static void jump_to_global(AppCtx* ctx, int global_idx) {
|
||||||
if (global_idx < 0) global_idx = 0;
|
if (global_idx < 0) global_idx = 0;
|
||||||
if (global_idx >= ctx->total_verses) global_idx = ctx->total_verses-1;
|
if (global_idx >= ctx->total_verses) global_idx = ctx->total_verses-1;
|
||||||
@@ -769,6 +853,7 @@ static void jump_to_global(AppCtx* ctx, int global_idx) {
|
|||||||
|
|
||||||
static void jump_to_book(AppCtx* ctx, int book_idx) {
|
static void jump_to_book(AppCtx* ctx, int book_idx) {
|
||||||
if (book_idx < 0 || book_idx >= ctx->book_count) return;
|
if (book_idx < 0 || book_idx >= ctx->book_count) return;
|
||||||
|
ctx->favorite_navigation_active = false;
|
||||||
// compute global start of book
|
// compute global start of book
|
||||||
int global = 0;
|
int global = 0;
|
||||||
for (int i=0;i<book_idx;i++) global += ctx->books[i].verse_count;
|
for (int i=0;i<book_idx;i++) global += ctx->books[i].verse_count;
|
||||||
@@ -779,7 +864,10 @@ static void jump_to_book(AppCtx* ctx, int book_idx) {
|
|||||||
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);
|
||||||
if (ctx->auto_paused) return;
|
if (ctx->auto_paused) return;
|
||||||
if (ctx->cur_global < ctx->total_verses-1) {
|
bool has_next = ctx->favorite_navigation_active
|
||||||
|
? ctx->favorite_dial_idx < ctx->favorite_count - 1
|
||||||
|
: ctx->cur_global < ctx->total_verses - 1;
|
||||||
|
if (has_next) {
|
||||||
advance_verse(ctx, 1);
|
advance_verse(ctx, 1);
|
||||||
} else {
|
} else {
|
||||||
ctx->auto_paused = true;
|
ctx->auto_paused = true;
|
||||||
@@ -803,6 +891,51 @@ static void book_fade_anim_cb(void* var, int32_t v) {
|
|||||||
|
|
||||||
static void book_dial_update_center(AppCtx* ctx) {
|
static void book_dial_update_center(AppCtx* ctx) {
|
||||||
if (!ctx->book_center_name) return;
|
if (!ctx->book_center_name) return;
|
||||||
|
if (ctx->browsing_favorites) {
|
||||||
|
if (ctx->favorite_count <= 0) {
|
||||||
|
lv_label_set_text(ctx->book_center_name, "No favorites yet");
|
||||||
|
lv_label_set_text(ctx->book_center_verses, "Mark a verse with the check button, then return here.");
|
||||||
|
lv_label_set_text(ctx->book_center_num, "0 / 0");
|
||||||
|
lv_slider_set_range(ctx->book_slider, 0, 0);
|
||||||
|
lv_slider_set_value(ctx->book_slider, 0, LV_ANIM_OFF);
|
||||||
|
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(0xF5C16C), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->favorite_dial_idx < 0) ctx->favorite_dial_idx = 0;
|
||||||
|
if (ctx->favorite_dial_idx >= ctx->favorite_count) ctx->favorite_dial_idx = ctx->favorite_count - 1;
|
||||||
|
int global_idx = ctx->favorite_indices[ctx->favorite_dial_idx];
|
||||||
|
int remaining = global_idx;
|
||||||
|
int book_idx = 0;
|
||||||
|
while (book_idx < ctx->book_count - 1 &&
|
||||||
|
remaining >= ctx->books[book_idx].verse_count) {
|
||||||
|
remaining -= ctx->books[book_idx].verse_count;
|
||||||
|
book_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* verse_text = "Unable to load verse";
|
||||||
|
char ref[64];
|
||||||
|
snprintf(ref, sizeof(ref), "%s", ctx->books[book_idx].bname);
|
||||||
|
if (load_book(ctx, ctx->books[book_idx].bnumber) &&
|
||||||
|
remaining >= 0 && remaining < ctx->book_idx_count) {
|
||||||
|
VerseIndex* vi = &ctx->book_idx[remaining];
|
||||||
|
snprintf(ref, sizeof(ref), "%s %d:%d", ctx->books[book_idx].bname, vi->cnum, vi->vnum);
|
||||||
|
if (vi->offset < ctx->book_bin_size) {
|
||||||
|
verse_text = (const char*)(ctx->book_bin + vi->offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_label_set_text(ctx->book_center_name, ref);
|
||||||
|
lv_label_set_text(ctx->book_center_verses, verse_text);
|
||||||
|
char count[24];
|
||||||
|
snprintf(count, sizeof(count), "%d / %d", ctx->favorite_dial_idx + 1, ctx->favorite_count);
|
||||||
|
lv_label_set_text(ctx->book_center_num, count);
|
||||||
|
lv_slider_set_range(ctx->book_slider, 0, ctx->favorite_count - 1);
|
||||||
|
lv_slider_set_value(ctx->book_slider, ctx->favorite_dial_idx, LV_ANIM_OFF);
|
||||||
|
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(0xF5C16C), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx->book_dial_idx <0) ctx->book_dial_idx=0;
|
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;
|
if (ctx->book_dial_idx >= ctx->book_count) ctx->book_dial_idx = ctx->book_count-1;
|
||||||
BookInfo* b = &ctx->books[ctx->book_dial_idx];
|
BookInfo* b = &ctx->books[ctx->book_dial_idx];
|
||||||
@@ -835,6 +968,7 @@ static void book_dial_update_center(AppCtx* ctx) {
|
|||||||
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(col), 0);
|
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(col), 0);
|
||||||
|
|
||||||
if (ctx->book_slider) {
|
if (ctx->book_slider) {
|
||||||
|
lv_slider_set_range(ctx->book_slider, 0, ctx->book_count>0 ? ctx->book_count-1 : 0);
|
||||||
// avoid recursive event loop - set without anim, but we add subtle anim for drag
|
// 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);
|
lv_slider_set_value(ctx->book_slider, ctx->book_dial_idx, LV_ANIM_OFF);
|
||||||
}
|
}
|
||||||
@@ -843,7 +977,13 @@ static void book_dial_update_center(AppCtx* ctx) {
|
|||||||
static void set_book_browser_visible(AppCtx* ctx, bool visible) {
|
static void set_book_browser_visible(AppCtx* ctx, bool visible) {
|
||||||
ctx->book_browser_visible = visible;
|
ctx->book_browser_visible = visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
ctx->browsing_favorites = false;
|
||||||
ctx->book_dial_idx = ctx->cur_book_idx;
|
ctx->book_dial_idx = ctx->cur_book_idx;
|
||||||
|
load_favorites(ctx);
|
||||||
|
if (ctx->btn_favorites) {
|
||||||
|
lv_obj_set_style_bg_color(ctx->btn_favorites, lv_color_hex(0x252538), 0);
|
||||||
|
lv_obj_set_style_text_color(ctx->btn_favorites, lv_color_hex(0xC4C4D4), 0);
|
||||||
|
}
|
||||||
book_dial_update_center(ctx);
|
book_dial_update_center(ctx);
|
||||||
if (ctx->book_browser) {
|
if (ctx->book_browser) {
|
||||||
lv_obj_remove_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
lv_obj_remove_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||||
@@ -890,20 +1030,65 @@ static void on_books_close_click(lv_event_t* e) {
|
|||||||
}
|
}
|
||||||
static void on_book_center_click(lv_event_t* e) {
|
static void on_book_center_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
jump_to_book(ctx, ctx->book_dial_idx);
|
if (ctx->browsing_favorites) {
|
||||||
|
if (ctx->favorite_count > 0) {
|
||||||
|
jump_to_global(ctx, ctx->favorite_indices[ctx->favorite_dial_idx]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jump_to_book(ctx, ctx->book_dial_idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
static void on_dial_prev_click(lv_event_t* e) {
|
static void on_dial_prev_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(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); }
|
if (ctx->browsing_favorites) {
|
||||||
|
if (ctx->favorite_dial_idx > 0) {
|
||||||
|
ctx->favorite_dial_idx--;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
}
|
||||||
|
} else 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) {
|
static void on_dial_next_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(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); }
|
if (ctx->browsing_favorites) {
|
||||||
|
if (ctx->favorite_dial_idx < ctx->favorite_count - 1) {
|
||||||
|
ctx->favorite_dial_idx++;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
}
|
||||||
|
} else 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) {
|
static void on_slider_changed(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
int v = lv_slider_get_value(ctx->book_slider);
|
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); }
|
if (ctx->browsing_favorites) {
|
||||||
|
if (v != ctx->favorite_dial_idx) {
|
||||||
|
ctx->favorite_dial_idx = v;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
}
|
||||||
|
} else if (v!=ctx->book_dial_idx) {
|
||||||
|
ctx->book_dial_idx=v;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void on_favorites_click(lv_event_t* e) {
|
||||||
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
|
load_favorites(ctx);
|
||||||
|
if (ctx->favorite_count <= 0) {
|
||||||
|
ctx->browsing_favorites = true;
|
||||||
|
book_dial_update_center(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int current_pos = find_favorite_position(ctx, ctx->cur_global);
|
||||||
|
ctx->favorite_dial_idx = current_pos >= 0 ? current_pos : 0;
|
||||||
|
ctx->favorite_navigation_active = true;
|
||||||
|
ctx->browsing_favorites = false;
|
||||||
|
jump_to_global(ctx, ctx->favorite_indices[ctx->favorite_dial_idx]);
|
||||||
}
|
}
|
||||||
static void on_swipe_area_event(lv_event_t* e) {
|
static void on_swipe_area_event(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
@@ -919,11 +1104,15 @@ static void on_swipe_area_event(lv_event_t* e) {
|
|||||||
int thresh = 28; // px per book
|
int thresh = 28; // px per book
|
||||||
if (abs(dx) >= thresh) {
|
if (abs(dx) >= thresh) {
|
||||||
int steps = dx / thresh;
|
int steps = dx / thresh;
|
||||||
int new_idx = ctx->book_dial_idx - steps; // swipe left -> next
|
int old_idx = ctx->browsing_favorites ? ctx->favorite_dial_idx : ctx->book_dial_idx;
|
||||||
|
int max_idx = ctx->browsing_favorites ? ctx->favorite_count - 1 : ctx->book_count - 1;
|
||||||
|
if (max_idx < 0) max_idx = 0;
|
||||||
|
int new_idx = old_idx - steps; // swipe left -> next
|
||||||
if (new_idx<0) new_idx=0;
|
if (new_idx<0) new_idx=0;
|
||||||
if (new_idx>=ctx->book_count) new_idx=ctx->book_count-1;
|
if (new_idx>max_idx) new_idx=max_idx;
|
||||||
if (new_idx!=ctx->book_dial_idx) {
|
if (new_idx!=old_idx) {
|
||||||
ctx->book_dial_idx = new_idx;
|
if (ctx->browsing_favorites) ctx->favorite_dial_idx = new_idx;
|
||||||
|
else ctx->book_dial_idx = new_idx;
|
||||||
book_dial_update_center(ctx);
|
book_dial_update_center(ctx);
|
||||||
}
|
}
|
||||||
ctx->book_drag_start_x = p.x; // reset for next step
|
ctx->book_drag_start_x = p.x; // reset for next step
|
||||||
@@ -992,6 +1181,9 @@ static void build_book_browser(AppCtx* ctx, lv_obj_t* parent) {
|
|||||||
|
|
||||||
ctx->book_center_verses = lv_label_create(mid_wrap);
|
ctx->book_center_verses = lv_label_create(mid_wrap);
|
||||||
lv_label_set_text(ctx->book_center_verses, "—");
|
lv_label_set_text(ctx->book_center_verses, "—");
|
||||||
|
lv_obj_set_width(ctx->book_center_verses, LV_PCT(100));
|
||||||
|
lv_obj_set_height(ctx->book_center_verses, 72);
|
||||||
|
lv_label_set_long_mode(ctx->book_center_verses, LV_LABEL_LONG_MODE_DOTS);
|
||||||
lv_obj_set_style_text_color(ctx->book_center_verses, lv_color_hex(0x5A5A78), 0);
|
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_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);
|
lv_obj_set_style_text_align(ctx->book_center_verses, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
@@ -1005,7 +1197,7 @@ static void build_book_browser(AppCtx* ctx, lv_obj_t* parent) {
|
|||||||
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_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);
|
lv_obj_add_event_cb(btn_r, on_dial_next_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
// Bottom bar: [X cancel] [slider] [N / 66]
|
// Bottom bar: [X cancel] [Fav] [slider] [N / 66]
|
||||||
lv_obj_t* bottom = lv_obj_create(ctx->book_browser);
|
lv_obj_t* bottom = lv_obj_create(ctx->book_browser);
|
||||||
lv_obj_set_size(bottom, LV_PCT(100), 56);
|
lv_obj_set_size(bottom, LV_PCT(100), 56);
|
||||||
lv_obj_align(bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
|
lv_obj_align(bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||||
@@ -1029,6 +1221,17 @@ static void build_book_browser(AppCtx* ctx, lv_obj_t* parent) {
|
|||||||
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_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);
|
lv_obj_add_event_cb(btn_cancel, on_books_close_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
|
ctx->btn_favorites = lv_button_create(bottom);
|
||||||
|
lv_obj_set_size(ctx->btn_favorites, 44, 32);
|
||||||
|
lv_obj_set_style_radius(ctx->btn_favorites, 8, 0);
|
||||||
|
lv_obj_set_style_bg_color(ctx->btn_favorites, lv_color_hex(0x252538), 0);
|
||||||
|
lv_obj_set_style_text_color(ctx->btn_favorites, lv_color_hex(0xC4C4D4), 0);
|
||||||
|
lv_obj_t* l_favorites = lv_label_create(ctx->btn_favorites);
|
||||||
|
lv_label_set_text(l_favorites, "Fav");
|
||||||
|
lv_obj_set_style_text_font(l_favorites, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||||
|
lv_obj_center(l_favorites);
|
||||||
|
lv_obj_add_event_cb(ctx->btn_favorites, on_favorites_click, LV_EVENT_CLICKED, ctx);
|
||||||
|
|
||||||
ctx->book_slider = lv_slider_create(bottom);
|
ctx->book_slider = lv_slider_create(bottom);
|
||||||
lv_obj_set_flex_grow(ctx->book_slider, 1);
|
lv_obj_set_flex_grow(ctx->book_slider, 1);
|
||||||
lv_obj_set_height(ctx->book_slider, 8);
|
lv_obj_set_height(ctx->book_slider, 8);
|
||||||
@@ -1052,8 +1255,35 @@ static void build_book_browser(AppCtx* ctx, lv_obj_t* parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── UI callbacks ── */
|
/* ── UI callbacks ── */
|
||||||
|
static void on_verse_pointer_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->verse_drag_start_x = p.x;
|
||||||
|
ctx->verse_drag_start_y = p.y;
|
||||||
|
ctx->verse_dragging = true;
|
||||||
|
ctx->verse_swipe_consumed = false;
|
||||||
|
} else if ((code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) && ctx->verse_dragging) {
|
||||||
|
int dx = p.x - ctx->verse_drag_start_x;
|
||||||
|
int dy = p.y - ctx->verse_drag_start_y;
|
||||||
|
ctx->verse_dragging = false;
|
||||||
|
if (abs(dx) >= 40 && abs(dx) > abs(dy)) {
|
||||||
|
ctx->verse_swipe_consumed = true;
|
||||||
|
manually_advance_verse(ctx, dx < 0 ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
if (ctx->verse_swipe_consumed) {
|
||||||
|
ctx->verse_swipe_consumed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
toggle_ui(ctx);
|
toggle_ui(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,11 +1333,11 @@ static void set_chrome_visible(AppCtx* ctx, bool visible) {
|
|||||||
|
|
||||||
static void on_prev_click(lv_event_t* e) {
|
static void on_prev_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
advance_verse(ctx, -1);
|
manually_advance_verse(ctx, -1);
|
||||||
}
|
}
|
||||||
static void on_next_click(lv_event_t* e) {
|
static void on_next_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
advance_verse(ctx, 1);
|
manually_advance_verse(ctx, 1);
|
||||||
}
|
}
|
||||||
static void on_play_pause_click(lv_event_t* e) {
|
static void on_play_pause_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
@@ -1120,6 +1350,17 @@ static void on_play_pause_click(lv_event_t* e) {
|
|||||||
static void on_highlight_click(lv_event_t* e) {
|
static void on_highlight_click(lv_event_t* e) {
|
||||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||||
toggle_favorite(ctx);
|
toggle_favorite(ctx);
|
||||||
|
if (ctx->favorite_navigation_active && !ctx->is_highlighted) {
|
||||||
|
if (ctx->favorite_count <= 0) {
|
||||||
|
ctx->favorite_navigation_active = false;
|
||||||
|
} else {
|
||||||
|
if (ctx->favorite_dial_idx >= ctx->favorite_count) {
|
||||||
|
ctx->favorite_dial_idx = ctx->favorite_count - 1;
|
||||||
|
}
|
||||||
|
ctx->cur_global = ctx->favorite_indices[ctx->favorite_dial_idx];
|
||||||
|
if (ctx->auto_timer) lv_timer_reset(ctx->auto_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
// refresh highlight visuals without reloading
|
// refresh highlight visuals without reloading
|
||||||
show_current_verse(ctx);
|
show_current_verse(ctx);
|
||||||
}
|
}
|
||||||
@@ -1198,6 +1439,9 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
lv_obj_set_style_pad_all(g_ctx.verse_wrapper, 0, 0);
|
lv_obj_set_style_pad_all(g_ctx.verse_wrapper, 0, 0);
|
||||||
lv_obj_remove_flag(g_ctx.verse_wrapper, LV_OBJ_FLAG_SCROLLABLE);
|
lv_obj_remove_flag(g_ctx.verse_wrapper, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
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_pointer_event, LV_EVENT_PRESSED, &g_ctx);
|
||||||
|
lv_obj_add_event_cb(g_ctx.verse_wrapper, on_verse_pointer_event, LV_EVENT_RELEASED, &g_ctx);
|
||||||
|
lv_obj_add_event_cb(g_ctx.verse_wrapper, on_verse_pointer_event, LV_EVENT_PRESS_LOST, &g_ctx);
|
||||||
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 - avoid top chrome crop
|
// Center reading column with generous breathing room - avoid top chrome crop
|
||||||
@@ -1213,6 +1457,9 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
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);
|
||||||
|
lv_obj_add_event_cb(center_col, on_verse_pointer_event, LV_EVENT_PRESSED, &g_ctx);
|
||||||
|
lv_obj_add_event_cb(center_col, on_verse_pointer_event, LV_EVENT_RELEASED, &g_ctx);
|
||||||
|
lv_obj_add_event_cb(center_col, on_verse_pointer_event, LV_EVENT_PRESS_LOST, &g_ctx);
|
||||||
lv_obj_add_event_cb(center_col, on_verse_tap, LV_EVENT_CLICKED, &g_ctx);
|
lv_obj_add_event_cb(center_col, on_verse_tap, LV_EVENT_CLICKED, &g_ctx);
|
||||||
|
|
||||||
g_ctx.lbl_verse = lv_label_create(center_col);
|
g_ctx.lbl_verse = lv_label_create(center_col);
|
||||||
@@ -1324,7 +1571,7 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
|||||||
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_PLUS); 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);
|
||||||
|
|
||||||
set_chrome_visible(&g_ctx, true); // start with menu showing per user request
|
set_chrome_visible(&g_ctx, true); // start with menu showing per user request
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#define DEFAULT_PATH "/sdcard/download/test2.mp3"
|
#define DEFAULT_PATH "/sdcard/download/test2.mp3"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
lv_obj_t *root_obj;
|
||||||
lv_obj_t *bar;
|
lv_obj_t *bar;
|
||||||
lv_obj_t *lbl_url;
|
lv_obj_t *lbl_url;
|
||||||
lv_obj_t *lbl_path;
|
lv_obj_t *lbl_path;
|
||||||
@@ -37,6 +38,7 @@ typedef struct {
|
|||||||
TaskHandle_t dl_handle;
|
TaskHandle_t dl_handle;
|
||||||
volatile bool downloading;
|
volatile bool downloading;
|
||||||
volatile bool cancel_req;
|
volatile bool cancel_req;
|
||||||
|
volatile bool task_exited;
|
||||||
volatile int total;
|
volatile int total;
|
||||||
volatile int expected;
|
volatile int expected;
|
||||||
int last_pct;
|
int last_pct;
|
||||||
@@ -82,13 +84,18 @@ static void ensure_parent_dir(const char *filepath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DL_DOWNLOADED 0
|
||||||
|
#define DL_EXISTED 1
|
||||||
|
#define DL_FAILED -1
|
||||||
|
|
||||||
static void update_ui_status(const char *txt) {
|
static void update_ui_status(const char *txt) {
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
if (G.lbl_status) lv_label_set_text(G.lbl_status, txt);
|
if (G.lbl_status) lv_label_set_text(G.lbl_status, txt);
|
||||||
tt_lvgl_unlock();
|
tt_lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_result_and_close(bool success) {
|
static void set_result_and_close(int dl_res) {
|
||||||
|
bool success = (dl_res != DL_FAILED);
|
||||||
G.result_success = success;
|
G.result_success = success;
|
||||||
size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
BundleHandle result_bundle = NULL;
|
BundleHandle result_bundle = NULL;
|
||||||
@@ -97,6 +104,13 @@ static void set_result_and_close(bool success) {
|
|||||||
}
|
}
|
||||||
if (result_bundle) {
|
if (result_bundle) {
|
||||||
tt_bundle_put_bool(result_bundle, "success", success);
|
tt_bundle_put_bool(result_bundle, "success", success);
|
||||||
|
if (dl_res == DL_DOWNLOADED) {
|
||||||
|
tt_bundle_put_string(result_bundle, "status", "downloaded");
|
||||||
|
} else if (dl_res == DL_EXISTED) {
|
||||||
|
tt_bundle_put_string(result_bundle, "status", "existed");
|
||||||
|
} else {
|
||||||
|
tt_bundle_put_string(result_bundle, "status", "failure");
|
||||||
|
}
|
||||||
// Keep bundle minimal to avoid low heap crash (previous put_string path 512B caused OOM)
|
// Keep bundle minimal to avoid low heap crash (previous put_string path 512B caused OOM)
|
||||||
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, result_bundle);
|
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, result_bundle);
|
||||||
} else {
|
} else {
|
||||||
@@ -106,16 +120,9 @@ static void set_result_and_close(bool success) {
|
|||||||
}
|
}
|
||||||
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, NULL);
|
tt_app_set_result(G.app_handle, success ? APP_RESULT_OK : APP_RESULT_ERROR, NULL);
|
||||||
}
|
}
|
||||||
// Change Cancel button to Close once done (manual close, auto-close unreliable)
|
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
|
||||||
if (G.btn_cancel) {
|
|
||||||
lv_obj_t* lbl = lv_obj_get_child(G.btn_cancel, 0);
|
|
||||||
if (lbl) lv_label_set_text(lbl, "Close");
|
|
||||||
}
|
|
||||||
tt_lvgl_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool download_one_file(const char *url, const char *path) {
|
static int download_one_file(const char *url, const char *path) {
|
||||||
ESP_LOGI(TAG, "DL one file url=%s path=%s override=%d", url, path, G.override_existing);
|
ESP_LOGI(TAG, "DL one file url=%s path=%s override=%d", url, path, G.override_existing);
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -124,7 +131,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
ESP_LOGW(TAG, "File exists and override=false: %s (returning true as cached)", path);
|
ESP_LOGW(TAG, "File exists and override=false: %s (returning true as cached)", path);
|
||||||
G.total = st.st_size;
|
G.total = st.st_size;
|
||||||
update_ui_status("Already on SD");
|
update_ui_status("Already on SD");
|
||||||
return true;
|
return DL_EXISTED;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "File exists but override=true, overwriting %s", path);
|
ESP_LOGI(TAG, "File exists but override=true, overwriting %s", path);
|
||||||
unlink(path);
|
unlink(path);
|
||||||
@@ -141,7 +148,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
|
|
||||||
if (strlen(url) < 8 || strncmp(url, "http://", 7)!=0) {
|
if (strlen(url) < 8 || strncmp(url, "http://", 7)!=0) {
|
||||||
update_ui_status("Invalid URL");
|
update_ui_status("Invalid URL");
|
||||||
return false;
|
return DL_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
char tmp_path[520];
|
char tmp_path[520];
|
||||||
@@ -151,7 +158,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
if (!f) {
|
if (!f) {
|
||||||
ESP_LOGE(TAG, "fopen fail %s errno=%d", tmp_path, errno);
|
ESP_LOGE(TAG, "fopen fail %s errno=%d", tmp_path, errno);
|
||||||
update_ui_status("Failed to open file");
|
update_ui_status("Failed to open file");
|
||||||
return false;
|
return DL_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_http_client_config_t cfg = {
|
esp_http_client_config_t cfg = {
|
||||||
@@ -166,7 +173,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
unlink(tmp_path);
|
unlink(tmp_path);
|
||||||
update_ui_status("HTTP init failed");
|
update_ui_status("HTTP init failed");
|
||||||
return false;
|
return DL_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t err = esp_http_client_open(client, 0);
|
esp_err_t err = esp_http_client_open(client, 0);
|
||||||
@@ -175,7 +182,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
unlink(tmp_path);
|
unlink(tmp_path);
|
||||||
update_ui_status("HTTP open failed");
|
update_ui_status("HTTP open failed");
|
||||||
return false;
|
return DL_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int content_len = esp_http_client_fetch_headers(client);
|
int content_len = esp_http_client_fetch_headers(client);
|
||||||
@@ -189,7 +196,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
esp_http_client_cleanup(client);
|
esp_http_client_cleanup(client);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
unlink(tmp_path);
|
unlink(tmp_path);
|
||||||
return false;
|
return DL_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
G.expected = content_len;
|
G.expected = content_len;
|
||||||
@@ -203,7 +210,7 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
unlink(tmp_path);
|
unlink(tmp_path);
|
||||||
update_ui_status("No memory");
|
update_ui_status("No memory");
|
||||||
return false;
|
return DL_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,18 +271,19 @@ static bool download_one_file(const char *url, const char *path) {
|
|||||||
esp_http_client_cleanup(client);
|
esp_http_client_cleanup(client);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
if (G.cancel_req) { unlink(tmp_path); update_ui_status("Canceled"); return false; }
|
if (G.cancel_req) { unlink(tmp_path); update_ui_status("Canceled"); return DL_FAILED; }
|
||||||
if (!ok || total < 512) { unlink(tmp_path); update_ui_status("Failed – error"); return false; }
|
if (!ok || total < 512) { unlink(tmp_path); update_ui_status("Failed – error"); return DL_FAILED; }
|
||||||
if (content_len >=0 && total < content_len * 95 / 100) { unlink(tmp_path); update_ui_status("Failed – incomplete"); return false; }
|
if (content_len >=0 && total < content_len * 95 / 100) { unlink(tmp_path); update_ui_status("Failed – incomplete"); return DL_FAILED; }
|
||||||
if (rename(tmp_path, path) != 0) { unlink(tmp_path); update_ui_status("Rename failed"); return false; }
|
if (rename(tmp_path, path) != 0) { unlink(tmp_path); update_ui_status("Rename failed"); return DL_FAILED; }
|
||||||
|
|
||||||
return true;
|
return DL_DOWNLOADED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void download_task(void *arg) {
|
static void download_task(void *arg) {
|
||||||
(void)arg;
|
(void)arg;
|
||||||
G.downloading = true;
|
G.downloading = true;
|
||||||
G.cancel_req = false;
|
G.cancel_req = false;
|
||||||
|
G.task_exited = false;
|
||||||
G.last_pct = -1;
|
G.last_pct = -1;
|
||||||
|
|
||||||
char url[512];
|
char url[512];
|
||||||
@@ -285,35 +293,48 @@ static void download_task(void *arg) {
|
|||||||
url[sizeof(url)-1]=0;
|
url[sizeof(url)-1]=0;
|
||||||
path[sizeof(path)-1]=0;
|
path[sizeof(path)-1]=0;
|
||||||
|
|
||||||
bool ok = download_one_file(url, path);
|
int dl_res = download_one_file(url, path);
|
||||||
if (ok) {
|
if (dl_res == DL_DOWNLOADED || dl_res == DL_EXISTED) {
|
||||||
char msg[64];
|
char msg[64];
|
||||||
snprintf(msg, sizeof(msg), "Done! %d KB", G.total/1024);
|
if (dl_res == DL_EXISTED) snprintf(msg, sizeof(msg), "Already on SD (%d KB)", G.total/1024);
|
||||||
|
else snprintf(msg, sizeof(msg), "Done! %d KB", G.total/1024);
|
||||||
update_ui_status(msg);
|
update_ui_status(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if (G.btn_cancel) {
|
||||||
|
lv_obj_t* lbl = lv_obj_get_child(G.btn_cancel, 0);
|
||||||
|
if (lbl) lv_label_set_text(lbl, "Close");
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
|
|
||||||
G.downloading = false;
|
G.downloading = false;
|
||||||
|
set_result_and_close(dl_res);
|
||||||
|
G.task_exited = true;
|
||||||
G.dl_handle = NULL;
|
G.dl_handle = NULL;
|
||||||
set_result_and_close(ok);
|
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void btn_cancel_cb(lv_event_t *e) {
|
static void btn_cancel_cb(lv_event_t *e) {
|
||||||
(void)e;
|
(void)e;
|
||||||
|
ESP_LOGI(TAG, "btn_cancel_cb clicked! downloading=%d", G.downloading);
|
||||||
|
if (G.btn_cancel) {
|
||||||
|
lv_obj_remove_flag(G.btn_cancel, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (!G.downloading) {
|
if (!G.downloading) {
|
||||||
tt_app_stop();
|
tt_app_stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
G.cancel_req = true;
|
G.cancel_req = true;
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
|
||||||
if (G.lbl_status) lv_label_set_text(G.lbl_status, "Canceling...");
|
if (G.lbl_status) lv_label_set_text(G.lbl_status, "Canceling...");
|
||||||
tt_lvgl_unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void build_ui(void) {
|
static void build_ui(void) {
|
||||||
tt_lvgl_lock(portMAX_DELAY);
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
|
||||||
lv_obj_t *root = lv_obj_create(lv_scr_act());
|
lv_obj_t *root = lv_obj_create(lv_scr_act());
|
||||||
|
G.root_obj = root;
|
||||||
lv_obj_set_size(root, LV_PCT(100), LV_PCT(100));
|
lv_obj_set_size(root, LV_PCT(100), LV_PCT(100));
|
||||||
lv_obj_set_style_pad_all(root, 8, 0);
|
lv_obj_set_style_pad_all(root, 8, 0);
|
||||||
lv_obj_set_style_bg_color(root, lv_color_hex(0x111111), 0);
|
lv_obj_set_style_bg_color(root, lv_color_hex(0x111111), 0);
|
||||||
@@ -448,9 +469,31 @@ static void onHide(AppHandle app, void* data) {
|
|||||||
(void)app; (void)data;
|
(void)app; (void)data;
|
||||||
if (G.dl_handle) {
|
if (G.dl_handle) {
|
||||||
G.cancel_req = true;
|
G.cancel_req = true;
|
||||||
for (int i=0;i<20 && G.dl_handle!=NULL; i++) vTaskDelay(pdMS_TO_TICKS(100));
|
for (int i = 0; i < 30 && !G.task_exited; i++) {
|
||||||
if (G.dl_handle) { vTaskDelete(G.dl_handle); G.dl_handle = NULL; G.downloading = false; }
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
if (!G.task_exited && G.dl_handle) {
|
||||||
|
ESP_LOGW(TAG, "onHide: force deleting dl task after timeout");
|
||||||
|
vTaskDelete(G.dl_handle);
|
||||||
|
G.dl_handle = NULL;
|
||||||
|
G.downloading = false;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tt_lvgl_lock(portMAX_DELAY);
|
||||||
|
if (G.root_obj) {
|
||||||
|
lv_obj_delete(G.root_obj);
|
||||||
|
G.root_obj = NULL;
|
||||||
|
G.lbl_url = NULL;
|
||||||
|
G.lbl_path = NULL;
|
||||||
|
G.lbl_pct = NULL;
|
||||||
|
G.lbl_detail = NULL;
|
||||||
|
G.lbl_status = NULL;
|
||||||
|
G.bar = NULL;
|
||||||
|
G.btn_cancel = NULL;
|
||||||
|
}
|
||||||
|
tt_lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user