Browse Source

feat(message): 实现图片消息展示功能- 新增 ImageItem 组件用于展示图片消息

- 支持网络图片加载与错误处理
- 添加图片尺寸自适应逻辑- 优化消息气泡样式与布局
- 移除模拟推荐用户列表代码- 删除 IM 登录后的测试消息发送逻辑
ios
Jolie 4 months ago
parent
commit
23c5316d18
4 changed files with 214 additions and 135 deletions
  1. 1
      lib/im/im_manager.dart
  2. 73
      lib/pages/message/conversation_tab.dart
  3. 195
      lib/widget/message/image_item.dart
  4. 80
      lib/widget/message/message_item.dart

1
lib/im/im_manager.dart

@ -122,7 +122,6 @@ class IMManager {
var userId = storage.read('userId');
await EMClient.getInstance.logout();
await EMClient.getInstance.loginWithToken(userId, token);
await sendTextMessage('哈哈哈哈', '1114267797208305664');
//
_registerListeners();
print('IM login successful');

73
lib/pages/message/conversation_tab.dart

@ -16,24 +16,12 @@ class ConversationTab extends StatefulWidget {
class _ConversationTabState extends State<ConversationTab> with AutomaticKeepAliveClientMixin {
final ConversationController controller = Get.find<ConversationController>();
// -
final List<Map<String, dynamic>> _recommendedUsers = [
{"id": 1, "avatar": Assets.imagesAvatarsExample, "type": "相亲"},
{"id": 2, "avatar": Assets.imagesAvatarsExample, "type": "交友"},
{"id": 3, "avatar": Assets.imagesAvatarsExample, "type": "相亲"},
{"id": 4, "avatar": Assets.imagesAvatarsExample, "type": "相亲"},
{"id": 5, "avatar": Assets.imagesAvatarsExample, "type": "相亲"},
{"id": 6, "avatar": Assets.imagesAvatarsExample, "type": "相亲"},
];
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: [
//
_buildRecommendedUsers(),
//
Expanded(
child: Obx(() {
@ -74,67 +62,6 @@ class _ConversationTabState extends State<ConversationTab> with AutomaticKeepAli
);
}
//
Widget _buildRecommendedUsers() {
return SizedBox(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _recommendedUsers.length,
itemBuilder: (context, index) {
final user = _recommendedUsers[index];
final bool isSelected = index == 1; //
return Container(
margin: const EdgeInsets.only(right: 12),
child: Column(
children: [
Stack(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
border: Border.all(
color: isSelected ? Colors.blue : Colors.transparent,
width: 2,
),
borderRadius: BorderRadius.circular(30),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Image.asset(user["avatar"], fit: BoxFit.cover),
),
),
if (user["type"] == "交友")
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.people,
size: 12,
color: Colors.white,
),
),
),
],
),
],
),
);
},
),
);
}
//
Widget _buildConversationItem(EMConversation conversation) {
// 使FutureBuilder获取未读消息数和最新消息

195
lib/widget/message/image_item.dart

@ -0,0 +1,195 @@
import 'dart:io';
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
class ImageItem extends StatelessWidget {
final EMImageMessageBody imageBody;
final bool isSentByMe;
final bool showTime;
final String formattedTime;
const ImageItem({
required this.imageBody,
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(
margin: EdgeInsets.only(top: 10.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: _buildImage(),
),
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,
),
),
);
}
//
Widget _buildImage() {
// 200
double maxWidth = 200.w;
double maxHeight = 200.w;
double width = maxWidth;
double height = maxHeight;
Get.log(imageBody.thumbnailLocalPath ?? '');
//
if (imageBody.width != null && imageBody.width! > 0 &&
imageBody.height != null && imageBody.height! > 0) {
final aspectRatio = imageBody.width! / imageBody.height!;
if (aspectRatio > 1) {
//
width = maxWidth;
height = maxWidth / aspectRatio;
} else {
//
height = maxHeight;
width = maxHeight * aspectRatio;
}
}
//
if (imageBody.thumbnailRemotePath != null && imageBody.thumbnailRemotePath!.isNotEmpty) {
return Image.network(
imageBody.thumbnailRemotePath!,
width: width,
height: height,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return _buildLoadingContainer(width, height);
},
errorBuilder: (context, error, stackTrace) {
return _buildErrorContainer(width, height);
},
);
}
//
if (imageBody.remotePath != null && imageBody.remotePath!.isNotEmpty) {
return Image.network(
imageBody.remotePath!,
width: width,
height: height,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return _buildLoadingContainer(width, height);
},
errorBuilder: (context, error, stackTrace) {
return _buildErrorContainer(width, height);
},
);
}
//
return _buildErrorContainer(width, height);
}
//
Widget _buildLoadingContainer(double width, double height) {
return Container(
width: width,
height: height,
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
color: Colors.grey[200],
),
alignment: Alignment.center,
child: CircularProgressIndicator(
strokeWidth: 2.w,
color: Colors.grey[400],
),
);
}
//
Widget _buildErrorContainer(double width, double height) {
return Container(
width: width,
height: height,
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
color: Colors.grey[200],
),
alignment: Alignment.center,
child: Icon(
Icons.image_not_supported,
size: 32.w,
color: Colors.grey[400],
),
);
}
}

80
lib/widget/message/message_item.dart

@ -2,7 +2,8 @@ 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';
import 'text_item.dart';
import 'image_item.dart';
class MessageItem extends StatelessWidget {
final EMMessage message;
@ -18,54 +19,24 @@ class MessageItem extends StatelessWidget {
@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(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(),
],
),
),
],
return TextItem(
textBody: textBody,
isSentByMe: isSentByMe,
showTime: shouldShowTime(),
formattedTime: formatMessageTime(message.serverTime),
);
}
//
else if (message.body.type == MessageType.IMAGE) {
final imageBody = message.body as EMImageMessageBody;
return ImageItem(
imageBody: imageBody,
isSentByMe: isSentByMe,
showTime: shouldShowTime(),
formattedTime: formatMessageTime(message.serverTime),
);
}
@ -138,18 +109,5 @@ class MessageItem extends StatelessWidget {
}
}
//
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