Files
MeowOS-SDK/tuku/logic/reader.c
T
AskaEth c5985b2c67 图库: 进入文件夹后顶栏显示「续读」,一键回到上次那页(M33)
用户要求:文件夹拆开可进之后仍保留进度,但入口只在**进入了该文件夹**之后才出现,
不在上层列表的文件夹行加角标/按钮。

- ui/library/library.c:顶栏按 mr_cont_resume_page(g_cur_dir, 图片数) 判定,有进度才加
  MEOW_ICON_PLAY_ARROW「续读」动作(label 保留为语义名);点按 = mr_reader_open_dir_at
  (g_cur_dir, MR_READER_START_RESUME)。SDK 的 TopAppBar 动作创建期固定、无隐藏 API,
  因此可见性变化时整条顶栏重建:无进度是真的 2 个动作(没有空占位/死点击区),
  续读插最左,刷新/设置的下标与命中区不变。
- 可见性实时重判:load_dir(进入/返回/刷新)与 library_page_enter(从阅读器返回,
  进度可能刚写盘)都调 resume_refresh;根目录有进度时同样显示。
- logic/reader.c/.h:新增 mr_cont_resume_page(dir, pages)——无记录/空目录 → -1(不显示);
  保存页超页数(文件被删)→ 钳到末页并 INFO 日志;页数未知(列表被 2000 截断)不夹持,
  上界交给阅读器 open_book 自己的 mr_nav_clamp_page。
- 不改列表行语义(不重新引入 M18 删掉的「目录自动当书」)。
- 宿主回归(test_browse_ui_host U8):有进度目录 3 个动作/无进度 2 个、三个动作的点击区
  互不遮挡、点续读从当前目录续读页打开、超页数钳到末页、空目录不显示、上层/进入/返回
  与阅读器返回后的可见性切换;test_library T5 覆盖 mr_cont_resume_page 的全部边界。
2026-11-06 19:35:43 +08:00

201 lines
6.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* logic/reader.c — 阅读方向状态(默认 RTL;M4/M6 的阅读器直接复用)+ 每册续读状态(M7)。
*
* M5 评审 P1 修复:方向设置要「下次启动生效」。最小持久化(不新增任何方向状态):
* - mr_reader_set_direction 成功切换后 best-effort 写 meow_config("reader.dir","rtl"/"ltr");
* 写失败只打 WARN,不阻断切换(本次运行仍生效);
* - mr_reader_get_direction 首次调用惰性读一次配置恢复;无配置/值非法 → 默认 RTL;
* 之后走纯内存快路径(不再读配置)。
* M7:阅读模式(翻页/条漫)与每册 last page 同为 meow_config 小键(reader.mode/page.<册标识>,
* 册标识=规范化目录路径的 FNV-1a64);书库行角标经 mr_cont_badge 读取同一份状态。
* M33:mr_cont_resume_page 给「进入目录后的顶栏续读按钮」做可用性判定与页码夹持
* (保存页超出当前页数 → 钳到末页;空目录/无图片 → 无入口)。
* 依赖面:只 include 配置/日志两个模块头(不含 UI);宿主测试用
* test/settings_reader_stub.c 顶替 meow_config_* 与 meow_log。
*/
#include "reader.h"
#include "include/meow_ui_config.h"
#include "include/meow_ui_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MR_DIR_CFG_KEY "reader.dir"
#define MR_DIR_CFG_LTR "ltr"
#define MR_DIR_CFG_RTL "rtl"
static int g_direction = MR_DIR_RTL;
static int g_restored; /* 0=尚未从配置恢复(首次 get/set 时读一次) */
static void reader_restore_once(void)
{
char buf[8];
if (g_restored) return;
g_restored = 1; /* 无配置也只读一次:之后纯内存 */
if (meow_config_get(MR_DIR_CFG_KEY, buf, sizeof buf) != MEOW_CFG_OK) return;
if (strcmp(buf, MR_DIR_CFG_LTR) == 0) g_direction = MR_DIR_LTR;
else if (strcmp(buf, MR_DIR_CFG_RTL) == 0) g_direction = MR_DIR_RTL;
/* 其它值:忽略,保持默认 RTL */
}
int mr_reader_get_direction(void)
{
reader_restore_once();
return g_direction;
}
void mr_reader_set_direction(int dir)
{
if (dir != MR_DIR_RTL && dir != MR_DIR_LTR) return;
reader_restore_once(); /* 先恢复:避免「没 get 过就 set」把配置里的旧方向当默认值 */
if (g_direction == dir) return; /* 无变化不写盘 */
g_direction = dir;
if (meow_config_set(MR_DIR_CFG_KEY, dir == MR_DIR_LTR ? MR_DIR_CFG_LTR : MR_DIR_CFG_RTL)
!= MEOW_CFG_OK)
meow_log(MEOW_LOG_WARN, "reader.dir: save failed (best-effort, kept in memory)");
}
#ifdef MR_READER_TEST_HOOKS
void mr_reader_test_reset(void)
{
g_direction = MR_DIR_RTL;
g_restored = 0;
}
#endif
/* ============================================================ */
/* 阅读模式 + 每册续读状态(M7) */
/* ============================================================ */
int mr_mode_valid(int mode)
{
return mode == MR_MODE_PAGED || mode == MR_MODE_WEBTOON;
}
const char *mr_mode_name(int mode)
{
return mode == MR_MODE_WEBTOON ? "webtoon" : "paged";
}
int mr_mode_parse(const char *s)
{
if (!s) return -1;
if (strcmp(s, "paged") == 0) return MR_MODE_PAGED;
if (strcmp(s, "webtoon") == 0) return MR_MODE_WEBTOON;
return -1;
}
/* 目录规范化:去掉末尾 '/'(根 "/" 保留),保证同一册跨调用/跨启动得到同一键。 */
static void cont_norm_dir(const char *dir, char *out, int outsz)
{
if (!out || outsz <= 0) return;
out[0] = '\0';
if (!dir || !dir[0]) return;
int n = (int)strlen(dir);
while (n > 1 && dir[n - 1] == '/') n--;
if (n >= outsz) n = outsz - 1;
memcpy(out, dir, (size_t)n);
out[n] = '\0';
}
static unsigned long long cont_fnv1a64(const char *s)
{
unsigned long long h = 1469598103934665603ULL;
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
h ^= (unsigned long long)*p;
h *= 1099511628211ULL;
}
return h;
}
int mr_cont_key(const char *dir, const char *field, char *out, int outsz)
{
if (!out || outsz <= 0) return -1;
out[0] = '\0';
if (!dir || !dir[0] || !field || !field[0]) return -1;
char norm[MR_CONT_PATH_MAX];
cont_norm_dir(dir, norm, (int)sizeof norm);
if (!norm[0]) return -1;
int n = snprintf(out, (size_t)outsz, "reader.%s.%016llx", field, cont_fnv1a64(norm));
if (n <= 0 || n >= outsz) { out[0] = '\0'; return -1; }
return 0;
}
int mr_cont_get_page(const char *dir, int def)
{
char key[64];
char buf[24];
if (mr_cont_key(dir, "page", key, (int)sizeof key) != 0) return def;
if (meow_config_get(key, buf, (int)sizeof buf) != MEOW_CFG_OK) return def;
char *end = NULL;
long v = strtol(buf, &end, 10);
if (end == buf || (end && *end != '\0')) return def;
if (v < 0 || v > MR_CONT_PAGE_MAX) return def;
return (int)v;
}
void mr_cont_set_page(const char *dir, int page)
{
char key[64];
char val[24];
if (page < 0) page = 0;
if (page > MR_CONT_PAGE_MAX) page = MR_CONT_PAGE_MAX;
if (mr_cont_key(dir, "page", key, (int)sizeof key) != 0) return;
snprintf(val, sizeof val, "%d", page);
if (meow_config_set(key, val) != MEOW_CFG_OK)
meow_log(MEOW_LOG_WARN, "reader: save page failed (best-effort)");
}
int mr_cont_get_mode(const char *dir, int def)
{
char key[64];
char buf[16];
if (!mr_mode_valid(def)) def = MR_MODE_PAGED;
if (mr_cont_key(dir, "mode", key, (int)sizeof key) != 0) return def;
if (meow_config_get(key, buf, (int)sizeof buf) != MEOW_CFG_OK) return def;
int m = mr_mode_parse(buf);
return m >= 0 ? m : def;
}
void mr_cont_set_mode(const char *dir, int mode)
{
char key[64];
if (!mr_mode_valid(mode)) return;
if (mr_cont_key(dir, "mode", key, (int)sizeof key) != 0) return;
if (meow_config_set(key, mr_mode_name(mode)) != MEOW_CFG_OK)
meow_log(MEOW_LOG_WARN, "reader: save mode failed (best-effort)");
}
void mr_cont_save(const char *dir, int page, int mode)
{
if (!dir || !dir[0]) return;
mr_cont_set_page(dir, page);
mr_cont_set_mode(dir, mode);
}
int mr_cont_badge(const char *dir, char *out, int outsz)
{
if (!out || outsz <= 0) return 0;
out[0] = '\0';
int page = mr_cont_get_page(dir, -1);
if (page <= 0) return 0;
snprintf(out, (size_t)outsz, "续读 P%d", page + 1);
return 1;
}
int mr_cont_resume_page(const char *dir, int pages)
{
int page = mr_cont_get_page(dir, -1);
if (page < 0) return -1;
if (pages == 0) return -1; /* 空目录/无图片:没有可续读的页 */
if (pages < 0) return page; /* 页数未知(列表截断):上界交给阅读器钳制 */
if (page >= pages) {
meow_log(MEOW_LOG_INFO, "reader: resume page %d beyond %d pages (%s), clamp to last",
page + 1, pages, dir ? dir : "");
return pages - 1;
}
return page;
}