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.
1918 lines
71 KiB
1918 lines
71 KiB
import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:tdesign_flutter/tdesign_flutter.dart';
|
|
|
|
import '../../im/im_manager.dart';
|
|
import '../../im/chat_presence_manager.dart';
|
|
import '../../model/home/marriage_data.dart';
|
|
import '../../model/live/gift_product_model.dart';
|
|
import '../../network/network_service.dart';
|
|
import '../../widget/live/live_recharge_popup.dart';
|
|
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
|
|
import 'conversation_controller.dart';
|
|
import '../mine/rose_controller.dart';
|
|
|
|
class ChatController extends GetxController {
|
|
final String userId;
|
|
|
|
// 用户信息
|
|
final Rx<EMUserInfo?> userInfo = Rx<EMUserInfo?>(null);
|
|
|
|
// 外部传入的用户数据模型(用于在获取不到环信用户信息时使用)
|
|
MarriageData? _userData;
|
|
|
|
// 外部传入的用户信息(用于在获取不到环信用户信息时使用)
|
|
String? _externalNickName;
|
|
String? _externalAvatarUrl;
|
|
|
|
// 用户在线状态(从接口获取)
|
|
final RxBool isUserOnline = RxBool(false);
|
|
|
|
// 消息列表
|
|
final RxList<EMMessage> messages = RxList<EMMessage>([]);
|
|
|
|
// 加载更多的游标
|
|
String? _cursor;
|
|
|
|
// 视频发送状态
|
|
final RxBool isSendingVideo = RxBool(false);
|
|
final RxString sendingStatus = RxString('');
|
|
var isDialogShowing = false.obs;
|
|
// 语音发送状态(防止重复发送)
|
|
final RxBool isSendingVoice = RxBool(false);
|
|
|
|
// 礼物产品列表
|
|
final RxList<GiftProductModel> giftProducts = <GiftProductModel>[].obs;
|
|
|
|
// 需要显示508错误提示的消息ID集合(临时状态,不持久化)
|
|
final Set<String> _roseErrorMessageIds = <String>{};
|
|
|
|
// 需要显示敏感词错误提示的消息ID集合(临时状态,不持久化)
|
|
final Set<String> _sensitiveWordMessageIds = <String>{};
|
|
|
|
// 网络服务
|
|
final NetworkService _networkService = NetworkService();
|
|
|
|
/// 检查消息是否需要显示508错误提示
|
|
bool shouldShowRoseError(String messageId) {
|
|
return _roseErrorMessageIds.contains(messageId);
|
|
}
|
|
|
|
/// 添加需要显示508错误提示的消息ID
|
|
void addRoseErrorMessageId(String messageId) {
|
|
_roseErrorMessageIds.add(messageId);
|
|
update();
|
|
}
|
|
|
|
/// 检查消息是否需要显示敏感词错误提示
|
|
bool shouldShowSensitiveWordError(String messageId) {
|
|
return _sensitiveWordMessageIds.contains(messageId);
|
|
}
|
|
|
|
/// 添加需要显示敏感词错误提示的消息ID
|
|
void addSensitiveWordMessageId(String messageId) {
|
|
_sensitiveWordMessageIds.add(messageId);
|
|
update();
|
|
}
|
|
|
|
ChatController({
|
|
required this.userId,
|
|
MarriageData? userData,
|
|
}) {
|
|
// 如果构造函数传入了用户数据模型,保存并使用
|
|
if (userData != null) {
|
|
_userData = userData;
|
|
_externalNickName = userData.nickName;
|
|
_externalAvatarUrl = userData.profilePhoto;
|
|
}
|
|
}
|
|
|
|
/// 获取用户数据模型
|
|
MarriageData? get userData => _userData;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
// 注册到 IMManager,以便接收消息时能通知到此 Controller
|
|
IMManager.instance.registerChatController(this);
|
|
|
|
// 如果有外部传入的用户信息,先同步到 ConversationController 的缓存中
|
|
if (_userData != null) {
|
|
_syncUserInfoToConversationController();
|
|
} else {
|
|
// 如果没有外部传入的用户信息,尝试从 ConversationController 的缓存中获取
|
|
_loadUserInfoFromConversationCache();
|
|
}
|
|
|
|
// 初始化时获取用户信息和消息列表
|
|
fetchUserInfo();
|
|
fetchMessages().then((_) {
|
|
// 加载消息后,标记所有消息为已读
|
|
markAllMessagesAsRead();
|
|
});
|
|
// 加载礼物产品列表
|
|
loadGiftProducts();
|
|
// 订阅对方用户在线状态(实时接收状态变化)
|
|
subscribeUserPresence();
|
|
// 如果有外部传入的用户信息,触发 UI 更新
|
|
if (_externalNickName != null || _externalAvatarUrl != null) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void setDialogDismiss(bool flag){
|
|
isDialogShowing.value = flag;
|
|
}
|
|
|
|
/// 订阅用户在线状态(实时接收状态变化)
|
|
Future<void> subscribeUserPresence() async {
|
|
try {
|
|
await ChatPresenceManager().subscribeUserPresence(
|
|
userId: userId,
|
|
onStatusChanged: (isOnline) {
|
|
// 状态变化回调
|
|
isUserOnline.value = isOnline;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 用户在线状态更新: userId=$userId, isOnline=$isOnline');
|
|
}
|
|
update();
|
|
},
|
|
);
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 订阅用户在线状态异常: $e');
|
|
}
|
|
// 获取异常时,默认为离线状态
|
|
isUserOnline.value = false;
|
|
update();
|
|
}
|
|
}
|
|
|
|
/// 取消订阅用户在线状态
|
|
Future<void> unsubscribeUserPresence() async {
|
|
try {
|
|
await ChatPresenceManager().unsubscribeUserPresence(userId);
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 取消订阅用户在线状态异常: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 从 ConversationController 的缓存中加载用户信息
|
|
void _loadUserInfoFromConversationCache() {
|
|
try {
|
|
if (Get.isRegistered<ConversationController>()) {
|
|
final conversationController = Get.find<ConversationController>();
|
|
// 尝试从缓存中获取用户信息
|
|
conversationController.loadContact(userId).then((extendedInfo) {
|
|
if (extendedInfo != null && (extendedInfo.nickName != null || extendedInfo.avatarUrl != null)) {
|
|
// 如果从缓存中获取到了用户信息,设置到外部信息中
|
|
_externalNickName = extendedInfo.nickName;
|
|
_externalAvatarUrl = extendedInfo.avatarUrl;
|
|
// 触发 UI 更新
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 从会话缓存加载用户信息: userId=$userId, nickName=${extendedInfo.nickName}');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 从会话缓存加载用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 将用户信息同步到 ConversationController 的缓存中
|
|
/// 注意:用户信息会通过消息扩展字段自动传递,这里只是提前缓存以确保聊天列表能立即显示
|
|
void _syncUserInfoToConversationController() {
|
|
try {
|
|
if (_userData == null) return;
|
|
|
|
final nickName = _userData!.nickName;
|
|
final avatarUrl = _userData!.profilePhoto.trim().replaceAll('`', ''); // 移除照片URL中的反引号
|
|
|
|
// 更新到 ConversationController 的缓存(确保聊天列表能立即显示)
|
|
if (Get.isRegistered<ConversationController>()) {
|
|
final conversationController = Get.find<ConversationController>();
|
|
|
|
// 创建 ExtendedUserInfo 并缓存
|
|
final extendedInfo = ExtendedUserInfo(
|
|
userId: userId,
|
|
nickName: nickName,
|
|
avatarUrl: avatarUrl,
|
|
);
|
|
|
|
// 直接更新缓存
|
|
conversationController.cacheUserInfo(userId, extendedInfo);
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 已将用户信息同步到会话列表缓存: userId=$userId, nickName=$nickName');
|
|
Get.log('ℹ️ [ChatController] 用户信息将通过消息扩展字段自动传递给对方');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 同步用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
// 取消订阅用户在线状态
|
|
unsubscribeUserPresence();
|
|
// 注销 ChatController
|
|
IMManager.instance.unregisterChatController(userId);
|
|
super.onClose();
|
|
}
|
|
|
|
/// 获取用户信息
|
|
Future<void> fetchUserInfo() async {
|
|
try {
|
|
final EMUserInfo? info = await IMManager.instance.getUserInfo(userId);
|
|
if (info != null) {
|
|
userInfo.value = info;
|
|
update(); // 通知 GetBuilder 更新 UI
|
|
if (Get.isLogEnable) {
|
|
Get.log('获取用户信息成功: ${info.nickName}');
|
|
}
|
|
} else {
|
|
// 如果没有获取到用户信息,但 userInfo 已经有值(从外部设置的),则不覆盖
|
|
if (userInfo.value == null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('未找到用户信息: $userId,使用外部传入的信息');
|
|
}
|
|
// 即使没有从环信获取到用户信息,也触发 UI 更新,因为外部可能传入了信息
|
|
update();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('获取用户信息失败: $e,使用外部传入的信息');
|
|
}
|
|
// 即使获取失败,也触发 UI 更新
|
|
update();
|
|
}
|
|
}
|
|
|
|
/// 从外部设置用户信息(用于从其他页面传递用户数据)
|
|
void setUserInfoFromExternal({String? nickName, String? avatarUrl}) {
|
|
try {
|
|
// 保存外部传入的用户信息
|
|
_externalNickName = nickName;
|
|
_externalAvatarUrl = avatarUrl;
|
|
|
|
// 如果当前没有用户信息,尝试从服务器获取
|
|
if (userInfo.value == null) {
|
|
fetchUserInfo().then((_) {
|
|
// 如果获取后还是没有用户信息,触发 UI 更新以使用外部传入的信息
|
|
if (userInfo.value == null) {
|
|
update();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 触发 UI 更新,使外部传入的信息生效
|
|
update();
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('设置外部用户信息: nickName=$nickName, avatarUrl=$avatarUrl');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('设置用户信息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 获取用户昵称(优先使用环信用户信息,如果没有或为空则使用外部传入的)
|
|
String? get userNickName {
|
|
final hxNickName = userInfo.value?.nickName;
|
|
// 只有当环信用户信息的昵称不为空且不为空字符串时,才使用它
|
|
if (hxNickName != null && hxNickName.isNotEmpty) {
|
|
return hxNickName;
|
|
}
|
|
// 否则回退到外部传入的昵称
|
|
return _externalNickName;
|
|
}
|
|
|
|
/// 获取用户头像(优先使用环信用户信息,如果没有或为空则使用外部传入的)
|
|
String? get userAvatarUrl {
|
|
final hxAvatarUrl = userInfo.value?.avatarUrl;
|
|
// 只有当环信用户信息的头像不为空且不为空字符串时,才使用它
|
|
if (hxAvatarUrl != null && hxAvatarUrl.isNotEmpty) {
|
|
return hxAvatarUrl;
|
|
}
|
|
// 否则回退到外部传入的头像
|
|
return _externalAvatarUrl;
|
|
}
|
|
|
|
/// 发送消息
|
|
Future<bool> sendMessage(String content) async {
|
|
EMMessage? tempMessage;
|
|
try {
|
|
if (Get.isLogEnable) {
|
|
Get.log('📤 [ChatController] 准备发送消息: content=$content, targetUserId=$userId');
|
|
} else {
|
|
print('📤 [ChatController] 准备发送消息: content=$content, targetUserId=$userId');
|
|
}
|
|
|
|
// 检查目标用户是否存在于IM系统中
|
|
final userExists = await IMManager.instance.checkUserExists(userId);
|
|
if (!userExists) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 警告:目标用户可能不存在于IM系统中: $userId');
|
|
} else {
|
|
print('⚠️ [ChatController] 警告:目标用户可能不存在于IM系统中: $userId');
|
|
}
|
|
// 继续发送,但记录警告
|
|
}
|
|
|
|
// 先创建消息对象(即使发送失败也要显示在列表中)
|
|
tempMessage = EMMessage.createTxtSendMessage(
|
|
targetId: userId,
|
|
content: content,
|
|
);
|
|
|
|
// 将消息添加到列表末尾(显示发送中状态)
|
|
messages.add(tempMessage);
|
|
update();
|
|
|
|
// 尝试发送消息
|
|
final message = await IMManager.instance.sendTextMessage(content, userId);
|
|
if (message != null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] SDK返回消息: msgId=${message.msgId}, status=${message.status}');
|
|
} else {
|
|
print('✅ [ChatController] SDK返回消息: msgId=${message.msgId}, status=${message.status}');
|
|
}
|
|
|
|
// 发送成功,替换临时消息
|
|
// 注意:SDK 可能会改变消息ID(onMessageIdChanged),所以需要根据原始消息ID或内容来匹配
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 找到临时消息,替换: index=$index, oldMsgId=${tempMessage.msgId}, newMsgId=${message.msgId}');
|
|
} else {
|
|
print('✅ [ChatController] 找到临时消息,替换: index=$index, oldMsgId=${tempMessage.msgId}, newMsgId=${message.msgId}');
|
|
}
|
|
messages[index] = message;
|
|
} else {
|
|
// 如果找不到原始消息(可能ID改变了),尝试根据内容匹配
|
|
final contentIndex = messages.indexWhere((msg) =>
|
|
msg.body.type == MessageType.TXT &&
|
|
(msg.body as EMTextMessageBody).content == content &&
|
|
msg.direction == MessageDirection.SEND
|
|
);
|
|
if (contentIndex != -1) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 根据内容找到消息,替换: index=$contentIndex');
|
|
} else {
|
|
print('✅ [ChatController] 根据内容找到消息,替换: index=$contentIndex');
|
|
}
|
|
messages[contentIndex] = message;
|
|
} else {
|
|
// 如果还是找不到,直接添加新消息
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 未找到匹配的消息,添加新消息');
|
|
} else {
|
|
print('⚠️ [ChatController] 未找到匹配的消息,添加新消息');
|
|
}
|
|
messages.add(message);
|
|
}
|
|
}
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] UI已更新,消息状态: ${message.status}');
|
|
} else {
|
|
print('✅ [ChatController] UI已更新,消息状态: ${message.status}');
|
|
}
|
|
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
|
|
// 如果消息状态仍然是 PROGRESS,说明 SDK 的状态更新是异步的
|
|
// 等待一段时间后再次检查并更新状态
|
|
// 注意:即使 sendMessage 返回成功,SDK 可能随后报错(如日志中的 custom internal error),所以需要持续检查状态
|
|
if (message.status == MessageStatus.PROGRESS) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⏳ [ChatController] 消息状态仍为PROGRESS,将延迟检查状态');
|
|
} else {
|
|
print('⏳ [ChatController] 消息状态仍为PROGRESS,将延迟检查状态');
|
|
}
|
|
// 延迟一小段时间后检查状态(给 SDK 时间更新状态)
|
|
// 由于 SDK 可能在发送后报错,我们需要更频繁地检查状态
|
|
// 注意:SDK 可能会改变消息ID(onMessageIdChanged),所以需要从最新消息列表中查找
|
|
Future.delayed(Duration(milliseconds: 500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
// 再延迟一段时间后再次检查(捕获可能的失败回调)
|
|
Future.delayed(Duration(milliseconds: 1500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
// 再延迟一段时间后最后一次检查
|
|
Future.delayed(Duration(milliseconds: 3000), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
} else if (message.status == MessageStatus.FAIL) {
|
|
// 如果状态已经是 FAIL,立即更新UI
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 消息发送失败,状态: ${message.status}');
|
|
} else {
|
|
print('❌ [ChatController] 消息发送失败,状态: ${message.status}');
|
|
}
|
|
} else {
|
|
// 状态不是 PROGRESS,说明已经更新完成
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 消息发送成功,状态: ${message.status}');
|
|
} else {
|
|
print('✅ [ChatController] 消息发送成功,状态: ${message.status}');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
// 发送失败,更新消息状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
// 注意:EMMessage对象的状态是只读的,需要通过SDK的onError回调来更新
|
|
// 这里先更新UI,等待onError回调更新实际的消息对象
|
|
update();
|
|
|
|
// 刷新会话列表,确保聊天列表能显示失败状态
|
|
_refreshConversationList();
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 消息发送失败,等待onError回调更新状态: msgId=${tempMessage.msgId}');
|
|
}
|
|
}
|
|
SmartDialog.showToast('消息发送失败,请点击重发');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送消息失败: $e');
|
|
}
|
|
// 发送异常,更新消息状态
|
|
if (tempMessage != null) {
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
update();
|
|
}
|
|
}
|
|
SmartDialog.showToast('消息发送失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 发送图片消息
|
|
Future<bool> sendImageMessage(String imagePath) async {
|
|
EMMessage? tempMessage;
|
|
try {
|
|
if (Get.isLogEnable) {
|
|
Get.log('📤 [ChatController] 准备发送图片消息: path=$imagePath, targetUserId=$userId');
|
|
} else {
|
|
print('📤 [ChatController] 准备发送图片消息: path=$imagePath, targetUserId=$userId');
|
|
}
|
|
|
|
// 先创建消息对象(即使发送失败也要显示在列表中)
|
|
tempMessage = EMMessage.createImageSendMessage(
|
|
targetId: userId,
|
|
filePath: imagePath,
|
|
sendOriginalImage: false,
|
|
);
|
|
|
|
// 将消息添加到列表末尾(显示发送中状态)
|
|
messages.add(tempMessage);
|
|
update();
|
|
|
|
// 尝试发送消息
|
|
final message = await IMManager.instance.sendImageMessage(
|
|
imagePath,
|
|
userId,
|
|
);
|
|
if (message != null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] SDK返回图片消息: msgId=${message.msgId}, status=${message.status}');
|
|
} else {
|
|
print('✅ [ChatController] SDK返回图片消息: msgId=${message.msgId}, status=${message.status}');
|
|
}
|
|
|
|
// 发送成功,替换临时消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
messages[index] = message;
|
|
} else {
|
|
// 如果找不到原始消息(可能ID改变了),尝试根据内容匹配
|
|
final contentIndex = messages.indexWhere((msg) =>
|
|
msg.body.type == MessageType.IMAGE &&
|
|
msg.direction == MessageDirection.SEND
|
|
);
|
|
if (contentIndex != -1) {
|
|
messages[contentIndex] = message;
|
|
} else {
|
|
messages.add(message);
|
|
}
|
|
}
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
|
|
// 如果消息状态仍然是 PROGRESS,延迟检查状态
|
|
if (message.status == MessageStatus.PROGRESS) {
|
|
Future.delayed(Duration(milliseconds: 500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 1500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 3000), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
// 发送失败,更新消息状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
update();
|
|
}
|
|
SmartDialog.showToast('图片发送失败,请点击重发');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送图片消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('图片发送失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 发送语音消息
|
|
Future<bool> sendVoiceMessage(String filePath, int seconds) async {
|
|
// 防止重复发送
|
|
if (isSendingVoice.value) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 语音消息正在发送中,忽略重复发送请求');
|
|
} else {
|
|
print('⚠️ [ChatController] 语音消息正在发送中,忽略重复发送请求');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
EMMessage? tempMessage;
|
|
try {
|
|
isSendingVoice.value = true;
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('📤 [ChatController] 准备发送语音消息: path=$filePath, duration=$seconds, targetUserId=$userId');
|
|
} else {
|
|
print('📤 [ChatController] 准备发送语音消息: path=$filePath, duration=$seconds, targetUserId=$userId');
|
|
}
|
|
|
|
// 先创建消息对象(即使发送失败也要显示在列表中)
|
|
tempMessage = EMMessage.createVoiceSendMessage(
|
|
targetId: userId,
|
|
filePath: filePath,
|
|
duration: seconds,
|
|
);
|
|
|
|
// 将消息添加到列表末尾(显示发送中状态)
|
|
messages.add(tempMessage);
|
|
update();
|
|
|
|
// 尝试发送消息
|
|
final message = await IMManager.instance.sendVoiceMessage(
|
|
filePath,
|
|
userId,
|
|
seconds,
|
|
);
|
|
if (message != null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] SDK返回语音消息: msgId=${message.msgId}, status=${message.status}');
|
|
} else {
|
|
print('✅ [ChatController] SDK返回语音消息: msgId=${message.msgId}, status=${message.status}');
|
|
}
|
|
|
|
// 发送成功,替换临时消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
messages[index] = message;
|
|
} else {
|
|
// 如果找不到原始消息(可能ID改变了),尝试根据内容匹配
|
|
final contentIndex = messages.indexWhere((msg) =>
|
|
msg.body.type == MessageType.VOICE &&
|
|
msg.direction == MessageDirection.SEND
|
|
);
|
|
if (contentIndex != -1) {
|
|
messages[contentIndex] = message;
|
|
} else {
|
|
messages.add(message);
|
|
}
|
|
}
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
|
|
// 如果消息状态仍然是 PROGRESS,延迟检查状态
|
|
if (message.status == MessageStatus.PROGRESS) {
|
|
Future.delayed(Duration(milliseconds: 500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 1500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 3000), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
}
|
|
|
|
isSendingVoice.value = false;
|
|
return true;
|
|
} else {
|
|
// 发送失败,更新消息状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
update();
|
|
}
|
|
isSendingVoice.value = false;
|
|
SmartDialog.showToast('语音发送失败,请点击重发');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
isSendingVoice.value = false;
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送语音消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('语音发送失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 发送视频消息
|
|
Future<bool> sendVideoMessage(String filePath, int duration) async {
|
|
EMMessage? tempMessage;
|
|
try {
|
|
if (Get.isLogEnable) {
|
|
Get.log('🎬 [ChatController] 准备发送视频消息: path=$filePath, duration=$duration, targetUserId=$userId');
|
|
} else {
|
|
print('🎬 [ChatController] 准备发送视频消息: path=$filePath, duration=$duration, targetUserId=$userId');
|
|
}
|
|
|
|
// 先创建消息对象(即使发送失败也要显示在列表中)
|
|
tempMessage = EMMessage.createVideoSendMessage(
|
|
targetId: userId,
|
|
filePath: filePath,
|
|
duration: duration,
|
|
);
|
|
|
|
// 将消息添加到列表末尾(显示发送中状态)
|
|
messages.add(tempMessage);
|
|
update();
|
|
|
|
// 尝试发送消息(后台上传,消息状态会自动更新)
|
|
final message = await IMManager.instance.sendVideoMessage(
|
|
filePath,
|
|
userId,
|
|
duration,
|
|
);
|
|
|
|
if (message != null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] SDK返回视频消息: msgId=${message.msgId}, status=${message.status}');
|
|
} else {
|
|
print('✅ [ChatController] SDK返回视频消息: msgId=${message.msgId}, status=${message.status}');
|
|
}
|
|
|
|
// 发送成功,替换临时消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
messages[index] = message;
|
|
} else {
|
|
// 如果找不到原始消息(可能ID改变了),尝试根据内容匹配
|
|
final contentIndex = messages.indexWhere((msg) =>
|
|
msg.body.type == MessageType.VIDEO &&
|
|
msg.direction == MessageDirection.SEND
|
|
);
|
|
if (contentIndex != -1) {
|
|
messages[contentIndex] = message;
|
|
} else {
|
|
messages.add(message);
|
|
}
|
|
}
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
|
|
// 如果消息状态仍然是 PROGRESS,延迟检查状态
|
|
if (message.status == MessageStatus.PROGRESS) {
|
|
Future.delayed(Duration(milliseconds: 500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 1500), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 3000), () {
|
|
_checkMessageStatusAndUpdate(message.msgId);
|
|
});
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
// 发送失败,更新消息状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
|
|
if (index != -1) {
|
|
update();
|
|
}
|
|
SmartDialog.showToast('视频发送失败,请点击重发');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送视频消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('视频发送失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 获取消息列表
|
|
Future<void> fetchMessages({bool loadMore = false}) async {
|
|
try {
|
|
// 如果加载更多但没有游标,说明已经加载完所有消息
|
|
if (loadMore && _cursor == null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('没有更多消息了');
|
|
}
|
|
return;
|
|
}
|
|
|
|
final List<EMMessage?> fetchedMessages = await IMManager.instance
|
|
.getMessages(
|
|
userId,
|
|
pageSize: 20,
|
|
startMsgId: loadMore ? _cursor : null,
|
|
);
|
|
|
|
// 过滤掉null消息
|
|
final List<EMMessage> validMessages = fetchedMessages
|
|
.whereType<EMMessage>()
|
|
.toList();
|
|
|
|
if (loadMore) {
|
|
// 加载更多时,需要去重并添加到列表开头(更旧的消息)
|
|
final existingMsgIds = messages.map((msg) => msg.msgId).toSet();
|
|
final newMessages = validMessages
|
|
.where((msg) => !existingMsgIds.contains(msg.msgId))
|
|
.toList();
|
|
|
|
if (newMessages.isNotEmpty) {
|
|
// 从新消息的 attributes 中恢复错误码状态
|
|
for (var msg in newMessages) {
|
|
if (msg.status == MessageStatus.FAIL && msg.direction == MessageDirection.SEND) {
|
|
try {
|
|
final errorCode = msg.attributes?['errorCode'] as String?;
|
|
if (errorCode == 'E0002') {
|
|
_roseErrorMessageIds.add(msg.msgId);
|
|
} else if (errorCode == 'E0001') {
|
|
_sensitiveWordMessageIds.add(msg.msgId);
|
|
}
|
|
} catch (e) {
|
|
// 忽略错误
|
|
}
|
|
}
|
|
}
|
|
|
|
messages.insertAll(0, newMessages);
|
|
// 更新游标为最旧的消息ID(列表开头)
|
|
_cursor = newMessages.first.msgId;
|
|
if (Get.isLogEnable) {
|
|
Get.log('加载更多消息成功,新增: ${newMessages.length} 条,总数量: ${messages.length}');
|
|
}
|
|
} else {
|
|
// 没有新消息,说明已经加载完所有消息
|
|
_cursor = null;
|
|
if (Get.isLogEnable) {
|
|
Get.log('没有更多消息了');
|
|
}
|
|
}
|
|
} else {
|
|
// 刷新时替换整个列表,但需要去重(处理重发消息的情况)
|
|
// 对于相同内容的消息,只保留最新的(msgId更大的)
|
|
// 重新进入页面时,清空临时错误提示状态,然后从消息 attributes 中恢复
|
|
_roseErrorMessageIds.clear();
|
|
_sensitiveWordMessageIds.clear();
|
|
|
|
final Map<String, EMMessage> contentToMessage = {};
|
|
|
|
for (var msg in validMessages) {
|
|
// 生成消息的唯一标识(基于内容和时间戳)
|
|
String contentKey;
|
|
if (msg.body.type == MessageType.TXT) {
|
|
final textBody = msg.body as EMTextMessageBody;
|
|
contentKey = '${msg.direction}_${msg.serverTime}_${textBody.content}';
|
|
} else {
|
|
// 对于其他类型的消息,使用时间戳和类型作为key(处理重发时msgId改变的情况)
|
|
contentKey = '${msg.direction}_${msg.serverTime}_${msg.body.type}';
|
|
}
|
|
|
|
// 如果已存在相同内容的消息,比较msgId,保留更大的(更新的)
|
|
if (contentToMessage.containsKey(contentKey)) {
|
|
final existingMsg = contentToMessage[contentKey]!;
|
|
// 比较msgId,保留更大的(通常更新的消息ID更大)
|
|
if (msg.msgId.compareTo(existingMsg.msgId) > 0) {
|
|
contentToMessage[contentKey] = msg;
|
|
}
|
|
} else {
|
|
contentToMessage[contentKey] = msg;
|
|
}
|
|
}
|
|
|
|
// 将去重后的消息列表转换为列表并排序
|
|
final deduplicatedMessages = contentToMessage.values.toList();
|
|
// 按时间戳排序(从旧到新)
|
|
deduplicatedMessages.sort((a, b) => a.serverTime.compareTo(b.serverTime));
|
|
|
|
// 从消息 attributes 中恢复错误码状态
|
|
for (var msg in deduplicatedMessages) {
|
|
if (msg.status == MessageStatus.FAIL && msg.direction == MessageDirection.SEND) {
|
|
try {
|
|
final errorCode = msg.attributes?['errorCode'] as String?;
|
|
if (errorCode == 'E0002') {
|
|
_roseErrorMessageIds.add(msg.msgId);
|
|
} else if (errorCode == 'E0001') {
|
|
_sensitiveWordMessageIds.add(msg.msgId);
|
|
}
|
|
} catch (e) {
|
|
// 忽略错误
|
|
}
|
|
}
|
|
}
|
|
|
|
messages.assignAll(deduplicatedMessages);
|
|
// 更新游标为最旧的消息ID(列表开头)
|
|
if (deduplicatedMessages.isNotEmpty) {
|
|
_cursor = deduplicatedMessages.first.msgId;
|
|
} else {
|
|
_cursor = null;
|
|
}
|
|
if (Get.isLogEnable) {
|
|
Get.log('刷新消息成功,去重前: ${validMessages.length} 条,去重后: ${deduplicatedMessages.length} 条');
|
|
}
|
|
}
|
|
|
|
// 通知UI更新
|
|
update();
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('获取消息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 加载更多消息
|
|
Future<void> loadMoreMessages() async {
|
|
if (_cursor != null) {
|
|
await fetchMessages(loadMore: true);
|
|
}
|
|
}
|
|
|
|
/// 检查是否还有更多消息可以加载
|
|
bool hasMoreMessages() {
|
|
return _cursor != null;
|
|
}
|
|
|
|
bool _isGiftMessage(EMMessage message) {
|
|
try {
|
|
if (message.body.type == MessageType.CUSTOM) {
|
|
final customBody = message.body as EMCustomMessageBody;
|
|
return customBody.event == 'gift';
|
|
}
|
|
} catch (e) {
|
|
// 解析失败,不是礼物消息
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// 添加接收到的消息
|
|
void addReceivedMessage(EMMessage message) {
|
|
// 检查消息是否已存在(避免重复添加)
|
|
if (!messages.any((msg) => msg.msgId == message.msgId)) {
|
|
// 将新消息添加到列表末尾
|
|
messages.add(message);
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
if(_isGiftMessage(message) && Get.currentRoute == '/ChatPage'){
|
|
Get.log('message915: $message');
|
|
final giftInfo = _parseGiftInfo(message);
|
|
var svgaFile = '', giftProductId = '';
|
|
if (giftInfo != null) {
|
|
svgaFile = giftInfo['svgaFile']?.toString() ?? '';
|
|
giftProductId = giftInfo['giftProductId']?.toString() ?? '';
|
|
}
|
|
if(svgaFile.isNotEmpty){
|
|
final svgaManager = SvgaPlayerManager.instance;
|
|
svgaManager.addToQueue(
|
|
SvgaAnimationItem(
|
|
svgaFile: svgaFile,
|
|
giftProductId: giftProductId,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
if (Get.isLogEnable) {
|
|
Get.log('收到新消息并添加到列表: ${message.msgId}');
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic>? _parseGiftInfo(EMMessage message) {
|
|
try {
|
|
if (message.body.type == MessageType.CUSTOM) {
|
|
final customBody = message.body as EMCustomMessageBody;
|
|
if (customBody.event == 'gift' && customBody.params != null) {
|
|
// 将 Map<String, String> 转换为 Map<String, dynamic>
|
|
final params = customBody.params!;
|
|
return {
|
|
'giftProductId': params['giftProductId'] ?? '',
|
|
'giftProductTitle': params['giftProductTitle'] ?? '',
|
|
'giftMainPic': params['giftMainPic'] ?? '',
|
|
'svgaFile': params['svgaFile'] ?? '',
|
|
'giftPrice': params['giftPrice'] ?? '0',
|
|
'quantity': int.tryParse(params['quantity'] ?? '1') ?? 1,
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('解析礼物信息失败: $e');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// 撤回消息
|
|
Future<bool> recallMessage(EMMessage message) async {
|
|
try {
|
|
final success = await IMManager.instance.recallMessage(message);
|
|
if (success) {
|
|
// 从消息列表中移除或更新消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == message.msgId);
|
|
if (index != -1) {
|
|
// 可以保留消息但标记为已撤回,或者直接移除
|
|
// 这里选择移除消息
|
|
messages.removeAt(index);
|
|
update();
|
|
}
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
SmartDialog.showToast('消息已撤回');
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('撤回消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('撤回消息失败');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 删除消息
|
|
Future<bool> deleteMessage(EMMessage message, {bool deleteRemote = false}) async {
|
|
try {
|
|
final conversationId = message.conversationId ?? userId;
|
|
final messageId = message.msgId;
|
|
if (messageId.isEmpty) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('消息ID为空,无法删除');
|
|
}
|
|
return false;
|
|
}
|
|
final success = await IMManager.instance.deleteMessage(conversationId, messageId, deleteRemote: deleteRemote);
|
|
if (success) {
|
|
// 从消息列表中移除
|
|
final index = messages.indexWhere((msg) => msg.msgId == message.msgId);
|
|
if (index != -1) {
|
|
messages.removeAt(index);
|
|
update();
|
|
}
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
SmartDialog.showToast('消息已删除');
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('删除消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('删除消息失败');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 标记消息为已读
|
|
Future<bool> markMessageAsRead(String messageId) async {
|
|
try {
|
|
final success = await IMManager.instance.markMessageAsRead(userId, messageId);
|
|
if (success) {
|
|
// 更新消息的已读状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (index != -1) {
|
|
update();
|
|
}
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('标记消息为已读失败: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 标记会话所有消息为已读
|
|
Future<bool> markAllMessagesAsRead() async {
|
|
try {
|
|
final success = await IMManager.instance.markAllMessagesAsRead(userId);
|
|
if (success) {
|
|
// 更新所有消息的已读状态
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('标记所有消息为已读失败: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 处理消息撤回通知
|
|
void handleMessageRecalled(EMMessage message) {
|
|
try {
|
|
// 从消息列表中移除或更新消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == message.msgId);
|
|
if (index != -1) {
|
|
messages.removeAt(index);
|
|
update();
|
|
}
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
if (Get.isLogEnable) {
|
|
Get.log('处理消息撤回: ${message.msgId}');
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('处理消息撤回失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 更新消息状态
|
|
void updateMessageStatus(EMMessage message, MessageStatus status) {
|
|
try {
|
|
final index = messages.indexWhere((msg) => msg.msgId == message.msgId);
|
|
if (index != -1) {
|
|
// 更新消息状态
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('更新消息状态: ${message.msgId}, status=$status');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('更新消息状态失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 检查消息是否发送失败
|
|
bool isMessageFailed(EMMessage message) {
|
|
return message.status == MessageStatus.FAIL;
|
|
}
|
|
|
|
/// 发送通话消息
|
|
/// [callType] 通话类型:'voice' 语音通话,'video' 视频通话
|
|
/// [callStatus] 通话状态:'calling' 通话中,'missed' 未接听,'cancelled' 已取消,'rejected' 已拒绝
|
|
/// [callDuration] 通话时长(秒),仅在 callStatus 为 'calling' 时有效
|
|
/// [channelId] RTC频道ID
|
|
Future<bool> sendCallMessage({
|
|
required String callType, // 'voice' 或 'video'
|
|
required String callStatus, // 'calling', 'missed', 'cancelled', 'rejected'
|
|
int? callDuration, // 通话时长(秒)
|
|
String? channelId, // RTC频道ID
|
|
int? uid
|
|
}) async {
|
|
try {
|
|
// 构建通话消息参数(需要转换为 Map<String, String>)
|
|
final callParams = <String, String>{
|
|
'callType': callType,
|
|
'callStatus': callStatus,
|
|
'uid': uid.toString(),
|
|
};
|
|
if (callDuration != null) {
|
|
callParams['callDuration'] = callDuration.toString();
|
|
}
|
|
if (channelId != null && channelId.isNotEmpty) {
|
|
callParams['channelId'] = channelId;
|
|
}
|
|
|
|
// 先创建自定义消息对象(即使发送失败也要显示在列表中)
|
|
final tempMessage = EMMessage.createCustomSendMessage(
|
|
targetId: userId,
|
|
event: 'call',
|
|
params: callParams,
|
|
);
|
|
|
|
// 将消息添加到列表末尾(显示发送中状态)
|
|
messages.add(tempMessage);
|
|
update();
|
|
|
|
// 发送自定义消息
|
|
try {
|
|
final message = await IMManager.instance.sendCustomMessage(userId, 'call', callParams);
|
|
if (message != null) {
|
|
// 发送成功,替换临时消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId);
|
|
if (index != -1) {
|
|
messages[index] = message;
|
|
}
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
return true;
|
|
} else {
|
|
// 发送失败,消息状态会自动变为FAIL
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送通话消息失败: message为null');
|
|
}
|
|
SmartDialog.showToast('通话消息发送失败');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
// 发送失败,消息状态会自动变为FAIL
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送通话消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('通话消息发送失败: $e');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('发送通话消息失败: $e');
|
|
}
|
|
SmartDialog.showToast('通话消息发送失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 重发消息
|
|
Future<bool> resendMessage(EMMessage failedMessage) async {
|
|
try {
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔄 [ChatController] 开始重发消息: ${failedMessage.msgId}');
|
|
}
|
|
|
|
// 找到原消息的索引
|
|
final oldIndex = messages.indexWhere((msg) => msg.msgId == failedMessage.msgId);
|
|
if (oldIndex == -1) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 未找到要重发的消息: ${failedMessage.msgId}');
|
|
}
|
|
SmartDialog.showToast('未找到要重发的消息');
|
|
return false;
|
|
}
|
|
|
|
// 先删除SDK中的旧消息(避免重新加载时出现重复)
|
|
try {
|
|
final conversationId = failedMessage.conversationId ?? userId;
|
|
await IMManager.instance.deleteMessage(conversationId, failedMessage.msgId);
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 已删除SDK中的旧消息: ${failedMessage.msgId}');
|
|
}
|
|
} catch (e) {
|
|
// 如果删除失败,记录日志但继续重发流程
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 删除SDK中的旧消息失败: $e,继续重发');
|
|
}
|
|
}
|
|
|
|
// 调用IMManager的重发方法
|
|
final newMessage = await IMManager.instance.resendMessage(failedMessage);
|
|
if (newMessage != null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] SDK返回重发消息: msgId=${newMessage.msgId}, status=${newMessage.status}');
|
|
}
|
|
|
|
// 删除旧消息,添加新消息(在相同位置)
|
|
messages.removeAt(oldIndex);
|
|
messages.insert(oldIndex, newMessage);
|
|
update();
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 消息重发成功,已替换旧消息');
|
|
}
|
|
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
|
|
// 如果消息状态仍然是 PROGRESS,设置状态检查和监听(与sendMessage逻辑一致)
|
|
if (newMessage.status == MessageStatus.PROGRESS) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⏳ [ChatController] 重发消息状态仍为PROGRESS,将延迟检查状态');
|
|
}
|
|
// 延迟检查状态(与sendMessage逻辑一致)
|
|
Future.delayed(Duration(milliseconds: 500), () {
|
|
_checkMessageStatusAndUpdate(newMessage.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 1500), () {
|
|
_checkMessageStatusAndUpdate(newMessage.msgId);
|
|
});
|
|
Future.delayed(Duration(milliseconds: 3000), () {
|
|
_checkMessageStatusAndUpdate(newMessage.msgId);
|
|
});
|
|
} else if (newMessage.status == MessageStatus.FAIL) {
|
|
// 如果状态已经是 FAIL,立即更新UI
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 重发消息失败,状态: ${newMessage.status}');
|
|
}
|
|
SmartDialog.showToast('消息重发失败,请重试');
|
|
} else {
|
|
// 状态不是 PROGRESS,说明已经更新完成
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 重发消息成功,状态: ${newMessage.status}');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 消息重发失败,SDK返回null');
|
|
}
|
|
SmartDialog.showToast('消息重发失败,请重试');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 重发消息异常: $e');
|
|
}
|
|
SmartDialog.showToast('重发消息失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 刷新会话列表
|
|
void _refreshConversationList() {
|
|
try {
|
|
// 尝试获取 ConversationController 并刷新会话列表
|
|
if (Get.isRegistered<ConversationController>()) {
|
|
final conversationController = Get.find<ConversationController>();
|
|
conversationController.refreshConversations();
|
|
}
|
|
} catch (e) {
|
|
// ConversationController 可能未注册,忽略错误
|
|
if (Get.isLogEnable) {
|
|
Get.log('刷新会话列表失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 刷新玫瑰余额
|
|
void _refreshRoseBalance() {
|
|
try {
|
|
// 尝试获取 RoseController 并刷新玫瑰余额
|
|
if (Get.isRegistered<RoseController>()) {
|
|
final roseController = Get.find<RoseController>();
|
|
roseController.getRoseNum();
|
|
}
|
|
} catch (e) {
|
|
// RoseController 可能未注册,忽略错误
|
|
if (Get.isLogEnable) {
|
|
Get.log('刷新玫瑰余额失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 检查消息状态并更新(用于处理发送后状态可能变化的情况)
|
|
void _checkMessageStatusAndUpdate(String messageId) async {
|
|
final currentIndex = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (currentIndex != -1) {
|
|
final currentMessage = messages[currentIndex];
|
|
// 如果状态仍然是 PROGRESS,尝试从 SDK 重新获取消息状态
|
|
if (currentMessage.status == MessageStatus.PROGRESS) {
|
|
// 尝试从 SDK 重新获取消息的最新状态
|
|
try {
|
|
// 先尝试通过原始ID获取
|
|
var latestMessage = await IMManager.instance.getMessageById(userId, messageId);
|
|
|
|
// 如果获取失败或找不到,可能是消息ID改变了,尝试刷新消息列表
|
|
if (latestMessage == null) {
|
|
await _refreshMessageFromSDK(messageId);
|
|
// 刷新后再次尝试获取
|
|
latestMessage = await IMManager.instance.getMessageById(userId, messageId);
|
|
}
|
|
|
|
if (latestMessage != null) {
|
|
// 如果从 SDK 获取到的消息状态已更新,替换列表中的消息
|
|
if (latestMessage.status != MessageStatus.PROGRESS) {
|
|
messages[currentIndex] = latestMessage;
|
|
// 强制刷新UI
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
if (latestMessage.status == MessageStatus.FAIL) {
|
|
Get.log('❌ [ChatController] 从SDK获取到消息失败状态,已更新UI');
|
|
} else {
|
|
Get.log('✅ [ChatController] 从SDK获取到消息最新状态: ${latestMessage.status},已更新UI');
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
// 状态仍然是 PROGRESS,但消息对象可能已更新(SDK内部状态可能已变化)
|
|
// 更新消息对象以确保使用最新的消息数据
|
|
|
|
messages[currentIndex] = latestMessage;
|
|
update();
|
|
}
|
|
} else {
|
|
// 如果通过ID找不到,尝试通过刷新消息列表来匹配
|
|
await _refreshMessageFromSDK(messageId);
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 从SDK获取消息状态失败: $e');
|
|
}
|
|
}
|
|
|
|
// 如果从 SDK 获取失败或状态仍然是 PROGRESS,继续轮询检查
|
|
_checkMessageStatusUntilComplete(messageId, maxAttempts: 6);
|
|
} else {
|
|
// 状态已更新,刷新UI
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 消息状态已自动更新: ${currentMessage.status}');
|
|
}
|
|
// 如果状态是 FAIL,显示提示
|
|
if (currentMessage.status == MessageStatus.FAIL) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 消息发送失败,请点击重发');
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// 如果找不到消息(可能ID改变了),尝试从 SDK 获取最新消息列表
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 找不到消息ID: $messageId,尝试从SDK获取最新消息');
|
|
}
|
|
// 刷新消息列表以获取最新状态(处理消息ID可能改变的情况)
|
|
await _refreshMessageFromSDK(messageId);
|
|
}
|
|
}
|
|
|
|
/// 检查消息是否在会话中(用于验证消息是否真的发送成功)
|
|
Future<void> _checkMessageInConversation(String messageId) async {
|
|
try {
|
|
// 先找到当前消息,用于匹配内容和时间戳
|
|
final currentIndex = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (currentIndex == -1) {
|
|
return;
|
|
}
|
|
final currentMessage = messages[currentIndex];
|
|
|
|
// 从会话中获取最新消息(如果会话不存在,尝试创建)
|
|
final conversationMessages = await IMManager.instance.getMessages(
|
|
userId,
|
|
pageSize: 20, // 检查最近20条消息
|
|
createIfNeed: true, // 如果会话不存在,尝试创建
|
|
);
|
|
|
|
// 过滤掉null消息
|
|
final validMessages = conversationMessages.whereType<EMMessage>().toList();
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔍 [ChatController] 检查会话消息,共 ${validMessages.length} 条,查找消息ID: $messageId');
|
|
// 打印会话中的消息详情,用于调试
|
|
for (var msg in validMessages) {
|
|
Get.log(' - 消息ID: ${msg.msgId}, 方向: ${msg.direction}, 状态: ${msg.status}, 时间: ${msg.serverTime}');
|
|
if (msg.body.type == MessageType.TXT) {
|
|
final body = msg.body as EMTextMessageBody;
|
|
Get.log(' 内容: ${body.content}');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 先尝试通过消息ID查找
|
|
EMMessage? foundMessage;
|
|
try {
|
|
foundMessage = validMessages.firstWhere(
|
|
(msg) => msg.msgId == messageId,
|
|
);
|
|
} catch (e) {
|
|
// 未找到,继续尝试通过内容和时间戳匹配
|
|
}
|
|
|
|
// 如果通过ID找不到,尝试通过内容和时间戳匹配
|
|
if (foundMessage == null) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 在会话中未找到消息ID: $messageId,尝试通过内容和时间戳匹配');
|
|
}
|
|
|
|
// 通过内容和时间戳匹配
|
|
for (var msg in validMessages) {
|
|
// 只匹配发送方向的消息
|
|
if (msg.direction != MessageDirection.SEND) {
|
|
continue;
|
|
}
|
|
|
|
// 匹配条件:相同的内容、相同的时间戳(允许一定误差)
|
|
// 放宽时间戳匹配条件到30秒,因为消息可能延迟保存
|
|
final msgTime = msg.serverTime ?? 0;
|
|
final currentTime = currentMessage.serverTime ?? 0;
|
|
final timeDiff = (msgTime - currentTime).abs();
|
|
|
|
if (timeDiff < 30000) { // 30秒内的误差
|
|
// 进一步检查消息类型和内容是否相同
|
|
if (msg.body.type == currentMessage.body.type) {
|
|
bool isMatch = false;
|
|
|
|
if (msg.body.type == MessageType.TXT) {
|
|
// 文本消息:比较内容
|
|
final msgBody = msg.body as EMTextMessageBody;
|
|
final currentBody = currentMessage.body as EMTextMessageBody;
|
|
isMatch = msgBody.content == currentBody.content;
|
|
if (!isMatch && Get.isLogEnable) {
|
|
Get.log(' - 文本内容不匹配: 会话消息="${msgBody.content}", 原始消息="${currentBody.content}"');
|
|
}
|
|
} else if (msg.body.type == MessageType.IMAGE) {
|
|
// 图片消息:比较本地路径或远程URL
|
|
final msgBody = msg.body as EMImageMessageBody;
|
|
final currentBody = currentMessage.body as EMImageMessageBody;
|
|
// 如果都有本地路径,比较路径;否则比较远程URL
|
|
if (msgBody.localPath != null && currentBody.localPath != null) {
|
|
isMatch = msgBody.localPath == currentBody.localPath;
|
|
} else if (msgBody.remotePath != null && currentBody.remotePath != null) {
|
|
isMatch = msgBody.remotePath == currentBody.remotePath;
|
|
} else {
|
|
// 如果路径都为空,通过时间戳匹配(同一时间发送的图片)
|
|
isMatch = true;
|
|
}
|
|
if (!isMatch && Get.isLogEnable) {
|
|
Get.log(' - 图片路径不匹配: 会话消息路径="${msgBody.localPath ?? msgBody.remotePath}", 原始消息路径="${currentBody.localPath ?? currentBody.remotePath}"');
|
|
}
|
|
} else if (msg.body.type == MessageType.VOICE) {
|
|
// 语音消息:比较时长和本地路径
|
|
final msgBody = msg.body as EMVoiceMessageBody;
|
|
final currentBody = currentMessage.body as EMVoiceMessageBody;
|
|
if (msgBody.localPath != null && currentBody.localPath != null) {
|
|
isMatch = msgBody.localPath == currentBody.localPath;
|
|
} else {
|
|
// 如果路径都为空,比较时长(同一时间发送的语音,时长应该相同)
|
|
isMatch = msgBody.duration == currentBody.duration;
|
|
}
|
|
if (!isMatch && Get.isLogEnable) {
|
|
Get.log(' - 语音不匹配: 会话消息时长=${msgBody.duration}, 原始消息时长=${currentBody.duration}');
|
|
}
|
|
} else if (msg.body.type == MessageType.VIDEO) {
|
|
// 视频消息:比较时长和本地路径
|
|
final msgBody = msg.body as EMVideoMessageBody;
|
|
final currentBody = currentMessage.body as EMVideoMessageBody;
|
|
if (msgBody.localPath != null && currentBody.localPath != null) {
|
|
isMatch = msgBody.localPath == currentBody.localPath;
|
|
} else {
|
|
// 如果路径都为空,比较时长(同一时间发送的视频,时长应该相同)
|
|
isMatch = msgBody.duration == currentBody.duration;
|
|
}
|
|
if (!isMatch && Get.isLogEnable) {
|
|
Get.log(' - 视频不匹配: 会话消息时长=${msgBody.duration}, 原始消息时长=${currentBody.duration}');
|
|
}
|
|
} else {
|
|
// 其他类型消息:仅通过时间戳匹配
|
|
isMatch = true;
|
|
}
|
|
|
|
if (isMatch) {
|
|
foundMessage = msg;
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 通过内容和时间戳匹配到消息: 原ID=$messageId, 新ID=${msg.msgId}, 类型=${msg.body.type}, 时间差: ${timeDiff}ms');
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('🔍 [ChatController] 消息类型不匹配: 会话消息类型=${msg.body.type}, 原始消息类型=${currentMessage.body.type}');
|
|
}
|
|
}
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log(' - 时间戳不匹配: 会话消息时间=${msg.serverTime}, 原始消息时间=${currentMessage.serverTime}, 时间差=${timeDiff}ms');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundMessage != null) {
|
|
// 如果找到了消息,更新消息状态
|
|
messages[currentIndex] = foundMessage;
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
if (foundMessage.status == MessageStatus.FAIL) {
|
|
Get.log('❌ [ChatController] 从会话中找到消息,状态为失败');
|
|
} else if (foundMessage.status == MessageStatus.SUCCESS) {
|
|
Get.log('✅ [ChatController] 从会话中找到消息,状态为成功,消息ID已更新: ${foundMessage.msgId}');
|
|
} else {
|
|
Get.log('⏳ [ChatController] 从会话中找到消息,状态仍为: ${foundMessage.status}');
|
|
}
|
|
}
|
|
// 找到消息后,返回true表示已找到,不需要继续查找
|
|
return;
|
|
} else {
|
|
// 如果找不到消息,说明消息可能没有真正发送成功
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ [ChatController] 在会话中未找到消息,可能发送失败。会话消息数量: ${validMessages.length}');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 检查会话消息失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 从 SDK 刷新消息列表,查找指定消息ID的消息(处理消息ID可能改变的情况)
|
|
Future<void> _refreshMessageFromSDK(String originalMessageId) async {
|
|
try {
|
|
// 从 SDK 获取最新消息列表
|
|
final List<EMMessage?> fetchedMessages = await IMManager.instance.getMessages(
|
|
userId,
|
|
pageSize: 50, // 获取最近50条消息
|
|
startMsgId: null,
|
|
);
|
|
|
|
// 过滤掉null消息
|
|
final List<EMMessage> validMessages = fetchedMessages.whereType<EMMessage>().toList();
|
|
|
|
// 查找对应的消息(可能是原始ID,也可能是新的ID)
|
|
// 由于消息ID可能改变,我们通过消息内容和时间戳来匹配
|
|
final currentIndex = messages.indexWhere((msg) => msg.msgId == originalMessageId);
|
|
if (currentIndex != -1) {
|
|
final originalMessage = messages[currentIndex];
|
|
|
|
// 尝试通过时间戳和内容匹配找到对应的消息
|
|
EMMessage? matchedMessage;
|
|
for (var msg in validMessages) {
|
|
// 匹配条件:相同的内容、相同的时间戳(允许一定误差)、发送方向相同
|
|
if (msg.direction == originalMessage.direction &&
|
|
msg.serverTime == originalMessage.serverTime) {
|
|
// 进一步检查内容是否相同
|
|
if (msg.body.type == originalMessage.body.type) {
|
|
if (msg.body.type == MessageType.TXT) {
|
|
final msgBody = msg.body as EMTextMessageBody;
|
|
final originalBody = originalMessage.body as EMTextMessageBody;
|
|
if (msgBody.content == originalBody.content) {
|
|
matchedMessage = msg;
|
|
break;
|
|
}
|
|
} else {
|
|
// 对于其他类型的消息,如果时间戳和方向匹配,就认为是同一条消息
|
|
matchedMessage = msg;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matchedMessage != null) {
|
|
// 找到匹配的消息,更新列表
|
|
messages[currentIndex] = matchedMessage;
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 从SDK刷新消息成功,新消息ID: ${matchedMessage.msgId},状态: ${matchedMessage.status}');
|
|
}
|
|
} else {
|
|
// 如果找不到匹配的消息,尝试直接通过消息ID查找(可能ID没有改变)
|
|
final directMatch = validMessages.firstWhere(
|
|
(msg) => msg.msgId == originalMessageId,
|
|
orElse: () => originalMessage,
|
|
);
|
|
if (directMatch.msgId == originalMessageId) {
|
|
messages[currentIndex] = directMatch;
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 通过消息ID找到消息,状态: ${directMatch.status}');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 从SDK刷新消息失败: $e');
|
|
}
|
|
// 刷新消息列表以获取最新状态
|
|
try {
|
|
await fetchMessages(loadMore: false);
|
|
update();
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 刷新消息列表失败: $e');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 轮询检查消息状态,直到状态不再是 PROGRESS
|
|
void _checkMessageStatusUntilComplete(String messageId, {int maxAttempts = 6, int attempt = 0}) async {
|
|
if (attempt >= maxAttempts) {
|
|
// 超过最大尝试次数,尝试从 SDK 最后一次获取消息状态
|
|
// 由于消息ID可能改变,使用刷新消息列表的方式
|
|
try {
|
|
await _refreshMessageFromSDK(messageId);
|
|
// 如果刷新后仍然找不到,尝试直接通过ID获取
|
|
final index = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (index != -1) {
|
|
final latestMessage = await IMManager.instance.getMessageById(userId, messageId);
|
|
if (latestMessage != null) {
|
|
messages[index] = latestMessage;
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
if (latestMessage.status == MessageStatus.FAIL) {
|
|
Get.log('❌ [ChatController] 从SDK获取到消息失败状态(最终检查)');
|
|
} else if (latestMessage.status == MessageStatus.PROGRESS) {
|
|
Get.log('⚠️ [ChatController] 消息状态检查超时,状态仍为 PROGRESS,可能发送失败');
|
|
} else {
|
|
Get.log('✅ [ChatController] 从SDK获取到消息最新状态: ${latestMessage.status}');
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 最终检查时从SDK获取消息状态失败: $e');
|
|
}
|
|
}
|
|
|
|
// 如果从 SDK 获取失败,检查内存中的消息状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (index != -1) {
|
|
final message = messages[index];
|
|
// 刷新UI,显示当前状态(可能是 PROGRESS、SUCCESS 或 FAIL)
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
if (message.status == MessageStatus.PROGRESS) {
|
|
Get.log('⚠️ [ChatController] 消息状态检查超时,状态仍为 PROGRESS,可能发送失败');
|
|
} else {
|
|
Get.log('✅ [ChatController] 消息状态检查完成,最终状态: ${message.status}');
|
|
}
|
|
}
|
|
// 如果状态仍然是 PROGRESS,可能是发送失败但没有更新状态
|
|
// 这种情况下,用户可以通过重发按钮重新发送
|
|
}
|
|
return;
|
|
}
|
|
|
|
Future.delayed(Duration(milliseconds: 500), () async {
|
|
// 每次检查时都尝试从 SDK 获取最新状态
|
|
// 注意:SDK 可能会改变消息ID(onMessageIdChanged),所以需要处理这种情况
|
|
try {
|
|
// 先尝试通过原始ID获取
|
|
var latestMessage = await IMManager.instance.getMessageById(userId, messageId);
|
|
|
|
// 如果获取失败或找不到,可能是消息ID改变了,尝试刷新消息列表
|
|
if (latestMessage == null) {
|
|
await _refreshMessageFromSDK(messageId);
|
|
// 刷新后再次尝试获取
|
|
latestMessage = await IMManager.instance.getMessageById(userId, messageId);
|
|
}
|
|
|
|
// 如果仍然找不到,尝试从会话中检查
|
|
if (latestMessage == null) {
|
|
await _checkMessageInConversation(messageId);
|
|
}
|
|
|
|
if (latestMessage != null) {
|
|
final index = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (index != -1) {
|
|
// 更新消息对象(无论状态如何,都更新以确保使用最新的消息数据)
|
|
messages[index] = latestMessage;
|
|
update();
|
|
|
|
// 如果从 SDK 获取到的消息状态已更新,停止检查
|
|
if (latestMessage.status != MessageStatus.PROGRESS) {
|
|
if (Get.isLogEnable) {
|
|
if (latestMessage.status == MessageStatus.FAIL) {
|
|
Get.log('❌ [ChatController] 从SDK获取到消息失败状态');
|
|
} else {
|
|
Get.log('✅ [ChatController] 从SDK获取到消息最新状态: ${latestMessage.status}');
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
// 状态仍然是 PROGRESS,先检查会话中的消息
|
|
// 如果从会话中找到消息并更新了状态,_checkMessageInConversation 会返回,不会继续执行
|
|
await _checkMessageInConversation(messageId);
|
|
// 检查消息状态是否已更新(可能通过 _checkMessageInConversation 更新了)
|
|
final updatedIndex = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (updatedIndex != -1) {
|
|
final updatedMessage = messages[updatedIndex];
|
|
// 如果状态已更新为成功或失败,停止检查
|
|
if (updatedMessage.status != MessageStatus.PROGRESS) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ [ChatController] 消息状态已通过会话检查更新: ${updatedMessage.status}');
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// 继续检查
|
|
_checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1);
|
|
}
|
|
} else {
|
|
// 如果本地找不到消息(可能ID改变了),尝试刷新消息列表
|
|
await _refreshMessageFromSDK(messageId);
|
|
// 继续检查
|
|
_checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1);
|
|
}
|
|
} else {
|
|
// 如果从 SDK 获取失败,使用本地消息状态继续检查
|
|
final index = messages.indexWhere((msg) => msg.msgId == messageId);
|
|
if (index != -1) {
|
|
final message = messages[index];
|
|
if (message.status != MessageStatus.PROGRESS) {
|
|
// 状态已更新,刷新UI
|
|
update();
|
|
if (Get.isLogEnable) {
|
|
if (message.status == MessageStatus.FAIL) {
|
|
Get.log('❌ [ChatController] 消息发送失败,状态: ${message.status}');
|
|
} else {
|
|
Get.log('✅ [ChatController] 消息状态已更新: ${message.status}');
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
// 状态仍然是 PROGRESS,继续检查
|
|
_checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1);
|
|
}
|
|
} else {
|
|
// 如果本地找不到消息,尝试刷新消息列表
|
|
await _refreshMessageFromSDK(messageId);
|
|
// 继续检查
|
|
_checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('⚠️ [ChatController] 从SDK获取消息状态失败: $e');
|
|
}
|
|
// 继续检查
|
|
_checkMessageStatusUntilComplete(messageId, maxAttempts: maxAttempts, attempt: attempt + 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 加载礼物产品列表
|
|
Future<void> loadGiftProducts() async {
|
|
try {
|
|
final response = await _networkService.rtcApi.listGiftProduct();
|
|
final base = response.data;
|
|
if (base.isSuccess && base.data != null) {
|
|
giftProducts.assignAll(base.data!);
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ 礼物产品列表加载成功,共 ${giftProducts.length} 个');
|
|
}
|
|
} else {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ 加载礼物产品列表失败: ${base.message}');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ 加载礼物产品列表异常: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 发送礼物(聊天界面)
|
|
Future<bool> sendGift({
|
|
required GiftProductModel gift,
|
|
int quantity = 1,
|
|
}) async {
|
|
try {
|
|
// 准备请求参数(IM 聊天送礼接口)
|
|
final requestData = {
|
|
'toUserId': int.tryParse(userId) ?? 0,
|
|
'productSpecId': int.tryParse(gift.productSpecId) ?? 0,
|
|
'quantity': quantity,
|
|
};
|
|
|
|
// 调用 IM 送礼接口
|
|
final response = await _networkService.rtcApi.costImGift(
|
|
requestData,
|
|
);
|
|
|
|
if (response.data.isSuccess && !response.data.data['success']) {
|
|
final code = response.data.data['code'];
|
|
if (code == 'E0002') {
|
|
// 玫瑰不足,显示 toast 并弹出充值弹框
|
|
SmartDialog.showToast('玫瑰不足请充值');
|
|
setDialogDismiss(true);
|
|
SmartDialog.show(
|
|
alignment: Alignment.bottomCenter,
|
|
maskColor: Get.context != null
|
|
? TDTheme.of(Get.context!).fontGyColor2
|
|
: Colors.black.withOpacity(0.5),
|
|
builder: (_) => const LiveRechargePopup(),
|
|
onDismiss: () {
|
|
setDialogDismiss(false);
|
|
}
|
|
);
|
|
} else {
|
|
SmartDialog.showToast('发送礼物失败');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 发送成功,创建礼物消息参数(需要转换为 Map<String, String>)
|
|
final giftParams = <String, String>{
|
|
'giftProductId': gift.productId,
|
|
'giftProductTitle': gift.productTitle,
|
|
'giftMainPic': gift.mainPic,
|
|
'giftPrice': gift.unitSellingPrice.toString(),
|
|
'quantity': quantity.toString(),
|
|
'svgaFile': gift.svgaFile,
|
|
};
|
|
|
|
// 先创建自定义消息对象(即使发送失败也要显示在列表中)
|
|
final tempMessage = EMMessage.createCustomSendMessage(
|
|
targetId: userId,
|
|
event: 'gift',
|
|
params: giftParams,
|
|
);
|
|
|
|
// 将消息添加到列表末尾(显示发送中状态)
|
|
messages.add(tempMessage);
|
|
update();
|
|
|
|
// 发送自定义消息
|
|
final message = await IMManager.instance.sendCustomMessage(userId, 'gift', giftParams);
|
|
if (message != null) {
|
|
// 发送成功,替换临时消息
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId);
|
|
if (index != -1) {
|
|
messages[index] = message;
|
|
}
|
|
update();
|
|
// 更新会话列表
|
|
_refreshConversationList();
|
|
|
|
// 刷新玫瑰余额
|
|
_refreshRoseBalance();
|
|
|
|
// 消费成功后再添加到本地播放队列
|
|
final svgaManager = SvgaPlayerManager.instance;
|
|
svgaManager.addToQueue(
|
|
SvgaAnimationItem(
|
|
svgaFile: gift.svgaFile,
|
|
giftProductId: gift.productId,
|
|
),
|
|
);
|
|
print('✅ 礼物已添加到播放队列: ${gift.productTitle}');
|
|
|
|
if (Get.isLogEnable) {
|
|
Get.log('✅ 礼物消息发送成功: ${gift.productTitle}');
|
|
}
|
|
return true;
|
|
} else {
|
|
// 发送失败,更新消息状态
|
|
final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId);
|
|
if (index != -1) {
|
|
update();
|
|
}
|
|
SmartDialog.showToast('礼物消息发送失败,请点击重发');
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
if (Get.isLogEnable) {
|
|
Get.log('❌ 发送礼物失败: $e');
|
|
}
|
|
SmartDialog.showToast('发送礼物失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
}
|