Merge branch 'feat/pin-keypad-merge-ready-narrow-rework'
This commit is contained in:
commit
ce34974727
3 files changed
+357
-90
No files matched your search
+25
@@ -81,8 +81,32 @@ static void app_on_swipe(int dir, int dx, int dy)
|
|||||||
if (app_active() == ACT_LIBRARY) library_on_swipe(dir, dx);
|
if (app_active() == ACT_LIBRARY) library_on_swipe(dir, dx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 通用长按(SDK §6.3)是 opt-in:未注册回调时 meow_input_long_press_poll 直接返回 0,
|
||||||
|
* MEOW_EV_LONG_PRESS 永远不会产生 —— 锁屏的「长按退格清空」在真机上就退化成单删一位。
|
||||||
|
* 这里注册一个 no-op 回调把长按打开;事件仍然先经 meow_view_dispatch_event 路由,锁屏页
|
||||||
|
* 自己消费 LONG_PRESS(其它页命中不到就走 no-op)。
|
||||||
|
*
|
||||||
|
* 只在锁屏为顶层页时启用:长按一旦触发,其抬手不再产生 TAP/SWIPE(SDK 抑制点击),
|
||||||
|
* 全程开启会让书库/阅读器里「按住稍久」的点击被吞掉。锁屏页解锁 pop 后由 app_on_tick
|
||||||
|
* 下一拍关掉,避免把长按语义泄漏到其它页。 */
|
||||||
|
static int g_long_press_on;
|
||||||
|
|
||||||
|
static void app_on_long_press(int x, int y)
|
||||||
|
{
|
||||||
|
(void)x; (void)y; /* 锁屏已在 view 分发里消费;其余页没有长按语义 */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_sync_long_press(void)
|
||||||
|
{
|
||||||
|
int on = (app_active() == ACT_LOCK);
|
||||||
|
if (on == g_long_press_on) return;
|
||||||
|
meow_ui_set_long_press_cb(on ? app_on_long_press : NULL);
|
||||||
|
g_long_press_on = on;
|
||||||
|
}
|
||||||
|
|
||||||
static void app_on_tick(void)
|
static void app_on_tick(void)
|
||||||
{
|
{
|
||||||
|
app_sync_long_press();
|
||||||
library_on_tick();
|
library_on_tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +150,7 @@ int main(void)
|
|||||||
meow_view *lock = lock_view_create();
|
meow_view *lock = lock_view_create();
|
||||||
|
|
||||||
meow_view_page_push(lock_view_should_show() ? ACT_LOCK : ACT_LIBRARY);
|
meow_view_page_push(lock_view_should_show() ? ACT_LOCK : ACT_LIBRARY);
|
||||||
|
app_sync_long_press(); /* 进主循环前先对齐一次:启动即锁屏时首帧长按就可用 */
|
||||||
|
|
||||||
meow_ui_events ev = {
|
meow_ui_events ev = {
|
||||||
app_on_key, app_on_tap, app_on_swipe, app_on_tick,
|
app_on_key, app_on_tap, app_on_swipe, app_on_tick,
|
||||||
|
|||||||
@@ -8,8 +8,12 @@
|
|||||||
* 的 on_swipe),否则左缘返回会把 ACT_LOCK page_pop 掉、PIN 被绕过。
|
* 的 on_swipe),否则左缘返回会把 ACT_LOCK page_pop 掉、PIN 被绕过。
|
||||||
* 另外覆盖:设置页回到书库(topbar 返回)、控件跨进出复用不重建、阅读方向经
|
* 另外覆盖:设置页回到书库(topbar 返回)、控件跨进出复用不重建、阅读方向经
|
||||||
* mr_reader_set_direction 切换、lock_view_should_show() 随 PIN 状态变化、
|
* mr_reader_set_direction 切换、lock_view_should_show() 随 PIN 状态变化、
|
||||||
* 主题模式切换后整帧重绘不崩,以及**真实 MEOW_IME_NUM 键盘**驱动的两条端到端路径:
|
* 主题模式切换后整帧重绘不崩,以及两条端到端路径:
|
||||||
* 锁屏错 PIN(留在锁屏)→ 对 PIN(page_pop 回书库)、设置页两遍输入开启 PIN。
|
* - **锁屏页内自绘 12 键数字键盘**(1-9 / 退格·0·确认):错 PIN 留在锁屏 → 对 PIN
|
||||||
|
* page_pop 回书库;位数不足确认不可用、超长输入截断、退格/长按清空、错误后清空重输;
|
||||||
|
* 键高/间距/屏底 28px 热区几何契约;长按退格走真实 SDK opt-in 路径(未注册回调不产生
|
||||||
|
* MEOW_EV_LONG_PRESS = M29 评审 P2 的根因,注册后才清空);
|
||||||
|
* - 设置页两遍输入开启 PIN(仍走真实 MEOW_IME_NUM 键盘注入)。
|
||||||
*
|
*
|
||||||
* 编译:见 test/run_tests.sh —— 它把本测试、settings/lock/pin 源码与 SDK 源码
|
* 编译:见 test/run_tests.sh —— 它把本测试、settings/lock/pin 源码与 SDK 源码
|
||||||
* (-DMEOW_UI_HOST_TEST,附 test_host_stub.c 打桩 font/global)一起编;SDK 对象清单在脚本里。
|
* (-DMEOW_UI_HOST_TEST,附 test_host_stub.c 打桩 font/global)一起编;SDK 对象清单在脚本里。
|
||||||
@@ -88,17 +92,42 @@ static void *pin_injector(void *arg)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lock_try_pin(const char *digits)
|
/* ============================================================ */
|
||||||
|
/* 锁屏页内自绘 12 键数字键盘(宿主没有真实触摸):按 widget 矩形中心投递 TAP。
|
||||||
|
* 键下标 = lock.c 创建序:0..8 = 1..9,9 = 退格,10 = 0,11 = 确认(LK_K*)。
|
||||||
|
* ============================================================ */
|
||||||
|
enum { LK_K1 = 0, LK_K9 = 8, LK_KBACK = 9, LK_K0 = 10, LK_KOK = 11 };
|
||||||
|
|
||||||
|
static int lock_key_center(meow_view *lock, int idx, int *cx, int *cy)
|
||||||
{
|
{
|
||||||
int sb = meow_ui_statusbar_height();
|
int r[4] = { 0 };
|
||||||
int cy = sb + (meow_ui_height() - sb) / 2;
|
if (!lock || idx < 0 || idx >= lock->nwidgets || !lock->widgets[idx]) return 0;
|
||||||
pin_job job = { digits, 1 };
|
meow_widget_rect(lock->widgets[idx], r);
|
||||||
pthread_t th;
|
*cx = r[0] + r[2] / 2;
|
||||||
if (pthread_create(&th, NULL, pin_injector, &job) != 0) return;
|
*cy = r[1] + r[3] / 2;
|
||||||
meow_view_dispatch_event(MEOW_EV_TAP, 240, cy + 40); /* 点「输入 PIN」按钮 */
|
return 1;
|
||||||
pthread_join(th, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void lock_tap_key(meow_view *lock, int idx)
|
||||||
|
{
|
||||||
|
int cx, cy;
|
||||||
|
if (lock_key_center(lock, idx, &cx, &cy))
|
||||||
|
meow_view_dispatch_event(MEOW_EV_TAP, cx, cy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lock_key_idx(char d)
|
||||||
|
{
|
||||||
|
return (d == '0') ? LK_K0 : (d - '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lock_type(meow_view *lock, const char *digits)
|
||||||
|
{
|
||||||
|
for (const char *s = digits; *s; s++) lock_tap_key(lock, lock_key_idx(*s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* app.c 注册的 no-op 长按回调(事件先经 view 分发,锁屏页消费;其它页落到这里)。 */
|
||||||
|
static void app_long_press_noop(int x, int y) { (void)x; (void)y; }
|
||||||
|
|
||||||
static void settings_tap_pin_row(void){
|
static void settings_tap_pin_row(void){
|
||||||
/* 布局:sb + 56 顶栏 + 6 + 段头34 + 方向行64 + 12 + 段头34 → PIN 列表首行中心 +32 */
|
/* 布局:sb + 56 顶栏 + 6 + 段头34 + 方向行64 + 12 + 段头34 → PIN 列表首行中心 +32 */
|
||||||
int y = meow_ui_statusbar_height() + 56 + 6 + 34 + 64 + 12 + 34 + 32;
|
int y = meow_ui_statusbar_height() + 56 + 6 + 34 + 64 + 12 + 34 + 32;
|
||||||
@@ -182,8 +211,31 @@ int main(void)
|
|||||||
|
|
||||||
meow_view_page_push(ACT_LOCK);
|
meow_view_page_push(ACT_LOCK);
|
||||||
CHECK(meow_ui_activity_active() == ACT_LOCK, "lock active");
|
CHECK(meow_ui_activity_active() == ACT_LOCK, "lock active");
|
||||||
CHECK(lock->nwidgets == 1, "lock built its button on enter (got %d)", lock->nwidgets);
|
CHECK(lock->nwidgets == 12, "lock built 12-key pad on enter (got %d)", lock->nwidgets);
|
||||||
CHECK(meow_widget_owner(lock->widgets[0]) == ACT_LOCK, "lock button owned by ACT_LOCK");
|
int lock_owner_ok = 1;
|
||||||
|
for (int i = 0; i < lock->nwidgets; i++)
|
||||||
|
if (meow_widget_owner(lock->widgets[i]) != ACT_LOCK) lock_owner_ok = 0;
|
||||||
|
CHECK(lock_owner_ok, "pad keys owned by ACT_LOCK (no cross-page leak)");
|
||||||
|
|
||||||
|
/* 几何契约:键 ≥64px 高、间距 ≥8px、不覆盖屏底 28px 上滑热区、全在屏内。 */
|
||||||
|
int rk[12][4];
|
||||||
|
for (int i = 0; i < 12; i++) meow_widget_rect(lock->widgets[i], rk[i]);
|
||||||
|
int geo_ok = 1, gap_ok = 1, hot_ok = 1;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (rk[i][2] < 64 || rk[i][3] < 64) geo_ok = 0;
|
||||||
|
if (rk[i][0] < 0 || rk[i][0] + rk[i][2] > meow_ui_width()) geo_ok = 0;
|
||||||
|
if (rk[i][1] + rk[i][3] > meow_ui_height() - 28) hot_ok = 0;
|
||||||
|
}
|
||||||
|
/* 列间距(行内 0/1/2)与行间距(行 0 与行 3:下标 0 与 9)。 */
|
||||||
|
if (rk[1][0] - (rk[0][0] + rk[0][2]) < 8) gap_ok = 0;
|
||||||
|
if (rk[2][0] - (rk[1][0] + rk[1][2]) < 8) gap_ok = 0;
|
||||||
|
if (rk[9][1] - (rk[0][1] + rk[0][3]) < 8) gap_ok = 0;
|
||||||
|
CHECK(geo_ok, "pad keys >=64px and within screen");
|
||||||
|
CHECK(gap_ok, "pad gaps >=8px");
|
||||||
|
CHECK(hot_ok, "pad leaves the 28px bottom swipe hot zone");
|
||||||
|
printf(" pad geometry: key %dx%d, pad top=%d bottom=%d (screen %dx%d)\n",
|
||||||
|
rk[0][2], rk[0][3], rk[0][1], rk[11][1] + rk[11][3],
|
||||||
|
meow_ui_width(), meow_ui_height());
|
||||||
|
|
||||||
CHECK(meow_view_dispatch_event(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 240) == 1,
|
CHECK(meow_view_dispatch_event(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 240) == 1,
|
||||||
"lock consumes SWIPE (left-edge back cannot bypass PIN)");
|
"lock consumes SWIPE (left-edge back cannot bypass PIN)");
|
||||||
@@ -194,11 +246,85 @@ int main(void)
|
|||||||
CHECK(mr_pin_verify("1357") == 1, "stored PIN verifies");
|
CHECK(mr_pin_verify("1357") == 1, "stored PIN verifies");
|
||||||
CHECK(mr_pin_verify("1358") == 0, "wrong PIN rejected");
|
CHECK(mr_pin_verify("1358") == 0, "wrong PIN rejected");
|
||||||
|
|
||||||
/* ---- 锁屏真解锁路径:点按钮 → MEOW_IME_NUM 输入 → 校验 → pop(错误则留在锁屏) ---- */
|
/* 空输入 / 位数不足:确认不可用(不误判、不退出锁屏)。 */
|
||||||
lock_try_pin("1358"); /* 错 PIN:留在锁屏 */
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LOCK, "empty confirm keeps lock");
|
||||||
|
lock_type(lock, "135");
|
||||||
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LOCK, "confirm disabled below 4 digits");
|
||||||
|
|
||||||
|
/* 第 4 位后确认可用:1-3-5-7 → 回书库。 */
|
||||||
|
lock_tap_key(lock, lock_key_idx('7'));
|
||||||
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "4-digit correct PIN pops lock to library");
|
||||||
|
CHECK(meow_ime_active() == 0, "in-page pad never opens the system IME");
|
||||||
|
|
||||||
|
/* 错 PIN:留在锁屏 + 清空;再输对 → 解锁(证明错误后缓冲已清空)。 */
|
||||||
|
meow_view_page_push(ACT_LOCK);
|
||||||
|
lock_type(lock, "1358");
|
||||||
|
lock_tap_key(lock, LK_KOK);
|
||||||
CHECK(meow_ui_activity_active() == ACT_LOCK, "wrong PIN keeps lock (retry allowed)");
|
CHECK(meow_ui_activity_active() == ACT_LOCK, "wrong PIN keeps lock (retry allowed)");
|
||||||
lock_try_pin("1357"); /* 正确:page_pop 回书库 */
|
lock_type(lock, "1357");
|
||||||
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "correct PIN pops lock to library");
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LIBRARY,
|
||||||
|
"retry after wrong PIN unlocks (buffer cleared on error)");
|
||||||
|
|
||||||
|
/* 8 位上限:9 个键只收前 8 位;退格 4 次回到 "1357" → 解锁。 */
|
||||||
|
meow_view_page_push(ACT_LOCK);
|
||||||
|
lock_type(lock, "135713579");
|
||||||
|
for (int i = 0; i < 4; i++) lock_tap_key(lock, LK_KBACK);
|
||||||
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LIBRARY,
|
||||||
|
"9th digit ignored (max 8) + backspace removes digits");
|
||||||
|
|
||||||
|
/* 退格空缓冲边界:多按不崩,随后正常输入。 */
|
||||||
|
meow_view_page_push(ACT_LOCK);
|
||||||
|
for (int i = 0; i < 3; i++) lock_tap_key(lock, LK_KBACK);
|
||||||
|
lock_type(lock, "1357");
|
||||||
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "backspace on empty buffer is a no-op");
|
||||||
|
|
||||||
|
/* 长按退格清空(M29 评审 P2-1)走真实 SDK 路径:
|
||||||
|
* SDK 长按是 opt-in —— 未注册回调时 meow_input_long_press_poll 恒返回 0,
|
||||||
|
* MEOW_EV_LONG_PRESS 永远不会产生(真机上「长按清空」只会退化成单删一位,P2 的根因);
|
||||||
|
* app.c 在锁屏为顶层页时注册 no-op 回调后,按住 700ms(≥500 阈值)应真正产出 LONG_PRESS,
|
||||||
|
* 并按主循环顺序先交 view 分发(锁屏页消费),且其抬手不再补一个 TAP。 */
|
||||||
|
meow_view_page_push(ACT_LOCK);
|
||||||
|
int bx = 0, by = 0;
|
||||||
|
CHECK(lock_key_center(lock, LK_KBACK, &bx, &by), "backspace key has a rect");
|
||||||
|
|
||||||
|
meow_ui_set_long_press_cb(NULL);
|
||||||
|
meow_input_touch_set(1, bx, by, 700);
|
||||||
|
CHECK(meow_input_long_press_poll() == 0,
|
||||||
|
"no LONG_PRESS while callback unregistered (opt-in gate = P2 root cause)");
|
||||||
|
meow_input_touch_release();
|
||||||
|
meow_input_drain(); /* 未成长按的这次按压,抬手会补一个 TAP,清掉 */
|
||||||
|
|
||||||
|
lock_type(lock, "135");
|
||||||
|
meow_ui_set_long_press_cb(app_long_press_noop); /* 同 app.c:锁屏为顶层页时注册 */
|
||||||
|
meow_input_touch_set(1, bx, by, 700);
|
||||||
|
CHECK(meow_input_long_press_poll() == 1, "registered long-press crosses the 500ms threshold");
|
||||||
|
meow_input_ev lp = { 0, 0, 0 };
|
||||||
|
CHECK(meow_input_pop(&lp) == 1 && lp.type == MEOW_EV_LONG_PRESS &&
|
||||||
|
lp.a == bx && lp.b == by, "LONG_PRESS queued at the pressed backspace key");
|
||||||
|
CHECK(meow_view_dispatch_event(MEOW_EV_LONG_PRESS, lp.a, lp.b) == 1,
|
||||||
|
"lock view consumes LONG_PRESS before the no-op callback");
|
||||||
|
meow_input_touch_release();
|
||||||
|
CHECK(meow_input_pop(&lp) == 0, "long-press release does not emit a stray TAP");
|
||||||
|
/* 「到底清没清空」不在这里断言:此刻缓冲只有 "135"(3 位),而确认在 <4 位时本就
|
||||||
|
* disabled,任何「仍停在 ACT_LOCK」的判断都恒真、测不出清空(M30 评审 P2-1)。
|
||||||
|
* 真正的判据是下一段:若没清空,缓冲会变成 "135"+"1357" = 7 位错码 → 校验失败。 */
|
||||||
|
lock_type(lock, "1357");
|
||||||
|
lock_tap_key(lock, LK_KOK);
|
||||||
|
CHECK(meow_ui_activity_active() == ACT_LIBRARY,
|
||||||
|
"unlock after long-press clear (7-digit buffer would fail if not cleared)");
|
||||||
|
|
||||||
|
/* 解锁后 app.c 关掉长按:不得把长按语义泄漏到书库/阅读器(按住稍久的点击仍算 TAP)。 */
|
||||||
|
meow_ui_set_long_press_cb(NULL);
|
||||||
|
meow_input_touch_set(1, bx, by, 700);
|
||||||
|
CHECK(meow_input_long_press_poll() == 0, "long-press off again after leaving the lock page");
|
||||||
|
meow_input_touch_release();
|
||||||
|
meow_input_drain();
|
||||||
|
|
||||||
/* ---- 设置页两遍输入开启 PIN(MEOW_IME_NUM) ---- */
|
/* ---- 设置页两遍输入开启 PIN(MEOW_IME_NUM) ---- */
|
||||||
mr_pin_disable();
|
mr_pin_disable();
|
||||||
|
|||||||
+190
-74
@@ -1,18 +1,27 @@
|
|||||||
/* ui/lock/lock.c — 锁屏页(ACT_LOCK):APP 启动 PIN。
|
/* ui/lock/lock.c — 锁屏页(ACT_LOCK):APP 启动 PIN(页内自绘数字键盘)。
|
||||||
*
|
*
|
||||||
* 流程(用户原话「允许用户在APP设置中设置APP启动 PIN」):
|
* 流程(用户原话「允许用户在APP设置中设置APP启动 PIN」):
|
||||||
* - lock_view_should_show() = logic/pin 的 mr_pin_enabled();app.c 据此在启动时先 push
|
* - lock_view_should_show() = logic/pin 的 mr_pin_enabled();app.c 据此在启动时先 push
|
||||||
* 本页,验证通过前书库不进入/不绘制(view 注册表只画已进入的页,widget 还按 owner 过滤);
|
* 本页,验证通过前书库不进入/不绘制(view 注册表只画已进入的页,widget 还按 owner 过滤);
|
||||||
* - 本页 on_enter 建「输入 PIN」按钮(owner=ACT_LOCK),点按弹系统数字键盘(MEOW_IME_NUM),
|
* - 本页 on_enter 建 12 键数字键盘(1-9 / 退格·0·确认,owner=ACT_LOCK)。**不再调系统
|
||||||
* 校验通过 → page_pop 回书库;错误 → 就地提示重试(不做惩罚性锁定/计数);
|
* 输入法**:真机 goodix 抬手长尾会让 MEOW_IME_NUM 打开即被「点背景=取消」关掉,表现为
|
||||||
|
* 「点好几下才弹键盘」;页内键盘完全离线、无 IME 会话,首次点按必响应;
|
||||||
|
* - 4-8 位数字(logic/pin 的 MR_PIN_MIN_LEN/MAX_LEN),顶部只显示星号/圆点数位,不回显明文;
|
||||||
|
* 满 4 位后「确认」才可用;错误 → 清空 + 提示;通过 → page_pop 回书库;
|
||||||
|
* - 长按「退格」清空已输入(不做惩罚性计数/锁定);SDK 长按是 opt-in,需 app.c 在锁屏
|
||||||
|
* 为顶层页时注册 meow_ui_set_long_press_cb(事件仍先走本页 view 分发),否则真机上
|
||||||
|
* MEOW_EV_LONG_PRESS 不会产生、长按只会退化成单删一位;
|
||||||
* - 左缘返回**必须**由本页 on_event 消费 SWIPE(meow_view_dispatch_event 先于 app.c 的
|
* - 左缘返回**必须**由本页 on_event 消费 SWIPE(meow_view_dispatch_event 先于 app.c 的
|
||||||
* on_swipe;否则 app.c 对非 library 页无条件 page_pop,PIN 会被绕过);
|
* on_swipe;否则 app.c 对非 library 页无条件 page_pop,PIN 会被绕过);TAP/LONG_PRESS
|
||||||
|
* 也一律消费,物理键不接(返回 0,交 app.c 的 on_key 仅日志);屏底 28px 上滑热区
|
||||||
|
* 不被键盘覆盖(键盘底边留 28+12px);
|
||||||
* - PIN 未启用时 app.c 直接 push 书库,本页不会被进入。
|
* - PIN 未启用时 app.c 直接 push 书库,本页不会被进入。
|
||||||
*
|
*
|
||||||
* 安全口径(诚实标注):PIN 防的是随手翻看,不是安全边界——TF 卡可拔出直读,配置目录
|
* 安全口径(诚实标注):PIN 防的是随手翻看,不是安全边界——TF 卡可拔出直读,配置目录
|
||||||
* 可改。存储为加盐迭代哈希(logic/pin.c),不引入加密库;锁屏文案也如实告知用户。
|
* 可改。存储为加盐迭代哈希(logic/pin.c),不引入加密库;锁屏文案也如实告知用户。
|
||||||
*
|
*
|
||||||
* 控件归属(§6.1,M3 桩注释①):on_create 时 activity 栈为空,控件一律在 on_enter 建。
|
* 控件归属(§6.1,M3 桩注释①):on_create 时 activity 栈为空,控件一律在 on_enter 建。
|
||||||
|
* 键位顺序(宿主测试依赖):1..9、退格、0、确认 = 下标 0..11。
|
||||||
*/
|
*/
|
||||||
#include "tuku_internal.h"
|
#include "tuku_internal.h"
|
||||||
#include "logic/pin.h"
|
#include "logic/pin.h"
|
||||||
@@ -20,19 +29,35 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#define LK_KEY_COUNT 12
|
||||||
|
#define LK_GRID_COLS 3
|
||||||
|
#define LK_GRID_ROWS 4
|
||||||
|
#define LK_GAP 10 /* 键间距(≥8;触摸目标互不粘连) */
|
||||||
|
#define LK_KEY_MIN_H 64 /* 最小键高 */
|
||||||
|
#define LK_KEY_MAX_H 96
|
||||||
|
#define LK_KEY_MAX_W 160
|
||||||
|
#define LK_SIDE 24 /* 键盘左右留白 */
|
||||||
|
#define LK_BOTTOM_HOT 28 /* 屏底上滑回桌面热区:键盘不得覆盖 */
|
||||||
|
#define LK_BOTTOM_PAD 12 /* 再留一点呼吸,避免贴住热区 */
|
||||||
|
#define LK_HEAD_MIN 220 /* 键盘上方留给标题/圆点/提示的最小高度 */
|
||||||
|
|
||||||
|
/* 键下标 = 创建序(宿主测试依赖此序)。 */
|
||||||
|
enum { LK_K1 = 0, LK_K9 = 8, LK_KBACK = 9, LK_K0 = 10, LK_KOK = 11 };
|
||||||
|
|
||||||
static mr_page_ctrl g_ctrl;
|
static mr_page_ctrl g_ctrl;
|
||||||
|
|
||||||
static meow_widget *g_btn;
|
static meow_widget *g_keys[LK_KEY_COUNT];
|
||||||
|
|
||||||
|
static char g_pin[MR_PIN_MAX_LEN + 1];
|
||||||
|
static int g_pin_len;
|
||||||
static char g_msg[64];
|
static char g_msg[64];
|
||||||
static int g_msg_err;
|
static int g_msg_err;
|
||||||
static int g_waiting; /* 1=数字键盘打开中(上部提示改为输入说明) */
|
|
||||||
static char g_ime_pin[MR_PIN_MAX_LEN + 12];
|
|
||||||
|
|
||||||
/* meow_view on_event 的事件码:公共伞头未导出(sdk/meow_ui_internal.h:
|
/* meow_view on_event 的事件码:公共伞头未导出(sdk/meow_ui_internal.h:
|
||||||
* MEOW_EV_TAP=2 / MEOW_EV_SWIPE=3,值稳定),此处按契约镜像。 */
|
* MEOW_EV_TAP=2 / MEOW_EV_SWIPE=3 / MEOW_EV_LONG_PRESS=8,值稳定),按契约镜像。 */
|
||||||
#define MR_VEV_TAP 2
|
#define MR_VEV_TAP 2
|
||||||
#define MR_VEV_SWIPE 3
|
#define MR_VEV_SWIPE 3
|
||||||
|
#define MR_VEV_LONG_PRESS 8
|
||||||
|
|
||||||
int lock_view_should_show(void)
|
int lock_view_should_show(void)
|
||||||
{
|
{
|
||||||
@@ -40,55 +65,111 @@ int lock_view_should_show(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
/* PIN 输入(系统数字键盘) */
|
/* 几何(480×720:键 ~137×96,四行 + 屏底热区留白) */
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
static void ime_pin_change(const char *text, void *ctx)
|
static void lock_keypad_geom(int *x0, int *y0, int *kw, int *kh)
|
||||||
{
|
{
|
||||||
(void)ctx;
|
int w = meow_ui_width();
|
||||||
snprintf(g_ime_pin, sizeof g_ime_pin, "%s", text ? text : "");
|
int h = meow_ui_height();
|
||||||
|
int sb = meow_ui_statusbar_height();
|
||||||
|
int bottom = h - LK_BOTTOM_HOT - LK_BOTTOM_PAD;
|
||||||
|
int top_min = sb + LK_HEAD_MIN;
|
||||||
|
|
||||||
|
int kw_ = (w - 2 * LK_SIDE - (LK_GRID_COLS - 1) * LK_GAP) / LK_GRID_COLS;
|
||||||
|
if (kw_ > LK_KEY_MAX_W) kw_ = LK_KEY_MAX_W;
|
||||||
|
if (kw_ < LK_KEY_MIN_H) kw_ = LK_KEY_MIN_H;
|
||||||
|
|
||||||
|
int kh_ = (bottom - top_min - (LK_GRID_ROWS - 1) * LK_GAP) / LK_GRID_ROWS;
|
||||||
|
if (kh_ > LK_KEY_MAX_H) kh_ = LK_KEY_MAX_H;
|
||||||
|
if (kh_ < LK_KEY_MIN_H) kh_ = LK_KEY_MIN_H;
|
||||||
|
|
||||||
|
*kw = kw_;
|
||||||
|
*kh = kh_;
|
||||||
|
*x0 = (w - (LK_GRID_COLS * kw_ + (LK_GRID_COLS - 1) * LK_GAP)) / 2;
|
||||||
|
*y0 = bottom - (LK_GRID_ROWS * kh_ + (LK_GRID_ROWS - 1) * LK_GAP);
|
||||||
|
if (*y0 < top_min) *y0 = top_min; /* 极小屏兜底;480×720 不会触发 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 阻塞数字键盘;返回 1=收集到非空文本(关闭键盘即结束/校验)。
|
static void lock_key_rect(int idx, int *x, int *y, int *w, int *h)
|
||||||
* SDK 现状:键盘「确定」只置内部 submit 标记不关闭,关闭路径(取消/点背景)又把标记清 0,
|
|
||||||
* 故 meow_ui_text_input 的返回值不可作“确认”判据;文本经 on_change 实时收集,空=取消。 */
|
|
||||||
static int lock_read_pin(char *out, int outsz)
|
|
||||||
{
|
{
|
||||||
meow_ime_ctx c;
|
int x0, y0, kw, kh;
|
||||||
memset(&c, 0, sizeof c);
|
lock_keypad_geom(&x0, &y0, &kw, &kh);
|
||||||
c.mode = MEOW_IME_NUM;
|
int row = idx / LK_GRID_COLS, col = idx % LK_GRID_COLS;
|
||||||
c.maxlen = (int)sizeof g_ime_pin;
|
*x = x0 + col * (kw + LK_GAP);
|
||||||
c.on_change = ime_pin_change;
|
*y = y0 + row * (kh + LK_GAP);
|
||||||
c.ctx = NULL;
|
*w = kw;
|
||||||
|
*h = kh;
|
||||||
|
}
|
||||||
|
|
||||||
g_ime_pin[0] = '\0';
|
/* ============================================================ */
|
||||||
|
/* PIN 状态 */
|
||||||
|
/* ============================================================ */
|
||||||
|
static void lock_refresh(void)
|
||||||
|
{
|
||||||
|
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 「确认」仅在 ≥4 位时可用;其余情况保持禁用(SDK 按状态机变淡且不响应命中)。 */
|
||||||
|
static void lock_sync_confirm(void)
|
||||||
|
{
|
||||||
|
if (g_keys[LK_KOK])
|
||||||
|
meow_widget_set_enabled(g_keys[LK_KOK], g_pin_len >= MR_PIN_MIN_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lock_clear_digits(void)
|
||||||
|
{
|
||||||
|
g_pin_len = 0;
|
||||||
|
g_pin[0] = '\0';
|
||||||
|
lock_sync_confirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lock_append_digit(int d)
|
||||||
|
{
|
||||||
|
if (g_pin_len >= MR_PIN_MAX_LEN) return; /* 8 位上限:多余按键忽略 */
|
||||||
|
g_pin[g_pin_len++] = (char)('0' + d);
|
||||||
|
g_pin[g_pin_len] = '\0';
|
||||||
g_msg[0] = '\0';
|
g_msg[0] = '\0';
|
||||||
g_msg_err = 0;
|
g_msg_err = 0;
|
||||||
g_waiting = 1; /* 锁屏上部提示改为输入说明 */
|
lock_sync_confirm();
|
||||||
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height()); /* 键盘区背景快照取当前帧 */
|
lock_refresh();
|
||||||
(void)meow_ui_text_input(&c);
|
|
||||||
g_waiting = 0;
|
|
||||||
|
|
||||||
snprintf(out, (size_t)outsz, "%s", g_ime_pin);
|
|
||||||
return out[0] != '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lock_attempt(void)
|
static void lock_backspace(void)
|
||||||
{
|
{
|
||||||
char pin[32] = "";
|
if (g_pin_len > 0) g_pin[--g_pin_len] = '\0';
|
||||||
if (!lock_read_pin(pin, (int)sizeof pin)) {
|
g_msg[0] = '\0';
|
||||||
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
|
g_msg_err = 0;
|
||||||
return; /* 关闭键盘未输入:留在锁屏 */
|
lock_sync_confirm();
|
||||||
}
|
lock_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
if (!mr_pin_verify(pin)) {
|
static void lock_clear_all(void)
|
||||||
snprintf(g_msg, sizeof g_msg, "PIN 错误,请重试");
|
{
|
||||||
|
lock_clear_digits();
|
||||||
|
g_msg[0] = '\0';
|
||||||
|
g_msg_err = 0;
|
||||||
|
lock_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lock_confirm(void)
|
||||||
|
{
|
||||||
|
if (g_pin_len < MR_PIN_MIN_LEN) {
|
||||||
|
snprintf(g_msg, sizeof g_msg, "请输入至少 %d 位数字", MR_PIN_MIN_LEN);
|
||||||
g_msg_err = 1;
|
g_msg_err = 1;
|
||||||
meow_ui_invalidate(0, 0, meow_ui_width(), meow_ui_height());
|
lock_refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mr_pin_verify(g_pin)) { /* 与设置页同一校验入口(logic/pin) */
|
||||||
|
meow_log(MEOW_LOG_INFO, "lock: pin wrong (len=%d), buffer cleared", g_pin_len);
|
||||||
|
snprintf(g_msg, sizeof g_msg, "PIN 错误,请重试");
|
||||||
|
g_msg_err = 1;
|
||||||
|
lock_clear_digits();
|
||||||
|
lock_refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_msg[0] = '\0';
|
g_msg[0] = '\0';
|
||||||
g_msg_err = 0;
|
g_msg_err = 0;
|
||||||
|
meow_log(MEOW_LOG_INFO, "lock: pin ok");
|
||||||
meow_view_page_pop(); /* 通过 → 回书库(ACT_LIBRARY on_enter) */
|
meow_view_page_pop(); /* 通过 → 回书库(ACT_LIBRARY on_enter) */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,33 +182,42 @@ static void lock_draw(meow_view *v, const meow_theme *th, void *ctx)
|
|||||||
int w = meow_ui_width();
|
int w = meow_ui_width();
|
||||||
int h = meow_ui_height();
|
int h = meow_ui_height();
|
||||||
int sb = meow_ui_statusbar_height();
|
int sb = meow_ui_statusbar_height();
|
||||||
int cy = sb + (h - sb) / 2;
|
|
||||||
|
int x0, y0, kw, kh;
|
||||||
|
lock_keypad_geom(&x0, &y0, &kw, &kh);
|
||||||
|
(void)x0; (void)kw; (void)kh;
|
||||||
|
|
||||||
/* 自绘整页底色:验证前不露任何书库内容(本页是顶层唯一 entered 页)。 */
|
/* 自绘整页底色:验证前不露任何书库内容(本页是顶层唯一 entered 页)。 */
|
||||||
meow_ui_fill_rect(0, sb, w, h - sb, th->bg);
|
meow_ui_fill_rect(0, sb, w, h - sb, th->bg);
|
||||||
|
|
||||||
meow_ui_text_center(w / 2, cy - 150, "图库已锁定", th->font_title, th->onSurface);
|
int title_y = sb + 36;
|
||||||
if (g_waiting) {
|
meow_ui_text_center(w / 2, title_y, "图库已锁定", th->font_title, th->onSurface);
|
||||||
meow_ui_text_center(w / 2, cy - 104, "输入 4-8 位数字后关闭键盘", th->font_body,
|
|
||||||
th->onSurface);
|
|
||||||
meow_ui_text_center(w / 2, cy - 76, "空输入 = 取消,回到锁屏", th->font_body,
|
|
||||||
th->onSurfaceVariant);
|
|
||||||
} else {
|
|
||||||
meow_ui_text_center(w / 2, cy - 104, "输入启动 PIN 继续", th->font_body,
|
|
||||||
th->onSurfaceVariant);
|
|
||||||
meow_ui_text_center(w / 2, cy - 76, "4-8 位数字 · 系统数字键盘", th->font_body,
|
|
||||||
th->onSurfaceVariant);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_msg[0]) {
|
/* 已输入位数:星号=已输入,点=空位;不回显明文。 */
|
||||||
meow_ui_text_center(w / 2, cy - 26, g_msg, th->font_body,
|
char dots[MR_PIN_MAX_LEN * 2 + 1];
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < MR_PIN_MAX_LEN; i++) {
|
||||||
|
dots[n++] = (i < g_pin_len) ? '*' : '.';
|
||||||
|
if (i + 1 < MR_PIN_MAX_LEN) dots[n++] = ' ';
|
||||||
|
}
|
||||||
|
dots[n] = '\0';
|
||||||
|
int dots_y = title_y + meow_ui_text_height(th->font_title) + 18;
|
||||||
|
meow_ui_text_center(w / 2, dots_y, dots, th->font_title,
|
||||||
|
g_pin_len > 0 ? th->primary : th->onSurfaceVariant);
|
||||||
|
|
||||||
|
int status_y = dots_y + meow_ui_text_height(th->font_title) + 8;
|
||||||
|
if (g_msg[0])
|
||||||
|
meow_ui_text_center(w / 2, status_y, g_msg, th->font_body,
|
||||||
g_msg_err ? th->error : th->primary);
|
g_msg_err ? th->error : th->primary);
|
||||||
}
|
else
|
||||||
|
meow_ui_text_center(w / 2, status_y, "输入 4-8 位数字后点确认", th->font_body,
|
||||||
/* 安全口径(如实告知):PIN 不是安全边界。 */
|
|
||||||
meow_ui_text_center(w / 2, h - 86, "PIN 仅用于防止随手查看,", th->font_body,
|
|
||||||
th->onSurfaceVariant);
|
th->onSurfaceVariant);
|
||||||
meow_ui_text_center(w / 2, h - 58, "TF 卡可被直接读取,不构成安全边界。", th->font_body,
|
|
||||||
|
/* 安全口径(如实告知),紧贴键盘上方。 */
|
||||||
|
int note_y = y0 - 54;
|
||||||
|
meow_ui_text_center(w / 2, note_y, "PIN 仅用于防止随手查看,", th->font_body,
|
||||||
|
th->onSurfaceVariant);
|
||||||
|
meow_ui_text_center(w / 2, note_y + 24, "TF 卡可被直接读取,不构成安全边界。", th->font_body,
|
||||||
th->onSurfaceVariant);
|
th->onSurfaceVariant);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,13 +225,13 @@ static void lock_place(meow_view *v)
|
|||||||
{
|
{
|
||||||
int w = meow_ui_width();
|
int w = meow_ui_width();
|
||||||
int h = meow_ui_height();
|
int h = meow_ui_height();
|
||||||
int sb = meow_ui_statusbar_height();
|
|
||||||
int cy = sb + (h - sb) / 2;
|
|
||||||
|
|
||||||
v->x = 0; v->y = 0; v->w = w; v->h = h;
|
v->x = 0; v->y = 0; v->w = w; v->h = h;
|
||||||
|
|
||||||
int bw = 220, bh = 52;
|
for (int i = 0; i < LK_KEY_COUNT; i++) {
|
||||||
if (g_btn) meow_widget_set_rect(g_btn, (w - bw) / 2, cy + 20, bw, bh);
|
int x, y, kw, kh;
|
||||||
|
lock_key_rect(i, &x, &y, &kw, &kh);
|
||||||
|
if (g_keys[i]) meow_widget_set_rect(g_keys[i], x, y, kw, kh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
@@ -154,12 +244,24 @@ static int lock_on_event(meow_view *v, int ev, int a, int b, void *ctx)
|
|||||||
/* 左缘返回(以及任何滑动)一律吞掉:锁屏不允许退出 App,也不允许绕过 PIN。 */
|
/* 左缘返回(以及任何滑动)一律吞掉:锁屏不允许退出 App,也不允许绕过 PIN。 */
|
||||||
if (ev == MR_VEV_SWIPE) return 1;
|
if (ev == MR_VEV_SWIPE) return 1;
|
||||||
|
|
||||||
if (ev == MR_VEV_TAP) {
|
if (ev == MR_VEV_LONG_PRESS) {
|
||||||
int x = a, y = b;
|
/* 长按「退格」= 清空(可选增强;其余长按也只吞掉,不透传其它页)。 */
|
||||||
if (meow_widget_hit(g_btn, x, y)) lock_attempt();
|
if (meow_widget_hit(g_keys[LK_KBACK], a, b)) lock_clear_all();
|
||||||
return 1; /* 锁屏页消费点击,不向 App 其它页透传 */
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
|
if (ev == MR_VEV_TAP) {
|
||||||
|
for (int i = 0; i < LK_KEY_COUNT; i++) {
|
||||||
|
if (!g_keys[i] || !meow_widget_hit(g_keys[i], a, b)) continue;
|
||||||
|
if (i <= LK_K9) lock_append_digit(i + 1); /* 1..9 */
|
||||||
|
else if (i == LK_KBACK) lock_backspace();
|
||||||
|
else if (i == LK_K0) lock_append_digit(0);
|
||||||
|
else lock_confirm();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 1; /* 锁屏页消费所有点击(含空白区),不向 App 其它页透传 */
|
||||||
|
}
|
||||||
|
return 0; /* 物理键交 app.c 的 on_key 仅日志(不映射数字/确认) */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
@@ -171,15 +273,28 @@ static void lock_page_enter(meow_view *v, void *ctx)
|
|||||||
meow_ui_set_fullscreen(0);
|
meow_ui_set_fullscreen(0);
|
||||||
meow_ui_set_page_bg(meow_theme_get()->bg);
|
meow_ui_set_page_bg(meow_theme_get()->bg);
|
||||||
|
|
||||||
|
g_pin_len = 0;
|
||||||
|
g_pin[0] = '\0';
|
||||||
g_msg[0] = '\0';
|
g_msg[0] = '\0';
|
||||||
g_msg_err = 0;
|
g_msg_err = 0;
|
||||||
|
|
||||||
if (!g_btn) {
|
static const char *const labels[LK_KEY_COUNT] = {
|
||||||
|
"1", "2", "3", "4", "5", "6", "7", "8", "9", "退格", "0", "确认",
|
||||||
|
};
|
||||||
|
for (int i = 0; i < LK_KEY_COUNT; i++) {
|
||||||
|
if (g_keys[i]) continue;
|
||||||
/* page_push 已先 meow_ui_activity_push(ACT_LOCK) → 控件 owner 正确。 */
|
/* page_push 已先 meow_ui_activity_push(ACT_LOCK) → 控件 owner 正确。 */
|
||||||
g_btn = meow_btn_create_variant(0, 0, 220, 52, "输入 PIN", MEOW_BTN_FILLED);
|
int variant = (i == LK_KOK) ? MEOW_BTN_FILLED
|
||||||
if (g_btn) meow_view_add_widget(v, g_btn);
|
: (i == LK_KBACK) ? MEOW_BTN_OUTLINED
|
||||||
|
: MEOW_BTN_TONAL;
|
||||||
|
g_keys[i] = meow_btn_create_variant(0, 0, 96, LK_KEY_MIN_H, labels[i], variant);
|
||||||
|
if (g_keys[i]) meow_view_add_widget(v, g_keys[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
lock_place(v);
|
lock_place(v);
|
||||||
|
lock_sync_confirm();
|
||||||
|
meow_log(MEOW_LOG_INFO, "lock: pad ready (12 keys, screen %dx%d)",
|
||||||
|
meow_ui_width(), meow_ui_height());
|
||||||
mr_nav_report();
|
mr_nav_report();
|
||||||
meow_ui_paint_full();
|
meow_ui_paint_full();
|
||||||
}
|
}
|
||||||
@@ -193,7 +308,8 @@ static void lock_page_leave(meow_view *v, void *ctx)
|
|||||||
static void lock_page_destroy(meow_view *v, void *ctx)
|
static void lock_page_destroy(meow_view *v, void *ctx)
|
||||||
{
|
{
|
||||||
(void)v; (void)ctx;
|
(void)v; (void)ctx;
|
||||||
g_btn = NULL; /* 控件随 meow_view_free 释放(v->widgets) */
|
for (int i = 0; i < LK_KEY_COUNT; i++) g_keys[i] = NULL;
|
||||||
|
/* 控件随 meow_view_free 释放(v->widgets) */
|
||||||
}
|
}
|
||||||
|
|
||||||
meow_view *lock_view_create(void)
|
meow_view *lock_view_create(void)
|
||||||
|
|||||||
Reference in new issue
Block a user