From 23c5316d187037b1ce1caf46cd43f770d041b124 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 11 Nov 2025 22:03:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(message):=20=E5=AE=9E=E7=8E=B0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=B6=88=E6=81=AF=E5=B1=95=E7=A4=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?-=20=E6=96=B0=E5=A2=9E=20ImageItem=20=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=B1=95=E7=A4=BA=E5=9B=BE=E7=89=87=E6=B6=88?= =?UTF-8?q?=E6=81=AF=20-=20=E6=94=AF=E6=8C=81=E7=BD=91=E7=BB=9C=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=8A=A0=E8=BD=BD=E4=B8=8E=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=20-=20=E6=B7=BB=E5=8A=A0=E5=9B=BE=E7=89=87=E5=B0=BA?= =?UTF-8?q?=E5=AF=B8=E8=87=AA=E9=80=82=E5=BA=94=E9=80=BB=E8=BE=91-=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E6=B0=94=E6=B3=A1=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E4=B8=8E=E5=B8=83=E5=B1=80=20-=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E6=8E=A8=E8=8D=90=E7=94=A8=E6=88=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BB=A3=E7=A0=81-=20=E5=88=A0=E9=99=A4=20IM=20?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=90=8E=E7=9A=84=E6=B5=8B=E8=AF=95=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8F=91=E9=80=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/im/im_manager.dart | 1 - lib/pages/message/conversation_tab.dart | 73 --------- lib/widget/message/image_item.dart | 195 ++++++++++++++++++++++++ lib/widget/message/message_item.dart | 80 +++------- 4 files changed, 214 insertions(+), 135 deletions(-) create mode 100644 lib/widget/message/image_item.dart diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index d5715dc..c7b936f 100644 --- a/lib/im/im_manager.dart +++ b/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'); diff --git a/lib/pages/message/conversation_tab.dart b/lib/pages/message/conversation_tab.dart index 01cbd75..f15fd1d 100644 --- a/lib/pages/message/conversation_tab.dart +++ b/lib/pages/message/conversation_tab.dart @@ -16,24 +16,12 @@ class ConversationTab extends StatefulWidget { class _ConversationTabState extends State with AutomaticKeepAliveClientMixin { final ConversationController controller = Get.find(); - - // 模拟数据 - 顶部推荐用户列表 - final List> _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 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获取未读消息数和最新消息 diff --git a/lib/widget/message/image_item.dart b/lib/widget/message/image_item.dart new file mode 100644 index 0000000..f688521 --- /dev/null +++ b/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], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widget/message/message_item.dart b/lib/widget/message/message_item.dart index 30db38a..a879f2d 100644 --- a/lib/widget/message/message_item.dart +++ b/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, - ), - ), - ); - } + } \ No newline at end of file