|
|
|
@ -1,6 +1,5 @@ |
|
|
|
import 'dart:io'; |
|
|
|
import 'dart:async'; |
|
|
|
import 'dart:convert'; |
|
|
|
import 'package:flutter/widgets.dart'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
@ -1415,158 +1414,174 @@ class IMManager { |
|
|
|
} |
|
|
|
|
|
|
|
// 处理视频通话消息(CALL消息)- 显示特殊的视频通话邀请弹框 |
|
|
|
if (message.body.type == MessageType.TXT) { |
|
|
|
try { |
|
|
|
final textBody = message.body as EMTextMessageBody; |
|
|
|
final content = textBody.content; |
|
|
|
if (content != null && content.startsWith('[CALL:]')) { |
|
|
|
// 解析通话信息 |
|
|
|
// 支持新格式的自定义消息和旧格式的文本消息 |
|
|
|
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, |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 如果解析到通话信息,检查是否需要显示视频通话邀请弹框 |
|
|
|
if (callInfo != null && callType != null && callStatus != null) { |
|
|
|
// 只处理视频通话且状态为 missed 或 calling 的消息(新邀请) |
|
|
|
if (callType == 'video' && (callStatus == 'missed' || callStatus == 'calling')) { |
|
|
|
// 获取用户信息 |
|
|
|
Map<String, dynamic>? attributes; |
|
|
|
try { |
|
|
|
final jsonStr = content.substring(7); // 移除 '[CALL:]' 前缀 |
|
|
|
final callInfo = jsonDecode(jsonStr) as Map<String, dynamic>; |
|
|
|
final callType = callInfo['callType'] as String?; |
|
|
|
final callStatus = callInfo['callStatus'] as String?; |
|
|
|
|
|
|
|
// 只处理视频通话且状态为 missed 或 calling 的消息(新邀请) |
|
|
|
if (callType == 'video' && (callStatus == 'missed' || callStatus == 'calling')) { |
|
|
|
// 获取用户信息 |
|
|
|
Map<String, dynamic>? attributes; |
|
|
|
try { |
|
|
|
attributes = message.attributes; |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
attributes = message.attributes; |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
String? nickName; |
|
|
|
String? avatarUrl; |
|
|
|
|
|
|
|
String? nickName; |
|
|
|
String? avatarUrl; |
|
|
|
if (attributes != null) { |
|
|
|
nickName = attributes['sender_nickName'] as String?; |
|
|
|
avatarUrl = attributes['sender_avatarUrl'] as String?; |
|
|
|
} |
|
|
|
|
|
|
|
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('⚠️ [IMManager] 从ConversationController获取用户信息失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 如果从消息扩展字段中获取不到,尝试从 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; |
|
|
|
final finalNickName = nickName ?? fromId; |
|
|
|
final finalAvatarUrl = avatarUrl ?? ''; |
|
|
|
|
|
|
|
// 接收方收到视频通话时,开始播放来电铃声 |
|
|
|
final callManager = CallManager.instance; |
|
|
|
callManager.startCallAudio(); |
|
|
|
|
|
|
|
// 显示视频通话邀请弹框 |
|
|
|
SmartDialog.show( |
|
|
|
builder: (context) { |
|
|
|
return VideoCallInviteDialog( |
|
|
|
avatarUrl: finalAvatarUrl, |
|
|
|
nickName: finalNickName, |
|
|
|
onTap: () async { |
|
|
|
// 关闭弹框 |
|
|
|
SmartDialog.dismiss(); |
|
|
|
|
|
|
|
// 停止播放来电铃声 |
|
|
|
callManager.stopCallAudio(); |
|
|
|
|
|
|
|
// 只跳转到视频通话页面,不自动接通 |
|
|
|
Get.to(() => VideoCallPage( |
|
|
|
targetUserId: fromId, |
|
|
|
isInitiator: false, |
|
|
|
)); |
|
|
|
}, |
|
|
|
onAccept: () async { |
|
|
|
// 关闭弹框 |
|
|
|
SmartDialog.dismiss(); |
|
|
|
|
|
|
|
// 停止播放来电铃声(acceptCall 中也会停止,但这里提前停止以更快响应) |
|
|
|
callManager.stopCallAudio(); |
|
|
|
|
|
|
|
// 接听通话 |
|
|
|
ChatController? chatController; |
|
|
|
try { |
|
|
|
final tag = 'chat_$fromId'; |
|
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 从ConversationController获取用户信息失败: $e'); |
|
|
|
|
|
|
|
final accepted = await callManager.acceptCall( |
|
|
|
message: message, |
|
|
|
chatController: chatController, |
|
|
|
); |
|
|
|
|
|
|
|
if (accepted) { |
|
|
|
// 跳转到视频通话页面 |
|
|
|
Get.to(() => VideoCallPage( |
|
|
|
targetUserId: fromId, |
|
|
|
isInitiator: false, |
|
|
|
)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
final finalNickName = nickName ?? fromId; |
|
|
|
final finalAvatarUrl = avatarUrl ?? ''; |
|
|
|
|
|
|
|
// 显示视频通话邀请弹框 |
|
|
|
SmartDialog.show( |
|
|
|
builder: (context) { |
|
|
|
return VideoCallInviteDialog( |
|
|
|
avatarUrl: finalAvatarUrl, |
|
|
|
nickName: finalNickName, |
|
|
|
onTap: () async { |
|
|
|
// 关闭弹框 |
|
|
|
SmartDialog.dismiss(); |
|
|
|
|
|
|
|
// 只跳转到视频通话页面,不自动接通 |
|
|
|
Get.to(() => VideoCallPage( |
|
|
|
targetUserId: fromId, |
|
|
|
isInitiator: false, |
|
|
|
)); |
|
|
|
}, |
|
|
|
onAccept: () async { |
|
|
|
// 关闭弹框 |
|
|
|
SmartDialog.dismiss(); |
|
|
|
|
|
|
|
// 接听通话 |
|
|
|
final callManager = CallManager.instance; |
|
|
|
ChatController? chatController; |
|
|
|
try { |
|
|
|
final tag = 'chat_$fromId'; |
|
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
final accepted = await callManager.acceptCall( |
|
|
|
message: message, |
|
|
|
chatController: chatController, |
|
|
|
); |
|
|
|
|
|
|
|
if (accepted) { |
|
|
|
// 跳转到视频通话页面 |
|
|
|
Get.to(() => VideoCallPage( |
|
|
|
targetUserId: fromId, |
|
|
|
isInitiator: false, |
|
|
|
)); |
|
|
|
} |
|
|
|
}, |
|
|
|
onReject: () async { |
|
|
|
// 关闭弹框 |
|
|
|
SmartDialog.dismiss(); |
|
|
|
|
|
|
|
// 拒绝通话 |
|
|
|
final callManager = CallManager.instance; |
|
|
|
ChatController? chatController; |
|
|
|
try { |
|
|
|
final tag = 'chat_$fromId'; |
|
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
await callManager.rejectCall( |
|
|
|
message: message, |
|
|
|
chatController: chatController, |
|
|
|
); |
|
|
|
}, |
|
|
|
}, |
|
|
|
onReject: () async { |
|
|
|
// 先关闭弹框 |
|
|
|
SmartDialog.dismiss(); |
|
|
|
|
|
|
|
// 停止播放来电铃声(rejectCall 中也会停止,但这里提前停止以更快响应) |
|
|
|
callManager.stopCallAudio(); |
|
|
|
|
|
|
|
// 拒绝通话(会修改消息状态为 rejected) |
|
|
|
ChatController? chatController; |
|
|
|
try { |
|
|
|
final tag = 'chat_$fromId'; |
|
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 调用拒绝通话,会使用 modifyMessage 修改消息状态 |
|
|
|
await callManager.rejectCall( |
|
|
|
message: message, |
|
|
|
chatController: chatController, |
|
|
|
); |
|
|
|
}, |
|
|
|
alignment: Alignment.topCenter, |
|
|
|
animationType: SmartAnimationType.centerFade_otherSlide, |
|
|
|
animationTime: Duration(milliseconds: 300), |
|
|
|
maskColor: Colors.transparent, |
|
|
|
maskWidget: null, |
|
|
|
clickMaskDismiss: false, |
|
|
|
); |
|
|
|
|
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('📞 [IMManager] 显示视频通话邀请弹框: $fromId'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 对于所有 CALL 消息(包括视频和语音),都不显示普通消息通知弹框 |
|
|
|
return; |
|
|
|
} catch (e) { |
|
|
|
// 解析失败,继续处理普通消息 |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 解析CALL消息失败: $e'); |
|
|
|
} |
|
|
|
}, |
|
|
|
alignment: Alignment.topCenter, |
|
|
|
animationType: SmartAnimationType.centerFade_otherSlide, |
|
|
|
animationTime: Duration(milliseconds: 300), |
|
|
|
maskColor: Colors.transparent, |
|
|
|
maskWidget: null, |
|
|
|
clickMaskDismiss: false, |
|
|
|
keepSingle: true, // 确保只有一个弹框显示 |
|
|
|
); |
|
|
|
|
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('📞 [IMManager] 显示视频通话邀请弹框: $fromId'); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// 解析失败,继续处理 |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 解析消息内容失败: $e'); |
|
|
|
} |
|
|
|
|
|
|
|
// 对于所有 CALL 消息(包括视频和语音),都不显示普通消息通知弹框 |
|
|
|
return; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// 解析失败,继续处理普通消息 |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 解析CALL消息失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1721,33 +1736,7 @@ class IMManager { |
|
|
|
try { |
|
|
|
if (message.body.type == MessageType.TXT) { |
|
|
|
final body = message.body as EMTextMessageBody; |
|
|
|
final content = body.content; |
|
|
|
|
|
|
|
// 检查是否是CALL消息 |
|
|
|
if (content != null && content.startsWith('[CALL:]')) { |
|
|
|
try { |
|
|
|
final jsonStr = content.substring(7); // 移除 '[CALL:]' 前缀 |
|
|
|
final callInfo = jsonDecode(jsonStr) as Map<String, dynamic>; |
|
|
|
final callType = callInfo['callType'] as String?; |
|
|
|
if (callType == 'video') { |
|
|
|
return '[视频通话]'; |
|
|
|
} else if (callType == 'voice') { |
|
|
|
return '[语音通话]'; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// 解析失败,返回原始内容 |
|
|
|
if (Get.isLogEnable) { |
|
|
|
Get.log('⚠️ [IMManager] 解析CALL消息失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 检查是否是GIFT消息 |
|
|
|
if (content != null && content.startsWith('[GIFT:]')) { |
|
|
|
return '[礼物]'; |
|
|
|
} |
|
|
|
|
|
|
|
return content ?? ''; |
|
|
|
return body.content; |
|
|
|
} else if (message.body.type == MessageType.IMAGE) { |
|
|
|
return '[图片]'; |
|
|
|
} else if (message.body.type == MessageType.VOICE) { |
|
|
|
@ -1762,6 +1751,25 @@ class IMManager { |
|
|
|
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('⚠️ [IMManager] 解析通话消息类型失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
return '[通话消息]'; |
|
|
|
} |
|
|
|
return '[自定义消息]'; |
|
|
|
} |
|
|
|
|