feat(ui): M6.38批 — P1 MD3细化(SDK对话框28)+批②底部上滑回桌面+批6 ncm顶栏回播放页+批④面板后台列表+launcher UI微调(歌词页黑半透明底/播放页布局下移)+封面空artwork保留修复

- P1: meow_ui_dialog.c 对话框底框 radius_card→MEOW_SHAPE_EXTRA_LARGE(28) 三处(:103填充/:104描边/:361 BottomSheet); meow_ui_widgets.h 新增 MEOW_SHAPE_EXTRA_LARGE 28 宏; TextField/FAB/Slider/Progress 按设计文档保留
- 批②: np_gesture.h 新增 NP_GEST_HOME+np_home_gesture(底部热区上滑,与左缘返回对称互斥); now_playing.c 新增 np_draw_home_bar(屏底左缘pill指示条)+手势衔接; test_now_playing_widgets G5b
- 批6: ncm home.c 顶栏加回播放页按钮(MEOW_ICON_QUEUE_MUSIC)→push(ACT_PLAYER), 无音频也生效
- 批④: meowpanel /api/ams/bg 只读端点+前端后台列表页+杀/清复用kill通道
- UI微调: now_playing.c 播放页布局宏下移(NP_COVER_Y 84/NP_BTN_Y 612消底部留白)+歌词页背景统一黑色半透明叠层(弃封面取色底,删 np_draw_lyric_coverbg)
- 封面保留: now_playing.c/notif_center.c 空artwork保留上一张已解码封面(防暂停/开歌单致META报空artwork后封面闪没); test_np_cover C2断言更新+mirror同步

0warning;宿主测试 test_np_cover ALL PASS;launcher/ncm/panel 宿主测试无回归。二进制留批量批次未入库
This commit is contained in:
AskaEth committed 2026-09-04 13:09:03 +08:00
1 parent b3ce719742
commit a2aec783a7
15 files changed
+418 -47

No files matched your search

+1
View File
@@ -900,6 +900,7 @@ static void ncp_media_cover_load(const meow_media_session_t *s)
{
if (!s) return;
if (strcmp(g_ncp_media_cover_path, s->artwork) == 0) return;
if (!s->artwork[0]) return; /* 用户 2026-09-04:空 artwork 保留上一张媒体卡封面(勿清,防暂停/开歌单后封面闪没) */
if (g_ncp_media_cover) { meow_img_free(g_ncp_media_cover); g_ncp_media_cover = NULL; }
ncp_strncpy(g_ncp_media_cover_path, s->artwork, sizeof g_ncp_media_cover_path);
if (!s->artwork[0]) return;
+45 -26
View File
@@ -17,20 +17,28 @@
#define NP_COVER 300 /* 封面边长(方,居中) */
#define NP_COVER_X ((fb_xres - NP_COVER) / 2)
#define NP_COVER_Y 60
#define NP_TITLE_Y 412
#define NP_ARTIST_Y 450
#define NP_ALBUM_Y 480
#define NP_COVER_Y 84 /* 用户 2026-09-04:控件组整体下移,消除底部大留白 */
#define NP_TITLE_Y 436
#define NP_ARTIST_Y 476
#define NP_ALBUM_Y 508
#define NP_PROG_X 40
#define NP_PROG_W (fb_xres - 80)
#define NP_PROG_Y 518
#define NP_TIME_Y 492
#define NP_BTN_Y 568 /* 控制按钮中心 y */
#define NP_PROG_Y 548
#define NP_TIME_Y 526
#define NP_BTN_Y 612 /* 控制按钮中心 y */
#define NP_PLAY_CX (fb_xres / 2)
#define NP_PREV_CX (NP_PLAY_CX - 78)
#define NP_NEXT_CX (NP_PLAY_CX + 78)
#define NP_PLAY_R 30
#define NP_PREV_R 22
/* 批② home 指示条(底部上滑回桌面):位于屏底左缘、避开左缘返回区(NP_EDGE_W=24)。
* 与 np_gesture.h np_home_gesture 的起滑区一致(down_x>=NP_EDGE_W 才认 home),保证
* 「指示条可触发手势」自洽。y 距屏底 16px,高 6px(镜像 SDK meow_ui_swipe_hint 提示线高一档)。
*/
#define NP_HOME_BAR_X (NP_EDGE_W + 8) /* 左缘 32:紧贴左缘返回区右侧 */
#define NP_HOME_BAR_W 48
#define NP_HOME_BAR_H 6
#define NP_HOME_BAR_Y (fb_yres - 16)
static meow_img *g_np_cover = NULL;
static char g_np_cover_path[200] = "";
@@ -59,6 +67,8 @@ static void np_cover_load(const meow_media_session_t *s)
{
if (!s) return;
if (g_np_cover && strcmp(g_np_cover_path, s->artwork) == 0) return; /* 已载且未变 → 跳过 */
if (!s->artwork[0]) return; /* 用户 2026-09-04:空 artwork(如打开歌单清 g_cover_path 致 META 报空)
* 时保留上一张已解码封面,勿据此清封面 → 播放页/歌词页封面不闪没 */
if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; }
snprintf(g_np_cover_path, sizeof g_np_cover_path, "%s", s->artwork);
if (!s->artwork[0]) return;
@@ -326,20 +336,18 @@ static void np_lyric_refresh(const meow_media_session_t *s)
g_np_lyric_last_valid = valid;
}
/* 歌词页封面作整页背景:等比 cover 到 max(W, H-SB_BAR_H) 方块、居中铺满内容区(裁剪溢出
* 部分),衬在 lyric_view 半透明 scrim 之下形成压暗封面;无封面不画(回退渐变/主题面)。
* 批6:批5 只铺渐变、从不画封面 → 歌词页无封面;此处补上封面背景,取色只做附加。 */
static void np_draw_lyric_coverbg(int W, int H)
/* 批② home 指示条:底部上滑回桌面的可见反馈(镜向左缘返回提示条;唯位置=屏底左缘)。
* 主题用 on_surface_variant(淡灰指示,iOS home 指示同款语义);draw 在整页/空态/歌词页
* 均调用一次(np_draw_page 播放分支/空态分支 + np_draw_lyric_page)。 */
static void np_draw_home_bar(void)
{
if (!g_np_cover) return;
int size = W > (H - SB_BAR_H) ? W : (H - SB_BAR_H);
int x = (W - size) / 2;
int y = SB_BAR_H + (H - SB_BAR_H - size) / 2;
if (y < SB_BAR_H) y = SB_BAR_H;
meow_ui_img_cover(x, y, size, g_np_cover);
const meow_ui_theme_t *th = s_ui_theme();
meow_gfx_fill_round(NP_HOME_BAR_X, NP_HOME_BAR_Y, NP_HOME_BAR_W, NP_HOME_BAR_H,
NP_HOME_BAR_H / 2, th->on_surface_variant);
}
/* 歌词页整页绘制:铺封面取色渐变(或 theme surface)底 → lyric_view(自绘 scrim + 歌词/空态)。 */
/* 歌词页整页绘制:背景统一黑色半透明叠加层(用户 2026-09-04,弃封面取色/封面图半透明底,
* 保证高亮封面下歌词对比度)→ lyric_view(自绘 scrim + 歌词/空态)。 */
static void np_draw_lyric_page(int redraw_sb)
{
const meow_ui_theme_t *th = s_ui_theme();
@@ -354,15 +362,13 @@ static void np_draw_lyric_page(int redraw_sb)
np_cover_load(meow_media_current());
np_cover_theme_update();
np_apply_lyric_colors();
if (g_np_cover)
np_draw_lyric_coverbg(W, H);
else if (g_np_cc_valid)
np_fill_gradient(0, SB_BAR_H, W, H - SB_BAR_H, g_np_cc.bg_top, g_np_cc.bg_bottom);
else
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
/* 用户 2026-09-04:歌词页背景统一黑色半透明叠加层(弃封面取色/封面图半透明底)——
* 部分高亮度封面对歌词显示不友好,黑底保证对比度。歌词行颜色/布局/滚动不变。 */
meow_ui_fill_rect_alpha(0, SB_BAR_H, W, H - SB_BAR_H, 0x000000, 176);
np_lyric_refresh(meow_media_current());
if (g_np_lyric)
meow_widget_draw(meow_lyric_view_widget(g_np_lyric));
np_draw_home_bar(); /* 批②:歌词页也示 home 指示(底部上滑可回桌面) */
if (redraw_sb) draw_statusbar();
}
@@ -388,6 +394,7 @@ static void np_draw_page(int redraw_sb)
ui_draw((W - ui_width(t2, 18)) / 2, 348, t2, th->on_surface_variant, 18);
const char *t3 = meow_str_zh("np.edge_back", "左缘右滑返回");
ui_draw((W - ui_width(t3, 16)) / 2, 396, t3, th->on_surface_variant, 16);
np_draw_home_bar(); /* 批②:空态也示 home 指示(底部上滑可回桌面) */
if (redraw_sb) draw_statusbar();
return;
}
@@ -424,8 +431,9 @@ static void np_draw_page(int redraw_sb)
np_draw_progress(s, pos, th);
/* 控制按钮(SDK meow_iconbtn,命中/状态走 SDK) */
np_draw_ctl(s);
/* 播放页底部干净(M6.33:歌词占位已移除——已有独立歌词页,占位多余;
* 歌词页无歌词时的空态由歌词页自身承载,播放页不再显示歌词占位文案)。 */
/* 播放页底部(M6.33:歌词占位已移除——已有独立歌词页,占位多余;批②:底部仅示
* home 指示条,非歌词占位)。 */
np_draw_home_bar(); /* 批②:播放页示 home 指示(底部上滑可回桌面) */
if (redraw_sb) draw_statusbar();
}
@@ -553,6 +561,11 @@ void run_now_playing(struct app *apps, int napps)
* 的方向感知判定(真机定位「返回滑不出」根因曾依赖此日志;
* 保留,键改为 ges=np_gesture_decide 返回值)。 */
np_gest_t gnp = np_gesture_decide(g_np_page, tdx, tdy, dx, dy);
/* 批②:底部上滑(home)优先——该手势竖向主导、起点在底部起滑区,
* 与左缘返回(横向+左缘起点)/顶部下滑(顶部热区)互斥,故先判 home
* 覆盖(np_gesture_decide 对纵滑返回 NONE,不冲突)。 */
if (np_home_gesture(tdx, tdy, dx, dy, fb_yres))
gnp = NP_GEST_HOME;
logmsg("np up: page=%d down=(%d,%d) d=(%d,%d) adx=%d ady=%d ges=%d",
g_np_page, tdx, tdy, dx, dy, adx, ady, (int)gnp);
switch (gnp) {
@@ -576,6 +589,12 @@ void run_now_playing(struct app *apps, int napps)
logmsg("np: BACK break (exit, page=%d)", g_np_page);
np_exit = 1;
break;
case NP_GEST_HOME:
/* 批②:底部上滑 → 回桌面(后台)。媒体会话续播/进程保活归批①
* keepalive(本页退出不改保活核心),此处仅走回桌面的既有路径。 */
logmsg("np: HOME bottom-swipe-up -> back to desktop (bg)");
np_exit = 1;
break;
case NP_GEST_NEXT:
/* 播放页 右→左滑 → 进歌词页。 */
g_np_page = NP_PAGE_LYRIC;
+21
View File
@@ -25,6 +25,7 @@ typedef enum {
NP_GEST_NEXT, /* 播放页 右→左滑 → 进入歌词页 */
NP_GEST_PREV, /* 歌词页 左→右滑 → 回到播放页 */
NP_GEST_EXIT, /* 播放页 任意右滑 → 返回桌面 */
NP_GEST_HOME, /* 批②:底部上滑 → 返回桌面(后台保留) */
NP_GEST_TAP, /* 近点按(按当前页分派:播放页=按钮/进度看点) */
} np_gest_t;
@@ -47,6 +48,12 @@ typedef enum {
#ifndef NP_EDGE_SWIPE
#define NP_EDGE_SWIPE 65 /* 左缘右滑位移阈值(镜像 meow_touch.h MEOW_EDGE_SWIPE) */
#endif
#ifndef NP_HOME_EDGE_H
#define NP_HOME_EDGE_H 28 /* 底部起滑区高(自屏底向上;镜像顶部热区 NP_TOP_HOTZONE 同为 28) */
#endif
#ifndef NP_HOME_SWIPE
#define NP_HOME_SWIPE 65 /* 底部上滑位移阈值(镜像 NP_SWIPE_THRESH / meow_edge_back 同口径) */
#endif
/* 左缘返回(镜像 meow_touch.c meow_edge_back:down_x∈[0,NP_EDGE_W) 且 dx>=NP_EDGE_SWIPE
* 且横向主导)。为宿主测试免链 meow_touch.c,此处 static inline 复刻同口径。 */
@@ -58,6 +65,20 @@ static inline int np_edge_back(int down_x, int dx, int dy)
return 0;
}
/* 批② 底部上滑返回桌面(home,与左缘返回 np_edge_back 对称):原点在底部起滑区
* (down_y∈[fb_h-NP_HOME_EDGE_H, fb_h))且**不在左缘**(down_x>=NP_EDGE_W,与 np_edge_back 按
* 原点分界互斥——左缘 24px 列留给左缘返回)+ 向上位移≥NP_HOME_SWIPE 且竖向主导(adx<-dy)。
* fb_h=屏高(调用方喂 fb_yres,宿主单测亦可喂固定值)。其余(顶部/中部起滑、横向主导、
* 向下滑、未达阈值)一律 0,不与顶部下滑/横向切页/点按冲突。 */
static inline int np_home_gesture(int down_x, int down_y, int dx, int dy, int fb_h)
{
int adx = dx < 0 ? -dx : dx;
if (down_x >= NP_EDGE_W && down_y >= fb_h - NP_HOME_EDGE_H && down_y >= 0 &&
dy <= -NP_HOME_SWIPE && adx < -dy)
return 1;
return 0;
}
/* 按当前页把手势几何 → 动作。down_x/down_y=抬手原点;dx/dy=抬手位移(释放-原点,即 tlx-tdx)。
* 优先级:顶部下滑 > 横向主导切页/返回 > 点按。横向反向或无主导 → NONE(调用方忽略,不误切)。 */
static inline np_gest_t np_gesture_decide(int page, int down_x, int down_y, int dx, int dy)
@@ -308,6 +308,50 @@ int main(void)
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 300, 5, 80) == NP_GEST_NONE,
"vertical dominant -> NONE");
/* ============ G5b 批② 底部上滑(home)手势(np_gesture.h np_home_gesture,纯逻辑) ============ */
printf("G5b bottom-swipe-up home gesture\n");
const int FBRES = 720; /* 屏高(宿主 480x720;真机 fb_yres=720) */
const int BZ = FBRES - NP_HOME_EDGE_H; /* 底部起滑区上界 692 */
/* 底部起滑 + 向上主导 ≥阈值 → HOME */
CHECK(np_home_gesture(200, BZ + 10, 0, -120, FBRES) == 1,
"home: bottom zone + up-swipe -> HOME");
CHECK(np_home_gesture(100, FBRES - 1, 20, -80, FBRES) == 1,
"home: very bottom + slight right-drift up -> HOME");
/* 与左缘返回互斥:左缘起点(down_x<NP_EDGE_W)不认 home(让给左缘返回) */
CHECK(np_home_gesture(10, BZ + 10, 0, -120, FBRES) == 0,
"home: left-edge origin -> NOT home (reserved for edge-back)");
/* 起点不在底部起滑区(顶部/中部)不认 home */
CHECK(np_home_gesture(200, 10, 0, -120, FBRES) == 0,
"home: top origin -> NOT home");
CHECK(np_home_gesture(200, 300, 0, -120, FBRES) == 0,
"home: mid origin -> NOT home");
/* 横向主导(向右滑)不认 home(该判给左缘返回/切页) */
CHECK(np_home_gesture(200, BZ + 10, 120, 0, FBRES) == 0,
"home: right-dominant -> NOT home");
/* 向下滑 / 未达阈值 / 竖向不主导 不认 home */
CHECK(np_home_gesture(200, BZ + 10, 0, 120, FBRES) == 0,
"home: downward -> NOT home");
CHECK(np_home_gesture(200, BZ + 10, 0, -NP_HOME_SWIPE + 1, FBRES) == 0,
"home: below up-threshold -> NOT home");
CHECK(np_home_gesture(200, BZ + 10, -120, -80, FBRES) == 0,
"home: horizontal-dominant up -> NOT home");
/* 与 now_playing.c 集成顺序一致:np_gesture_decide 对底部纵滑返回 NONE,再由 home 覆盖为 HOME */
{
int dx = 0, dy = -120, tdx = 200, tdy = BZ + 10;
np_gest_t gnp = np_gesture_decide(NP_PAGE_PLAY, tdx, tdy, dx, dy);
if (np_home_gesture(tdx, tdy, dx, dy, FBRES)) gnp = NP_GEST_HOME;
CHECK(gnp == NP_GEST_HOME,
"integration: bottom up-swipe -> HOME (overrides decide-NONE)");
}
/* 集成:左缘起点底部上滑不被误判为 home(保留给左缘返回) */
{
int tdx = 8, tdy = BZ + 10, dx = 0, dy = -120; /* 左缘 + 上滑(竖向主导) */
np_gest_t gnp = np_gesture_decide(NP_PAGE_PLAY, tdx, tdy, dx, dy);
if (np_home_gesture(tdx, tdy, dx, dy, FBRES)) gnp = NP_GEST_HOME;
CHECK(gnp != NP_GEST_HOME,
"integration: left-edge origin up-swipe -> NOT home");
}
/* ============ G6 歌词页:空态 + 渲染(批2 控件 + 批3 数据语义) ============ */
printf("G6 lyric view: empty + render\n");
meow_ui_activity_push(NP_SENTINEL);
+5 -4
View File
@@ -48,6 +48,7 @@ static void np_cover_load(const meow_media_session_t *s)
{
if (!s) return;
if (g_np_cover && strcmp(g_np_cover_path, s->artwork) == 0) return; /* 已载且未变 → 跳过 */
if (!s->artwork[0]) return; /* 镜像 now_playing.c 2026-09-04:空 artwork 保留上一张封面(勿清) */
if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; }
snprintf(g_np_cover_path, sizeof g_np_cover_path, "%s", s->artwork);
if (!s->artwork[0]) return;
@@ -135,12 +136,12 @@ int main(void)
np_cover_load(&s);
CHECK(g_np_cover == keep, "same path unchanged -> cache hit (no reload)");
/* ============ C2 artwork 空 → 封面清空(回退主题占位) ============ */
printf("C2 artwork empty -> cover cleared (theme placeholder)\n");
/* ============ C2 artwork 空 → 封面保留(用户 2026-09-04:勿因空 artwork 清封面) ============ */
printf("C2 artwork empty -> cover kept (NOT cleared)\n");
memset(s.artwork, 0, sizeof s.artwork);
np_cover_load(&s);
CHECK(g_np_cover == NULL, "artwork empty -> cover NULL (theme placeholder)");
CHECK(g_np_cover_path[0] == 0, "empty -> cached path reset to \"\"");
CHECK(g_np_cover == keep, "artwork empty -> cover kept (non-NULL, NOT cleared)");
CHECK(strcmp(g_np_cover_path, pa) == 0, "empty -> cached path kept (still pa)");
/* ============ C3 artwork 从空复原(同路径)→ 封面重载(M6.33 修复) ============ */
printf("C3 artwork restored (same path) -> cover reloads (M6.33)\n");