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.
928 lines
34 KiB
928 lines
34 KiB
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
|
|
import 'package:app_badge_plus/app_badge_plus.dart';
|
|
import '../../im/im_manager.dart';
|
|
import '../../model/mine/user_base_data.dart';
|
|
import '../mine/user_controller.dart';
|
|
import 'chat_controller.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,
|
|
);
|
|
}
|
|
}
|
|
|
|
// 筛选类型枚举
|
|
enum FilterType {
|
|
none, // 无筛选
|
|
lastChatTime, // 最后聊天时间
|
|
unread, // 未读消息
|
|
online, // 当前在线
|
|
}
|
|
|
|
class ConversationController extends GetxController {
|
|
// 会话列表数据
|
|
final conversations = <EMConversation>[].obs;
|
|
// 加载状态
|
|
final isLoading = false.obs;
|
|
// 错误消息
|
|
final errorMessage = ''.obs;
|
|
|
|
// 用户信息缓存(userId -> ExtendedUserInfo)
|
|
final Map<String, ExtendedUserInfo> _userInfoCache = {};
|
|
|
|
// 筛选类型
|
|
final filterType = FilterType.none.obs;
|
|
|
|
// 总未读数
|
|
final totalUnreadCount = 0.obs;
|
|
|
|
// 防抖定时器,用于避免频繁刷新会话列表
|
|
Timer? _refreshDebounceTimer;
|
|
|
|
|
|
final FocusNode blankFocusNode = FocusNode();
|
|
final searchController = TextEditingController().obs;
|
|
final name = "".obs;
|
|
|
|
final showSearch = false.obs;
|
|
|
|
|
|
|
|
void onTextChanged(String text) {
|
|
name.value = text;
|
|
}
|
|
|
|
search(){
|
|
if(name.value == ""){
|
|
_checkAndLoadConversations();
|
|
} else {
|
|
List<ExtendedUserInfo> list = _userInfoCache.values.where((e) => e.nickName!.contains(name.value)).toList();
|
|
List<EMConversation> result = conversations.where((a) {
|
|
return list.any((b) => b.userId == a.id);
|
|
}).toList();
|
|
conversations.value = result;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// 缓存用户信息(公开方法,供 ChatController 调用)
|
|
void cacheUserInfo(String userId, ExtendedUserInfo userInfo) {
|
|
_userInfoCache[userId] = userInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 已缓存用户信息: userId=$userId, nickName=${userInfo.nickName}');
|
|
}
|
|
}
|
|
|
|
/// 从缓存获取用户信息(公开方法,供 UI 调用)
|
|
ExtendedUserInfo? getCachedUserInfo(String userId) {
|
|
return _userInfoCache[userId];
|
|
}
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
// 监听总未读数变化,同步更新应用角标
|
|
totalUnreadCount.listen((count) {
|
|
_updateAppBadge(count);
|
|
});
|
|
// 初始化时检查 IM 登录状态,如果已登录则加载会话列表
|
|
_checkAndLoadConversations();
|
|
}
|
|
|
|
/// 更新应用角标
|
|
Future<void> _updateAppBadge(int count) async {
|
|
try {
|
|
await AppBadgePlus.updateBadge(count);
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 应用角标已更新: $count');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 更新应用角标失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 检查 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({bool showLoading = true}) async {
|
|
if (isLoading.value && showLoading) return;
|
|
|
|
// 检查 IM 登录状态
|
|
if (!IMManager.instance.isLoggedIn) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] IM 未登录,无法加载会话列表');
|
|
}
|
|
errorMessage.value = 'IM 未登录,无法加载会话列表';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 只有在需要显示加载状态时才设置
|
|
if (showLoading) {
|
|
isLoading.value = true;
|
|
}
|
|
errorMessage.value = '';
|
|
|
|
// 从IMManager获取会话列表
|
|
final List<EMConversation> convList = await IMManager.instance
|
|
.getConversations();
|
|
|
|
// 先提取用户信息并缓存,然后再更新会话列表
|
|
// 这样可以确保在列表渲染时,用户信息已经在缓存中了
|
|
await _extractUserInfoFromConversations(convList);
|
|
|
|
// 更新会话列表(在用户信息提取完成后)
|
|
conversations.value = convList;
|
|
|
|
// 计算总未读数
|
|
await _updateTotalUnreadCount();
|
|
|
|
// 使用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 {
|
|
if (showLoading) {
|
|
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;
|
|
|
|
// 获取会话的最新消息(增加数量以提高找到用户信息的概率)
|
|
final messages = await conversation.loadMessages(
|
|
loadCount: 50, // 从20增加到50,提高找到用户信息的概率
|
|
);
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔍 [ConversationController] 开始提取用户信息: userId=$targetUserId, 消息数量=${messages.length}');
|
|
}
|
|
|
|
// 从消息中提取用户信息
|
|
// 接收消息:提取发送者信息(sender_ 前缀)
|
|
// 发送消息:提取接收者信息(receiver_ 前缀)
|
|
// 遍历所有消息,找到最新的包含用户信息的消息
|
|
ExtendedUserInfo? foundUserInfo;
|
|
for (var message in messages) {
|
|
Map<String, dynamic>? attributes;
|
|
try {
|
|
attributes = message.attributes;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log(' ⚠️ 无法访问消息 attributes: msgId=${message.msgId}, error=$e');
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (attributes == null || attributes.isEmpty) {
|
|
if (Get.isLogEnable) {
|
|
Get.log(' ⚠️ 消息 attributes 为空: msgId=${message.msgId}, direction=${message.direction}');
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log(' 📨 检查消息: msgId=${message.msgId}, direction=${message.direction}, from=${message.from}, to=${message.to}, attributes keys=${attributes.keys.toList()}');
|
|
}
|
|
|
|
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) {
|
|
// 如果找到用户信息,保存(但继续遍历以找到最新的)
|
|
foundUserInfo = ExtendedUserInfo(
|
|
userId: targetUserId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从接收消息找到用户信息: userId=$targetUserId, nickName=$nickName, msgId=${message.msgId}');
|
|
}
|
|
// 继续遍历,找到最新的消息(因为消息是按时间倒序的,第一个就是最新的)
|
|
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) {
|
|
// 如果找到用户信息,保存(但继续遍历以找到最新的)
|
|
foundUserInfo = ExtendedUserInfo(
|
|
userId: targetUserId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从发送消息找到用户信息: userId=$targetUserId, nickName=$nickName, msgId=${message.msgId}');
|
|
}
|
|
// 继续遍历,找到最新的消息(因为消息是按时间倒序的,第一个就是最新的)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 如果找到了用户信息,保存到缓存
|
|
if (foundUserInfo != null) {
|
|
_userInfoCache[targetUserId] = foundUserInfo;
|
|
}
|
|
|
|
// 如果从消息中提取不到用户信息,尝试从环信获取(作为备选)
|
|
if (!_userInfoCache.containsKey(targetUserId)) {
|
|
try {
|
|
var data = await IMManager.instance.getContacts(targetUserId);
|
|
var emUserInfo = data[targetUserId];
|
|
|
|
if (emUserInfo != null && (emUserInfo.nickName?.isNotEmpty ?? false)) {
|
|
final extendedInfo = ExtendedUserInfo.fromEMUserInfo(emUserInfo);
|
|
_userInfoCache[targetUserId] = extendedInfo;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从环信获取到用户信息: userId=$targetUserId, nickName=${extendedInfo.nickName}');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 从环信获取用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 从会话提取用户信息失败: ${conversation.id}, 错误: $e');
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 批量提取用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_refreshDebounceTimer?.cancel();
|
|
super.onClose();
|
|
}
|
|
|
|
/// 刷新会话列表(带防抖,避免频繁刷新导致闪烁)
|
|
Future<void> refreshConversations({bool force = false}) async {
|
|
// 如果正在加载且不是强制刷新,使用防抖机制
|
|
if (!force && isLoading.value) {
|
|
// 取消之前的定时器
|
|
_refreshDebounceTimer?.cancel();
|
|
// 设置新的定时器,延迟300ms后刷新
|
|
_refreshDebounceTimer = Timer(const Duration(milliseconds: 300), () {
|
|
refreshConversations(force: true);
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 取消之前的定时器
|
|
_refreshDebounceTimer?.cancel();
|
|
|
|
// 如果IM未登录,先尝试登录
|
|
if (!IMManager.instance.isLoggedIn) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔄 [ConversationController] IM未登录,尝试重新登录...');
|
|
}
|
|
|
|
try {
|
|
// 尝试获取token并登录
|
|
if (Get.isRegistered<UserController>()) {
|
|
final userController = Get.find<UserController>();
|
|
final token = await userController.getHxUserToken();
|
|
|
|
if (token != null) {
|
|
// 等待登录完成(最多等待5秒)
|
|
int waitCount = 0;
|
|
const maxWait = 10; // 最多等待10次,每次500ms,总共5秒
|
|
|
|
while (waitCount < maxWait && !IMManager.instance.isLoggedIn) {
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
waitCount++;
|
|
}
|
|
|
|
if (IMManager.instance.isLoggedIn) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] IM登录成功,开始加载会话列表');
|
|
}
|
|
// 登录成功后加载会话列表
|
|
await loadConversations();
|
|
return;
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] IM登录超时');
|
|
}
|
|
errorMessage.value = 'IM登录超时,请稍后重试';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ConversationController] 获取IM token失败');
|
|
}
|
|
errorMessage.value = '获取IM token失败,请稍后重试';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ConversationController] UserController未注册');
|
|
}
|
|
errorMessage.value = 'IM未登录,请稍后重试';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ConversationController] 重试登录失败: $e');
|
|
}
|
|
errorMessage.value = '重试登录失败,请稍后重试';
|
|
isLoading.value = false;
|
|
return;
|
|
}
|
|
} else {
|
|
// 如果已登录,直接加载会话列表(不显示加载状态,避免闪烁)
|
|
await loadConversations(showLoading: false);
|
|
}
|
|
}
|
|
|
|
/// 清除会话列表和用户信息缓存(用于退出登录时)
|
|
void clearConversations() {
|
|
conversations.clear();
|
|
_userInfoCache.clear();
|
|
errorMessage.value = '';
|
|
isLoading.value = false;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 已清除会话列表和用户信息缓存');
|
|
}
|
|
}
|
|
|
|
/// 获取会话的最新消息
|
|
String getLastMessageContent(EMMessage? message) {
|
|
if (message == null) {
|
|
return '暂无消息';
|
|
}
|
|
|
|
// 检查消息是否发送失败(发送的消息且状态为FAIL)
|
|
if (message.direction == MessageDirection.SEND && message.status == MessageStatus.FAIL) {
|
|
return '[发送失败]';
|
|
}
|
|
|
|
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('⚠️ [ConversationController] 解析通话消息类型失败: $e');
|
|
}
|
|
}
|
|
return '[通话消息]';
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// 更新总未读数(内部方法)
|
|
Future<void> _updateTotalUnreadCount() async {
|
|
try {
|
|
int total = 0;
|
|
for (var conversation in conversations) {
|
|
final unreadCount = await getUnreadCount(conversation);
|
|
total += unreadCount;
|
|
}
|
|
totalUnreadCount.value = total;
|
|
// 更新应用角标
|
|
await _updateAppBadge(total);
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 总未读数已更新: $total');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 更新总未读数失败: $e');
|
|
}
|
|
totalUnreadCount.value = 0;
|
|
// 更新应用角标为 0
|
|
await _updateAppBadge(0);
|
|
}
|
|
}
|
|
|
|
/// 刷新总未读数(公开方法,供外部调用)
|
|
Future<void> refreshTotalUnreadCount() async {
|
|
await _updateTotalUnreadCount();
|
|
}
|
|
|
|
/// 格式化消息时间
|
|
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) {
|
|
// 获取最近的消息(增加数量以提高找到用户信息的概率)
|
|
final messages = await conversation.loadMessages(
|
|
loadCount: 50, // 从20增加到50,提高找到用户信息的概率
|
|
);
|
|
|
|
// 从消息中查找用户信息(只查找接收到的消息,因为那是对方的用户信息)
|
|
// 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{
|
|
try {
|
|
// 优先尝试从ChatController获取最后一条消息(如果ChatController还在内存中)
|
|
// 这样可以获取到失败状态的消息(SDK可能不会保存失败消息)
|
|
try {
|
|
final tag = 'chat_${conversation.id}';
|
|
if (Get.isRegistered<ChatController>(tag: tag)) {
|
|
final chatController = Get.find<ChatController>(tag: tag);
|
|
if (chatController.messages.isNotEmpty) {
|
|
// 获取最后一条消息(列表末尾,按时间戳排序后最后一条是最新的)
|
|
// 注意:messages列表是按时间戳从旧到新排序的,所以last是最新的
|
|
final lastMsg = chatController.messages.last;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 从ChatController获取最后一条消息: msgId=${lastMsg.msgId}, status=${lastMsg.status}, direction=${lastMsg.direction}, content=${lastMsg.body.type == MessageType.TXT ? (lastMsg.body as EMTextMessageBody).content : lastMsg.body.type}');
|
|
}
|
|
return lastMsg;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 从ChatController获取最后一条消息失败: $e');
|
|
}
|
|
}
|
|
|
|
// 如果ChatController不存在或没有消息,从SDK获取
|
|
final sdkMessage = await conversation.latestMessage();
|
|
if (Get.isLogEnable) {
|
|
if (sdkMessage != null) {
|
|
Get.log('✅ [ConversationController] 从SDK获取最后一条消息: msgId=${sdkMessage.msgId}, status=${sdkMessage.status}, direction=${sdkMessage.direction}');
|
|
} else {
|
|
Get.log('⚠️ [ConversationController] SDK返回的最后一条消息为null');
|
|
}
|
|
}
|
|
return sdkMessage;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 获取最后一条消息失败: $e');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// 删除会话
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// 设置筛选类型
|
|
void setFilterType(FilterType type) {
|
|
filterType.value = type;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ConversationController] 设置筛选类型: $type');
|
|
}
|
|
}
|
|
|
|
/// 获取筛选后的会话列表
|
|
Future<List<EMConversation>> getFilteredConversations() async {
|
|
List<EMConversation> filteredList = List.from(conversations);
|
|
|
|
switch (filterType.value) {
|
|
case FilterType.none:
|
|
// 无筛选,按默认顺序返回
|
|
break;
|
|
|
|
case FilterType.lastChatTime:
|
|
// 按最后聊天时间排序(最新的在前)
|
|
// 先获取所有消息的时间戳
|
|
final List<MapEntry<EMConversation, int>> conversationTimes = await Future.wait(
|
|
filteredList.map((conv) async {
|
|
final message = await lastMessage(conv);
|
|
final time = message?.serverTime ?? 0;
|
|
return MapEntry(conv, time);
|
|
}),
|
|
);
|
|
// 按时间戳排序(降序,最新的在前)
|
|
conversationTimes.sort((a, b) => b.value.compareTo(a.value));
|
|
filteredList = conversationTimes.map((entry) => entry.key).toList();
|
|
break;
|
|
|
|
case FilterType.unread:
|
|
// 只显示有未读消息的会话
|
|
final List<MapEntry<EMConversation, int>> conversationUnreads = await Future.wait(
|
|
filteredList.map((conv) async {
|
|
final unreadCount = await getUnreadCount(conv);
|
|
return MapEntry(conv, unreadCount);
|
|
}),
|
|
);
|
|
// 过滤出有未读消息的会话
|
|
final unreadConversations = conversationUnreads
|
|
.where((entry) => entry.value > 0)
|
|
.toList();
|
|
// 按未读数量排序(未读数多的在前)
|
|
unreadConversations.sort((a, b) => b.value.compareTo(a.value));
|
|
filteredList = unreadConversations.map((entry) => entry.key).toList();
|
|
break;
|
|
|
|
case FilterType.online:
|
|
// 只显示在线用户的会话
|
|
final List<EMConversation?> onlineConversations = await Future.wait(
|
|
filteredList.map((conv) async {
|
|
final isOnline = await _checkUserOnline(conv);
|
|
return isOnline ? conv : null;
|
|
}),
|
|
);
|
|
filteredList = onlineConversations
|
|
.whereType<EMConversation>()
|
|
.toList();
|
|
break;
|
|
}
|
|
|
|
return filteredList;
|
|
}
|
|
|
|
/// 检查用户是否在线
|
|
Future<bool> _checkUserOnline(EMConversation conversation) async {
|
|
try {
|
|
// 获取会话的最新消息
|
|
final message = await lastMessage(conversation);
|
|
if (message == null) {
|
|
return false;
|
|
}
|
|
|
|
// 从消息扩展字段中获取在线状态
|
|
Map<String, dynamic>? attributes;
|
|
try {
|
|
attributes = message.attributes;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
if (attributes == null || attributes.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
// 检查在线状态字段
|
|
// 接收消息:检查 sender_isOnline
|
|
// 发送消息:检查 receiver_isOnline
|
|
String? isOnlineStr;
|
|
String? lastActiveTimeStr;
|
|
|
|
if (message.direction == MessageDirection.RECEIVE) {
|
|
isOnlineStr = attributes['sender_isOnline'] as String?;
|
|
lastActiveTimeStr = attributes['sender_lastActiveTime'] as String?;
|
|
} else {
|
|
isOnlineStr = attributes['receiver_isOnline'] as String?;
|
|
lastActiveTimeStr = attributes['receiver_lastActiveTime'] as String?;
|
|
}
|
|
|
|
if (isOnlineStr == 'true') {
|
|
// 进一步检查最后活跃时间(5分钟内认为在线)
|
|
if (lastActiveTimeStr != null) {
|
|
try {
|
|
final lastActiveTime = int.parse(lastActiveTimeStr);
|
|
final now = DateTime.now().millisecondsSinceEpoch;
|
|
final diff = now - lastActiveTime;
|
|
// 5分钟内认为在线(5 * 60 * 1000 毫秒)
|
|
return diff < 5 * 60 * 1000;
|
|
} catch (e) {
|
|
// 解析失败,使用 isOnline 字段
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ConversationController] 检查用户在线状态失败: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|