Browse Source

feat(message): 实现消息页面UI优化与文本消息组件

- 在聊天控制器中添加update()调用以通知UI更新- 调整聊天页面背景颜色并增加列表内边距- 修复消息方向判断逻辑,正确显示发送/接收状态
- 完善IM管理器中的连接事件处理器代码格式
- 移除消息类型处理中的空实现块
-优化发送文本消息的返回逻辑
- 格式化用户信息获取方法参数
- 修正消息气泡圆角样式,统一底部圆角- 新增独立的文本消息展示组件TextItem- 实现带时间戳和头像的文本消息渲染
- 支持根据发送方调整消息布局和样式
ios
Jolie 4 months ago
parent
commit
e9b21b5bf3
5 changed files with 167 additions and 64 deletions
  1. 3
      lib/controller/message/chat_controller.dart
  2. 87
      lib/im/im_manager.dart
  3. 32
      lib/pages/message/chat_page.dart
  4. 2
      lib/widget/message/message_item.dart
  5. 107
      lib/widget/message/text_item.dart

3
lib/controller/message/chat_controller.dart

@ -84,6 +84,9 @@ class ChatController extends GetxController {
messages.assignAll(validMessages);
}
// UI更新
update();
//
if (validMessages.isNotEmpty) {
_cursor = validMessages.last.msgId;

87
lib/im/im_manager.dart

@ -9,7 +9,7 @@ class IMManager {
final storage = GetStorage();
// getter用于instance访问
static IMManager get instance => _instance;
bool _isInitialized = false;
IMManager._internal() {
@ -48,22 +48,24 @@ class IMManager {
try {
//
//
EMClient.getInstance.addConnectionEventHandler('', EMConnectionEventHandler(
onConnected: () {
print('Connected to IM server');
},
onDisconnected: () {
print('Disconnected from IM server:');
},
onTokenDidExpire: () {
print('IM token about to expire');
},
onUserKickedByOtherDevice: () {
print('User kicked out of IM server');
},
));
EMClient.getInstance.addConnectionEventHandler(
'',
EMConnectionEventHandler(
onConnected: () {
print('Connected to IM server');
},
onDisconnected: () {
print('Disconnected from IM server:');
},
onTokenDidExpire: () {
print('IM token about to expire');
},
onUserKickedByOtherDevice: () {
print('User kicked out of IM server');
},
),
);
EMClient.getInstance.chatManager.addEventHandler(
// EMChatEventHandler key
"",
@ -72,44 +74,28 @@ class IMManager {
for (var msg in messages) {
switch (msg.body.type) {
case MessageType.TXT:
{
}
{}
break;
case MessageType.IMAGE:
{
}
{}
break;
case MessageType.VIDEO:
{
}
{}
break;
case MessageType.LOCATION:
{
}
{}
break;
case MessageType.VOICE:
{
}
{}
break;
case MessageType.FILE:
{
}
{}
break;
case MessageType.CUSTOM:
{
}
{}
break;
case MessageType.COMBINE:
{
}
{}
break;
case MessageType.CMD:
{
@ -172,10 +158,8 @@ class IMManager {
content: content,
);
//
await EMClient.getInstance.chatManager.sendMessage(message);
print('Text message sent successfully');
return message;
return await EMClient.getInstance.chatManager.sendMessage(message);
} catch (e) {
print('Failed to send text message: $e');
return null;
@ -212,7 +196,9 @@ class IMManager {
///
Future<Map<String, EMUserInfo>> getContacts(String userId) async {
return await EMClient.getInstance.userInfoManager.fetchUserInfoById([userId]);
return await EMClient.getInstance.userInfoManager.fetchUserInfoById([
userId,
]);
}
///
@ -222,15 +208,20 @@ class IMManager {
String? startMsgId,
}) async {
EMConversationType convType = EMConversationType.Chat;
EMCursorResult<EMMessage?> cursor = await EMClient.getInstance.chatManager.fetchHistoryMessagesByOption(
conversationId, convType, pageSize: pageSize
);
EMCursorResult<EMMessage?> cursor = await EMClient.getInstance.chatManager
.fetchHistoryMessagesByOption(
conversationId,
convType,
pageSize: pageSize,
);
return cursor.data;
}
///
Future<EMUserInfo?> getUserInfo(String userId) async {
var data = await EMClient.getInstance.userInfoManager.fetchUserInfoById([userId]);
var data = await EMClient.getInstance.userInfoManager.fetchUserInfoById([
userId,
]);
return data[userId];
}

32
lib/pages/message/chat_page.dart

@ -48,22 +48,24 @@ class ChatPage extends StatelessWidget {
//
Expanded(
child: Container(
alignment: Alignment.topCenter,
color: Color(0xffF5F5F5),
child: ListView.builder(
reverse: true,
itemCount: controller.messages.length,
itemBuilder: (context, index) {
final message = controller.messages[index];
final isSentByMe = message.direction == MessageDirection.SEND;
//
final previousMessage = index > 0 ? controller.messages[index - 1] : null;
return MessageItem(
message: message,
isSentByMe: false,
previousMessage: previousMessage,
);
},
),
reverse: true,
padding: EdgeInsets.all(16.w),
itemCount: controller.messages.length,
itemBuilder: (context, index) {
final message = controller.messages[index];
final isSentByMe = message.direction == MessageDirection.SEND;
//
final previousMessage = index > 0 ? controller.messages[index - 1] : null;
return MessageItem(
message: message,
isSentByMe: isSentByMe,
previousMessage: previousMessage,
);
},
),
),
),
// 使

2
lib/widget/message/message_item.dart

@ -49,7 +49,7 @@ class MessageItem extends StatelessWidget {
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),
bottomRight: Radius.circular(12.w),
),
),
child: Text(

107
lib/widget/message/text_item.dart

@ -0,0 +1,107 @@
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 TextItem extends StatelessWidget {
final EMTextMessageBody textBody;
final bool isSentByMe;
final bool showTime;
final String formattedTime;
const TextItem({
required this.textBody,
required this.isSentByMe,
required this.showTime,
required this.formattedTime,
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
//
if (showTime) _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(12.w),
),
),
child: Text(
textBody.content,
style: TextStyle(
fontSize: 14.sp,
color: isSentByMe ? Colors.white : Colors.black, //
),
),
),
if (isSentByMe) SizedBox(width: 8.w),
if (isSentByMe) _buildAvatar(),
],
),
),
],
);
}
//
Widget _buildTimeLabel() {
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,
),
),
),
);
}
//
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