feat: initial commit - Habo habit tracking app

- Complete MVP with Repository Pattern, SQLite storage
- Provider + ChangeNotifier state management
- Navigation 2.0 with deep link support
- Habit CRUD with twoDayRule, notifications, categories
- Backup/Restore via JSON
- Statistics with streak tracking
- Material You theme support
- Biometric lock support
- Desktop widget support
- 27 languages i18n structure
- Comprehensive test suite (87/89 passing)
This commit is contained in:
2026-04-13 15:02:30 +00:00
commit aa69f2a91e
212 changed files with 16694 additions and 0 deletions

194
docs/01-REQUIREMENTS.md Normal file
View File

@@ -0,0 +1,194 @@
# Habo 需求分析文档
> 版本: 3.1.2 | 最后更新: 2026-04-13
---
## 1. 项目定位
### 1.1 产品定义
**Habo** 是一款**极简主义的习惯追踪应用**,帮助用户通过每日打卡、进度可视化和心理模型引导,建立并维持长期习惯。
### 1.2 解决的核心问题
| 痛点 | 描述 |
|------|------|
| **习惯难以坚持** | 人们制定了计划但缺乏日常反馈机制,几天后放弃 |
| **缺乏正向激励** | 完成习惯后没有即时奖励感,大脑无法建立正向反馈回路 |
| **数据孤岛** | 习惯数据存储在云端,依赖网络,存在隐私顾虑 |
| **功能过度** | 现有习惯类 App 功能繁杂,设置成本高,反而降低使用意愿 |
| **缺乏科学方法** | 用户不了解习惯形成的心理学原理,只靠意志力 |
### 1.3 目标用户画像
| 画像 | 特征 | 核心需求 |
|------|------|----------|
| **自律建设者** | 想建立新习惯(运动、阅读、冥想)但经常中断的成年人 | 简单打卡 + 可视化连续天数 |
| **数据驱动型** | 喜欢用数据量化自我、追踪进步的用户 | 统计图表 + 导出功能 |
| **隐私敏感型** | 不信任云服务,希望所有数据留在本地的用户 | 纯本地存储 + 本地备份 |
| **多设备用户** | 在手机、平板、桌面端都需要使用 | 跨平台支持Android/iOS/macOS/Linux |
### 1.4 差异化定位
与 Habitica、Streaks、Loop 等竞品相比Habo 的核心差异化:
- **极简设计** — 无社交、无游戏化、无广告,聚焦习惯本身
- **心理学模型内置** — Cue-Routine-Reward 习惯循环、两天法则、习惯合约(惩罚/问责伙伴)
- **纯本地架构** — 零网络依赖,数据完全由用户掌控
- **跨平台开源** — Flutter 实现的真正跨平台体验
- **数值型习惯** — 不仅支持"做了/没做",还支持"做了多少"(如跑步 5km、喝水 8 杯)
---
## 2. 功能需求
### 2.1 功能矩阵
#### P0 — 核心功能MVP 必须)
| ID | 功能 | 描述 |
|----|------|------|
| F01 | 习惯创建 | 创建新习惯,设置标题和基础配置 |
| F02 | 每日打卡 | 日历视图中对每一天标记状态:完成 / 失败 / 跳过 |
| F03 | 日历视图 | 月度日历展示,每天显示对应的打卡状态标记 |
| F04 | 连续天数 | 计算并展示当前连续完成天数和最高连续记录 |
| F05 | 习惯列表 | 展示所有活跃习惯,支持拖拽排序 |
| F06 | 编辑/删除 | 编辑习惯属性或永久删除 |
| F07 | 持久化存储 | 所有数据本地 SQLite 存储,关闭应用不丢失 |
| F08 | 浅色/深色主题 | 跟随系统或手动切换浅色/深色主题 |
#### P1 — 重要功能
| ID | 功能 | 描述 |
|----|------|------|
| F09 | 两天法则 | 允许用户启用"两天法则"——允许间隔一天失败而不打断连续天数 |
| F10 | 习惯循环 (Cue-Routine-Reward) | 基于《原子习惯》的习惯循环模型,设置提示、例行、奖励 |
| F11 | 习惯合约 | 设置惩罚和问责伙伴,失败时显示惩罚提醒 |
| F12 | 统计总览 | 饼图展示所有习惯的整体完成率分布 |
| F13 | 个人统计 | 每个习惯的详细统计:最高连续、当前连续、月度柱状图 |
| F14 | 数值型习惯 | 支持设置目标值和单位的习惯类型(如 10000 步) |
| F15 | 进度追踪 | 数值型习惯可以记录部分进度,不要求一次性完成 |
| F16 | 分类系统 | 习惯可归属多个分类,支持按分类筛选显示 |
| F17 | 通知提醒 | 每日定时提醒用户打卡 |
| F18 | 备份/恢复 | 导出/导入 JSON 备份文件,支持跨设备数据迁移 |
| F19 | 引导页 | 首次使用时展示三步引导(定义习惯 → 记录天数 → 观察进步) |
| F20 | 归档功能 | 将不再追踪的习惯归档而非删除,保留历史数据 |
| F21 | 音效反馈 | 打卡时播放音效,增强即时满足感 |
| F22 | 备注/日记 | 每日打卡时可添加文字备注 |
#### P2 — 增强功能
| ID | 功能 | 描述 |
|----|------|------|
| F23 | Material You 主题 | Android 12+ 动态取色主题 |
| F24 | OLED 黑色主题 | 纯黑背景,适配 OLED 屏幕省电 |
| F25 | 自定义颜色 | 用户可自定义完成、失败、跳过、进度的颜色 |
| F26 | 生物识别锁 | 指纹/面容锁定应用,保护隐私 |
| F27 | 桌面小组件 | iOS/Android 主屏幕小组件显示今日完成进度 |
| F28 | 一键打卡 | 无需打开菜单,单击直接标记完成 |
| F29 | 深度链接 | 支持 `habo://` URL scheme 跳转到指定页面 |
| F30 | 周起始日设置 | 用户可选择周日或周一作为日历的起始日 |
| F31 | 显示月份名称 | 可选在日历中显示月份名称 |
| F32 | 27 种语言 | 支持中英日韩等 27 种语言的完整界面翻译 |
### 2.2 功能依赖关系
```
F01 (创建) ──→ F02 (打卡) ──→ F04 (连续天数)
│ │
│ ├──→ F12 (统计总览)
│ └──→ F13 (个人统计)
├──→ F03 (日历视图)
├──→ F05 (列表) ──→ F16 (分类)
├──→ F06 (编辑/删除)
├──→ F10 (习惯循环) ──→ F11 (习惯合约)
├──→ F14 (数值型) ──→ F15 (进度追踪)
└──→ F20 (归档)
```
---
## 3. 非功能性需求
### 3.1 平台支持
| 平台 | 最低版本 | 说明 |
|------|----------|------|
| Android | API 21 (Android 5.0) | 支持 split-per-abi APK 分包 |
| iOS | 12.0+ | 完整功能 |
| Linux | - | 通过 sqflite_common_ffi 支持 |
| macOS | - | 原生窗口管理 |
### 3.2 性能要求
| 指标 | 要求 |
|------|------|
| 启动时间 | 冷启动 < 2 秒(含 splash screen |
| 日历切换 | 月份切换流畅 60fps无明显卡顿 |
| 数据容量 | 支持至少 100 个习惯 × 365 天的事件记录 |
| 备份文件 | 支持 10MB 以内的备份文件导入 |
| 内存占用 | 日常使用 < 100MB RAM |
### 3.3 安全与隐私
| 要求 | 实现方式 |
|------|----------|
| 数据不离开设备 | 纯 SQLite 本地存储,无网络请求 |
| 生物识别保护 | 通过 `local_auth` 调用系统指纹/面容识别 |
| 备份文件控制 | 用户手动导出/导入,应用不自动上传 |
| 无第三方分析 | 不集成 Google Analytics、Firebase 等追踪服务 |
### 3.4 可访问性
| 要求 | 说明 |
|------|------|
| Material Design 规范 | 遵循 Material Design 无障碍指南 |
| 高对比度 | 深色/OLED 主题提供高对比度 |
| 大字体支持 | 响应系统字体缩放设置 |
| 语义化标签 | Flutter Semantics 为屏幕阅读器提供语义信息 |
---
## 4. 用户故事
### 4.1 核心用户故事
| 编号 | 用户故事 | 验收标准 |
|------|---------|----------|
| US01 | 作为用户,我想快速创建一个新习惯,以便开始追踪 | 输入标题 → 点击保存 → 习惯出现在列表中 |
| US02 | 作为用户,我想在日历上点击某天标记完成,以便记录我的每日进度 | 点击日期 → 选择"完成" → 日历显示绿色标记 |
| US03 | 作为用户,我想看到当前连续天数,以便获得坚持的动力 | 连续 2 天以上完成时显示绿色连续天数徽章 |
| US04 | 作为用户,我想查看月度统计图表,以便了解长期趋势 | 统计页显示柱状图,可切换年份和事件类型 |
| US05 | 作为用户,我想导出数据备份,以便在更换设备时迁移数据 | 设置页点击备份 → 生成 JSON 文件 → 保存到文件系统 |
| US06 | 作为用户,我想设置每日提醒通知,以便不忘记打卡 | 设置通知时间 → 每天定时收到通知 |
| US07 | 作为用户,我想追踪数值型目标(如每天 8 杯水),以便记录部分完成 | 创建数值习惯 → 输入进度值 → 查看百分比完成度 |
| US08 | 作为用户,我想启用两天法则,以便在偶尔失败时不中断连续记录 | 开启两天法则 → 失败一天后次日完成 → 连续天数不中断 |
| US09 | 作为用户,我想用分类管理习惯,以便快速筛选查看 | 创建分类 → 习惯关联分类 → 按分类筛选列表 |
| US10 | 作为用户,我想归档不再追踪的习惯,以便保持列表整洁但不丢失历史 | 点击归档 → 习惯从主列表消失 → 可在归档列表中查看 |
### 4.2 边界场景
| 场景 | 期望行为 |
|------|----------|
| 用户 31 天没有任何事件记录 | 连续天数显示为 0无徽章 |
| 用户连续完成 100 天 | 连续天数正确显示 100 |
| 用户在两天法则下连续两天失败 | 连续天数归零 |
| 数值习惯进度超过目标值 | 标记为已完成,进度条满格 |
| 备份文件格式错误 | 显示错误提示,不覆盖现有数据 |
| 应用从后台恢复且已启用生物识别 | 弹出认证对话框,认证失败可重试 |
| 跨午夜使用应用23:59 → 00:01 | 自动检测日变化,刷新日历视图 |
| 空标题尝试保存 | 显示错误提示"The habit title cannot be empty" |
---
## 5. 设计原则
| 原则 | 说明 |
|------|------|
| **极简优先** | 默认只显示标题和日历,高级选项折叠隐藏 |
| **即时反馈** | 每次打卡有视觉(颜色变化)和听觉(音效)反馈 |
| **正向强化** | 奖励通知优先于惩罚通知,连续天数优先于失败天数 |
| **零配置可用** | 安装后即可使用,不需要注册、登录或网络 |
| **用户控制** | 所有数据可导出、可删除,用户完全掌控 |

482
docs/02-ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,482 @@
# Habo 架构设计文档
> 基于 REQUIREMENTS.md 中的需求,说明架构决策及其理由
---
## 1. 架构决策总览
### 1.1 为什么选择这个架构
| 决策 | 理由 |
|------|------|
| **纯客户端架构** | 需求零网络依赖、数据不离开设备NFR-安全)。没有服务端意味着无运维成本、无数据泄露风险 |
| **SQLite 本地存储** | 需求持久化存储、离线可用。SQLite 是最成熟的嵌入式数据库,无需额外进程 |
| **Flutter 跨平台** | 需求:支持 Android/iOS/Linux/macOS 四个平台。一套代码覆盖所有目标平台 |
| **Provider + ChangeNotifier** | 需求:响应式 UI 更新。Flutter 官方推荐方案,学习曲线低,适合中等复杂度应用 |
| **Repository Pattern** | 需求:业务逻辑与数据访问解耦。便于替换数据源和编写单元测试 |
| **Navigation 2.0** | 需求深度链接支持habo://settings。声明式路由更易管理页面栈 |
### 1.2 架构约束
- **无网络** — 所有功能离线可用,备份通过文件系统完成
- **单用户** — 无需多用户系统,简化数据模型
- **单数据库** — 一个 SQLite 文件 `habo_db0.db`,数据库版本 9
- **无实时同步** — 数据只存在本地,跨设备迁移依赖手动备份/恢复
---
## 2. 分层架构
```
┌─────────────────────────────────────────────────────────────────┐
│ Presentation Layer (展示层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Screens │ │ Widgets │ │ Onboarding│ │ Dialogs │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │ │
├───────┴─────────────┴────────────┴──────────────┴─────────────────┤
│ Business Logic Layer (业务逻辑层) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ HabitsManager│ │SettingsManager│ │ Statistics │ │
│ │ (习惯 CRUD) │ │ (设置管理) │ │ (统计计算) │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
├─────────┴──────────────────┴────────────────────┴─────────────────┤
│ Service Layer (服务层) │
│ ┌────────────┐ ┌────────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Notification│ │ Backup │ │UIFeedback│ │ BiometricAuth │ │
│ │ Service │ │ Service │ │ Service │ │ Service │ │
│ └─────┬──────┘ └─────┬──────┘ └────┬─────┘ └──────┬────────┘ │
│ │ │ │ │ │
├────────┴───────────────┴─────────────┴───────────────┴────────────┤
│ Repository Layer (数据访问层) │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ HabitRepo │ │ EventRepo │ │ CategoryRepo │ │
│ │ (接口) │ │ (接口) │ │ (接口) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │ │
├─────────┴───────────────┴───────────────┴─────────────────────────┤
│ Data Layer (数据层) │
│ ┌──────────────────────────────────────────┐ │
│ │ HaboModel → SQLite (sqflite / ffi) │ │
│ │ 数据库: habo_db0.db (版本 9) │ │
│ └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
```
---
## 3. 模块职责划分
### 3.1 Presentation Layer
| 模块 | 文件 | 职责 |
|------|------|------|
| **HabitsScreen** | `habits/habits_screen.dart` | 主屏幕入口承载习惯列表、导航栏、FAB |
| **CalendarColumn** | `habits/calendar_column.dart` | 可拖拽排序的习惯列表容器,含分类筛选 |
| **Habit Card** | `habits/habit.dart` | 单个习惯卡片组件(含日历、连续天数、事件标记) |
| **EditHabit** | `habits/edit_habit.dart` | 创建/编辑习惯的表单页面 |
| **StatisticsScreen** | `statistics/statistics_screen.dart` | 统计总览页面 |
| **SettingsScreen** | `settings/settings_screen.dart` | 设置页面 |
| **Onboarding** | `onboarding/onboarding.dart` | 三步引导流程 |
**设计原则**
- 展示层不直接操作数据库,只通过 Manager 类
- 每个 Screen 是独立的 StatefulWidget
- 可复用的 UI 片段提取到 `widgets/` 目录
### 3.2 Business Logic Layer
#### HabitsManager核心管理器
**为什么需要 Manager 而非直接操作 Repository**
```
┌─────────────────────────────────────────────┐
│ HabitsManager │
│ ┌─────────────────────────────────────┐ │
│ │ 1. 内存状态管理 (allHabits 列表) │ │
│ │ 2. 业务规则执行 (排序、归档、undo) │ │
│ │ 3. 服务协调 (通知、备份、小组件) │ │
│ │ 4. 变更通知 (notifyListeners) │ │
│ └─────────────────────────────────────┘ │
│ │ │ │ │
│ HabitRepo EventRepo CategoryRepo │
│ │ │ │ │
│ NotificationService BackupService ... │
└─────────────────────────────────────────────┘
```
**职责清单**
- 维护 `allHabits` 内存列表(活跃 + 归档)
- 协调 Repository 的 CRUD 操作
- 调用通知服务更新提醒
- 调用小组件服务更新桌面小组件
- 调用 UI 反馈服务展示消息
- 处理 undo删除后可撤销
- 处理拖拽排序的位置更新
#### SettingsManager
**职责**
- 管理所有用户偏好设置(主题、音效、通知等)
- 使用 SharedPreferences 持久化
- 管理 SoLoud 音效引擎的初始化和播放
- 通知 UI 主题变化
#### Statistics
**为什么统计是纯函数而非 Manager**
统计计算是无状态的——每次从当前习惯数据实时计算,不需要维护额外状态。因此设计为纯静态方法 `Statistics.calculateStatistics()`
### 3.3 Service Layer
**为什么需要 Service 层?**
跨领域的关注点通知、备份、UI反馈、认证不属于任何单一 Manager 的职责,提取为独立服务便于复用和测试。
| 服务 | 依赖 | 被谁调用 |
|------|------|----------|
| NotificationService | awesome_notifications | HabitsManager |
| BackupService | RepositoryFactory, 文件系统 | HabitsManager, SettingsScreen |
| UIFeedbackService | ScaffoldMessenger | HabitsManager |
| BiometricAuthService | local_auth | BiometricAuthWrapper |
| HomeWidgetService | home_widget | HabitsManager |
| ServiceLocator | 所有服务 | main.dart (初始化) |
### 3.4 Repository Layer
**为什么用 Repository Pattern 而非直接用 HaboModel**
```
// 不好的方式 — UI 直接依赖数据层
HaboModel().insertHabit(habit);
// 好的方式 — 通过抽象接口解耦
HabitRepository habitRepo = RepositoryFactory.createHabitRepository(model);
habitRepo.createHabit(habit);
```
好处:
1. **可测试** — 可以用 MockRepository 替换真实数据库
2. **可替换** — 未来换数据库引擎只需改 Repository 实现
3. **关注点分离** — UI/Manager 不知道也不关心数据如何存储
### 3.5 Data Layer (HaboModel)
**定位**:直接操作 SQLite 的底层类,是整个数据层的基石。
**特殊处理**
- 移动端使用 `sqflite`
- 桌面端Linux/macOS使用 `sqflite_common_ffi`
- 启用 `PRAGMA foreign_keys = ON` 确保级联删除
- 管理数据库版本升级迁移(版本 1→9
---
## 4. 数据流设计
### 4.1 用户打卡事件流
```
用户点击日历日期
├─── 一键打卡模式 ON
│ └── 直接切换 check/clear
└─── 一键打卡模式 OFF
└── 弹出选择菜单
├── Check (完成)
├── Progress (进度,数值型)
├── Fail (失败)
├── Skip (跳过)
├── Note (备注)
└── Date (修改日期)
HabitsManager.addEvent(id, date, eventData)
┌─────────┴─────────┐
│ │
内存状态更新 Repository 写入
habitData.events EventRepository.add()
│ │
│ SQLite INSERT/REPLACE
│ │
▼ ▼
notifyListeners() NotificationService
│ (奖励/惩罚通知)
Provider → Widget 重建
(日历标记更新、连续天数更新)
```
### 4.2 应用初始化流
```
main()
├── 1. WidgetsFlutterBinding.ensureInitialized()
├── 2. SettingsManager.loadData() ← SharedPreferences
├── 3. HaboModel.initDatabase() ← SQLite 初始化
├── 4. ServiceLocator.init(model) ← 注册所有服务
├── 5. HabitsManager(repos, services) ← 注入依赖
├── 6. HabitsManager.loadHabits() ← 从数据库加载所有习惯
├── 7. NotificationService 初始化
├── 8. AppRouter(stateManagers) ← 创建路由
├── 9. 日变化定时器启动 ← 检测跨日刷新
└── runApp(MultiProvider → MaterialApp.router)
├── ChangeNotifierProvider<SettingsManager>
├── ChangeNotifierProvider<HabitsManager>
└── ChangeNotifierProvider<AppStateManager>
```
### 4.3 跨日刷新流
```
DayChangeTimer (每小时检查)
├── 检测到日期变化 (now.day != lastDay.day)
│ │
│ ├── HabitsManager.loadHabits() ← 重新加载习惯数据
│ ├── NotificationService.reset() ← 重置通知调度
│ └── HomeWidgetService.update() ← 更新桌面小组件
└── 未变化 → 等待下次检查
```
---
## 5. 导航架构
### 5.1 为什么选择 Navigation 2.0
| Navigation 1.0 (Navigator) | Navigation 2.0 (RouterDelegate) |
|---------------------------|----------------------------------|
| 命令式 push/pop | 声明式页面栈 |
| 不支持深度链接 | 原生支持 URL 映射 |
| 难以管理复杂页面栈 | 通过状态管理器统一控制 |
### 5.2 页面栈结构
```
AppRouter (RouterDelegate)
├── SplashScreen ← 初始页(条件判断后自动跳转)
│ │
│ ├── 首次使用 → OnboardingScreen
│ ├── 有新版本 → WhatsNewScreen
│ └── 正常使用 → HabitsScreen (主页面)
├── HabitsScreen ← 主页面(始终在栈底)
│ ├── → StatisticsScreen
│ ├── → SettingsScreen
│ ├── → CreateHabitScreen
│ └── → EditHabitScreen
└── 深度链接: habo://settings → 直接打开 SettingsScreen
```
### 5.3 状态驱动导航
```
AppStateManager (ChangeNotifier)
├── _statistics: bool → 控制 StatisticsScreen 显示
├── _settings: bool → 控制 SettingsScreen 显示
├── _onboarding: bool → 控制 OnboardingScreen 显示
├── _whatsNew: bool → 控制 WhatsNewScreen 显示
├── _createHabit: bool → 控制 CreateHabitScreen 显示
└── _editHabit: bool → 控制 EditHabitScreen 显示
AppRouter 监听 AppStateManager 变更
└── 根据 bool 标志位组合构建 pages 列表
```
**关键设计决策**`AppRouter` 不监听 `HabitsManager`,因为习惯数据变化不应触发导航跳转,只应刷新当前页面内容。
---
## 6. 状态管理策略
### 6.1 Provider 分布
```
MultiProvider(
providers: [
ChangeNotifierProvider<SettingsManager> ← 主题、音效、设置
ChangeNotifierProvider<HabitsManager> ← 习惯数据、分类
ChangeNotifierProvider<AppStateManager> ← 导航状态
]
)
```
### 6.2 读写模式
```dart
// 读取 — context.watch<T>() 或 Provider.of<T>(context)
// UI 组件监听变化并自动重建
final habits = context.watch<HabitsManager>().activeHabits;
// 写入 — context.read<T>() 或 Provider.of<T>(context, listen: false)
// 事件处理中调用方法,不触发当前 widget 重建
context.read<HabitsManager>().addEvent(id, date, event);
```
### 6.3 状态生命周期
| 状态 | 作用域 | 生命周期 |
|------|--------|----------|
| HabitsManager.allHabits | 全局 | 应用启动到关闭 |
| SettingsManager.* | 全局 | 应用启动到关闭 |
| AppStateManager.* | 全局 | 应用启动到关闭 |
| HabitData.events | 每个习惯 | 随 HabitsManager 加载 |
| 习惯卡片 UI 状态 (streak, calendar) | 单个 Widget | Widget 生命周期 |
---
## 7. 主题系统设计
### 7.1 为什么支持 5 种主题模式
| 主题 | 目标用户 | 技术实现 |
|------|----------|----------|
| Device | 大多数用户 | 跟随系统 MediaQuery |
| Light | 强制浅色偏好 | 固定浅色 ColorScheme |
| Dark | 强制深色偏好 | 固定深色 ColorScheme |
| OLED | OLED 屏幕用户 | 纯黑 (#000000) 背景 |
| Material You | Android 12+ 用户 | dynamic_color 提取壁纸颜色 |
### 7.2 颜色体系
```
核心颜色常量:
primary: #09BF30 (完成/主色)
red: #F44336 (失败)
skip: #FBC02D (跳过)
orange: #FF9800 (两天法则警告)
progress: #2196F3 (进度)
progressBg: #E3F2FD (进度背景)
用户可自定义:
checkColor: 默认 primary
failColor: 默认 red
skipColor: 默认 skip
progressColor: 默认 progress
```
---
## 8. 通知系统设计
### 8.1 通知类型
| 类型 | 触发条件 | 内容 |
|------|----------|------|
| **每日提醒** | 用户设定时间 | "Do not forget to check your habits." |
| **奖励通知** | 习惯标记完成 + showReward 开启 | "Congratulations! Your reward: {reward}" |
| **惩罚通知** | 习惯标记失败 + showSanction 开启 | "Oh no! Your sanction: {sanction}" |
### 8.2 通知调度策略
```
创建/编辑习惯
└── NotificationService.resetNotifications()
├── 取消所有现有通知
└── 为每个启用通知的习惯创建定时通知
└── awesome_notifications.createNotification()
├── channel: "habit_notifications"
├── schedule: 每日重复 at notTime
└── payload: habitId
删除习惯
└── NotificationService.removeNotifications(id)
```
---
## 9. 备份系统设计
### 9.1 为什么选择 JSON 文件而非二进制
- **用户可读** — 用户可以打开 JSON 查看自己的数据
- **调试友好** — 开发时可直接检查备份内容
- **版本控制** — 可以 git diff 对比变化
- **跨平台** — JSON 在所有平台上通用
### 9.2 备份文件格式
```json
{
"version": 3,
"habits": [...],
"events": { "habitId": { "date": [dayType, ...] } },
"categories": [...],
"habit_categories": [{ "habit_id": 1, "category_id": 1 }],
"metadata": {
"imported_from": "legacy_list",
"import_timestamp": "ISO8601"
}
}
```
### 9.3 兼容性策略
- 新格式包含 `version` 字段用于版本识别
- 支持读取旧版数组格式(无 version 字段 = 旧版)
- 导入时自动转换为当前格式
- 文件大小限制 10MB
---
## 10. 依赖注入设计
### 10.1 ServiceLocator 模式
```
ServiceLocator (单例)
├── 提供:
│ ├── RepositoryFactory → 创建各 Repository
│ ├── NotificationService
│ ├── BackupService
│ ├── UIFeedbackService
│ ├── BiometricAuthService
│ └── HomeWidgetService
├── 初始化时接收:
│ └── HaboModel (共享数据库连接)
└── 使用方:
└── main.dart 中创建并注入到 HabitsManager
```
### 10.2 HabitsManager 的依赖注入
```dart
HabitsManager(
habitRepository: repoFactory.habitRepository,
eventRepository: repoFactory.eventRepository,
categoryRepository: repoFactory.categoryRepository,
backupService: serviceLocator.backupService, // 可选
notificationService: serviceLocator.notificationService, // 可选
uiFeedbackService: serviceLocator.uiFeedbackService, // 可选
)
```
可选服务的设计使得在测试时可以传入 null方便隔离测试业务逻辑。
---
## 11. 错误处理策略
| 层级 | 策略 | 用户体验 |
|------|------|----------|
| Data Layer (HaboModel) | try-catch + debugPrint | 静默失败,不中断应用 |
| Repository Layer | 抛出异常 | 向上传播 |
| Service Layer | 返回结果对象 (BackupResult) | UI 反馈服务展示错误消息 |
| Manager Layer | catch + UIFeedbackService.showError() | 用户看到错误提示 |
| Presentation Layer | FutureBuilder 处理 loading/error | 加载指示器 + 错误状态 |

807
docs/03-SPECIFICATION.md Normal file
View File

@@ -0,0 +1,807 @@
# Habo 实现规格文档
> 基于 REQUIREMENTS.md 和 ARCHITECTURE.md定义每个功能的具体实现逻辑、算法和交互流程
---
## 1. 连续天数 (Streak) 算法
### 1.1 普通模式 (`_updateLastStreakNormal`)
```
输入: habitData.events (SplayTreeMap<DateTime, List>, 按日期升序排列)
输出: streak 值, streakVisible (bool), orangeStreak (bool)
算法:
1. 从最后一天开始,向前遍历 events
2. 初始化 inStreak = 0
3. 对每个事件(从后往前):
a. 如果 DayType == clear → 跳过
b. 如果日期间隔 > 1 天 → 断开,结束循环
c. 如果 DayType == check → inStreak++
d. 如果 DayType == progress (数值型):
- 如果 progressValue >= targetValue → inStreak++
- 否则 → 跳过
e. 如果 DayType == fail 或 skip → 断开,结束循环
4. streak = inStreak
5. streakVisible = (streak >= 2)
6. orangeStreak = false
```
### 1.2 两天法则模式 (`_updateLastStreakTwoDay`)
```
输入: habitData.events, habitData.twoDayRule == true
输出: streak 值, streakVisible, orangeStreak
算法:
1. 从最后一天开始,向前遍历 events
2. 变量: inStreak = 0, usingTwoDayRule = false
3. 对每个事件(从后往前):
a. 如果 DayType == clear → 跳过
b. 如果日期间隔 > 1 天 → 断开,结束循环
c. 如果 DayType == check → inStreak++, usingTwoDayRule = false
d. 如果 DayType == progress (数值型) 且 progress >= target → inStreak++, usingTwoDayRule = false
e. 如果 DayType == fail:
- 如果 usingTwoDayRule == true → 断开,结束循环(连续两天失败)
- 如果 usingTwoDayRule == false → usingTwoDayRule = true, 不增加 inStreak
f. 如果 DayType == skip:
- 如果 usingTwoDayRule == true → 断开
- 否则 → 跳过(不影响连续)
4. streak = inStreak
5. streakVisible = (streak >= 2)
6. orangeStreak = usingTwoDayRule ← 橙色表示"处于危险中"
```
**两天法则图解**
```
情况 1: ✅ ✅ ❌ ✅ ✅ → streak = 5 (✅ 失败一天后立即恢复)
情况 2: ✅ ✅ ❌ ❌ ✅ → streak = 0 (✅ 连续两天失败,归零)
情况 3: ✅ ✅ ❌ ⏭ ✅ → streak = 0 (⏭ 在两天法则期间跳过,归零)
情况 4: ✅ ✅ ⏭ ✅ ✅ → streak = 5 (⏭ 跳过不影响连续)
```
---
## 2. 日历事件交互流程
### 2.1 日历日期点击
```
用户点击日历日期
├── 检查 oneTapCheck 设置
│ │
│ ├── oneTapCheck == true (一键打卡模式):
│ │ │
│ │ ├── 布尔型习惯:
│ │ │ ├── 当前无事件 → 创建 [DayType.check, ""]
│ │ │ └── 当前有事件 → 删除事件 (设为 clear)
│ │ │
│ │ └── 数值型习惯:
│ │ ├── 当前无事件 → 创建 [DayType.check, "", targetValue, targetValue]
│ │ └── 当前有事件 → 删除事件
│ │
│ └── oneTapCheck == false (菜单模式):
│ │
│ └── 弹出选择菜单6 个选项:
│ │
│ ├── 📅 Date → 修改日期选择器
│ │
│ ├── ✅ Check → 标记完成
│ │ ├── 布尔型: 事件 = [DayType.check, ""]
│ │ └── 数值型: 事件 = [DayType.check, "", targetValue, targetValue]
│ │ └── 播放 check 音效
│ │ └── 如果 showReward → 显示奖励通知
│ │
│ ├── Plus/Progress → 数值型专用
│ │ └── 弹出 ProgressInputModal
│ │ ├── 圆形进度指示器 (120px)
│ │ ├── 当前值 / 目标值 显示
│ │ ├── 快捷按钮: +partialValue, -partialValue
│ │ ├── 直接输入文本框
│ │ └── "Complete" 按钮直接设为目标值
│ │
│ ├── ❌ Fail → 标记失败
│ │ └── 事件 = [DayType.fail, ""]
│ │ └── 播放 click 音效
│ │ └── 如果 showSanction → 显示惩罚通知
│ │
│ ├── ⏭ Skip → 标记跳过
│ │ └── 事件 = [DayType.skip, ""]
│ │ └── 播放 click 音效
│ │
│ └── 💬 Note → 添加备注
│ └── 弹出文本输入对话框
│ └── 保留原有事件类型,更新 comment
HabitsManager.addEvent(habitId, date, eventData)
├── 更新内存: habitData.events[date] = eventData
├── 写入数据库: EventRepository → SQLite REPLACE
├── 更新连续天数: _updateLastStreak()
├── 更新桌面小组件: HomeWidgetService.update()
└── notifyListeners() → UI 重建
```
### 2.2 事件数据结构
```dart
// 布尔型习惯
[DayType.check, ""] // 完成
[DayType.fail, ""] // 失败
[DayType.skip, ""] // 跳过
[DayType.check, "好的开始"] // 完成 + 备注
// 数值型习惯
[DayType.check, "", 5.0, 5.0] // 完成 (5/5 km)
[DayType.progress, "", 3.5, 5.0] // 部分进度 (3.5/5 km)
[DayType.fail, ""] // 失败
// 数组索引:
// [0] = DayType (枚举)
// [1] = comment (String)
// [2] = progressValue (double, 数值型)
// [3] = targetValue (double, 数值型)
```
---
## 3. 统计计算算法
### 3.1 数据结构
```dart
class StatisticsData {
String title; // 习惯标题
int topStreak = 0; // 最高连续天数
int actualStreak = 0; // 当前连续(遍历中)
int checks = 0; // 完成次数
int fails = 0; // 失败次数
int skips = 0; // 跳过次数
int progress = 0; // 进度次数
SplayTreeMap<int, Map<DayType, List<int>>> monthlyTracking;
// key = year * 100 + month (如 202604)
// value = { DayType: [day1, day2, ...] }
}
class OverallStatisticsData {
int totalChecks;
int totalFails;
int totalSkips;
int totalProgress;
}
```
### 3.2 计算流程
```
Statistics.calculateStatistics(habits):
├── 1. 创建 AllStatistics 容器
├── 2. 遍历每个 habit:
│ │
│ ├── 创建 StatisticsData
│ │
│ ├── 3. 遍历 events (按日期升序):
│ │ │
│ │ ├── 计算日期间隔:
│ │ │ └── 如果间隔 > 1 天 → actualStreak 归零
│ │ │
│ │ ├── DayType.check:
│ │ │ ├── checks++
│ │ │ ├── actualStreak++
│ │ │ └── if actualStreak > topStreak → topStreak = actualStreak
│ │ │
│ │ ├── DayType.progress:
│ │ │ ├── progress++
│ │ │ └── if 数值型 && progressValue >= targetValue:
│ │ │ ├── actualStreak++
│ │ │ └── update topStreak
│ │ │
│ │ ├── DayType.fail:
│ │ │ ├── fails++
│ │ │ └── if twoDayRule:
│ │ │ ├── if usingTwoDayRule → actualStreak = 0
│ │ │ └── else → usingTwoDayRule = true
│ │ │ └── else → actualStreak = 0
│ │ │
│ │ ├── DayType.skip:
│ │ │ ├── skips++
│ │ │ └── if usingTwoDayRule → actualStreak = 0
│ │ │
│ │ └── 记录到 monthlyTracking:
│ │ └── key = year * 100 + month
│ │ └── monthlyTracking[key][dayType].add(day)
│ │
│ └── 添加到 allStatistics
└── 4. 汇总 OverallStatisticsData:
├── totalChecks = sum(各习惯 checks)
├── totalFails = sum(各习惯 fails)
├── totalSkips = sum(各习惯 skips)
└── totalProgress = sum(各习惯 progress)
```
---
## 4. 习惯 CRUD 交互流程
### 4.1 创建习惯
```
用户点击 FAB (+)
└── AppStateManager.goCreateHabit(true)
└── EditHabitScreen(isNew: true)
├── 表单字段:
│ ├── title (必填, 不能为空)
│ ├── habitType (下拉: Checkable / Progressive)
│ │ └── 如果 Progressive:
│ │ ├── targetValue (NumberFormat('#.##'))
│ │ ├── partialValue
│ │ └── unit
│ ├── twoDayRule (Checkbox)
│ ├── categories (多选)
│ ├── notification (Checkbox)
│ │ └── notTime (TimePicker)
│ └── [展开] Advanced:
│ ├── cue (提示触发器)
│ ├── routine (例行动作)
│ ├── reward (奖励)
│ ├── showReward (显示奖励通知)
│ ├── sanction (惩罚)
│ ├── showSanction (显示惩罚通知)
│ └── accountant (问责伙伴)
└── 点击保存 (FAB ✓):
├── 验证 title 非空
├── 创建 HabitData:
│ ├── id: null (数据库自增)
│ ├── position: activeHabits.length (追加到末尾)
│ └── ... 各字段
├── HabitsManager.addHabit(habit)
│ ├── HabitRepository.createHabit() → INSERT
│ ├── CategoryRepository.updateHabitCategories()
│ ├── 如果 notification → NotificationService.reset()
│ └── notifyListeners()
└── 导航回主页面
```
### 4.2 编辑习惯
```
用户点击习惯卡片标题区域
└── AppStateManager.goEditHabit(true)
└── EditHabitScreen(isNew: false, habit: currentHabit)
├── 表单预填充现有数据
├── 额外按钮:
│ ├── 归档/取消归档 (FAB 左侧)
│ └── 删除 (AppBar)
│ └── 直接删除,无确认对话框
└── 点击保存:
├── 更新 HabitData 字段
├── HabitsManager.editHabit(habit)
│ ├── HabitRepository.updateHabit() → UPDATE
│ ├── CategoryRepository.updateHabitCategories()
│ ├── NotificationService.reset()
│ └── notifyListeners()
└── 导航回主页面
```
### 4.3 删除习惯
```
用户在编辑页点击删除按钮
└── HabitsManager.deleteHabit(id)
├── 从内存列表中移除
├── HabitRepository.deleteHabit() → DELETE FROM habits WHERE id = ?
│ └── CASCADE DELETE events, habit_categories
├── NotificationService.removeNotifications(id)
├── HomeWidgetService.update()
├── UIFeedbackService.showMessageWithAction(
│ "Habit deleted.",
│ "Undo",
│ () => undoDelete()
│ )
└── notifyListeners()
```
### 4.4 归档/取消归档
```
归档:
HabitsManager.archiveHabit(id)
├── 更新 habit.archived = true
├── HabitRepository.updateHabit()
├── NotificationService.removeNotifications(id)
├── UIFeedbackService.showSuccess("Habit archived")
└── notifyListeners()
取消归档:
HabitsManager.unarchiveHabit(id)
├── 更新 habit.archived = false
├── HabitRepository.updateHabit()
├── 如果 habit.notification → NotificationService.reset()
├── UIFeedbackService.showSuccess("Habit unarchived")
└── notifyListeners()
```
### 4.5 拖拽排序
```
用户长按拖动习惯卡片到新位置
└── onReorder(oldIndex, newIndex)
├── 调整 newIndex (如果 oldIndex < newIndex → newIndex--)
├── 列表操作: list.removeAt(oldIndex), list.insert(newIndex, item)
├── 更新所有习惯的 position 字段
├── HabitsManager.reorderList(oldIndex, newIndex)
│ ├── 更新内存列表
│ ├── HabitRepository.updateHabitsOrder() → UPDATE position
│ └── notifyListeners()
└── UI 自动刷新
```
---
## 5. 数值型习惯进度输入
### 5.1 ProgressInputModal 交互
```
弹出 ProgressInputModal
├── 显示:
│ ├── 标题: "Save Progress"
│ ├── 圆形进度指示器 (120px 直径)
│ │ ├── 未完成: 显示百分比 (如 "70%")
│ │ ├── 已完成: 显示 ✓ 图标
│ │ └── 超出: 显示百分比 (> 100%)
│ ├── 当前值 / 目标值 显示
│ └── 控制按钮行:
│ ├── [-] 减少 partialValue
│ ├── [+] 增加 partialValue
│ └── [Complete] 直接设为 targetValue
├── 用户操作:
│ ├── 点击 [+]:
│ │ ├── currentValue += partialValue
│ │ └── clamp(0, targetValue * 2)
│ ├── 点击 [-]:
│ │ ├── currentValue -= partialValue
│ │ └── clamp(0, targetValue * 2)
│ ├── 点击 Complete:
│ │ └── currentValue = targetValue
│ ├── 点击当前值:
│ │ └── 弹出文本输入框直接编辑
│ └── 点击 Save:
│ ├── 确定 DayType:
│ │ ├── currentValue >= targetValue → DayType.check
│ │ └── currentValue < targetValue → DayType.progress
│ ├── 创建事件: [DayType, "", currentValue, targetValue]
│ └── 返回事件数据给调用方
└── 点击 Cancel → 返回 null
```
---
## 6. 日历组件行为
### 6.1 日历格式
```
月视图 → 显示整月
周视图 → 显示一周
切换触发:
├── 用户点击日历标题区域 → 切换格式
└── 月份切换时重置为月视图
```
### 6.2 日历日期标记
```
每个日期根据当天的 event[0] 显示不同颜色的圆点:
DayType.check → checkColor (默认 #09BF30 绿色)
DayType.fail → failColor (默认 #F44336 红色)
DayType.skip → skipColor (默认 #FBC02D 黄色)
DayType.progress → progressColor (默认 #2196F3 蓝色)
DayType.clear → 无标记
今天特殊显示:
→ 外圈高亮环
```
### 6.3 月份名称显示
```
如果 showMonthName 设置为 true:
→ 在日历上方显示当前月份名称文本
如果 false:
→ 不显示
```
---
## 7. 设置项完整规格
### 7.1 所有设置项及默认值
| 设置项 | 类型 | 默认值 | 持久化 Key |
|--------|------|--------|------------|
| theme | Themes 枚举 | Themes.device | theme |
| weekStart | StartingDayOfWeek | monday | weekStart |
| showDailyNot | bool | true | showDailyNot |
| dailyNotTime | TimeOfDay | 20:00 | dailyNotTime |
| soundEffects | bool | true | soundEffects |
| soundVolume | double | 3.0 (范围 0-5) | soundVolume |
| biometricLock | bool | false | biometricLock |
| oneTapCheck | bool | false | oneTapCheck |
| showMonthName | bool | true | showMonthName |
| showCategories | bool | true | showCategories |
| seenOnboarding | bool | false | seenOnboarding |
| lastWhatsNewVersion | String | '' | lastWhatsNewVersion |
| checkColor | Color | #09BF30 | checkColor |
| failColor | Color | #F44336 | failColor |
| skipColor | Color | #FBC02D | skipColor |
| progressColor | Color | #2196F3 | progressColor |
### 7.2 设置项分组 (UI 展示顺序)
```
外观 (Appearance):
├── Theme (下拉: Device / Light / Dark / OLED / Material You)
├── First day of the week (下拉: Su Mo Tu We Th Fr Sa)
├── Show month name (开关)
├── Show categories (开关)
└── Set colors (点击打开颜色选择器 × 4)
通知 (Notifications):
├── App notifications (开关) → 控制每日提醒
└── Notification time (时间选择器, 仅通知开启时可用)
音效 (Sound):
└── Sound effects (滑块 0-5, 左侧图标, 右侧数字)
安全 (Security):
├── Biometric Lock (开关, 需设备支持)
└── Single tap to check (开关)
数据管理:
├── Backup → Create (导出 JSON 文件)
├── Backup → Restore (导入 JSON 文件)
├── Onboarding (重播引导)
└── What's New (查看更新日志)
关于:
├── App 名称 + 版本号
├── Terms and Conditions (URL)
├── Privacy Policy (URL)
├── Source code (GitHub URL)
└── Support (捐赠 URL)
```
---
## 8. 备份/恢复流程
### 8.1 创建备份
```
用户点击 "Create" 备份按钮
└── BackupService.createDatabaseBackup()
├── 1. 从 HabitsManager 获取所有习惯
├── 2. 序列化每个习惯:
│ ├── id, position, title, twoDayRule, cue, routine, reward
│ ├── showReward, advanced, notification, notTime
│ ├── sanction, showSanction, accountant
│ ├── habitType, targetValue, partialValue, unit
│ └── events: { "YYYY-MM-DD": [dayTypeIndex, ...] }
├── 3. 序列化分类:
│ └── categories: [{ id, title, iconCodePoint, fontFamily }]
├── 4. 序列化关联:
│ └── habit_categories: [{ habit_id, category_id }]
├── 5. 添加元数据:
│ └── metadata: { import_timestamp, version }
├── 6. JSON.encode → 字符串
├── 7. 弹出文件保存对话框 (flutter_file_dialog / file_picker)
└── 8. 写入文件
├── 成功 → UIFeedbackService.showSuccess("Backup created successfully!")
└── 失败 → UIFeedbackService.showError("Backup failed!")
```
### 8.2 恢复备份
```
用户点击 "Restore" 备份按钮
└── 弹出确认对话框: "All habits will be replaced with habits from backup."
├── Cancel → 取消
└── Restore →
├── BackupService.loadBackup()
│ │
│ ├── 1. 弹出文件选择对话框
│ ├── 2. 读取文件内容
│ ├── 3. 验证:
│ │ ├── 文件是否存在
│ │ ├── 文件大小 <= 10MB
│ │ └── JSON 格式是否有效
│ ├── 4. 解析 JSON:
│ │ ├── 如果是数组 → 旧版格式,转换为新格式
│ │ └── 如果是对象 → 新版格式
│ ├── 5. 清空数据库:
│ │ ├── DELETE FROM habits
│ │ └── DELETE FROM events
│ ├── 6. 逐个插入习惯和事件
│ └── 7. 插入分类和关联
└── 成功后:
├── HabitsManager.loadHabits() ← 重新加载
├── NotificationService.reset() ← 重置通知
├── HomeWidgetService.update() ← 更新小组件
└── UIFeedbackService.showSuccess("Restore completed successfully!")
```
---
## 9. 引导页流程
```
首次启动应用
└── SettingsManager.seenOnboarding == false
└── 显示 OnboardingScreen
├── Step 1: "Define your habits"
│ ├── 插图: empty_list.svg
│ ├── 描述: "To better stick to your habits, you can define:"
│ ├── 概念 1: Cue (提示触发器)
│ ├── 概念 2: Routine (例行动作)
│ └── 概念 3: Reward (奖励)
├── Step 2: "Log your days"
│ ├── 插图: habit_tracking.svg
│ └── 每日操作:
│ ├── ✓ Successful (完成)
│ ├── + Progressive (进度)
│ ├── ✗ Not so successful (失败)
│ ├── ⏭ Skip (跳过)
│ └── 💬 Note (备注)
├── Step 3: "Observe your progress"
│ ├── 插图: progress.svg
│ └── 描述: "You can track your progress through the calendar
│ view in every habit or on the statistics page."
├── 导航: Skip (跳过) / Next (下一步) / Done (完成)
└── 完成:
├── SettingsManager.seenOnboarding = true
├── SettingsManager.saveData()
└── 导航到 HabitsScreen
```
---
## 10. 生物识别认证流程
```
应用启动 或 从后台恢复 (AppLifecycleState.resumed)
└── BiometricAuthWrapper
├── 检查 biometricLock 设置
│ ├── false → 直接显示内容
│ └── true →
│ │
│ ├── BiometricAuthService.authenticate()
│ │ ├── 获取可用生物识别类型
│ │ │ ├── 指纹 → "Fingerprint"
│ │ │ ├── 面容 → "Face ID"
│ │ │ ├── 虹膜 → "Iris"
│ │ │ └── 设备凭据 → "Device PIN, Pattern, or Password"
│ │ │
│ │ └── local_auth.authenticate(
│ │ localizedReason: "Please authenticate to access Habo",
│ │ biometricOnly: false
│ │ )
│ │
│ ├── 成功 → 显示内容
│ │
│ └── 失败 → 显示认证错误界面:
│ ├── 标题: "Authentication Required"
│ ├── 描述: "Please authenticate to access Habo"
│ ├── 图标: 指纹图标
│ └── "Try Again" 按钮 → 重新认证
└── 设备不支持生物识别:
├── showToast: "Please set up fingerprint/face unlock"
└── 自动关闭 biometricLock 设置
```
---
## 11. 桌面小组件数据更新
```
习惯事件变化时
└── HomeWidgetService.update()
├── 1. 计算今日习惯进度:
│ ├── 获取所有活跃习惯
│ ├── 计算今日已完成数 (DayType.check 或 progress >= target)
│ └── 计算总习惯数
├── 2. 写入小组件数据:
│ ├── HomeWidget.saveWidgetData<int>("habitsCompleted", count)
│ ├── HomeWidget.saveWidgetData<int>("habitsTotal", total)
│ └── HomeWidget.updateWidget()
└── 3. 小组件渲染:
├── CircularProgressPainter
│ └── 弧度 = (completed / total) * 2π
├── 中心显示: "completed / total"
└── 尺寸: 170 × 170
```
---
## 12. 通知调度逻辑
```
NotificationService.resetNotifications():
├── 1. 取消所有现有通知:
│ └── AwesomeNotifications().cancelAll()
├── 2. 检查全局通知开关:
│ └── if !showDailyNot → return
└── 3. 遍历所有活跃习惯:
└── if habit.notification:
├── 创建每日重复通知:
│ ├── id: habit.id
│ ├── channel: "habit_notifications"
│ ├── title: "Habo"
│ ├── body: "Do not forget to check your habits."
│ ├── schedule: 每日 at habit.notTime
│ └── payload: { habitId: habit.id }
└── AwesomeNotifications().createNotification()
```
---
## 13. 完整数据库 Schema
### 13.1 habits 表
```sql
CREATE TABLE habits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
position INTEGER, -- 排序权重
title TEXT NOT NULL, -- 习惯标题 (必填)
twoDayRule INTEGER DEFAULT 0, -- 两天法则 0=关 1=开
cue TEXT DEFAULT '', -- 提示触发器
routine TEXT DEFAULT '', -- 例行动作
reward TEXT DEFAULT '', -- 奖励
showReward INTEGER DEFAULT 0, -- 显示奖励 0=关 1=开
advanced INTEGER DEFAULT 0, -- 高级模式 0=关 1=开
notification INTEGER DEFAULT 0, -- 通知开关 0=关 1=开
notTime TEXT DEFAULT '', -- 通知时间 "HH:MM"
sanction TEXT DEFAULT '', -- 惩罚描述
showSanction INTEGER DEFAULT 0, -- 显示惩罚 0=关 1=开
accountant TEXT DEFAULT '', -- 问责伙伴
habitType INTEGER DEFAULT 0, -- 0=布尔 1=数值
targetValue REAL DEFAULT 1.0, -- 目标值
partialValue REAL DEFAULT 1.0, -- 部分增量
unit TEXT DEFAULT '', -- 单位
archived INTEGER DEFAULT 0 -- 0=活跃 1=归档
);
```
### 13.2 events 表
```sql
CREATE TABLE events (
id INTEGER NOT NULL, -- FK → habits.id
dateTime TEXT NOT NULL, -- ISO8601 日期字符串
dayType INTEGER NOT NULL, -- 0=clear 1=check 2=fail 3=skip 4=progress
comment TEXT DEFAULT '', -- 备注
progressValue REAL DEFAULT 0.0, -- 进度值 (数值型)
targetValue REAL DEFAULT 0.0, -- 目标值快照 (数值型)
PRIMARY KEY (id, dateTime),
FOREIGN KEY (id) REFERENCES habits(id) ON DELETE CASCADE
);
```
### 13.3 categories 表
```sql
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, -- 分类名称
iconCodePoint INTEGER NOT NULL, -- IconData.codePoint
fontFamily TEXT -- 字体族 (如 fontAwesomeFlutter)
);
```
### 13.4 habit_categories 关联表
```sql
CREATE TABLE habit_categories (
habit_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
PRIMARY KEY (habit_id, category_id),
FOREIGN KEY (habit_id) REFERENCES habits(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
```
---
## 14. 跨日自动刷新
```
应用启动时:
└── _startDayChangeTimer()
└── Timer.periodic(Duration(hours: 1), callback)
├── 记录当前日期: lastDate = DateTime.now().day
└── 每小时检查:
├── if DateTime.now().day != lastDate:
│ ├── lastDate = DateTime.now().day
│ ├── HabitsManager.loadHabits() ← 重载数据
│ ├── NotificationService.reset() ← 重置通知
│ └── HomeWidgetService.update() ← 更新小组件
└── else → 无操作
应用暂停时:
└── _stopDayChangeTimer() ← 暂停定时器
应用恢复时:
└── 检查日期变化 → 刷新 → _startDayChangeTimer() ← 恢复定时器
```
---
## 15. 颜色选择器交互
```
用户点击颜色设置项
└── 弹出 ColorIcon 对话框
├── 显示 HueRingPicker:
│ ├── 色相环 (360°)
│ └── 饱和度/亮度选择区域
├── 当前选中颜色的实时预览
├── 重置按钮 → 恢复默认颜色
└── 确认 → SettingsManager 更新颜色值
├── SharedPreferences 保存 (ARGB int)
└── notifyListeners() → 全局主题刷新
```

547
docs/04-ASSETS.md Normal file
View File

@@ -0,0 +1,547 @@
# Habo 可复用素材清单
> 从原项目提取的静态资源、国际化文本、测试用例,作为 AI 复刻项目的起点素材
---
## 1. 静态资源清单
### 1.1 图片资源 (`assets/images/`)
| 文件 | 用途 | 格式 |
|------|------|------|
| `icon.png` | Android 应用图标 | PNG |
| `ios_icon.jpg` | iOS 应用图标 | JPG |
| `macos_icon.png` | macOS 应用图标 | PNG |
| `app_icon.png` | 通用应用图标 | PNG |
| `splash_icon.png` | 启动画面图标 | PNG |
| `splash_icon2.png` | 备用启动图标 | PNG |
| `android_foreground.png` | Android 自适应图标前景 | PNG |
| `android_background.png` | Android 自适应图标背景 | PNG |
| `android_monochrome.svg` | Android 单色图标 | SVG |
| `emptyList.svg` | 空列表占位图 | SVG |
| `noDataStatistics.svg` | 统计页空数据占位图 | SVG |
### 1.2 引导页图片 (`assets/images/onboard/`)
| 文件 | 用途 |
|------|------|
| `1.svg` | 第 1 步: 定义习惯 (空列表插图) |
| `2.svg` | 第 2 步: 记录天数 (习惯追踪插图) |
| `3.svg` | 第 3 步: 观察进步 (进度追踪插图) |
### 1.3 音效资源 (`assets/sounds/`)
| 文件 | 用途 | 说明 |
|------|------|------|
| `check.wav` | 打卡完成音效 | 成功完成的正向音效 |
| `click.wav` | 通用点击音效 | 失败/跳过等操作的反馈音效 |
| `sound_sources.txt` | 音效来源说明 | 开源协议信息 |
### 1.4 字体资源 (`assets/google_fonts/`)
**字体族**: Nunito18 个字重/样式变体)
| 文件 | 字重 |
|------|------|
| Nunito-ExtraLight.ttf | 200 |
| Nunito-Light.ttf | 300 |
| Nunito-Regular.ttf | 400 |
| Nunito-Medium.ttf | 500 |
| Nunito-SemiBold.ttf | 600 |
| Nunito-Bold.ttf | 700 |
| Nunito-ExtraBold.ttf | 800 |
| Nunito-Black.ttf | 900 |
| 以及对应的 Italic 变体 | |
许可证: OFL (SIL Open Font License)
---
## 2. 国际化文本 (英文基准 ARB)
> 完整的 `intl_en.arb`,包含 **354 个键**,可直接复制为项目的国际化基准文件
```json
{
"@@locale": "en",
"habits": "Habits:",
"statistics": "Statistics",
"emptyList": "Empty list",
"noDataAboutHabits": "There is no data about habits.",
"topStreak": "Top streak",
"currentStreak": "Current streak",
"total": "Total",
"unknown": "Unknown",
"warning": "Warning",
"allHabitsWillBeReplaced": "All habits will be replaced with habits from backup.",
"restore": "Restore",
"cancel": "Cancel",
"settings": "Settings",
"theme": "Theme",
"firstDayOfWeek": "First day of the week",
"notifications": "Notifications",
"notificationTime": "Notification time",
"soundEffects": "Sound effects",
"showMonthName": "Show month name",
"setColors": "Set colors",
"backup": "Backup",
"create": "Create",
"onboarding": "Onboarding",
"about": "About",
"habo": "Habo",
"copyright": "©2023 Habo",
"termsAndConditions": "Terms and Conditions",
"privacyPolicy": "Privacy Policy",
"disclaimer": "Disclaimer",
"sourceCode": "Source code (GitHub)",
"ifYouWantToSupport": "If you want to support Habo you can:",
"buyMeACoffee": "Buy me a coffee",
"reset": "Reset",
"done": "Done",
"congratulationsReward": "Congratulations! Your reward:",
"ohNoSanction": "Oh no! Your sanction:",
"month": "Month",
"week": "Week",
"habitLoop": "Habit loop",
"habitLoopDescription": "Habit Loop is a psychological model describing the process of habit formation. It consists of three components: Cue, Routine, and Reward. The Cue triggers the Routine (habitual action), which is then reinforced by the Reward, creating a loop that makes the habit more ingrained and likely to be repeated.",
"cue": "Cue",
"cueDescription": "is the trigger that initiates your habit. It could be a specific time, location, feeling, or an event.",
"routine": "Routine",
"routineDescription": "is the action you take in response to the cue. This is the habit itself.",
"reward": "Reward",
"rewardDescription": "is the benefit or positive feeling you experience after performing the routine. It reinforces the habit.",
"editHabit": "Edit Habit",
"createHabit": "Create Habit",
"delete": "Delete",
"habitTitleEmptyError": "The habit title can not be empty.",
"save": "Save",
"exercise": "Exercise",
"habit": "Habit",
"useTwoDayRule": "Use Two day rule",
"twoDayRule": "Two day rule",
"twoDayRuleDescription": "With two day rule, you can miss one day and do not lose a streak if the next day is successful.",
"advancedHabitBuilding": "Advanced habit building",
"advancedHabitBuildingDescription": "This section helps you better define your habits utilizing the Habit loop. You should define cues, routines, and rewards for every habit.",
"at7AM": "At 7:00AM",
"do50PushUps": "Do 50 push ups",
"fifteenMinOfVideoGames": "15 min. of video games",
"showReward": "Show reward",
"remainderOfReward": "The reminder of the reward after a successful routine.",
"habitContract": "Habit contract",
"habitContractDescription": "While positive reinforcement is recommended, some people may opt for a habit contract. A habit contract allows you to specify a sanction that will be imposed if you miss your habit, and may involve an accountability partner who helps supervise your goals.",
"donateToCharity": "Donate 10$ to charity",
"sanction": "Sanction",
"showSanction": "Show sanction",
"remainderOfSanction": "The reminder of the sanction after a unsuccessful routine.",
"dan": "Dan",
"accountabilityPartner": "Accountability partner",
"add": "Add",
"haboNeedsPermission": "Habo needs permission to send notifications to work properly.",
"allow": "Allow",
"date": "Date",
"check": "Check",
"fail": "Fail",
"skip": "Skip",
"note": "Note",
"yourCommentHere": "Your note here",
"close": "Close",
"createYourFirstHabit": "Create your first habit.",
"modify": "Modify",
"backupFailedError": "ERROR: Creating backup failed.",
"restoreFailedError": "ERROR: Restoring backup failed.",
"habitDeleted": "Habit deleted.",
"undo": "Undo",
"appNotifications": "App notifications",
"appNotificationsChannel": "Notification channel for application notifications",
"habitNotifications": "Habit notifications",
"habitNotificationsChannel": "Notification channel for habit notifications",
"doNotForgetToCheckYourHabits": "Do not forget to check your habits.",
"themeSelect": "{theme, select, device {Device} light {Light} dark {Dark} oled {OLED black} materialYou {Material You} other{Device}}",
"@themeSelect": {
"placeholders": {
"theme": {
"type": "String"
}
}
},
"defineYourHabits": "Define your habits",
"defineYourHabitsDescription": "To better stick to your habits, you can define:",
"cueNumbered": "1. Cue",
"routineNumbered": "2. Routine",
"rewardNumbered": "3. Reward",
"logYourDays": "Log your days",
"successful": "Successful",
"notSoSuccessful": "Not so successful",
"skipDoesNotAffectStreaks": "Skip (does not affect streaks)",
"observeYourProgress": "Observe your progress",
"trackYourProgress": "You can track your progress through the calendar view in every habit or on the statistics page.",
"backupCreatedSuccessfully": "Backup created successfully!",
"backupFailed": "Backup failed!",
"restoreCompletedSuccessfully": "Restore completed successfully!",
"restoreFailed": "Restore failed!",
"fileNotFound": "File not found",
"fileTooLarge": "File too large (max 10MB)",
"invalidBackupFile": "Invalid backup file",
"progress": "Progress",
"enterAmount": "Enter amount",
"complete": "Complete",
"saveProgress": "Save Progress",
"currentProgress": "Current: {current} {unit}",
"@currentProgress": {
"placeholders": {
"current": { "type": "String" },
"unit": { "type": "String" }
}
},
"targetProgress": "Target: {target} {unit}",
"@targetProgress": {
"placeholders": {
"target": { "type": "String" },
"unit": { "type": "String" }
}
},
"progressOf": "{current} / {target} {unit}",
"@progressOf": {
"placeholders": {
"current": { "type": "String" },
"target": { "type": "String" },
"unit": { "type": "String" }
}
},
"numericHabit": "Progressive",
"targetValue": "Target value",
"partialValue": "Partial value",
"unit": "Unit",
"habitType": "Habit type",
"booleanHabit": "Checkable (Yes/No)",
"slider": "Slider",
"input": "Input",
"numericHabitDescription": "Numeric habits let you track progress in increments throughout the day.",
"partialValueDescription": "To track progress in smaller increments",
"categories": "Categories",
"addCategory": "Add Category",
"editCategory": "Edit Category",
"category": "Category",
"noCategoriesYet": "No categories yet",
"createFirstCategory": "Create your first category to organize your habits",
"pleaseEnterCategoryTitle": "Please enter a category title",
"categoryAlreadyExists": "Category \"{title}\" already exists",
"@categoryAlreadyExists": { "placeholders": { "title": { "type": "String" } } },
"categoryCreatedSuccessfully": "Category \"{title}\" created successfully",
"@categoryCreatedSuccessfully": { "placeholders": { "title": { "type": "String" } } },
"categoryUpdatedSuccessfully": "Category \"{title}\" updated successfully",
"@categoryUpdatedSuccessfully": { "placeholders": { "title": { "type": "String" } } },
"categoryDeletedSuccessfully": "Category \"{title}\" deleted successfully",
"@categoryDeletedSuccessfully": { "placeholders": { "title": { "type": "String" } } },
"failedToSaveCategory": "Failed to save category: {error}",
"@failedToSaveCategory": { "placeholders": { "error": { "type": "String" } } },
"failedToDeleteCategory": "Failed to delete category: {error}",
"@failedToDeleteCategory": { "placeholders": { "error": { "type": "String" } } },
"selectCategories": "Select Categories",
"selectedCategories": "Selected Categories ({count})",
"@selectedCategories": { "placeholders": { "count": { "type": "int" } } },
"allCategories": "All Categories",
"deleteCategory": "Delete Category",
"deleteCategoryConfirmation": "Are you sure you want to delete \"{title}\"?\n\nThis will remove the category from all habits that use it.",
"@deleteCategoryConfirmation": { "placeholders": { "title": { "type": "String" } } },
"noHabitsInCategory": "No habits in \"{title}\"",
"@noHabitsInCategory": { "placeholders": { "title": { "type": "String" } } },
"createHabitForCategory": "Create a habit and assign it to this category",
"showCategories": "Show Categories",
"archive": "Archive",
"unarchive": "Unarchive",
"archiveHabit": "Archive habit",
"unarchiveHabit": "Unarchive habit",
"archivedHabits": "Archived Habits",
"noArchivedHabits": "No archived habits",
"viewArchivedHabits": "View archived habits",
"habitArchived": "Habit archived",
"habitUnarchived": "Habit unarchived",
"biometric": "Biometric",
"biometricLockEnabled": "Biometric lock enabled",
"biometricLockDisabled": "Biometric lock disabled",
"authenticationError": "Authentication error",
"biometricAuthenticationRequired": "Biometric authentication required",
"setupFingerprintFaceUnlock": "Please set up your fingerprint or face unlock in device settings",
"touchSensor": "Touch sensor",
"biometricNotRecognized": "Biometric not recognized, try again",
"biometricRequired": "Biometric required",
"biometricAuthenticationSucceeded": "Biometric authentication succeeded",
"deviceCredentialsRequired": "Device credentials required",
"setupDeviceCredentials": "Please set up device credentials in settings",
"setupTouchIdFaceId": "Please set up your Touch ID or Face ID in device settings",
"reenableTouchIdFaceId": "Please reenable your Touch ID or Face ID",
"biometricLock": "Biometric Lock",
"biometricLockDescription": "Secure app with {authMethod}",
"@biometricLockDescription": { "placeholders": { "authMethod": { "type": "String" } } },
"authenticateToEnable": "Authenticate to enable biometric lock",
"authenticateToAccess": "Please authenticate to access Habo",
"authenticationRequired": "Authentication Required",
"authenticationFailedMessage": "Please authenticate to access Habo using {authMethod}",
"@authenticationFailedMessage": { "placeholders": { "authMethod": { "type": "String" } } },
"tryAgain": "Try Again",
"authenticating": "Authenticating…",
"authenticate": "Authenticate",
"buildingBetterHabits": "Building Better Habits",
"authenticationPrompt": "Please authenticate using {authMethod} to access your habits",
"@authenticationPrompt": { "placeholders": { "authMethod": { "type": "String" } } },
"devicePinPatternPassword": "Device PIN, Pattern, or Password",
"fingerprint": "Fingerprint",
"iris": "Iris",
"whatsNewTitle": "What's New",
"whatsNewVersion": "Version {version}",
"@whatsNewVersion": { "placeholders": { "version": { "type": "String" } } },
"featureNumericTitle": "Numeric values in habits",
"featureNumericDesc": "Track counts like glasses of water or pages read",
"featureDeepLinksTitle": "URL scheme (deep links)",
"featureDeepLinksDesc": "Open Habo directly to screens like settings or create",
"featureCategoriesTitle": "Categories",
"featureCategoriesDesc": "Organize habits with category filters",
"featureArchiveTitle": "Archive",
"featureArchiveDesc": "Hide habits you no longer track without deleting",
"featureMaterialYouTitle": "Material You theme (Android)",
"featureMaterialYouDesc": "Dynamic colors that match your wallpaper",
"featureSoundTitle": "New sound engine",
"featureSoundDesc": "Adjustable volume",
"featureLockTitle": "Lock feature",
"featureLockDesc": "Secure the app with Face ID / Touch ID / biometrics",
"featureIosSoundMixingTitle": "Fixed sound mixing",
"featureIosSoundMixingDesc": "Habo sounds no longer interrupt your music or podcasts",
"featureHomescreenWidgetTitle": "Homescreen widget",
"featureHomescreenWidgetDesc": "View your habit progress at a glance from your home screen (experimental)",
"featureLongpressCheckTitle": "Longpress check",
"featureLongpressCheckDesc": "Longpress on habit buttons to quickly change status",
"haboSyncComingSoon": "Coming Soon",
"haboSyncDescription": "Sync your habits across all your devices with Habo's end-to-end encrypted cloud service.",
"haboSyncLearnMore": "Learn more at habo.space/sync",
"habitsToday": "Habits today",
"or": "or",
"oneTapCheck": "Single tap to check",
"tapCheckLongPressMenu": "Tap to check, long press for menu",
"categoryName": "Category name",
"createCategory": "Create category",
"all": "All",
"selectIcon": "Pick an icon",
"searchIcons": "Search"
}
```
---
## 3. 支持的语言 (27 种)
| 代码 | 语言 |
|------|------|
| `en` | English (基准) |
| `zh_Hans` | 中文简体 |
| `zh_Hant` | 中文繁體 |
| `es` | Español |
| `fr` | Français |
| `de` | Deutsch |
| `it` | Italiano |
| `pt` | Português |
| `pt_BR` | Português (Brasil) |
| `ru` | Русский |
| `ja` | (可能缺失, 需确认) |
| `ar` | العربية |
| `he` | עברית |
| `pl` | Polski |
| `nl` | Nederlands |
| `sv` | Svenska |
| `cs` | Čeština |
| `sk` | Slovenčina |
| `uk` | Українська |
| `vi` | Tiếng Việt |
| `id` | Bahasa Indonesia |
| `tr` | Türkçe |
| `ca` | Català |
| `ta` | தமிழ் |
| `nb_NO` | Norsk bokmål |
| `eo` | Esperanto |
| `ia` | Interlingua |
| `ckb` | کوردی |
---
## 4. 测试用例清单
> 13 个测试文件,覆盖核心业务逻辑
### 4.1 单元测试
| 文件 | 测试目标 | 测试用例数 |
|------|----------|-----------|
| `test/habits/habits_manager_test.dart` | HabitsManager CRUD | 13 |
| `test/habits/habits_manager_updated_test.dart` | Repository 模式集成 | 8 |
| `test/habits/habits_manager_fixed_test.dart` | 归档功能 | 8 |
| `test/habits/habits_manager_notifications_test.dart` | 通知调度 | 3 |
| `test/habits/backup_enhancement_test.dart` | 备份格式 | 4 |
| `test/services/backup_service_test.dart` | 备份服务 | 7 |
| `test/services/backup_feature_comprehensive_test.dart` | 备份完整性 | 18 |
| `test/services/notification_service_test.dart` | 通知服务 | 11 |
| `test/app_test.dart` | 应用初始化 | 4 |
| `test/repositories/repository_test.dart` | Repository 模式 | 6 |
### 4.2 Widget 测试
| 文件 | 测试目标 | 测试用例数 |
|------|----------|-----------|
| `test/widgets/habit_details_widget_test.dart` | 习惯详情组件 | 3 |
| `test/widgets/habit_list_widget_test.dart` | 习惯列表组件 | 2 |
### 4.3 集成测试
| 文件 | 测试目标 | 测试用例数 |
|------|----------|-----------|
| `test/integration/habit_crud_integration_test.dart` | 完整 CRUD 流程 | 2 |
### 4.4 测试 Mock 基础设施
| 文件 | 内容 |
|------|------|
| `test/mocks/mock_repositories.dart` | MockHabitRepository, MockEventRepository, MockCategoryRepository, MockBackupRepository + InMemory 实现用于集成测试 |
### 4.5 关键测试场景摘要
**HabitsManager 测试**:
- 初始化时从 Repository 加载习惯
- 空列表正确处理
- 创建习惯并分配正确位置
- 编辑更新已有习惯
- 删除习惯并支持 Undo
- 归档/取消归档切换
- 活跃/归档习惯正确过滤
- 位置排序更新
- 通知调度触发
**备份测试**:
- 正确的 JSON 结构验证
- 时间戳格式验证
- 空数据备份处理
- 事件类型保留
- 分类关联保留
- 大数据集处理
- 损坏数据检测
- 10MB 文件大小限制
- 并发操作处理
**通知测试**:
- 空习惯列表不崩溃
- 习惯通知调度
- 事件添加/删除触发通知
- 多习惯批量通知
- 当日/非当日事件区分
---
## 5. 备份文件格式规格
### 5.1 当前格式 (Version 3)
```json
{
"version": 3,
"habits": [
{
"id": 1,
"position": 0,
"title": "Exercise",
"twoDayRule": false,
"cue": "At 7:00AM",
"routine": "Do 50 push ups",
"reward": "15 min. of video games",
"showReward": true,
"advanced": true,
"notification": true,
"notTime": {"hour": 7, "minute": 0},
"events": {
"2024-01-01 00:00:00.000": [1, ""],
"2024-01-02 00:00:00.000": [2, "Tired"]
},
"sanction": "Donate 10$ to charity",
"showSanction": true,
"accountant": "Dan",
"habitType": 0,
"targetValue": 1.0,
"partialValue": 1.0,
"unit": ""
}
],
"categories": [
{ "id": 1, "title": "Health", "iconCodePoint": 58718, "fontFamily": "fontAwesomeFlutter" }
],
"habit_categories": [
{ "habit_id": 1, "category_id": 1 }
],
"metadata": {
"import_timestamp": "2024-01-15T10:30:00.000Z"
}
}
```
### 5.2 旧版兼容格式 (数组)
```json
[
{
"id": 1,
"title": "Exercise",
"position": 0,
"events": {},
...
}
]
```
### 5.3 事件类型编码
| 值 | DayType | 含义 |
|----|---------|------|
| 0 | clear | 清除/无事件 |
| 1 | check | 完成 |
| 2 | fail | 失败 |
| 3 | skip | 跳过 |
| 4 | progress | 进度(部分完成) |
---
## 6. pubspec.yaml 关键配置
```yaml
name: habo
version: 3.1.2+5115
environment:
sdk: ">=3.11.0 <4.0.0"
flutter: ">=3.41.1"
flutter:
uses-material-design: true
generate: true
assets:
- assets/
- assets/images/
- assets/images/onboard/
- assets/sounds/
- assets/google_fonts/
flutter_intl:
enabled: true
flutter_native_splash:
color: "#FAFAFA"
color_dark: "#000000"
image: assets/images/splash_icon.png
image_dark: assets/images/splash_icon.png
ios_content_mode: center
android_gravity: center
fullscreen: false
android_12:
color: "#FAFAFA"
color_dark: "#000000"
image: assets/images/splash_icon.png
android: true
ios: true
web: false
```

687
docs/05-DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,687 @@
# Habo 开发文档
> 版本: 3.1.2+5115 | Flutter 3.41.1+ | Dart 3.11.0+
Habo 是一款极简风格的习惯追踪应用,支持 Android、iOS、Linux、macOS 多平台。所有数据存储在本地 SQLite 数据库中,无需服务端。
---
## 目录
1. [项目概览](#1-项目概览)
2. [技术栈与依赖](#2-技术栈与依赖)
3. [目录结构](#3-目录结构)
4. [架构设计](#4-架构设计)
5. [数据模型](#5-数据模型)
6. [数据库 Schema](#6-数据库-schema)
7. [核心模块详解](#7-核心模块详解)
8. [导航系统](#8-导航系统)
9. [状态管理](#9-状态管理)
10. [国际化 (i18n)](#10-国际化-i18n)
11. [主题系统](#11-主题系统)
12. [通知系统](#12-通知系统)
13. [备份与恢复](#13-备份与恢复)
14. [桌面端小组件](#14-桌面端小组件)
15. [生物识别认证](#15-生物识别认证)
16. [CI/CD 与构建](#16-cicd-与构建)
17. [开发指南](#17-开发指南)
---
## 1. 项目概览
Habo 是一个功能完整的习惯追踪应用,核心功能包括:
- **习惯管理** — 创建、编辑、归档、删除习惯,支持拖拽排序
- **两种习惯类型** — 布尔型(打卡/未打卡)和数值型(进度追踪,如跑步 5km
- **日历视图** — 基于 `table_calendar` 的月度视图,标记每日状态
- **连续天数 (Streak)** — 支持普通模式和"两天法则"(允许间隔一天)
- **分类系统** — 习惯可归属多个分类,支持按分类筛选
- **统计分析** — 饼图总览、月度柱状图、个人习惯统计卡片
- **通知提醒** — 每日提醒和成就/惩罚通知
- **备份恢复** — JSON 文件导入/导出,支持跨设备迁移
- **桌面小组件** — iOS/Android 主屏幕小组件显示今日进度
- **生物识别锁** — 支持指纹/面容锁定应用
- **Material You** — 支持动态取色主题
---
## 2. 技术栈与依赖
| 类别 | 技术 | 说明 |
|------|------|------|
| 框架 | Flutter 3.41.1+ | 跨平台 UI 框架 |
| 语言 | Dart 3.11.0+ | 支持 null safety |
| 数据库 | sqflite / sqflite_common_ffi | SQLite移动端/桌面端) |
| 状态管理 | provider + ChangeNotifier | 响应式状态管理 |
| 图表 | fl_chart | 统计图表渲染 |
| 日历 | table_calendar | 日历视图组件 |
| 通知 | awesome_notifications | 本地通知调度 |
| 国际化 | flutter_localizations + intl | ARB 文件管理多语言 |
| 字体 | google_fonts + 动态取色 | dynamic_color (Material You) |
| 音效 | flutter_soloud + audio_session | 习惯完成音效反馈 |
| 认证 | local_auth | 指纹/面容生物识别 |
| 小组件 | home_widget | iOS/Android 桌面小组件 |
| 桌面窗口 | window_manager | Linux/macOS 窗口管理 |
| 测试 | flutter_test + mocktail | 单元测试与 mock |
| CI | GitHub Actions | 自动测试与 APK 构建 |
| 发布 | fastlane | 多平台商店发布自动化 |
---
## 3. 目录结构
```
Habo-master/
├── lib/ # 应用主源码
│ ├── main.dart # 应用入口,初始化流程
│ ├── constants.dart # 枚举类型和颜色常量
│ ├── themes.dart # 主题定义(亮色/暗色/OLED
│ ├── helpers.dart # 工具函数(日期解析等)
│ │
│ ├── model/ # 数据模型层
│ │ ├── habit_data.dart # HabitData 习惯数据模型
│ │ ├── habo_model.dart # HaboModel 数据库操作层
│ │ ├── category.dart # Category 分类模型
│ │ ├── settings_data.dart # 设置数据模型
│ │ └── backup.dart # 备份数据模型
│ │
│ ├── habits/ # 习惯管理模块
│ │ ├── habit.dart # Habit StatefulWidget日历卡片
│ │ ├── habits_manager.dart # HabitsManager 业务逻辑中心
│ │ ├── habits_screen.dart # 习惯列表主屏幕
│ │ ├── create_habit.dart # 创建习惯页面
│ │ └── edit_habit.dart # 编辑习惯页面
│ │
│ ├── statistics/ # 统计分析模块
│ │ ├── statistics.dart # 统计数据计算逻辑
│ │ ├── statistics_screen.dart # 统计主屏幕
│ │ ├── statistics_card.dart # 单个习惯统计卡片
│ │ ├── overall_statistics_card.dart # 总览饼图卡片
│ │ └── monthly_graph.dart # 月度柱状图
│ │
│ ├── settings/ # 设置模块
│ │ ├── settings_manager.dart # 设置管理器(持久化)
│ │ ├── settings_screen.dart # 设置页面 UI
│ │ └── color_icon.dart # 颜色选择器组件
│ │
│ ├── navigation/ # 导航系统
│ │ ├── routes.dart # 路由常量
│ │ ├── app_router.dart # RouterDelegate 实现
│ │ ├── app_state_manager.dart # 导航状态管理
│ │ ├── route_information_parser.dart # 深度链接解析
│ │ └── navigation.dart # 导出文件
│ │
│ ├── repositories/ # 数据仓库层Repository Pattern
│ │ ├── habit_repository.dart # 习惯仓库接口
│ │ ├── sqlite_habit_repository.dart # SQLite 实现
│ │ ├── event_repository.dart # 事件仓库接口
│ │ ├── category_repository.dart # 分类仓库接口
│ │ └── repository_factory.dart # 仓库工厂DI
│ │
│ ├── services/ # 服务层
│ │ ├── service_locator.dart # 服务定位器DI 容器)
│ │ ├── notification_service.dart # 通知服务
│ │ ├── backup_service.dart # 备份/恢复服务
│ │ ├── ui_feedback_service.dart # UI 反馈服务Snackbar
│ │ ├── biometric_auth_service.dart # 生物识别服务
│ │ └── home_widget_service.dart # 桌面小组件服务
│ │
│ ├── widgets/ # 可复用 UI 组件
│ │ ├── habit_progress_indicator.dart # 进度指示器
│ │ ├── biometric_auth_wrapper.dart # 生物识别包裹组件
│ │ ├── category_filter_row.dart # 分类筛选行
│ │ ├── progress_input_modal.dart # 数值进度输入弹窗
│ │ ├── home_widget_data.dart # 小组件数据模型
│ │ ├── habo_home_widget.dart # 小组件渲染
│ │ └── text_container.dart # 文本输入组件
│ │
│ ├── onboarding/ # 引导页
│ │ ├── onboarding_screen.dart
│ │ └── onboarding.dart
│ │
│ ├── l10n/ # 国际化 ARB 文件27 种语言)
│ └── generated/ # 自动生成的代码intl 等)
├── test/ # 测试目录
├── assets/ # 静态资源
│ ├── images/ # 图片(含 onboard/ 引导图)
│ ├── sounds/ # 音效文件
│ └── google_fonts/ # 本地字体文件
├── android/ # Android 平台代码
├── ios/ # iOS 平台代码
├── linux/ # Linux 平台代码
├── macos/ # macOS 平台代码
├── fastlane/ # 发布自动化配置
├── .github/workflows/ci.yml # CI/CD 流水线
└── pubspec.yaml # 项目配置和依赖声明
```
---
## 4. 架构设计
### 4.1 整体架构
```
┌─────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Screens, Widgets) │
│ habits_screen statistics_screen settings_screen │
├─────────────────────────────────────────────────────┤
│ Business Logic Layer │
│ HabitsManager (ChangeNotifier) │
│ SettingsManager │
├─────────────────────────────────────────────────────┤
│ Service Layer │
│ NotificationService BackupService UIFeedbackService│
│ BiometricAuthService HomeWidgetService │
├─────────────────────────────────────────────────────┤
│ Repository Layer │
│ HabitRepository EventRepository CategoryRepository│
│ RepositoryFactory │
├─────────────────────────────────────────────────────┤
│ Data Layer │
│ HaboModel → SQLite (sqflite) │
└─────────────────────────────────────────────────────┘
```
### 4.2 设计模式
| 模式 | 应用场景 |
|------|----------|
| **Repository Pattern** | `HabitRepository``EventRepository``CategoryRepository` 抽象数据访问 |
| **Service Locator** | `ServiceLocator` 单例管理全局服务实例 |
| **Factory Pattern** | `RepositoryFactory` 创建各仓库实例 |
| **Observer Pattern** | `ChangeNotifier` + `Provider` 实现响应式 UI 更新 |
| **Singleton Pattern** | `HaboModel``ServiceLocator` 确保单实例 |
| **Strategy Pattern** | `HabitType` 枚举区分布尔/数值习惯的不同处理逻辑 |
### 4.3 数据流
```
用户操作 → Widget
→ HabitsManager (ChangeNotifier)
→ Repository (数据访问抽象)
→ HaboModel (SQLite 操作)
→ Database
HabitsManager.notifyListeners()
→ Provider 更新
→ Widget 重建
```
### 4.4 初始化流程 (`main.dart`)
```
1. SettingsManager 初始化(读取持久化设置)
2. 创建 HaboModel 实例(共享数据库连接)
3. 调用 HaboModel.initDatabase() 初始化 SQLite
4. 初始化 ServiceLocator注册所有服务
5. 创建 HabitsManager注入仓库和服务依赖
6. 调用 HabitsManager.loadHabits() 加载数据
7. 初始化通知服务
8. 创建 AppRouter注入状态管理器
9. 启动日变化定时器(检测跨日自动刷新)
10. 渲染 MaterialApp.router
```
---
## 5. 数据模型
### 5.1 核心枚举 (`constants.dart`)
```dart
// 习惯类型
enum HabitType { boolean, numeric }
// 日状态类型
enum DayType { clear, check, fail, skip, progress }
// 主题模式
enum Themes { device, light, dark, oled, materialYou }
```
### 5.2 HabitData (`model/habit_data.dart`)
习惯的完整数据模型:
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int?` | 数据库自增主键 |
| `position` | `int` | 排序位置 |
| `title` | `String` | 习惯标题 |
| `twoDayRule` | `bool` | 是否启用两天法则 |
| `cue` | `String` | 提示(触发器) |
| `routine` | `String` | 例行动作描述 |
| `reward` | `String` | 奖励描述 |
| `showReward` | `bool` | 是否显示奖励通知 |
| `sanction` | `String` | 惩罚描述 |
| `showSanction` | `bool` | 是否显示惩罚通知 |
| `accountant` | `String` | 问责伙伴 |
| `advanced` | `bool` | 是否显示高级选项cue/routine/reward |
| `notification` | `bool` | 是否启用通知提醒 |
| `notTime` | `TimeOfDay` | 通知时间 |
| `events` | `SplayTreeMap<DateTime, List>` | 事件记录(日期→事件列表) |
| `habitType` | `HabitType` | 布尔型或数值型 |
| `targetValue` | `double` | 数值型目标值(默认 100 |
| `partialValue` | `double` | 数值型部分进度增量(默认 10 |
| `unit` | `String` | 数值型单位 |
| `categories` | `List<Category>` | 所属分类列表 |
| `archived` | `bool` | 是否已归档 |
| `streak` | `int` | 当前连续天数(运行时计算) |
**事件列表结构** (`events[date]`):
```
布尔型: [DayType, comment]
数值型: [DayType, comment, progressValue, targetValue]
```
**关键方法**:
- `isCompletedForDate(date)` — 判断某日是否完成
- `getProgressForDate(date)` — 获取某日进度值
- `getProgressPercentage(date)` — 获取进度百分比
### 5.3 Category (`model/category.dart`)
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | `int?` | 自增主键 |
| `title` | `String` | 分类名称 |
| `iconCodePoint` | `int` | 图标 Unicode 码点 |
| `fontFamily` | `String?` | 图标字体族(如 FontAwesome |
---
## 6. 数据库 Schema
数据库版本: **9**,文件: `habo_db0.db`
### 6.1 habits 表
```sql
CREATE TABLE habits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
position INTEGER, -- 排序位置
title TEXT, -- 习惯标题
twoDayRule INTEGER, -- 两天法则开关 (0/1)
cue TEXT DEFAULT '', -- 提示触发器
routine TEXT DEFAULT '', -- 例行动作
reward TEXT DEFAULT '', -- 奖励
showReward INTEGER, -- 显示奖励 (0/1)
advanced INTEGER, -- 高级模式 (0/1)
notification INTEGER, -- 通知开关 (0/1)
notTime TEXT, -- 通知时间 (HH:MM)
sanction TEXT DEFAULT '', -- 惩罚
showSanction INTEGER DEFAULT 0, -- 显示惩罚 (0/1)
accountant TEXT DEFAULT '', -- 问责伙伴
habitType INTEGER DEFAULT 0, -- 习惯类型 (0=布尔, 1=数值)
targetValue REAL DEFAULT 1.0, -- 目标值
partialValue REAL DEFAULT 1.0, -- 部分增量
unit TEXT DEFAULT '', -- 单位
archived INTEGER DEFAULT 0 -- 归档状态 (0/1)
);
```
### 6.2 events 表
```sql
CREATE TABLE events (
id INTEGER, -- 外键 → habits.id
dateTime TEXT, -- 日期时间字符串
dayType INTEGER, -- DayType 枚举索引
comment TEXT DEFAULT '', -- 备注
progressValue REAL DEFAULT 0.0, -- 进度值(数值习惯)
targetValue REAL DEFAULT 0.0, -- 目标值快照
PRIMARY KEY(id, dateTime),
FOREIGN KEY (id) REFERENCES habits(id) ON DELETE CASCADE
);
```
**DayType 枚举值**:
| 值 | 含义 |
|----|------|
| 0 | clear — 清除 |
| 1 | check — 完成 |
| 2 | fail — 失败 |
| 3 | skip — 跳过 |
| 4 | progress — 进行中(数值型部分完成) |
### 6.3 categories 表
```sql
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, -- 分类名称
iconCodePoint INTEGER NOT NULL, -- 图标码点
fontFamily TEXT -- 图标字体族
);
```
### 6.4 habit_categories 关联表
```sql
CREATE TABLE habit_categories (
habit_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
PRIMARY KEY (habit_id, category_id),
FOREIGN KEY (habit_id) REFERENCES habits(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
```
### 6.5 数据库迁移历史
| 版本 | 变更 |
|------|------|
| V1→V2 | events 表增加 `comment` 字段 |
| V2→V3 | habits 表增加 `sanction``showSanction``accountant` 字段 |
| V3→V4 | habits 表增加 `habitType``targetValue``partialValue``unit`events 增加 `progressValue` |
| V4→V5 | events 表增加 `targetValue`;新建 `categories``habit_categories` 表 |
| V5→V6 | habits 表增加 `archived` 字段 |
| V6→V7 | events 表确保有 `targetValue`categories 增加 `fontFamily` |
| V7→V8 | events 表确保有 `targetValue` |
| V8→V9 | events 表确保有 `targetValue` |
---
## 7. 核心模块详解
### 7.1 HabitsManager (`habits/habits_manager.dart`)
**中心业务逻辑管理器**,继承 `ChangeNotifier`,是整个应用的核心。
**职责**:
- 管理 habits 和 categories 的内存状态
- 协调 Repository 层的数据操作
- 集成通知、备份、UI 反馈等服务
- 处理拖拽排序、归档、undo 等交互逻辑
**核心 API**:
```dart
// 习惯 CRUD
Future<void> loadHabits()
Future<void> addHabit(Habit habit)
Future<void> editHabit(Habit habit)
Future<void> deleteHabit(int id)
Future<void> archiveHabit(int id)
void reorderList(int oldIndex, int newIndex)
// 事件操作
Future<void> addEvent(int id, DateTime date, List event)
Future<void> deleteEvent(int id, DateTime date)
// 分类操作
Future<void> loadCategories()
Future<void> addCategory(Category category)
Future<void> updateCategory(Category category)
Future<void> deleteCategory(int id)
Future<void> updateHabitCategories(int habitId, List<Category> categories)
// 服务调用
Future<void> createBackup()
Future<void> loadBackup(String path)
Future<void> resetNotifications()
void updateHomeWidget()
// 数据访问
List<Habit> get activeHabits // 未归档习惯
List<Habit> get archivedHabits // 已归档习惯
Habit? findHabitById(int id)
```
### 7.2 HaboModel (`model/habo_model.dart`)
**直接操作 SQLite 数据库的底层类**
- 使用 `sqflite`(移动端)或 `sqflite_common_ffi`Linux/macOS
- 管理数据库创建、迁移、CRUD
- 处理 `PRAGMA foreign_keys = ON` 级联删除
### 7.3 Repository 层 (`repositories/`)
`HaboModel` 提供**抽象接口**,实现关注点分离:
- `HabitRepository` — 习惯 CRUD、排序、批量操作
- `EventRepository` — 事件增删查
- `CategoryRepository` — 分类 CRUD 及关联管理
- `RepositoryFactory` — 创建各 Repository 实例,注入 `HaboModel`
### 7.4 Service 层 (`services/`)
| 服务 | 职责 |
|------|------|
| `ServiceLocator` | 单例 DI 容器,初始化并持有所有服务实例 |
| `NotificationService` | 管理本地通知调度(每日提醒、奖励/惩罚) |
| `BackupService` | 数据库备份为 JSON 文件、从 JSON 恢复 |
| `UIFeedbackService` | 统一的 Snackbar 消息展示(成功/失败/警告) |
| `BiometricAuthService` | 封装 `local_auth` 生物识别认证 |
| `HomeWidgetService` | 更新 iOS/Android 主屏幕小组件数据 |
---
## 8. 导航系统
采用 **Flutter Navigation 2.0**,基于 `RouterDelegate`
### 路由定义
| 常量 | 路径 | 页面 |
|------|------|------|
| `splashPath` | `/` | 启动页 |
| `habitsPath` | `/habits` | 习惯列表主页 |
| `statisticsPath` | `/statistics` | 统计页 |
| `settingsPath` | `/settings` | 设置页 |
| `onboardingPath` | `/onboarding` | 引导页 |
| `createHabitPath` | `/create` | 创建习惯 |
| `editHabitPath` | `/edit` | 编辑习惯 |
| `whatsNewPath` | `/whatsnew` | 更新日志 |
### 深度链接
支持 `habo://` scheme例如 `habo://settings` 直接跳转设置页。
### 关键类
- **`AppRouter`** — `RouterDelegate` 实现,管理页面栈
- **`AppStateManager`** — 管理各页面的显示状态bool 标志位)
- **`HaboRouteInformationParser`** — 解析 URL 到 `HaboRouteConfiguration`
---
## 9. 状态管理
使用 **Provider + ChangeNotifier** 模式:
```
MultiProvider(
providers:
- ChangeNotifierProvider<SettingsManager>
- ChangeNotifierProvider<HabitsManager>
- ChangeNotifierProvider<AppStateManager>
)
```
- `HabitsManager` — 习惯数据变化时调用 `notifyListeners()`,驱动 UI 重建
- `SettingsManager` — 设置变更时通知(主题、音效等)
- `AppStateManager` — 导航状态变更通知
- **注意**: `AppRouter` 不监听 `HabitsManager`,避免数据变化导致非预期的导航跳转
---
## 10. 国际化 (i18n)
- 使用 `flutter_intl` + ARB 文件管理
- 文件位于 `lib/l10n/intl_*.arb`
- 支持 **27 种语言**
- 中文(简体/繁体)、英语、西班牙语、法语、德语、意大利语、葡萄牙语、俄语、日语(未列出但可能有)、阿拉伯语、希伯来语、波兰语、荷兰语、瑞典语、捷克语、越南语、印尼语、土耳其语、乌克兰语、加泰罗尼亚语、斯洛伐克语、巴斯克语、世界语、挪威语等
- 生成代码在 `lib/generated/` 目录
**添加新语言**:
1.`lib/l10n/` 下创建 `intl_<locale>.arb`
2. 运行 `flutter gen-l10n` 生成代码
---
## 11. 主题系统
`HaboTheme` 类提供三种主题:
| 主题 | 说明 |
|------|------|
| `lightTheme` | 浅色主题,浅灰背景 (#FAFAFA) |
| `darkTheme` | 深色主题,纯黑背景 (#000000) |
| `oledTheme` | OLED 深色主题,纯黑背景 |
**主题模式** (`Themes` 枚举):
- `device` — 跟随系统
- `light` — 强制浅色
- `dark` — 强制深色
- `oled` — OLED 黑色
- `materialYou` — Material You 动态取色
**特性**:
- 使用 Google Fonts 自定义字体
- 主色调: `#09BF30`(绿色)
- 支持平台差异iOS/Android 不同组件样式)
---
## 12. 通知系统
使用 `awesome_notifications` 实现本地通知:
- **每日提醒** — 用户设定时间推送提醒
- **奖励通知** — 完成习惯时触发(可配置音效)
- **惩罚通知** — 习惯失败时触发
`NotificationService` 通过 `HabitsManager` 调用:
- `resetNotifications()` — 重置所有习惯通知
- `removeNotifications(id)` — 删除指定习惯的通知
- `handleHabitEventAdded()` — 事件添加后触发通知
---
## 13. 备份与恢复
`BackupService` 提供完整的数据导入/导出:
- **导出**: 将所有 habits、events、categories 序列化为 JSON 文件
- **导入**: 从 JSON 文件解析并恢复到数据库
- **兼容性**: 支持旧版格式迁移
- **文件选择**: 使用 `flutter_file_dialog`(移动端)或 `file_picker`(桌面端)
---
## 14. 桌面端小组件
使用 `home_widget` 包实现 iOS/Android 主屏幕小组件:
- **小组件类型**: 170x170 圆形进度指示器
- **数据传递**: 通过 `HomeWidgetService` 更新数据
- **显示内容**: 今日习惯完成数量 / 总数量
- **渲染**: `CircularProgressPainter` 自定义绘制多段圆弧
---
## 15. 生物识别认证
- `BiometricAuthService` 封装 `local_auth`
- `BiometricAuthWrapper` Widget 包裹主内容
- 支持指纹和面容识别
- 应用从后台恢复时重新验证
- 认证失败提供重试对话框
---
## 16. CI/CD 与构建
### GitHub Actions (`ci.yml`)
**触发条件**: push/PR 到 `main``develop` 分支
**流水线**:
1. **test**`flutter analyze` + `flutter test`
2. **build-android**(依赖 test 通过):
- `flutter build apk --release --split-per-abi --no-tree-shake-icons`
- 按 CPU 架构分拆 APKarm64-v8a, armeabi-v7a, x86_64
- 上传构建产物
### 本地构建
```bash
# 安装依赖
flutter pub get
# 生成图标包
dart run flutter_iconpicker:generate_packs --packs fontAwesomeIcons
# 生成国际化代码
flutter gen-l10n
# 运行测试
flutter test
# 构建 APK
flutter build apk --release
# 构建 iOS
flutter build ios --release
# 桌面端
flutter build linux --release
flutter build macos --release
```
### Fastlane
`fastlane/` 目录包含多平台商店发布的自动化配置。
---
## 17. 开发指南
### 环境要求
- Flutter SDK >= 3.41.1
- Dart SDK >= 3.11.0
- Android: Java 17, minSdk 21
- iOS: Xcode (最新版)
- Linux: 额外依赖 `sqflite_common_ffi`
### 项目约定
1. **状态管理** — 使用 `ChangeNotifier` + `Provider`,新功能应创建 Manager 类
2. **数据访问** — 通过 Repository 接口,不直接使用 `HaboModel`
3. **服务依赖** — 通过 `ServiceLocator` 获取,不手动创建实例
4. **国际化** — 所有用户可见文本必须使用 `AppLocalizations`,不硬编码字符串
5. **主题** — 使用 `HaboTheme` 定义的颜色和样式,不直接写色值
6. **数据库迁移** — 修改 Schema 必须新增迁移方法并更新 `_dbVersion`
7. **测试** — 使用 `mocktail` mock Repository 层,测试业务逻辑而非 UI 渲染
### 添加新功能的典型流程
1.`model/` 中定义或修改数据模型
2.`repositories/` 中添加/更新 Repository 接口和实现
3.`services/` 中添加服务(如需要)
4.`HabitsManager` 中添加业务逻辑方法
5. 在对应的 screen/widget 中实现 UI
6.`lib/l10n/intl_en.arb` 中添加国际化文本
7. 编写单元测试
### 关键文件速查
| 需求 | 文件 |
|------|------|
| 添加新习惯字段 | `model/habit_data.dart` + `model/habo_model.dart` + 数据库迁移 |
| 修改日历行为 | `habits/habit.dart` |
| 添加新统计图表 | `statistics/` 目录 |
| 修改通知逻辑 | `services/notification_service.dart` |
| 添加新设置项 | `settings/settings_manager.dart` + `settings_screen.dart` |
| 修改导航流程 | `navigation/app_router.dart` + `navigation/app_state_manager.dart` |
| 添加新语言 | `lib/l10n/intl_<locale>.arb` |
| 修改主题 | `themes.dart` |