import 'dart:io'; import 'dart:async'; import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import '../controller/message/conversation_controller.dart'; import '../controller/message/chat_controller.dart'; import '../controller/global.dart'; import '../pages/mine/login_page.dart'; import '../pages/message/chat_page.dart'; import '../network/user_api.dart'; import '../widget/message/message_notification_dialog.dart'; import '../widget/message/video_call_invite_dialog.dart'; import '../pages/message/video_call_page.dart'; import '../controller/message/call_controller.dart'; import '../service/local_notification_service.dart'; // 消息通知数据结构 class _NotificationMessage { final String fromId; final String nickName; final String avatarUrl; final String messageContent; _NotificationMessage({ required this.fromId, required this.nickName, required this.avatarUrl, required this.messageContent, }); } // 完整的IM管理器实现,使用实际的SDK类型和方法 class IMManager { // 单例模式 static final IMManager _instance = IMManager._internal(); factory IMManager() => _instance; final storage = GetStorage(); // 静态getter用于instance访问 static IMManager get instance => _instance; bool _initialized = false; bool _listenersRegistered = false; bool _loggedIn = false; bool _isReconnecting = false; // 是否正在重连 Completer? _initCompleter; // 用于确保并发调用时只初始化一次 // 监听器标识符 static const String _connectionHandlerKey = 'im_manager_connection_handler'; static const String _chatHandlerKey = 'im_manager_chat_handler'; static const String _presenceHandlerKey = 'im_manager_presence_handler'; // 存储活跃的 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; IMManager._internal() { print('IMManager instance created'); } bool get isInitialized => _initialized; bool get isLoggedIn => _loggedIn; /// 确保 IM 初始化完成(支持并发调用,只初始化一次) Future ensureInitialized({required String appKey}) async { // 如果已经初始化,直接返回 if (_initialized) { debugPrint('✅ IM 已初始化,无需重复初始化'); return; } // 如果正在初始化,等待完成 if (_initCompleter != null) { debugPrint('🟡 IM 初始化中,等待完成...'); return _initCompleter!.future; } _initCompleter = Completer(); debugPrint('🟡 IM 开始初始化'); debugPrint('📋 AppKey: $appKey'); try { // 确保 Flutter 绑定已初始化 WidgetsFlutterBinding.ensureInitialized(); debugPrint('✅ WidgetsFlutterBinding 已初始化'); // 验证 appKey 格式 if (appKey.isEmpty) { throw Exception('AppKey 不能为空'); } if (!appKey.contains('#')) { debugPrint('⚠️ AppKey 格式可能不正确,应为 "orgname#appname" 格式'); } // 创建 EMOptions final options = EMOptions.withAppKey( appKey, autoLogin: false, debugMode: true, usingHttpsOnly: true, acceptInvitationAlways: false, ); debugPrint('✅ EMOptions 创建成功'); // 调用 SDK 初始化 debugPrint('🟡 调用 EMClient.getInstance.init()...'); try { await EMClient.getInstance.init(options); debugPrint('🟢 EMClient.getInstance.init() 调用完成'); } on PlatformException catch (e) { debugPrint('❌ PlatformException 捕获:'); debugPrint(' code: ${e.code}'); debugPrint(' message: ${e.message}'); debugPrint(' details: ${e.details}'); debugPrint(' stacktrace: ${e.stacktrace}'); rethrow; } catch (e, s) { debugPrint('❌ 其他异常: $e'); debugPrint('堆栈: $s'); rethrow; } // 注册监听器 _registerListeners(); // 初始化本地通知服务 await _localNotificationService.initialize(); _initialized = true; debugPrint('✅ IM 初始化成功'); _initCompleter!.complete(); } catch (e, s) { debugPrint('❌ IM 初始化失败'); debugPrint('错误类型: ${e.runtimeType}'); debugPrint('错误信息: $e'); debugPrint('堆栈跟踪:'); debugPrint('$s'); _initialized = false; _initCompleter!.completeError(e, s); _initCompleter = null; // 允许重试 // 重新抛出异常,让调用者知道初始化失败 rethrow; } } // 注册监听器 void _registerListeners() { try { // 防止重复注册 if (_listenersRegistered) { if (Get.isLogEnable) { Get.log('监听器已注册,跳过重复注册'); } return; } // 连接监听器 EMClient.getInstance.addConnectionEventHandler( _connectionHandlerKey, EMConnectionEventHandler( onConnected: () { // 重置重连状态 _isReconnecting = false; debugPrint('🔌 IM 已连接'); // 连接成功后,通知 ConversationController 加载会话列表 _refreshConversationList(); }, onDisconnected: () { debugPrint('🔌 IM 已断开'); _loggedIn = false; // 自动重连逻辑 _handleDisconnected(); }, onTokenDidExpire: () { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] IM token即将过期'); } // TODO: 可以在这里添加自动刷新token的逻辑 }, onUserKickedByOtherDevice: () { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 用户被其他设备踢出'); } else { print('⚠️ [IMManager] 用户被其他设备踢出'); } // 处理踢下线逻辑 _handleUserKickedOffline(); }, ), ); // 消息监听器 EMClient.getInstance.chatManager.addEventHandler( _chatHandlerKey, EMChatEventHandler( onMessagesReceived: (messages) { debugPrint('📩 收到消息数: ${messages.length}'); // 从消息扩展字段中解析用户信息并缓存 for (var message in messages) { if (message.direction == MessageDirection.RECEIVE && message.onlineState) { // 检查发送者是否是当前正在聊天的用户 final fromId = message.from; if (fromId != null && _activeChatControllers.containsKey(fromId)) { // 如果是当前正在聊天的联系人,将该会话的所有消息标记为已读 Future.microtask(() async { await markAllMessagesAsRead(fromId); }); } _parseUserInfoFromMessageExt(message); // 检查发送者是否是当前正在聊天的用户,如果不是则显示弹框 // 只有在 APP 处于前台时才显示弹框,后台时显示本地通知 final lifecycleState = WidgetsBinding.instance.lifecycleState; if (lifecycleState == AppLifecycleState.resumed) { _checkAndShowNotificationDialog(message); } else { // APP 在后台,显示本地通知 _localNotificationService.showNotification( message, activeChatUserIds: _activeChatControllers.keys.toSet(), ); } } } // 收到新消息时,更新会话列表 _refreshConversationList(); // 通知对应的 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}', ); if (Get.isLogEnable) { Get.log('📨 [IMManager] 消息内容已修改: ${message.localTime}'); } // 检查是否是通话消息 bool isCallMessage = false; try { if (message.body.type == MessageType.CUSTOM) { final customBody = message.body as EMCustomMessageBody; isCallMessage = customBody.event == 'call'; } } catch (e) { // 解析失败,不是通话消息 } // 如果是通话消息,更新 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 userId = message.from; } else if (message.direction == MessageDirection.SEND) { // 发送的消息,使用接收者ID userId = message.to; } if (Get.isLogEnable) { 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, ); if (index != -1) { // 保存旧状态 final oldMessage = controller.messages[index]; final oldCustomBody = oldMessage.body as EMCustomMessageBody; oldCallStatus = oldCustomBody.params?['callStatus']; if (Get.isLogEnable) { 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', ); } else { 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', ); } // 尝试通过通话消息的特征来匹配(通过channelId) final customBody = message.body as EMCustomMessageBody; final channelId = customBody.params?['channelId']; if (channelId != null && channelId.isNotEmpty) { // 尝试通过channelId和时间戳匹配 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) { final msgChannelId = msgBody.params?['channelId']; // 匹配相同的channelId和相似的时间戳(允许一定误差) if (msgChannelId == channelId) { // 检查时间戳是否接近(允许5秒误差) 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, 旧状态: $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', ); } else { 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}', ); } controller.messages.add(message); controller.messages.refresh(); controller.update(); } } else { if (Get.isLogEnable) { Get.log( '⚠️ [IMManager] 通话消息没有channelId,无法匹配: msgId=${message.msgId}', ); } } } } else { // 即使找不到 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 { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] userId 为空,无法更新通话消息'); } } } catch (e, stackTrace) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 处理通话消息更新失败: $e'); Get.log('堆栈跟踪: $stackTrace'); } else { print('⚠️ [IMManager] 处理通话消息更新失败: $e'); print('堆栈跟踪: $stackTrace'); } } } // 处理金币数值:只对接收到的消息处理金币标签 // 从消息 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, ); if (index != -1) { controller.messages[index] = message; controller.update(); if (Get.isLogEnable) { Get.log( '✅ [IMManager] 已更新接收消息的金币信息: msgId=${message.msgId}, revenueInfo=$revenueInfo', ); } } } } } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 处理金币信息失败: $e'); } } } }, ), ); 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}', ); } } controller.update(); // 刷新会话列表,确保聊天列表能显示失败状态 try { if (Get.isRegistered()) { final conversationController = Get.find(); conversationController.refreshConversations(); } } 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}', ); } } // 如果是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(); } } catch (e) { // ConversationController 可能未注册,忽略错误 } if (Get.isLogEnable) { Get.log( '✅ [IMManager] 已通过内容匹配更新消息状态: $targetId, status=FAIL', ); } } } } } } } 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} 个用户', ); } // 处理状态变化 for (var presence in presences) { final userId = presence.publisher; if (userId != null && userId.isNotEmpty) { // 使用 statusDescription 字段来判断在线状态 final statusDescStr = presence.statusDescription ?? ''; final statusDesc = statusDescStr.toLowerCase(); // 判断在线状态:online、available 等表示在线 final isOnline = statusDesc == 'online' || statusDesc == 'available'; if (Get.isLogEnable) { Get.log( '📡 [IMManager] Presence状态变化: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', ); } else { 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', ); } } } }, ), ); _listenersRegistered = true; if (Get.isLogEnable) { Get.log('✅ [IMManager] 监听器注册成功'); } } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 注册监听器失败: $e'); } } } /// 登录(真正判断 IM 是否可用的地方) Future loginWithToken({ required String appKey, required String userId, required String token, }) async { try { await ensureInitialized(appKey: appKey); // 防止脏状态 await EMClient.getInstance.logout().catchError((_) {}); await EMClient.getInstance.loginWithToken(userId, token); _loggedIn = true; // 重置重连状态 _isReconnecting = false; // 登录成功后,通知 ConversationController 刷新会话列表 _refreshConversationList(); debugPrint('✅ IM 登录成功: $userId'); return true; } catch (e, s) { _loggedIn = false; debugPrint('❌ IM 登录失败: $e'); debugPrint('$s'); return false; } } /// 登录IM服务(向后兼容方法) Future login(String token) async { final userId = storage.read('userId'); if (userId == null) { debugPrint('❌ IM 登录失败: userId 为空'); return false; } return await loginWithToken( appKey: '1165251016193374#demo', userId: userId, token: token, ); } /// 登出IM服务 Future logout() async { try { await EMClient.getInstance.logout(); } finally { _loggedIn = false; } } /// 处理连接断开,自动重连 void _handleDisconnected() async { // 如果正在重连,避免重复重连 if (_isReconnecting) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 正在重连中,跳过重复重连'); } return; } // 如果未初始化或未登录,不需要重连 if (!_initialized || !_loggedIn) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] SDK未初始化或用户未登录,跳过重连'); } return; } _isReconnecting = true; if (Get.isLogEnable) { Get.log('🔄 [IMManager] 开始自动重连...'); } else { print('🔄 [IMManager] 开始自动重连...'); } try { // 延迟2秒后重连,避免频繁重连 await Future.delayed(const Duration(seconds: 2)); // 检查是否仍然需要重连(可能在延迟期间已经连接成功) if (!_loggedIn) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 用户已登出,取消重连'); } _isReconnecting = false; return; } // 获取 UserApi 来获取新的 token if (!Get.isRegistered()) { if (Get.isLogEnable) { Get.log('❌ [IMManager] UserApi 未注册,无法获取token重连'); } _isReconnecting = false; return; } final userApi = Get.find(); final response = await userApi.getHxUserToken(); // 检查响应:code == 0 表示成功 if (Get.isLogEnable) { Get.log( '📡 [IMManager] 获取token响应: code=${response.data.code}, message=${response.data.message}', ); } else { print( '📡 [IMManager] 获取token响应: code=${response.data.code}, message=${response.data.message}', ); } if (response.data.isSuccess && response.data.data != null) { final token = response.data.data!; if (Get.isLogEnable) { Get.log('✅ [IMManager] 获取到新的token (长度: ${token.length}),开始重新登录'); } else { print('✅ [IMManager] 获取到新的token (长度: ${token.length}),开始重新登录'); } // 重新登录 final loginSuccess = await login(token); if (loginSuccess) { if (Get.isLogEnable) { Get.log('✅ [IMManager] 自动重连成功'); } else { print('✅ [IMManager] 自动重连成功'); } } else { if (Get.isLogEnable) { Get.log('❌ [IMManager] 自动重连失败:登录失败'); } else { print('❌ [IMManager] 自动重连失败:登录失败'); } } } else { if (Get.isLogEnable) { 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}', ); } } } catch (e, stackTrace) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 自动重连异常: $e'); Get.log('堆栈跟踪: $stackTrace'); } else { print('❌ [IMManager] 自动重连异常: $e'); print('堆栈跟踪: $stackTrace'); } } finally { _isReconnecting = false; } } /// 处理用户被踢下线 void _handleUserKickedOffline() async { try { // 显示提示 SmartDialog.showToast('您的账号在其他设备登录,已被强制下线'); // 先退出 IM 登录 await logout(); // 清除会话列表和用户信息缓存 if (Get.isRegistered()) { final conversationController = Get.find(); conversationController.clearConversations(); } // 清除本地存储 storage.remove('userId'); storage.remove('token'); // 清除全局数据 GlobalData().logout(); // 延迟一小段时间再跳转,确保用户看到提示 await Future.delayed(Duration(milliseconds: 500)); // 跳转到登录页 Get.offAll(() => LoginPage()); if (Get.isLogEnable) { Get.log('✅ [IMManager] 用户被踢下线处理完成,已跳转到登录页'); } } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 处理踢下线失败: $e'); } else { print('❌ [IMManager] 处理踢下线失败: $e'); } } } /// 检查用户是否存在于IM系统中 Future checkUserExists(String userId) async { try { final userInfo = await getUserInfo(userId); if (userInfo != null) { if (Get.isLogEnable) { Get.log('✅ [IMManager] 用户存在: $userId'); } else { print('✅ [IMManager] 用户存在: $userId'); } return true; } else { if (Get.isLogEnable) { Get.log('❌ [IMManager] 用户不存在: $userId'); } else { print('❌ [IMManager] 用户不存在: $userId'); } return false; } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 检查用户是否存在失败: $e'); } else { print('⚠️ [IMManager] 检查用户是否存在失败: $e'); } return false; } } /// 发送文本消息 Future sendTextMessage( String content, String toChatUsername, ) async { try { // 检查是否已登录 // 注意:如果发送失败,可能是未登录或目标用户ID不正确 if (Get.isLogEnable) { Get.log('📤 [IMManager] 准备发送消息: to=$toChatUsername, content=$content'); } else { print('📤 [IMManager] 准备发送消息: to=$toChatUsername, content=$content'); } // 检查当前登录的用户ID final currentUserId = storage.read('userId'); if (Get.isLogEnable) { Get.log( '📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername', ); } else { print( '📤 [IMManager] 当前登录用户ID: $currentUserId, 目标用户ID: $toChatUsername', ); } // 检查目标用户是否存在于IM系统中 final userExists = await checkUserExists(toChatUsername); if (!userExists) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 目标用户不存在于IM系统中: $toChatUsername'); } else { print('❌ [IMManager] 目标用户不存在于IM系统中: $toChatUsername'); } // 即使用户不存在,也尝试发送,因为某些IM系统可能允许发送给未注册用户 // 但会记录警告信息 } // 创建文本消息 final message = EMMessage.createTxtSendMessage( targetId: toChatUsername, content: content, ); // 在消息扩展字段中添加当前用户信息(用于接收方显示头像和昵称) _addUserInfoToMessageExt(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) { // 捕获异常,可能是未登录或其他错误 if (Get.isLogEnable) { Get.log('❌ [IMManager] 发送文本消息失败: to=$toChatUsername, 错误: $e'); } else { print('❌ [IMManager] 发送文本消息失败: to=$toChatUsername, 错误: $e'); } return null; } } /// 发送语音消息 Future sendVoiceMessage( String filePath, String toChatUsername, int duration, ) async { try { // 创建语音消息 final message = EMMessage.createVoiceSendMessage( targetId: toChatUsername, filePath: filePath, duration: duration, ); // 在消息扩展字段中添加当前用户信息 _addUserInfoToMessageExt(message); // 发送消息 await EMClient.getInstance.chatManager.sendMessage(message); if (Get.isLogEnable) { Get.log('✅ [IMManager] 语音消息发送成功'); } return message; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 发送语音消息失败: $e'); } return null; } } /// 发送图片消息 Future sendImageMessage( String imagePath, String toChatUsername, ) async { try { // 创建图片消息 final message = EMMessage.createImageSendMessage( targetId: toChatUsername, filePath: imagePath, sendOriginalImage: false, ); // 在消息扩展字段中添加当前用户信息 _addUserInfoToMessageExt(message); // 发送消息 await EMClient.getInstance.chatManager.sendMessage(message); print('Image message sent successfully'); return message; } catch (e) { print('Failed to send image message: $e'); return null; } } /// 发送视频消息 Future sendVideoMessage( String videoPath, String toChatUsername, int duration, ) async { try { print('🎬 [IMManager] 创建视频消息'); print('视频路径: $videoPath'); print('接收用户: $toChatUsername'); print('视频时长: $duration 秒'); // 🎯 手动生成视频缩略图 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, // 图片质量 ); if (uint8list != null && File(uint8list).existsSync()) { thumbnailPath = uint8list; print('✅ [IMManager] 缩略图生成成功: $thumbnailPath'); } else { print('⚠️ [IMManager] 缩略图生成返回null'); thumbnailPath = null; } } catch (e) { print('❌ [IMManager] 生成缩略图失败: $e'); thumbnailPath = null; } // 创建视频消息 final message = EMMessage.createVideoSendMessage( targetId: toChatUsername, filePath: videoPath, duration: duration, thumbnailLocalPath: thumbnailPath, // 🎯 指定缩略图路径 ); // 在消息扩展字段中添加当前用户信息 _addUserInfoToMessageExt(message); 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!); print('📸 [IMManager] 缩略图文件是否存在: ${thumbFile.existsSync()}'); } } // 发送消息 await EMClient.getInstance.chatManager.sendMessage(message); print('✅ [IMManager] 视频消息发送成功'); return message; } catch (e) { print('❌ [IMManager] 发送视频消息失败: $e'); return null; } } /// 发送自定义消息 Future sendCustomMessage( String targetId, String event, Map? data, ) async { final customMsg = EMMessage.createCustomSendMessage( targetId: targetId, // `event` 为需要传递的自定义消息事件,比如礼物消息,可以设置: event: event, // `params` 类型为 `Map`。 params: data, ); // 在消息扩展字段中添加当前用户信息 _addUserInfoToMessageExt(customMsg); return await EMClient.getInstance.chatManager.sendMessage(customMsg); } /// 获取所有联系人 Future> getAllContacts() async { return await EMClient.getInstance.contactManager.fetchAllContacts(); } /// 获取会话列表 Future> getConversations() async { return await EMClient.getInstance.chatManager.loadAllConversations(); } /// 获取用户信息(单个用户) /// 删除指定会话 Future deleteConversation( String conversationId, { bool deleteMessages = true, }) async { try { await EMClient.getInstance.chatManager.deleteConversation( conversationId, deleteMessages: deleteMessages, ); return true; } catch (e) { if (Get.isLogEnable) { Get.log('删除会话失败: $e'); } else { print('删除会话失败: $e'); } return false; } } /// 获取好有列表 Future> getContacts(String userId) async { return await EMClient.getInstance.userInfoManager.fetchUserInfoById([ userId, ]); } /// 订阅用户在线状态(用于实时接收状态变化) /// [userId] 用户ID /// [callback] 状态变化回调函数 Future subscribeUserPresence( String userId, Function(bool isOnline) callback, ) async { try { if (!_loggedIn) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] IM未登录,无法订阅在线状态'); } return false; } // 保存回调 _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', ); } else { print( '✅ [IMManager] 订阅后立即获取状态: userId=$userId, isOnline=$onlineStatus', ); } callback(onlineStatus); } else { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 订阅后获取状态失败: userId=$userId'); } else { print('⚠️ [IMManager] 订阅后获取状态失败: userId=$userId'); } } return true; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 订阅用户在线状态失败: $userId, 错误: $e'); } else { print('❌ [IMManager] 订阅用户在线状态失败: $userId, 错误: $e'); } return false; } } /// 取消订阅用户在线状态 /// [userId] 用户ID Future unsubscribeUserPresence(String userId) async { try { if (!_loggedIn) { return false; } // 移除回调 _presenceCallbacks.remove(userId); 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) { Get.log('❌ [IMManager] 取消订阅用户在线状态失败: $userId, 错误: $e'); } return false; } } /// 获取用户在线状态 /// [userId] 用户ID /// 返回 true 表示在线,false 表示离线,null 表示获取失败 Future getUserPresenceStatus(String userId) async { try { if (!_loggedIn) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] IM未登录,无法获取在线状态'); } return null; } final presenceManager = EMClient.getInstance.presenceManager; // 获取用户在线状态 final presences = await presenceManager.fetchPresenceStatus( members: [userId], ); if (presences.isEmpty) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 未获取到用户在线状态: $userId'); } return null; } final presence = presences.first; // 使用 statusDescription 字段来判断在线状态 final statusDescStr = presence.statusDescription ?? ''; final statusDesc = statusDescStr.toLowerCase(); // 判断在线状态:online、available 等表示在线 final isOnline = statusDesc == 'online' || statusDesc == 'available'; if (Get.isLogEnable) { Get.log( '✅ [IMManager] 获取用户在线状态: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', ); } else { print( '✅ [IMManager] 获取用户在线状态: userId=$userId, statusDescription=$statusDesc, isOnline=$isOnline', ); } return isOnline; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 获取用户在线状态失败: $userId, 错误: $e'); } else { print('❌ [IMManager] 获取用户在线状态失败: $userId, 错误: $e'); } return null; } } /// 获取指定会话的消息记录 Future> getMessages( String conversationId, { int pageSize = 20, String? startMsgId, bool createIfNeed = false, }) async { try { EMConversationType convType = EMConversationType.Chat; // 获取会话对象 final conversation = await EMClient.getInstance.chatManager .getConversation( conversationId, type: convType, createIfNeed: createIfNeed, ); if (conversation == null) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 会话不存在: $conversationId'); } return []; } // 如果有startMsgId,从该消息开始加载;否则加载最新消息 if (startMsgId != null && startMsgId.isNotEmpty) { // 从指定消息ID开始加载更旧的消息 final messages = await conversation.loadMessages( startMsgId: startMsgId, loadCount: pageSize, ); return messages.map((msg) => msg as EMMessage?).toList(); } else { // 加载最新消息 final messages = await conversation.loadMessages(loadCount: pageSize); return messages.map((msg) => msg as EMMessage?).toList(); } } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 获取消息失败: $e'); } return []; } } /// 从 SDK 重新获取消息的最新状态(用于检查消息发送状态) Future getMessageById( String conversationId, String messageId, ) async { try { EMConversationType convType = EMConversationType.Chat; // 获取会话对象 final conversation = await EMClient.getInstance.chatManager .getConversation(conversationId, type: convType, createIfNeed: false); if (conversation == null) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 会话不存在: $conversationId'); } return null; } // 加载最新消息(包含刚发送的消息) 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) { Get.log('❌ [IMManager] 获取消息状态失败: $e'); } return null; } } /// 获取用户信息 Future getUserInfo(String userId) async { var data = await EMClient.getInstance.userInfoManager.fetchUserInfoById([ userId, ]); return data[userId]; } /// 在消息扩展字段中添加当前用户信息 /// 这样接收方可以从消息中获取发送方的头像和昵称 void _addUserInfoToMessageExt(EMMessage message) { try { // 获取当前登录用户ID final currentUserId = storage.read('userId'); if (currentUserId == null) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 无法获取当前用户ID,跳过添加用户信息到消息扩展字段'); } return; } // 获取接收者ID(消息的 to 字段) final receiverId = message.to; // ========== 获取发送者信息 ========== String? senderNickName; String? senderAvatarUrl; // 尝试从 ChatController 获取发送者信息 final senderChatController = _activeChatControllers[currentUserId]; if (senderChatController != null) { senderNickName = senderChatController.userNickName; senderAvatarUrl = senderChatController.userAvatarUrl; } // 如果 ChatController 中没有,尝试从全局数据获取 if (senderNickName == null || senderAvatarUrl == null) { try { final globalData = GlobalData.instance; final userData = globalData.userData; if (userData != null) { senderNickName = senderNickName ?? userData.nickName; senderAvatarUrl = senderAvatarUrl ?? userData.profilePhoto; } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 从 GlobalData 获取发送者信息失败: $e'); } } } // ========== 获取接收者信息 ========== String? receiverNickName; String? receiverAvatarUrl; // 尝试从 ChatController 获取接收者信息(通过接收者ID查找) if (receiverId != null && receiverId.isNotEmpty) { final receiverChatController = _activeChatControllers[receiverId]; if (receiverChatController != null && receiverChatController.userData != null) { // 从接收者的 ChatController 获取接收者的信息 // 注意:这里获取的是接收者自己的信息(用于发送方在聊天列表显示) receiverNickName = receiverChatController.userData!.nickName; receiverAvatarUrl = receiverChatController.userData!.profilePhoto; } else { // 如果 ChatController 没有 userData,尝试从 ConversationController 的缓存中获取 try { if (Get.isRegistered()) { final conversationController = Get.find(); 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', ); } } } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 从ConversationController获取接收者信息失败: $e'); } } } } // ========== 设置消息扩展字段 ========== // 同时存储发送者和接收者的信息 // 发送者信息:用于接收方在聊天列表显示发送者的头像和昵称 // 接收者信息:用于发送方在聊天列表显示接收者的头像和昵称 message.attributes ??= {}; // 发送者信息(sender_ 前缀) message.attributes!['sender_userId'] = currentUserId; if (senderNickName != null && senderNickName.isNotEmpty) { message.attributes!['sender_nickName'] = senderNickName; } if (senderAvatarUrl != null && senderAvatarUrl.isNotEmpty) { final cleanSenderAvatarUrl = senderAvatarUrl.trim().replaceAll('`', ''); message.attributes!['sender_avatarUrl'] = cleanSenderAvatarUrl; } // 添加发送者在线状态(发送消息时肯定在线) message.attributes!['sender_isOnline'] = 'true'; message.attributes!['sender_lastActiveTime'] = DateTime.now() .millisecondsSinceEpoch .toString(); // 接收者信息(receiver_ 前缀) if (receiverId != null && receiverId.isNotEmpty) { message.attributes!['receiver_userId'] = receiverId; if (receiverNickName != null && receiverNickName.isNotEmpty) { message.attributes!['receiver_nickName'] = receiverNickName; } if (receiverAvatarUrl != null && receiverAvatarUrl.isNotEmpty) { final cleanReceiverAvatarUrl = receiverAvatarUrl.trim().replaceAll( '`', '', ); message.attributes!['receiver_avatarUrl'] = cleanReceiverAvatarUrl; } } // 为了兼容旧代码,也保留原来的字段(发送者信息) message.attributes!['userId'] = currentUserId; if (senderNickName != null && senderNickName.isNotEmpty) { message.attributes!['nickName'] = senderNickName; } if (senderAvatarUrl != null && senderAvatarUrl.isNotEmpty) { final cleanSenderAvatarUrl = senderAvatarUrl.trim().replaceAll('`', ''); message.attributes!['avatarUrl'] = cleanSenderAvatarUrl; } 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('`', '')}', ); } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 添加用户信息到消息扩展字段失败: $e'); } } } /// 从消息扩展字段中解析用户信息并缓存 void _parseUserInfoFromMessageExt(EMMessage message) { try { // 获取发送方用户ID final fromUserId = message.from; if (fromUserId == null || fromUserId.isEmpty) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 消息发送者ID为空,跳过解析用户信息'); } return; } // 从消息扩展字段中获取用户信息 Map? attributes; try { attributes = message.attributes; } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e'); } return; } if (attributes == null || attributes.isEmpty) { if (Get.isLogEnable) { Get.log( '⚠️ [IMManager] 消息扩展字段为空: msgId=${message.msgId}, fromUserId=$fromUserId', ); } return; } if (Get.isLogEnable) { Get.log('🔍 [IMManager] 消息扩展字段内容: $attributes'); } final nickName = attributes['nickName'] as String?; final avatarUrl = attributes['avatarUrl'] as String?; if (nickName == null && avatarUrl == null) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 消息扩展字段中没有用户信息: msgId=${message.msgId}'); } return; } // 缓存用户信息到 ConversationController if (Get.isRegistered()) { final conversationController = Get.find(); final extendedInfo = ExtendedUserInfo( userId: fromUserId, nickName: nickName, avatarUrl: avatarUrl, ); conversationController.cacheUserInfo(fromUserId, extendedInfo); if (Get.isLogEnable) { Get.log( '✅ [IMManager] 从消息扩展字段解析并缓存用户信息: userId=$fromUserId, nickName=$nickName, avatarUrl=$avatarUrl', ); } } else { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] ConversationController 未注册,无法缓存用户信息'); } } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 从消息扩展字段解析用户信息失败: $e'); } } } /// 注册 ChatController void registerChatController(ChatController controller) { _activeChatControllers[controller.userId] = controller; if (Get.isLogEnable) { Get.log('注册 ChatController: ${controller.userId}'); } } /// 注销 ChatController void unregisterChatController(String userId) { _activeChatControllers.remove(userId); if (Get.isLogEnable) { Get.log('注销 ChatController: $userId'); } } /// 通知 ChatController 更新消息列表 void _notifyChatControllers(List messages) { try { // 遍历所有收到的消息 for (var message in messages) { // 只处理接收到的消息(direction == RECEIVE) if (message.direction == MessageDirection.RECEIVE) { // 获取消息的发送者ID(from 属性) final fromId = message.from; if (fromId != null && fromId.isNotEmpty) { // 查找对应的 ChatController final controller = _activeChatControllers[fromId]; if (controller != null) { controller.addReceivedMessage(message); if (Get.isLogEnable) { Get.log('通知 ChatController 更新消息: $fromId'); } } } } } } catch (e) { if (Get.isLogEnable) { Get.log('通知 ChatController 更新消息列表失败: $e'); } } } /// 检查并显示消息通知弹框(如果发送者不是当前正在聊天的用户) void _checkAndShowNotificationDialog(EMMessage message) { try { // 获取消息发送者ID final fromId = message.from; if (fromId == null || fromId.isEmpty) { return; } // 处理视频通话消息(CALL消息)- 显示特殊的视频通话邀请弹框 // 支持新格式的自定义消息和旧格式的文本消息 Map? callInfo; String? callType; String? callStatus; String? channelId; 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'; channelId = params['channelId'] ?? ''; callInfo = {'callType': callType, 'callStatus': callStatus}; } } // 如果解析到通话信息,检查是否需要显示视频通话邀请弹框 if (callInfo != null && callType != null && callStatus != null) { // 只处理视频通话且状态为 missed 或 calling 的消息(新邀请) if ((callType == 'video' || callType == 'voice') && (callStatus == 'waitCalling' || callStatus == 'calling')) { // 检查当前是否正在通话中,如果是则自动拒绝新通话 final callController = CallController.instance; if (callController.currentCall.value != null) { // 当前正在通话中,自动拒绝新通话 if (Get.isLogEnable) { Get.log('📞 [IMManager] 当前正在通话中,自动拒绝新通话邀请: $fromId'); } else { print('📞 [IMManager] 当前正在通话中,自动拒绝新通话邀请: $fromId'); } // 自动拒绝新通话 Future.microtask(() async { try { ChatController? chatController; try { final tag = 'chat_$fromId'; if (Get.isRegistered(tag: tag)) { chatController = Get.find(tag: tag); } } catch (e) { // 忽略错误,chatController 可以为 null } await callController.rejectCall( message: message, chatController: chatController, ); if (Get.isLogEnable) { Get.log('✅ [IMManager] 已自动拒绝新通话邀请'); } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 自动拒绝新通话失败: $e'); } } }); // 不显示通话邀请弹框,直接返回 return; } // 获取用户信息 Map? attributes; try { attributes = message.attributes; } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e'); } } 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()) { final conversationController = Get.find(); 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'); } } } final finalNickName = nickName ?? fromId; final finalAvatarUrl = avatarUrl ?? ''; // 接收方收到视频通话时,开始播放来电铃声 callController.startCallAudio(); // 显示视频通话邀请弹框 SmartDialog.show( tag: 'video_call_invite_dialog', builder: (context) { return VideoCallInviteDialog( callType: callType, avatarUrl: finalAvatarUrl, nickName: finalNickName, onTap: () async { // 关闭弹框 SmartDialog.dismiss(); // 停止播放来电铃声 callController.stopCallAudio(); callController.callRole = CallRole.callee; // 只跳转到视频通话页面,不自动接通 Get.to( () => VideoCallPage( targetUserId: fromId, isInitiator: false, callType: callType, channelId: channelId, callMessage: message, ), ); }, onAccept: () async { // 关闭弹框 SmartDialog.dismiss(); // 停止播放来电铃声(acceptCall 中也会停止,但这里提前停止以更快响应) callController.stopCallAudio(); // 接听通话 ChatController? chatController; try { final tag = 'chat_$fromId'; if (Get.isRegistered(tag: tag)) { chatController = Get.find(tag: tag); } } catch (e) { if (Get.isLogEnable) { 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, ), ); } }, onReject: () async { // 先关闭弹框 SmartDialog.dismiss(); // 停止播放来电铃声(rejectCall 中也会停止,但这里提前停止以更快响应) callController.stopCallAudio(); // 拒绝通话(会修改消息状态为 rejected) ChatController? chatController; try { final tag = 'chat_$fromId'; if (Get.isRegistered(tag: tag)) { chatController = Get.find(tag: tag); } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 获取ChatController失败: $e'); } } // 调用拒绝通话,会使用 modifyMessage 修改消息状态 await callController.rejectCall( message: message, chatController: chatController, ); }, ); }, 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'); } } // 对于所有 CALL 消息(包括视频和语音),都不显示普通消息通知弹框 return; } } catch (e) { // 解析失败,继续处理普通消息 if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 解析CALL消息失败: $e'); } } // 检查发送者是否是当前正在聊天的用户 // 如果 _activeChatControllers 中包含该用户ID,说明当前正在和该用户聊天 if (_activeChatControllers.containsKey(fromId)) { // 是当前正在聊天的用户,不显示弹框 return; } // 获取消息内容 String messageContent = _getMessageContent(message); if (messageContent.isEmpty) { return; } // 从消息扩展字段中获取用户信息(头像、昵称) Map? attributes; try { attributes = message.attributes; } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $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()) { final conversationController = Get.find(); 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'); } } } // 使用默认值 final finalNickName = nickName ?? fromId; final finalAvatarUrl = avatarUrl ?? ''; // 将消息加入队列 _notificationQueue.add( _NotificationMessage( fromId: fromId, nickName: finalNickName, avatarUrl: finalAvatarUrl, messageContent: messageContent, ), ); if (Get.isLogEnable) { Get.log( '✅ [IMManager] 消息已加入通知队列: fromId=$fromId, nickName=$finalNickName, 队列长度=${_notificationQueue.length}', ); } // 如果当前没有弹框显示,立即显示队列中的第一条消息 if (!_isShowingNotification) { _showNextNotification(); } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 处理消息通知失败: $e'); } } } /// 显示队列中的下一条消息通知 void _showNextNotification() { // 如果队列为空或正在显示弹框,直接返回 if (_notificationQueue.isEmpty || _isShowingNotification) { return; } // 从队列中取出第一条消息 final notification = _notificationQueue.removeAt(0); _isShowingNotification = true; // 显示弹框(从上方弹出) SmartDialog.show( builder: (context) { return MessageNotificationDialog( avatarUrl: notification.avatarUrl, nickName: notification.nickName, messageContent: notification.messageContent, onTap: () { // 点击弹框时关闭弹框 SmartDialog.dismiss(); _onNotificationDismissed(); // 跳转到聊天页面 Get.to( () => ChatPage( userId: notification.fromId, userData: null, // userData 可选,ChatPage 会自己处理 ), ); }, ); }, alignment: Alignment.topCenter, animationType: SmartAnimationType.centerFade_otherSlide, animationTime: Duration(milliseconds: 300), usePenetrate: false, clickMaskDismiss: true, backDismiss: true, keepSingle: true, // 保持只有一个弹框显示 maskColor: Colors.transparent, // 透明背景 ); // 3秒后自动关闭弹框 Future.delayed(Duration(seconds: 3), () { if (_isShowingNotification) { SmartDialog.dismiss(); // 延迟一小段时间后调用,确保弹框关闭动画完成 Future.delayed(Duration(milliseconds: 300), () { _onNotificationDismissed(); }); } }); } /// 弹框关闭时的处理 void _onNotificationDismissed() { if (!_isShowingNotification) { return; } _isShowingNotification = false; // 延迟一小段时间后显示下一条消息(确保动画完成) Future.delayed(Duration(milliseconds: 300), () { if (!_isShowingNotification) { _showNextNotification(); } }); } /// 获取消息内容文本 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('⚠️ [IMManager] 解析通话消息类型失败: $e'); } } return '[通话消息]'; } return '[自定义消息]'; } } catch (e) { if (Get.isLogEnable) { Get.log('⚠️ [IMManager] 获取消息内容失败: $e'); } } return ''; } /// 刷新会话列表 void _refreshConversationList() { try { // 尝试获取 ConversationController 并刷新会话列表 if (Get.isRegistered()) { final conversationController = Get.find(); conversationController.refreshConversations(); } } catch (e) { // ConversationController 可能未注册,忽略错误 if (Get.isLogEnable) { Get.log('刷新会话列表失败: $e'); } } } /// 添加用户到黑名单 Future addToBlacklist(String userId) async { try { await EMClient.getInstance.contactManager.addUserToBlockList(userId); print('已将用户 $userId 添加到黑名单'); } catch (e) { print('添加黑名单失败: $e'); rethrow; } } /// 从黑名单移除用户 Future removeFromBlacklist(String userId) async { try { await EMClient.getInstance.contactManager.removeUserFromBlockList(userId); print('已将用户 $userId 从黑名单移除'); } catch (e) { print('移除黑名单失败: $e'); rethrow; } } /// 获取黑名单列表 Future> getBlacklist() async { try { final list = await EMClient.getInstance.contactManager .getBlockListFromServer(); print('获取黑名单列表成功,共 ${list.length} 个用户'); return list; } catch (e) { print('获取黑名单列表失败: $e'); return []; } } /// 撤回消息 Future recallMessage(EMMessage message) async { try { final messageId = message.msgId; if (messageId.isEmpty) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 消息ID为空,无法撤回'); } return false; } await EMClient.getInstance.chatManager.recallMessage(messageId); if (Get.isLogEnable) { Get.log('✅ [IMManager] 消息撤回成功: $messageId'); } // 刷新会话列表 _refreshConversationList(); return true; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 消息撤回失败: $e'); } return false; } } /// 删除消息 Future deleteMessage( String conversationId, String messageId, { bool deleteRemote = false, }) async { try { final conversation = await EMClient.getInstance.chatManager .getConversation( conversationId, type: EMConversationType.Chat, createIfNeed: false, ); if (conversation != null) { await conversation.deleteMessage(messageId); if (Get.isLogEnable) { Get.log('✅ [IMManager] 消息删除成功: $messageId'); } // 刷新会话列表 _refreshConversationList(); return true; } return false; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 消息删除失败: $e'); } return false; } } /// 标记消息为已读 Future markMessageAsRead( String conversationId, String messageId, ) async { try { final conversation = await EMClient.getInstance.chatManager .getConversation( conversationId, type: EMConversationType.Chat, createIfNeed: false, ); if (conversation != null) { await conversation.markMessageAsRead(messageId); if (Get.isLogEnable) { Get.log('✅ [IMManager] 消息标记为已读: $messageId'); } // 刷新会话列表 _refreshConversationList(); return true; } return false; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 标记消息为已读失败: $e'); } return false; } } /// 标记会话所有消息为已读 Future markAllMessagesAsRead(String conversationId) async { try { final conversation = await EMClient.getInstance.chatManager .getConversation( conversationId, type: EMConversationType.Chat, createIfNeed: false, ); if (conversation != null) { await conversation.markAllMessagesAsRead(); if (Get.isLogEnable) { Get.log('✅ [IMManager] 会话所有消息标记为已读: $conversationId'); } // 刷新会话列表 _refreshConversationList(); return true; } return false; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 标记会话所有消息为已读失败: $e'); } return false; } } /// 重发消息 Future resendMessage(EMMessage failedMessage) async { try { final targetId = failedMessage.to ?? failedMessage.conversationId; if (targetId == null || targetId.isEmpty) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 重发消息失败:目标ID为空'); } return null; } EMMessage? newMessage; // 根据消息类型重新创建消息 switch (failedMessage.body.type) { case MessageType.TXT: final textBody = failedMessage.body as EMTextMessageBody; newMessage = EMMessage.createTxtSendMessage( targetId: targetId, content: textBody.content, ); break; case MessageType.IMAGE: final imageBody = failedMessage.body as EMImageMessageBody; final localPath = imageBody.localPath; if (localPath.isEmpty) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 重发图片消息失败:本地路径为空'); } return null; } newMessage = EMMessage.createImageSendMessage( targetId: targetId, filePath: localPath, sendOriginalImage: false, ); break; case MessageType.VOICE: final voiceBody = failedMessage.body as EMVoiceMessageBody; final localPath = voiceBody.localPath; if (localPath.isEmpty) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 重发语音消息失败:本地路径为空'); } return null; } newMessage = EMMessage.createVoiceSendMessage( targetId: targetId, filePath: localPath, duration: voiceBody.duration, ); break; case MessageType.VIDEO: final videoBody = failedMessage.body as EMVideoMessageBody; final localPath = videoBody.localPath; if (localPath.isEmpty) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 重发视频消息失败:本地路径为空'); } return null; } newMessage = EMMessage.createVideoSendMessage( targetId: targetId, filePath: localPath, duration: videoBody.duration ?? 0, thumbnailLocalPath: videoBody.thumbnailLocalPath, ); break; default: if (Get.isLogEnable) { Get.log('❌ [IMManager] 不支持重发该类型的消息: ${failedMessage.body.type}'); } return null; } // 在消息扩展字段中添加当前用户信息(重要:确保重发的消息也包含用户信息) _addUserInfoToMessageExt(newMessage); // 重新发送消息 final result = await EMClient.getInstance.chatManager.sendMessage( newMessage, ); if (Get.isLogEnable) { Get.log('✅ [IMManager] 消息重发成功: ${newMessage.msgId}'); } return result; } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 重发消息失败: $e'); } return null; } } /// 清理资源 void dispose() { try { if (_listenersRegistered) { EMClient.getInstance.removeConnectionEventHandler( _connectionHandlerKey, ); EMClient.getInstance.chatManager.removeEventHandler(_chatHandlerKey); _listenersRegistered = false; if (Get.isLogEnable) { Get.log('✅ [IMManager] 资源清理完成'); } } } catch (e) { if (Get.isLogEnable) { Get.log('❌ [IMManager] 清理资源失败: $e'); } } } } // 导出单例实例 final IMManager imManager = IMManager();