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.
493 lines
18 KiB
493 lines
18 KiB
import 'package:get/get.dart';
|
|
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
|
|
import '../../im/im_manager.dart';
|
|
import '../../model/mine/user_base_data.dart';
|
|
|
|
// 扩展类用于存储用户信息(包括业务系统的信息)
|
|
class ExtendedUserInfo {
|
|
final String userId;
|
|
final String? nickName;
|
|
final String? avatarUrl;
|
|
|
|
ExtendedUserInfo({
|
|
required this.userId,
|
|
this.nickName,
|
|
this.avatarUrl,
|
|
});
|
|
|
|
// 从 EMUserInfo 创建
|
|
factory ExtendedUserInfo.fromEMUserInfo(EMUserInfo emUserInfo) {
|
|
return ExtendedUserInfo(
|
|
userId: emUserInfo.userId ?? '',
|
|
nickName: emUserInfo.nickName,
|
|
avatarUrl: emUserInfo.avatarUrl,
|
|
);
|
|
}
|
|
|
|
// 从 UserBaseData 创建
|
|
factory ExtendedUserInfo.fromUserBaseData(UserBaseData userBaseData, {String? avatarUrl}) {
|
|
return ExtendedUserInfo(
|
|
userId: userBaseData.userId,
|
|
nickName: userBaseData.nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ConversationController extends GetxController {
|
|
// 会话列表数据
|
|
final conversations = <EMConversation>[].obs;
|
|
// 加载状态
|
|
final isLoading = false.obs;
|
|
// 错误消息
|
|
final errorMessage = ''.obs;
|
|
|
|
// 用户信息缓存(userId -> ExtendedUserInfo)
|
|
final Map<String, ExtendedUserInfo> _userInfoCache = {};
|
|
|
|
/// 缓存用户信息(公开方法,供 ChatController 调用)
|
|
void cacheUserInfo(String userId, ExtendedUserInfo userInfo) {
|
|
_userInfoCache[userId] = userInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 已缓存用户信息: userId=$userId, nickName=${userInfo.nickName}');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
// 初始化时检查 IM 登录状态,如果已登录则加载会话列表
|
|
_checkAndLoadConversations();
|
|
}
|
|
|
|
/// 检查 IM 登录状态并加载会话列表
|
|
Future<void> _checkAndLoadConversations() async {
|
|
// 如果已登录,直接加载
|
|
if (IMManager.instance.isLoggedIn) {
|
|
await loadConversations();
|
|
return;
|
|
}
|
|
|
|
// 如果未登录,等待登录完成(最多等待 10 秒)
|
|
if (Get.isLogEnable) {
|
|
Get.log('⏳ [ConversationController] IM 未登录,等待登录完成...');
|
|
}
|
|
|
|
int retryCount = 0;
|
|
const maxRetries = 20; // 最多重试 20 次(每次 500ms,总共 10 秒)
|
|
|
|
while (retryCount < maxRetries && !IMManager.instance.isLoggedIn) {
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
retryCount++;
|
|
}
|
|
|
|
if (IMManager.instance.isLoggedIn) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] IM 已登录,开始加载会话列表');
|
|
}
|
|
await loadConversations();
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] IM 登录超时,显示错误提示');
|
|
}
|
|
errorMessage.value = 'IM 未登录,请稍后重试';
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/// 加载会话列表
|
|
Future<void> loadConversations() async {
|
|
if (isLoading.value) return;
|
|
|
|
// 检查 IM 登录状态
|
|
if (!IMManager.instance.isLoggedIn) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] IM 未登录,无法加载会话列表');
|
|
}
|
|
errorMessage.value = 'IM 未登录,无法加载会话列表';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
isLoading.value = true;
|
|
errorMessage.value = '';
|
|
|
|
// 从IMManager获取会话列表
|
|
final List<EMConversation> convList = await IMManager.instance
|
|
.getConversations();
|
|
// 更新会话列表
|
|
conversations.value = convList;
|
|
|
|
// 从所有会话的历史消息中提取用户信息并缓存(应用重启后恢复用户信息)
|
|
_extractUserInfoFromConversations(convList);
|
|
|
|
// 使用GetX日志系统
|
|
if (Get.isLogEnable) {
|
|
Get.log('Loaded ${convList.length} conversations');
|
|
}
|
|
} catch (e) {
|
|
// 使用GetX日志系统
|
|
if (Get.isLogEnable) {
|
|
Get.log('Failed to load conversations: $e');
|
|
}
|
|
errorMessage.value = '加载会话列表失败,请稍后重试';
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
/// 从所有会话的历史消息中提取用户信息并缓存
|
|
/// 这样应用重启后也能恢复用户信息
|
|
/// 注意:只提取对方(接收到的消息)的用户信息,不提取自己的信息
|
|
Future<void> _extractUserInfoFromConversations(List<EMConversation> convList) async {
|
|
try {
|
|
for (var conversation in convList) {
|
|
try {
|
|
// conversation.id 是对方用户ID
|
|
final targetUserId = conversation.id;
|
|
|
|
// 如果缓存中已有该用户信息,跳过
|
|
if (_userInfoCache.containsKey(targetUserId)) continue;
|
|
|
|
// 获取会话的最新消息(最多获取最近20条)
|
|
final messages = await conversation.loadMessages(
|
|
loadCount: 20,
|
|
);
|
|
|
|
// 从消息中提取用户信息
|
|
// 接收消息:提取发送者信息(sender_ 前缀)
|
|
// 发送消息:提取接收者信息(receiver_ 前缀)
|
|
for (var message in messages) {
|
|
Map<String, dynamic>? attributes;
|
|
try {
|
|
attributes = message.attributes;
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
|
|
if (attributes == null || attributes.isEmpty) {
|
|
continue;
|
|
}
|
|
|
|
if (message.direction == MessageDirection.RECEIVE) {
|
|
// 接收到的消息:从扩展字段中提取发送者信息(sender_ 前缀)
|
|
final fromUserId = message.from;
|
|
if (fromUserId != null && fromUserId == targetUserId) {
|
|
// 优先使用 sender_ 前缀的字段(发送者信息)
|
|
final nickName = attributes['sender_nickName'] as String? ?? attributes['nickName'] as String?;
|
|
final avatarUrl = attributes['sender_avatarUrl'] as String? ?? attributes['avatarUrl'] as String?;
|
|
|
|
if (nickName != null || avatarUrl != null) {
|
|
final extendedInfo = ExtendedUserInfo(
|
|
userId: targetUserId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
_userInfoCache[targetUserId] = extendedInfo;
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从接收消息恢复对方用户信息: userId=$targetUserId, nickName=$nickName');
|
|
}
|
|
// 找到一个就足够了,跳出循环
|
|
break;
|
|
}
|
|
}
|
|
} else if (message.direction == MessageDirection.SEND) {
|
|
// 发送的消息:从扩展字段中提取接收者信息(receiver_ 前缀)
|
|
final toUserId = message.to;
|
|
if (toUserId != null && toUserId == targetUserId) {
|
|
// 使用 receiver_ 前缀的字段(接收者信息)
|
|
final nickName = attributes['receiver_nickName'] as String?;
|
|
final avatarUrl = attributes['receiver_avatarUrl'] as String?;
|
|
|
|
if (nickName != null || avatarUrl != null) {
|
|
final extendedInfo = ExtendedUserInfo(
|
|
userId: targetUserId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
_userInfoCache[targetUserId] = extendedInfo;
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从发送消息恢复对方用户信息: userId=$targetUserId, nickName=$nickName');
|
|
}
|
|
// 找到一个就足够了,跳出循环
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 从会话提取用户信息失败: ${conversation.id}, 错误: $e');
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 批量提取用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 刷新会话列表
|
|
Future<void> refreshConversations() async {
|
|
await loadConversations();
|
|
}
|
|
|
|
/// 清除会话列表和用户信息缓存(用于退出登录时)
|
|
void clearConversations() {
|
|
conversations.clear();
|
|
_userInfoCache.clear();
|
|
errorMessage.value = '';
|
|
isLoading.value = false;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 已清除会话列表和用户信息缓存');
|
|
}
|
|
}
|
|
|
|
/// 获取会话的最新消息
|
|
String getLastMessageContent(EMMessage? message) {
|
|
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 '[位置]';
|
|
}
|
|
return '暂无消息';
|
|
}
|
|
|
|
/// 获取会话的未读消息数量
|
|
Future<int> getUnreadCount(EMConversation conversation) async {
|
|
try {
|
|
// 简化实现,返回0
|
|
return await conversation.unreadCount();
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('Error getting unread count: $e');
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// 格式化消息时间
|
|
String formatMessageTime(int timestamp) {
|
|
DateTime messageTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
|
DateTime now = DateTime.now();
|
|
|
|
// 检查是否是今天
|
|
if (messageTime.year == now.year &&
|
|
messageTime.month == now.month &&
|
|
messageTime.day == now.day) {
|
|
// 今天显示时:分
|
|
return '${messageTime.hour.toString().padLeft(2, '0')}:${messageTime.minute.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
// 检查是否是昨天
|
|
DateTime yesterday = now.subtract(Duration(days: 1));
|
|
if (messageTime.year == yesterday.year &&
|
|
messageTime.month == yesterday.month &&
|
|
messageTime.day == yesterday.day) {
|
|
return '昨天';
|
|
}
|
|
|
|
// 其他日期显示月-日
|
|
return '${messageTime.month.toString().padLeft(2, '0')}-${messageTime.day.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
Future<ExtendedUserInfo?> loadContact(String userId) async{
|
|
try {
|
|
// 1. 先从缓存中查找(优先级最高)
|
|
if (_userInfoCache.containsKey(userId)) {
|
|
final cachedUser = _userInfoCache[userId]!;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从缓存获取到用户信息: $userId, nickName=${cachedUser.nickName}, avatarUrl=${cachedUser.avatarUrl}');
|
|
}
|
|
return cachedUser;
|
|
}
|
|
|
|
// 2. 尝试从会话的历史消息中提取用户信息(从消息扩展字段)
|
|
// 这是最重要的数据源,因为用户信息存储在消息扩展字段中
|
|
// 注意:conversation.id 是对方用户ID,应该从接收到的消息中提取对方的信息
|
|
try {
|
|
final conversation = await EMClient.getInstance.chatManager.getConversation(
|
|
userId,
|
|
type: EMConversationType.Chat,
|
|
createIfNeed: false,
|
|
);
|
|
if (conversation != null) {
|
|
// 获取最近的消息(最多20条),查找包含用户信息的消息
|
|
final messages = await conversation.loadMessages(
|
|
loadCount: 20,
|
|
);
|
|
|
|
// 从消息中查找用户信息(只查找接收到的消息,因为那是对方的用户信息)
|
|
// conversation.id 是对方用户ID,所以应该从接收到的消息中提取对方的信息
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔍 [ConversationController] 开始从会话历史消息查找用户信息: userId=$userId, 消息数量=${messages.length}');
|
|
}
|
|
|
|
// 先打印所有消息的详细信息,用于调试
|
|
if (Get.isLogEnable) {
|
|
for (var msg in messages) {
|
|
final fromId = msg.from;
|
|
final toId = msg.to;
|
|
final direction = msg.direction;
|
|
Map<String, dynamic>? attrs;
|
|
try {
|
|
attrs = msg.attributes;
|
|
} catch (e) {
|
|
attrs = null;
|
|
}
|
|
Get.log(' 📨 消息: msgId=${msg.msgId}, 方向=$direction, from=$fromId, to=$toId, attributes=$attrs');
|
|
}
|
|
}
|
|
|
|
for (var message in messages) {
|
|
Map<String, dynamic>? attributes;
|
|
try {
|
|
attributes = message.attributes;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 无法访问 message.attributes: $e');
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (attributes == null || attributes.isEmpty) {
|
|
continue;
|
|
}
|
|
|
|
if (message.direction == MessageDirection.RECEIVE) {
|
|
// 接收到的消息:从扩展字段中提取发送者信息(sender_ 前缀)
|
|
// conversation.id 是对方用户ID,应该从接收到的消息中提取发送者(对方)的信息
|
|
final fromUserId = message.from;
|
|
if (fromUserId != null && fromUserId == userId) {
|
|
// 优先使用 sender_ 前缀的字段(发送者信息)
|
|
final nickName = attributes['sender_nickName'] as String? ?? attributes['nickName'] as String?;
|
|
final avatarUrl = attributes['sender_avatarUrl'] as String? ?? attributes['avatarUrl'] as String?;
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔍 [ConversationController] 从接收消息提取发送者信息: msgId=${message.msgId}, fromUserId=$fromUserId, nickName=$nickName, avatarUrl=$avatarUrl');
|
|
}
|
|
|
|
if (nickName != null || avatarUrl != null) {
|
|
final extendedInfo = ExtendedUserInfo(
|
|
userId: userId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
_userInfoCache[userId] = extendedInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从接收消息扩展字段获取到对方用户信息: userId=$userId, nickName=$nickName');
|
|
}
|
|
return extendedInfo;
|
|
}
|
|
}
|
|
} else if (message.direction == MessageDirection.SEND) {
|
|
// 发送的消息:从扩展字段中提取接收者信息(receiver_ 前缀)
|
|
// conversation.id 是对方用户ID,应该从发送的消息中提取接收者(对方)的信息
|
|
final toUserId = message.to;
|
|
if (toUserId != null && toUserId == userId) {
|
|
// 使用 receiver_ 前缀的字段(接收者信息)
|
|
final nickName = attributes['receiver_nickName'] as String?;
|
|
final avatarUrl = attributes['receiver_avatarUrl'] as String?;
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔍 [ConversationController] 从发送消息提取接收者信息: msgId=${message.msgId}, toUserId=$toUserId, nickName=$nickName, avatarUrl=$avatarUrl');
|
|
}
|
|
|
|
if (nickName != null || avatarUrl != null) {
|
|
final extendedInfo = ExtendedUserInfo(
|
|
userId: userId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
_userInfoCache[userId] = extendedInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从发送消息扩展字段获取到对方用户信息: userId=$userId, nickName=$nickName');
|
|
}
|
|
return extendedInfo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 在会话历史消息中未找到用户信息: userId=$userId');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 从会话消息提取用户信息失败: $e');
|
|
}
|
|
}
|
|
|
|
// 3. 如果从消息扩展字段获取不到,尝试从环信获取用户信息(作为备选)
|
|
try {
|
|
var data = await IMManager.instance.getContacts(userId);
|
|
var emUserInfo = data[userId];
|
|
|
|
if (emUserInfo != null && (emUserInfo.nickName?.isNotEmpty ?? false)) {
|
|
final extendedInfo = ExtendedUserInfo.fromEMUserInfo(emUserInfo);
|
|
_userInfoCache[userId] = extendedInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从环信获取到用户信息: $userId, nickName=${extendedInfo.nickName}');
|
|
}
|
|
return extendedInfo;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 从环信获取用户信息失败: $e');
|
|
}
|
|
}
|
|
|
|
// 4. 如果都获取不到,返回一个基本的 ExtendedUserInfo(至少显示用户ID)
|
|
final fallbackInfo = ExtendedUserInfo(userId: userId);
|
|
_userInfoCache[userId] = fallbackInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 未从任何来源获取到用户信息,使用默认值: $userId');
|
|
}
|
|
return fallbackInfo;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ConversationController] 获取用户信息失败: $userId, 错误: $e');
|
|
}
|
|
// 即使出错也返回一个基本的 ExtendedUserInfo
|
|
final fallbackInfo = ExtendedUserInfo(userId: userId);
|
|
_userInfoCache[userId] = fallbackInfo;
|
|
return fallbackInfo;
|
|
}
|
|
}
|
|
|
|
Future<EMMessage?> lastMessage(EMConversation conversation) async{
|
|
return await conversation.latestMessage();
|
|
}
|
|
|
|
/// 删除会话
|
|
Future<bool> deleteConversation(String conversationId) async {
|
|
try {
|
|
final success = await IMManager.instance.deleteConversation(
|
|
conversationId,
|
|
);
|
|
if (success) {
|
|
conversations.removeWhere((element) => element.id == conversationId);
|
|
}
|
|
return success;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('删除会话失败: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|