Files
habo/docs/02-ARCHITECTURE.md
dazhuang aa69f2a91e 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)
2026-04-13 15:02:30 +00:00

19 KiB
Raw Permalink Blame History

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 读写模式

// 读取 — 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 备份文件格式

{
  "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 的依赖注入

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 加载指示器 + 错误状态