You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
10 KiB
315 lines
10 KiB
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
|
|
import '../pages/message/chat_page.dart';
|
|
import '../controller/message/conversation_controller.dart';
|
|
|
|
/// 本地通知服务
|
|
class LocalNotificationService {
|
|
// 单例模式
|
|
static final LocalNotificationService _instance = LocalNotificationService._internal();
|
|
factory LocalNotificationService() => _instance;
|
|
static LocalNotificationService get instance => _instance;
|
|
|
|
final FlutterLocalNotificationsPlugin _localNotifications = FlutterLocalNotificationsPlugin();
|
|
bool _notificationsInitialized = false;
|
|
static int _notificationId = 0; // 通知ID计数器
|
|
|
|
LocalNotificationService._internal();
|
|
|
|
/// 初始化本地通知
|
|
Future<void> initialize() async {
|
|
if (_notificationsInitialized) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Android 初始化设置
|
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
|
|
// iOS 初始化设置
|
|
const DarwinInitializationSettings initializationSettingsIOS =
|
|
DarwinInitializationSettings(
|
|
requestAlertPermission: true,
|
|
requestBadgePermission: true,
|
|
requestSoundPermission: true,
|
|
);
|
|
|
|
// 初始化设置
|
|
const InitializationSettings initializationSettings =
|
|
InitializationSettings(
|
|
android: initializationSettingsAndroid,
|
|
iOS: initializationSettingsIOS,
|
|
);
|
|
|
|
// 初始化插件
|
|
final bool? initialized = await _localNotifications.initialize(
|
|
initializationSettings,
|
|
onDidReceiveNotificationResponse: _onNotificationTapped,
|
|
);
|
|
|
|
if (initialized == true) {
|
|
_notificationsInitialized = true;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [LocalNotificationService] 本地通知初始化成功');
|
|
}
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 本地通知初始化失败');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [LocalNotificationService] 本地通知初始化异常: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知点击回调
|
|
void _onNotificationTapped(NotificationResponse response) {
|
|
try {
|
|
final payload = response.payload;
|
|
if (payload != null && payload.isNotEmpty) {
|
|
// payload 格式: "type|fromId"
|
|
final parts = payload.split('|');
|
|
if (parts.length >= 2) {
|
|
final fromId = parts[1];
|
|
// 跳转到聊天页面
|
|
Get.to(() => ChatPage(
|
|
userId: fromId,
|
|
userData: null,
|
|
));
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 处理通知点击失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 获取消息内容文本
|
|
String _getMessageContent(EMMessage message) {
|
|
try {
|
|
if (message.body.type == MessageType.TXT) {
|
|
final body = message.body as EMTextMessageBody;
|
|
return body.content ?? '';
|
|
} else if (message.body.type == MessageType.IMAGE) {
|
|
return '[图片]';
|
|
} else if (message.body.type == MessageType.VOICE) {
|
|
return '[语音]';
|
|
} else if (message.body.type == MessageType.VIDEO) {
|
|
return '[视频]';
|
|
} else if (message.body.type == MessageType.FILE) {
|
|
return '[文件]';
|
|
} else if (message.body.type == MessageType.LOCATION) {
|
|
return '[位置]';
|
|
} else if (message.body.type == MessageType.CUSTOM) {
|
|
final body = message.body as EMCustomMessageBody;
|
|
if (body.event == 'live_room_invite') {
|
|
return '[分享房间]';
|
|
} else if (body.event == 'gift') {
|
|
return '[礼物]';
|
|
} else if (body.event == 'call') {
|
|
// 解析通话类型
|
|
try {
|
|
if (body.params != null) {
|
|
final callType = body.params!['callType'] ?? 'voice';
|
|
if (callType == 'video') {
|
|
return '[视频通话]';
|
|
} else if (callType == 'voice') {
|
|
return '[语音通话]';
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 解析通话消息类型失败: $e');
|
|
}
|
|
}
|
|
return '[通话消息]';
|
|
}
|
|
return '[自定义消息]';
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 获取消息内容失败: $e');
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/// 显示本地通知(当 APP 在后台时)
|
|
Future<void> showNotification(EMMessage message, {Set<String>? activeChatUserIds}) async {
|
|
if (!_notificationsInitialized) {
|
|
await initialize();
|
|
}
|
|
|
|
try {
|
|
// 获取消息发送者ID
|
|
final fromId = message.from;
|
|
if (fromId == null || fromId.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
// 检查发送者是否是当前正在聊天的用户
|
|
if (activeChatUserIds != null && activeChatUserIds.contains(fromId)) {
|
|
// 是当前正在聊天的用户,不显示通知
|
|
return;
|
|
}
|
|
|
|
// 获取消息内容
|
|
String messageContent = _getMessageContent(message);
|
|
if (messageContent.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
// 从消息扩展字段中获取用户信息(头像、昵称)
|
|
Map<String, dynamic>? attributes;
|
|
try {
|
|
attributes = message.attributes;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 无法访问消息扩展字段: $e');
|
|
}
|
|
return;
|
|
}
|
|
|
|
String? nickName;
|
|
String? avatarUrl;
|
|
|
|
if (attributes != null) {
|
|
// 从扩展字段中获取发送者信息
|
|
nickName = attributes['sender_nickName'] as String?;
|
|
avatarUrl = attributes['sender_avatarUrl'] as String?;
|
|
}
|
|
|
|
// 如果从消息扩展字段中获取不到,尝试从 ConversationController 的缓存中获取
|
|
if ((nickName == null || nickName.isEmpty) || (avatarUrl == null || avatarUrl.isEmpty)) {
|
|
try {
|
|
if (Get.isRegistered<ConversationController>()) {
|
|
final conversationController = Get.find<ConversationController>();
|
|
final cachedUserInfo = conversationController.getCachedUserInfo(fromId);
|
|
if (cachedUserInfo != null) {
|
|
nickName = nickName ?? cachedUserInfo.nickName;
|
|
avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 从ConversationController获取用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 使用默认值
|
|
final finalNickName = nickName ?? fromId;
|
|
|
|
// 处理视频通话消息
|
|
Map<String, dynamic>? callInfo;
|
|
String? callType;
|
|
String? callStatus;
|
|
|
|
try {
|
|
// 自定义消息
|
|
if (message.body.type == MessageType.CUSTOM) {
|
|
final customBody = message.body as EMCustomMessageBody;
|
|
if (customBody.event == 'call' && customBody.params != null) {
|
|
final params = customBody.params!;
|
|
callType = params['callType'] ?? 'voice';
|
|
callStatus = params['callStatus'] ?? 'missed';
|
|
callInfo = {
|
|
'callType': callType,
|
|
'callStatus': callStatus,
|
|
};
|
|
}
|
|
}
|
|
|
|
// 如果是视频通话且状态为 missed 或 calling
|
|
if (callInfo != null && callType != null && callStatus != null) {
|
|
if (callType == 'video' && (callStatus == 'missed' || callStatus == 'calling')) {
|
|
// 显示视频通话通知
|
|
final notificationId = _notificationId++;
|
|
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
|
AndroidNotificationDetails(
|
|
'video_call_channel',
|
|
'视频通话',
|
|
channelDescription: '视频通话通知',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
showWhen: true,
|
|
);
|
|
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
|
|
DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
);
|
|
const NotificationDetails platformChannelSpecifics =
|
|
NotificationDetails(
|
|
android: androidPlatformChannelSpecifics,
|
|
iOS: iOSPlatformChannelSpecifics,
|
|
);
|
|
|
|
await _localNotifications.show(
|
|
notificationId,
|
|
'视频通话',
|
|
'$finalNickName 邀请您进行视频通话',
|
|
platformChannelSpecifics,
|
|
payload: 'video_call|$fromId',
|
|
);
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('📱 [LocalNotificationService] 显示视频通话本地通知: $fromId');
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [LocalNotificationService] 处理视频通话通知失败: $e');
|
|
}
|
|
}
|
|
|
|
// 显示普通消息通知
|
|
final notificationId = _notificationId++;
|
|
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
|
AndroidNotificationDetails(
|
|
'message_channel',
|
|
'消息通知',
|
|
channelDescription: '新消息通知',
|
|
importance: Importance.high,
|
|
priority: Priority.high,
|
|
showWhen: true,
|
|
);
|
|
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
|
|
DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
);
|
|
const NotificationDetails platformChannelSpecifics =
|
|
NotificationDetails(
|
|
android: androidPlatformChannelSpecifics,
|
|
iOS: iOSPlatformChannelSpecifics,
|
|
);
|
|
|
|
await _localNotifications.show(
|
|
notificationId,
|
|
finalNickName,
|
|
messageContent,
|
|
platformChannelSpecifics,
|
|
payload: 'message|$fromId',
|
|
);
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('📱 [LocalNotificationService] 显示本地通知: $fromId, $messageContent');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [LocalNotificationService] 显示本地通知失败: $e');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|