我做了一个把「专注计时」包装成护照旅行的 iOS App
去年有段时间我特别依赖番茄钟,用过 Forest、Toggl、甚至原生的提醒事项。Forest 我种了大概 30 棵树,但有次打开记录页,看着一排绿色小树完全不知道自己那几十个小时在忙什么——任务名都是「专注」两个字,什么信息都没有。那一刻觉得挺荒唐的,时间明明花出去了,却像什么都没留下。
我觉得缺的不是功能,是那段时间「值得被记住」的感觉。于是做了声境护照。
核心想法:把专注包装成一次旅行
App 的核心隐喻是护照 + 飞行里程。每次专注结束,你不是「完成了一个计时器」,而是完成了一段旅程,赚到里程,盖了一个章。
声景是这个体验的关键部分。打开 App,你可以选「东京雨夜」「咖啡馆午后」「深夜图书馆」这类场景,配着环境音进入专注。25 分钟结束后,App 弹出一张会话战报——时长、效率指数、获得的经验值、连续天数,还有一句根据当前连续记录生成的提示。这张战报可以直接截图分享,卡片排版做得还算好看,我自己用的时候会发给学习搭子。
数据层设计:FocusLog 要不要拆表
最纠结的一个决策是 FocusLog 要不要拆表。专注记录、任务信息、声景偏好,一开始我想分三张表存,关联查询。后来发现统计逻辑全是按「某段专注」为单位聚合的,跨表 join 反而麻烦,干脆全塞一条记录里:
struct FocusLog: Identifiable, Codable {
let id: String
let dateKey: String
let minutes: Int
let hour: Int // 用于统计高峰时段
let taskId: String
let taskCompleted: Bool
let sceneId: String? // 记录用的哪个声景
let comboIndex: Int? // 当天第 N 段,满 3 段触发连击加成
}
hour 字段是专门为「高峰专注时段」统计加的,周报会用到——你可以看到自己哪个时间段专注质量最高。comboIndex 记的是当天第几段,同一天内满 3 段触发连击加成,额外给经验值。字段名叫 combo 是因为一开始想做更复杂的连击系数,后来简化了,名字就没改,有点历史遗留。
里程那边有一套探险章节系统,用 ExpeditionChapterDefinition 定义每个城市关卡,包含若干任务(sessionCount、focusMinutes、deepFocusCount),完成任务解锁下一个城市。这块给长期用户准备的,坚持用一两周后会发现有个隐形进度条在推着你走。
战报文案写了三稿才对
战报页有个细节:每次专注结束后,App 会生成一段具体的文字建议,告诉你下一段怎么安排。
第一版我用的模板字符串插值,实际输出长这样:「当前任务完成度为 60%,建议继续保持。」——读完没有任何感觉,像在看系统日志。
然后试了按「任务完成度」单一维度分支,但完全忽略连续天数的权重,一个连续专注 10 天的用户和第一天用户收到同一句话,说不过去。
最后把维度拆开——今日计划缺口、任务状态、连续天数分三个判断块各自生成一句,再拼起来。比如连续天数不足 3 天时输出「建议先连续 3 天完成每日最小闭环」,满 5 天后换成「你已连续 X 天,重点是稳定复用」。硬写,但读起来最自然。
用着感受怎么样
我自己日常写代码和写文章都在用。最大的变化是开始在乎连续天数了。有次晚上 11 点发现当天还没专注,硬开了一个 25 分钟就为了保住记录——任务随便选了个「编程训练」,其实就是在整理桌面。有点荒唐,但确实有效。
关键在于:推送我能划掉,完全没有心理负担。但连续天数要断掉,我会盯着那个数字难受几分钟,然后真的去开一段专注。这两种感觉根本不一样。目前做番茄钟的工具普遍把精力放在推送提醒上,我觉得方向选错了。
周日晚上生成一张本周回顾卡片,看一眼高峰时段在哪,完成了多少分钟,满足感是真实的。有时候发现某天专注特别少,能想起来那天确实状态差——记录本身就有意义。
目前有一个地方有点可惜:声景数量还不够多,东亚城市风格的音源比较难找到质量好的免费素材,后续版本打算陆续补。
App Store:声境护照 - 专注计时
对了,我一直在纠结要不要加「放弃本次专注」按钮——你觉得有这个选项会让人更容易半途而废,还是反而减少愧疚感、下次更愿意再开一段?

浙公网安备 33010602011771号