Files
MeowOS-For-R3P2/patch_v2/reader_app/app/settings.c
T
AskaEth 7c7d891570 feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。

- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
  逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
  偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
  rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
  (P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
  各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
  保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
  main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
  env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
  永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
  wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
  IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
  权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步

范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
2026-11-04 20:02:28 +08:00

281 lines
11 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.
/* app/settings.c — 全局设置页(C,书架顶栏「设置」入口 → 独立页 ACT_SETTINGS)。
*
* 配置项(MVP,见 READER_UI_DESIGN §4.3):默认字号/默认行距/默认背景(黑白米黄)/主题(亮暗跟随
* 全局)/书架布局(list-grid)/页头条/页底条。全部存 App 私有 reader.conf(rdconf),不进系统
* settings.conf。UI:meow_topappbar(返回+标题) + meow_listview 行(每行 label + 当前值尾部箭头,
* 点行开 meow_dialog(SELECT) 单选;值即改即存)。
*/
#include "../reader_internal.h"
#include "progress.h"
#include "reader_conf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SET_X 0
#define SET_W 480
#define SET_APPBAR_H 56
static meow_widget *g_appbar;
static meow_widget *g_list;
static int g_active;
/* 行项枚举(顺序 = 列表顺序)。ITEM_BASE = 固定的全局项数;重解析本书章节仅在
* 已打开一本「支持章节解析」(TXT+seg 模式)的书时追加(见 settings_on_enter)。 */
enum {
ITEM_FS = 0, ITEM_LS, ITEM_BG, ITEM_THEME, ITEM_SHELF, ITEM_HDR, ITEM_FTR,
ITEM_REPARSE, ITEM_COUNT
};
#define ITEM_BASE ITEM_REPARSE /* 7:不含重解析的固定项数 */
static int g_nitems = ITEM_BASE;
static const char *const g_labels[ITEM_COUNT] = {
"默认字号", "默认行距", "默认背景", "主题", "书架布局", "页头条", "页底条", "重解析本书章节"
};
static void settings_redraw(void);
static const char *bg_label(const rdconf *c)
{
if (!c) return "黑";
if (!strcmp(c->bg, "white")) return "白";
if (!strcmp(c->bg, "sepia")) return "米黄";
if (!strcmp(c->bg, "auto")) return "自动";
return "黑";
}
static const char *theme_label(const rdconf *c)
{
if (!c) return "跟随系统";
if (!strcmp(c->theme, "light")) return "日";
if (!strcmp(c->theme, "dark")) return "夜";
return "跟随系统";
}
static const char *shelf_label(const rdconf *c)
{
if (!c) return "列表";
return (!strcmp(c->shelf, "grid")) ? "宫格" : "列表";
}
/* 当前行值(subtitle)。 */
static void item_value(int item, char *out, int outsz)
{
const rdconf *c = rdconf_get();
if (!c) { snprintf(out, (size_t)outsz, "-"); return; }
switch (item) {
case ITEM_FS: snprintf(out, (size_t)outsz, "%d", c->fs); break;
case ITEM_LS: snprintf(out, (size_t)outsz, "%d", c->ls); break;
case ITEM_BG: snprintf(out, (size_t)outsz, "%s", bg_label(c)); break;
case ITEM_THEME: snprintf(out, (size_t)outsz, "%s", theme_label(c)); break;
case ITEM_SHELF: snprintf(out, (size_t)outsz, "%s", shelf_label(c)); break;
case ITEM_HDR: snprintf(out, (size_t)outsz, "%s", c->header ? "显示" : "隐藏"); break;
case ITEM_FTR: snprintf(out, (size_t)outsz, "%s", c->footer ? "显示" : "隐藏"); break;
case ITEM_REPARSE: snprintf(out, (size_t)outsz, "%s", "点按重新扫描章节"); break;
default: snprintf(out, (size_t)outsz, "-"); break;
}
}
/* ---- listview 行源(src2:每次绘制读最新 rdconf 值) ---- */
static int settings_src(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= g_nitems || idx >= ITEM_COUNT) return 0;
static char valbuf[48];
item_value(idx, valbuf, sizeof valbuf);
out->icon_kind = MEOW_LIST_ICON_NONE;
out->title = g_labels[idx];
out->subtitle = valbuf;
out->right = MEOW_LIST_RIGHT_ARROW;
out->on = 0;
out->enabled = (idx == ITEM_REPARSE) ? reader_has_chapter_book() : 1;
return 1;
}
/* 由当前值求 SELECT 对话框的选中下标。 */
static int idx_for(int item, const char *const *opts, int n, const rdconf *c)
{
if (item == ITEM_FS) { for (int i = 0; i < n; i++) if (atoi(opts[i]) == c->fs) return i; return 0; }
if (item == ITEM_LS) { for (int i = 0; i < n; i++) if (atoi(opts[i]) == c->ls) return i; return 0; }
if (item == ITEM_BG) {
for (int i = 0; i < n; i++) if (!strcmp(opts[i], bg_label(c))) return i;
return 0;
}
if (item == ITEM_THEME) {
for (int i = 0; i < n; i++) if (!strcmp(opts[i], theme_label(c))) return i;
return 0;
}
if (item == ITEM_SHELF) {
for (int i = 0; i < n; i++) if (!strcmp(opts[i], shelf_label(c))) return i;
return 0;
}
if (item == ITEM_HDR) return c->header ? 0 : 1;
if (item == ITEM_FTR) return c->footer ? 0 : 1;
return 0;
}
/* 选完整数数组项(字号/行距);写回 rdconf。 */
static void apply_fs_ls(int item, const char *sel)
{
rdconf *c = rdconf_get();
if (!c) return;
int v = atoi(sel);
if (item == ITEM_FS) { if (v >= 14 && v <= 40) c->fs = v; }
else if (item == ITEM_LS) { if (v >= 100 && v <= 200) c->ls = v; }
rdconf_persist();
}
static void apply_choice(int item, int sel)
{
rdconf *c = rdconf_get();
if (!c) return;
switch (item) {
case ITEM_BG:
if (sel < 0) sel = 0;
if (sel > 3) sel = 3;
if (sel == 0) strcpy(c->bg, "black");
else if (sel == 1) strcpy(c->bg, "white");
else if (sel == 2) strcpy(c->bg, "sepia");
else strcpy(c->bg, "auto");
break;
case ITEM_THEME:
if (sel < 0) sel = 0;
if (sel > 2) sel = 2;
if (sel == 0) strcpy(c->theme, "auto");
else if (sel == 1) strcpy(c->theme, "light");
else strcpy(c->theme, "dark");
break;
case ITEM_SHELF:
shelf_set_view(sel ? 1 : 0); /* 写 rdconf + 重建书架 */
return;
case ITEM_HDR: c->header = sel ? 0 : 1; break; /* 0 显示 1 隐藏 */
case ITEM_FTR: c->footer = sel ? 0 : 1; break;
default: return;
}
rdconf_persist();
}
/* 打开一个 SELECT 对话框并应用。 */
static void settings_open_choice(int item)
{
if (item == ITEM_REPARSE) return; /* 重解析走 settings_reparse(非 SELECT) */
const rdconf *c = rdconf_get();
if (!c) return;
const char *title = g_labels[item];
meow_dialog *d = meow_dialog_create(MEOW_DLG_SELECT, title);
if (!d) return;
const char *opts[16]; int n = 0; int cur = 0;
static char numopts[16][8];
if (item == ITEM_FS) {
for (int v = 14; v <= 40; v += 2) { snprintf(numopts[n], sizeof numopts[n], "%d", v); opts[n] = numopts[n]; n++; }
} else if (item == ITEM_LS) {
for (int v = 100; v <= 200; v += 10) { snprintf(numopts[n], sizeof numopts[n], "%d", v); opts[n] = numopts[n]; n++; }
} else if (item == ITEM_BG) {
static const char *b[4] = { "黑", "白", "米黄", "自动" };
for (int i = 0; i < 4; i++) opts[n++] = b[i];
} else if (item == ITEM_THEME) {
static const char *t[3] = { "跟随系统", "日", "夜" };
for (int i = 0; i < 3; i++) opts[n++] = t[i];
} else if (item == ITEM_SHELF) {
static const char *s[2] = { "列表", "宫格" };
for (int i = 0; i < 2; i++) opts[n++] = s[i];
} else if (item == ITEM_HDR || item == ITEM_FTR) {
static const char *h[2] = { "显示", "隐藏" };
for (int i = 0; i < 2; i++) opts[n++] = h[i];
}
cur = idx_for(item, opts, n, c);
meow_dialog_set_items(d, opts, n, cur);
meow_dialog_show(d);
int res = meow_dialog_result(d);
meow_dialog_free(d);
if (res < 0 || res >= n) return; /* 取消 */
if (item == ITEM_FS || item == ITEM_LS) apply_fs_ls(item, opts[res]);
else apply_choice(item, res);
settings_redraw();
}
static void settings_redraw(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
if (g_appbar) meow_widget_draw(g_appbar);
if (g_list) meow_widget_draw(g_list);
meow_ui_flush();
}
/* R3.4 «重解析本书章节»:确认 → 清缓存+重扫 → 结果对话框(成功/失败/进行中=确认弹窗)。 */
static void settings_reparse(void)
{
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "重解析本书章节?\n将清除章节缓存并重新扫描全书。");
if (!d) return;
meow_dialog_show(d);
int ok = meow_dialog_result(d);
meow_dialog_free(d);
if (ok != 1) { settings_redraw(); return; } /* 取消 */
int rc = reader_reparse_chapters(); /* 阻塞:重扫全书段 */
char msg[120];
if (rc >= 0) snprintf(msg, sizeof msg, "重解析完成:共识别 %d 章(已保存缓存)", rc);
else if (rc == -2) snprintf(msg, sizeof msg, "重解析失败:扫描出错,已回退为无章节(未保存)");
else snprintf(msg, sizeof msg, "该书不支持章节解析");
meow_dialog *r = meow_dialog_create(MEOW_DLG_INFO, msg);
if (r) { meow_dialog_show(r); meow_dialog_result(r); meow_dialog_free(r); }
settings_redraw();
}
/* view 生命周期 */
static void settings_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
static void settings_on_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 1;
int sbh = meow_ui_statusbar_height();
if (g_appbar) { meow_widget_free(g_appbar); g_appbar = NULL; }
g_appbar = meow_topappbar_create(0, sbh, 480, SET_APPBAR_H, "阅读设置");
if (g_appbar) meow_topappbar_set_back(g_appbar, 1);
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
int ly = sbh + SET_APPBAR_H + 8;
/* 仅当打开的书「支持章节解析」(TXT+seg)才显示「重解析本书章节」项 */
g_nitems = ITEM_BASE + (reader_has_chapter_book() ? 1 : 0);
g_list = meow_listview_create(SET_X, ly, SET_W, 720 - ly, g_nitems,
settings_src, NULL, MEOW_LIST_SEL_SINGLE);
if (g_list) {
meow_listview_set_container(g_list, 1);
meow_listview_set_item_tier(g_list, 2);
}
settings_redraw();
}
static void settings_on_leave(meow_view *v, void *ctx) { (void)v; (void)ctx; g_active = 0; }
static void settings_on_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
if (g_appbar) { meow_widget_free(g_appbar); g_appbar = NULL; }
}
meow_view *settings_view_create(void)
{
static meow_view_hooks hooks = { settings_on_create, settings_on_enter, settings_on_leave,
settings_on_destroy, NULL, NULL, NULL };
return meow_view_create(ACT_SETTINGS, MEOW_VIEW_ACTIVITY, &hooks, NULL);
}
int settings_is_active(void) { return g_active; }
void settings_reload(void) { if (g_active) settings_redraw(); }
int settings_on_tap(int x, int y)
{
/* 顶栏返回 */
if (g_appbar) {
int bi = meow_topappbar_hit(g_appbar, x, y);
if (bi == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return 1; }
if (bi >= 0) return 1;
}
if (g_list) {
int idx = meow_listview_tap_at(g_list, x, y);
if (idx >= 0 && idx < g_nitems && idx < ITEM_COUNT) {
if (idx == ITEM_REPARSE) { settings_reparse(); return 1; }
settings_open_choice(idx);
return 1;
}
if (idx >= 0) { settings_redraw(); return 1; }
}
return 0;
}