脏工作区现场收编: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 与容器一致)。
281 lines
11 KiB
C
281 lines
11 KiB
C
/* 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;
|
||
}
|