logic/pin(纯 C):加盐迭代哈希(FNV-1a ×1000 轮 + 每次设置新随机盐,16 位 hex)经
meow_config_* 持久化 pin.enabled/pin.hash/pin.salt;提供 set/verify/change/disable 与
4-8 位纯数字校验;enabled=1 但盐/哈希残缺时 fail-closed;落盘半途失败会还原旧盐,不留
「新盐+旧哈希」的双失效状态。
ui/settings(ACT_SETTINGS):MD3 分组表单(meow_listview 容器圆角卡 + 段头标签)——
阅读方向 RTL/LTR 走 mr_reader_set_direction;启动 PIN 开关与修改(开启/修改均两遍
MEOW_IME_NUM 确认);版本与主题状态行(跟随系统 + 当前明暗,随 app.c 主题 listener
整帧重绘)。控件一律在 on_enter 建(栈顶=ACT_SETTINGS),避免 §6.1 owner 绑到
ACT_LIBRARY 的跨页命中泄漏。
ui/lock(ACT_LOCK):lock_view_should_show() 读 logic/pin;启用时启动先入锁屏,验证前
不绘制任何书库内容;「输入 PIN」按钮弹系统数字键盘(MEOW_IME_NUM),错 PIN 就地提示
重试(无惩罚性锁定),正确则 page_pop 回书库;on_event 消费 SWIPE——左缘返回不能绕过
PIN 或退出 App(meow_view_dispatch_event 先于 app.c 的 on_swipe)。
logic/reader(评审 P1 修复,最小持久化):mr_reader_set_direction 成功切换后 best-effort
写 meow_config("reader.dir","rtl"/"ltr"),写失败只打 WARN 不阻断本次切换;
mr_reader_get_direction 首次调用惰性读一次配置恢复(无配置/非法值 → 默认 RTL),之后
纯内存快路径。宿主测试经 -DMR_READER_TEST_HOOKS 的 mr_reader_test_reset() 模拟重启。
测试/构建:test_pin(85 项,PIN 全分支,含单键半途落盘失败);test_settings_reader_persist
(19 项:默认/落盘/重启恢复/坏值/非法值/只读一次/写失败只告警);可选宿主集成
test_settings_lock_host(真实 SDK view/activity/widget + IME 注入,端到端验证错/对 PIN
解锁与设置页两遍开启;SDK 无源码时 SKIP);run_tests.sh 保持 46/46 并纳入上述测试
(reader.c 的配置/日志依赖由 test/settings_reader_stub.c 存根),build_tuku.sh 加入
logic/pin.c。交叉构建静态 MIPS ELF(zig-mipsel)零警告。
安全口径:PIN 防随手翻看,不是安全边界——TF 卡可拔出直读、配置可改;存储不使用加密库,
锁屏与设置页文案均如实标注该局限。
450 lines
17 KiB
C
450 lines
17 KiB
C
/* ui/settings/settings.c — 设置页(ACT_SETTINGS):MD3 分组表单。
|
||
*
|
||
* 内容(用户原话「允许用户在APP设置中设置APP启动 PIN」+「同步系统主题状态」):
|
||
* 阅读:阅读方向 RTL/LTR —— 直接读写 M3 的 mr_reader_get/set_direction(不另立状态);
|
||
* 安全:启动 PIN 开关(开启需两遍 MEOW_IME_NUM 输入确认)、修改 PIN;
|
||
* 关于:版本号、主题状态(跟随系统 + 当前明暗,随系统实时同步)。
|
||
*
|
||
* 观感/主题:SDK 的 meow_listview 容器模式(MD3 surfaceContainer 圆角卡 + 行间空隙)
|
||
* + 段头标签;开关/箭头由 SDK renderer 用主题语义色绘制;所有颜色取 meow_theme_get(),
|
||
* 不缓存颜色——app.c 的主题 listener 整帧 invalidate,本页 on_draw/src 回调下一帧即用新色。
|
||
*
|
||
* 控件归属(§6.1,M3 桩注释①):本页非根页,控件**必须**在 on_enter 里建(此时
|
||
* activity 栈顶=ACT_SETTINGS),否则 owner 会绑到 ACT_LIBRARY 造成跨页命中。
|
||
*
|
||
* 返回:左缘返回由 app.c 的 on_swipe 统一 page_pop(本页 on_event 不消费 SWIPE)。
|
||
* PIN 输入:SDK 系统数字键盘(MEOW_IME_NUM),不自绘键盘;文本经 on_change 实时收集,
|
||
* 「非空 + 关闭键盘 = 校验、空 = 取消」(SDK 阻塞 IME 的返回值不能当确认判据,见
|
||
* settings_read_pin 注释);键盘打开期间步骤提示画在键盘面板上方(置顶 widget)。
|
||
*/
|
||
#include "tuku_internal.h"
|
||
#include "logic/pin.h"
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#define ST_TOP_H 56
|
||
#define ST_SEC_H 34
|
||
#define ST_ROW_H 64
|
||
#define ST_GAP 12
|
||
|
||
/* meow_view on_event 的事件码:公共伞头未导出(定义在 sdk/meow_ui_internal.h:
|
||
* MEOW_EV_TAP=2 / MEOW_EV_SWIPE=3,值稳定)。此处按契约镜像,避免 App include SDK 内部头。
|
||
* (已在交付报告里作为 SDK 缺口提出:建议公共头导出事件枚举。) */
|
||
#define MR_VEV_TAP 2
|
||
|
||
/* PIN 行下标(g_lv_pin) */
|
||
#define ST_PIN_ROW_SWITCH 0
|
||
#define ST_PIN_ROW_CHANGE 1
|
||
|
||
static mr_page_ctrl g_ctrl;
|
||
|
||
static meow_widget *g_tb;
|
||
static meow_widget *g_lv_dir; /* 阅读:1 行 */
|
||
static meow_widget *g_lv_pin; /* 安全:2 行 */
|
||
static meow_widget *g_lv_about; /* 关于:2 行 */
|
||
static meow_widget *g_overlay; /* 置顶提示条(仅拨号键盘期间显示步骤文案) */
|
||
|
||
static int g_yl_dir, g_yl_pin, g_yl_about; /* 段头标签 y */
|
||
static int g_note_y;
|
||
static int g_ov_y, g_ov_h; /* 提示条几何(App 自记;widget 句柄不透明) */
|
||
|
||
static char g_note[96];
|
||
static int g_note_err;
|
||
static int g_pin_on; /* 缓存的 PIN 启用态(避免每帧读 3 个小配置文件的 TF IO) */
|
||
static char g_theme_line[64];
|
||
static char g_prompt[64]; /* 非空=正在输入 PIN(键盘打开期间顶部提示条) */
|
||
static char g_ime_pin[MR_PIN_MAX_LEN + 12];
|
||
|
||
/* ============================================================ */
|
||
/* 文案/状态 */
|
||
/* ============================================================ */
|
||
static int theme_is_dark(const meow_theme *th)
|
||
{
|
||
int r = (int)((th->surface >> 16) & 0xFF);
|
||
int g = (int)((th->surface >> 8) & 0xFF);
|
||
int b = (int)(th->surface & 0xFF);
|
||
return (r * 299 + g * 587 + b * 114) / 1000 < 128;
|
||
}
|
||
|
||
static void theme_line_update(void)
|
||
{
|
||
const meow_theme *th = meow_theme_get();
|
||
meow_theme_mode m = meow_theme_get_mode();
|
||
const char *mode = (m == MEOW_THEME_AUTO) ? "跟随系统"
|
||
: (m == MEOW_THEME_DARK) ? "固定深色" : "固定浅色";
|
||
snprintf(g_theme_line, sizeof g_theme_line, "%s · 当前%s", mode,
|
||
theme_is_dark(th) ? "深色" : "浅色");
|
||
}
|
||
|
||
static void settings_note(const char *msg, int is_err)
|
||
{
|
||
snprintf(g_note, sizeof g_note, "%s", msg ? msg : "");
|
||
g_note_err = is_err;
|
||
}
|
||
|
||
static void settings_repaint(void)
|
||
{
|
||
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* PIN 输入(系统数字键盘) */
|
||
/* ============================================================ */
|
||
static void ime_pin_change(const char *text, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
snprintf(g_ime_pin, sizeof g_ime_pin, "%s", text ? text : "");
|
||
}
|
||
|
||
/* 阻塞弹出 MEOW_IME_NUM 收集 4-8 位数字。返回 1=收到非空文本;0=空(视为取消)。
|
||
* 说明(SDK 现状,如实记录):阻塞 IME 只以「关闭键盘」结束;数字键盘的「确定」只置内部
|
||
* submit 标记而不关闭,随后「取消/点背景」关闭时该标记又被清 0(sdk/meow_ui_ime.c 的
|
||
* K_ESC/背景路径),故 meow_ui_text_input 的返回值不可作为「确认」判据。本页改用
|
||
* on_change 实时收集文本:非空 + 关闭键盘 = 校验;空 = 取消(提示条照此告知用户)。
|
||
* 键盘面板覆盖屏底(y≥420),步骤提示画在其上方,否则打字时看不见当前步骤。 */
|
||
static int settings_read_pin(const char *prompt, char *out, int outsz)
|
||
{
|
||
meow_ime_ctx c;
|
||
memset(&c, 0, sizeof c);
|
||
c.mode = MEOW_IME_NUM;
|
||
c.maxlen = (int)sizeof g_ime_pin;
|
||
c.on_change = ime_pin_change;
|
||
c.ctx = NULL;
|
||
|
||
g_ime_pin[0] = '\0';
|
||
settings_note("", 0);
|
||
snprintf(g_prompt, sizeof g_prompt, "%s", prompt);
|
||
settings_repaint(); /* 先画提示条(键盘区背景快照从当前帧取) */
|
||
(void)meow_ui_text_input(&c);
|
||
g_prompt[0] = '\0';
|
||
|
||
snprintf(out, (size_t)outsz, "%s", g_ime_pin);
|
||
return out[0] != '\0';
|
||
}
|
||
|
||
/* 两遍输入(开启/修改共用):1=一致且合法(写入 out);0=取消/不一致/非法(已置 note)。 */
|
||
static int settings_pin_two_pass(const char *prompt, char *out, int outsz)
|
||
{
|
||
char a[32] = "", b[32] = "";
|
||
|
||
if (!settings_read_pin(prompt, a, (int)sizeof a)) {
|
||
settings_note("已取消", 0);
|
||
return 0;
|
||
}
|
||
if (!mr_pin_valid(a)) {
|
||
settings_note("PIN 需为 4-8 位数字,未保存", 1);
|
||
return 0;
|
||
}
|
||
if (!settings_read_pin("再次输入以确认", b, (int)sizeof b)) {
|
||
settings_note("已取消", 0);
|
||
return 0;
|
||
}
|
||
if (strcmp(a, b) != 0) {
|
||
settings_note("两次输入不一致,未保存", 1);
|
||
return 0;
|
||
}
|
||
snprintf(out, (size_t)outsz, "%s", a);
|
||
return 1;
|
||
}
|
||
|
||
static void settings_toggle_pin(void)
|
||
{
|
||
if (!g_pin_on) {
|
||
char pin[32] = "";
|
||
if (!settings_pin_two_pass("设置启动 PIN(4-8 位数字)", pin, (int)sizeof pin)) return;
|
||
int r = mr_pin_set(pin);
|
||
if (r == MR_PIN_OK) settings_note("启动 PIN 已开启 · 下次启动生效", 0);
|
||
else settings_note("保存失败:数据目录不可写", 1);
|
||
g_pin_on = mr_pin_enabled();
|
||
return;
|
||
}
|
||
|
||
int r = mr_pin_disable();
|
||
if (r == MR_PIN_OK) settings_note("启动 PIN 已关闭", 0);
|
||
else settings_note("关闭失败:配置不可写", 1);
|
||
g_pin_on = mr_pin_enabled();
|
||
}
|
||
|
||
static void settings_change_pin(void)
|
||
{
|
||
if (!g_pin_on) {
|
||
settings_note("请先开启启动 PIN", 0);
|
||
return;
|
||
}
|
||
char pin[32] = "";
|
||
if (!settings_pin_two_pass("设置新 PIN(4-8 位数字)", pin, (int)sizeof pin)) return;
|
||
int r = mr_pin_set(pin); /* 重设即换新盐;旧 PIN 失效 */
|
||
if (r == MR_PIN_OK) settings_note("PIN 已更新 · 下次启动生效", 0);
|
||
else settings_note("保存失败:数据目录不可写", 1);
|
||
g_pin_on = mr_pin_enabled();
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 行数据源(src2:每帧重取 → 开关/副标题随状态即时刷新) */
|
||
/* ============================================================ */
|
||
static int src_dir(int idx, meow_list_item *it, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
if (idx != 0) return 0;
|
||
memset(it, 0, sizeof *it);
|
||
int rtl = mr_reader_get_direction() == MR_DIR_RTL;
|
||
it->title = "阅读方向";
|
||
it->subtitle = rtl ? "右到左 · 日式漫画(RTL)" : "左到右 · 常规漫画(LTR)";
|
||
it->right = MEOW_LIST_RIGHT_SWITCH;
|
||
it->on = rtl;
|
||
it->enabled = 1;
|
||
return 1;
|
||
}
|
||
|
||
static int src_pin(int idx, meow_list_item *it, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
memset(it, 0, sizeof *it);
|
||
int on = g_pin_on;
|
||
if (idx == ST_PIN_ROW_SWITCH) {
|
||
it->title = "启动 PIN";
|
||
it->subtitle = on ? "已启用 · 启动时先输入 PIN"
|
||
: "未设置 · 启动直接进入书库";
|
||
it->right = MEOW_LIST_RIGHT_SWITCH;
|
||
it->on = on;
|
||
it->enabled = 1;
|
||
return 1;
|
||
}
|
||
if (idx == ST_PIN_ROW_CHANGE) {
|
||
it->title = "修改 PIN";
|
||
it->subtitle = on ? "重新设置 4-8 位数字" : "开启后可修改";
|
||
it->right = MEOW_LIST_RIGHT_ARROW;
|
||
it->enabled = on; /* 未启用时变淡且不响应命中 */
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int src_about(int idx, meow_list_item *it, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
memset(it, 0, sizeof *it);
|
||
if (idx == 0) {
|
||
it->title = "版本";
|
||
it->subtitle = "0.1 · os.meow.picbrowser";
|
||
it->enabled = 1;
|
||
return 1;
|
||
}
|
||
if (idx == 1) {
|
||
theme_line_update();
|
||
it->title = "主题";
|
||
it->subtitle = g_theme_line;
|
||
it->enabled = 1;
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 布局 */
|
||
/* ============================================================ */
|
||
/* 置顶提示条(z=MEOW_Z_TOP):键盘面板从屏底覆盖,步骤文案必须画在其上方;
|
||
* 仅 g_prompt 非空时绘制,否则透明不挡顶栏。
|
||
* 注:meow_widget 对 App 不透明(公共头只给句柄),坐标由本文件自己记录。 */
|
||
static void settings_overlay_render(meow_widget *w, const meow_theme *th, void *ctx)
|
||
{
|
||
(void)w; (void)ctx;
|
||
if (!g_prompt[0]) return;
|
||
int wd = meow_ui_width();
|
||
meow_ui_fill_rect(0, g_ov_y, wd, g_ov_h, th->surfaceContainerHighest);
|
||
meow_ui_text_center(wd / 2, g_ov_y + 28, g_prompt, th->font_title, th->onSurface);
|
||
meow_ui_text_center(wd / 2, g_ov_y + 76, "输入后关闭键盘即校验 · 空输入=取消",
|
||
th->font_body, th->onSurfaceVariant);
|
||
meow_ui_fill_rect(0, g_ov_y + g_ov_h - 1, wd, 1, th->outlineVariant);
|
||
}
|
||
|
||
static void settings_build(void)
|
||
{
|
||
/* 控件先以占位矩形创建,统一由 settings_place() 摆位(避免两处布局数学不一致)。 */
|
||
g_tb = meow_topappbar_create(0, 0, 1, 1, "设置");
|
||
if (g_tb) meow_topappbar_set_back(g_tb, 1);
|
||
|
||
g_lv_dir = meow_listview_create(0, 0, 1, 1, 1, src_dir, NULL, MEOW_LIST_SEL_NONE);
|
||
g_lv_pin = meow_listview_create(0, 0, 1, 1, 2, src_pin, NULL, MEOW_LIST_SEL_NONE);
|
||
g_lv_about = meow_listview_create(0, 0, 1, 1, 2, src_about, NULL, MEOW_LIST_SEL_NONE);
|
||
|
||
/* 列表:MD3 容器卡 + 双行(标题/副标题)+ 右尾件(开关/箭头)。
|
||
* 容器模式由 SDK 默认 renderer 绘制(surfaceContainer 圆角卡 + 行间空隙)。 */
|
||
meow_widget *lvs[3] = { g_lv_dir, g_lv_pin, g_lv_about };
|
||
for (int i = 0; i < 3; i++) {
|
||
if (!lvs[i]) continue;
|
||
meow_listview_set_item_h(lvs[i], ST_ROW_H);
|
||
meow_listview_set_item_renderer(lvs[i], 2, 0, 1);
|
||
meow_listview_set_container(lvs[i], 1);
|
||
}
|
||
|
||
g_overlay = meow_widget_custom_create(0, 0, 1, 1,
|
||
settings_overlay_render, NULL, NULL);
|
||
if (g_overlay) meow_widget_set_z(g_overlay, MEOW_Z_TOP);
|
||
}
|
||
|
||
/* 布局参数(窗口可能变化;每次进入重新摆位)。 */
|
||
static void settings_place(meow_view *v)
|
||
{
|
||
int w = meow_ui_width();
|
||
int sb = meow_ui_statusbar_height();
|
||
|
||
v->x = 0; v->y = 0; v->w = w; v->h = meow_ui_height();
|
||
|
||
int lx = 8, lw = w - 16;
|
||
int y = sb + ST_TOP_H + 6;
|
||
|
||
if (g_tb) meow_widget_set_rect(g_tb, 0, sb, w, ST_TOP_H);
|
||
|
||
g_yl_dir = y; y += ST_SEC_H;
|
||
if (g_lv_dir) meow_widget_set_rect(g_lv_dir, lx, y, lw, ST_ROW_H);
|
||
y += ST_ROW_H + ST_GAP;
|
||
|
||
g_yl_pin = y; y += ST_SEC_H;
|
||
if (g_lv_pin) meow_widget_set_rect(g_lv_pin, lx, y, lw, ST_ROW_H * 2);
|
||
y += ST_ROW_H * 2 + ST_GAP;
|
||
|
||
g_yl_about = y; y += ST_SEC_H;
|
||
if (g_lv_about) meow_widget_set_rect(g_lv_about, lx, y, lw, ST_ROW_H * 2);
|
||
y += ST_ROW_H * 2 + 14;
|
||
|
||
g_note_y = y;
|
||
|
||
g_ov_y = sb;
|
||
g_ov_h = 128;
|
||
if (g_overlay) meow_widget_set_rect(g_overlay, 0, g_ov_y, w, g_ov_h);
|
||
}
|
||
|
||
static void settings_section_label(int y, const char *text, const meow_theme *th)
|
||
{
|
||
meow_ui_text(24, y + 6, text, th->font_body, th->primary);
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 绘制 */
|
||
/* ============================================================ */
|
||
static void settings_draw(meow_view *v, const meow_theme *th, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
int w = meow_ui_width();
|
||
|
||
settings_section_label(g_yl_dir, "阅读", th);
|
||
settings_section_label(g_yl_pin, "安全", th);
|
||
settings_section_label(g_yl_about, "关于", th);
|
||
|
||
if (g_note[0]) {
|
||
meow_ui_text(24, g_note_y, g_note, th->font_body,
|
||
g_note_err ? th->error : th->primary);
|
||
}
|
||
int hy = g_note_y + (g_note[0] ? 30 : 0);
|
||
meow_ui_text(24, hy, "改动即时保存,下次启动生效", th->font_body, th->onSurfaceVariant);
|
||
meow_ui_text(24, hy + 28, "PIN 仅防随手查看,非安全边界", th->font_body,
|
||
th->onSurfaceVariant);
|
||
meow_ui_fill_rect(0, meow_ui_height() - 1, w, 1, th->outlineVariant);
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 事件 */
|
||
/* ============================================================ */
|
||
static int settings_on_event(meow_view *v, int ev, int a, int b, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
if (ev == MR_VEV_TAP) {
|
||
int x = a, y = b;
|
||
if (g_tb) {
|
||
int hit = meow_topappbar_hit(g_tb, x, y);
|
||
if (hit == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return 1; }
|
||
if (hit >= 0) return 1;
|
||
}
|
||
if (g_lv_dir) {
|
||
int idx = meow_listview_hit(g_lv_dir, x, y);
|
||
if (idx == 0) {
|
||
mr_reader_set_direction(mr_reader_get_direction() == MR_DIR_RTL
|
||
? MR_DIR_LTR : MR_DIR_RTL);
|
||
settings_note(mr_reader_get_direction() == MR_DIR_RTL
|
||
? "阅读方向:右到左(RTL)"
|
||
: "阅读方向:左到右(LTR)", 0);
|
||
settings_repaint();
|
||
return 1;
|
||
}
|
||
}
|
||
if (g_lv_pin) {
|
||
int idx = meow_listview_hit(g_lv_pin, x, y);
|
||
if (idx == ST_PIN_ROW_SWITCH) {
|
||
settings_toggle_pin();
|
||
settings_repaint();
|
||
return 1;
|
||
}
|
||
if (idx == ST_PIN_ROW_CHANGE) {
|
||
settings_change_pin();
|
||
settings_repaint();
|
||
return 1;
|
||
}
|
||
}
|
||
if (g_lv_about) {
|
||
int idx = meow_listview_hit(g_lv_about, x, y);
|
||
if (idx == 1) {
|
||
settings_note("主题跟随系统设置,App 内不可改", 0);
|
||
settings_repaint();
|
||
return 1;
|
||
}
|
||
if (idx == 0) return 1; /* 版本行:仅展示 */
|
||
}
|
||
return 0;
|
||
}
|
||
/* SWIPE 不消费:app.c 对非 library 页的左缘返回统一 page_pop。 */
|
||
return 0;
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* view 生命周期 */
|
||
/* ============================================================ */
|
||
static void settings_page_enter(meow_view *v, void *ctx)
|
||
{
|
||
((mr_page_ctrl *)ctx)->active = 1;
|
||
meow_ui_set_fullscreen(0); /* 保留系统状态栏(与书库一致) */
|
||
meow_ui_set_page_bg(meow_theme_get()->bg);
|
||
|
||
if (!g_tb) {
|
||
/* 栈顶此时已是 ACT_SETTINGS(page_push 先 push 再 on_enter)→ 控件 owner 正确。 */
|
||
settings_build();
|
||
meow_widget *all[5] = { g_tb, g_lv_dir, g_lv_pin, g_lv_about, g_overlay };
|
||
for (int i = 0; i < 5; i++)
|
||
if (all[i]) meow_view_add_widget(v, all[i]);
|
||
}
|
||
settings_place(v);
|
||
g_pin_on = mr_pin_enabled(); /* 进入页时读一次;页内改动由操作函数同步 */
|
||
mr_nav_report();
|
||
meow_ui_paint_full();
|
||
}
|
||
|
||
static void settings_page_leave(meow_view *v, void *ctx)
|
||
{
|
||
(void)v;
|
||
((mr_page_ctrl *)ctx)->active = 0;
|
||
}
|
||
|
||
static void settings_page_destroy(meow_view *v, void *ctx)
|
||
{
|
||
(void)v; (void)ctx;
|
||
/* 控件随 meow_view_free 统一释放(v->widgets);这里只清指针,避免悬垂。 */
|
||
g_tb = NULL;
|
||
g_lv_dir = NULL;
|
||
g_lv_pin = NULL;
|
||
g_lv_about = NULL;
|
||
g_overlay = NULL;
|
||
}
|
||
|
||
meow_view *settings_view_create(void)
|
||
{
|
||
static const meow_view_hooks h = {
|
||
.on_enter = settings_page_enter,
|
||
.on_leave = settings_page_leave,
|
||
.on_destroy = settings_page_destroy,
|
||
.on_draw = settings_draw,
|
||
.on_event = settings_on_event,
|
||
};
|
||
return meow_view_create(ACT_SETTINGS, MEOW_VIEW_ACTIVITY, &h, &g_ctrl);
|
||
}
|