- reader: app.c/shelf.c/reader.c 全套UI; 新增 reader_conf(reader.conf)/settings(App设置页)/reader_ipc(音量翻页IPC响应); app.json加volume_page - launcher: keys.c meow_volpage_*门 + util.c app_id→fd映射(HELLO记fd/断开清) + app_launcher.c 前台三处接线(进入sink/按键路径volpage_key/离开clear) → 前台声明volume_page的App音量键→MSG_KEY_PAGEUP/PAGEDOWN翻页(非阅读器前台音量照旧) - sdk: meow_select.c 底部面板28 + meow_ui_dialog.c 对话框按钮胶囊(h/2) + test_meow_ui_select.c修采样 - meowpanel: /api/net/policy·/api/power/policy 加防自锁+policy默认关两条护栏(panel.policy_enabled默认0) - 文档: MEOW_APP_SDK.md §12.6 + meowpanel README端点表 0warning;宿主测试全过;二进制留批量批次未入库
251 lines
9.1 KiB
C
251 lines
9.1 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;
|
||
|
||
/* 行项枚举(顺序 = 列表顺序) */
|
||
enum { ITEM_FS = 0, ITEM_LS, ITEM_BG, ITEM_THEME, ITEM_SHELF, ITEM_HDR, ITEM_FTR, ITEM_COUNT };
|
||
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;
|
||
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 >= ITEM_COUNT) return 0;
|
||
static char valbuf[16];
|
||
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 = 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)
|
||
{
|
||
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();
|
||
}
|
||
|
||
/* 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;
|
||
g_list = meow_listview_create(SET_X, ly, SET_W, 720 - ly, ITEM_COUNT,
|
||
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 < ITEM_COUNT) {
|
||
settings_open_choice(idx);
|
||
return 1;
|
||
}
|
||
if (idx >= 0) { settings_redraw(); return 1; }
|
||
}
|
||
return 0;
|
||
}
|