通知中心增强(agent-126/127/128/130) + App内通知中心方案(agent-129)

- 开关平移动画(时间轴180ms可打断/快照memcpy/顶28不盖状态栏) agent-126
- 音量/亮度滑条复用设置页方案(跳步+上限clamp+写DAC去抖/状态栏带仅redraw_sb清绘) agent-127
- 通知列表滚动(方向随input.swipe_invert, 滚动≠关闭, 回弹才关) agent-128
- 去音量chip(静音↔恢复last_nz, NCP_CHIP_N 5→4: WiFi/蓝牙/主题/亮度) agent-130
- meow_ui.a 零改动(d249c6e6); launcher md5 c7893faf; 宿主 90+/0warn
- ④App前台划出通知中心方案: FRAMEWORK_DESIGN §10.5 + NOTIFICATION_SYSTEM §9 + MEOW_APP_SDK §6.1(M3跨进程: SDK全局状态栏下滑唤出+半程可回+点通知拉app)
This commit is contained in:
MeowOS committed 2026-08-30 17:01:47 +08:00
1 parent 2b17f69f24
commit a8ec8377ed
7 files changed
+542 -59

No files matched your search

+114
View File
@@ -96,6 +96,18 @@ static void test_pure_logic(void)
CHECK(mnp_slider_val_from_x(0, 20, 440, 1, 100) == 1, "slider x out-left clamped to min");
CHECK(mnp_slider_val_from_x(999, 20, 440, 1, 100) == 100, "slider x out-right clamped to max");
/* mnp_step_round:跳步吸附(音量滑条去抖/减少逐值写 DAC) */
CHECK(mnp_step_round(37, 1) == 37, "step_round step=1 -> unchanged (continuous, same as settings slider)");
CHECK(mnp_step_round(0, 1) == 0, "step_round step=1 -> 0 kept");
CHECK(mnp_step_round(37, 5) == 35, "step_round 37 step=5 -> 35 (nearest multiple of 5)");
CHECK(mnp_step_round(38, 5) == 40, "step_round 38 step=5 -> 40 (rounds up to nearest)");
CHECK(mnp_step_round(50, 5) == 50, "step_round 50 step=5 -> exactly on step");
CHECK(mnp_step_round(0, 5) == 0, "step_round 0 step=5 -> 0");
CHECK(mnp_step_round(100, 5) == 100, "step_round 100 step=5 -> 100");
CHECK(mnp_step_round(99, 5) == 100, "step_round 99 step=5 -> 100 (rounds up to max)");
CHECK(mnp_step_round(2, 4) == 4, "step_round 2 step=4 -> 4 (round to nearest multiple)");
CHECK(mnp_step_round(-3, 5) == 0, "step_round negative clamps to 0 (value domain >=0)");
/* mnp_brightness_clamp */
CHECK(mnp_brightness_clamp(0, 1) == 1, "brightness clamp below min -> min");
CHECK(mnp_brightness_clamp(150, 1) == 100, "brightness clamp above 100 -> 100");
@@ -126,6 +138,106 @@ static void test_pure_logic(void)
CHECK(mnp_scroll_clamp(5, 400, 444) == 0, "content fits view -> 0");
}
/* P4 通知列表滚动:方向映射(input.swipe_invert)+ 回弹判定(纯逻辑,header-only)。
* 语义与设置页列表一致:invert=0 手指上滑(d<0) → target 增大(内容上滚看后面项);
* invert=1(反向)手指上滑 → target 减小。进滚动锁 base 保证触发瞬间不跳变。 */
static void test_scroll_pure(void)
{
fprintf(stderr, "--- P4 notification list scroll (swipe_invert direction + rebound) ---\n");
int total = 2000, view = 444, max_scroll = total - view; /* 1556 */
/* mnp_scroll_max */
CHECK(mnp_scroll_max(2000, 444) == 1556, "scroll_max overflow -> total-view");
CHECK(mnp_scroll_max(400, 444) == 0, "scroll_max content fits -> 0");
CHECK(mnp_scroll_max(444, 444) == 0, "scroll_max equal -> 0");
CHECK(mnp_scroll_max(445, 444) == 1, "scroll_max 1px over -> 1");
/* mnp_scroll_base:进滚动锁基准(invert=0 同设置页 `scroll + d`;invert=1 反向) */
CHECK(mnp_scroll_base(100, -45, 0) == 55, "base invert=0 finger-up -> scroll+d");
CHECK(mnp_scroll_base(100, -45, 1) == 145, "base invert=1 finger-up -> scroll-d");
/* mnp_scroll_target:触发瞬间 target 回到原 scroll(连续跟手,不跳变) */
CHECK(mnp_scroll_target(55, -45, 0) == 100, "target invert=0 at engage -> back to scroll");
CHECK(mnp_scroll_target(145, -45, 1) == 100, "target invert=1 at engage -> back to scroll");
/* 方向映射:invert=0 手指持续上滑(更负) → target 增大;invert=1 → target 减小 */
CHECK(mnp_scroll_target(55, -200, 0) == 255, "target invert=0 finger-up more -> increases");
CHECK(mnp_scroll_target(145, -200, 1) == -55, "target invert=1 finger-up more -> decreases");
CHECK(mnp_scroll_target(55, 200, 0) == -145, "target invert=0 finger-down -> decreases");
CHECK(mnp_scroll_target(145, 200, 1) == 345, "target invert=1 finger-down -> increases");
/* clamp 落位:进滚动 + 拖到边界 → 钳到 [0,max_scroll] */
CHECK(mnp_scroll_clamp(mnp_scroll_target(55, -200, 0), total, view) == 255, "clamp invert=0 up -> in range");
CHECK(mnp_scroll_clamp(mnp_scroll_target(145, -200, 1), total, view) == 0, "clamp invert=1 up -> bottom(0)");
CHECK(mnp_scroll_clamp(mnp_scroll_target(55, 200, 0), total, view) == 0, "clamp invert=0 down -> top(0)");
/* mnp_scroll_rebound:边内容差 tol=48;界内不触发,越过才触发 */
CHECK(mnp_scroll_rebound(0, max_scroll, 48) == 0, "rebound at top(0) in-tol -> 0");
CHECK(mnp_scroll_rebound(-47, max_scroll, 48) == 0, "rebound top overshoot 47<48 -> 0");
CHECK(mnp_scroll_rebound(-49, max_scroll, 48) == 1, "rebound top overshoot 49>48 -> 1");
CHECK(mnp_scroll_rebound(max_scroll, max_scroll, 48) == 0, "rebound at bottom in-tol -> 0");
CHECK(mnp_scroll_rebound(max_scroll + 47, max_scroll, 48) == 0, "rebound bottom overshoot 47<48 -> 0");
CHECK(mnp_scroll_rebound(max_scroll + 49, max_scroll, 48) == 1, "rebound bottom overshoot 49>48 -> 1");
CHECK(mnp_scroll_rebound(500, max_scroll, 48) == 0, "rebound mid-range -> 0");
}
/* P3 开关平移动画状态机:p 推进/翻转/中断、offset 计算、裁切边界、关闭到 0 才退出。
* 用任意 fix/ph 参数化(默认 1024/692,通知中心面板 PH=720-28=692);纯函数不依赖 fb。 */
static void test_anim_pure(void)
{
fprintf(stderr, "--- P3 open/close slide animation state machine ---\n");
enum { FIX = 1024, PH = 692 };
/* mnp_anim_off:p→offset 线性映射(∈[-PH,0];p=0 全隐藏 / p=FIX 到位) */
CHECK(mnp_anim_off(0, FIX, PH) == -PH, "anim_off p=0 -> -PH (fully hidden above)");
CHECK(mnp_anim_off(FIX, FIX, PH) == 0, "anim_off p=FIX -> 0 (settled open)");
CHECK(mnp_anim_off(FIX / 2, FIX, PH) == -(PH / 2), "anim_off mid -> -PH/2");
/* mnp_anim_step:打开推进(0→FIX) */
CHECK(mnp_anim_step(0, FIX, FIX, 180, 180) == FIX, "step dt=dur -> reaches FIX in one tick");
CHECK(mnp_anim_step(0, FIX, FIX, 90, 180) == FIX / 2, "step dt=dur/2 -> half progress");
CHECK(mnp_anim_step(0, FIX, FIX, 10, 180) >= 1, "step small dt -> at least 1 (monotonic, no stall)");
CHECK(mnp_anim_step(0, FIX, FIX, 0, 180) == 0, "step dt=0 -> no move");
CHECK(mnp_anim_step(0, FIX, FIX, 100000, 180) == FIX, "step huge dt -> clamped at FIX");
/* 反向推进(关闭:FIX→0,线性步进率 = dt*fix/dur,非剩余比例) */
CHECK(mnp_anim_step(FIX, 0, FIX, 90, 180) == FIX / 2, "step close dt=dur/2 -> half back");
CHECK(mnp_anim_step(FIX / 2, 0, FIX, 45, 180) == FIX / 4, "step reversal mid -> toward 0 linearly");
CHECK(mnp_anim_step(FIX / 4, 0, FIX, 100000, 180) == 0, "step reversal huge dt -> clamped at 0");
/* 打断(打开中→关闭→再打开):平滑转向,不跳变到目标 */
int p = mnp_anim_step(0, FIX, FIX, 90, 180); /* 打开到半程 (512) */
int p_before = p;
p = mnp_anim_step(p, 0, FIX, 30, 180); /* 打断:转为关闭,小步推进 */
CHECK(p < p_before && p > 0, "interrupt open->close: heading to 0 (not target), mid value ok");
int s2 = 0;
while (s2++ < 1000 && p != 0) p = mnp_anim_step(p, 0, FIX, 30, 180);
CHECK(p == 0, "interrupt close completes at 0");
p = mnp_anim_step(p, FIX, FIX, 180, 180); /* 关闭到位后再打开 */
CHECK(p == FIX, "reopen after interrupt -> settled open");
/* mnp_anim_covered_bot:裁切边界(off∈[-PH,0] → bot∈[top, top+PH]) */
CHECK(mnp_anim_covered_bot(28, PH, -PH) == 28, "covered_bot fully hidden -> top (statusbar bottom)");
CHECK(mnp_anim_covered_bot(28, PH, 0) == 28 + PH, "covered_bot settled -> top+PH");
CHECK(mnp_anim_covered_bot(28, PH, -(PH / 2)) == 28 + PH / 2, "covered_bot halfway -> top+PH/2");
CHECK(mnp_anim_covered_bot(28, PH, 0) <= 720 && mnp_anim_covered_bot(28, PH, -PH) >= 28,
"covered_bot within [28,720] — 不盖状态栏、不出屏底");
/* 完整关闭:多次 tick 才到 0;单调不增;且只在 p==0 才算退出 */
p = FIX;
int steps = 0, prev = p, monotonic = 1;
while (steps++ < 100000 && p != 0) {
int np = mnp_anim_step(p, 0, FIX, 10, 180);
if (np > prev) monotonic = 0; /* 关闭方向必须单调减小 */
if (np < 0 || np > FIX) monotonic = 0;
p = np; prev = np;
}
CHECK(p == 0, "close animation terminates at p=0 (exit condition)");
CHECK(steps > 1, "close takes multiple ticks (smooth sliding, not a jump)");
CHECK(monotonic, "close progress monotonic non-increasing");
CHECK(mnp_anim_step(0, 0, FIX, 10, 180) == 0, "at p==0 with target 0 stays put (would break)");
}
static void test_storage_contract(void)
{
fprintf(stderr, "--- P0 storage contract (panel reads) ---\n");
@@ -227,6 +339,8 @@ int main(void)
meow_notif_set_tf_max(1024 * 1024);
test_pure_logic();
test_scroll_pure();
test_anim_pure();
test_storage_contract();
unlink(g_tflog); rmdir(dir);