Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/ui/settings.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

329 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;每档标注规格+会员门槛(畅听会员/黑胶 VIP/超级会员);超权限档允许选定,播放时钳顶+逐级降级,
* 见 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)。
* 门槛串按用户语义对齐:1=畅听会员 / 2=黑胶 VIP / 3=超级会员(门槛恒 ≤2,SUPER 兜底)。 */
int gate = ncm_api_level_gate(g_st.level);
const char *gate_s = (gate == NCM_VIP_SVIP) ? "黑胶 VIP"
: (gate == NCM_VIP_CVIP) ? "畅听会员"
: (gate == NCM_VIP_SUPER) ? "超级会员" : "";
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 需畅听会员 ・ 臻音/母带需黑胶 VIP,不支持时自动降级", 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];
/* 每档标注 规格+会员门槛;未解锁档标注「需 畅听会员/黑胶 VIP」(sheet 无置灰能力,
* 以文字标注代替——允许选定,实际播放逐级降级到可用档) */
const char *gate_s = (gate == NCM_VIP_SVIP) ? "黑胶 VIP"
: (gate == NCM_VIP_CVIP) ? "畅听会员"
: (gate == NCM_VIP_SUPER) ? "超级会员" : 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);
}