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:
194
docs/01-REQUIREMENTS.md
Normal file
194
docs/01-REQUIREMENTS.md
Normal 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
482
docs/02-ARCHITECTURE.md
Normal 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
807
docs/03-SPECIFICATION.md
Normal 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
547
docs/04-ASSETS.md
Normal 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/`)
|
||||
|
||||
**字体族**: Nunito(18 个字重/样式变体)
|
||||
|
||||
| 文件 | 字重 |
|
||||
|------|------|
|
||||
| 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
687
docs/05-DEVELOPMENT.md
Normal 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 架构分拆 APK(arm64-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` |
|
||||
Reference in New Issue
Block a user