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

531 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📱 本地通知功能 - 实现文档
**实现日期**: 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()` - 初始化服务
```dart
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
```dart
Future<bool> requestPermissions() async {
// iOS 需要显式请求Android 自动授予
final result = await _notifications
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(...);
return result ?? true;
}
```
#### `showFocusCompletedNotification()` - 显示完成通知
```dart
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 初始化
**修改内容**:
```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 行
**修改内容**:
```dart
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`
**添加的权限**:
```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
**如何处理**:
```dart
if (kIsWeb) {
print('Notifications not supported on web platform');
return; // 静默跳过,不报错
}
```
**用户体验**:
- Web 版用户不会看到通知
- 不会报错或崩溃
- 计时完成后仍正常跳转到 Complete Screen
**未来改进** (可选):
- 使用 Web Notification API
- 或显示应用内弹窗提示
---
## 🧪 测试指南
### Android 测试
#### 准备工作
```bash
# 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 测试
#### 准备工作
```bash
# 需要 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!`
### 正文
动态内容,根据分心次数变化:
```dart
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` 中:
```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 不实现,用户应在前台使用
---
## 📝 总结
### ✅ 已实现
- [x] 通知服务架构
- [x] 计时完成通知
- [x] Android 权限配置
- [x] iOS 权限请求
- [x] Web 平台兼容
- [x] 完整文档
### 📊 影响
- **代码变更**: 3 个文件新增1个修改2个
- **新增行数**: ~200 行
- **配置变更**: Android + iOS 权限
- **测试时间**: ~15 分钟(手动测试)
### 🎯 MVP 完成度
```
██████████████████▓░ 95% → 98%
```
**新增功能**: 本地通知 ✅
**待完成**: 应用图标、截图、上架准备
---
**文档版本**: 1.0
**最后更新**: 2025-11-22
**维护者**: Claude