Folo 移动端 v0.1.5 版本特性技术解读:通知与未读体系、批量已读与体验打磨
导读
本文围绕 Folo(AI RSS Reader)移动端 v0.1.5 变更记录 展开,逐条解读该版本引入的可配置新条目通知、未读角标与详细未读计数、批量将订阅源或分类标记为已读、AI 摘要默认开启等核心改动,并结合apps/mobile源码说明其底层实现链路(Firebase Cloud Messaging 推送、expo-notifications 角标、上下文菜单批量操作等)。读完本文,你将能对照当前仓库源码理解这些发布说明背后的功能入口、默认值配置与数据流走向。
一、版本概览:一次围绕“未读治理”的集中交付
v0.1.5 的变更按 changelog 模板 的三段式组织:Shiny new things(新特性)、Improvements(改进)与No longer broken(修复)。三部分围绕同一条主线展开:帮助用户更高效地掌握“有新内容 → 有多少未读 → 一键清空 → 点击直达正文”这一完整阅读链路。
新特性清单如下:
- 为新条目提供可配置的通知(#3424)
- 引入未读角标(#3424)
- 将文档链接整合进动作页(7e553db)
- 支持将订阅源或分类一键标记为已读(53b2136)
- 提供显示详细未读计数的选项(#3453)
其中 #3424 从变更记录看同时牵动通知、角标、分享入口隐藏与分享修复等多个改动,可以推断这是一个围绕“推送与分享体系”的横切工作流。
二、可配置的新条目通知:从 FCM 令牌到点击直达正文
2.1 功能描述
v0.1.5 为移动端引入了可配置的新条目通知:当用户订阅的源产生新条目时,App 可推送提醒,且用户能够按需开关该能力。这说明“是否接收推送”已成为一个可配置项,而不再是无条件行为。
2.2 源码侧的推送链路
在 useMessaging.ts 中可以看到完整链路:
- 令牌注册:
useUpdateMessagingToken在用户登录(whoami?.id存在)且具备通知操作权限(hasNotificationActions)时触发,通过followClient.api.messaging.createToken({ token, channel: Platform.OS })将设备 FCM 令牌上报服务端,同时缓存在本地kv(键名为firebase_messaging_token,见 useMessaging.ts)。 - 令牌刷新:通过 Firebase Messaging 的
onTokenRefresh监听令牌轮换并自动重新注册。 - 消息路由:
useMessaging接收 Remote Message,识别message.data.type === "new-entry"类型的推送,读取其中的entryId与view字段,然后调用navigation.pushControllerView(EntryDetailScreen, ...)直接将用户带入对应条目详情页(见 useMessaging.ts)。
这一设计意味着服务端推送的 data 载荷至少包含type、view、entryId三个字段,移动端据此完成“点开推送即读文章”的深度链接体验。
从依赖清单看,推送能力由@react-native-firebase/messaging(FCM)与expo-notifications(通知呈现、权限与角标)共同承担,二者均声明于 apps/mobile/package.json。
2.3 权限请求与系统通道
[v0.1.5 之后仍沿用的] 通知权限申请逻辑位于 permission.ts:
- Android 端首先创建名为
default的通知渠道,配置importance: AndroidImportance.MAX、振动模式[0, 250, 250, 250]与提示色; - 随后按“先查询、再请求”的顺序获取权限,未授权时弹出系统请求;
- 权限被拒绝时返回
false并弹出错误提示,不会静默失败。
2.4 可配置性入口
仓库中 Notifications 设置路由 已为通知设置页预留了占位实现,同时 SettingsList.tsx 以titles.notifications作为菜单项文案并配NotificationCuteReIcon图标,从源码结构看通知配置入口已接入设置体系。
三、未读角标与详细未读计数:一个开关,两种粒度
3.1 未读角标(App Icon Badge)
角标能力由 useUnreadCountBadge.ts 实现:它通过useUnreadAll()读取全局未读总数,在每次变化后调用setBadgeCountAsyncWithPermission(unread)把数字同步到系统桌面角标(expo-notifications的setBadgeCountAsync)。
关键约束在其开关语义上:角标默认关闭。在共享设置默认值 defaults.ts 中showUnreadCountBadgeMobile: false,对应 UI 设置接口 interface.ts。开关判定实现在 permission.ts:
- 读取
getUISettings().showUnreadCountBadgeMobile; - 开关关闭(默认状态)且未显式跳过检查时直接返回
false,不写角标; - 开启后还需完成通知授权,否则同样放弃;
- 为避免无效调用,若目标计数与当前角标相同则直接返回(幂等保护)。
3.2 详细未读计数(#3453)
除“角标显示/隐藏”这一粗粒度选项外,#3453 提供了显示详细未读计数的选项。从订阅列表组件(如 SubscriptionItem.tsx、InboxItem.tsx)与 CategoryGrouped.tsx 对未读计数的使用可以推断:该选项控制列表内未读数字的展示粒度(例如分组/分类维度上的统计明细),使得大列表场景下用户无需进入具体分组即可感知各维度积压量。
四、批量已读:订阅源与分类的“一键清零”
4.1 功能描述
v0.1.5 让用户可以对单个订阅源或整个分类执行“标记为已读”操作,解决订阅量大时逐条划掉的痛点。
4.2 上下文菜单的实现
在 feeds.tsx 的订阅源上下文菜单中可以看到markFeedAsRead的调用:菜单项触发后执行unreadSyncService.markFeedAsRead(feedIds)(见 feeds.tsx),其中feedIds可来自单选订阅源或整组订阅源集合。分类场景则通过对选中分类展开为feedIds数组后复用同一markFeedAsRead服务完成批量清零,达到“分类维度一键已读”的效果。
4.3 与既有已读能力的配合
该能力与移动端既有“条目级已读”逻辑(apps/mobile/src/modules/entry-list/hooks.ts等)互补:条目级用于逐条消费,源/分类级用于整体清空,二者共同构成完整的未读治理闭环。
五、动作页文档链接与分享链路收口(#3424 / 7e553db)
5.1 文档链接进入动作页
7e553db 将“文档链接”接入动作页(Actions),对应设置路由 Actions.tsx。从变更语义推断,用户可在条目/订阅的动作面板中直接跳转查看对应的原文文档,减少“复制链接 → 浏览器打开”的手动步骤。
5.2 分享按钮的可用性判断(#3424)
Improvements 中“在不适用于某订阅源时隐藏分享按钮”与 No longer broken 中“修复无法分享订阅源 URL”同属 #3424 的分享链路整改:
- 旧行为:分享按钮无条件出现,但对某些订阅源(例如无站点的纯列表源或特殊订阅类型)分享操作会失败;
- 新行为:先判断该订阅源是否存在可分享的 URL/资源,再决定按钮显隐;同时修复了此前导致分享动作失败的根因,保证对具备分享条件的源能稳定完成分享。
这两条改动在 feeds.tsx 的上下文菜单中也有对应:仅当subscription.feedId对应源存在(getFeedById可命中)时才执行setStringAsync(feed.url)之类分享逻辑。
六、AI 摘要与阅读视觉层的打磨
6.1 AI 摘要默认开启(8da8a71)
v0.1.5 将 AI 摘要设置为默认启用。这一语义在共享 UI 设置的默认值中得到印证:defaults.ts 中summary: true,对应接口 interface.ts 的布尔字段。默认开启意味着新用户开箱即可看到条目摘要,无需手动进入设置开启;该默认值位于@follow/shared共享包,可推断为保证桌面端与移动端行为一致。
6.2 AI 摘要样式增强(87aa062)
同版本对 AI 摘要的视觉样式做了增强,使其在正文中与普通内容区分更清晰。移动端 AI 摘要相关 UI 集中在 modules/ai/summary.tsx,可结合该文件继续查看样式实现。
6.3 其余视觉与反馈改进
- 订阅源请求失败的错误提示(#3455):当订阅源抓取失败时不再静默,而是向用户展示明确错误信息。上下文菜单中
feed.errorAt/feed.errorMessage字段的存在(见 feeds.tsx)说明数据层已具备错误状态字段,前端据此渲染提示。 - 时间线列表分隔线样式(b1c356f)与Toast 样式升级(83e7764):属于信息密度与反馈层的一致性打磨。
- 显著性能改进(23eaf86):变更记录仅给出哈希而未量化具体指标,从措辞看属于渲染/滚动链路的优化,具体收益以实际使用为准。
七、No longer broken:六个关键修复的技术要点
| 修复点 | 影响 |
|---|---|
| 订阅源 URL 分享失效(#3424) | 恢复对可分享订阅源的分享能力 |
| 同名分类在不同视图下的区分错误(d70f082) | 订阅列表按视图准确区分同名分类,避免“标记已读/展开”作用于错误目标 |
| 重复菜单项(f24f21a) | 清理上下文菜单与动作面板中的重复操作项 |
| 条目文字偶发错位(17dd1e7) | 修正条目文本定位,保证正文排版稳定 |
| 视图选择器按压缩放闪烁(d66d49c) | 消除 Timeline 视图切换时的闪烁,提升交互顺滑度 |
| 特定图片加载导致崩溃(7b20633) | 修复特定图片来源下的稳定性问题 |
其中“同名分类跨视图混淆”的修复对多视图(如时间线/收件箱/订阅列表等视图体系,见 TimelineViewSelector.tsx)用户尤为重要:分类名称相同但归属视图不同时,此前操作可能命中错误上下文,修复后按视图隔离语义得到保证。
八、如何在本仓库验证这些能力
若希望对照源码逐条验证 v0.1.5 的能力,推荐按以下路径阅读:
- 推送与角标:useMessaging.ts、permission.ts、useUnreadCountBadge.ts,配合共享默认值 defaults.ts;
- 批量已读:context-menu/feeds.tsx;
- 设置入口:modules/settings/SettingsList.tsx 与
modules/settings/routes/下的 Actions、Notifications、General 路由; - 依赖版本:通知与角标相关的
expo-notifications、@react-native-firebase/messaging等声明见 apps/mobile/package.json; - 相邻版本对比:可对比 v0.1.4 与 v0.1.6 的变更记录,观察该特性线的演进脉络。
需要说明的是:changelog 中的版本号(0.1.x)是移动端早期里程碑的记录;以当前仓库为准,apps/mobile/package.json 中包的当前版本已迭代至更高版本,本文所述功能的能力入口与默认值均以仓库内源码现状为准。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考