- 去标题栏(不再用 ncm_topbar);返回=左缘手势/系统返回钮语义 - 双页结构:封面页/歌词页,屏幕正中左/右滑切换(与左缘≤24px返回、顶部通知分流), 页面指示点显示当前页;中部横滑=on_touch_move 锁存 page-drag + on_tap/on_swipe 判向 - 封面页:方形大图+圆角(无旋转动画)+进度条(点 seek)+MD3 矢量控制钮(prev/play/pause/next, 新增 app/ui/ncm_icons.h 32x32 单通道 alpha,参照 sb_icons.h 范式)+元信息+实际音质档 - 歌词页:LRC 滚动(当前行居中高亮、上下渐隐)+点行跳转(新增 lyric_line_time); 默认=封面模糊置底(封面铺底+主题 scrim),省电=封面背景+黑色半透明叠加层(设置 ncm.player_bg_mode) - 日夜间:主题配色只映射到半透明叠加层/scrim/accent(红),不动沉浸底(NCM_PLAYER_BG 恒定) - 分带刷新沿用:封面页=封面静态带+动态带(互不重叠 bbox,tick 只刷动态带);歌词页换行(~5s)才重画 - 性能:宿主 4 页 PPM 核验+手势自检 PASS;mipsel -Wall -Wextra 0 warning;qemu core 回归 PASS; 0 真实请求(封面/歌词用本地样本注入)。未部署真机(主会话统一) md5: ncm 730e0b6e / ncm_host 500ac85 / player.c 00934de / ncm_icons.h 20f0bba
324 lines
13 KiB
C
324 lines
13 KiB
C
/* ui/settings.c — 设置页(ACT_SETTINGS)。
|
||
*
|
||
* 行项:音质(§20 八档:标准/极高 HQ/无损 SQ/Hi-Res/高清臻音/臻音全景声/超清母带/沉浸环绕声,
|
||
* 默认极高 HQ;每档标注规格+会员门槛 CVIP/SVIP;超权限档允许选定,播放时钳顶+逐级降级,
|
||
* 见 player_manager.c song_local_file)+ 缓存上限(默认 10GB,上限 100GB,支持预设与
|
||
* 「自定义」数字输入)+ 清理缓存(显示当前占用)+ 退出登录 + 关于/版本。
|
||
* 设置持久化到 MeowData/dev.player.ncm/data(meow_config_*,与 core
|
||
* ncm_store_read_cfg_int 同目录同键,ncm.cache_limit_gb/ncm.level)。超上限触发 ncm_cache LRU
|
||
* 淘汰(写缓存前后 + 手动调低上限时,见 player_manager.c/cover.c/appstate.c)。
|
||
* 空态/绘制用 custom 绘制物 + 手动行命中(仅 5 行,无需列表滚动)。
|
||
*/
|
||
#include "../ncm_app_internal.h"
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
|
||
typedef struct { meow_view *view; int active; } settings_ctrl;
|
||
static settings_ctrl g_sc;
|
||
|
||
static meow_widget *g_tb;
|
||
static ncm_rect_t r_cont;
|
||
static int g_custom_id;
|
||
static int g_row_h; /* 行高 */
|
||
static uint64_t g_cache_used; /* 缓存占用字节(进设置页时算一次,清理后清零) */
|
||
|
||
#define SETTINGS_ROWS 6
|
||
|
||
/* 八档音质 UI 表(用户手机 App 口径为权威,NCM_CACHE_DESIGN §3):显示顺序高→低。
|
||
* HIGHER(较高)与 DOLBY(杜比全景声)不进 UI 清单(映射保留,见 ncm_api.h)。 */
|
||
static const int g_ui_levels[8] = {
|
||
NCM_LEVEL_SURROUND, NCM_LEVEL_JYMASTER, NCM_LEVEL_SKY, NCM_LEVEL_JYEFFECT,
|
||
NCM_LEVEL_HIRES, NCM_LEVEL_LOSSLESS, NCM_LEVEL_EXHIGH, NCM_LEVEL_STANDARD,
|
||
};
|
||
|
||
/* 缓存占用字节 → 人类可读(整数运算,不用浮点/软浮点)。buf ≥ 24。 */
|
||
static void fmt_bytes(uint64_t b, char *buf, size_t sz)
|
||
{
|
||
const uint64_t GB = 1024ULL * 1024 * 1024;
|
||
const uint64_t MB = 1024ULL * 1024;
|
||
if (b >= GB) {
|
||
uint64_t g = b / GB, fr = (b % GB) / (GB / 10);
|
||
snprintf(buf, sz, "%llu.%llu GB", (unsigned long long)g, (unsigned long long)fr);
|
||
} else if (b >= MB) {
|
||
uint64_t m = b / MB, fr = (b % MB) / (MB / 10);
|
||
snprintf(buf, sz, "%llu.%llu MB", (unsigned long long)m, (unsigned long long)fr);
|
||
} else if (b >= 1024) {
|
||
snprintf(buf, sz, "%llu KB", (unsigned long long)(b / 1024));
|
||
} else {
|
||
snprintf(buf, sz, "%llu B", (unsigned long long)b);
|
||
}
|
||
}
|
||
|
||
/* 刷新缓存占用缓存(进设置页/清理后调用)。 */
|
||
static void refresh_cache_used(void)
|
||
{
|
||
g_cache_used = ncm_cache_total_bytes(&g_st.ctx.cache);
|
||
}
|
||
|
||
static void settings_draw(void *ctx)
|
||
{
|
||
(void)ctx;
|
||
if (meow_ui_activity_active() != ACT_SETTINGS) return;
|
||
const meow_theme *th = meow_theme_get();
|
||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||
g_row_h = 60;
|
||
int y = r_cont.y + 16;
|
||
int w = r_cont.w - 2 * APP_PAD;
|
||
int x = APP_PAD;
|
||
|
||
char usetxt[24];
|
||
fmt_bytes(g_cache_used, usetxt, sizeof usetxt);
|
||
|
||
/* 行:音质 / 歌词页背景 / 缓存上限 / 清理缓存 / 退出登录 / 关于。 */
|
||
struct { const char *title; char sub[160]; int red; } rows[SETTINGS_ROWS];
|
||
rows[0].title = "音质"; rows[0].red = 0;
|
||
rows[1].title = "歌词页背景"; rows[1].red = 0;
|
||
rows[2].title = "缓存上限"; rows[2].red = 0;
|
||
rows[3].title = "清理缓存"; rows[3].red = 0;
|
||
rows[4].title = "退出登录"; rows[4].red = 1;
|
||
rows[5].title = "关于网易云"; rows[5].red = 0;
|
||
{
|
||
/* 当前档 + 会员门槛标注;超权限(未解锁)时提示将降级(不硬拒,§20) */
|
||
int gate = ncm_api_level_gate(g_st.level);
|
||
const char *gate_s = (gate == NCM_VIP_SVIP) ? "SVIP" : (gate == NCM_VIP_CVIP) ? "CVIP" : "";
|
||
int locked = (account_vip() < gate);
|
||
if (gate_s[0])
|
||
snprintf(rows[0].sub, sizeof rows[0].sub, "当前: %s (%s) · 需 %s%s",
|
||
ncm_level_name(g_st.level), ncm_level_spec(g_st.level), gate_s,
|
||
locked ? ",将降级" : "");
|
||
else
|
||
snprintf(rows[0].sub, sizeof rows[0].sub, "当前: %s (%s)",
|
||
ncm_level_name(g_st.level), ncm_level_spec(g_st.level));
|
||
}
|
||
/* §22:歌词页背景模式(default=封面模糊置底 / power_save=封面+黑叠加层) */
|
||
snprintf(rows[1].sub, sizeof rows[1].sub, "当前: %s",
|
||
g_st.player_bg_mode == NCM_BG_POWER_SAVE ? "省电模式" : "默认(模糊置底)");
|
||
snprintf(rows[2].sub, sizeof rows[2].sub, "当前: %d GB · 上限 100", g_st.cache_limit_gb);
|
||
snprintf(rows[3].sub, sizeof rows[3].sub, "占用: %s", usetxt);
|
||
snprintf(rows[4].sub, sizeof rows[4].sub, "%s", g_st.nickname[0] ? g_st.nickname : "退出");
|
||
snprintf(rows[5].sub, sizeof rows[5].sub, "%s", "v0.1 · MeowOS");
|
||
|
||
for (int i = 0; i < SETTINGS_ROWS; i++) {
|
||
int yy = y + i * (g_row_h + 8);
|
||
ncm_fill_round(x, yy, w, g_row_h, 12, th->surfaceContainer);
|
||
uint32_t tcol = rows[i].red ? th->error : th->onSurface;
|
||
uint32_t scol = rows[i].red ? th->error : th->onSurfaceVariant;
|
||
meow_ui_text(x + APP_PAD, yy + 12, rows[i].title, 20, tcol);
|
||
meow_ui_text(x + APP_PAD, yy + 34, rows[i].sub, 20, scol);
|
||
meow_ui_text_right(x + w - APP_PAD, yy + 18, ">", 28, rows[i].red ? th->error : th->onSurfaceVariant);
|
||
}
|
||
|
||
/* 品牌红页脚小字。 */
|
||
int ny = y + SETTINGS_ROWS * (g_row_h + 8) + 4;
|
||
meow_ui_text(x, ny, "无损/Hi-Res 需 CVIP ・ 臻音/母带需 SVIP,不支持时自动降级", 20, th->onSurfaceVariant);
|
||
meow_ui_text(x, ny + 24, "网易云音乐 · MeowOS 移植版", 20, pa->red);
|
||
}
|
||
|
||
static int row_at(int x, int y)
|
||
{
|
||
(void)x;
|
||
int r = r_cont.y + 16;
|
||
for (int i = 0; i < SETTINGS_ROWS; i++) {
|
||
int yy = r + i * (g_row_h + 8);
|
||
if (y >= yy && y <= yy + g_row_h) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/* 「自定义」缓存上限:INPUT 弹窗 + NUM IME(数字键盘输入 1~100,越界钳制)。
|
||
* 回车/ESC 关闭后 d->ime.buf 已同步(dialog INPUT 关闭时回写 ime_orig),读回即可。 */
|
||
static int input_cache_limit_gb(void)
|
||
{
|
||
char buf[8];
|
||
snprintf(buf, sizeof buf, "%d", g_st.cache_limit_gb);
|
||
meow_ime_ctx ime;
|
||
memset(&ime, 0, sizeof ime);
|
||
ime.mode = MEOW_IME_NUM;
|
||
ime.maxlen = sizeof buf;
|
||
ncm_strlcpy(ime.buf, buf, sizeof ime.buf);
|
||
|
||
meow_dialog *d = meow_dialog_create(MEOW_DLG_INPUT, "缓存上限 (GB, 1~100)");
|
||
meow_dialog_set_ime(d, &ime);
|
||
meow_dialog_show(d);
|
||
meow_dialog_free(d);
|
||
|
||
int gb = atoi(ime.buf);
|
||
if (gb < 1) gb = 1;
|
||
if (gb > 100) gb = 100;
|
||
return gb;
|
||
}
|
||
|
||
int settings_is_active(void) { return g_sc.active; }
|
||
|
||
int settings_on_tap(int x, int y)
|
||
{
|
||
if (g_tb) {
|
||
int ah = meow_topappbar_hit(g_tb, x, y);
|
||
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return 1; }
|
||
if (ah >= 0) { return 1; }
|
||
}
|
||
if (x < APP_PAD || x > r_cont.w - APP_PAD) return 0;
|
||
int row = row_at(x, y);
|
||
if (row < 0) return 0;
|
||
|
||
if (row == 0) { /* 音质:八档(§20;超权限档可选不硬拒,播放时钳顶+逐级降级) */
|
||
meow_sheet *s = meow_sheet_create("选择音质", 0);
|
||
int vip = account_vip();
|
||
for (int i = 0; i < 8; i++) {
|
||
int lv = g_ui_levels[i];
|
||
int gate = ncm_api_level_gate(lv);
|
||
char lbl[96];
|
||
/* 每档标注 规格+会员门槛;未解锁档标注「需 CVIP/SVIP」(sheet 无置灰能力,
|
||
* 以文字标注代替——允许选定,实际播放逐级降级到可用档) */
|
||
const char *gate_s = (gate == NCM_VIP_SVIP) ? "SVIP" : (gate == NCM_VIP_CVIP) ? "CVIP" : NULL;
|
||
if (gate_s)
|
||
snprintf(lbl, sizeof lbl, "%s (%s) · %s%s", ncm_level_name(lv),
|
||
ncm_level_spec(lv), gate_s, (vip < gate) ? "·未解锁" : "");
|
||
else
|
||
snprintf(lbl, sizeof lbl, "%s (%s)", ncm_level_name(lv), ncm_level_spec(lv));
|
||
meow_sheet_add_item(s, lbl);
|
||
}
|
||
int op = meow_sheet_open(s);
|
||
meow_sheet_free(s);
|
||
if (op >= 0 && op < 8) {
|
||
g_st.level = g_ui_levels[op];
|
||
player_manager_set_quality(g_st.level, g_st.cache_limit_gb);
|
||
appstate_save_settings();
|
||
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
||
}
|
||
return 1;
|
||
}
|
||
if (row == 1) { /* §22 歌词页背景:default(封面模糊置底)/ power_save(省电) */
|
||
meow_sheet *s = meow_sheet_create("歌词页背景", 0);
|
||
meow_sheet_add_item(s, "默认 (模糊置底)");
|
||
meow_sheet_add_item(s, "省电模式 (黑叠加层)");
|
||
int op = meow_sheet_open(s);
|
||
meow_sheet_free(s);
|
||
if (op == 0 || op == 1) {
|
||
g_st.player_bg_mode = (op == 1) ? NCM_BG_POWER_SAVE : NCM_BG_DEFAULT;
|
||
appstate_save_settings();
|
||
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
||
}
|
||
return 1;
|
||
}
|
||
if (row == 2) { /* 缓存上限:预设 + 自定义(数字输入 1~100) */
|
||
static const int gbs[] = { 1, 2, 5, 10, 20, 30, 50, 100 };
|
||
static const char *labels[] = { "1 GB", "2 GB", "5 GB", "10 GB",
|
||
"20 GB", "30 GB", "50 GB", "100 GB" };
|
||
meow_sheet *s = meow_sheet_create("缓存上限", 0);
|
||
for (int i = 0; i < 8; i++) meow_sheet_add_item(s, labels[i]);
|
||
meow_sheet_add_item(s, "自定义...");
|
||
int op = meow_sheet_open(s);
|
||
meow_sheet_free(s);
|
||
if (op == 8) g_st.cache_limit_gb = input_cache_limit_gb();
|
||
else if (op >= 0 && op < 8) g_st.cache_limit_gb = gbs[op];
|
||
if (op >= 0) {
|
||
player_manager_set_quality(g_st.level, g_st.cache_limit_gb);
|
||
appstate_save_settings(); /* 内含手动调低上限后的 LRU 淘汰 */
|
||
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
||
}
|
||
return 1;
|
||
}
|
||
if (row == 3) { /* 清理缓存 */
|
||
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "清理缓存?");
|
||
char msg[160];
|
||
char usetxt[24];
|
||
fmt_bytes(g_cache_used, usetxt, sizeof usetxt);
|
||
snprintf(msg, sizeof msg, "将删除全部缓存 (%s)。\n已播放的歌曲需重新下载。", usetxt);
|
||
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) {
|
||
ncm_cache_clear(&g_st.ctx.cache);
|
||
refresh_cache_used();
|
||
ncm_snack("缓存已清理");
|
||
meow_ui_invalidate(r_cont.x, r_cont.y, r_cont.w, r_cont.h);
|
||
}
|
||
return 1;
|
||
}
|
||
if (row == 4) { /* 退出登录 */
|
||
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "退出登录?");
|
||
const char *msgs[1];
|
||
msgs[0] = "将停止播放并清除登录态,\n下次使用需重新扫码。";
|
||
meow_dialog_set_items(d, msgs, 1, -1);
|
||
int yes = meow_dialog_confirm(d);
|
||
meow_dialog_free(d);
|
||
if (yes) {
|
||
appstate_logout();
|
||
meow_view_page_replace(ACT_LOGIN);
|
||
}
|
||
return 1;
|
||
}
|
||
if (row == 5) { /* 关于 */
|
||
ncm_snack("网易云音乐 v0.1 · MeowOS 移植版");
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void settings_swipe(int dir, int dist) { (void)dir; (void)dist; }
|
||
void settings_touch_down(int x, int y) { (void)x; (void)y; }
|
||
void settings_touch_move(int x, int y) { (void)x; (void)y; }
|
||
|
||
void settings_open(void)
|
||
{
|
||
g_st.playing_song = -1;
|
||
meow_view_page_push(ACT_SETTINGS);
|
||
}
|
||
|
||
void settings_reload(void)
|
||
{
|
||
refresh_cache_used();
|
||
const meow_theme *th = meow_theme_get();
|
||
meow_ui_set_page_bg(th->bg);
|
||
meow_ui_paint_full();
|
||
}
|
||
|
||
static void st_page_create(meow_view *v, void *ctx)
|
||
{
|
||
(void)v;
|
||
((settings_ctrl *)ctx)->view = v;
|
||
r_cont = ncm_content_rect();
|
||
meow_ui_activity_push(ACT_SETTINGS); /* owner=ACT_SETTINGS */
|
||
g_tb = ncm_topbar_create("设置", 1, NULL);
|
||
meow_ui_activity_pop();
|
||
g_row_h = 60;
|
||
g_custom_id = meow_draw_custom_register(1, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
|
||
settings_draw, NULL);
|
||
}
|
||
|
||
static void st_page_enter(meow_view *v, void *ctx)
|
||
{
|
||
(void)v;
|
||
((settings_ctrl *)ctx)->active = 1;
|
||
settings_reload();
|
||
}
|
||
|
||
static void st_page_leave(meow_view *v, void *ctx)
|
||
{
|
||
(void)v;
|
||
((settings_ctrl *)ctx)->active = 0;
|
||
}
|
||
|
||
static void st_page_destroy(meow_view *v, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
if (g_custom_id) { meow_draw_unregister(g_custom_id); g_custom_id = 0; }
|
||
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
|
||
}
|
||
|
||
meow_view *settings_view_create(void)
|
||
{
|
||
static const meow_view_hooks h = {
|
||
.on_create = st_page_create,
|
||
.on_enter = st_page_enter,
|
||
.on_leave = st_page_leave,
|
||
.on_destroy = st_page_destroy,
|
||
};
|
||
return meow_view_create(ACT_SETTINGS, MEOW_VIEW_ACTIVITY, &h, &g_sc);
|
||
}
|