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.
 
 
 
 
 

837 lines
25 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';
// 完整的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;
// 监听器标识符
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服务器');
}
},
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} 条新消息');
}
// 收到新消息时,更新会话列表
_refreshConversationList();
// 通知对应的 ChatController 更新消息列表
_notifyChatControllers(messages);
},
onCmdMessagesReceived: (cmdMessages) {
if (Get.isLogEnable) {
Get.log('📨 [IMManager] 收到 ${cmdMessages.length} 条CMD消息');
}
// 处理CMD消息(如撤回消息等)
for (var msg in cmdMessages) {
_handleCmdMessage(msg);
}
},
),
);
_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');
await EMClient.getInstance.logout();
await EMClient.getInstance.loginWithToken(userId, token);
// 注册监听器
_registerListeners();
print('IM login successful');
return true;
} catch (e) {
print('IM login failed: $e');
return false;
}
}
/// 登出IM服务
Future<bool> logout() async {
try {
await EMClient.getInstance.logout();
print('IM logout successful');
return true;
} catch (e) {
print('IM logout failed: $e');
return false;
}
}
/// 发送文本消息
Future<EMMessage?> sendTextMessage(
String content,
String toChatUsername,
) async {
print('Text message sent');
try {
// 创建文本消息
final message = EMMessage.createTxtSendMessage(
targetId: toChatUsername,
content: content,
);
print('Text message sent successfully');
return await EMClient.getInstance.chatManager.sendMessage(message);
} catch (e) {
print('Failed to send text message: $e');
return null;
}
}
/// 发送语音消息
Future<EMMessage?> sendVoiceMessage(
String filePath,
String toChatUsername,
int duration,
) async {
try {
// 创建语音消息
final message = EMMessage.createVoiceSendMessage(
targetId: toChatUsername,
filePath: filePath,
duration: duration,
);
// 发送消息
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,
);
// 发送消息
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, // 🎯 指定缩略图路径
);
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<List<EMConversation>> getConversations() async {
return 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,
}) 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 [];
}
// 如果有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 [];
}
}
/// 获取用户信息
Future<EMUserInfo?> getUserInfo(String userId) async {
var data = await EMClient.getInstance.userInfoManager.fetchUserInfoById([
userId,
]);
return data[userId];
}
/// 注册 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();