01 / SUMMARY结论先行。
这不是一份“设想中的方案”。截至 0.5.2,客户端已经完成时间线、发现、友链、详情、评论、九图发布、草稿 / 队列、6 种扩展卡、热力图、Zen、按月侧栏、主题色盘与纸张背景的完整闭环。
这份手记用源码、线上实例、模拟器实测和 26 条 mock 数据替换了初版的估算与推演。它不试图证明一切都已完成,而是把已经验证的、尚未验证的、明确不做的,分开讲清楚。
02 / PLATFORM平台不只有发帖接口。
Ech0 是自托管的个人微博客 / 说说平台。它的极简部署形态是最重要的产品特征:单个 Go 二进制同时提供 REST API 与内嵌 Vue3 SPA,默认 SQLite;OpenAPI 由 Huma 类型生成。前台是“时间线 + 说说 + 评论 + 友链”的轻社区,后台是完整的管理面板。
这决定了移动端不该做成一个“只会发帖的壳”。但平台的能力边界同样重要,尤其是下面这些会直接影响 UI 文案的约束:
- 发帖、上传、建标签仅限管理员。普通用户仍可浏览、点赞、评论;发布入口必须如实显示权限,不诱导。
private是布尔值。只有公开 / 私密,没有好友可见、密码可见等分级。客户端不能伪造不存在的选项。- 没有草稿 API。草稿箱只能是本地能力,换设备不同步;这一点必须在界面里说清楚。
- 点赞没有 unlike。服务端只提供点赞,客户端记录已赞状态,重复取消时提示“服务端不支持取消”。
- refresh token 在 HttpOnly cookie 里。原生客户端需要持久化 CookieJar,401 时自动续期并重放一次。
- 友链是实例对实例。Hub 必须按来源分页、失败隔离,不能因为一个实例超时就整页空白。
API 契约本身并不复杂:统一信封 {code, msg, data},Authorization: Bearer,X-Timezone / X-Locale;时间线用 POST /api/echo/query 分页,发布用全量替换的 POST /api/echo,上传拿 24 小时临时 file_id,发帖时转正。真正花时间的是把这些契约在手机上做得顺手,而不是把接口逐个包一层。
03 / DESIGN玻璃适合展示,时间线需要稳定。
初版方案想把 backdrop 的液态玻璃铺满全站:lens 折射、边缘高光、嵌套玻璃,视觉记忆点最强。真正把列表、评论、编辑器、输入法和低端机跑一遍之后,结论变了。
| 维度 | backdrop 玻璃 | Material 3 Expressive |
|---|---|---|
| 视觉记忆点 | 最强,液态折射 + 边缘高光 | 弹簧动效、形变组件,克制 |
| 机型覆盖 | lens 需 API 33+,低版本降级 | Android 10+ 一致 |
| 列表性能 | 大量 blur / lens 容易掉帧 | 原生组件,滚动稳定 |
| 组件完整度 | 按钮、开关、弹窗都要自建 | 完整组件 + 无障碍语义 |
| 工程风险 | AGSL / 嵌套玻璃调参成本高 | 官方 API,升级路径清晰 |
最终方案:MD3E 做全站骨架,保留 AndroidLiquidGlass-kmp 的动效与 tokens 作为参考;玻璃皮肤留到 P2,不在 0.5.2 引入依赖。这个决定让 0.5.x 的迭代速度从“调 shader”回到“补功能”。
04 / PAPER把背景调回纸的温度。
0.5.2 把内置「纸张」从偏橙的奶油色改成更接近宣纸的低饱和暖白,并同步调整深色纸张。目标不是“更黄”,而是让背景和 surfaceContainerLow 卡片、留白、蓝色主色在同一个温度里。
| 模式 | 旧色值 | 新色值 |
|---|---|---|
| 浅色 | #FDF6EA → #F6E7D0 → #EFD9BC | #FBF8F2 → #F3ECDF → #E7DAC6 |
| 深色 | #2A2118 → #1A140F | #2A241D → #1A1511 |
截图里能明显看到顶部与卡片之间的过渡更干净。默认蓝 #0B57D0 仍是唯一的强调色;用户可以在 160dp 的色相 / 饱和度色盘上自由取色,恢复默认一键返回。
05 / ENGINEERING六个真实取舍。
时间线、发现、友链、详情首帧读磁盘缓存,再后台刷新;断网也能看已打开过的内容。
Access Token 15 分钟,refresh 在 HttpOnly cookie;OkHttp CookieJar 持久化,401 自动续期重放一次。
服务端没有 unlike,客户端本地记录已赞;列表页和详情页共用同一份计数,不假装取消成功。
WorkManager + 客户端 UUID,网络恢复后指数退避重试;成功前不重复提交同一任务。
服务端 extension 优先,正文链接自动识别;GitHub / B 站 / X / Meting 失败时回退为可点击卡片,不阻塞正文。
Photo Picker → WebP 压缩(长边 2048)→ 逐张上传(进度、单张重试)→ 发帖回传 file_id。
04_discover.png 时发现,HeatmapView(days) 用 remember(days) 做 key,而 days 是 SnapshotStateList;刷新时原地 clear()/addAll(),引用不变,remember 不会重算。改为按内容快照做 key 后修复,所有用户都受益,不只是截图环境。
// 修复前:引用不变,缓存不会失效
val normalized = remember(days) { ... }
// 修复后:用内容快照作为 key
val normalized = remember(days.toList()) { ... }
06 / VALIDATION哪些验证过,哪些还没。
我们尽量不把“代码路径存在”写成“功能已验证”。下面这张表区分了本轮实测、代码实现和仍需复验的部分。
| 场景 | 方式 | 结果 |
|---|---|---|
| 真实契约 | 线上 echo.saltfish.top(5.8.2) | hello / settings / echo query / comments form 结构与模型一致 |
| 登录 | 账号密码 + CookieJar | 登录通过;401 续期逻辑已实现,未做令牌过期专项演练 |
| 时间线 | 分页 / 标签 / 刷新 / 滚动位置 | 通过;刷新去重、标签筛选、位置恢复已走查 |
| 发布 | 9 图选择 / 压缩 / 上传 | 编辑器与上传链路通过;一键发布与断网队列待正式实例复验 |
| 评论 | 评论树 / 二级回复 / 输入 | 渲染通过;发表与 PoW 待开启验证码的实例复验 |
| 友链 | 本站 + 2 个实例聚合 | 2 实例聚合通过;单实例失败隔离为代码路径,未做断网演练 |
| 主题 | 浅色 / 深色 / 纸张 | 通过;薄雾 / 暮色 / 自定义图片未逐项截图 |
| 热力图 | 30 天数据刷新 | 发现并修复 remember 不刷新问题 |
已知边界
- 管理面板(评论审核 / 文件管理 / 系统日志 / 设置)仍在 Web;App 只做轻量入口。
- Copilot SSE、Access Token 管理、S3 预签名直传尚未做 UI。
- 用户资料编辑 / 头像上传未做。
- 草稿与队列是本地能力,不跨设备同步。
- 截图数据来自 mock,不代表真实生产内容;线上实例仍需代理访问。
- 仅 Android;iOS / Web 不在本期范围。
07 / VERSIONS九个版本,九次收敛。
- 0.2.0首个 MD3E 版本:时间线、发现、友链、我的、About、写说说、草稿 / 队列、主题与缓存基础。起点
- 0.3.0启动静默同步、160dp 色盘、更新检查、点赞统一。
- 0.3.1编辑说说、图片查看器、画质、话题、定位。
- 0.3.2友链分页 / 远端详情、自定义背景、服务端资料。
- 0.3.3修复检查更新仓库迁移。
- 0.4.0低流量模式、图片手势、6 种扩展卡、补记选择器、评论默认身份。
- 0.5.0长文折叠、二级评论、Markdown 图片、卡片粘贴识别、音乐播放。
- 0.5.1按月侧栏、评论重构、草稿编辑、背景模糊。
- 0.5.2详情缓存、底部渐变、友链头像、刷新去重、纸张调色、热力图修复。当前
08 / NEXT下一步。
- 0.6 管理轻量版:评论审核 / 置顶 / 删除、文件管理、访客统计,让管理员在手机上处理 80% 的日常操作。
- Copilot:SSE 流式对话 + 近况总结,先做只读,再开放管理动作。
- 玻璃皮肤 P2:用 backdrop 做 1–2 个展示型页面(关于 / 启动 / 分享卡),不铺全站。
- 质量:Baseline Profile、Compose 稳定性、固定 mock 数据与状态栏的截图回归。
- 发布:把最新 APK、SHA-256 与 16 张截图同步到网站和 GitHub Releases。
把 Ech0 装进口袋。
0.5.2 · Android 10+ · 4.8 MB · AGPL-3.0