Files
FocusBuddy/NOTIFICATION_IMPLEMENTATION.md
2025-11-22 18:17:35 +08:00

12 KiB
Raw Blame History

📱 本地通知功能 - 实现文档

实现日期: 2025-11-22 状态: 完成 平台支持: Android, iOS (Web不支持)


📋 功能概述

FocusBuddy 现在支持本地通知,在专注计时完成时自动提醒用户,即使应用在后台运行。

核心特性

  • 计时完成时自动发送通知
  • 显示专注时长和分心次数
  • 支持震动和声音
  • 自动请求权限
  • Web 平台优雅降级(不报错)

🏗️ 架构设计

文件结构

lib/services/notification_service.dart  # 通知服务(新增)
lib/main.dart                           # 初始化通知服务(已修改)
lib/screens/focus_screen.dart          # 计时完成时调用通知(已修改)
android/app/src/main/AndroidManifest.xml  # Android权限已修改

服务设计

  • 单例模式: 全局只有一个 NotificationService 实例
  • 延迟初始化: 首次调用时才初始化
  • 平台检测: 自动识别 Web 平台并跳过

📝 代码实现

1. NotificationService 类

位置: lib/services/notification_service.dart

关键方法:

initialize() - 初始化服务

Future<void> initialize() async {
  if (_initialized) return;

  // Web 平台跳过
  if (kIsWeb) return;

  // Android/iOS 配置
  const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
  const iosSettings = DarwinInitializationSettings(...);

  await _notifications.initialize(initSettings);
}

requestPermissions() - 请求权限iOS

Future<bool> requestPermissions() async {
  // iOS 需要显式请求Android 自动授予
  final result = await _notifications
      .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
      ?.requestPermissions(...);
  return result ?? true;
}

showFocusCompletedNotification() - 显示完成通知

Future<void> showFocusCompletedNotification({
  required int minutes,
  required int distractionCount,
}) async {
  final title = '🎉 Focus session complete!';
  final body = distractionCount == 0
      ? 'You focused for $minutes minutes without distractions!'
      : 'You focused for $minutes minutes. Great effort!';

  await _notifications.show(0, title, body, details);
}

2. main.dart 初始化

修改内容:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await StorageService.init();

  // 新增:初始化通知服务
  final notificationService = NotificationService();
  await notificationService.initialize();
  await notificationService.requestPermissions();

  runApp(MyApp(...));
}

何时调用:

  • 应用启动时自动初始化
  • 自动请求权限iOS 会弹出权限对话框)

3. FocusScreen 计时完成

修改位置: lib/screens/focus_screen.dart 第 56-79 行

修改内容:

void _onTimerComplete() async {
  _timer.cancel();
  _saveFocusSession(completed: true);

  // 新增:发送通知
  final notificationService = NotificationService();
  await notificationService.showFocusCompletedNotification(
    minutes: widget.durationMinutes,
    distractionCount: _distractions.length,
  );

  if (!mounted) return;
  Navigator.pushReplacement(...);
}

触发时机:

  • 计时器倒数到 0 时
  • 在导航到 Complete Screen 之前
  • 保存数据之后

📱 Android 配置

AndroidManifest.xml

文件位置: android/app/src/main/AndroidManifest.xml

添加的权限:

<!-- Android 13+ 通知权限(必需)-->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<!-- 震动权限(可选,增强体验)-->
<uses-permission android:name="android.permission.VIBRATE"/>

<!-- Wake Lock可选后台计时-->
<uses-permission android:name="android.permission.WAKE_LOCK"/>

权限说明

权限 必需性 用途 Android版本
POST_NOTIFICATIONS 必需 发送通知 13+ (API 33+)
VIBRATE 可选 通知震动 所有版本
WAKE_LOCK 可选 后台计时 所有版本

Android 权限流程

Android 12 及以下:

  1. 自动授予通知权限
  2. 无需用户确认

Android 13+:

  1. 首次发送通知时,系统自动弹出权限对话框
  2. 用户可以选择"允许"或"拒绝"
  3. 拒绝后可在设置中手动开启

🍎 iOS 配置

Info.plist

不需要修改 - iOS 通知权限在运行时请求,无需配置文件声明。

iOS 权限流程

  1. 首次启动:

    • App 启动时调用 requestPermissions()
    • 系统弹出权限对话框:
      "FocusBuddy" Would Like to Send You Notifications
      Notifications may include alerts, sounds, and icon badges.
      [Don't Allow] [Allow]
      
  2. 用户选择:

    • 允许: 正常发送通知
    • 拒绝: 静默失败(不影响应用运行)
  3. 后续修改:

    • 用户可在 设置 > FocusBuddy > 通知 中修改

🌐 Web 平台处理

策略:优雅降级

为什么 Web 不支持:

  • flutter_local_notifications 不支持 Web
  • Web 使用不同的通知 API需要 Service Worker

如何处理:

if (kIsWeb) {
  print('Notifications not supported on web platform');
  return; // 静默跳过,不报错
}

用户体验:

  • Web 版用户不会看到通知
  • 不会报错或崩溃
  • 计时完成后仍正常跳转到 Complete Screen

未来改进 (可选):

  • 使用 Web Notification API
  • 或显示应用内弹窗提示

🧪 测试指南

Android 测试

准备工作

# 1. 连接 Android 设备或启动模拟器
flutter devices

# 2. 运行应用
flutter run -d <android-device-id>

测试步骤

测试 1: 首次权限请求Android 13+

1. 卸载应用(清除权限状态)
2. 重新安装并启动
3. 开始一次专注(设置为 1 分钟测试)
4. 等待计时完成
5. 预期:系统弹出权限对话框
6. 点击"允许"
7. 预期:看到通知

测试 2: 前台通知

1. 应用在前台
2. 开始专注1分钟
3. 等待完成
4. 预期:
   - 顶部通知栏出现通知
   - 有震动(如果手机未静音)
   - 有声音(如果手机未静音)
   - 自动跳转到 Complete Screen

测试 3: 后台通知

1. 开始专注1分钟
2. 按 Home 键,应用进入后台
3. 等待计时完成
4. 预期:
   - 收到通知
   - 点击通知可回到应用
   - 看到 Complete Screen

测试 4: 拒绝权限

1. 在设置中禁用通知权限
2. 开始专注
3. 完成后不会有通知
4. 但应用正常跳转到 Complete Screen
5. 预期:无崩溃

iOS 测试

准备工作

# 需要 macOS + Xcode
# 1. 连接 iPhone 或启动模拟器
flutter devices

# 2. 运行应用
flutter run -d <ios-device-id>

测试步骤

测试 1: 权限对话框

1. 首次启动应用
2. 预期:立即看到权限对话框
   "FocusBuddy Would Like to Send You Notifications"
3. 点击 "Allow"

测试 2: 通知内容

1. 完成一次专注0次分心
2. 预期通知内容:
   标题: 🎉 Focus session complete!
   正文: You focused for 15 minutes without distractions!

3. 完成一次专注3次分心
4. 预期通知内容:
   标题: 🎉 Focus session complete!
   正文: You focused for 15 minutes. Great effort!

测试 3: 后台通知

1. 开始专注
2. 滑回主屏幕
3. 等待完成
4. 预期:锁屏/顶部有通知
5. 点击通知打开应用

Web 测试

测试步骤

1. 运行 Web 版flutter run -d edge
2. 开始专注
3. 等待完成
4. 预期:
   - 控制台输出Notifications not supported on web platform
   - 无通知
   - 正常跳转到 Complete Screen
   - 无报错

通过标准: 应用正常运行,无崩溃


📊 通知内容逻辑

标题

固定内容:🎉 Focus session complete!

正文

动态内容,根据分心次数变化:

if (distractionCount == 0) {
  body = 'You focused for $minutes minutes without distractions!';
} else {
  body = 'You focused for $minutes minutes. Great effort!';
}

示例:

  • 25分钟0次分心: "You focused for 25 minutes without distractions!"
  • 15分钟3次分心: "You focused for 15 minutes. Great effort!"

设计理念

  • 正向鼓励: 即使有分心,也用"Great effort"
  • 无惩罚: 不会说"但你分心了3次"
  • 符合产品价值观: 温柔、支持、无评判

🔧 配置选项

当前实现

特性 Android iOS
声音
震动
Badge
优先级 High Default
Channel focus_completed -

可调整的参数

notification_service.dart 中:

// Android 配置
const androidDetails = AndroidNotificationDetails(
  'focus_completed',              // Channel ID不建议改
  'Focus Session Completed',      // Channel Name用户可见
  channelDescription: '...',      // Channel描述
  importance: Importance.high,    // 重要性(改为 max 会横幅提示)
  priority: Priority.high,        // 优先级
  enableVibration: true,          // 震动开关
  playSound: true,                // 声音开关
);

// iOS 配置
const iosDetails = DarwinNotificationDetails(
  presentAlert: true,             // 显示提示
  presentBadge: true,             // 显示角标
  presentSound: true,             // 播放声音
);

🚀 未来改进建议

优先级低(可选)

  1. 自定义通知声音

    • 添加温柔的提示音
    • 替换系统默认声音
    • 需要音频资源
  2. 定时提醒

    • "你已经2小时没专注了要来一次吗"
    • 使用 showReminderNotification()
    • 需要后台任务WorkManager
  3. 通知操作按钮

    • Android: 通知上添加 "再来一次" 按钮
    • 点击直接开始新的专注
    • 需要额外配置
  4. 通知统计

    • "本周你已经专注了 12 小时!"
    • 定期(如周日)发送总结
    • 需要调度逻辑
  5. Web 通知支持

    • 使用 Web Notification API
    • 需要 Service Worker
    • 需要用户手动授权

常见问题

Q1: 为什么 Android 13 没有弹出权限对话框?

A: Android 13+ 权限会在首次发送通知时自动弹出,不是应用启动时。

解决方案:

  • 完成一次完整的专注会话
  • 或在设置中手动开启

Q2: iOS 模拟器收不到通知?

A: iOS 模拟器通知功能有限制。

解决方案:

  • 使用真机测试
  • 或检查模拟器的通知设置

Q3: Web 版为什么没有通知?

A: flutter_local_notifications 不支持 Web。

当前方案: 优雅降级,不报错 未来方案: 实现 Web Notification API


Q4: 通知没有声音?

A: 检查以下设置:

  1. 手机是否静音/勿扰模式
  2. 应用通知权限是否开启
  3. 通知重要性是否足够高

Q5: 后台计时不准确?

A: Android 后台限制可能影响计时。

建议:

  • 添加前台服务Foreground Service
  • 或使用 WorkManager
  • 当前 MVP 不实现,用户应在前台使用

📝 总结

已实现

  • 通知服务架构
  • 计时完成通知
  • Android 权限配置
  • iOS 权限请求
  • Web 平台兼容
  • 完整文档

📊 影响

  • 代码变更: 3 个文件新增1个修改2个
  • 新增行数: ~200 行
  • 配置变更: Android + iOS 权限
  • 测试时间: ~15 分钟(手动测试)

🎯 MVP 完成度

██████████████████▓░ 95% → 98%

新增功能: 本地通知 待完成: 应用图标、截图、上架准备


文档版本: 1.0 最后更新: 2025-11-22 维护者: Claude