Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/ui/settings.c
T
AskaEth 3397a702aa fix(ncm §30): 真机四连症状——JSON unescape + 封面流式 + 歌单豆腐/分隔符 tofu + 首屏诊断日志
症状/根因/修法(0 真实 NCM 业务请求;宿主+qemu 全绿):
- ③ LRC 挤一行/时间戳未解析:ncm_json_get_str_at 不做 JSON 转义还原,
  lrc.lyric 的 \n 转义原样进串 → lrc_parse 无真实换行整段一行。修:ncm_json.c 加
  json_unescape(RFC8259 全转义 + \uXXXX/代理对),get_str_at 返回前还原。
- ⑤ 封面全 failed→give up:cover.c 静态 g_buf 288KB < 封面 1.34MB → NCM_ERR_FULL。
  设备 RAM 仅 56MB。修:cover.c 改流式写文件(ncm_http_download cb→ncm_cache_write +
  meow_img_load_file 临时文件),NCM_COVER_MAX 8MB 改下载上限(非缓冲,bss 未涨)。
- ① 162 首歌单所有条目「三个豆腐」+「加载中」:占位符 ···(U+00B7)在 default 字体缺
  该字形 → 3 豆腐(font.idx glyph 扫实证 U+00B7 MISSING)。修:占位 ···→...、全 App
  · 分隔符→/;playlist_worker 首屏打 first-screen 诊断日志。
- ② 返回首页歌单列表 1 秒方框:字体缺 U+00B7/特殊字形(◢◤/Kannada)tofu;瞬态疑 glyph
  atlas 冷缓存(SDK 内部未深挖)。修:·→/。
验证:test_ncm_core(+test_json_unescape, host+qemu) / listvirt44 / dispatch / box42 /
scroll17 / anim32 / widgets / overlay_suppress / sdk_input_keys / theme / vol / ime_fix /
pinyin / notif_gesture / notif_open / sdk_fixes / sb_collapse / ui_audio_host / notify_ipc /
notify_integration + playlist_race_sim / window_sim / search_sim / cache_evict_sim(1-9) /
player_flow_sim(P1-12) / stream_sim(16+2resume) 全 PASS;mipsel -Wall -Wextra 0 warning。
新 md5:ncm d91cd906、ncm_host b731654a、test_ncm_core_host 7a38194c。

(症状④ VOL_DOWN×3 杀 App 的 launcher 源修复 app_launcher.c 门控 !a->framework 已随
 并发 commit 54a6e82 存入 HEAD;launcher 二进制 03dfcf76 留主会话/固件打包。)
注:build_ncm_host.sh 补 SDK 292/295 的 anim/box/scroll 源(宿主链接)。
2026-09-02 02:25:37 +08:00

324 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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);
}