be9229b80d
- Extracted app_context (global AppCtx G definition) - storage: path helpers, LRU, sort, fmt_time, per-episode resume map, state persistence - network: resolve_host, normalize URL, http_get_raw with growing PSRAM buffer, fetch_data - download: dl_ep_raw with progress tracking, cancel, overlay UI (show/hide/update) - player: audio device, stream, play_task with resume seek, start/select/next/prev with position save/restore - ui: build_ui, timers, season picker, button callbacks, periodic save - main: minimal glue, fetch_task, onShow/onHide, app registration - Preserves all previous fixes: download progress overlay, per-episode resume, robust download handling - Build verified: 111 undef, all exported
245 lines
8.2 KiB
C
245 lines
8.2 KiB
C
#include "ui.h"
|
|
#include "storage.h"
|
|
#include "player.h"
|
|
#include "download.h"
|
|
|
|
#include <tt_lvgl.h>
|
|
#include <tactility/lvgl_fonts.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "esp_log.h"
|
|
|
|
void close_overlay(){
|
|
if(G.overlay){
|
|
lv_obj_delete(G.overlay);
|
|
G.overlay=NULL;
|
|
G.dropdown=NULL;
|
|
}
|
|
}
|
|
|
|
void dropdown_cb(lv_event_t* e){
|
|
if(lv_event_get_code(e)!=LV_EVENT_VALUE_CHANGED) return;
|
|
lv_obj_t* dd = (lv_obj_t*)lv_event_get_target(e);
|
|
int sel=lv_dropdown_get_selected(dd);
|
|
if(sel<0||sel>=G.season_cnt) return;
|
|
int sn=G.seasons[sel].num;
|
|
close_overlay();
|
|
int first=-1, first_unseen=-1;
|
|
for(int i=0;i<G.ep_cnt;i++) if(G.eps[i].season==sn){
|
|
if(first==-1) first=i;
|
|
if(!G.eps[i].seen && first_unseen==-1) first_unseen=i;
|
|
}
|
|
int tgt=first_unseen!=-1?first_unseen:first;
|
|
if(tgt!=-1){
|
|
G.cur_season=sn;
|
|
select_ep(tgt);
|
|
}
|
|
}
|
|
|
|
void btn_seasons_cb(lv_event_t* e){
|
|
(void)e;
|
|
if(G.downloading) return;
|
|
if(G.overlay){ close_overlay(); return; }
|
|
tt_lvgl_lock(portMAX_DELAY);
|
|
G.overlay=lv_obj_create(lv_scr_act());
|
|
lv_obj_set_size(G.overlay,240,280);
|
|
lv_obj_center(G.overlay);
|
|
lv_obj_set_style_bg_color(G.overlay,lv_color_hex(0x222222),0);
|
|
lv_obj_set_style_radius(G.overlay,12,0);
|
|
lv_obj_t* title=lv_label_create(G.overlay);
|
|
lv_label_set_text(title,"Select Season");
|
|
lv_obj_align(title,LV_ALIGN_TOP_MID,0,8);
|
|
|
|
static char opts[1024];
|
|
opts[0]=0;
|
|
for(int i=0;i<G.season_cnt;i++){
|
|
char line[16];
|
|
if(i==0) snprintf(opts,sizeof(opts),"S%d",G.seasons[i].num);
|
|
else { snprintf(line,sizeof(line),"\nS%d",G.seasons[i].num); strncat(opts,line,sizeof(opts)-strlen(opts)-1); }
|
|
}
|
|
G.dropdown=lv_dropdown_create(G.overlay);
|
|
lv_dropdown_set_options(G.dropdown, opts);
|
|
lv_obj_set_size(G.dropdown,160,40);
|
|
lv_obj_align(G.dropdown,LV_ALIGN_CENTER,0,10);
|
|
int sel=0;
|
|
for(int i=0;i<G.season_cnt;i++) if(G.seasons[i].num==G.cur_season) sel=i;
|
|
lv_dropdown_set_selected(G.dropdown, sel);
|
|
lv_obj_add_event_cb(G.dropdown,dropdown_cb,LV_EVENT_VALUE_CHANGED,NULL);
|
|
|
|
lv_obj_t* bc=lv_btn_create(G.overlay);
|
|
lv_obj_set_size(bc,80,32);
|
|
lv_obj_align(bc,LV_ALIGN_BOTTOM_MID,0,-8);
|
|
lv_obj_t* lc=lv_label_create(bc);
|
|
lv_label_set_text(lc,"Close");
|
|
lv_obj_center(lc);
|
|
lv_obj_add_event_cb(bc,(lv_event_cb_t)close_overlay,LV_EVENT_CLICKED,NULL);
|
|
tt_lvgl_unlock();
|
|
}
|
|
|
|
void build_ui(){
|
|
tt_lvgl_lock(portMAX_DELAY);
|
|
lv_obj_t* root=lv_obj_create(lv_scr_act());
|
|
lv_obj_set_size(root,LV_PCT(100),LV_PCT(100));
|
|
lv_obj_set_style_bg_color(root,lv_color_hex(0x111111),0);
|
|
lv_obj_set_style_pad_all(root,8,0);
|
|
lv_obj_set_style_border_width(root,0,0);
|
|
lv_obj_remove_flag(root, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
G.lbl_title=lv_label_create(root);
|
|
lv_label_set_text(G.lbl_title,"Discovery Mountain");
|
|
lv_obj_set_style_text_font(G.lbl_title, lvgl_get_text_font(FONT_SIZE_DEFAULT), 0);
|
|
lv_obj_set_width(G.lbl_title,LV_PCT(100));
|
|
lv_label_set_long_mode(G.lbl_title,LV_LABEL_LONG_WRAP);
|
|
lv_obj_align(G.lbl_title,LV_ALIGN_TOP_LEFT,0,0);
|
|
|
|
G.lbl_meta=lv_label_create(root);
|
|
lv_label_set_text(G.lbl_meta,"Loading...");
|
|
lv_obj_set_style_text_color(G.lbl_meta,lv_color_hex(0xAAAAAA),0);
|
|
lv_obj_align(G.lbl_meta,LV_ALIGN_TOP_LEFT,0,28);
|
|
|
|
G.bar=lv_bar_create(root);
|
|
lv_obj_set_size(G.bar,LV_PCT(100),12);
|
|
lv_obj_align(G.bar,LV_ALIGN_TOP_LEFT,0,50);
|
|
lv_bar_set_value(G.bar,0,LV_ANIM_OFF);
|
|
|
|
G.lbl_time=lv_label_create(root);
|
|
lv_label_set_text(G.lbl_time,"0:00 / --:--");
|
|
lv_obj_align(G.lbl_time,LV_ALIGN_TOP_LEFT,0,66);
|
|
|
|
G.lbl_status=lv_label_create(root);
|
|
lv_label_set_text(G.lbl_status,"Fetching data...");
|
|
lv_obj_set_style_text_color(G.lbl_status,lv_color_hex(0x888888),0);
|
|
lv_obj_align(G.lbl_status,LV_ALIGN_TOP_LEFT,0,82);
|
|
|
|
lv_obj_t* row=lv_obj_create(root);
|
|
lv_obj_set_size(row,LV_PCT(100),56);
|
|
lv_obj_align(row,LV_ALIGN_TOP_LEFT,0,104);
|
|
lv_obj_set_flex_flow(row,LV_FLEX_FLOW_ROW);
|
|
lv_obj_set_flex_align(row,LV_FLEX_ALIGN_SPACE_EVENLY,LV_FLEX_ALIGN_CENTER,LV_FLEX_ALIGN_CENTER);
|
|
lv_obj_set_style_bg_opa(row,LV_OPA_TRANSP,0);
|
|
lv_obj_set_style_border_width(row,0,0);
|
|
lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
lv_obj_t* b1=lv_btn_create(row);
|
|
lv_obj_set_size(b1,56,48);
|
|
lv_obj_t* l1=lv_label_create(b1);
|
|
lv_label_set_text(l1,LV_SYMBOL_PREV);
|
|
lv_obj_center(l1);
|
|
lv_obj_add_event_cb(b1,btn_prev_cb,LV_EVENT_CLICKED,NULL);
|
|
|
|
G.btn_play=lv_btn_create(row);
|
|
lv_obj_set_size(G.btn_play,72,48);
|
|
lv_obj_set_style_bg_color(G.btn_play,lv_color_hex(0x2E7D32),0);
|
|
lv_obj_t* l2=lv_label_create(G.btn_play);
|
|
lv_label_set_text(l2,LV_SYMBOL_PLAY);
|
|
lv_obj_center(l2);
|
|
lv_obj_add_event_cb(G.btn_play,btn_play_cb,LV_EVENT_CLICKED,NULL);
|
|
|
|
lv_obj_t* b3=lv_btn_create(row);
|
|
lv_obj_set_size(b3,56,48);
|
|
lv_obj_t* l3=lv_label_create(b3);
|
|
lv_label_set_text(l3,LV_SYMBOL_NEXT);
|
|
lv_obj_center(l3);
|
|
lv_obj_add_event_cb(b3,btn_next_cb,LV_EVENT_CLICKED,NULL);
|
|
|
|
lv_obj_t* row2=lv_obj_create(root);
|
|
lv_obj_set_size(row2,LV_PCT(100),44);
|
|
lv_obj_align(row2,LV_ALIGN_BOTTOM_LEFT,0,0);
|
|
lv_obj_set_flex_flow(row2,LV_FLEX_FLOW_ROW);
|
|
lv_obj_set_flex_align(row2,LV_FLEX_ALIGN_SPACE_EVENLY,LV_FLEX_ALIGN_CENTER,LV_FLEX_ALIGN_CENTER);
|
|
lv_obj_set_style_bg_opa(row2,LV_OPA_TRANSP,0);
|
|
lv_obj_set_style_border_width(row2,0,0);
|
|
lv_obj_remove_flag(row2, LV_OBJ_FLAG_SCROLLABLE);
|
|
|
|
lv_obj_t* bs=lv_btn_create(row2);
|
|
lv_obj_set_size(bs,LV_PCT(100),36);
|
|
lv_obj_t* ls=lv_label_create(bs);
|
|
lv_label_set_text(ls,"Seasons");
|
|
lv_obj_center(ls);
|
|
lv_obj_add_event_cb(bs,btn_seasons_cb,LV_EVENT_CLICKED,NULL);
|
|
|
|
lv_timer_create(ui_timer,200,NULL);
|
|
tt_lvgl_unlock();
|
|
}
|
|
|
|
void ui_timer(lv_timer_t* t){
|
|
(void)t;
|
|
if(G.downloading){
|
|
update_dl_overlay_ui();
|
|
}
|
|
if(G.bar){
|
|
if(G.total_sec>0){
|
|
int pct=G.pos_sec*100/G.total_sec;
|
|
if(pct>100) pct=100;
|
|
if(pct!=G.last_pct){
|
|
G.last_pct=pct;
|
|
lv_bar_set_value(G.bar,pct,LV_ANIM_OFF);
|
|
}
|
|
} else if(G.is_playing){
|
|
int pct=(G.pos_sec%10)*10;
|
|
if(pct!=G.last_pct){
|
|
G.last_pct=pct;
|
|
lv_bar_set_value(G.bar,pct,LV_ANIM_OFF);
|
|
}
|
|
}
|
|
}
|
|
if(G.lbl_time){
|
|
char a[16],b[16];
|
|
fmt_time(a,G.pos_sec);
|
|
if(G.total_sec>0) fmt_time(b,G.total_sec);
|
|
else snprintf(b,sizeof(b),"--:--");
|
|
char buf[40];
|
|
snprintf(buf,sizeof(buf),"%s / %s",a,b);
|
|
lv_label_set_text(G.lbl_time,buf);
|
|
}
|
|
if(G.is_playing && G.lbl_status && !G.downloading){
|
|
const char* cur = lv_label_get_text(G.lbl_status);
|
|
if(!cur || strcmp(cur,"Playing")!=0) lv_label_set_text(G.lbl_status,"Playing");
|
|
}
|
|
if(G.is_playing && !G.is_paused && !G.downloading){
|
|
G.last_save_tick++;
|
|
if(G.last_save_tick >= 25){
|
|
G.last_save_tick=0;
|
|
save_current_ep_pos();
|
|
save_state();
|
|
}
|
|
}
|
|
}
|
|
|
|
void btn_play_cb(lv_event_t* e){
|
|
(void)e;
|
|
if(G.downloading){
|
|
return;
|
|
}
|
|
if(G.is_playing && !G.is_paused){
|
|
G.is_paused=true;
|
|
save_current_ep_pos();
|
|
save_state();
|
|
tt_lvgl_lock(portMAX_DELAY);
|
|
if(G.lbl_status) lv_label_set_text(G.lbl_status,"Paused");
|
|
if(G.btn_play){
|
|
lv_obj_t* ch=lv_obj_get_child(G.btn_play,0);
|
|
if(ch) lv_label_set_text(ch,LV_SYMBOL_PLAY);
|
|
}
|
|
tt_lvgl_unlock();
|
|
} else if(G.is_paused){
|
|
G.is_paused=false;
|
|
tt_lvgl_lock(portMAX_DELAY);
|
|
if(G.btn_play){
|
|
lv_obj_t* ch=lv_obj_get_child(G.btn_play,0);
|
|
if(ch) lv_label_set_text(ch,LV_SYMBOL_PAUSE);
|
|
}
|
|
tt_lvgl_unlock();
|
|
} else {
|
|
start_idx(G.cur_ep_idx);
|
|
tt_lvgl_lock(portMAX_DELAY);
|
|
if(G.btn_play){
|
|
lv_obj_t* ch=lv_obj_get_child(G.btn_play,0);
|
|
if(ch) lv_label_set_text(ch,LV_SYMBOL_PAUSE);
|
|
}
|
|
tt_lvgl_unlock();
|
|
}
|
|
}
|
|
void btn_prev_cb(lv_event_t* e){ (void)e; if(G.downloading) return; go_prev(); }
|
|
void btn_next_cb(lv_event_t* e){ (void)e; if(G.downloading) return; go_next(); }
|