feat(reader): M33 目录模式逐级下钻 — 子目录/书分层显示(416 本不再平铺)
- folder 模式从「按第一级目录分段」改为当前目录视图:子目录在前(带子树书数), 直接书在后;进入/返回只过滤已扫平的 g_books,不 readdir、不重扫(M32 索引不动)。 - 纯函数 shelf_build_rows_at(books,n,folder,cwd,...) + nav 工具(enter/up/breadcrumb/ count)全部宿主可测;format 模式行序列/副题逐字不变,path 仍是完整路径(进度键不变)。 - 交互:点目录进入;顶栏返回箭头/左缘返回手势回上一级,根层再返回退出 App;顶栏标题= 当前目录名(按像素宽截断),副题行=面包屑全路径+当前层书数;list/grid 均支持导航。 - 状态:folder↔format、list↔grid 切换保留 cwd;刷新重扫后 cwd 失效则收缩到有书的祖先。 - 边界:空目录不显示(导航只读 g_books,不额外 readdir);深层目录逐级进入;真实名为 「未分类」的目录按普通目录处理,不与根层书合并。 - 测试:shelf_group_smoke 63→99 断言;M27 的按第一级分组断言改写为「逐级进入后可见该书」 (同等强度),新增进入/返回/面包屑/计数/空目录/纯内存/rowcap/UTF-8 截断断言。 - 交付 0.1.7/code8(M32 已占用任务书里的 0.1.6/code7);真机 0.1.4 未被覆盖。
This commit is contained in:
1 parent
190041cd67
commit
2881ce908a
7 files changed
+609
-141
No files matched your search
@@ -3,14 +3,15 @@
|
||||
* 从 shelf.c 拆出,供 shelf.c(控件装配)、宿主单测(test/shelf_group_smoke.c)与
|
||||
* 扫描计时(test/shelf_scan_bench.c)共用。
|
||||
*
|
||||
* 目录归属(M27 r2):folder 模式按「Books/ 下的第一级目录」分组(如 "轻小说"、
|
||||
* "400+本高质量完本合集");更深的相对子路径("01、玄幻/01、斗破")进该书的副题,
|
||||
* 不再每组一本书把书架炸成上百个段头。Books/ 根下的书 top="",显示段头 SHELF_GROUP_ROOT。
|
||||
* 分组键是「第一级目录名」,完整相对路径保留在 group[] 供副题/调试用。
|
||||
* a/b 与 a/c 同属 a 组;a/b 与 b 仍是两个组(不会因为只取末级目录名而混)。
|
||||
* 目录视图(M33):folder 模式不再是「按第一级目录分段」,而是**逐级进入**——书架只显示
|
||||
* 当前目录(cwd,相对 Books/,根="")的内容:子目录在前(每个带子树书数)、直接属于该层的
|
||||
* 书在后。cwd 由 App 维护;本模块的 shelf_build_rows_at() 只在已扫平的 books[].group 上做
|
||||
* 前缀过滤,**不读盘、不 readdir**(M32 索引省下的 readdir 不能又被导航用回去)。
|
||||
* M27 的 top(第一级目录名)仍保留在 shelf_book 里供索引/副题/诊断使用,但不再决定行序列。
|
||||
* 完整相对路径保留在 group[]:path 仍是书文件的完整绝对路径。
|
||||
*
|
||||
* 硬约束:path 永远是书的完整路径(打开书 + 进度键 rdprog_hash(path)),分组只写 top/group,
|
||||
* 绝不改写 path;两个分组模式间切换不影响进度。
|
||||
* 硬约束:path 永远是书的完整路径(打开书 + 进度键 rdprog_hash(path)),导航只改 cwd,
|
||||
* 绝不改写 path;format/folder 两个模式间切换不影响进度。
|
||||
*/
|
||||
#ifndef SHELF_MODEL_H
|
||||
#define SHELF_MODEL_H
|
||||
@@ -27,24 +28,29 @@
|
||||
#define SHELF_SCAN_MAX_DEPTH 6
|
||||
/* 行数上限:每本书最多贡献 1 行书 + 1 行段头(段头数 ≤ 书数),故 2N 必够。 */
|
||||
#define SHELF_MAX_ROWS (SHELF_MAX_BOOKS * 2)
|
||||
#define SHELF_GROUP_ROOT "未分类" /* Books/ 根下书籍的段头 */
|
||||
|
||||
typedef struct shelf_book {
|
||||
char path[400]; /* 完整路径:打开书 + 进度键;分组不得改写 */
|
||||
char title[128];
|
||||
char sub[160]; /* 默认(format/宫格)副题:格式 · 大小 [· 读],与 M27 前逐字一致 */
|
||||
char sub_folder[160]; /* folder 模式副题:更深子路径 · 格式 · 大小 [· 读] */
|
||||
char group[SHELF_GROUP_MAX]; /* 相对 Books/ 的完整目录路径(供副题);根下="" */
|
||||
char top[SHELF_GROUP_MAX]; /* 分组键:相对路径的第一级目录名;根下="" */
|
||||
char sub[160]; /* 默认副题:格式 · 大小 [· 读],与 M27 前逐字一致;
|
||||
* format 模式与 folder 导航视图(书就在当前层)共用 */
|
||||
char sub_folder[160]; /* 深层子路径 · 格式 · 大小 [· 读](M33 起导航视图不再显示;
|
||||
* 保留字段供测试/兼容,不要用它改写 path) */
|
||||
char group[SHELF_GROUP_MAX]; /* 相对 Books/ 的完整目录路径:folder 导航的前缀键;根下="" */
|
||||
char top[SHELF_GROUP_MAX]; /* 第一级目录名(M27 遗留字段,供索引/诊断;M33 起不决定行序列) */
|
||||
int fmt; /* 0 txt,1 fb2,2 epub,3 mobi,4 cbz */
|
||||
long size;
|
||||
uint64_t ts; /* 最近阅读 unix;无=0 */
|
||||
} shelf_book;
|
||||
|
||||
typedef struct shelf_row {
|
||||
int section; /* 1=段头行 */
|
||||
int section; /* 1=段头行(仅 format 模式) */
|
||||
int dir; /* 1=目录导航行(仅 folder 模式);0=书行 */
|
||||
int fmt; /* 段头/书行格式;段头无 label 时显示格式名 */
|
||||
int book; /* 书行=books 下标;段头=-1 */
|
||||
int book; /* 书行=books 下标;段头/目录行=-1 */
|
||||
int count; /* 目录行:该子目录下(含更深层)书籍数 */
|
||||
char name[SHELF_GROUP_MAX]; /* 目录行:子目录名(不含父路径) */
|
||||
char info[24]; /* 目录行:展示副题,如 "12 本" */
|
||||
const char *label; /* 段头标题;NULL=格式名(指向 books[].top 或静态串) */
|
||||
} shelf_row;
|
||||
|
||||
@@ -82,12 +88,36 @@ int shelf_scan_books(const char *root, shelf_book *books, int cap,
|
||||
|
||||
/* 生成行序列:
|
||||
* folder_mode=0:按格式分段(TXT/FB2/EPUB/MOBI/CBZ,空格式不出段头)——与 M27 前逐行一致;
|
||||
* folder_mode=1:按 books[].top(Books/ 第一级目录)分组,根组「未分类」置顶,
|
||||
* 其余组按组名排序;组内沿用 books[] 全局顺序(最近阅读优先、其次书名)。
|
||||
* folder_mode=1:当前目录 cwd(相对 Books/,根="")的导航行——子目录在前(按名排序,
|
||||
* 每个带 count/info),直接属于该层的书在后(沿用 books[] 全局顺序)。
|
||||
* 纯内存:只读 books[].group,不做任何 opendir/readdir/stat。
|
||||
* 返回行数;超过 rowcap 时只写 rowcap 行并返回 rowcap。 */
|
||||
int shelf_build_rows_at(const shelf_book *books, int nbooks, int folder_mode,
|
||||
const char *cwd, shelf_row *rows, int rowcap);
|
||||
|
||||
/* 兼容旧调用:等价于 shelf_build_rows_at(..., cwd=""),即 folder 模式显示 Books/ 根层。 */
|
||||
int shelf_build_rows(const shelf_book *books, int nbooks, int folder_mode,
|
||||
shelf_row *rows, int rowcap);
|
||||
|
||||
/* ---- 目录导航(纯字符串/纯内存工具,宿主可测) ---- */
|
||||
|
||||
/* cwd 子树(含 cwd 本层书)的书籍数。cwd 无效/无书返回 0。 */
|
||||
int shelf_nav_count_under(const shelf_book *books, int nbooks, const char *cwd);
|
||||
|
||||
/* 进入子目录:cwd + "/" + name → out。1=成功;0=非法/超长(out 不改)。
|
||||
* 根目录 cwd="" 时结果为 name(无前导 '/')。 */
|
||||
int shelf_nav_enter(const char *cwd, const char *name, char *out, int outsz);
|
||||
|
||||
/* 返回上一级:非根返回 1 并写 out(根父为 "");已在根返回 0。 */
|
||||
int shelf_nav_up(const char *cwd, char *out, int outsz);
|
||||
|
||||
/* 面包屑:根 → "Books/";子目录 → "Books/<cwd>"。UTF-8 安全截断到 outsz;
|
||||
* 超长时保留末尾(当前目录最重要)并加 "…" 前缀。返回写入字节数。 */
|
||||
int shelf_breadcrumb(const char *cwd, char *out, int outsz);
|
||||
|
||||
/* 当前目录名(cwd 最后一级);根返回 ""。返回写入字节数。 */
|
||||
int shelf_dir_name(const char *cwd, char *out, int outsz);
|
||||
|
||||
/* 工具(单测直接调用)。 */
|
||||
int shelf_fmt_from_ext(const char *name);
|
||||
const char *shelf_fmt_name(int fmt);
|
||||
|
||||
Reference in new issue
Block a user