挑战系统说明

介绍「认真喝水」18 项饮水挑战的分类、判定规则、进度计算方式与界面行为。挑战为纯函数实时计算,无独立持久化。

最后更新:2026-06-20

挑战页(Tab「挑战」)通过 ChallengeEngine 根据本地饮水记录实时计算进度,不写入 CloudKit,也没有「领取奖励」或等级体系。每项挑战展示 本轮进度累计完成次数

设计意图:规则简单、可解释,用户只看记录数据就能理解「还差什么」;避免复杂游戏化状态。


一、总体结构

DrinkRecord(全部历史记录)
        +
DailyGoalSnapshot(每日目标快照)
        +
UserProfile(今日目标、饮品库列表)

ChallengeEngine(纯函数,无持久化)

ChallengesView(展示、筛选、详情)
组件路径职责
ChallengeEngineWaterMind/Models/ChallengeEngine.swift18 项挑战的判定与进度计算
ChallengesViewWaterMind/Views/Challenges/ChallengesView.swift挑战 Tab UI、分类筛选、详情弹层
单元测试WaterMindTests/ChallengeEngineTests.swift历史目标快照对达标判定的影响

二、核心约定

2.1 饮水量怎么算

  • 每日总量、达标判定一律使用 effectiveMLvolumeML × hydrationRate,记录时冻结)。
  • 补水率为负的饮品(如部分酒类)会扣减当日有效饮水量,可能影响「满杯」类挑战。
  • 「有饮水记录」仅看当天是否存在至少一条 DrinkRecord,与容量正负无关。

2.2 「每日目标」用哪天的数

  • 今天UserProfile.dailyGoalML(含智能规划、手动目标等当前逻辑)。
  • 历史某天:优先读 DailyGoalSnapshot 固化值,防止日后改设置重写历史达标。
  • 无快照的历史日 fallback 见 DailyGoalStore(与统计页一致)。

例:昨天快照目标 1000 ml、今天目标 3000 ml;昨天喝了 1500 ml,则昨天算达标、今天按 3000 ml 判。

2.3 时间与时区

  • 全部按 设备本地日历Calendar.current)切日、切周、切月。
  • 周起始日为周一weekStart(weekday + 5) % 7 回退到周一 0 点)。
  • 时段挑战按记录 timestamp 的小时 判定(见下文各每日挑战)。

2.4 进度与完成次数

字段含义
progress0…1,本轮进度条
progressText3 / 5 天1200 / 2000 ml
completionCount历史上累计完成轮数(非「已领取次数」)
nextStepText距本轮完成还差什么,或「今天/本周/本月已完成」

无持久化:关闭 App 再开,进度由最新记录重新算;不存在「漏领勋章」。

2.5 不做什么

  • 无 CloudKit 同步、无 UserDefaults 缓存挑战状态
  • 无等级、无积分商城、无手动「领取」
  • 探索类「饮品库」计数来自 UserProfile.recentDrinkTypeRaws(饮品库/快捷栏已保存的饮品 ref 数量),非全 App 内置目录总数

三、六大分类 · 18 项挑战

3.1 每日(4 项)— 每天可完成 1 次

completionCount = 历史上满足条件的天数总和。

ID名称完成条件时段/说明
daily.morning早安,第一杯!当天 10:00 记录 ≥1 次小时 < 10
daily.afternoon午后补给当天 14:00–16:59 记录 ≥1 次[14, 17)
daily.rhythm节奏大师四个时段各记录 ≥1 次0–1010–1414–1818–24
daily.full满杯!当天 effectiveML 合计 ≥ 当日目标进度条按目标 ml 显示

3.2 连续(3 项)— 每连续 N 天完成 1 轮

今天或昨天起向前数连续满足条件的天数;中断则本轮进度归零。

ID名称连续条件轮次
streak.three三天连击连续 3 天有记录每满 3 天 +1 次完成
streak.week一周不断流连续 7 天有记录每满 7 天 +1 次
streak.fullWeek满杯七连连续 7 天达标每满 7 天 +1 次

completionCount = 历史上所有连续段长度之和 ÷ 目标天数(整除)。

3.3 每周(3 项)— 每周最多完成 1 次

统计 本周一 0 点 起至今天(含)的 7 日窗口;历史周按同样规则回看。

ID名称完成条件
weekly.online本周在线本周 ≥5 天有记录
weekly.full五天满杯!本周 ≥5 天达标
weekly.logs记录达人本周累计记录 ≥20 次

3.4 每月(3 项)— 每月最多完成 1 次

统计 本月 1 日 0 点 起至今天(含)。

ID名称完成条件
monthly.regular月度常客本月 ≥20 天有记录
monthly.full满杯半月本月 ≥15 天达标
monthly.logs一百次!本月累计记录 ≥100 次

3.5 探索(3 项)— 可重复多轮

基于全部历史记录或饮品库;每凑满一轮目标 completionCount +1,进度条显示当前轮余量。

ID名称完成条件计数来源
explore.flavors换个口味!每累计 3 种不同饮品名Set(records.map(\.itemName))
explore.volumes杯杯不同每累计 5 种不同容量Set(records.map(\.volumeML))
explore.library我的饮品柜每拥有 8 种饮品recentDrinkTypeRaws.count

3.6 累计(2 项)— 长期里程碑,可重复多轮

ID名称完成条件
milestone.logs百记不停每累计记录 100 次
milestone.goalDays满杯收藏家每累计 30 个达标日

四、界面行为(ChallengesView)

4.1 数据注入

ChallengeEngine(
    records: allRecords,           // @Query 全部 DrinkRecord
    goalML: profile.dailyGoalML,   // 今日执行目标
    goalsByDay: DailyGoalStore.goalsByDay(...),  // 历史快照
    libraryCount: profile.recentDrinkTypeRaws.count
)

每次进入挑战 Tab 或记录变化触发 SwiftUI 刷新时,引擎重新计算。

4.2 「离完成最近」推荐卡

每日 / 连续 / 每周 三类中,取 progress < 1 且进度最高的一项置顶;若均已满则 fallback 到列表第一项。

4.3 分类筛选

顶部分类 Chip:全部 + 六大类 ChallengeCategory。选中后网格只显示该分类挑战。

4.4 详情弹层

点击卡片打开 ChallengeDetailSheet,展示:

  • 完成条件(condition
  • 本轮进度与完成次数
  • 周期说明(cycleText
  • 贴纸勋章视觉(StickerBadge

五、与产品其他模块的关系

模块关系
今日 / 记录新增、删除记录会改变挑战进度
智能规划仅影响「今日目标」及未来快照,不改变挑战规则本身
统计页共用 DailyGoalStore.goalsByDay 思路,达标定义一致
Widget不展示挑战
备份备份 DrinkRecord / DailyGoalSnapshot / UserProfile 即可间接恢复挑战进度

六、修改挑战时请同步

  1. ChallengeEngine.swift — 规则与文案(title / condition / cycleText
  2. ChallengeEngineTests.swift — 边界用例(历史目标快照、连续中断、周/月边界等)
  3. 本文档 — 规则表与约定说明

七、快速对照表(18 项)

#分类名称周期
1每日早安,第一杯!每天
2每日午后补给每天
3每日节奏大师每天
4每日满杯!每天
5连续三天连击每连续 3 天
6连续一周不断流每连续 7 天
7连续满杯七连每连续 7 天
8每周本周在线每周
9每周五天满杯!每周
10每周记录达人每周
11每月月度常客每月
12每月满杯半月每月
13每月一百次!每月
14探索换个口味!每 3 种饮品
15探索杯杯不同每 5 种容量
16探索我的饮品柜每 8 种库内饮品
17累计百记不停每 100 次记录
18累计满杯收藏家每 30 个达标日