feat(ncm): 网易云客户端 v1 UI+播放链路 (agent-205) — os.meow.ncm: 登录页(真实unikey二维码+轮询状态机)/我的歌单(listview)/歌单详情/播放页(封面+seek+滚动歌词+竖横屏切换)/设置(音质默认Hi-res+缓存上限默认10G); 播放线程(缓存LRU+minimp3→ALSA UAPI直开hw:0,0); 媒体会话MSG_META上报+PLAYCTL接收(首真实媒体源)

ncm 1e331964 (text 764KB); 0 warning; 宿主离屏6页截图目检+真实QR可扫+qemu-mipsel干净启动+解码446xRT
真机待联调: ALSA时序/803扫码闭环/媒体会话联动/gbk大字库渲染
This commit is contained in:
MeowOS committed 2026-08-31 07:24:59 +08:00
1 parent 32543ac616
commit 21519ce2e4
23 files changed
+3255

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
/* ui/common/chrome.c — os.meow.ncm 共享 chrome(TopAppBar 建置 / 几何 / 空态&页脚 / 底部提示条)。
*
* 与 files_app/ui/common/chrome.c 同理:把「页面 chrome 固定不随内容重建」分出去。绘制全部读
* meow_theme_get() 语义色(§8.1),不硬编码配色。topbar 由 meow_topappbar 建,自动注册进绘制物
* 注册表(§6.2),owner=创建时 active activity。
*/
#include "../ncm_app_internal.h"
#include <string.h>
int ncm_statusbar(void)
{
int sb = meow_ui_statusbar_height();
return sb < 0 ? 0 : sb;
}
ncm_rect_t ncm_topbar_rect(void)
{
int sb = ncm_statusbar();
return NR(0, sb, meow_ui_width(), APP_TOPBAR_H);
}
ncm_rect_t ncm_content_rect(void)
{
int sb = ncm_statusbar();
int y = sb + APP_TOPBAR_H;
int h = meow_ui_height() - y;
if (h < 0) h = 0;
return NR(0, y, meow_ui_width(), h);
}
meow_widget *ncm_topbar_create(const char *title, int back, const char *action_label)
{
ncm_rect_t r = ncm_topbar_rect();
meow_widget *tb = meow_topappbar_create(r.x, r.y, r.w, r.h, title);
meow_topappbar_set_back(tb, back);
if (action_label) meow_topappbar_add_action(tb, action_label);
meow_widget_set_z(tb, 999); /* chrome 置顶(§6.2:> 一切自动 z) */
return tb;
}
/* 空态/错误:居中两行(kind 大字 + what 小字)。 */
void ncm_draw_empty(ncm_rect_t r, const char *kind, const char *what)
{
const meow_theme *th = meow_theme_get();
int cy = r.y + r.h / 2;
meow_ui_text_center(r.x + r.w / 2, cy - 34, kind ? kind : "", 28, th->primary);
if (what && what[0])
meow_ui_text_center(r.x + r.w / 2, cy + 4, what, 20, th->dim);
}
/* 底部信息(右对齐 dim)。 */
void ncm_draw_footer(ncm_rect_t r, const char *text)
{
const meow_theme *th = meow_theme_get();
if (!text || !text[0]) return;
meow_ui_text_right(r.x + r.w - APP_PAD, r.y + r.h - 26, text, 20, th->dim);
}
/* 底部提示条(非阻塞):画在内容区底部之上,2s 后由 tick 清除。
* 只画一次并标脏;g_snack_until 为过期时间(0=无)。 */
static char s_snack[192];
static uint64_t s_snack_until; /* monotonic ms 过期时刻 */
static ncm_rect_t s_snack_r;
static int s_snack_id; /* <0=未注册 */
static void snack_draw(void *ctx)
{
(void)ctx;
if (meow_ui_time_ms() >= s_snack_until && s_snack_until != 0) {
meow_ui_invalidate(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h);
s_snack_until = 0;
return;
}
const meow_theme *th = meow_theme_get();
/* 深色底 + 白字 */
meow_ui_fill_rect(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h, th->inverseSurface);
meow_ui_text_center(s_snack_r.x + s_snack_r.w / 2, s_snack_r.y + (s_snack_r.h - 26) / 2 + 4,
s_snack, 20, th->inverseOnSurface);
}
void ncm_snack(const char *text)
{
if (!text) return;
strncpy(s_snack, text, sizeof s_snack - 1);
s_snack[sizeof s_snack - 1] = 0;
s_snack_until = meow_ui_time_ms() + 2000;
int w = meow_ui_width() - 2 * APP_PAD;
s_snack_r = NR(APP_PAD, meow_ui_height() - 56, w, 40);
if (s_snack_id < 0)
s_snack_id = meow_draw_custom_register(999, s_snack_r.x, s_snack_r.y, s_snack_r.w,
s_snack_r.h, snack_draw, NULL);
else
meow_draw_set_z(s_snack_id, 999);
meow_ui_invalidate(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h);
}
/* 周期检查:snack 到期清除。可由各页 tick 调。 */
void chrome_tick(void)
{
if (s_snack_until && meow_ui_time_ms() >= s_snack_until) {
meow_ui_invalidate(s_snack_r.x, s_snack_r.y, s_snack_r.w, s_snack_r.h);
s_snack_until = 0;
}
}