Merge branch 'feat/tuku-open-book-and-back-gesture-fixes'
This commit is contained in:
commit
e6a9e53ae9
7 files changed
+377
-13
No files matched your search
+3
-1
@@ -4,7 +4,9 @@
|
||||
* - main 只做 meow_ui_init → 建 view → meow_ui_run → 清理;退出走 meow_ui_request_exit;
|
||||
* - 页面切换统一走 meow_view_page_push/pop(activity 栈 + on_enter/on_leave 唯一入口);
|
||||
* - 物理键不映射返回/确认(on_key 仅日志);屏底上滑由 SDK 系统手势接管,App 不拦截;
|
||||
* - 左缘返回:书库内逐级上移,根目录才退出。
|
||||
* - 左缘返回:reader/settings=pop;lock 页自行消费;书库逐级上移,
|
||||
* **TF 根=退出 App**(系统返回语义,不再上溯系统目录);TF 根以外的上级浏览
|
||||
* 走顶栏显式「上级」动作。
|
||||
*
|
||||
* 主题同步:注册 meow_theme_add_listener;明暗/风格族切换时更新页面底色并整帧 invalidate,
|
||||
* 各页 on_draw / SDK 控件下一帧即用新语义色重绘(不缓存颜色)。
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
# - test_settings_lock_host(可选):设置页/锁屏与 SDK view/activity/widget 接线的宿主集成
|
||||
# 验证(owner 归属 + 锁屏 SWIPE 拦截)。需要 SDK **源码树**(不只 meow_ui.a);
|
||||
# 交付环境只有库+头时自动 SKIP。
|
||||
# - test_open_book_host(可选):书库点击「封面图 + 含图子目录」的父目录 → 阅读器打开的
|
||||
# 完整 UI 链路回归(合成夹具,不读真实相册);同样需要 SDK 源码树。
|
||||
# reader.c 的方向持久化只 include 配置/日志两个模块头;宿主测试用 test/settings_reader_stub.c
|
||||
# 顶替 meow_config_* 与 meow_log(所以下面各条都带 -I"$SDK" 与该存根)。
|
||||
# logic/library.c 的探测缓存用 pthread 互斥锁 → 相关编译带 -pthread。
|
||||
@@ -87,6 +89,24 @@ if [ -f "$SDK/meow_ui_view.c" ] && [ -f "$SDK/meow_ui_config.c" ]; then
|
||||
"$SDK"/meow_ui_nav.c "$SDK"/stb_image.c "$SDK"/test_host_stub.c \
|
||||
-pthread -lm -lz -o "$OUT"/test_browse_ui_host
|
||||
"$OUT"/test_browse_ui_host
|
||||
|
||||
# 点击父目录(封面图 + 含图子目录)→ 阅读器打开:完整 UI 链路回归。
|
||||
gcc -Wall -Wextra -O1 -DMEOW_UI_HOST_TEST -DMR_READER_HOST_TEST \
|
||||
-DMR_LIBRARY_UI_TEST_HOOKS -I. -I"$SDK" -I"$SDK/include" \
|
||||
test/test_open_book_host.c ui/library/library.c ui/reader/reader.c \
|
||||
logic/library.c logic/reader.c logic/reader/page_source.c \
|
||||
logic/reader/archive_source.c logic/reader/page_cache.c logic/zip/zip.c \
|
||||
logic/reader/nav.c \
|
||||
"$SDK"/meow_ui_gfx.c "$SDK"/meow_ui_sys.c "$SDK"/meow_ui_theme.c \
|
||||
"$SDK"/meow_ui_vol.c "$SDK"/meow_ui_log.c "$SDK"/meow_ui_widget.c \
|
||||
"$SDK"/meow_ui_input.c "$SDK"/meow_ui_activity.c "$SDK"/meow_ui_dialog.c \
|
||||
"$SDK"/meow_ui_ime.c "$SDK"/meow_ui_str.c "$SDK"/meow_pinyin.c \
|
||||
"$SDK"/meow_ui_draw.c "$SDK"/meow_ui_view.c "$SDK"/meow_ui_box.c \
|
||||
"$SDK"/meow_ui_anim.c "$SDK"/meow_ui_scroll.c "$SDK"/meow_ui_img.c \
|
||||
"$SDK"/meow_ui_media.c "$SDK"/meow_notify_ipc.c "$SDK"/meow_ui_config.c \
|
||||
"$SDK"/meow_ui_nav.c "$SDK"/stb_image.c "$SDK"/test_host_stub.c \
|
||||
-pthread -lm -lz -o "$OUT"/test_open_book_host
|
||||
"$OUT"/test_open_book_host
|
||||
else
|
||||
echo "SKIP test_settings_lock_host/test_browse_ui_host: SDK 源码树不可用(只发布 meow_ui.a 的环境)"
|
||||
fi
|
||||
|
||||
@@ -39,6 +39,8 @@ int library_test_hint_active(void);
|
||||
const char *library_test_hint(void);
|
||||
int library_test_go_root(void);
|
||||
int library_test_go_up(void);
|
||||
int library_test_go_parent(void);
|
||||
int library_test_drag_active(void);
|
||||
int library_test_has_list(void);
|
||||
const char *library_test_cache_dir(void);
|
||||
int library_test_cache_on(void);
|
||||
@@ -336,6 +338,30 @@ int main(void)
|
||||
CHECK(library_test_go_up() == 0, "go_up at / returns 0 (app exits)");
|
||||
CHECK(strcmp(library_test_dir(), "/") == 0, "still at / after exit gesture");
|
||||
|
||||
/* ---- U3b:系统返回语义 = TF 根退出 App;顶栏「上级」保留越界浏览 ---- */
|
||||
CHECK(library_test_load(root) == 1, "load fixture root");
|
||||
CHECK(library_test_go_up() == 0, "go_up at TF root returns 0 (app exits)");
|
||||
CHECK(strcmp(library_test_dir(), root) == 0, "still at TF root after exit gesture");
|
||||
|
||||
/* 顶栏显式「上级」:访问放开,TF 根 → 其父目录(fixture root 的 parent = data)。 */
|
||||
CHECK(library_test_go_parent() == 1, "explicit up from TF root consumed");
|
||||
CHECK(strcmp(library_test_dir(), data) == 0,
|
||||
"explicit up goes above TF root (%s)", library_test_dir());
|
||||
CHECK(library_test_load(root) == 1, "back to fixture root");
|
||||
|
||||
/* ---- U3c:左缘 24px 系统返回手势带不启动列表拖拽 ---- */
|
||||
int row_y = list_top + 28;
|
||||
library_on_touch_down(120, row_y);
|
||||
CHECK(library_test_drag_active() == 1, "touch inside list starts drag");
|
||||
library_on_touch_down(5, row_y);
|
||||
CHECK(library_test_drag_active() == 0, "left-edge touch does not start drag");
|
||||
library_on_touch_down(23, row_y);
|
||||
CHECK(library_test_drag_active() == 0, "left-edge (23px) does not start drag");
|
||||
library_on_touch_down(24, row_y);
|
||||
CHECK(library_test_drag_active() == 1, "just outside left band starts drag");
|
||||
library_on_touch_down(120, meow_ui_height() - 4);
|
||||
CHECK(library_test_drag_active() == 0, "bottom hotzone does not start drag");
|
||||
|
||||
/* ---- U4:不可读目录 → 停留 + 提示 ---- */
|
||||
CHECK(library_test_load(root) == 1, "back to fixture root");
|
||||
char noread[MR_PATH_MAX];
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/* test_open_book_host.c — 书库点击 → 阅读器打开的宿主集成回归(需 SDK 源码树)。
|
||||
*
|
||||
* 复现用户报告:「父文件夹有封面图片、子文件夹也有图片,点开父文件夹报无法打开此册」。
|
||||
* 用合成夹具(真实 BMP 当封面/子图,不读用户相册)走完整链路:
|
||||
* library 列表 → 行点击 → mr_reader_open_dir → reader on_enter → mr_book_open_auto
|
||||
* 并断言父目录以「书」打开(count=1)且没有进入 open_err。
|
||||
*
|
||||
* 另覆盖:封面图很大时打开仍成功(打开只枚举,不解码);同目录二次打开/离开重开。
|
||||
*
|
||||
* 编译:见 test/run_tests.sh(与 test_mode_switch_host 同口径)。
|
||||
*/
|
||||
#include "tuku_internal.h"
|
||||
#include "ui/reader/reader.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ui/library/library.c 的测试接缝 */
|
||||
int library_test_load(const char *dir);
|
||||
void library_test_set_root(const char *root);
|
||||
const char *library_test_dir(void);
|
||||
int library_test_rows(void);
|
||||
int library_test_src(int idx, meow_list_item *item);
|
||||
int library_test_worker_on(void);
|
||||
|
||||
/* ui/reader/reader.c 的测试接缝(-DMR_READER_HOST_TEST) */
|
||||
int mr_reader_ui_mode(void);
|
||||
int mr_reader_ui_page(void);
|
||||
int mr_reader_ui_count(void);
|
||||
int mr_reader_ui_open_err(void);
|
||||
|
||||
static int g_fail;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { printf(" PASS\n"); } \
|
||||
else { g_fail++; printf(" FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
void mr_nav_report(void) {}
|
||||
|
||||
static void put16(unsigned char *p, unsigned v)
|
||||
{
|
||||
p[0] = (unsigned char)v;
|
||||
p[1] = (unsigned char)(v >> 8);
|
||||
}
|
||||
static void put32(unsigned char *p, unsigned v)
|
||||
{
|
||||
p[0] = (unsigned char)v; p[1] = (unsigned char)(v >> 8);
|
||||
p[2] = (unsigned char)(v >> 16); p[3] = (unsigned char)(v >> 24);
|
||||
}
|
||||
|
||||
static int write_bmp24(const char *path, int w, int h)
|
||||
{
|
||||
int stride = ((w * 3 + 3) / 4) * 4;
|
||||
int imgsz = stride * h;
|
||||
unsigned char fh[14] = { 0 }, ih[40] = { 0 };
|
||||
fh[0] = 'B'; fh[1] = 'M';
|
||||
put32(fh + 2, (unsigned)(54 + imgsz));
|
||||
put32(fh + 10, 54);
|
||||
put32(ih + 0, 40);
|
||||
put32(ih + 4, (unsigned)w);
|
||||
put32(ih + 8, (unsigned)h);
|
||||
put16(ih + 12, 1);
|
||||
put16(ih + 14, 24);
|
||||
put32(ih + 20, (unsigned)imgsz);
|
||||
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
int ok = fwrite(fh, 1, sizeof fh, f) == sizeof fh &&
|
||||
fwrite(ih, 1, sizeof ih, f) == sizeof ih;
|
||||
unsigned char *row = (unsigned char *)calloc(1, (size_t)stride);
|
||||
if (!row) ok = 0;
|
||||
for (int y = 0; ok && y < h; y++)
|
||||
if (fwrite(row, 1, (size_t)stride, f) != (size_t)stride) ok = 0;
|
||||
free(row);
|
||||
if (fclose(f) != 0) ok = 0;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int mkdir_p(const char *path)
|
||||
{
|
||||
char tmp[MR_PATH_MAX];
|
||||
snprintf(tmp, sizeof tmp, "%s", path);
|
||||
for (size_t i = 1; i <= strlen(tmp); i++) {
|
||||
if (tmp[i] == '/' || tmp[i] == '\0') {
|
||||
char ch = tmp[i];
|
||||
tmp[i] = '\0';
|
||||
if (mkdir(tmp, 0755) != 0) {
|
||||
struct stat st;
|
||||
if (stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)) return -1;
|
||||
}
|
||||
tmp[i] = ch;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rm_rf(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
if (lstat(path, &st) != 0) return -1;
|
||||
if (!S_ISDIR(st.st_mode)) return unlink(path);
|
||||
DIR *d = opendir(path);
|
||||
if (!d) return -1;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d)) != NULL) {
|
||||
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue;
|
||||
char full[MR_PATH_MAX];
|
||||
snprintf(full, sizeof full, "%s/%s", path, de->d_name);
|
||||
rm_rf(full);
|
||||
}
|
||||
closedir(d);
|
||||
return rmdir(path);
|
||||
}
|
||||
|
||||
static void wait_ready(int ms)
|
||||
{
|
||||
for (int t = 0; t < ms / 10; t++) {
|
||||
library_on_tick();
|
||||
int ready = 1;
|
||||
int rows = library_test_rows();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
meow_list_item it;
|
||||
if (!library_test_src(i, &it)) continue;
|
||||
if (it.subtitle && strcmp(it.subtitle, "…") == 0) { ready = 0; break; }
|
||||
}
|
||||
if (ready) return;
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static int row_index_for(const char *name)
|
||||
{
|
||||
int rows = library_test_rows();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
meow_list_item it;
|
||||
if (!library_test_src(i, &it)) continue;
|
||||
if (it.title && strcmp(it.title, name) == 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 用真实点击坐标点某一行(列表顶端 = 状态栏 + 56px 顶栏,行高 56)。 */
|
||||
static void tap_row(const char *name)
|
||||
{
|
||||
int idx = row_index_for(name);
|
||||
int list_top = meow_ui_statusbar_height() + 56;
|
||||
library_on_tap(120, list_top + idx * 56 + 28);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const char *base = getenv("TMPDIR");
|
||||
if (!base || !base[0]) base = ".";
|
||||
char tmpl[512];
|
||||
snprintf(tmpl, sizeof tmpl, "%s/mr_open_host_XXXXXX", base);
|
||||
char *data = mkdtemp(tmpl);
|
||||
if (!data) { printf("SKIP: mkdtemp failed\n"); return 0; }
|
||||
setenv("MEOW_DATA_DIR", data, 1);
|
||||
|
||||
char root[MR_PATH_MAX], parent[MR_PATH_MAX], sub[MR_PATH_MAX], p[MR_PATH_MAX];
|
||||
snprintf(root, sizeof root, "%s/library", data);
|
||||
snprintf(parent, sizeof parent, "%s/Pictures", root);
|
||||
if (mkdir_p(parent) != 0) { printf("SKIP: mkdir failed\n"); return 0; }
|
||||
|
||||
/* 父目录:一张封面图 + 两个含图子目录(用户报告的结构)。 */
|
||||
snprintf(p, sizeof p, "%s/cover.jpg", parent); /* 扩展名即可;打开只枚举不解码 */
|
||||
FILE *f = fopen(p, "wb");
|
||||
if (!f) { printf("SKIP: cover write failed\n"); return 0; }
|
||||
fputs("x", f);
|
||||
fclose(f);
|
||||
snprintf(sub, sizeof sub, "%s/2023", parent);
|
||||
mkdir_p(sub);
|
||||
snprintf(p, sizeof p, "%s/a.bmp", sub);
|
||||
write_bmp24(p, 40, 60);
|
||||
snprintf(sub, sizeof sub, "%s/2024", parent);
|
||||
mkdir_p(sub);
|
||||
snprintf(p, sizeof p, "%s/b.bmp", sub);
|
||||
write_bmp24(p, 40, 60);
|
||||
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("SKIP: meow_ui_init failed (no host backend)\n"); return 0; }
|
||||
|
||||
library_test_set_root(root);
|
||||
meow_view *lib = library_view_create();
|
||||
meow_view *reader = reader_view_create();
|
||||
meow_view_page_push(ACT_LIBRARY);
|
||||
|
||||
CHECK(library_test_load(root) == 1, "load fixture root");
|
||||
wait_ready(3000);
|
||||
|
||||
int ip = row_index_for("Pictures");
|
||||
meow_list_item it;
|
||||
CHECK(ip >= 0 && library_test_src(ip, &it), "Pictures row present");
|
||||
CHECK(it.subtitle && strcmp(it.subtitle, "1 page") == 0,
|
||||
"Pictures probed as book with direct cover (subtitle=%s)",
|
||||
it.subtitle ? it.subtitle : "");
|
||||
|
||||
/* 点击父目录 → 进阅读器;cover 是唯一直接子图 → count=1,不能是 open_err。 */
|
||||
tap_row("Pictures");
|
||||
CHECK(meow_ui_activity_active() == ACT_READER, "tap opens reader");
|
||||
CHECK(strcmp(mr_reader_dir(), parent) == 0, "reader dir=%s", mr_reader_dir());
|
||||
CHECK(mr_reader_ui_open_err() == 0, "parent folder opens without open_err");
|
||||
CHECK(mr_reader_ui_count() == 1, "parent book pages=%d (want 1)",
|
||||
mr_reader_ui_count());
|
||||
CHECK(library_test_worker_on() == 0,
|
||||
"library background decode stopped while reader is active");
|
||||
for (int i = 0; i < 30; i++) { usleep(10 * 1000); }
|
||||
CHECK(mr_reader_ui_open_err() == 0, "still no open_err after decode tick");
|
||||
|
||||
/* 返回书库 → 后台线程恢复,再次点开(同目录二次打开 + 续读恢复)。 */
|
||||
meow_view_page_pop();
|
||||
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "back to library");
|
||||
CHECK(library_test_worker_on() == 1, "library background decode restarted on return");
|
||||
tap_row("Pictures");
|
||||
CHECK(mr_reader_ui_open_err() == 0, "reopen parent without open_err");
|
||||
CHECK(mr_reader_ui_count() == 1, "reopen pages=%d", mr_reader_ui_count());
|
||||
meow_view_page_pop();
|
||||
|
||||
/* 封面是大图也不影响打开(打开只枚举文件名)。 */
|
||||
snprintf(p, sizeof p, "%s/cover.bmp", parent);
|
||||
CHECK(write_bmp24(p, 400, 600) == 0, "write large cover");
|
||||
CHECK(library_test_load(root) == 1, "reload after cover change");
|
||||
wait_ready(3000);
|
||||
tap_row("Pictures");
|
||||
CHECK(mr_reader_ui_open_err() == 0, "open with large cover has no open_err");
|
||||
CHECK(mr_reader_ui_count() == 2, "cover.jpg + cover.bmp pages=%d (want 2)",
|
||||
mr_reader_ui_count());
|
||||
meow_view_page_pop();
|
||||
|
||||
meow_view_free(reader);
|
||||
meow_view_free(lib);
|
||||
meow_ui_shutdown();
|
||||
rm_rf(data);
|
||||
printf("%s: %s\n", g_fail ? "FAIL" : "PASS",
|
||||
g_fail ? "see failures above" : "library→reader open checks passed");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -38,7 +38,8 @@ typedef struct mr_page_ctrl {
|
||||
/* ---- 书库页(ui/library/library.c) ---- */
|
||||
meow_view *library_view_create(void);
|
||||
int library_at_root(void); /* 1=当前在书库根(生产=MR_ROOT_DIR,宿主测试可改根) */
|
||||
int library_go_up(void); /* 上溯父目录:1=已上移/提示停留,0=已在文件系统 "/"(app.c 据此退出) */
|
||||
int library_go_up(void); /* 系统返回语义:逐级上移;TF 根/"/" 返回 0(app.c 据此退出) */
|
||||
int library_go_parent(void); /* 顶栏显式「上级」:访问放开,可越过 TF 根到 '/' */
|
||||
void library_on_tap(int x, int y);
|
||||
void library_on_swipe(int dir, int dist); /* dist=SDK on_swipe 的位移量(UP/DOWN 用) */
|
||||
void library_on_touch_down(int x, int y);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* - 首帧只做**单层**枚举(logic/library.c:一次 opendir/readdir+lstat),
|
||||
* 不做任何递归「子树含图」探测;列表行 = 当前目录的直接子目录(M11 起还包括
|
||||
* .cbz/.zip 档案册,与目录自然序混排);
|
||||
* - 系统目录/空目录/无图目录都列出且可进入;可一路向上到文件系统 '/',
|
||||
* 左缘返回由 app.c 逐级上溯、到 '/' 才退出 App;
|
||||
* - 系统目录/空目录/无图目录都列出且可进入;左缘返回按系统语义逐级上移,
|
||||
* **到 TF 根即退出 App**(不再上溯系统目录);TF 根以外的上级浏览走顶栏「上级」动作;
|
||||
* - 「直接含图目录=一本书」与档案册页数改为后台懒算:行仍在 PENDING 时副标题「…」,
|
||||
* 后台线程用 mr_library_probe_path(单层 + mtime/size 失效缓存)算出页数/首图,
|
||||
* on_tick 补位后只 invalidate 列表视口——首帧/滚动/命中判定都不读子目录/档案;
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#define MR_THUMB_MAX 24
|
||||
#define MR_THUMB_DIM 96
|
||||
#define MR_THUMB_DECODE_DIM 256 /* 缩略图源图预算解码边长(峰值 ~1MB;整幅解码可达数十 MB) */
|
||||
#define MR_TOPAPPBAR_H 56
|
||||
#define MR_DEFAULT_ROW_H 56
|
||||
#define MR_CACHE_MAX_FILES 64
|
||||
@@ -294,7 +295,10 @@ static meow_img *thumb_load(const char *cover)
|
||||
}
|
||||
}
|
||||
|
||||
meow_img *im = meow_img_load_file(src);
|
||||
/* 预算解码:封面可能是 12MP 手机照片,整幅解码峰值 ~48MB,会在低内存设备上
|
||||
* 挤掉随后 reader 打开所需的 opendir/malloc(用户报告「点开父文件夹=无法打开此册」)。
|
||||
* 缩略图最终只要 96px,先解到 ≤MR_THUMB_DECODE_DIM 再精修,视觉无损、峰值 ~1MB。 */
|
||||
meow_img *im = meow_img_load_file_dim(src, MR_THUMB_DECODE_DIM);
|
||||
if (!im) return NULL;
|
||||
meow_img *small = meow_img_downscale(im, MR_THUMB_DIM);
|
||||
if (small) { meow_img_free(im); im = small; }
|
||||
@@ -559,7 +563,8 @@ static void update_topbar(void)
|
||||
if (slash && slash[1]) title = slash + 1;
|
||||
}
|
||||
meow_topappbar_set_title(g_tb, title);
|
||||
/* 可一路向上到文件系统 '/':到 '/' 才隐藏返回(此时左缘返回=退出 App)。 */
|
||||
/* 顶栏「上级」是显式浏览动作(访问放开,可越过 TF 根上溯到 '/');
|
||||
* 左缘返回是系统返回语义,TF 根=退出 App(见 library_go_up)。到 '/' 才隐藏。 */
|
||||
meow_topappbar_set_back(g_tb, strcmp(g_cur_dir, "/") != 0);
|
||||
}
|
||||
|
||||
@@ -640,7 +645,8 @@ static int load_dir(const char *dir)
|
||||
char msg[200];
|
||||
snprintf(msg, sizeof msg, "无法读取目录:%s", name);
|
||||
show_hint(msg);
|
||||
meow_log(MEOW_LOG_WARN, "library scan failed: %s", target);
|
||||
meow_log(MEOW_LOG_WARN, "library scan failed: %s (errno=%d %s)", target,
|
||||
errno, strerror(errno));
|
||||
/* 启动首帧根目录不可读:仍建一个空列表,页面可交互(返回/回根/上溯)。 */
|
||||
if (!g_lv) {
|
||||
mr_library_free(&g_lib);
|
||||
@@ -678,7 +684,17 @@ int library_at_root(void)
|
||||
|
||||
int library_go_up(void)
|
||||
{
|
||||
if (strcmp(g_cur_dir, "/") == 0) return 0; /* 到文件系统根:交给 app.c 退出 */
|
||||
/* 系统返回语义:TF 根 = 退出 App(app.c 收到 0 后 request_exit),
|
||||
* 不再上溯系统目录;文件系统 "/" 同理。 */
|
||||
if (strcmp(g_cur_dir, g_root_dir) == 0) return 0;
|
||||
if (strcmp(g_cur_dir, "/") == 0) return 0;
|
||||
return library_go_parent();
|
||||
}
|
||||
|
||||
/* 顶栏「上级」:显式浏览动作,访问放开——可越过 TF 根一路到 '/'。 */
|
||||
int library_go_parent(void)
|
||||
{
|
||||
if (strcmp(g_cur_dir, "/") == 0) return 0;
|
||||
char parent[MR_PATH_MAX];
|
||||
if (mr_path_parent(g_cur_dir, parent, (int)sizeof parent) != 0) return 0;
|
||||
if (!load_dir(parent)) {
|
||||
@@ -719,10 +735,13 @@ static void library_open_entry(int idx)
|
||||
|
||||
if (e->probe_state == MR_LIB_PENDING) {
|
||||
mr_library_probe p;
|
||||
if (mr_library_probe_path(e->path, e->is_archive, cbz_archive_pages, NULL, &p) == 0)
|
||||
if (mr_library_probe_path(e->path, e->is_archive, cbz_archive_pages, NULL, &p) == 0) {
|
||||
mr_library_apply_probe(&g_lib, idx, &p);
|
||||
else
|
||||
} else {
|
||||
meow_log(MEOW_LOG_WARN, "library probe failed: %s (errno=%d %s)", e->path,
|
||||
errno, strerror(errno));
|
||||
mr_library_mark_error(&g_lib, idx);
|
||||
}
|
||||
list_invalidate();
|
||||
}
|
||||
if (e->probe_state == MR_LIB_FAILED) {
|
||||
@@ -752,7 +771,7 @@ void library_on_tap(int x, int y)
|
||||
|
||||
if (g_tb) {
|
||||
int hit = meow_topappbar_hit(g_tb, x, y);
|
||||
if (hit == MEOW_TOPBAR_BACK) { library_go_up(); return; }
|
||||
if (hit == MEOW_TOPBAR_BACK) { library_go_parent(); return; }
|
||||
if (g_tb_home_idx >= 0 && hit == g_tb_home_idx) { library_go_tf_root(); return; }
|
||||
if (hit >= 0) { meow_view_page_push(ACT_SETTINGS); return; }
|
||||
}
|
||||
@@ -788,8 +807,14 @@ void library_on_swipe(int dir, int dist)
|
||||
void library_on_touch_down(int x, int y)
|
||||
{
|
||||
if (!g_ctrl.active || !g_lv) return;
|
||||
/* 每次按下都作废上一次手势的拖拽状态:热区/左缘提前返回时不能残留 g_drag,
|
||||
* 否则系统返回手势的 move 仍会驱动列表滚动。 */
|
||||
g_drag = 0;
|
||||
g_dragged = 0;
|
||||
/* 屏底 28px 是系统「上滑回桌面」热区:不从这里启动列表拖拽,避免拦截系统手势。 */
|
||||
if (y >= meow_ui_height() - 28) return;
|
||||
/* 左缘 24px 是系统返回手势带(与 reader 同口径):列表不启动拖拽,把滑动留给系统返回。 */
|
||||
if (x < 24) return;
|
||||
if (x < g_list_x || x >= g_list_x + g_list_w ||
|
||||
y < g_list_y || y >= g_list_y + g_list_h) return;
|
||||
g_drag = 1;
|
||||
@@ -911,6 +936,7 @@ static void library_page_enter(meow_view *v, void *ctx)
|
||||
hint_place();
|
||||
g_drag = 0;
|
||||
g_dragged = 0;
|
||||
worker_start(); /* 离开时停止(见 leave),回页恢复探测/缩略图 */
|
||||
update_topbar();
|
||||
mr_nav_report();
|
||||
meow_ui_paint_full();
|
||||
@@ -923,6 +949,10 @@ static void library_page_leave(meow_view *v, void *ctx)
|
||||
g_drag = 0;
|
||||
g_dragged = 0;
|
||||
if (g_scroll) meow_scroll_stop(g_scroll);
|
||||
/* 停止后台解码:进 reader 前释放/停止潜在的大图解码,避免与 open_book 的
|
||||
* opendir/malloc 抢内存(设备上「点开父文件夹=无法打开此册」的诱因之一)。
|
||||
* worker_stop 会 join,保证 page_push 的 on_enter 打开册时线程已退出。 */
|
||||
worker_stop();
|
||||
}
|
||||
|
||||
static void library_page_destroy(meow_view *v, void *ctx)
|
||||
@@ -1012,6 +1042,23 @@ int library_test_go_up(void)
|
||||
return library_go_up();
|
||||
}
|
||||
|
||||
int library_test_go_parent(void)
|
||||
{
|
||||
return library_go_parent();
|
||||
}
|
||||
|
||||
/* 跟手拖拽是否在本次手势中被启动(左缘/屏底热区排除的回归观测口)。 */
|
||||
int library_test_drag_active(void)
|
||||
{
|
||||
return g_drag;
|
||||
}
|
||||
|
||||
/* 后台探测/缩略图线程是否在跑(离开书库页应停止,回页恢复)。 */
|
||||
int library_test_worker_on(void)
|
||||
{
|
||||
return g_worker_on;
|
||||
}
|
||||
|
||||
/* 缩略图 LRU 接缝(M11 P2-1 回归):指向当前缓存目录并同步跑一次 prune。 */
|
||||
const char *library_test_cache_dir(void)
|
||||
{
|
||||
|
||||
+29
-2
@@ -42,6 +42,7 @@
|
||||
#include "logic/reader/page_cache.h"
|
||||
#include "logic/reader/nav.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -79,6 +80,8 @@ static mr_book_handle g_handle; /* 目录册或档案册(M11 统一开册
|
||||
static mr_book g_book;
|
||||
static int g_count; /* 0=空册/不可读 */
|
||||
static int g_open_err;
|
||||
static uint64_t g_open_retry_at; /* 资源类打开失败(ENOMEM/EMFILE…)的自动重试时刻 */
|
||||
static int g_open_retry_used; /* 1=本次进入已用过自动重试(避免反复重试) */
|
||||
static int g_page; /* 翻页模式当前页;条漫模式=最近落位页 */
|
||||
static int g_mode = MR_MODE_PAGED;
|
||||
|
||||
@@ -1158,6 +1161,7 @@ static void open_book_reset_state(void)
|
||||
g_gen++; /* 作废旧册的异步结果 */
|
||||
g_count = 0;
|
||||
g_open_err = 0;
|
||||
g_open_retry_at = 0;
|
||||
g_page = 0;
|
||||
g_woff = 0;
|
||||
g_zoom_pct = 100;
|
||||
@@ -1179,7 +1183,9 @@ static int open_book(const char *dir, int start_page, int mode)
|
||||
{
|
||||
if (!dir || !dir[0]) return 0;
|
||||
open_book_reset_state();
|
||||
snprintf(g_dir, sizeof g_dir, "%s", dir);
|
||||
/* reader_page_enter 传入的 dir 就是 g_dir:自拷贝是 snprintf 的未定义行为,
|
||||
* 其它调用(册间衔接)才需要复制。 */
|
||||
if (dir != g_dir) snprintf(g_dir, sizeof g_dir, "%s", dir);
|
||||
g_mode = mr_mode_valid(mode) ? mode : MR_MODE_PAGED;
|
||||
|
||||
int maxdim = g_vw > g_vh ? g_vw : g_vh;
|
||||
@@ -1188,10 +1194,19 @@ static int open_book(const char *dir, int start_page, int mode)
|
||||
int n = mr_book_open_auto(&g_book, &g_handle, g_dir, maxdim,
|
||||
reader_load_page, reader_unload_page, NULL);
|
||||
if (n < 0) {
|
||||
int err = errno; /* opendir/malloc 失败原因(设备排障用) */
|
||||
g_open_err = 1;
|
||||
worker_start();
|
||||
update_title();
|
||||
meow_log(MEOW_LOG_WARN, "reader open failed: %s", g_dir);
|
||||
meow_log(MEOW_LOG_WARN, "reader open failed: %s (errno=%d %s)", g_dir, err,
|
||||
strerror(err));
|
||||
/* 资源类失败(内存/FD 瞬时紧张)自动重试一次:library 页已在 on_leave 停止
|
||||
* 后台解码,稍后内存回收后重开即可成功;权限/路径错误不重试。 */
|
||||
if ((err == ENOMEM || err == EMFILE || err == ENFILE ||
|
||||
err == EAGAIN || err == EINTR) && !g_open_retry_used) {
|
||||
g_open_retry_used = 1;
|
||||
g_open_retry_at = meow_ui_time_ms() + 200;
|
||||
}
|
||||
meow_ui_invalidate(0, 0, g_vw, g_vh);
|
||||
return 0;
|
||||
}
|
||||
@@ -1602,6 +1617,15 @@ static void reader_tick(void)
|
||||
int had_current = slot_find(current_page()) != NULL;
|
||||
if (results_drain() && !had_current && slot_find(current_page()) && !g_dragging)
|
||||
meow_ui_invalidate(0, 0, g_vw, g_vh);
|
||||
|
||||
/* 打开册的瞬时资源失败:稍后自动重试一次(进入本页时已停 library 后台解码)。 */
|
||||
if (g_open_err && g_open_retry_at && now >= g_open_retry_at) {
|
||||
g_open_retry_at = 0;
|
||||
int mode = mr_cont_get_mode(g_dir, MR_MODE_PAGED);
|
||||
int page = mr_cont_get_page(g_dir, 0);
|
||||
open_book(g_dir, page, mode);
|
||||
return;
|
||||
}
|
||||
if (g_fail_paint) {
|
||||
g_fail_paint = 0;
|
||||
if (!g_dragging) meow_ui_invalidate(0, 0, g_vw, g_vh);
|
||||
@@ -1712,6 +1736,8 @@ static void reader_state_reset(void)
|
||||
slots_free_all(); /* 静态槽位初值是 idx=0/img=NULL,先清空 */
|
||||
g_count = 0;
|
||||
g_open_err = 0;
|
||||
g_open_retry_at = 0;
|
||||
g_open_retry_used = 0;
|
||||
g_page = 0;
|
||||
g_mode = MR_MODE_PAGED;
|
||||
g_fit = MR_FIT_WIDTH;
|
||||
@@ -1869,6 +1895,7 @@ void mr_reader_open_dir(const char *path)
|
||||
int mr_reader_ui_mode(void) { return g_mode; }
|
||||
int mr_reader_ui_page(void) { return current_page(); }
|
||||
int mr_reader_ui_count(void) { return g_count; }
|
||||
int mr_reader_ui_open_err(void) { return g_open_err; }
|
||||
int mr_reader_ui_jump_open(void) { return g_jump_open; }
|
||||
int mr_reader_ui_jump_pending(void) { return mr_jump_active(&g_jump); }
|
||||
#endif
|
||||
Reference in new issue
Block a user