From a8cab1abfc415353166b6c2c56597f24284d5c4d Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 11 Nov 2025 16:36:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(message):=20=E5=AE=9E=E7=8E=B0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=97=B6=E9=97=B4=E6=98=BE=E7=A4=BA=E5=92=8C=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=A1=B9=E7=BB=84=E4=BB=B6=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将聊天页面中的消息项构建逻辑提取为独立的 MessageItem 组件- 添加消息时间显示功能,超过20分钟间隔则显示时间标签 - 支持今天、昨天和其他日期的时间格式化显示- 优化消息气泡样式,区分发送方和接收方背景色与文字颜色 - 移除聊天页面中原有的消息构建方法,使用新组件替代 -修复消息列表中时间戳显示不准确的问题 --- lib/pages/message/chat_page.dart | 79 ++------------ lib/widget/message/message_item.dart | 155 +++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 71 deletions(-) create mode 100644 lib/widget/message/message_item.dart diff --git a/lib/pages/message/chat_page.dart b/lib/pages/message/chat_page.dart index 7290fc5..7c39780 100644 --- a/lib/pages/message/chat_page.dart +++ b/lib/pages/message/chat_page.dart @@ -7,82 +7,14 @@ import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import '../../controller/message/chat_controller.dart'; import '../../generated/assets.dart'; import '../../../widget/message/chat_input_bar.dart'; +import '../../../widget/message/message_item.dart'; class ChatPage extends StatelessWidget { final String userId; const ChatPage({required this.userId, super.key}); - // 构建消息项 - Widget _buildMessageItem(EMMessage message, bool isSentByMe) { - // 只处理文本消息,其他类型消息可以根据需要扩展 - if (message.body.type == MessageType.TXT) { - final textBody = message.body as EMTextMessageBody; - return Container( - padding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 8.h, - ), - child: Row( - mainAxisAlignment: isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!isSentByMe) _buildAvatar(), - if (!isSentByMe) SizedBox(width: 8.w), - Container( - constraints: BoxConstraints(maxWidth: 240.w), - padding: EdgeInsets.symmetric( - horizontal: 12.w, - vertical: 8.h, - ), - decoration: BoxDecoration( - color: isSentByMe ? Color(0xff98E165) : Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.w), - topRight: Radius.circular(12.w), - bottomLeft: isSentByMe ? Radius.circular(12.w) : Radius.circular(0), - bottomRight: isSentByMe ? Radius.circular(0) : Radius.circular(12.w), - ), - ), - child: Text( - textBody.content, - style: TextStyle( - fontSize: 14.sp, - color: isSentByMe ? Colors.black : Colors.black, - ), - ), - ), - if (isSentByMe) SizedBox(width: 8.w), - if (isSentByMe) _buildAvatar(), - ], - ), - ); - } - - // 非文本消息显示占位符 - return Container( - padding: EdgeInsets.symmetric( - horizontal: 16.w, - vertical: 8.h, - ), - child: Text('不支持的消息类型'), - ); - } - // 构建头像 - Widget _buildAvatar() { - return Container( - width: 40.w, - height: 40.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20.w), - image: DecorationImage( - image: AssetImage(Assets.imagesAvatarsExample), - fit: BoxFit.cover, - ), - ), - ); - } @override Widget build(BuildContext context) { @@ -122,9 +54,14 @@ class ChatPage extends StatelessWidget { itemCount: controller.messages.length, itemBuilder: (context, index) { final message = controller.messages[index]; - print('message: $message'); final isSentByMe = message.direction == MessageDirection.SEND; - return _buildMessageItem(message, isSentByMe); + // 获取上一条消息(如果存在) + final previousMessage = index > 0 ? controller.messages[index - 1] : null; + return MessageItem( + message: message, + isSentByMe: false, + previousMessage: previousMessage, + ); }, ), ), diff --git a/lib/widget/message/message_item.dart b/lib/widget/message/message_item.dart new file mode 100644 index 0000000..309ca23 --- /dev/null +++ b/lib/widget/message/message_item.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:im_flutter_sdk/im_flutter_sdk.dart'; + +import '../../../generated/assets.dart'; + +class MessageItem extends StatelessWidget { + final EMMessage message; + final bool isSentByMe; + final EMMessage? previousMessage; + + const MessageItem({ + required this.message, + required this.isSentByMe, + this.previousMessage, + super.key, + }); + + @override + Widget build(BuildContext context) { + // 只处理文本消息,其他类型消息可以根据需要扩展 + if (message.body.type == MessageType.TXT) { + final textBody = message.body as EMTextMessageBody; + return Column( + children: [ + // 显示时间(如果距离上一条消息超过20分钟) + if (shouldShowTime()) _buildTimeLabel(), + Container( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 8.h, + ), + child: Row( + mainAxisAlignment: isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!isSentByMe) _buildAvatar(), + if (!isSentByMe) SizedBox(width: 8.w), + Container( + constraints: BoxConstraints(maxWidth: 240.w), + margin: EdgeInsets.only(top: 10.h), + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 8.h, + ), + decoration: BoxDecoration( + color: isSentByMe ? Color(0xff8E7BF6) : Colors.white, // 修改为紫色和白色 + borderRadius: BorderRadius.only( + topLeft: isSentByMe ? Radius.circular(12.w) : Radius.circular(0), + topRight: isSentByMe ? Radius.circular(0) : Radius.circular(12.w), + bottomLeft: Radius.circular(12.w), + bottomRight: Radius.circular(0), + ), + ), + child: Text( + textBody.content, + style: TextStyle( + fontSize: 14.sp, + color: isSentByMe ? Colors.white : Colors.black, // 发送方白色文字,接收方黑色文字 + ), + ), + ), + if (isSentByMe) SizedBox(width: 8.w), + if (isSentByMe) _buildAvatar(), + ], + ), + ), + ], + ); + } + + // 非文本消息显示占位符 + return Column( + children: [ + if (shouldShowTime()) _buildTimeLabel(), + Container( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 8.h, + ), + child: Text('不支持的消息类型'), + ), + ], + ); + } + + // 判断是否需要显示时间 + bool shouldShowTime() { + if (previousMessage == null) { + return true; // 第一条消息显示时间 + } + + // 判断距离上一条消息是否超过20分钟(1200000毫秒) + return (message.serverTime - previousMessage!.serverTime) > 1200000; + } + + // 构建时间标签 + Widget _buildTimeLabel() { + final formattedTime = formatMessageTime(message.serverTime); + + return Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + horizontal: 16.w, + ), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + ), + child: Text( + formattedTime, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + ), + ); + } + + // 格式化消息时间 + String formatMessageTime(int timestamp) { + final date = DateTime.fromMillisecondsSinceEpoch(timestamp); + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); + final messageDate = DateTime(date.year, date.month, date.day); + + if (messageDate.isAtSameMomentAs(today)) { + // 今天的消息显示时间 + return '今天 ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; + } else if (messageDate.isAtSameMomentAs(yesterday)) { + // 昨天的消息 + return '昨天 ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; + } else { + // 其他日期显示完整日期和时间 + return '${date.month}月${date.day}日 ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; + } + } + + // 构建头像 + Widget _buildAvatar() { + return Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.w), + image: DecorationImage( + image: AssetImage(Assets.imagesAvatarsExample), + fit: BoxFit.cover, + ), + ), + ); + } +} \ No newline at end of file