diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index 5f8d80c..039a149 100644 --- a/lib/controller/message/call_controller.dart +++ b/lib/controller/message/call_controller.dart @@ -1,19 +1,16 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; -import 'package:agora_rtm/agora_rtm.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; -import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:permission_handler/permission_handler.dart'; +import '../discover/room_controller.dart'; import '../overlay_controller.dart'; import 'chat_controller.dart'; @@ -118,88 +115,7 @@ class CallController extends GetxController { } }); - // 注册 RTM 消息监听器,用于接收通话相关的 RTM 消息 - _registerRtmMessageListener(); - } - - /// 注册 RTM 消息监听器 - void _registerRtmMessageListener() { - RTMManager.instance.onMessageEvent = (MessageEvent event) { - _handleRtmMessage(event); - }; - print('✅ [CallController] RTM 消息监听器已注册'); - } - - /// 处理 RTM 消息 - Future _handleRtmMessage(MessageEvent event) async { - try { - // 解析消息内容 - String messageText; - if (event.message is String) { - messageText = event.message as String; - } else if (event.message is Uint8List) { - messageText = utf8.decode(event.message as Uint8List); - } else { - messageText = event.message.toString(); - } - - final messageData = json.decode(messageText) as Map; - print('📥 [CallController] 收到 RTM 消息: $messageData'); - - // 处理通话消息 - if (messageData['type'] == 'call_message') { - final event = messageData['event'] as String?; - if (event == 'accept') { - // 发起方收到 accept 消息,设置远端用户 UID 并启动计时器 - final uid = messageData['uid']; - if (uid != null) { - remoteUid.value = uid is int ? uid : int.tryParse(uid.toString()); - print( - '📞 [CallController] 收到 accept 消息,设置 remoteUid: ${remoteUid.value}', - ); - } - // 取消超时计时器(对方已接听) - _stopCallTimeoutTimer(); - // 发起方收到 accept 消息后,启动通话计时器 - _startCallTimer(); - print('📞 [CallController] 收到 accept 消息,已启动通话计时器'); - } else if (event == 'hangup') { - // 收到挂断消息,执行退出逻辑 - print('📞 [CallController] 收到 hangup 消息,执行退出逻辑'); - final channelId = messageData['channelId'] as String?; - if (channelId != null && channelId.isNotEmpty) { - RTMManager.instance.unsubscribe(channelId); - } - SmartDialog.dismiss(tag: 'video_call_invite_dialog'); - _handleHangupMessage(); - } else if (event == 'reject') { - // 发起方收到 reject 消息,执行退出逻辑 - print('📞 [CallController] 收到 reject 消息,执行退出逻辑'); - // 取消超时计时器(对方已拒绝) - _stopCallTimeoutTimer(); - final channelId = messageData['channelId'] as String?; - if (channelId != null && channelId.isNotEmpty) { - RTMManager.instance.unsubscribe(channelId); - print('✅ [CallController] 已取消订阅 RTM 频道: $channelId'); - } - // 关闭通话小窗口 - if (Get.isRegistered()) { - final overlayController = Get.find(); - overlayController.hideVideoCall(); - print('✅ [CallController] 已关闭通话小窗口'); - } - // 退出 VideoCallPage(如果当前在 VideoCallPage) - if (Get.currentRoute.contains('VideoCallPage')) { - Get.back(); - print('✅ [CallController] 已退出 VideoCallPage'); - } - // 结束通话 - await endCall(callDuration: callDurationSeconds.value); - } - } - } catch (e) { - print('❌ [CallController] 处理 RTM 消息失败: $e'); - } + // RTM 消息监听器已移除,通话相关消息改为通过onMessageContentChanged处理 } /// 创建一对一RTC频道 @@ -326,10 +242,6 @@ class CallController extends GetxController { ); print('✅ [CallController] 已加入 RTC 频道: ${channelData.channelId}'); - // 加入 RTC 频道后订阅 RTM 频道 - await RTMManager.instance.subscribe(channelData.channelId); - print('✅ [CallController] 已订阅 RTM 频道: ${channelData.channelId}'); - // 启动30秒超时计时器(如果30秒内对方未接听,自动取消通话) _startCallTimeoutTimer(); @@ -408,15 +320,33 @@ class CallController extends GetxController { print('📞 [CallController] 视频通话,已打开摄像头'); } - // 加入 RTC 频道,接听通话 - await joinChannel(channelId); - print('✅ [CallController] 已加入 RTC 频道: $channelId'); + // 获取 RTC token 并加入频道 + final response = await _networkService.rtcApi.getSwRtcToken(channelId); + final base = response.data; + if (base.isSuccess && base.data != null) { + rtcChannel.value = base.data; + + // 保存 UID 为全局变量 + _callUid = base.data!.uid; + + await _joinRtcChannel( + base.data!.token, + channelId, + base.data!.uid, + ClientRoleType.clientRoleBroadcaster, + ); - // 加入 RTC 频道后,从消息中获取发起方的 uid,设置为远端用户 UID - final initiatorUid = callInfo['uid'] as int?; - if (initiatorUid != null) { - remoteUid.value = initiatorUid; - print('📞 [CallController] 从消息中获取到发起方 UID: $initiatorUid,已设置 remoteUid'); + // 从消息中获取发起方的 uid,设置为远端用户 UID + final initiatorUid = callInfo['uid'] as int?; + if (initiatorUid != null) { + remoteUid.value = initiatorUid; + print( + '📞 [CallController] 从消息中获取到发起方 UID: $initiatorUid,已设置 remoteUid', + ); + } + } else { + SmartDialog.showToast('获取RTC token失败'); + return false; } return true; @@ -454,22 +384,7 @@ class CallController extends GetxController { return false; } print('✅ [CallController] 已调用拒绝一对一RTC频道接口,channelId: $channelId'); - await RTMManager.instance.unsubscribe(channelId); - final callInfo = _parseCallInfo(message); - final callTypeStr = callInfo?['callType'] as String?; - final callType = callTypeStr == 'video' ? 'video' : 'voice'; - - // 发送拒绝 RTM 消息 - await RTMManager.instance.publishChannelMessage( - channelName: channelId, - message: json.encode({ - 'type': 'call_message', - 'uid': 0, - 'channelId': channelId, - 'callType': callType, - 'event': 'reject', - }), - ); + // 服务端会自动修改消息callStatus为'rejected',客户端通过onMessageContentChanged收到通知 } // 清理通话会话 @@ -631,32 +546,13 @@ class CallController extends GetxController { // 保存 UID 为全局变量 _callUid = base.data!.uid; - // 订阅 RTM 频道 - await RTMManager.instance.subscribe(channelName); - - // 获取当前通话信息 - final callSession = currentCall.value; - final callType = callSession?.callType == CallType.video - ? 'video' - : 'voice'; - - // 发布 RTM 消息,包含 UID 和通话相关字段 - await RTMManager.instance.publishChannelMessage( - channelName: channelName, - message: json.encode({ - 'type': 'call_message', - 'uid': base.data!.uid, - 'callType': callType, - 'event': 'accept', - }), - ); - await _joinRtcChannel( base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleBroadcaster, ); + // 服务端会自动修改消息callStatus为'calling',客户端通过onMessageContentChanged收到通知 } } @@ -675,19 +571,7 @@ class CallController extends GetxController { role: roleType, rtcType: RTCType.call, ); - - final data = { - 'channelId': channelName, - 'seatNumber': 1, - 'isMicrophoneOn': true, - 'isVideoOn': true, - }; - final response = await _networkService.rtcApi.connectRtcChannel(data); - if (!response.data.isSuccess) { - SmartDialog.showToast(response.data.message); - return; - } - SmartDialog.showToast('加入通话成功'); + print('✅ [CallController] 已加入 RTC 频道: $channelName'); } Future _ensureRtcPermissions() async { @@ -759,36 +643,10 @@ class CallController extends GetxController { print('✅ [CallController] 已调用终止一对一RTC频道接口,channelId: $_callChannelId'); } - if (_callChannelId != null && - _callChannelId!.isNotEmpty && - _callUid != null) { - final callType = callSession?.callType == CallType.video - ? 'video' - : 'voice'; - - await RTMManager.instance.publishChannelMessage( - channelName: _callChannelId!, - message: json.encode({ - 'type': 'call_message', - 'uid': _callUid!, - 'channelId': _callChannelId, - 'callType': callType, - 'event': 'hangup', - }), - ); - print( - '✅ [CallController] 已发送 RTM 挂断消息,channelId: $_callChannelId, uid: $_callUid', - ); - } - // 离开RTC频道 await RTCManager.instance.leaveChannel(); - // 取消订阅 RTM 频道 - if (_callChannelId != null && _callChannelId!.isNotEmpty) { - await RTMManager.instance.unsubscribe(_callChannelId!); - print('✅ [CallController] 已取消订阅 RTM 频道: $_callChannelId'); - } + // 服务端会自动修改消息callStatus为'cancelled'或'terminated',客户端通过onMessageContentChanged收到通知 // 结束通话(传递通话时长) await endCall(callDuration: callDurationSeconds.value); @@ -812,41 +670,127 @@ class CallController extends GetxController { print('✅ [CallController] 通话已挂断'); } - /// 处理挂断消息(对方挂断时调用) - Future _handleHangupMessage() async { - // 关闭视频通话邀请弹框(如果正在显示) - SmartDialog.dismiss(); - print('✅ [CallController] 已关闭视频通话邀请弹框'); + /// 处理通话消息callStatus变化(通过onMessageContentChanged调用) + Future handleCallStatusChange({ + required EMMessage message, + required String callStatus, + String? channelId, + int? uid, + int? callDuration, + }) async { + print( + '📞 [CallController] 处理callStatus变化: callStatus=$callStatus, channelId=$channelId', + ); - // 取消超时计时器 - _stopCallTimeoutTimer(); + final callSession = currentCall.value; + if (callSession == null) { + print('⚠️ [CallController] 当前没有进行中的通话,忽略callStatus变化'); + return; + } - // 停止播放来电铃声 - stopCallAudio(); + // 如果提供了channelId,验证是否匹配当前通话 + if (channelId != null && + channelId.isNotEmpty && + _callChannelId != channelId) { + print( + '⚠️ [CallController] channelId不匹配,忽略callStatus变化: 当前=$_callChannelId, 消息=$channelId', + ); + return; + } - // 离开RTC频道 - await RTCManager.instance.leaveChannel(); + try { + if (callStatus == 'calling') { + // 通话接通 + print('📞 [CallController] 通话已接通,callStatus=$callStatus'); + + // 如果是发起方,设置远端用户UID并启动计时器 + if (callSession.isInitiator) { + // 停止播放来电铃声(对方已接听) + stopCallAudio(); + // 取消超时计时器(对方已接听) + _stopCallTimeoutTimer(); + // 启动通话计时器 + _startCallTimer(); + print('📞 [CallController] 已启动通话计时器'); + // remoteUid 会由 RTCManager 的 onUserJoined 事件自动设置 + // 如果此时还没有设置,尝试从 RTCManager 的 remoteUsersNotifier 中获取 + if (remoteUid.value == null) { + final rtcManager = RTCManager.instance; + final remoteUsers = rtcManager.remoteUsersNotifier.value; + if (remoteUsers.isNotEmpty) { + remoteUid.value = remoteUsers.first; + print( + '📞 [CallController] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: ${remoteUsers.first}', + ); + } else if (uid != null) { + // 如果 RTCManager 还没有远端用户,使用消息中的 uid 作为备用 + remoteUid.value = uid; + print('📞 [CallController] 使用消息中的 uid 设置 remoteUid: $uid'); + } + } + } + } else if (callStatus == 'rejected') { + // 通话被拒绝 + print('📞 [CallController] 通话被拒绝,callStatus=$callStatus'); - // 取消订阅 RTM 频道 - if (_callChannelId != null && _callChannelId!.isNotEmpty) { - await RTMManager.instance.unsubscribe(_callChannelId!); - print('✅ [CallController] 已取消订阅 RTM 频道: $_callChannelId'); - } + // 如果是发起方,执行退出逻辑 + if (callSession.isInitiator) { + // 取消超时计时器(对方已拒绝) + _stopCallTimeoutTimer(); + // 关闭通话小窗口 + if (Get.isRegistered()) { + final overlayController = Get.find(); + overlayController.hideVideoCall(); + print('✅ [CallController] 已关闭通话小窗口'); + } + // 退出 VideoCallPage(如果当前在 VideoCallPage) + if (Get.currentRoute.contains('VideoCallPage')) { + Get.back(); + print('✅ [CallController] 已退出 VideoCallPage'); + } + // 结束通话 + await endCall(callDuration: callDurationSeconds.value); + } + } else if (callStatus == 'cancelled' || callStatus == 'terminated') { + // 通话被取消或终止 + print( + '📞 [CallController] 通话被取消/终止,callStatus=$callStatus, isInitiator=${callSession.isInitiator}', + ); - // 结束通话 - await endCall(callDuration: callDurationSeconds.value); + // 关闭视频通话邀请弹框(如果正在显示) + SmartDialog.dismiss(); + print('✅ [CallController] 已关闭视频通话邀请弹框'); - // 关闭通话小窗口 - if (Get.isRegistered()) { - final overlayController = Get.find(); - overlayController.hideVideoCall(); - print('✅ [CallController] 已关闭通话小窗口'); - } + // 取消超时计时器 + _stopCallTimeoutTimer(); - // 退出 VideoCallPage(如果当前在 VideoCallPage) - if (Get.currentRoute.contains('VideoCallPage')) { - Get.back(); - print('✅ [CallController] 已退出 VideoCallPage'); + // 停止播放来电铃声 + stopCallAudio(); + + // 停止通话计时器 + _stopCallTimer(); + + // 离开RTC频道 + await RTCManager.instance.leaveChannel(); + + // 结束通话 + await endCall(callDuration: callDuration ?? callDurationSeconds.value); + + // 关闭通话小窗口 + if (Get.isRegistered()) { + final overlayController = Get.find(); + overlayController.hideVideoCall(); + print('✅ [CallController] 已关闭通话小窗口'); + } + + // 退出 VideoCallPage(如果当前在 VideoCallPage) + if (Get.currentRoute.contains('VideoCallPage')) { + Get.back(); + print('✅ [CallController] 已退出 VideoCallPage'); + } + } + } catch (e) { + print('❌ [CallController] 处理callStatus变化失败: $e'); } } diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index ad1a393..352178e 100644 --- a/lib/im/im_manager.dart +++ b/lib/im/im_manager.dart @@ -59,23 +59,24 @@ class IMManager { // 存储活跃的 ChatController 实例,key 为 userId final Map _activeChatControllers = {}; - + /// 获取当前正在聊天的用户ID列表 Set getActiveChatUserIds() { return _activeChatControllers.keys.toSet(); } - + // 存储 Presence 状态变化回调,key 为 userId final Map _presenceCallbacks = {}; // 消息通知弹框队列 final List<_NotificationMessage> _notificationQueue = []; - + // 当前是否有弹框正在显示 bool _isShowingNotification = false; // 本地通知服务 - final LocalNotificationService _localNotificationService = LocalNotificationService.instance; + final LocalNotificationService _localNotificationService = + LocalNotificationService.instance; IMManager._internal() { print('IMManager instance created'); @@ -125,7 +126,6 @@ class IMManager { acceptInvitationAlways: false, ); debugPrint('✅ EMOptions 创建成功'); - // 调用 SDK 初始化 debugPrint('🟡 调用 EMClient.getInstance.init()...'); @@ -159,11 +159,11 @@ class IMManager { debugPrint('错误信息: $e'); debugPrint('堆栈跟踪:'); debugPrint('$s'); - + _initialized = false; _initCompleter!.completeError(e, s); _initCompleter = null; // 允许重试 - + // 重新抛出异常,让调用者知道初始化失败 rethrow; } @@ -223,10 +223,12 @@ class IMManager { debugPrint('📩 收到消息数: ${messages.length}'); // 从消息扩展字段中解析用户信息并缓存 for (var message in messages) { - if (message.direction == MessageDirection.RECEIVE && message.onlineState) { + if (message.direction == MessageDirection.RECEIVE && + message.onlineState) { // 检查发送者是否是当前正在聊天的用户 final fromId = message.from; - if (fromId != null && _activeChatControllers.containsKey(fromId)) { + if (fromId != null && + _activeChatControllers.containsKey(fromId)) { // 如果是当前正在聊天的联系人,将该会话的所有消息标记为已读 Future.microtask(() async { await markAllMessagesAsRead(fromId); @@ -253,15 +255,19 @@ class IMManager { // 通知对应的 ChatController 更新消息列表 _notifyChatControllers(messages); }, - onMessageContentChanged: (EMMessage message, String operatorId, int operationTime){ - Get.log('-------------📨 [IMManager] -------------: ${operatorId}'); - Get.log('-------------📨 [IMManager] -------------: ${message}'); - Get.log('-------------📨 [IMManager] -------------: ${operationTime}'); - Get.log('-------------📨 [IMManager] -------------: ${message.localTime}'); + onMessageContentChanged: (EMMessage message, String operatorId, int operationTime) { + Get.log('-------------📨 [IMManager] -------------: ${operatorId}'); + Get.log('-------------📨 [IMManager] -------------: ${message}'); + Get.log( + '-------------📨 [IMManager] -------------: ${operationTime}', + ); + Get.log( + '-------------📨 [IMManager] -------------: ${message.localTime}', + ); if (Get.isLogEnable) { Get.log('📨 [IMManager] 消息内容已修改: ${message.localTime}'); } - + // 检查是否是通话消息 bool isCallMessage = false; try { @@ -272,16 +278,34 @@ class IMManager { } catch (e) { // 解析失败,不是通话消息 } - - // 如果是通话消息,更新 CallItem 显示 + + // 如果是通话消息,更新 CallItem 显示并处理callStatus变化 if (isCallMessage) { try { if (Get.isLogEnable) { Get.log('📞 [IMManager] 检测到通话消息内容修改: msgId=${message.msgId}'); } - + String? userId; - + String? oldCallStatus; + String? newCallStatus; + String? channelId; + int? uid; + int? callDuration; + + // 解析新消息的callStatus + final customBody = message.body as EMCustomMessageBody; + newCallStatus = customBody.params?['callStatus']; + channelId = customBody.params?['channelId']; + if (customBody.params?['uid'] != null) { + uid = int.tryParse(customBody.params!['uid']!); + } + if (customBody.params?['callDuration'] != null) { + callDuration = int.tryParse( + customBody.params!['callDuration']!, + ); + } + // 根据消息方向确定对应的 userId if (message.direction == MessageDirection.RECEIVE) { // 接收到的消息,使用发送者ID @@ -290,83 +314,174 @@ class IMManager { // 发送的消息,使用接收者ID userId = message.to; } - + if (Get.isLogEnable) { - Get.log('📞 [IMManager] 通话消息方向: ${message.direction}, userId: $userId'); + Get.log( + '📞 [IMManager] 通话消息方向: ${message.direction}, userId: $userId, callStatus: $oldCallStatus -> $newCallStatus', + ); } - + // 找到对应的 ChatController 并更新消息 if (userId != null && userId.isNotEmpty) { final controller = _activeChatControllers[userId]; if (controller != null) { // 更新消息列表中的消息 - var index = controller.messages.indexWhere((msg) => msg.msgId == message.msgId); + var index = controller.messages.indexWhere( + (msg) => msg.msgId == message.msgId, + ); if (index != -1) { + // 保存旧状态 + final oldMessage = controller.messages[index]; + final oldCustomBody = + oldMessage.body as EMCustomMessageBody; + oldCallStatus = oldCustomBody.params?['callStatus']; + if (Get.isLogEnable) { - final oldMessage = controller.messages[index]; - final customBody = oldMessage.body as EMCustomMessageBody; - Get.log('📞 [IMManager] 找到通话消息,index=$index, 旧状态: ${customBody.params?['callStatus']}, 新状态: ${(message.body as EMCustomMessageBody).params?['callStatus']}'); + Get.log( + '📞 [IMManager] 找到通话消息,index=$index, 旧状态: $oldCallStatus, 新状态: $newCallStatus', + ); } - + // 更新消息对象 controller.messages[index] = message; // 强制刷新 RxList,确保 UI 更新 controller.messages.refresh(); // 调用 update() 触发 GetBuilder 重建 controller.update(); - + if (Get.isLogEnable) { - Get.log('✅ [IMManager] 已更新通话消息: msgId=${message.msgId}, userId=$userId, index=$index'); + Get.log( + '✅ [IMManager] 已更新通话消息: msgId=${message.msgId}, userId=$userId, index=$index', + ); } else { - print('✅ [IMManager] 已更新通话消息: msgId=${message.msgId}, userId=$userId, index=$index'); + print( + '✅ [IMManager] 已更新通话消息: msgId=${message.msgId}, userId=$userId, index=$index', + ); + } + + // 如果callStatus发生变化,通知CallController处理 + if (newCallStatus != null && + newCallStatus.isNotEmpty && + newCallStatus != oldCallStatus) { + Future.microtask(() async { + try { + final callController = CallController.instance; + await callController.handleCallStatusChange( + message: message, + callStatus: newCallStatus!, + channelId: channelId, + uid: uid, + callDuration: callDuration, + ); + if (Get.isLogEnable) { + Get.log( + '✅ [IMManager] 已通知CallController处理callStatus变化: $newCallStatus', + ); + } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 通知CallController失败: $e'); + } + } + }); } } else { // 如果找不到消息,尝试通过通话消息的特征来匹配(通过channelId和时间戳) if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 未找到通话消息(通过msgId),尝试其他匹配方式: msgId=${message.msgId}, userId=$userId'); + Get.log( + '⚠️ [IMManager] 未找到通话消息(通过msgId),尝试其他匹配方式: msgId=${message.msgId}, userId=$userId', + ); } - + // 尝试通过通话消息的特征来匹配(通过channelId) final customBody = message.body as EMCustomMessageBody; final channelId = customBody.params?['channelId']; - + if (channelId != null && channelId.isNotEmpty) { // 尝试通过channelId和时间戳匹配 - final matchedIndex = controller.messages.indexWhere((msg) { + final matchedIndex = controller.messages.indexWhere(( + msg, + ) { if (msg.body.type == MessageType.CUSTOM) { final msgBody = msg.body as EMCustomMessageBody; - if (msgBody.event == 'call' && msgBody.params != null) { + if (msgBody.event == 'call' && + msgBody.params != null) { final msgChannelId = msgBody.params?['channelId']; // 匹配相同的channelId和相似的时间戳(允许一定误差) if (msgChannelId == channelId) { // 检查时间戳是否接近(允许5秒误差) - final timeDiff = (message.serverTime - msg.serverTime).abs(); + final timeDiff = + (message.serverTime - msg.serverTime).abs(); return timeDiff < 5000; } } } return false; }); - + if (matchedIndex != -1) { + // 保存旧状态 + final oldMessage = controller.messages[matchedIndex]; + final oldCustomBody = + oldMessage.body as EMCustomMessageBody; + final oldCallStatusForMatch = + oldCustomBody.params?['callStatus']; + if (Get.isLogEnable) { - Get.log('📞 [IMManager] 通过channelId匹配到通话消息,index=$matchedIndex'); + Get.log( + '📞 [IMManager] 通过channelId匹配到通话消息,index=$matchedIndex, 旧状态: $oldCallStatusForMatch, 新状态: $newCallStatus', + ); } - + // 更新消息对象 controller.messages[matchedIndex] = message; controller.messages.refresh(); controller.update(); - + if (Get.isLogEnable) { - Get.log('✅ [IMManager] 已通过channelId更新通话消息: msgId=${message.msgId}, userId=$userId, index=$matchedIndex'); + Get.log( + '✅ [IMManager] 已通过channelId更新通话消息: msgId=${message.msgId}, userId=$userId, index=$matchedIndex', + ); } else { - print('✅ [IMManager] 已通过channelId更新通话消息: msgId=${message.msgId}, userId=$userId, index=$matchedIndex'); + print( + '✅ [IMManager] 已通过channelId更新通话消息: msgId=${message.msgId}, userId=$userId, index=$matchedIndex', + ); + } + + // 如果callStatus发生变化,通知CallController处理 + if (newCallStatus != null && + newCallStatus.isNotEmpty && + newCallStatus != oldCallStatusForMatch) { + Future.microtask(() async { + try { + final callController = CallController.instance; + await callController.handleCallStatusChange( + message: message, + callStatus: newCallStatus!, + channelId: channelId, + uid: uid, + callDuration: callDuration, + ); + if (Get.isLogEnable) { + Get.log( + '✅ [IMManager] 已通知CallController处理callStatus变化: $newCallStatus', + ); + } + } catch (e) { + if (Get.isLogEnable) { + Get.log( + '⚠️ [IMManager] 通知CallController失败: $e', + ); + } + } + }); } } else { // 如果还是找不到,直接添加到列表(可能是新消息) if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 无法匹配通话消息,添加到列表: msgId=${message.msgId}'); + Get.log( + '⚠️ [IMManager] 无法匹配通话消息,添加到列表: msgId=${message.msgId}', + ); } controller.messages.add(message); controller.messages.refresh(); @@ -374,15 +489,68 @@ class IMManager { } } else { if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 通话消息没有channelId,无法匹配: msgId=${message.msgId}'); + Get.log( + '⚠️ [IMManager] 通话消息没有channelId,无法匹配: msgId=${message.msgId}', + ); } } } } else { - if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 未找到 ChatController: userId=$userId'); - } else { - print('⚠️ [IMManager] 未找到 ChatController: userId=$userId'); + // 即使找不到 ChatController,也尝试通知 CallController 处理 callStatus 变化 + // 因为 CallController 可以通过 channelId 来匹配当前的通话会话 + Get.log( + '⚠️ [IMManager] 通话消息,尝试通知CallController处理callStatus变化: $newCallStatus', + ); + if (newCallStatus != null && newCallStatus.isNotEmpty) { + Get.log( + '⚠️ [IMManager] 通话消息没有找到对应的ChatController,尝试通知CallController处理callStatus变化: $newCallStatus', + ); + final customBody = message.body as EMCustomMessageBody; + final channelIdForCallController = + customBody.params?['channelId']; + int? uidForCallController; + if (customBody.params?['uid'] != null) { + uidForCallController = int.tryParse( + customBody.params!['uid']!, + ); + } + int? callDurationForCallController; + if (customBody.params?['callDuration'] != null) { + callDurationForCallController = int.tryParse( + customBody.params!['callDuration']!, + ); + } + Future.microtask(() async { + try { + final callController = CallController.instance; + await callController.handleCallStatusChange( + message: message, + callStatus: newCallStatus!, + channelId: channelIdForCallController, + uid: uidForCallController, + callDuration: callDurationForCallController, + ); + if (Get.isLogEnable) { + Get.log( + '✅ [IMManager] 已通知CallController处理callStatus变化(无ChatController): $newCallStatus', + ); + } else { + print( + '✅ [IMManager] 已通知CallController处理callStatus变化(无ChatController): $newCallStatus', + ); + } + } catch (e) { + if (Get.isLogEnable) { + Get.log( + '⚠️ [IMManager] 通知CallController失败(无ChatController): $e', + ); + } else { + print( + '⚠️ [IMManager] 通知CallController失败(无ChatController): $e', + ); + } + } + }); } } } else { @@ -400,37 +568,41 @@ class IMManager { } } } - + // 处理金币数值:只对接收到的消息处理金币标签 // 从消息 attributes 中的 revenueInfo 获取金币信息并直接显示 // 只处理接收到的消息(自己发送的消息不显示金币标签) if (message.direction == MessageDirection.RECEIVE) { try { String? revenueInfo; - + // 从消息 attributes 中获取 revenueInfo if (message.attributes != null) { revenueInfo = message.attributes!['revenueInfo'] as String?; } - + // 如果获取到 revenueInfo,确保存储到消息的 attributes 中(用于UI显示) if (revenueInfo != null && revenueInfo.isNotEmpty) { message.attributes ??= {}; // 将 revenueInfo 存储到 coin_value 中,以便UI组件可以直接使用 message.attributes!['coin_value'] = revenueInfo; - + // 通知对应的 ChatController 更新消息 final fromId = message.from; if (fromId != null && fromId.isNotEmpty) { final controller = _activeChatControllers[fromId]; if (controller != null) { // 更新消息列表中的消息 - final index = controller.messages.indexWhere((msg) => msg.msgId == message.msgId); + final index = controller.messages.indexWhere( + (msg) => msg.msgId == message.msgId, + ); if (index != -1) { controller.messages[index] = message; controller.update(); if (Get.isLogEnable) { - Get.log('✅ [IMManager] 已更新接收消息的金币信息: msgId=${message.msgId}, revenueInfo=$revenueInfo'); + Get.log( + '✅ [IMManager] 已更新接收消息的金币信息: msgId=${message.msgId}, revenueInfo=$revenueInfo', + ); } } } @@ -442,138 +614,166 @@ class IMManager { } } } - } + }, ), ); - EMClient.getInstance.chatManager.addMessageEvent(_chatHandlerKey, ChatMessageEvent( - onSuccess: (str, message){ - Get.log('✅ [IMManager] 发送消息成功: $str'); - }, - onError: (str, message, err){ - //code: 508为拦截 - Get.log('❌ [IMManager] 发送消息失败: $err----$str'); - - // 检查错误码是否为508(玫瑰不足) - try { - final errorCode = err.description; - - // 通知 ChatController 更新消息状态 - final targetId = message.to; - if (targetId != null) { - final controller = _activeChatControllers[targetId]; - if (controller != null) { - // 更新消息状态 - final index = controller.messages.indexWhere((msg) => msg.msgId == message.msgId); - if (index != -1) { - // 更新消息对象 - controller.messages[index] = message; - - // 将错误码保存到消息 attributes 中(用于页面重新加载时恢复状态) - message.attributes ??= {}; - message.attributes!['errorCode'] = errorCode; - - // 如果是508错误,添加到临时错误提示集合中 - if (errorCode == 'E0002') { - controller.addRoseErrorMessageId(message.msgId); - if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 检测到错误码E0002(玫瑰不足),已添加到临时错误提示集合: msgId=${message.msgId}'); + EMClient.getInstance.chatManager.addMessageEvent( + _chatHandlerKey, + ChatMessageEvent( + onSuccess: (str, message) { + Get.log('✅ [IMManager] 发送消息成功: $str'); + }, + onError: (str, message, err) { + //code: 508为拦截 + Get.log('❌ [IMManager] 发送消息失败: $err----$str'); + + // 检查错误码是否为508(玫瑰不足) + try { + final errorCode = err.description; + + // 通知 ChatController 更新消息状态 + final targetId = message.to; + if (targetId != null) { + final controller = _activeChatControllers[targetId]; + if (controller != null) { + // 更新消息状态 + final index = controller.messages.indexWhere( + (msg) => msg.msgId == message.msgId, + ); + if (index != -1) { + // 更新消息对象 + controller.messages[index] = message; + + // 将错误码保存到消息 attributes 中(用于页面重新加载时恢复状态) + message.attributes ??= {}; + message.attributes!['errorCode'] = errorCode; + + // 如果是508错误,添加到临时错误提示集合中 + if (errorCode == 'E0002') { + controller.addRoseErrorMessageId(message.msgId); + if (Get.isLogEnable) { + Get.log( + '⚠️ [IMManager] 检测到错误码E0002(玫瑰不足),已添加到临时错误提示集合: msgId=${message.msgId}', + ); + } } - } - - // 如果是E0001错误(敏感词拦截),添加到临时错误提示集合中 - if (errorCode == 'E0001') { - controller.addSensitiveWordMessageId(message.msgId); - if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 检测到错误码E0001(敏感词拦截),已添加到临时错误提示集合: msgId=${message.msgId}'); + + // 如果是E0001错误(敏感词拦截),添加到临时错误提示集合中 + if (errorCode == 'E0001') { + controller.addSensitiveWordMessageId(message.msgId); + if (Get.isLogEnable) { + Get.log( + '⚠️ [IMManager] 检测到错误码E0001(敏感词拦截),已添加到临时错误提示集合: msgId=${message.msgId}', + ); + } } - } - - controller.update(); - - // 刷新会话列表,确保聊天列表能显示失败状态 - try { - if (Get.isRegistered()) { - final conversationController = Get.find(); - conversationController.refreshConversations(); + + controller.update(); + + // 刷新会话列表,确保聊天列表能显示失败状态 + try { + if (Get.isRegistered()) { + final conversationController = + Get.find(); + conversationController.refreshConversations(); + } + } catch (e) { + // ConversationController 可能未注册,忽略错误 } - } catch (e) { - // ConversationController 可能未注册,忽略错误 - } - - if (Get.isLogEnable) { - Get.log('✅ [IMManager] 已通知ChatController更新消息状态: $targetId, status=FAIL'); - } - } else { - // 如果找不到消息,尝试通过内容匹配(处理消息ID可能改变的情况) - if (message.body.type == MessageType.TXT) { - final textBody = message.body as EMTextMessageBody; - final content = textBody.content; - final contentIndex = controller.messages.indexWhere((msg) => - msg.body.type == MessageType.TXT && - (msg.body as EMTextMessageBody).content == content && - msg.direction == MessageDirection.SEND && - msg.status == MessageStatus.PROGRESS - ); - if (contentIndex != -1) { - // 更新消息对象 - final matchedMessage = controller.messages[contentIndex]; - controller.messages[contentIndex] = message; - - // 将错误码保存到消息 attributes 中(用于页面重新加载时恢复状态) - message.attributes ??= {}; - message.attributes!['errorCode'] = errorCode.toString(); - - // 如果是508错误,添加到临时错误提示集合中(使用匹配到的消息ID) - if (errorCode == 508 || errorCode == 'E0002') { - controller.addRoseErrorMessageId(matchedMessage.msgId); - if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 检测到错误码508/E0002(玫瑰不足),已通过内容匹配添加到临时错误提示集合: msgId=${matchedMessage.msgId}'); + + if (Get.isLogEnable) { + Get.log( + '✅ [IMManager] 已通知ChatController更新消息状态: $targetId, status=FAIL', + ); + } + } else { + // 如果找不到消息,尝试通过内容匹配(处理消息ID可能改变的情况) + if (message.body.type == MessageType.TXT) { + final textBody = message.body as EMTextMessageBody; + final content = textBody.content; + final contentIndex = controller.messages.indexWhere( + (msg) => + msg.body.type == MessageType.TXT && + (msg.body as EMTextMessageBody).content == + content && + msg.direction == MessageDirection.SEND && + msg.status == MessageStatus.PROGRESS, + ); + if (contentIndex != -1) { + // 更新消息对象 + final matchedMessage = + controller.messages[contentIndex]; + controller.messages[contentIndex] = message; + + // 将错误码保存到消息 attributes 中(用于页面重新加载时恢复状态) + message.attributes ??= {}; + message.attributes!['errorCode'] = errorCode.toString(); + + // 如果是508错误,添加到临时错误提示集合中(使用匹配到的消息ID) + if (errorCode == 508 || errorCode == 'E0002') { + controller.addRoseErrorMessageId( + matchedMessage.msgId, + ); + if (Get.isLogEnable) { + Get.log( + '⚠️ [IMManager] 检测到错误码508/E0002(玫瑰不足),已通过内容匹配添加到临时错误提示集合: msgId=${matchedMessage.msgId}', + ); + } } - } - - // 如果是E0001错误(敏感词拦截),添加到临时错误提示集合中 - if (errorCode == 'E0001') { - controller.addSensitiveWordMessageId(matchedMessage.msgId); - if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 检测到错误码E0001(敏感词拦截),已通过内容匹配添加到临时错误提示集合: msgId=${matchedMessage.msgId}'); + + // 如果是E0001错误(敏感词拦截),添加到临时错误提示集合中 + if (errorCode == 'E0001') { + controller.addSensitiveWordMessageId( + matchedMessage.msgId, + ); + if (Get.isLogEnable) { + Get.log( + '⚠️ [IMManager] 检测到错误码E0001(敏感词拦截),已通过内容匹配添加到临时错误提示集合: msgId=${matchedMessage.msgId}', + ); + } } - } - - controller.update(); - - // 刷新会话列表,确保聊天列表能显示失败状态 - try { - if (Get.isRegistered()) { - final conversationController = Get.find(); - conversationController.refreshConversations(); + + controller.update(); + + // 刷新会话列表,确保聊天列表能显示失败状态 + try { + if (Get.isRegistered()) { + final conversationController = + Get.find(); + conversationController.refreshConversations(); + } + } catch (e) { + // ConversationController 可能未注册,忽略错误 + } + + if (Get.isLogEnable) { + Get.log( + '✅ [IMManager] 已通过内容匹配更新消息状态: $targetId, status=FAIL', + ); } - } catch (e) { - // ConversationController 可能未注册,忽略错误 - } - - if (Get.isLogEnable) { - Get.log('✅ [IMManager] 已通过内容匹配更新消息状态: $targetId, status=FAIL'); } } } } } + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [IMManager] 处理错误码失败: $e'); + } } - } catch (e) { - if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 处理错误码失败: $e'); - } - } - }, - )); - + }, + ), + ); + // Presence 状态监听器 EMClient.getInstance.presenceManager.addEventHandler( _presenceHandlerKey, EMPresenceEventHandler( onPresenceStatusChanged: (List presences) { if (Get.isLogEnable) { - Get.log('📡 [IMManager] 收到 Presence 状态变化: ${presences.length} 个用户'); + Get.log( + '📡 [IMManager] 收到 Presence 状态变化: ${presences.length} 个用户', + ); } // 处理状态变化 for (var presence in presences) { @@ -583,36 +783,43 @@ class IMManager { final statusDescStr = presence.statusDescription ?? ''; final statusDesc = statusDescStr.toLowerCase(); // 判断在线状态:online、available 等表示在线 - final isOnline = statusDesc == 'online' || statusDesc == 'available'; - + final isOnline = + statusDesc == 'online' || statusDesc == 'available'; + if (Get.isLogEnable) { - Get.log('📡 [IMManager] Presence状态变化: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline'); + Get.log( + '📡 [IMManager] Presence状态变化: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', + ); } else { - print('📡 [IMManager] Presence状态变化: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline'); + print( + '📡 [IMManager] Presence状态变化: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', + ); } - + // 通知对应的回调 final callback = _presenceCallbacks[userId]; if (callback != null) { callback(isOnline); } - + // 通知对应的 ChatController final controller = _activeChatControllers[userId]; if (controller != null) { controller.isUserOnline.value = isOnline; controller.update(); } - + if (Get.isLogEnable) { - Get.log('✅ [IMManager] 用户在线状态更新: userId=$userId, isOnline=$isOnline'); + Get.log( + '✅ [IMManager] 用户在线状态更新: userId=$userId, isOnline=$isOnline', + ); } } } }, ), ); - + _listenersRegistered = true; if (Get.isLogEnable) { Get.log('✅ [IMManager] 监听器注册成功'); @@ -696,7 +903,7 @@ class IMManager { } _isReconnecting = true; - + if (Get.isLogEnable) { Get.log('🔄 [IMManager] 开始自动重连...'); } else { @@ -730,9 +937,13 @@ class IMManager { // 检查响应:code == 0 表示成功 if (Get.isLogEnable) { - Get.log('📡 [IMManager] 获取token响应: code=${response.data.code}, message=${response.data.message}'); + Get.log( + '📡 [IMManager] 获取token响应: code=${response.data.code}, message=${response.data.message}', + ); } else { - print('📡 [IMManager] 获取token响应: code=${response.data.code}, message=${response.data.message}'); + print( + '📡 [IMManager] 获取token响应: code=${response.data.code}, message=${response.data.message}', + ); } if (response.data.isSuccess && response.data.data != null) { @@ -742,7 +953,7 @@ class IMManager { } else { print('✅ [IMManager] 获取到新的token (长度: ${token.length}),开始重新登录'); } - + // 重新登录 final loginSuccess = await login(token); if (loginSuccess) { @@ -760,9 +971,13 @@ class IMManager { } } else { if (Get.isLogEnable) { - Get.log('❌ [IMManager] 获取token失败:code=${response.data.code}, message=${response.data.message}, data=${response.data.data}'); + Get.log( + '❌ [IMManager] 获取token失败:code=${response.data.code}, message=${response.data.message}, data=${response.data.data}', + ); } else { - print('❌ [IMManager] 获取token失败:code=${response.data.code}, message=${response.data.message}, data=${response.data.data}'); + print( + '❌ [IMManager] 获取token失败:code=${response.data.code}, message=${response.data.message}, data=${response.data.data}', + ); } } } catch (e, stackTrace) { @@ -783,28 +998,28 @@ class IMManager { try { // 显示提示 SmartDialog.showToast('您的账号在其他设备登录,已被强制下线'); - + // 先退出 IM 登录 await logout(); - + // 清除会话列表和用户信息缓存 if (Get.isRegistered()) { final conversationController = Get.find(); conversationController.clearConversations(); } - + // 清除本地存储 GetStorage().erase(); - + // 清除全局数据 GlobalData().logout(); - + // 延迟一小段时间再跳转,确保用户看到提示 await Future.delayed(Duration(milliseconds: 500)); - + // 跳转到登录页 Get.offAll(() => LoginPage()); - + if (Get.isLogEnable) { Get.log('✅ [IMManager] 用户被踢下线处理完成,已跳转到登录页'); } @@ -859,15 +1074,19 @@ class IMManager { } else { print('📤 [IMManager] 准备发送消息: to=$toChatUsername, content=$content'); } - + // 检查当前登录的用户ID final currentUserId = storage.read('userId'); if (Get.isLogEnable) { - Get.log('📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername'); + Get.log( + '📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername', + ); } else { - print('📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername'); + print( + '📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername', + ); } - + // 检查目标用户是否存在于IM系统中 final userExists = await checkUserExists(toChatUsername); if (!userExists) { @@ -879,7 +1098,7 @@ class IMManager { // 即使用户不存在,也尝试发送,因为某些IM系统可能允许发送给未注册用户 // 但会记录警告信息 } - + // 创建文本消息 final message = EMMessage.createTxtSendMessage( targetId: toChatUsername, @@ -890,14 +1109,16 @@ class IMManager { _addUserInfoToMessageExt(message); // 发送消息(如果未登录会抛出异常) - final sentMessage = await EMClient.getInstance.chatManager.sendMessage(message); - + final sentMessage = await EMClient.getInstance.chatManager.sendMessage( + message, + ); + if (Get.isLogEnable) { Get.log('✅ [IMManager] 文本消息发送成功: to=$toChatUsername, content=$content'); } else { print('✅ [IMManager] 文本消息发送成功: to=$toChatUsername, content=$content'); } - + return sentMessage; } catch (e) { // 捕获异常,可能是未登录或其他错误 @@ -983,22 +1204,22 @@ class IMManager { String? thumbnailPath; try { print('📸 [IMManager] 开始生成视频缩略图...'); - + // 获取临时目录 final tempDir = await getTemporaryDirectory(); final fileName = videoPath.split('/').last.split('.').first; final thumbFileName = '${fileName}_thumb.jpg'; thumbnailPath = '${tempDir.path}/$thumbFileName'; - + // 使用 video_thumbnail 生成缩略图 final uint8list = await VideoThumbnail.thumbnailFile( video: videoPath, thumbnailPath: thumbnailPath, imageFormat: ImageFormat.JPEG, maxWidth: 400, // 缩略图最大宽度 - quality: 75, // 图片质量 + quality: 75, // 图片质量 ); - + if (uint8list != null && File(uint8list).existsSync()) { thumbnailPath = uint8list; print('✅ [IMManager] 缩略图生成成功: $thumbnailPath'); @@ -1024,13 +1245,13 @@ class IMManager { print('消息创建成功,消息类型: ${message.body.type}'); print('消息体是否为视频: ${message.body is EMVideoMessageBody}'); - + // 检查缩略图信息 if (message.body is EMVideoMessageBody) { final videoBody = message.body as EMVideoMessageBody; print('📸 [IMManager] 缩略图本地路径: ${videoBody.thumbnailLocalPath}'); print('📸 [IMManager] 缩略图远程路径: ${videoBody.thumbnailRemotePath}'); - + // 验证缩略图文件是否存在 if (videoBody.thumbnailLocalPath != null) { final thumbFile = File(videoBody.thumbnailLocalPath!); @@ -1049,7 +1270,11 @@ class IMManager { } /// 发送自定义消息 - Future sendCustomMessage(String targetId, String event, Map? data) async { + Future sendCustomMessage( + String targetId, + String event, + Map? data, + ) async { final customMsg = EMMessage.createCustomSendMessage( targetId: targetId, // `event` 为需要传递的自定义消息事件,比如礼物消息,可以设置: @@ -1106,7 +1331,10 @@ class IMManager { /// 订阅用户在线状态(用于实时接收状态变化) /// [userId] 用户ID /// [callback] 状态变化回调函数 - Future subscribeUserPresence(String userId, Function(bool isOnline) callback) async { + Future subscribeUserPresence( + String userId, + Function(bool isOnline) callback, + ) async { try { if (!_loggedIn) { if (Get.isLogEnable) { @@ -1119,24 +1347,28 @@ class IMManager { _presenceCallbacks[userId] = callback; final presenceManager = EMClient.getInstance.presenceManager; - + // 订阅用户状态(有效期7天) await presenceManager.subscribe( members: [userId], expiry: 7 * 24 * 60 * 60, // 7天 ); - + if (Get.isLogEnable) { Get.log('✅ [IMManager] 已订阅用户在线状态: userId=$userId'); } - + // 立即获取一次状态 final onlineStatus = await getUserPresenceStatus(userId); if (onlineStatus != null) { if (Get.isLogEnable) { - Get.log('✅ [IMManager] 订阅后立即获取状态: userId=$userId, isOnline=$onlineStatus'); + Get.log( + '✅ [IMManager] 订阅后立即获取状态: userId=$userId, isOnline=$onlineStatus', + ); } else { - print('✅ [IMManager] 订阅后立即获取状态: userId=$userId, isOnline=$onlineStatus'); + print( + '✅ [IMManager] 订阅后立即获取状态: userId=$userId, isOnline=$onlineStatus', + ); } callback(onlineStatus); } else { @@ -1146,7 +1378,7 @@ class IMManager { print('⚠️ [IMManager] 订阅后获取状态失败: userId=$userId'); } } - + return true; } catch (e) { if (Get.isLogEnable) { @@ -1171,11 +1403,11 @@ class IMManager { final presenceManager = EMClient.getInstance.presenceManager; await presenceManager.unsubscribe(members: [userId]); - + if (Get.isLogEnable) { Get.log('✅ [IMManager] 已取消订阅用户在线状态: userId=$userId'); } - + return true; } catch (e) { if (Get.isLogEnable) { @@ -1198,12 +1430,12 @@ class IMManager { } final presenceManager = EMClient.getInstance.presenceManager; - + // 获取用户在线状态 final presences = await presenceManager.fetchPresenceStatus( members: [userId], ); - + if (presences.isEmpty) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 未获取到用户在线状态: $userId'); @@ -1217,13 +1449,17 @@ class IMManager { final statusDesc = statusDescStr.toLowerCase(); // 判断在线状态:online、available 等表示在线 final isOnline = statusDesc == 'online' || statusDesc == 'available'; - + if (Get.isLogEnable) { - Get.log('✅ [IMManager] 获取用户在线状态: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline'); + Get.log( + '✅ [IMManager] 获取用户在线状态: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', + ); } else { - print('✅ [IMManager] 获取用户在线状态: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline'); + print( + '✅ [IMManager] 获取用户在线状态: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', + ); } - + return isOnline; } catch (e) { if (Get.isLogEnable) { @@ -1246,11 +1482,12 @@ class IMManager { EMConversationType convType = EMConversationType.Chat; // 获取会话对象 - final conversation = await EMClient.getInstance.chatManager.getConversation( - conversationId, - type: convType, - createIfNeed: createIfNeed, - ); + final conversation = await EMClient.getInstance.chatManager + .getConversation( + conversationId, + type: convType, + createIfNeed: createIfNeed, + ); if (conversation == null) { if (Get.isLogEnable) { @@ -1269,9 +1506,7 @@ class IMManager { return messages.map((msg) => msg as EMMessage?).toList(); } else { // 加载最新消息 - final messages = await conversation.loadMessages( - loadCount: pageSize, - ); + final messages = await conversation.loadMessages(loadCount: pageSize); return messages.map((msg) => msg as EMMessage?).toList(); } } catch (e) { @@ -1283,16 +1518,16 @@ class IMManager { } /// 从 SDK 重新获取消息的最新状态(用于检查消息发送状态) - Future getMessageById(String conversationId, String messageId) async { + Future getMessageById( + String conversationId, + String messageId, + ) async { try { EMConversationType convType = EMConversationType.Chat; // 获取会话对象 - final conversation = await EMClient.getInstance.chatManager.getConversation( - conversationId, - type: convType, - createIfNeed: false, - ); + final conversation = await EMClient.getInstance.chatManager + .getConversation(conversationId, type: convType, createIfNeed: false); if (conversation == null) { if (Get.isLogEnable) { @@ -1303,14 +1538,14 @@ class IMManager { // 加载最新消息(包含刚发送的消息) final messages = await conversation.loadMessages(loadCount: 50); - + // 查找指定 messageId 的消息 for (var msg in messages) { if (msg.msgId == messageId) { return msg; } } - + return null; } catch (e) { if (Get.isLogEnable) { @@ -1347,7 +1582,7 @@ class IMManager { // ========== 获取发送者信息 ========== String? senderNickName; String? senderAvatarUrl; - + // 尝试从 ChatController 获取发送者信息 final senderChatController = _activeChatControllers[currentUserId]; if (senderChatController != null) { @@ -1374,11 +1609,12 @@ class IMManager { // ========== 获取接收者信息 ========== String? receiverNickName; String? receiverAvatarUrl; - + // 尝试从 ChatController 获取接收者信息(通过接收者ID查找) if (receiverId != null && receiverId.isNotEmpty) { final receiverChatController = _activeChatControllers[receiverId]; - if (receiverChatController != null && receiverChatController.userData != null) { + if (receiverChatController != null && + receiverChatController.userData != null) { // 从接收者的 ChatController 获取接收者的信息 // 注意:这里获取的是接收者自己的信息(用于发送方在聊天列表显示) receiverNickName = receiverChatController.userData!.nickName; @@ -1388,12 +1624,16 @@ class IMManager { try { if (Get.isRegistered()) { final conversationController = Get.find(); - final cachedUserInfo = conversationController.getCachedUserInfo(receiverId); + final cachedUserInfo = conversationController.getCachedUserInfo( + receiverId, + ); if (cachedUserInfo != null) { receiverNickName = cachedUserInfo.nickName; receiverAvatarUrl = cachedUserInfo.avatarUrl; if (Get.isLogEnable) { - Get.log('✅ [IMManager] 从ConversationController缓存获取接收者信息: userId=$receiverId, nickName=$receiverNickName'); + Get.log( + '✅ [IMManager] 从ConversationController缓存获取接收者信息: userId=$receiverId, nickName=$receiverNickName', + ); } } } @@ -1422,7 +1662,9 @@ class IMManager { } // 添加发送者在线状态(发送消息时肯定在线) message.attributes!['sender_isOnline'] = 'true'; - message.attributes!['sender_lastActiveTime'] = DateTime.now().millisecondsSinceEpoch.toString(); + message.attributes!['sender_lastActiveTime'] = DateTime.now() + .millisecondsSinceEpoch + .toString(); // 接收者信息(receiver_ 前缀) if (receiverId != null && receiverId.isNotEmpty) { @@ -1431,7 +1673,10 @@ class IMManager { message.attributes!['receiver_nickName'] = receiverNickName; } if (receiverAvatarUrl != null && receiverAvatarUrl.isNotEmpty) { - final cleanReceiverAvatarUrl = receiverAvatarUrl.trim().replaceAll('`', ''); + final cleanReceiverAvatarUrl = receiverAvatarUrl.trim().replaceAll( + '`', + '', + ); message.attributes!['receiver_avatarUrl'] = cleanReceiverAvatarUrl; } } @@ -1448,8 +1693,12 @@ class IMManager { if (Get.isLogEnable) { Get.log('✅ [IMManager] 已添加用户信息到消息扩展字段:'); - Get.log(' 发送者: userId=$currentUserId, nickName=$senderNickName, avatarUrl=${senderAvatarUrl?.trim().replaceAll('`', '')}'); - Get.log(' 接收者: userId=$receiverId, nickName=$receiverNickName, avatarUrl=${receiverAvatarUrl?.trim().replaceAll('`', '')}'); + Get.log( + ' 发送者: userId=$currentUserId, nickName=$senderNickName, avatarUrl=${senderAvatarUrl?.trim().replaceAll('`', '')}', + ); + Get.log( + ' 接收者: userId=$receiverId, nickName=$receiverNickName, avatarUrl=${receiverAvatarUrl?.trim().replaceAll('`', '')}', + ); } } catch (e) { if (Get.isLogEnable) { @@ -1480,10 +1729,12 @@ class IMManager { } return; } - + if (attributes == null || attributes.isEmpty) { if (Get.isLogEnable) { - Get.log('⚠️ [IMManager] 消息扩展字段为空: msgId=${message.msgId}, fromUserId=$fromUserId'); + Get.log( + '⚠️ [IMManager] 消息扩展字段为空: msgId=${message.msgId}, fromUserId=$fromUserId', + ); } return; } @@ -1513,7 +1764,9 @@ class IMManager { conversationController.cacheUserInfo(fromUserId, extendedInfo); if (Get.isLogEnable) { - Get.log('✅ [IMManager] 从消息扩展字段解析并缓存用户信息: userId=$fromUserId, nickName=$nickName, avatarUrl=$avatarUrl'); + Get.log( + '✅ [IMManager] 从消息扩展字段解析并缓存用户信息: userId=$fromUserId, nickName=$nickName, avatarUrl=$avatarUrl', + ); } } else { if (Get.isLogEnable) { @@ -1587,7 +1840,7 @@ class IMManager { String? callType; String? callStatus; String? channelId; - + try { // 自定义消息 if (message.body.type == MessageType.CUSTOM) { @@ -1597,17 +1850,15 @@ class IMManager { callType = params['callType'] ?? 'voice'; callStatus = params['callStatus'] ?? 'missed'; channelId = params['channelId'] ?? ''; - callInfo = { - 'callType': callType, - 'callStatus': callStatus, - }; + callInfo = {'callType': callType, 'callStatus': callStatus}; } } - + // 如果解析到通话信息,检查是否需要显示视频通话邀请弹框 if (callInfo != null && callType != null && callStatus != null) { // 只处理视频通话且状态为 missed 或 calling 的消息(新邀请) - if ((callType == 'video' || callType == 'voice') && (callStatus == 'waitCalling' || callStatus == 'calling')) { + if ((callType == 'video' || callType == 'voice') && + (callStatus == 'waitCalling' || callStatus == 'calling')) { RTMManager.instance.subscribe(channelId ?? ''); // 获取用户信息 Map? attributes; @@ -1628,11 +1879,14 @@ class IMManager { } // 如果从消息扩展字段中获取不到,尝试从 ConversationController 的缓存中获取 - if ((nickName == null || nickName.isEmpty) || (avatarUrl == null || avatarUrl.isEmpty)) { + if ((nickName == null || nickName.isEmpty) || + (avatarUrl == null || avatarUrl.isEmpty)) { try { if (Get.isRegistered()) { - final conversationController = Get.find(); - final cachedUserInfo = conversationController.getCachedUserInfo(fromId); + final conversationController = + Get.find(); + final cachedUserInfo = conversationController + .getCachedUserInfo(fromId); if (cachedUserInfo != null) { nickName = nickName ?? cachedUserInfo.nickName; avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl; @@ -1662,26 +1916,28 @@ class IMManager { onTap: () async { // 关闭弹框 SmartDialog.dismiss(); - + // 停止播放来电铃声 callController.stopCallAudio(); - + // 只跳转到视频通话页面,不自动接通 - Get.to(() => VideoCallPage( - targetUserId: fromId, - isInitiator: false, - callType: callType, - channelId: channelId, - callMessage: message, - )); + Get.to( + () => VideoCallPage( + targetUserId: fromId, + isInitiator: false, + callType: callType, + channelId: channelId, + callMessage: message, + ), + ); }, onAccept: () async { // 关闭弹框 SmartDialog.dismiss(); - + // 停止播放来电铃声(acceptCall 中也会停止,但这里提前停止以更快响应) callController.stopCallAudio(); - + // 接听通话 ChatController? chatController; try { @@ -1694,30 +1950,32 @@ class IMManager { Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); } } - + final accepted = await callController.acceptCall( message: message, chatController: chatController, ); - + if (accepted) { // 跳转到视频通话页面 - Get.to(() => VideoCallPage( - targetUserId: fromId, - isInitiator: false, - callType: callType, - channelId: channelId, - callMessage: message, - )); + Get.to( + () => VideoCallPage( + targetUserId: fromId, + isInitiator: false, + callType: callType, + channelId: channelId, + callMessage: message, + ), + ); } }, onReject: () async { // 先关闭弹框 SmartDialog.dismiss(); - + // 停止播放来电铃声(rejectCall 中也会停止,但这里提前停止以更快响应) callController.stopCallAudio(); - + // 拒绝通话(会修改消息状态为 rejected) ChatController? chatController; try { @@ -1730,7 +1988,7 @@ class IMManager { Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); } } - + // 调用拒绝通话,会使用 modifyMessage 修改消息状态 await callController.rejectCall( message: message, @@ -1747,12 +2005,12 @@ class IMManager { clickMaskDismiss: false, keepSingle: true, // 确保只有一个弹框显示 ); - + if (Get.isLogEnable) { Get.log('📞 [IMManager] 显示视频通话邀请弹框: $fromId'); } } - + // 对于所有 CALL 消息(包括视频和语音),都不显示普通消息通知弹框 return; } @@ -1797,11 +2055,14 @@ class IMManager { } // 如果从消息扩展字段中获取不到,尝试从 ConversationController 的缓存中获取 - if ((nickName == null || nickName.isEmpty) || (avatarUrl == null || avatarUrl.isEmpty)) { + if ((nickName == null || nickName.isEmpty) || + (avatarUrl == null || avatarUrl.isEmpty)) { try { if (Get.isRegistered()) { final conversationController = Get.find(); - final cachedUserInfo = conversationController.getCachedUserInfo(fromId); + final cachedUserInfo = conversationController.getCachedUserInfo( + fromId, + ); if (cachedUserInfo != null) { nickName = nickName ?? cachedUserInfo.nickName; avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl; @@ -1819,15 +2080,19 @@ class IMManager { final finalAvatarUrl = avatarUrl ?? ''; // 将消息加入队列 - _notificationQueue.add(_NotificationMessage( - fromId: fromId, - nickName: finalNickName, - avatarUrl: finalAvatarUrl, - messageContent: messageContent, - )); + _notificationQueue.add( + _NotificationMessage( + fromId: fromId, + nickName: finalNickName, + avatarUrl: finalAvatarUrl, + messageContent: messageContent, + ), + ); if (Get.isLogEnable) { - Get.log('✅ [IMManager] 消息已加入通知队列: fromId=$fromId, nickName=$finalNickName, 队列长度=${_notificationQueue.length}'); + Get.log( + '✅ [IMManager] 消息已加入通知队列: fromId=$fromId, nickName=$finalNickName, 队列长度=${_notificationQueue.length}', + ); } // 如果当前没有弹框显示,立即显示队列中的第一条消息 @@ -1864,10 +2129,12 @@ class IMManager { SmartDialog.dismiss(); _onNotificationDismissed(); // 跳转到聊天页面 - Get.to(() => ChatPage( - userId: notification.fromId, - userData: null, // userData 可选,ChatPage 会自己处理 - )); + Get.to( + () => ChatPage( + userId: notification.fromId, + userData: null, // userData 可选,ChatPage 会自己处理 + ), + ); }, ); }, @@ -2000,7 +2267,8 @@ class IMManager { /// 获取黑名单列表 Future> getBlacklist() async { try { - final list = await EMClient.getInstance.contactManager.getBlockListFromServer(); + final list = await EMClient.getInstance.contactManager + .getBlockListFromServer(); print('获取黑名单列表成功,共 ${list.length} 个用户'); return list; } catch (e) { @@ -2035,13 +2303,18 @@ class IMManager { } /// 删除消息 - Future deleteMessage(String conversationId, String messageId, {bool deleteRemote = false}) async { + Future deleteMessage( + String conversationId, + String messageId, { + bool deleteRemote = false, + }) async { try { - final conversation = await EMClient.getInstance.chatManager.getConversation( - conversationId, - type: EMConversationType.Chat, - createIfNeed: false, - ); + final conversation = await EMClient.getInstance.chatManager + .getConversation( + conversationId, + type: EMConversationType.Chat, + createIfNeed: false, + ); if (conversation != null) { await conversation.deleteMessage(messageId); if (Get.isLogEnable) { @@ -2061,13 +2334,17 @@ class IMManager { } /// 标记消息为已读 - Future markMessageAsRead(String conversationId, String messageId) async { + Future markMessageAsRead( + String conversationId, + String messageId, + ) async { try { - final conversation = await EMClient.getInstance.chatManager.getConversation( - conversationId, - type: EMConversationType.Chat, - createIfNeed: false, - ); + final conversation = await EMClient.getInstance.chatManager + .getConversation( + conversationId, + type: EMConversationType.Chat, + createIfNeed: false, + ); if (conversation != null) { await conversation.markMessageAsRead(messageId); if (Get.isLogEnable) { @@ -2089,11 +2366,12 @@ class IMManager { /// 标记会话所有消息为已读 Future markAllMessagesAsRead(String conversationId) async { try { - final conversation = await EMClient.getInstance.chatManager.getConversation( - conversationId, - type: EMConversationType.Chat, - createIfNeed: false, - ); + final conversation = await EMClient.getInstance.chatManager + .getConversation( + conversationId, + type: EMConversationType.Chat, + createIfNeed: false, + ); if (conversation != null) { await conversation.markAllMessagesAsRead(); if (Get.isLogEnable) { @@ -2195,7 +2473,9 @@ class IMManager { _addUserInfoToMessageExt(newMessage); // 重新发送消息 - final result = await EMClient.getInstance.chatManager.sendMessage(newMessage); + final result = await EMClient.getInstance.chatManager.sendMessage( + newMessage, + ); if (Get.isLogEnable) { Get.log('✅ [IMManager] 消息重发成功: ${newMessage.msgId}'); } @@ -2212,7 +2492,9 @@ class IMManager { void dispose() { try { if (_listenersRegistered) { - EMClient.getInstance.removeConnectionEventHandler(_connectionHandlerKey); + EMClient.getInstance.removeConnectionEventHandler( + _connectionHandlerKey, + ); EMClient.getInstance.chatManager.removeEventHandler(_chatHandlerKey); _listenersRegistered = false; if (Get.isLogEnable) { diff --git a/lib/pages/discover/live_room_page.dart b/lib/pages/discover/live_room_page.dart index 48ac2bc..d2eef1b 100644 --- a/lib/pages/discover/live_room_page.dart +++ b/lib/pages/discover/live_room_page.dart @@ -248,7 +248,7 @@ class _LiveRoomPageState extends State { } // 等待页面关闭后再显示小窗口,确保小窗口能正确显示 Future.delayed(const Duration(milliseconds: 200), () { - _overlayController.show(); + _overlayController.show(); }); }, child: Scaffold( @@ -281,41 +281,41 @@ class _LiveRoomPageState extends State { ), Container( padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), - child: Column( - children: [ - SizedBox(height: 10.w), - Obx(() { + child: Column( + children: [ + SizedBox(height: 10.w), + Obx(() { final detail = _roomController.rtcChannelDetail.value; - final anchorInfo = detail?.anchorInfo; + final anchorInfo = detail?.anchorInfo; final userName = anchorInfo?.nickName ?? '用户'; final avatarAsset = anchorInfo?.profilePhoto ?? Assets.imagesUserAvatar; - const popularityText = '0'; // TODO: 使用真实数据 + const popularityText = '0'; // TODO: 使用真实数据 - return LiveRoomUserHeader( - userName: userName, - popularityText: popularityText, - avatarAsset: avatarAsset, - onCloseTap: () { - SmartDialog.dismiss(); - // 退出房间时清空RTM消息 - if (Get.isRegistered()) { + return LiveRoomUserHeader( + userName: userName, + popularityText: popularityText, + avatarAsset: avatarAsset, + onCloseTap: () { + SmartDialog.dismiss(); + // 退出房间时清空RTM消息 + if (Get.isRegistered()) { final roomController = Get.find(); - roomController.chatMessages.clear(); - } + roomController.chatMessages.clear(); + } Get.back(); // 等待页面关闭后再显示小窗口,确保小窗口能正确显示 Future.delayed(const Duration(milliseconds: 200), () { - _overlayController.show(); + _overlayController.show(); }); - }, - ); - }), - SizedBox(height: 7.w), - LiveRoomAnchorShowcase(), - SizedBox(height: 5.w), - const LiveRoomActiveSpeaker(), - SizedBox(height: 9.w), + }, + ); + }), + SizedBox(height: 7.w), + LiveRoomAnchorShowcase(), + SizedBox(height: 5.w), + const LiveRoomActiveSpeaker(), + SizedBox(height: 9.w), Expanded(child: const LiveRoomNoticeChatPanel()), // 根据键盘状态显示/隐藏 LiveRoomActionBar if (MediaQuery.of(context).viewInsets.bottom == 0) diff --git a/lib/rtc/rtc_manager.dart b/lib/rtc/rtc_manager.dart index 9f10d49..4638121 100644 --- a/lib/rtc/rtc_manager.dart +++ b/lib/rtc/rtc_manager.dart @@ -174,12 +174,13 @@ class RTCManager { } else if (type == RTCType.call) { // 通话场景:通知 CallController 远端用户已加入 print('📞 [RTCManager] 通话场景:检测到用户加入,UID:$remoteUid'); - if (Get.isRegistered()) { - final callController = Get.find(); + try { + // 直接使用 CallController.instance,因为它使用单例模式会自动注册 + final callController = CallController.instance; callController.remoteUid.value = remoteUid; print('✅ [RTCManager] 通话场景:已设置 CallController.remoteUid = $remoteUid'); - } else { - print('⚠️ [RTCManager] CallController 未注册,无法设置 remoteUid'); + } catch (e) { + print('⚠️ [RTCManager] 设置 CallController.remoteUid 失败: $e'); } } else { print('⚠️ [RTCManager] 未知的 RTC 类型:$type'); @@ -565,11 +566,16 @@ class RTCManager { remoteUsersNotifier.value = List.unmodifiable(_remoteUserIds); // 如果是通话场景,同时更新 CallController 的 remoteUid - if (type == RTCType.call && Get.isRegistered()) { - final callController = Get.find(); - if (callController.remoteUid.value == null) { - callController.remoteUid.value = remoteUid; - print('📞 [RTCManager] _handleRemoteUserJoined: 已同步 remoteUid 到 CallController: $remoteUid'); + if (type == RTCType.call) { + try { + // 直接使用 CallController.instance,因为它使用单例模式会自动注册 + final callController = CallController.instance; + if (callController.remoteUid.value == null) { + callController.remoteUid.value = remoteUid; + print('📞 [RTCManager] _handleRemoteUserJoined: 已同步 remoteUid 到 CallController: $remoteUid'); + } + } catch (e) { + print('⚠️ [RTCManager] _handleRemoteUserJoined 设置 CallController.remoteUid 失败: $e'); } } } diff --git a/lib/widget/live/live_room_anchor_showcase.dart b/lib/widget/live/live_room_anchor_showcase.dart index d83f04a..8a768fe 100644 --- a/lib/widget/live/live_room_anchor_showcase.dart +++ b/lib/widget/live/live_room_anchor_showcase.dart @@ -106,24 +106,24 @@ class _LiveRoomAnchorShowcaseState extends State { _showGiftPopupForUser(anchorInfo, 2); } }, - child: Container( - width: 47.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20.w)), - color: Colors.white, - ), - child: Center( - child: Text( - anchorInfo?.isFriend == true ? '好友' : "加好友", - style: TextStyle( - fontSize: 11.w, - color: const Color.fromRGBO(117, 98, 249, 1), - ), + child: Container( + width: 47.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20.w)), + color: Colors.white, + ), + child: Center( + child: Text( + anchorInfo?.isFriend == true ? '好友' : "加好友", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(117, 98, 249, 1), ), ), ), ), + ), ); }), ], @@ -290,25 +290,25 @@ class _LiveRoomAnchorShowcaseState extends State { } _showGiftPopupForUser(userInfo, 2); }, - child: Container( - width: 47.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(20.w), - ), - color: Colors.white, + child: Container( + width: 47.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.w), ), - child: Center( - child: Text( - userInfo.isFriend != true ? "加好友" : '好友', - style: TextStyle( - fontSize: 11.w, - color: const Color.fromRGBO(117, 98, 249, 1), - ), + color: Colors.white, + ), + child: Center( + child: Text( + userInfo.isFriend != true ? "加好友" : '好友', + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(117, 98, 249, 1), ), ), ), + ), ), ), Positioned(