From 76d30fb9c32bb8d7d720781a77328b6ed13be5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=90=E8=B4=A4?= Date: Wed, 25 Feb 2026 18:15:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0im=E5=9C=A8=E7=BA=BF=E7=9B=91?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/im/im_manager.dart | 12 +- lib/pages/message/chat_page.dart | 14 ++- lib/pages/message/conversation_tab.dart | 144 ++++++++++++++++-------- lib/pages/mine/mine_page.dart | 5 + 4 files changed, 128 insertions(+), 47 deletions(-) diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index 8520210..f95a008 100644 --- a/lib/im/im_manager.dart +++ b/lib/im/im_manager.dart @@ -61,6 +61,8 @@ class IMManager { bool _isReconnecting = false; // 是否正在重连 Completer? _initCompleter; // 用于确保并发调用时只初始化一次 + Timer? timer; + // 监听器标识符 static const String _connectionHandlerKey = 'im_manager_connection_handler'; static const String _chatHandlerKey = 'im_manager_chat_handler'; @@ -1331,6 +1333,13 @@ class IMManager { // 登录成功后,通知 ConversationController 刷新会话列表 _refreshConversationList(); + + // await EMClient.getInstance.presenceManager.publishPresence("online"); + // timer = Timer.periodic(const Duration(seconds: 30), (timer) async { + // // 每5秒执行一次 + // await EMClient.getInstance.presenceManager.publishPresence("online"); + // + // }); debugPrint('✅ IM 登录成功: $userId'); return true; } catch (e, s) { @@ -1929,7 +1938,8 @@ class IMManager { final statusDescStr = presence.statusDescription ?? ''; final statusDesc = statusDescStr.toLowerCase(); // 判断在线状态:online、available 等表示在线 - final isOnline = statusDesc == 'online' || statusDesc == 'available'; + Map detail = presence.statusDetails ?? {}; + final isOnline = detail.values.any((v) => v == 1); if (Get.isLogEnable) { Get.log( diff --git a/lib/pages/message/chat_page.dart b/lib/pages/message/chat_page.dart index 1cd3156..a157a0d 100644 --- a/lib/pages/message/chat_page.dart +++ b/lib/pages/message/chat_page.dart @@ -1,5 +1,6 @@ import 'package:dating_touchme_app/controller/discover/svga_player_manager.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/widget/live/svga_player_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -69,9 +70,18 @@ class _ChatPageState extends State { // ChatInputBar 的 GlobalKey,用于关闭底部面板 final GlobalKey> _chatInputBarKey = GlobalKey>(); + + bool isOnline = false; + @override void initState() { super.initState(); + IMManager.instance.getUserPresenceStatus(widget.userId).then((e){ + isOnline = e == true; + setState(() { + + }); + }); // 初始化 controller,直接传入用户信息参数 // 如果已存在相同 userId 的 controller,先删除再创建新的,确保参数正确 final tag = 'chat_${widget.userId}'; @@ -262,7 +272,7 @@ class _ChatPageState extends State { style: TextStyle(fontSize: 18.sp), ), // 使用 Obx 来响应在线状态的变化 - Obx(() { + if(isOnline) Obx(() { final isOnline = controller.isUserOnline.value; if (isOnline) { return Row( @@ -533,7 +543,7 @@ class _ChatPageState extends State { ), ), // 在线状态标签(和主页一样) - Container( + if(isOnline) Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 4.h, diff --git a/lib/pages/message/conversation_tab.dart b/lib/pages/message/conversation_tab.dart index 23d76a8..fb2dc42 100644 --- a/lib/pages/message/conversation_tab.dart +++ b/lib/pages/message/conversation_tab.dart @@ -1,3 +1,4 @@ +import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/pages/message/chat_page.dart'; import 'package:flutter/material.dart'; import 'package:dating_touchme_app/generated/assets.dart'; @@ -71,7 +72,7 @@ class _ConversationTabState extends State itemCount: filteredConversations.length, itemBuilder: (context, index) { final conversation = filteredConversations[index]; - return _buildConversationItem(conversation); + return BuildConversationItem(conversation: conversation); }, ); }); @@ -81,14 +82,49 @@ class _ConversationTabState extends State ); } - // 构建会话项 - Widget _buildConversationItem(EMConversation conversation) { - // 优先从缓存获取用户信息,避免闪烁 - final cachedUserInfo = controller.getCachedUserInfo(conversation.id); - + + + @override + bool get wantKeepAlive => true; +} + + +class BuildConversationItem extends StatefulWidget { + final EMConversation conversation; + const BuildConversationItem({super.key, required this.conversation}); + + @override + State createState() => _BuildConversationItemState(); +} + +class _BuildConversationItemState extends State { + + late EMConversation conversation; + + bool isOnline = false; + late var cachedUserInfo; + + + final ConversationController controller = Get.find(); + + @override + void initState() { + super.initState(); + conversation = widget.conversation; + cachedUserInfo = controller.getCachedUserInfo(conversation.id); + IMManager.instance.getUserPresenceStatus(conversation.id).then((e){ + isOnline = e == true; + setState(() { + + }); + }); + } + + @override + Widget build(BuildContext context) { return FutureBuilder( - future: cachedUserInfo != null - ? Future.value(cachedUserInfo) + future: cachedUserInfo != null + ? Future.value(cachedUserInfo) : controller.loadContact(conversation.id), initialData: cachedUserInfo, builder: (context, userSnapshot) { @@ -126,41 +162,57 @@ class _ConversationTabState extends State child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28), - color: Colors.grey[300], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(28), - child: (userInfo?.avatarUrl != null && userInfo!.avatarUrl!.isNotEmpty) - ? CachedNetworkImage( - imageUrl: userInfo.avatarUrl!, + Stack( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(28), + color: Colors.grey[300], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(28), + child: (userInfo?.avatarUrl != null && userInfo!.avatarUrl!.isNotEmpty) + ? CachedNetworkImage( + imageUrl: userInfo.avatarUrl!, + width: 56, + height: 56, + fit: BoxFit.cover, + placeholder: (context, url) => Image.asset( + Assets.imagesUserAvatar, width: 56, height: 56, fit: BoxFit.cover, - placeholder: (context, url) => Image.asset( - Assets.imagesUserAvatar, - width: 56, - height: 56, - fit: BoxFit.cover, - ), - errorWidget: (context, url, error) => Image.asset( - Assets.imagesUserAvatar, - width: 56, - height: 56, - fit: BoxFit.cover, - ), - ) - : Image.asset( + ), + errorWidget: (context, url, error) => Image.asset( Assets.imagesUserAvatar, width: 56, height: 56, fit: BoxFit.cover, ), - ), + ) + : Image.asset( + Assets.imagesUserAvatar, + width: 56, + height: 56, + fit: BoxFit.cover, + ), + ), + ), + if(isOnline) Positioned( + bottom: 0, + right: 0, + child: Container( + width: 11.w, + height: 11.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(11.w)), + color: const Color.fromRGBO(43, 255, 191, 1) + ), + ), + ) + ], ), const SizedBox(width: 12), Expanded( @@ -169,7 +221,7 @@ class _ConversationTabState extends State children: [ Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( @@ -246,16 +298,20 @@ class _ConversationTabState extends State ); } + + + + // 构建最后一条消息内容(支持表情显示) Widget _buildLastMessageContent(String content, EMMessage? message) { // 检查是否是发送失败的消息 - final isFailed = message != null && - message.direction == MessageDirection.SEND && - message.status == MessageStatus.FAIL; - + final isFailed = message != null && + message.direction == MessageDirection.SEND && + message.status == MessageStatus.FAIL; + // 检查是否包含表情 final containsEmoji = EmojiTextHelper.containsEmoji(content); - + if (containsEmoji && !isFailed) { // 如果包含表情且不是失败消息,使用自定义的单行表情文本 widget return _buildSingleLineEmojiText( @@ -281,6 +337,8 @@ class _ConversationTabState extends State } } + + // 构建单行表情文本(支持省略) Widget _buildSingleLineEmojiText(String text, TextStyle textStyle) { final RegExp emojiRegex = RegExp(r'\[emoji:(\d+)\]'); @@ -333,7 +391,5 @@ class _ConversationTabState extends State overflow: TextOverflow.ellipsis, ); } - - @override - bool get wantKeepAlive => true; } + diff --git a/lib/pages/mine/mine_page.dart b/lib/pages/mine/mine_page.dart index cd35342..2fa167e 100644 --- a/lib/pages/mine/mine_page.dart +++ b/lib/pages/mine/mine_page.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/controller/mine/mine_controller.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; +import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/model/mine/user_count_data.dart'; import 'package:dating_touchme_app/network/user_api.dart'; import 'package:dating_touchme_app/pages/mine/auth_center_page.dart'; @@ -58,6 +59,10 @@ class MinePageState extends State with AutomaticKeepAliveClientMixin{ _userApi = Get.find(); getUserCount(); + + // IMManager.instance.getUserPresenceStatus("1066419122415472640").then((e){ + // print(e); + // }); }