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.
1193 lines
38 KiB
1193 lines
38 KiB
import 'dart:io';
|
|
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 '../controller/message/conversation_controller.dart';
|
|
import '../controller/message/chat_controller.dart';
|
|
import '../controller/global.dart';
|
|
|
|
// 完整的IM管理器实现,使用实际的SDK类型和方法
|
|
class IMManager {
|
|
// 单例模式
|
|
static final IMManager _instance = IMManager._internal();
|
|
factory IMManager() => _instance;
|
|
final storage = GetStorage();
|
|
// 静态getter用于instance访问
|
|
static IMManager get instance => _instance;
|
|
|
|
bool _isInitialized = false;
|
|
bool _listenersRegistered = false;
|
|
bool _isLoggedIn = false;
|
|
|
|
// 监听器标识符
|
|
static const String _connectionHandlerKey = 'im_manager_connection_handler';
|
|
static const String _chatHandlerKey = 'im_manager_chat_handler';
|
|
|
|
// 存储活跃的 ChatController 实例,key 为 userId
|
|
final Map<String, ChatController> _activeChatControllers = {};
|
|
|
|
IMManager._internal() {
|
|
print('IMManager instance created');
|
|
}
|
|
|
|
/// 初始化IM SDK
|
|
Future<bool> initialize(String appKey) async {
|
|
try {
|
|
if (_isInitialized) {
|
|
print('IM SDK already initialized');
|
|
return true;
|
|
}
|
|
|
|
// 创建EMOptions实例
|
|
final options = EMOptions(
|
|
appKey: appKey,
|
|
autoLogin: false,
|
|
acceptInvitationAlways: false,
|
|
);
|
|
|
|
// 初始化SDK
|
|
await EMClient.getInstance.init(options);
|
|
|
|
_isInitialized = true;
|
|
print('IM SDK initialized successfully');
|
|
return true;
|
|
} catch (e) {
|
|
print('Failed to initialize IM SDK: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 注册监听器
|
|
void _registerListeners() {
|
|
try {
|
|
// 防止重复注册
|
|
if (_listenersRegistered) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('监听器已注册,跳过重复注册');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 连接监听器
|
|
EMClient.getInstance.addConnectionEventHandler(
|
|
_connectionHandlerKey,
|
|
EMConnectionEventHandler(
|
|
onConnected: () {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [IMManager] 已连接到IM服务器');
|
|
}
|
|
// 连接成功后,通知 ConversationController 加载会话列表
|
|
_refreshConversationList();
|
|
},
|
|
onDisconnected: () {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [IMManager] 与IM服务器断开连接');
|
|
}
|
|
// TODO: 可以在这里添加自动重连逻辑
|
|
},
|
|
onTokenDidExpire: () {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [IMManager] IM token即将过期');
|
|
}
|
|
// TODO: 可以在这里添加自动刷新token的逻辑
|
|
},
|
|
onUserKickedByOtherDevice: () {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [IMManager] 用户被其他设备踢出');
|
|
}
|
|
// TODO: 可以在这里添加处理逻辑,如跳转到登录页
|
|
},
|
|
),
|
|
);
|
|
|
|
// 消息监听器
|
|
EMClient.getInstance.chatManager.addEventHandler(
|
|
_chatHandlerKey,
|
|
EMChatEventHandler(
|
|
onMessagesReceived: (messages) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('📨 [IMManager] 收到 ${messages.length} 条新消息');
|
|
}
|
|
// 从消息扩展字段中解析用户信息并缓存
|
|
for (var message in messages) {
|
|
if (message.direction == MessageDirection.RECEIVE) {
|
|
_parseUserInfoFromMessageExt(message);
|
|
}
|
|
}
|
|
// 收到新消息时,更新会话列表
|
|
_refreshConversationList();
|
|
// 通知对应的 ChatController 更新消息列表
|
|
_notifyChatControllers(messages);
|
|
},
|
|
onCmdMessagesReceived: (cmdMessages) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('📨 [IMManager] 收到 ${cmdMessages.length} 条CMD消息');
|
|
}
|
|
// 处理CMD消息(如撤回消息等)
|
|
for (var msg in cmdMessages) {
|
|
_handleCmdMessage(msg);
|
|
}
|
|
},
|
|
),
|
|
);
|
|
EMClient.getInstance.chatManager.addMessageEvent(_chatHandlerKey, ChatMessageEvent(
|
|
onSuccess: (str, message){
|
|
Get.log('✅ [IMManager] 发送消息成功: $str');
|
|
},
|
|
onError: (str, message, err){
|
|
Get.log('❌ [IMManager] 发送消息失败: $err----$str');
|
|
},
|
|
));
|
|
_listenersRegistered = true;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [IMManager] 监听器注册成功');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [IMManager] 注册监听器失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 登录IM服务
|
|
Future<bool> login(String token) async {
|
|
try {
|
|
if (!_isInitialized) {
|
|
print('IM SDK not initialized');
|
|
return false;
|
|
}
|
|
var userId = storage.read('userId');
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔐 [IMManager] 准备登录IM,userId: $userId');
|
|
} else {
|
|
print('🔐 [IMManager] 准备登录IM,userId: $userId');
|
|
}
|
|
await EMClient.getInstance.logout();
|
|
await EMClient.getInstance.loginWithToken(userId, token);
|
|
// 注册监听器
|
|
_registerListeners();
|
|
// 设置登录状态
|
|
_isLoggedIn = true;
|
|
// 登录成功后,通知 ConversationController 刷新会话列表
|
|
_refreshConversationList();
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [IMManager] IM登录成功,userId: $userId');
|
|
} else {
|
|
print('✅ [IMManager] IM登录成功,userId: $userId');
|
|
}
|
|
return true;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [IMManager] IM登录失败: $e');
|
|
} else {
|
|
print('❌ [IMManager] IM登录失败: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 登出IM服务
|
|
Future<bool> logout() async {
|
|
try {
|
|
await EMClient.getInstance.logout();
|
|
// 清除登录状态
|
|
_isLoggedIn = false;
|
|
print('IM logout successful');
|
|
return true;
|
|
} catch (e) {
|
|
print('IM logout failed: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 检查是否已登录
|
|
bool get isLoggedIn => _isLoggedIn;
|
|
|
|
/// 检查用户是否存在于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,
|
|
);
|
|
|
|
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,
|
|
]);
|
|
}
|
|
|
|
/// 获取指定会话的消息记录
|
|
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;
|
|
}
|
|
}
|
|
|
|
// ========== 设置消息扩展字段 ==========
|
|
// 同时存储发送者和接收者的信息
|
|
// 发送者信息:用于接收方在聊天列表显示发送者的头像和昵称
|
|
// 接收者信息:用于发送方在聊天列表显示接收者的头像和昵称
|
|
if (message.attributes == null) {
|
|
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;
|
|
}
|
|
|
|
// 接收者信息(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 _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 [];
|
|
}
|
|
}
|
|
|
|
/// 处理CMD消息
|
|
void _handleCmdMessage(EMMessage cmdMessage) {
|
|
try {
|
|
// 这里可以处理各种CMD消息,如撤回消息等
|
|
if (Get.isLogEnable) {
|
|
Get.log('处理CMD消息: ${cmdMessage.msgId}');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('处理CMD消息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 注意:以下方法保留用于未来扩展,当SDK支持更多回调时可以使用
|
|
// 目前环信SDK的EMChatEventHandler可能不支持这些回调,所以暂时注释掉
|
|
/*
|
|
/// 通知消息已读
|
|
void _notifyMessageRead(List<EMMessage> messages, String from, String to) {
|
|
try {
|
|
// 找到对应的 ChatController 并通知消息已读
|
|
final controller = _activeChatControllers[from];
|
|
if (controller != null) {
|
|
// TODO: 在 ChatController 中添加更新消息已读状态的方法
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息已读: $from');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息已读失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知消息已送达
|
|
void _notifyMessageDelivered(List<EMMessage> messages, String to) {
|
|
try {
|
|
// 找到对应的 ChatController 并通知消息已送达
|
|
for (var message in messages) {
|
|
final targetId = message.to;
|
|
if (targetId != null) {
|
|
final controller = _activeChatControllers[targetId];
|
|
if (controller != null) {
|
|
// TODO: 在 ChatController 中添加更新消息送达状态的方法
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息已送达: $targetId');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息已送达失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知消息撤回
|
|
void _notifyMessageRecalled(List<EMMessage> messages) {
|
|
try {
|
|
// 通知所有相关的 ChatController 更新消息列表
|
|
for (var message in messages) {
|
|
final fromId = message.from;
|
|
final toId = message.to;
|
|
|
|
// 通知发送方的 ChatController
|
|
if (fromId != null) {
|
|
final controller = _activeChatControllers[fromId];
|
|
if (controller != null) {
|
|
// TODO: 在 ChatController 中添加处理消息撤回的方法
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息撤回(发送方): $fromId');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 通知接收方的 ChatController
|
|
if (toId != null) {
|
|
final controller = _activeChatControllers[toId];
|
|
if (controller != null) {
|
|
// TODO: 在 ChatController 中添加处理消息撤回的方法
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息撤回(接收方): $toId');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 刷新会话列表
|
|
_refreshConversationList();
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息撤回失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通知消息状态变更
|
|
void _notifyMessageStatusChanged(EMMessage message, MessageStatus status, EMError? error) {
|
|
try {
|
|
final targetId = message.to;
|
|
if (targetId != null) {
|
|
final controller = _activeChatControllers[targetId];
|
|
if (controller != null) {
|
|
// TODO: 在 ChatController 中添加更新消息状态的方法
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息状态变更: $targetId, status=$status');
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('通知消息状态变更失败: $e');
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
/// 撤回消息
|
|
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;
|
|
}
|
|
|
|
// 重新发送消息
|
|
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();
|