12 KiB
📱 本地通知功能 - 实现文档
实现日期: 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 及以下:
- 自动授予通知权限
- 无需用户确认
Android 13+:
- 首次发送通知时,系统自动弹出权限对话框
- 用户可以选择"允许"或"拒绝"
- 拒绝后可在设置中手动开启
🍎 iOS 配置
Info.plist
不需要修改 - iOS 通知权限在运行时请求,无需配置文件声明。
iOS 权限流程
-
首次启动:
- App 启动时调用
requestPermissions() - 系统弹出权限对话框:
"FocusBuddy" Would Like to Send You Notifications Notifications may include alerts, sounds, and icon badges. [Don't Allow] [Allow]
- App 启动时调用
-
用户选择:
- 允许: 正常发送通知
- 拒绝: 静默失败(不影响应用运行)
-
后续修改:
- 用户可在 设置 > 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, // 播放声音
);
🚀 未来改进建议
优先级低(可选)
-
自定义通知声音
- 添加温柔的提示音
- 替换系统默认声音
- 需要音频资源
-
定时提醒
- "你已经2小时没专注了,要来一次吗?"
- 使用
showReminderNotification() - 需要后台任务(WorkManager)
-
通知操作按钮
- Android: 通知上添加 "再来一次" 按钮
- 点击直接开始新的专注
- 需要额外配置
-
通知统计
- "本周你已经专注了 12 小时!"
- 定期(如周日)发送总结
- 需要调度逻辑
-
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: 检查以下设置:
- 手机是否静音/勿扰模式
- 应用通知权限是否开启
- 通知重要性是否足够高
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