Browse Source

聊天优化

ios
Jolie 3 months ago
parent
commit
9feded7369
13 changed files with 710 additions and 61 deletions
  1. BIN
      assets/images/filter_icon.png
  2. BIN
      assets/images/last_msg_icon.png
  3. BIN
      assets/images/online_msg_icon.png
  4. BIN
      assets/images/unread_icon.png
  5. 88
      lib/controller/message/chat_controller.dart
  6. 135
      lib/controller/message/conversation_controller.dart
  7. 5
      lib/generated/assets.dart
  8. 154
      lib/im/im_manager.dart
  9. 211
      lib/pages/message/chat_page.dart
  10. 34
      lib/pages/message/conversation_tab.dart
  11. 130
      lib/pages/message/message_page.dart
  12. 10
      lib/widget/message/message_item.dart
  13. 4
      lib/widget/message/voice_item.dart

BIN
assets/images/filter_icon.png

Before After
Width: 37  |  Height: 46  |  Size: 402 B

BIN
assets/images/last_msg_icon.png

Before After
Width: 52  |  Height: 52  |  Size: 857 B

BIN
assets/images/online_msg_icon.png

Before After
Width: 44  |  Height: 52  |  Size: 831 B

BIN
assets/images/unread_icon.png

Before After
Width: 48  |  Height: 52  |  Size: 892 B

88
lib/controller/message/chat_controller.dart

@ -35,8 +35,22 @@ class ChatController extends GetxController {
//
final RxList<GiftProductModel> giftProducts = <GiftProductModel>[].obs;
// 508ID集合
final Set<String> _roseErrorMessageIds = <String>{};
//
final NetworkService _networkService = NetworkService();
/// 508
bool shouldShowRoseError(String messageId) {
return _roseErrorMessageIds.contains(messageId);
}
/// 508ID
void addRoseErrorMessageId(String messageId) {
_roseErrorMessageIds.add(messageId);
update();
}
ChatController({
required this.userId,
@ -356,12 +370,17 @@ class ChatController extends GetxController {
//
final index = messages.indexWhere((msg) => msg.msgId == tempMessage!.msgId);
if (index != -1) {
// SDK FAIL
Future.delayed(Duration(milliseconds: 300), () {
update();
});
// EMMessage对象的状态是只读的SDK的onError回调来更新
// UIonError回调更新实际的消息对象
update();
//
_refreshConversationList();
if (Get.isLogEnable) {
Get.log('❌ [ChatController] 消息发送失败,等待onError回调更新状态: msgId=${tempMessage.msgId}');
}
}
update();
SmartDialog.showToast('消息发送失败,请点击重发');
return false;
}
@ -684,16 +703,50 @@ class ChatController extends GetxController {
}
}
} else {
//
messages.assignAll(validMessages);
//
// msgId更大的
//
_roseErrorMessageIds.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 {
// 使keymsgId改变的情况
contentKey = '${msg.direction}_${msg.serverTime}_${msg.body.type}';
}
// msgId
if (contentToMessage.containsKey(contentKey)) {
final existingMsg = contentToMessage[contentKey]!;
// msgIdID更大
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));
messages.assignAll(deduplicatedMessages);
// ID
if (validMessages.isNotEmpty) {
_cursor = validMessages.first.msgId;
if (deduplicatedMessages.isNotEmpty) {
_cursor = deduplicatedMessages.first.msgId;
} else {
_cursor = null;
}
if (Get.isLogEnable) {
Get.log('刷新消息成功,数量: ${validMessages.length}');
Get.log('刷新消息成功,去重前: ${validMessages.length} 条,去重后: ${deduplicatedMessages.length}');
}
}
@ -969,6 +1022,20 @@ class ChatController extends GetxController {
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) {
@ -1086,6 +1153,7 @@ class ChatController extends GetxController {
} else {
// PROGRESSSDK内部状态可能已变化
// 使
messages[currentIndex] = latestMessage;
update();
}

135
lib/controller/message/conversation_controller.dart

@ -2,6 +2,7 @@ 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';
import 'chat_controller.dart';
//
class ExtendedUserInfo {
@ -157,26 +158,42 @@ class ConversationController extends GetxController {
//
if (_userInfoCache.containsKey(targetUserId)) continue;
// 20
//
final messages = await conversation.loadMessages(
loadCount: 20,
loadCount: 50, // 2050
);
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;
@ -186,17 +203,16 @@ class ConversationController extends GetxController {
final avatarUrl = attributes['sender_avatarUrl'] as String? ?? attributes['avatarUrl'] as String?;
if (nickName != null || avatarUrl != null) {
final extendedInfo = ExtendedUserInfo(
//
foundUserInfo = ExtendedUserInfo(
userId: targetUserId,
nickName: nickName,
avatarUrl: avatarUrl,
);
_userInfoCache[targetUserId] = extendedInfo;
if (Get.isLogEnable) {
Get.log('✅ [ConversationController] 从接收消息恢复对方用户信息: userId=$targetUserId, nickName=$nickName');
Get.log('✅ [ConversationController] 从接收消息找到用户信息: userId=$targetUserId, nickName=$nickName, msgId=${message.msgId}');
}
//
//
break;
}
}
@ -209,22 +225,46 @@ class ConversationController extends GetxController {
final avatarUrl = attributes['receiver_avatarUrl'] as String?;
if (nickName != null || avatarUrl != null) {
final extendedInfo = ExtendedUserInfo(
//
foundUserInfo = ExtendedUserInfo(
userId: targetUserId,
nickName: nickName,
avatarUrl: avatarUrl,
);
_userInfoCache[targetUserId] = extendedInfo;
if (Get.isLogEnable) {
Get.log('✅ [ConversationController] 从发送消息恢复对方用户信息: userId=$targetUserId, nickName=$nickName');
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');
@ -256,21 +296,30 @@ class ConversationController extends GetxController {
///
String getLastMessageContent(EMMessage? message) {
if(message?.body.type == MessageType.TXT){
final body = message?.body as EMTextMessageBody;
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){
}else if(message.body.type == MessageType.IMAGE){
return '[图片]';
}else if(message?.body.type == MessageType.VOICE){
}else if(message.body.type == MessageType.VOICE){
return '[语音]';
}else if(message?.body.type == MessageType.VIDEO){
}else if(message.body.type == MessageType.VIDEO){
return '[视频]';
}else if(message?.body.type == MessageType.FILE){
}else if(message.body.type == MessageType.FILE){
return '[文件]';
}else if(message?.body.type == MessageType.LOCATION){
}else if(message.body.type == MessageType.LOCATION){
return '[位置]';
}else if(message?.body.type == MessageType.CUSTOM){
final body = message?.body as EMCustomMessageBody;
}else if(message.body.type == MessageType.CUSTOM){
final body = message.body as EMCustomMessageBody;
//
if(body.event == 'live_room_invite'){
return '[分享房间]';
@ -339,9 +388,9 @@ class ConversationController extends GetxController {
createIfNeed: false,
);
if (conversation != null) {
// 20
//
final messages = await conversation.loadMessages(
loadCount: 20,
loadCount: 50, // 2050
);
//
@ -484,7 +533,45 @@ class ConversationController extends GetxController {
}
Future<EMMessage?> lastMessage(EMConversation conversation) async{
return await conversation.latestMessage();
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;
}
}
///

5
lib/generated/assets.dart

@ -211,5 +211,8 @@ class Assets {
static const String imagesWallet = 'assets/images/wallet.png';
static const String imagesWechatPay = 'assets/images/wechat_pay.png';
static const String imagesWomenIcon = 'assets/images/women_icon.png';
static const String imagesAppLogo = 'assets/images/app_logo.png';
static const String imagesFilterIcon = 'assets/images/filter_icon.png';
static const String imagesLastMsgIcon = 'assets/images/last_msg_icon.png';
static const String imagesOnlineMsgIcon = 'assets/images/online_msg_icon.png';
static const String imagesUnreadIcon = 'assets/images/unread_icon.png';
}

154
lib/im/im_manager.dart

@ -139,6 +139,94 @@ class IMManager {
onError: (str, message, err){
//code: 508
Get.log('❌ [IMManager] 发送消息失败: $err----$str');
// 508
try {
final errorCode = err.code;
// ChatController
final targetId = message.to;
if (targetId != null) {
final controller = _activeChatControllers[targetId];
if (controller != null) {
//
final index = controller.messages.indexWhere((msg) => msg.msgId == message.msgId);
if (index != -1) {
//
controller.messages[index] = message;
// 508attributes
if (errorCode == 508) {
controller.addRoseErrorMessageId(message.msgId);
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 检测到错误码508(玫瑰不足),已添加到临时错误提示集合: msgId=${message.msgId}');
}
}
controller.update();
//
try {
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
conversationController.refreshConversations();
}
} catch (e) {
// ConversationController
}
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 已通知ChatController更新消息状态: $targetId, status=FAIL');
}
} else {
// ID可能改变的情况
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
final contentIndex = controller.messages.indexWhere((msg) =>
msg.body.type == MessageType.TXT &&
(msg.body as EMTextMessageBody).content == content &&
msg.direction == MessageDirection.SEND &&
msg.status == MessageStatus.PROGRESS
);
if (contentIndex != -1) {
//
final matchedMessage = controller.messages[contentIndex];
controller.messages[contentIndex] = message;
// 508使ID
if (errorCode == 508) {
controller.addRoseErrorMessageId(matchedMessage.msgId);
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 检测到错误码508(玫瑰不足),已通过内容匹配添加到临时错误提示集合: msgId=${matchedMessage.msgId}');
}
}
controller.update();
//
try {
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
conversationController.refreshConversations();
}
} catch (e) {
// ConversationController
}
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 已通过内容匹配更新消息状态: $targetId, status=FAIL');
}
}
}
}
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 处理错误码失败: $e');
}
}
},
));
_listenersRegistered = true;
@ -447,6 +535,9 @@ class IMManager {
params: data,
);
//
_addUserInfoToMessageExt(customMsg);
return await EMClient.getInstance.chatManager.sendMessage(customMsg);
}
@ -637,6 +728,25 @@ class IMManager {
//
receiverNickName = receiverChatController.userData!.nickName;
receiverAvatarUrl = receiverChatController.userData!.profilePhoto;
} else {
// ChatController userData ConversationController
try {
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
final cachedUserInfo = conversationController.getCachedUserInfo(receiverId);
if (cachedUserInfo != null) {
receiverNickName = cachedUserInfo.nickName;
receiverAvatarUrl = cachedUserInfo.avatarUrl;
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 从ConversationController缓存获取接收者信息: userId=$receiverId, nickName=$receiverNickName');
}
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 从ConversationController获取接收者信息失败: $e');
}
}
}
}
@ -657,6 +767,9 @@ class IMManager {
final cleanSenderAvatarUrl = senderAvatarUrl.trim().replaceAll('`', '');
message.attributes!['sender_avatarUrl'] = cleanSenderAvatarUrl;
}
// 线线
message.attributes!['sender_isOnline'] = 'true';
message.attributes!['sender_lastActiveTime'] = DateTime.now().millisecondsSinceEpoch.toString();
// receiver_
if (receiverId != null && receiverId.isNotEmpty) {
@ -961,9 +1074,41 @@ class IMManager {
if (targetId != null) {
final controller = _activeChatControllers[targetId];
if (controller != null) {
// TODO: ChatController
if (Get.isLogEnable) {
Get.log('通知消息状态变更: $targetId, status=$status');
//
final index = controller.messages.indexWhere((msg) => msg.msgId == message.msgId);
if (index != -1) {
//
final updatedMessage = message;
controller.messages[index] = updatedMessage;
controller.update();
if (Get.isLogEnable) {
final errorCode = error?.code;
Get.log('✅ [IMManager] 已通知ChatController更新消息状态: $targetId, status=$status, errorCode=$errorCode');
}
} else {
// ID可能改变的情况
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
final contentIndex = controller.messages.indexWhere((msg) =>
msg.body.type == MessageType.TXT &&
(msg.body as EMTextMessageBody).content == content &&
msg.direction == MessageDirection.SEND &&
msg.status == MessageStatus.PROGRESS
);
if (contentIndex != -1) {
//
final updatedMessage = message;
controller.messages[contentIndex] = updatedMessage;
controller.update();
if (Get.isLogEnable) {
final errorCode = error?.code;
Get.log('✅ [IMManager] 已通过内容匹配更新消息状态: $targetId, status=$status, errorCode=$errorCode');
}
}
}
}
}
}
@ -1157,6 +1302,9 @@ class IMManager {
return null;
}
//
_addUserInfoToMessageExt(newMessage);
//
final result = await EMClient.getInstance.chatManager.sendMessage(newMessage);
if (Get.isLogEnable) {

211
lib/pages/message/chat_page.dart

@ -15,6 +15,7 @@ import '../../../widget/message/chat_gift_popup.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'chat_settings_page.dart';
import '../home/user_information_page.dart';
import '../../../widget/live/live_recharge_popup.dart';
class ChatPage extends StatefulWidget {
final String userId; // MarriageData.userId
@ -91,6 +92,113 @@ class _ChatPageState extends State<ChatPage> {
super.dispose();
}
// 线
bool _isUserOnline(ChatController controller) {
try {
// userData
// 线线
if (widget.userData != null) {
// userData中读取在线状态的逻辑
// 线true
return true;
}
// userData线
//
final receiveMessages = controller.messages
.where((msg) => msg.direction == MessageDirection.RECEIVE)
.toList();
if (receiveMessages.isNotEmpty) {
//
final lastMessage = receiveMessages.last;
// 线
Map<String, dynamic>? attributes;
try {
attributes = lastMessage.attributes;
} catch (e) {
return false;
}
if (attributes != null && attributes.isNotEmpty) {
// sender_isOnline
final isOnlineStr = attributes['sender_isOnline'] as String?;
if (isOnlineStr == 'true') {
// 5线
final lastActiveTimeStr = attributes['sender_lastActiveTime'] as String?;
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;
}
}
}
}
//
// 线
if (controller.messages.isNotEmpty) {
//
final sortedMessages = List<EMMessage>.from(controller.messages)
..sort((a, b) {
final timeA = a.serverTime;
final timeB = b.serverTime;
return timeB.compareTo(timeA);
});
for (final message in sortedMessages) {
Map<String, dynamic>? attributes;
try {
attributes = message.attributes;
} catch (e) {
continue;
}
if (attributes != null && attributes.isNotEmpty) {
// 线
final senderIsOnline = attributes['sender_isOnline'] as String?;
final receiverIsOnline = attributes['receiver_isOnline'] as String?;
final isOnlineStr = senderIsOnline ?? receiverIsOnline;
if (isOnlineStr == 'true') {
final senderLastActiveTime = attributes['sender_lastActiveTime'] as String?;
final receiverLastActiveTime = attributes['receiver_lastActiveTime'] as String?;
final lastActiveTimeStr = senderLastActiveTime ?? receiverLastActiveTime;
if (lastActiveTimeStr != null) {
try {
final lastActiveTime = int.parse(lastActiveTimeStr);
final now = DateTime.now().millisecondsSinceEpoch;
final diff = now - lastActiveTime;
// 5线
if (diff < 5 * 60 * 1000) {
return true;
}
} catch (e) {
return true;
}
} else {
return true;
}
}
}
}
}
return false;
} catch (e) {
return false;
}
}
//
void _showGiftPopup() {
final giftProducts = _controller.giftProducts.toList();
@ -137,8 +245,38 @@ class _ChatPageState extends State<ChatPage> {
child: Scaffold(
backgroundColor: Color(0xffF5F5F5),
appBar: AppBar(
title: Text(controller.userNickName ?? widget.userData?.nickName ?? ''),
centerTitle: true,
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
controller.userNickName ?? widget.userData?.nickName ?? '',
style: TextStyle(fontSize: 18.sp),
),
if (_isUserOnline(controller))
SizedBox(width: 8.w),
// 线线
if (_isUserOnline(controller))
Container(
width: 46.w,
height: 26.h,
// padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 1.h),
decoration: BoxDecoration(
color: Color.fromRGBO(234, 255, 219, 0.5),
borderRadius: BorderRadius.circular(178),
),
child: Center(
child: Text(
'在线',
style: TextStyle(
fontSize: 12.sp,
color: Color.fromRGBO(38, 199, 124, 1),
),
),
),
),
],
),
centerTitle: false,
actions: [
Container(
padding: EdgeInsets.only(right: 16.w),
@ -196,13 +334,25 @@ class _ChatPageState extends State<ChatPage> {
? controller.messages[index - 2]
: null;
// 508使
final showRoseError = isSentByMe &&
message.status == MessageStatus.FAIL &&
controller.shouldShowRoseError(message.msgId);
// 🚀 key
return MessageItem(
key: ValueKey(message.msgId),
message: message,
isSentByMe: isSentByMe,
previousMessage: previousMessage,
chatController: controller, // controller 使 Get.find
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MessageItem(
key: ValueKey(message.msgId),
message: message,
isSentByMe: isSentByMe,
previousMessage: previousMessage,
chatController: controller, // controller 使 Get.find
),
// 508
if (showRoseError) _buildRoseErrorHint(context),
],
);
},
),
@ -535,4 +685,49 @@ class _ChatPageState extends State<ChatPage> {
}
}
}
//
Widget _buildRoseErrorHint(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 8.h, bottom: 8.h),
padding: EdgeInsets.symmetric(horizontal: 16.w),
width: double.infinity,
child: Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: [
TextSpan(
text: '发送信息所需的玫瑰不足,充值获取玫瑰继续嗨聊 ',
style: TextStyle(
fontSize: 11.sp,
color: Color.fromRGBO(199, 199, 199, 1),
),
),
WidgetSpan(
child: GestureDetector(
onTap: () {
//
FocusScope.of(context).unfocus();
SmartDialog.show(
alignment: Alignment.bottomCenter,
maskColor: Colors.black.withOpacity(0.5),
builder: (_) => const LiveRechargePopup(),
);
},
child: Text(
'立即充值',
style: TextStyle(
fontSize: 11.sp,
color: Color.fromRGBO(117, 98, 249, 1),
),
),
),
),
],
),
),
),
);
}
}

34
lib/pages/message/conversation_tab.dart

@ -108,11 +108,27 @@ class _ConversationTabState extends State<ConversationTab>
height: 56,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28),
image: DecorationImage(
image: NetworkImage(userInfo!.avatarUrl!),
fit: BoxFit.cover,
),
image: (userInfo?.avatarUrl != null && userInfo!.avatarUrl!.isNotEmpty)
? DecorationImage(
image: NetworkImage(userInfo.avatarUrl!),
fit: BoxFit.cover,
)
: null,
color: (userInfo?.avatarUrl == null || (userInfo?.avatarUrl?.isEmpty ?? true))
? Colors.grey[300]
: null,
),
child: (userInfo?.avatarUrl == null || (userInfo?.avatarUrl?.isEmpty ?? true))
? ClipRRect(
borderRadius: BorderRadius.circular(28),
child: Image.asset(
Assets.imagesAvatarsExample,
width: 56,
height: 56,
fit: BoxFit.cover,
),
)
: null,
),
const SizedBox(width: 12),
Expanded(
@ -126,7 +142,7 @@ class _ConversationTabState extends State<ConversationTab>
children: [
Expanded(
child: Text(
userInfo.nickName ?? '', // ID
userInfo?.nickName ?? conversation.id, // ID
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
@ -152,9 +168,13 @@ class _ConversationTabState extends State<ConversationTab>
Expanded(
child: Text(
controller.getLastMessageContent(message),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Colors.grey,
color: (message != null &&
message.direction == MessageDirection.SEND &&
message.status == MessageStatus.FAIL)
? Color.fromRGBO(248, 85, 66, 1) //
: Colors.grey,
),
overflow: TextOverflow.ellipsis,
),

130
lib/pages/message/message_page.dart

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'conversation_tab.dart';
import 'friend_tab.dart';
@ -15,6 +14,7 @@ class MessagePage extends StatefulWidget {
class _MessagePageState extends State<MessagePage> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
late TabController _tabController;
final GlobalKey _filterButtonKey = GlobalKey();
@override
void initState() {
@ -67,17 +67,16 @@ class _MessagePageState extends State<MessagePage> with AutomaticKeepAliveClient
],
),
actions: [
//
//
GestureDetector(
onTap: () {
//
},
key: _filterButtonKey,
onTap: () => _showFilterMenu(context),
child: Container(
width: 36,
height: 36,
width: 20,
height: 20,
margin: const EdgeInsets.only(right: 16),
alignment: Alignment.center,
child: Image.asset(Assets.imagesSearch, width: 16.w,),
child: Image.asset(Assets.imagesFilterIcon, width: 20,),
),
),
],
@ -123,6 +122,121 @@ class _MessagePageState extends State<MessagePage> with AutomaticKeepAliveClient
);
}
//
void _showFilterMenu(BuildContext context) {
final RenderBox? button = _filterButtonKey.currentContext?.findRenderObject() as RenderBox?;
final RenderBox? overlay = Overlay.of(context).context.findRenderObject() as RenderBox?;
if (button == null || overlay == null) return;
final Offset buttonPosition = button.localToGlobal(Offset.zero);
final Size buttonSize = button.size;
//
final double menuWidth = 160.0; //
final double screenWidth = overlay.size.width;
final double rightPadding = screenWidth - buttonPosition.dx - buttonSize.width;
final RelativeRect position = RelativeRect.fromLTRB(
screenWidth - rightPadding - menuWidth, // left
buttonPosition.dy + buttonSize.height + 4, // top (4px)
rightPadding, // right
overlay.size.height - (buttonPosition.dy + buttonSize.height + 4), // bottom
);
showMenu(
context: context,
position: position,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
items: [
PopupMenuItem(
padding: EdgeInsets.zero,
child: _buildFilterMenuItem(
icon: Assets.imagesLastMsgIcon,
text: '最后聊天时间',
showDivider: true,
onTap: () {
Navigator.pop(context);
// TODO:
},
),
),
PopupMenuItem(
padding: EdgeInsets.zero,
child: _buildFilterMenuItem(
icon: Assets.imagesUnreadIcon,
text: '未读消息',
showDivider: true,
onTap: () {
Navigator.pop(context);
// TODO:
},
),
),
PopupMenuItem(
padding: EdgeInsets.zero,
child: _buildFilterMenuItem(
icon: Assets.imagesOnlineMsgIcon,
text: '当前在线',
showDivider: false,
onTap: () {
Navigator.pop(context);
// TODO: 线
},
),
),
],
);
}
//
Widget _buildFilterMenuItem({
required String icon,
required String text,
required bool showDivider,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
child: Row(
children: [
Image.asset(
icon,
width: 20,
height: 20,
),
const SizedBox(width: 12),
Text(
text,
style: const TextStyle(
fontSize: 14,
color: Colors.black,
),
),
],
),
),
if (showDivider)
Divider(
height: 0.5,
thickness: 0.5,
color: Colors.grey[300],
indent: 10,
endIndent: 10,
),
],
),
);
}
// Tab内容区域

10
lib/widget/message/message_item.dart

@ -176,9 +176,19 @@ class MessageItem extends StatelessWidget {
return VoiceItem(
voiceBody: voiceBody,
messageId: message.msgId, // ID作为音频唯一标识
message: message, //
isSentByMe: isSentByMe,
showTime: shouldShowTime(),
formattedTime: formatMessageTime(message.serverTime),
onResend: () {
// controller Get ChatController
try {
final controller = chatController ?? Get.find<ChatController>();
controller.resendMessage(message);
} catch (e) {
print('重发消息失败: $e');
}
},
);
}
//

4
lib/widget/message/voice_item.dart

@ -14,16 +14,20 @@ import '../../../controller/message/voice_player_manager.dart';
class VoiceItem extends StatefulWidget {
final EMVoiceMessageBody voiceBody;
final String messageId; // ID
final EMMessage? message; //
final bool isSentByMe;
final bool showTime;
final String formattedTime;
final VoidCallback? onResend; //
const VoiceItem({
required this.voiceBody,
required this.messageId,
this.message,
required this.isSentByMe,
required this.showTime,
required this.formattedTime,
this.onResend,
super.key,
});

Loading…
Cancel
Save