Browse Source

feat(message): 实现消息时间显示和消息项组件化

- 将聊天页面中的消息项构建逻辑提取为独立的 MessageItem 组件- 添加消息时间显示功能,超过20分钟间隔则显示时间标签
- 支持今天、昨天和其他日期的时间格式化显示- 优化消息气泡样式,区分发送方和接收方背景色与文字颜色
- 移除聊天页面中原有的消息构建方法,使用新组件替代
-修复消息列表中时间戳显示不准确的问题
ios
Jolie 4 months ago
parent
commit
a8cab1abfc
2 changed files with 163 additions and 71 deletions
  1. 79
      lib/pages/message/chat_page.dart
  2. 155
      lib/widget/message/message_item.dart

79
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,
);
},
),
),

155
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; //
}
// 201200000
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,
),
),
);
}
}
Loading…
Cancel
Save