feat(ncm): 首页顶栏「已缓存」入口 + 已缓存列表页(歌名/歌手/实际档位/大小,点播/单曲删除/清空)
入口:首页 topbar 新增动作 3(MEOW_ICON_DOWNLOAD 位图,原「回播放页」顺延动作 4)→ push ACT_CACHE;左缘返回弹回首页(app.c 路由补齐 tap/swipe/touch/tick)。 列表页 app/ui/cache.c(对齐 queue.c 惯例:圆角卡/红 mini-vinyl/按压红条/当前播放行高亮): - 行 = 歌名 / 歌手 · 实际档位 · 大小;空态/页脚(N 首 · 合计 X MB)。 - 点行 = 点播(NCM_QUEUE_CACHE 队列,上/下一首在缓存列表内循环);行右「删除」=单曲删除; 顶栏「清空」=确认框 + ncm_cache_clear(与设置页「清理缓存」同语义)。 - 数据全在 worker:cache_reload → appstate_cache_list_load → ncm_cache_scan(读目录 + 文件内 tag + 音频头探测)→ 快照;UI 线程零磁盘 IO。快照上限 512 条(最近访问优先, ≈116KB bss),更多条目按 mtime 保留最近的。 core: - ncm_cache_scan / ncm_cache_remove(songs/ 池,song_<id>_<请求档>.<实际格式> 解析, 跳过 .tmp/.part 与非数字 id;按 mtime 降序;历史双点名删除兜底)。 - ncm_api_level_from_key(缓存键后缀 ↔ 档位枚举,与 ncm_api_level_key 互逆)。 - **档位显示实际档而非请求档**:真机存在 song_171650_hires.mp3(无损请求被降级链落到 exhigh mp3),故 scan 用 ncm_audio_probe_file 读实际容器参数后只降不升地推断 (FLAC 16bit→无损 / 24bit→Hi-Res+,MP3 ≤128k→标准 / >128k→极高)。 播放:player_manager 对 NCM_QUEUE_CACHE 用条目真实键(id+请求档+实际扩展名)直读缓存 文件,不按用户当前档键重下;song_local_file/play_is_same_song/queue 页均支持第三种队列。 宿主 mock(NCM_PAGE=cache)造 3 条缓存 + 嵌入 tag + 同步扫描。
This commit is contained in:
1 parent
184ebd0ac9
commit
f392ee5c48
16 files changed
+829
-18
No files matched your search
@@ -0,0 +1,412 @@
|
||||
/* ui/cache.c — 已缓存列表页(ACT_CACHE,首页顶栏「已缓存」钮进入)。
|
||||
*
|
||||
* 数据源:songs/ 音频池(键=歌曲ID+请求档位,NCM_CACHE_DESIGN §2.5)。扫描在 worker:
|
||||
* ncm_cache_scan(读目录 + 文件头 tag + 音频参数探测)→ appstate 快照;
|
||||
* UI 线程只读快照(零磁盘 IO),列表由 meow_listview 虚拟化。
|
||||
* 行:歌名 / 歌手 · **实际档位** · 大小。**档位显示实际档而非请求档**:缓存名里的档位段只是
|
||||
* 请求档(真机存在 song_<id>_hires.mp3 = 无损请求被降级链落到 exhigh mp3),实际档由
|
||||
* 文件头(FLAC bps/采样率、MP3 首帧码率)推断。
|
||||
* 交互:点行=点播(NCM_QUEUE_CACHE,上/下一首在缓存列表内循环);行右「删除」=单曲删除;
|
||||
* 顶栏「清空」=清空缓存池(确认框,与设置页「清理缓存」同语义)。
|
||||
* 主题/控件惯例同 queue.c:圆角卡 + 红 mini-vinyl + 按压红条 + 当前播放行高亮。
|
||||
* 左缘返回=弹回首页(app.c)。
|
||||
*/
|
||||
#include "../ncm_app_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct { meow_view *view; int active; } cache_ctrl;
|
||||
static cache_ctrl g_cc;
|
||||
|
||||
static meow_widget *g_tb, *g_list;
|
||||
static ncm_rect_t r_cont, r_list;
|
||||
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||
static int g_last_count;
|
||||
|
||||
/* 扫描 worker(磁盘 IO 不进 UI 线程;gen 计数做完成信号) */
|
||||
static pthread_mutex_t g_cmu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t g_cthr;
|
||||
static int g_cthr_on;
|
||||
static volatile int g_cgen, g_cacked;
|
||||
static volatile int g_cloading;
|
||||
static volatile int g_cerr;
|
||||
|
||||
static void c_drag_reset(void)
|
||||
{
|
||||
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
|
||||
}
|
||||
|
||||
/* 字节数 → "12.3 MB"(整数运算,不引浮点 printf;MIPS 软浮点省体积)。 */
|
||||
static void fmt_size(uint64_t n, char *out, size_t sz)
|
||||
{
|
||||
if (n >= (1024ULL * 1024ULL * 1024ULL)) {
|
||||
uint64_t t = (n * 10ULL + 512ULL * 1024ULL * 1024ULL) / (1024ULL * 1024ULL * 1024ULL);
|
||||
snprintf(out, sz, "%llu.%llu GB", (unsigned long long)(t / 10), (unsigned long long)(t % 10));
|
||||
} else if (n >= 1024ULL * 1024ULL) {
|
||||
uint64_t t = (n * 10ULL + 512ULL * 1024ULL) / (1024ULL * 1024ULL);
|
||||
snprintf(out, sz, "%llu.%llu MB", (unsigned long long)(t / 10), (unsigned long long)(t % 10));
|
||||
} else {
|
||||
snprintf(out, sz, "%llu KB", (unsigned long long)(n / 1024ULL));
|
||||
}
|
||||
}
|
||||
|
||||
static const char *level_label(int lv)
|
||||
{
|
||||
return (lv >= 0) ? ncm_level_name(lv) : "未知档";
|
||||
}
|
||||
|
||||
/* 列表数据源(逐行快照读取,零 IO)。 */
|
||||
static int c_src2(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
static ncm_cache_item_t it;
|
||||
static char sub[192];
|
||||
static char title[100];
|
||||
char sz[24];
|
||||
|
||||
if (appstate_cache_item(idx, &it) != NCM_OK) return 0;
|
||||
fmt_size(it.size, sz, sizeof sz);
|
||||
snprintf(sub, sizeof sub, "%s · %s · %s",
|
||||
it.artist[0] ? it.artist : "-", level_label(it.actual_level), sz);
|
||||
snprintf(title, sizeof title, "%s", it.name[0] ? it.name : "(未知歌曲)");
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE; /* 图标槽由红 overlays 画迷你黑胶 */
|
||||
out->title = title;
|
||||
out->subtitle = sub;
|
||||
out->right = MEOW_LIST_RIGHT_NONE; /* 右侧「删除」由 z=60 叠层自绘 */
|
||||
out->on = 0; out->enabled = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 行背景(z=5,画在 listview 文本之下):圆角卡 + 当前播放行红色高亮。 */
|
||||
static void c_row_bg(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_CACHE) return;
|
||||
int cnt = appstate_cache_count();
|
||||
if (cnt <= 0) return;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||
int item = APP_ROW_H;
|
||||
int off = meow_listview_scroll(g_list);
|
||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||
if (last > cnt) last = cnt;
|
||||
int cur = (appstate_get_queue() == NCM_QUEUE_CACHE) ? player_manager_playing_song() : -1;
|
||||
for (int i = first; i < last; i++) {
|
||||
int ry = ncm_row_card_y(item, i, off, r_list.y);
|
||||
int th2 = ncm_row_card_h(item);
|
||||
if (ry < r_list.y || ry + th2 > r_list.y + r_list.h) continue; /* 只画完整落在视口内的行 */
|
||||
if (i == cur) {
|
||||
ncm_fill_round(r_list.x, ry, r_list.w, th2, 12, pa->red_ctr);
|
||||
meow_ui_fill_rect(r_list.x + 2, ry + 4, 4, th2 - 8, pa->red);
|
||||
} else {
|
||||
ncm_fill_round(r_list.x, ry, r_list.w, th2, 12, th->surfaceContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 主绘制(z=60):迷你黑胶缩略 + 行右「删除」+ 按压红条;文本由 listview z=10 画。 */
|
||||
static void c_draw(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_CACHE) return;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||
int cnt = appstate_cache_count();
|
||||
if (cnt <= 0) return;
|
||||
int item = APP_ROW_H, isz = 24;
|
||||
int off = meow_listview_scroll(g_list);
|
||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||
if (last > cnt) last = cnt;
|
||||
for (int i = first; i < last; i++) {
|
||||
int ry = ncm_row_card_y(item, i, off, r_list.y);
|
||||
int th2 = ncm_row_card_h(item);
|
||||
if (ry < r_list.y || ry + th2 > r_list.y + r_list.h) continue;
|
||||
int icy = ry + (th2 - isz) / 2, bx = r_list.x + 16;
|
||||
ncm_fill_round(bx, icy, isz, isz, 6, pa->red);
|
||||
meow_ui_fill_rect(bx + isz / 2 - 2, icy + isz / 2 - 2, 4, 4, pa->on_red); /* 盘芯 */
|
||||
/* 行右「删除」(触达区 = 右 48px,见 cache_on_tap) */
|
||||
meow_ui_text_right(r_list.x + r_list.w - APP_PAD,
|
||||
ry + (th2 - 18) / 2, "删除", 18, pa->red);
|
||||
if (g_press_row == i) {
|
||||
meow_ui_fill_rect(r_list.x, ry, 4, th2, pa->red);
|
||||
meow_ui_fill_rect_alpha(r_list.x, ry, r_list.w, th2, pa->red, 26);
|
||||
}
|
||||
/* 擦除 z=10 的 SDK 行分隔线(现落进卡片间空隙):以页底色填空隙带;不越出列表视口底。 */
|
||||
int gy = ry + th2, gh = NCM_ROW_GAP;
|
||||
if (gy + gh > r_list.y + r_list.h) gh = r_list.y + r_list.h - gy;
|
||||
if (gh > 0) meow_ui_fill_rect(r_list.x, gy, r_list.w, gh, th->bg);
|
||||
}
|
||||
}
|
||||
|
||||
static void c_draw_empty(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_CACHE) return;
|
||||
if (appstate_cache_count() != 0) return;
|
||||
if (g_cloading) { ncm_draw_empty(r_list, "LOADING", "正在扫描缓存..."); return; }
|
||||
if (g_cerr) { ncm_draw_empty(r_list, "ERROR", "缓存目录读取失败"); return; }
|
||||
ncm_draw_empty(r_list, "暂无缓存", "播放过的歌曲会自动缓存");
|
||||
}
|
||||
|
||||
static void c_draw_footer(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_CACHE) return;
|
||||
int n = appstate_cache_count();
|
||||
if (n <= 0) return;
|
||||
uint64_t total = 0;
|
||||
ncm_cache_item_t it;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (appstate_cache_item(i, &it) == NCM_OK) total += it.size;
|
||||
char sz[24], foot[96];
|
||||
fmt_size(total, sz, sizeof sz);
|
||||
snprintf(foot, sizeof foot, "%d 首 · 合计 %s", n, sz);
|
||||
ncm_draw_footer(r_cont, foot);
|
||||
}
|
||||
|
||||
static void c_apply(void)
|
||||
{
|
||||
if (!g_list) return;
|
||||
int n = appstate_cache_count();
|
||||
meow_listview_set_count(g_list, n);
|
||||
if (n != g_last_count) {
|
||||
g_last_count = n;
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_set_page_bg(th->bg);
|
||||
}
|
||||
|
||||
static void *cache_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int rc = appstate_cache_list_load(); /* 扫描 + 文件内 tag + 旁路补名:全部磁盘 IO */
|
||||
pthread_mutex_lock(&g_cmu);
|
||||
g_cerr = (rc < 0) ? rc : 0;
|
||||
g_cloading = 0;
|
||||
g_cgen++;
|
||||
pthread_mutex_unlock(&g_cmu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cache_reload(void)
|
||||
{
|
||||
if (g_host_mock) { /* 宿主渲染:同步扫描(无线程/无延时,截图确定) */
|
||||
int rc = appstate_cache_list_load();
|
||||
g_cerr = (rc < 0) ? rc : 0;
|
||||
g_cloading = 0;
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
return;
|
||||
}
|
||||
if (g_cthr_on) return;
|
||||
g_cloading = 1;
|
||||
g_cthr_on = 1;
|
||||
if (ncm_thread_create(&g_cthr, cache_worker, NULL) != 0) {
|
||||
g_cthr_on = 0; g_cloading = 0;
|
||||
g_cerr = NCM_ERR_IO;
|
||||
}
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
|
||||
/* 单曲删除(行右「删除」):删文件 + 摘除快照项 + 提示。 */
|
||||
static void cache_delete_row(int row)
|
||||
{
|
||||
ncm_cache_item_t it;
|
||||
if (appstate_cache_item(row, &it) != NCM_OK) return;
|
||||
char name[96], msg[160];
|
||||
ncm_strlcpy(name, it.name[0] ? it.name : it.id, sizeof name);
|
||||
if (appstate_cache_remove(row) == 0) {
|
||||
snprintf(msg, sizeof msg, "已删除:%s", name);
|
||||
ncm_snack(msg);
|
||||
c_apply();
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
} else {
|
||||
ncm_snack("删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
/* 清空(顶栏「清空」):确认框(设置页「清理缓存」同语义:删除全部音频 + 封面缓存条目)。 */
|
||||
static void cache_confirm_clear(void)
|
||||
{
|
||||
int n = appstate_cache_count();
|
||||
if (n <= 0) { ncm_snack("缓存已为空"); return; }
|
||||
uint64_t total = 0;
|
||||
ncm_cache_item_t it;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (appstate_cache_item(i, &it) == NCM_OK) total += it.size;
|
||||
char sz[24], msg[192];
|
||||
fmt_size(total, sz, sizeof sz);
|
||||
snprintf(msg, sizeof msg, "将删除全部 %d 首已缓存歌曲 (%s) 及封面缓存。\n已播放的歌曲需重新下载。",
|
||||
n, sz);
|
||||
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "清空缓存?");
|
||||
const char *msgs[1];
|
||||
msgs[0] = msg;
|
||||
meow_dialog_set_items(d, msgs, 1, -1);
|
||||
int yes = meow_dialog_confirm(d);
|
||||
meow_dialog_free(d);
|
||||
if (!yes) return;
|
||||
appstate_cache_clear();
|
||||
ncm_snack("缓存已清理");
|
||||
c_apply();
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
|
||||
void cache_open(void)
|
||||
{
|
||||
meow_view_page_push(ACT_CACHE);
|
||||
cache_reload();
|
||||
c_apply();
|
||||
meow_ui_paint_full();
|
||||
}
|
||||
|
||||
int cache_is_active(void) { return g_cc.active; }
|
||||
|
||||
int cache_on_tap(int x, int y)
|
||||
{
|
||||
if (g_tb) {
|
||||
int ah = meow_topappbar_hit(g_tb, x, y);
|
||||
if (ah == MEOW_TOPBAR_BACK) { c_drag_reset(); meow_view_page_pop(); return 1; }
|
||||
if (ah == 0) { c_drag_reset(); cache_confirm_clear(); return 1; } /* 文本动作「清空」 */
|
||||
}
|
||||
if (meow_widget_hit(g_list, x, y)) {
|
||||
int was_drag = (g_press_delta > 10);
|
||||
int row = (g_press_row >= 0) ? g_press_row : meow_listview_hit(g_list, x, y);
|
||||
int del = (x >= r_list.x + r_list.w - APP_OP_W);
|
||||
c_drag_reset();
|
||||
if (row < 0 || was_drag) return 1;
|
||||
if (del) { cache_delete_row(row); return 1; }
|
||||
player_play_cache_song(row);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cache_touch_down(int x, int y)
|
||||
{
|
||||
c_drag_reset();
|
||||
if (appstate_cache_count() <= 0) return;
|
||||
if (meow_widget_hit(g_list, x, y)) {
|
||||
int row = meow_listview_hit(g_list, x, y);
|
||||
if (row >= 0) g_press_row = row;
|
||||
g_dragging = 1; g_drag_last_y = y;
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
}
|
||||
|
||||
void cache_touch_move(int x, int y)
|
||||
{
|
||||
(void)x;
|
||||
if (!g_dragging) return;
|
||||
if (g_press_row >= 0) {
|
||||
int d = y - g_drag_last_y; if (d < 0) d = -d;
|
||||
g_press_delta += d;
|
||||
if (g_press_delta > 10) g_press_row = -1;
|
||||
}
|
||||
int dy = y - g_drag_last_y; g_drag_last_y = y;
|
||||
if (dy == 0) return;
|
||||
int before = meow_listview_scroll(g_list);
|
||||
meow_listview_scroll_by(g_list, -dy);
|
||||
if (meow_listview_scroll(g_list) != before)
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
|
||||
void cache_swipe(int dir, int dist)
|
||||
{
|
||||
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
|
||||
if (appstate_cache_count() <= 0) return;
|
||||
int del = (dir == MEOW_SWIPE_UP) ? dist : -dist;
|
||||
int before = meow_listview_scroll(g_list);
|
||||
meow_listview_scroll_by(g_list, del);
|
||||
if (meow_listview_scroll(g_list) != before)
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
|
||||
void cache_tick(uint64_t now)
|
||||
{
|
||||
(void)now;
|
||||
chrome_tick();
|
||||
if (!g_cc.active) return;
|
||||
if (!g_cthr_on) return;
|
||||
int gen, acked;
|
||||
pthread_mutex_lock(&g_cmu);
|
||||
gen = g_cgen; acked = g_cacked;
|
||||
pthread_mutex_unlock(&g_cmu);
|
||||
if (gen == acked) return;
|
||||
pthread_mutex_lock(&g_cmu);
|
||||
g_cacked = gen;
|
||||
pthread_mutex_unlock(&g_cmu);
|
||||
g_cthr_on = 0;
|
||||
if (g_cerr) ncm_snack("缓存读取失败");
|
||||
c_apply();
|
||||
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
|
||||
}
|
||||
|
||||
/* 生命周期 */
|
||||
static void c_page_create(meow_view *v, void *ctx)
|
||||
{
|
||||
(void)v;
|
||||
((cache_ctrl *)ctx)->view = v;
|
||||
r_cont = ncm_content_rect();
|
||||
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
|
||||
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
|
||||
if (r_list.h < 0) r_list.h = 0;
|
||||
meow_ui_activity_push(ACT_CACHE);
|
||||
g_tb = ncm_topbar_create("已缓存", 1, "清空"); /* 文本动作(动作0)=清空缓存 */
|
||||
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||
c_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_item_h(g_list, APP_ROW_H);
|
||||
meow_ui_activity_pop();
|
||||
meow_widget_set_z(g_list, 10);
|
||||
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||
c_row_bg, NULL);
|
||||
g_draw_id = meow_draw_custom_register(60, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
|
||||
c_draw, NULL);
|
||||
g_empty_id = meow_draw_custom_register(1, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||
c_draw_empty, NULL);
|
||||
g_footer_id = meow_draw_custom_register(1, r_cont.x, r_cont.y + r_cont.h - APP_FOOTER_H,
|
||||
r_cont.w, APP_FOOTER_H, c_draw_footer, NULL);
|
||||
c_drag_reset();
|
||||
}
|
||||
|
||||
static void c_page_enter(meow_view *v, void *ctx)
|
||||
{
|
||||
(void)v;
|
||||
((cache_ctrl *)ctx)->active = 1;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_set_page_bg(th->bg);
|
||||
g_last_count = -1;
|
||||
cache_reload(); /* 每次进入重扫(播放/删除后 LRU 变化) */
|
||||
c_apply();
|
||||
meow_ui_paint_full();
|
||||
}
|
||||
|
||||
static void c_page_leave(meow_view *v, void *ctx)
|
||||
{
|
||||
(void)v;
|
||||
((cache_ctrl *)ctx)->active = 0;
|
||||
}
|
||||
|
||||
static void c_page_destroy(meow_view *v, void *ctx)
|
||||
{
|
||||
(void)v; (void)ctx;
|
||||
if (g_bg_id) { meow_draw_unregister(g_bg_id); g_bg_id = 0; }
|
||||
if (g_draw_id) { meow_draw_unregister(g_draw_id); g_draw_id = 0; }
|
||||
if (g_empty_id) { meow_draw_unregister(g_empty_id); g_empty_id = 0; }
|
||||
if (g_footer_id) { meow_draw_unregister(g_footer_id); g_footer_id = 0; }
|
||||
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
|
||||
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
|
||||
}
|
||||
|
||||
meow_view *cache_view_create(void)
|
||||
{
|
||||
static const meow_view_hooks h = {
|
||||
.on_create = c_page_create,
|
||||
.on_enter = c_page_enter,
|
||||
.on_leave = c_page_leave,
|
||||
.on_destroy = c_page_destroy,
|
||||
};
|
||||
return meow_view_create(ACT_CACHE, MEOW_VIEW_ACTIVITY, &h, &g_cc);
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
/* ui/home.c — 我的音乐页(ACT_HOME,根页,已登录)。
|
||||
*
|
||||
* 网易云风格(NCM_UI_DESIGN §4.2):红系 topbar(标题「我的音乐」+搜索/刷新/设置/回播放页动作)+
|
||||
* 网易云风格(NCM_UI_DESIGN §4.2):红系 topbar(标题「我的音乐」+刷新/设置/搜索/已缓存/回播放页动作)+
|
||||
* 个人区卡片(头像/昵称/ID)+ 双页签「我创建的/我收藏的」(meow_tabbar + 各自 meow_listview)+
|
||||
* 组头红条 + 歌单行(rounded card + 红 mini-vinyl 缩略 + 红色按压竖条)。
|
||||
* 批6:顶栏「回播放页」按钮(sb_icons 位图,动作3 最右)——无活动音频也生效,按下 push(ACT_PLAYER)。
|
||||
* 批6:顶栏「回播放页」按钮(sb_icons 位图,动作4 最右)——无活动音频也生效,按下 push(ACT_PLAYER)。
|
||||
* M5:顶栏「已缓存」按钮(动作3,MEOW_ICON_DOWNLOAD 位图)→ 已缓存列表页 ACT_CACHE。
|
||||
*
|
||||
* 品牌红走 ncm_palette(g_dark)(App 无权改 SDK MD3 蓝 primary),代码零硬编码散落:
|
||||
* 中性色读 meow_theme_get(),红读 ncm_palette()。SDK listview 的 MD3 蓝选中不可改,
|
||||
@@ -299,7 +300,8 @@ int home_on_tap(int x, int y)
|
||||
}
|
||||
if (ah == 1) { home_drag_reset(); settings_open(); return 1; } /* 设置 */
|
||||
if (ah == 2) { home_drag_reset(); search_open(); return 1; } /* 搜索 */
|
||||
if (ah == 3) { home_drag_reset(); meow_view_page_push(ACT_PLAYER); return 1; } /* 回播放页 */
|
||||
if (ah == 3) { home_drag_reset(); cache_open(); return 1; } /* 已缓存列表 */
|
||||
if (ah == 4) { home_drag_reset(); meow_view_page_push(ACT_PLAYER); return 1; } /* 回播放页 */
|
||||
}
|
||||
if (g_tabs && meow_widget_hit(g_tabs, x, y)) {
|
||||
int t = meow_tabbar_hit(g_tabs, x, y);
|
||||
@@ -376,15 +378,18 @@ static void home_page_create(meow_view *v, void *ctx)
|
||||
g_tb = ncm_topbar_create("我的音乐", 0, "\xe2\x86\xbb"); /* 刷新(动作0) */
|
||||
meow_topappbar_add_action(g_tb, "\xe2\x9a\x99"); /* 设置(动作1) */
|
||||
meow_topappbar_add_action(g_tb, "\xe2\x8c\x95"); /* 搜索(动作2) */
|
||||
/* 批6:回播放页(动作3, 最右)。无活动音频也生效——按下即 push(ACT_PLAYER),播放页按 appstate
|
||||
/* M5:已缓存列表入口(动作3)——songs/ 池全部已缓存歌曲(点播/删除/清空)。 */
|
||||
meow_topappbar_add_action(g_tb, "\xe2\x87\xa9"); /* 已缓存(动作3) */
|
||||
/* 批6:回播放页(动作4, 最右)。无活动音频也生效——按下即 push(ACT_PLAYER),播放页按 appstate
|
||||
* 现有语义展示空态(暂无播放)/上次会话状态。 */
|
||||
meow_topappbar_add_action(g_tb, "\xe2\x99\xaa"); /* 回播放页(动作3, 最右) */
|
||||
meow_topappbar_add_action(g_tb, "\xe2\x99\xaa"); /* 回播放页(动作4, 最右) */
|
||||
/* §307 topbar 图标迁移:三位码点(↻ ⚙ ⌕)在 4k 字库缺字形渲染成豆腐(WORK_STATE 310 前置
|
||||
* 症状①),改为 sb_icons 位图(Material Symbols Rounded,字符标签仅作占位,绘制走位图)。 */
|
||||
meow_topappbar_set_item_icon(g_tb, 0, MEOW_ICON_REFRESH);
|
||||
meow_topappbar_set_item_icon(g_tb, 1, MEOW_ICON_SETTINGS);
|
||||
meow_topappbar_set_item_icon(g_tb, 2, MEOW_ICON_SEARCH);
|
||||
meow_topappbar_set_item_icon(g_tb, 3, MEOW_ICON_QUEUE_MUSIC); /* 回播放页 */
|
||||
meow_topappbar_set_item_icon(g_tb, 3, MEOW_ICON_DOWNLOAD); /* 已缓存列表 */
|
||||
meow_topappbar_set_item_icon(g_tb, 4, MEOW_ICON_QUEUE_MUSIC); /* 回播放页 */
|
||||
g_tabs = meow_tabbar_create(r_tabs.x, r_tabs.y, r_tabs.w, r_tabs.h);
|
||||
meow_tabbar_add_tab(g_tabs, tab_label(0));
|
||||
meow_tabbar_add_tab(g_tabs, tab_label(1));
|
||||
|
||||
@@ -783,6 +783,8 @@ void player_play_queue_song(int idx)
|
||||
int have = 0;
|
||||
if (appstate_get_queue() == NCM_QUEUE_SEARCH) {
|
||||
if (appstate_search_song(idx, &s) == 0) have = 1;
|
||||
} else if (appstate_get_queue() == NCM_QUEUE_CACHE) {
|
||||
if (appstate_cache_song(idx, &s) == 0) have = 1;
|
||||
} else if (appstate_song_copy(idx, &s) == 0) {
|
||||
have = 1;
|
||||
}
|
||||
@@ -794,6 +796,15 @@ void player_play_queue_song(int idx)
|
||||
meow_view_page_push(ACT_PLAYER);
|
||||
}
|
||||
|
||||
/* 已缓存列表点播(ACT_CACHE 页点行):切 NCM_QUEUE_CACHE 队列后走同一套「队列点播」路径——
|
||||
* 上/下一首在缓存列表内循环;播放线程按条目真实键直读缓存文件(不重下)。 */
|
||||
void player_play_cache_song(int i)
|
||||
{
|
||||
if (i < 0 || i >= appstate_cache_count()) return;
|
||||
appstate_set_queue(NCM_QUEUE_CACHE);
|
||||
player_play_queue_song(i);
|
||||
}
|
||||
|
||||
void player_pause_toggle(int on)
|
||||
{
|
||||
if (player_manager_state() == NCM_PC_PAUSED) player_manager_pause(0);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ui/queue.c — 当前实时播放队列页(ACT_QUEUE,§NEW:播放页「队列」钮进入)。
|
||||
*
|
||||
* 展示**当前播放队列**(player_manager 的 queue_src —— 歌单滑动窗口 或 搜索单曲结果集),
|
||||
* 含当前曲高亮、可点某行切到那首播放。数据源与分页复用歌单滑动窗口(REPORT §17):
|
||||
* 展示**当前播放队列**(player_manager 的 queue_src —— 歌单滑动窗口 / 搜索单曲结果集 /
|
||||
* 已缓存列表),含当前曲高亮、可点某行切到那首播放。数据源与分页复用歌单滑动窗口(REPORT §17):
|
||||
* - count = appstate_queue_count()(全量:track_ids_count 或 search_song_count,可 5194 首);
|
||||
* - 行内容 = src2 逐行按 queue_src 取(PLAYLIST→滑窗拷贝,SEARCH→搜索数组),未物化槽显示
|
||||
* ".../加载中" 占位,视口 ±24 行未物化 → 后台 worker ensure_range 补载(盘缓存优先,批≤NCM_SONG_BATCH);
|
||||
@@ -47,8 +47,11 @@ static void q_drag_reset(void)
|
||||
* PLAYLIST:滑窗拷贝(UI 线程不触发网络);SEARCH:搜索数组(内存)。 */
|
||||
static int q_song_get(int idx, ncm_song_t *out)
|
||||
{
|
||||
if (appstate_get_queue() == NCM_QUEUE_SEARCH)
|
||||
int src = appstate_get_queue();
|
||||
if (src == NCM_QUEUE_SEARCH)
|
||||
return (appstate_search_song(idx, out) == 0) ? 0 : -1;
|
||||
if (src == NCM_QUEUE_CACHE)
|
||||
return (appstate_cache_song(idx, out) == 0) ? 0 : -1;
|
||||
return (appstate_song_copy(idx, out) == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
@@ -195,7 +198,7 @@ static void *queue_worker(void *arg)
|
||||
* SEARCH 队列全在内存,无需补载。 */
|
||||
static void q_maybe_ensure(void)
|
||||
{
|
||||
if (appstate_get_queue() != NCM_QUEUE_PLAYLIST) return;
|
||||
if (appstate_get_queue() != NCM_QUEUE_PLAYLIST) return; /* SEARCH/CACHE 队列全在内存 */
|
||||
if (g_qthr_on || g_host_mock) return;
|
||||
int total = appstate_queue_count();
|
||||
if (total <= 0) return;
|
||||
|
||||
Reference in new issue
Block a user