You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2555 lines
91 KiB

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<void>? _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<String, ChatController> _activeChatControllers = {};
/// 获取当前正在聊天的用户ID列表
Set<String> getActiveChatUserIds() {
return _activeChatControllers.keys.toSet();
}
// 存储 Presence 状态变化回调,key 为 userId
final Map<String, Function(bool)> _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<void> ensureInitialized({required String appKey}) async {
// 如果已经初始化,直接返回
if (_initialized) {
debugPrint('✅ IM 已初始化,无需重复初始化');
return;
}
// 如果正在初始化,等待完成
if (_initCompleter != null) {
debugPrint('🟡 IM 初始化中,等待完成...');
return _initCompleter!.future;
}
_initCompleter = Completer<void>();
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<ConversationController>()) {
final conversationController =
Get.find<ConversationController>();
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<ConversationController>()) {
final conversationController =
Get.find<ConversationController>();
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<EMPresence> 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<bool> 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<bool> 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<void> 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<UserApi>()) {
if (Get.isLogEnable) {
Get.log('❌ [IMManager] UserApi 未注册,无法获取token重连');
}
_isReconnecting = false;
return;
}
final userApi = Get.find<UserApi>();
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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
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<bool> 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<EMMessage?> 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<EMMessage?> 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<EMMessage?> 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<EMMessage?> 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<EMMessage?> sendCustomMessage(
String targetId,
String event,
Map<String, String>? data,
) async {
final customMsg = EMMessage.createCustomSendMessage(
targetId: targetId,
// `event` 为需要传递的自定义消息事件,比如礼物消息,可以设置:
event: event,
// `params` 类型为 `Map<String, String>`。
params: data,
);
// 在消息扩展字段中添加当前用户信息
_addUserInfoToMessageExt(customMsg);
return await EMClient.getInstance.chatManager.sendMessage(customMsg);
}
/// 获取所有联系人
Future<List<EMContact>> getAllContacts() async {
return await EMClient.getInstance.contactManager.fetchAllContacts();
}
/// 获取会话列表
Future<List<EMConversation>> getConversations() async {
return await EMClient.getInstance.chatManager.loadAllConversations();
}
/// 获取用户信息(单个用户)
/// 删除指定会话
Future<bool> 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<Map<String, EMUserInfo>> getContacts(String userId) async {
return await EMClient.getInstance.userInfoManager.fetchUserInfoById([
userId,
]);
}
/// 订阅用户在线状态(用于实时接收状态变化)
/// [userId] 用户ID
/// [callback] 状态变化回调函数
Future<bool> 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<bool> 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<bool?> 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<List<EMMessage?>> 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<EMMessage?> 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<EMUserInfo?> 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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
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<String, dynamic>? 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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
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<EMMessage> 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<String, dynamic>? 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<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(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<String, dynamic>? 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<ConversationController>()) {
final conversationController =
Get.find<ConversationController>();
final cachedUserInfo = conversationController
.getCachedUserInfo(fromId);
if (cachedUserInfo != null) {
nickName = nickName ?? cachedUserInfo.nickName;
avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl;
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 从ConversationController获取用户信息失败: $e');
}
}
}
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<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(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<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(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<String, dynamic>? 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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
final cachedUserInfo = conversationController.getCachedUserInfo(
fromId,
);
if (cachedUserInfo != null) {
nickName = nickName ?? cachedUserInfo.nickName;
avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl;
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 从ConversationController获取用户信息失败: $e');
}
}
}
// 使用默认值
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<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
conversationController.refreshConversations();
}
} catch (e) {
// ConversationController 可能未注册,忽略错误
if (Get.isLogEnable) {
Get.log('刷新会话列表失败: $e');
}
}
}
/// 添加用户到黑名单
Future<void> addToBlacklist(String userId) async {
try {
await EMClient.getInstance.contactManager.addUserToBlockList(userId);
print('已将用户 $userId 添加到黑名单');
} catch (e) {
print('添加黑名单失败: $e');
rethrow;
}
}
/// 从黑名单移除用户
Future<void> removeFromBlacklist(String userId) async {
try {
await EMClient.getInstance.contactManager.removeUserFromBlockList(userId);
print('已将用户 $userId 从黑名单移除');
} catch (e) {
print('移除黑名单失败: $e');
rethrow;
}
}
/// 获取黑名单列表
Future<List<String>> getBlacklist() async {
try {
final list = await EMClient.getInstance.contactManager
.getBlockListFromServer();
print('获取黑名单列表成功,共 ${list.length} 个用户');
return list;
} catch (e) {
print('获取黑名单列表失败: $e');
return [];
}
}
/// 撤回消息
Future<bool> 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<bool> 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<bool> 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<bool> 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<EMMessage?> 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();