🧭 一、模块定位与职责

模块结构

lib/
└── blocs/
    └── settings/
        ├── settings_bloc.dart   // 业务逻辑核心
        ├── settings_event.dart  // 所有可触发的事件定义
        ├── settings_state.dart  // 当前系统设置的状态模型
        └── bloc.dart            // 模块统一导出文件

模块作用

SettingsBloc 是整个应用的“系统控制中心”,主要负责:

  1. 加载与保存 用户配置(如导出选项、默认过滤条件、通知设置等);

  2. 同步 UI 与状态,使设置变动实时生效;

  3. 协调跨模块行为,如计时器提醒、数据库导入、角标更新等;

  4. 持久化存储 到本地(通过 SettingsProvider)。


⚙️ 二、设计理念:Bloc 架构中的三层分工

层次 职责 对应文件
Event(事件层) 描述用户或系统触发的动作,如切换开关、导入数据 settings_event.dart
Bloc(业务逻辑层) 接收事件、调用底层逻辑、计算新状态并发出 settings_bloc.dart
State(状态层) 表示应用当前的所有设置项 settings_state.dart

Bloc 数据流图

        ┌──────────────┐
        │   UI交互层   │
        └──────┬───────┘
               │(触发事件)
               ▼
        ┌──────────────┐
        │ SettingsBloc │  ←── 调用 SettingsProvider 持久化
        └──────┬───────┘
               │(emit)
               ▼
        ┌──────────────┐
        │ SettingsState│
        └──────────────┘
               │
               ▼
         UI 根据状态重建

📜 三、SettingsState:状态定义与默认值

该类表示应用中所有的配置项,在启动时由 Bloc 载入(或初始化为默认值)。

final bool exportGroupTimers; // 导出时是否按项目或标签分组
final bool exportIncludeDate; // 导出是否包含日期字段
final bool exportIncludeProject; // 导出是否包含项目名
final bool exportIncludeDescription; // 导出是否包含任务描述
final bool exportIncludeProjectDescription; // 导出是否包含项目说明
final bool exportIncludeStartTime; // 导出是否包含开始时间
final bool exportIncludeEndTime; // 导出是否包含结束时间
final bool exportIncludeDurationHours; // 导出是否包含时长(小时)
final bool exportIncludeNotes; // 导出是否包含备注
final bool groupTimers; // UI中是否分组显示计时器
final bool collapseDays; // 是否折叠日期
final bool autocompleteDescription; // 是否启用描述自动补全
final bool defaultFilterStartDateToMonday; // 筛选是否从周一开始
final bool oneTimerAtATime; // 是否同一时间只允许运行一个计时器
final bool showBadgeCounts; // 是否显示角标计数
final int defaultFilterDays; // 默认筛选天数
final bool hasAskedNotificationPermissions; // 是否请求过通知权限
final bool showRunningTimersAsNotifications; // 是否显示运行中计时器通知
final bool showProjectNames; // 是否显示项目名
final bool nagAboutMissingTimer; // 是否启用“未启动计时器”提醒

这些字段完全覆盖用户偏好项,后续拓展只需在 StateBloc 中同步增加。


🚀 四、SettingsEvent:事件触发机制

主要事件

事件类 作用说明
LoadSettingsFromRepository 启动时从本地加载用户配置
SetBoolValueEvent 当用户切换某个设置开关时触发
SetDefaultFilterDays 修改默认筛选天数
ImportDatabaseEvent 从外部数据库导入计时项目与数据

🧩 五、SettingsBloc:业务逻辑核心

1️⃣ 加载配置(LoadSettingsFromRepository

on<LoadSettingsFromRepository>((event, emit) {
  bool exportGroupTimers = settings.getBool("exportGroupTimers") ?? state.exportGroupTimers;
  ...
  emit(SettingsState(...)); // 初始化所有设置项
});

作用
在应用启动时或设置页首次加载时,从 SettingsProvider 读取本地存储值 → 生成新的 SettingsState


2️⃣ 更新配置(SetBoolValueEvent

on<SetBoolValueEvent>((event, emit) async {
  if (event.showBadgeCounts != null) {
    await settings.setBool("showBadgeCounts", event.showBadgeCounts);
    if (event.showBadgeCounts!) {
      FlutterAppBadger.removeBadge();
      await settings.setBool("hasAskedNotificationPermissions", true);
    }
  }
  emit(SettingsState.clone(state, showBadgeCounts: event.showBadgeCounts));
});

关键特性:

  • 仅更新被修改的字段;

  • 通过 SettingsState.clone 生成新状态;

  • 特殊开关触发系统行为(如角标清除或通知授权)。


3️⃣ 设置默认筛选天数(SetDefaultFilterDays

on<SetDefaultFilterDays>((event, emit) async {
  await settings.setInt("defaultFilterDays", event.days ?? -1);
  emit(SettingsState.clone(state, defaultFilterDays: event.days ?? -1));
});

设计亮点:

  • 支持负数代表“不限制天数”;

  • 与下方 getFilterStartDate()getDefaultFilterDays() 逻辑联动。


4️⃣ 导入数据库(ImportDatabaseEvent

on<ImportDatabaseEvent>((event, emit) async {
  final DatabaseProvider importData = await DatabaseProvider.open(event.path);
  await data.import(importData);
  event.projects.add(LoadProjects());
  event.timers.add(LoadTimers());
  await importData.close();
  emit(SettingsState.clone(state));
});

实现逻辑:

  • 打开外部数据库;

  • 将数据导入当前库;

  • 通知项目/计时器模块刷新;

  • 关闭外库句柄;

  • 发出新状态(保持 UI 同步)。


💾 六、SettingsProvider:持久化实现建议

可基于 shared_preferences 实现,示例:

import 'package:shared_preferences/shared_preferences.dart';

class SharedPrefsSettingsProvider implements SettingsProvider {
  final SharedPreferences _prefs;
  SharedPrefsSettingsProvider(this._prefs);

  bool? getBool(String key) => _prefs.getBool(key);
  int? getInt(String key) => _prefs.getInt(key);

  Future<void> setBool(String key, bool? value) async {
    if (value == null) return _prefs.remove(key);
    await _prefs.setBool(key, value);
  }

  Future<void> setInt(String key, int value) async =>
      _prefs.setInt(key, value);
}

🔔 七、与其它模块协作

模块 交互点 描述
TimersBloc LoadTimers / StopAllTimers 导入数据库后触发刷新
ProjectsBloc LoadProjects 同步项目数据
DataProvider .import() 实际数据迁移逻辑
FlutterAppBadger .removeBadge() / .updateBadgeCount() 角标管理
NotificationsProvider 显示运行中计时通知 结合系统通知使用

🔩 八、启动注册与初始化

main.dart 中注册 Bloc 并加载设置:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final settingsProvider = await loadSettingsProvider();
  final dataProvider = await DatabaseProvider.open('path_to_db');

  runApp(
    MultiBlocProvider(
      providers: [
        BlocProvider(create: (_) =>
          SettingsBloc(settingsProvider, dataProvider)
          ..add(LoadSettingsFromRepository())),
        BlocProvider(create: (_) => ProjectsBloc(dataProvider)..add(LoadProjects())),
        BlocProvider(create: (_) => TimersBloc(dataProvider, settingsProvider, NotificationsProvider())),
      ],
      child: MyApp(),
    ),
  );
}

🧠 九、扩展与优化方向

方向 说明
添加 copyWith() 让状态更新更简洁,替代 clone
角标联动 监听 TimersBloc 的计时数量并实时更新角标
通知权限自动申请 集成 permission_handler 动态申请权限
数据库导入界面优化 结合 file_picker 实现文件选择 UI
模块测试 使用 bloc_test 验证事件与状态流正确性

🧾 十、总结:从设置到架构

模块角色 职责
SettingsBloc 管理全局偏好逻辑、跨模块协调
SettingsEvent 抽象所有用户/系统行为
SettingsState 唯一可信的设置状态源
SettingsProvider 持久化实现(SharedPreferences / File / Cloud)

Settings 模块的核心价值在于:
它将“应用设置”抽象成状态流与事件流
让每个开关不再只是 UI 上的按钮,而是能驱动整个系统逻辑变化的中枢。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐