diff --git a/lib/controller/message/conversation_controller.dart b/lib/controller/message/conversation_controller.dart index 605d2d2..8c01d61 100644 --- a/lib/controller/message/conversation_controller.dart +++ b/lib/controller/message/conversation_controller.dart @@ -35,6 +35,14 @@ class ExtendedUserInfo { } } +// 筛选类型枚举 +enum FilterType { + none, // 无筛选 + lastChatTime, // 最后聊天时间 + unread, // 未读消息 + online, // 当前在线 +} + class ConversationController extends GetxController { // 会话列表数据 final conversations = [].obs; @@ -46,6 +54,9 @@ class ConversationController extends GetxController { // 用户信息缓存(userId -> ExtendedUserInfo) final Map _userInfoCache = {}; + // 筛选类型 + final filterType = FilterType.none.obs; + /// 缓存用户信息(公开方法,供 ChatController 调用) void cacheUserInfo(String userId, ExtendedUserInfo userInfo) { _userInfoCache[userId] = userInfo; @@ -591,4 +602,132 @@ class ConversationController extends GetxController { return false; } } + + /// 设置筛选类型 + void setFilterType(FilterType type) { + filterType.value = type; + if (Get.isLogEnable) { + Get.log('✅ [ConversationController] 设置筛选类型: $type'); + } + } + + /// 获取筛选后的会话列表 + Future> getFilteredConversations() async { + List filteredList = List.from(conversations); + + switch (filterType.value) { + case FilterType.none: + // 无筛选,按默认顺序返回 + break; + + case FilterType.lastChatTime: + // 按最后聊天时间排序(最新的在前) + // 先获取所有消息的时间戳 + final List> conversationTimes = await Future.wait( + filteredList.map((conv) async { + final message = await lastMessage(conv); + final time = message?.serverTime ?? 0; + return MapEntry(conv, time); + }), + ); + // 按时间戳排序(降序,最新的在前) + conversationTimes.sort((a, b) => b.value.compareTo(a.value)); + filteredList = conversationTimes.map((entry) => entry.key).toList(); + break; + + case FilterType.unread: + // 只显示有未读消息的会话 + final List> conversationUnreads = await Future.wait( + filteredList.map((conv) async { + final unreadCount = await getUnreadCount(conv); + return MapEntry(conv, unreadCount); + }), + ); + // 过滤出有未读消息的会话 + final unreadConversations = conversationUnreads + .where((entry) => entry.value > 0) + .toList(); + // 按未读数量排序(未读数多的在前) + unreadConversations.sort((a, b) => b.value.compareTo(a.value)); + filteredList = unreadConversations.map((entry) => entry.key).toList(); + break; + + case FilterType.online: + // 只显示在线用户的会话 + final List onlineConversations = await Future.wait( + filteredList.map((conv) async { + final isOnline = await _checkUserOnline(conv); + return isOnline ? conv : null; + }), + ); + filteredList = onlineConversations + .whereType() + .toList(); + break; + } + + return filteredList; + } + + /// 检查用户是否在线 + Future _checkUserOnline(EMConversation conversation) async { + try { + // 获取会话的最新消息 + final message = await lastMessage(conversation); + if (message == null) { + return false; + } + + // 从消息扩展字段中获取在线状态 + Map? attributes; + try { + attributes = message.attributes; + } catch (e) { + return false; + } + + if (attributes == null || attributes.isEmpty) { + return false; + } + + // 检查在线状态字段 + // 接收消息:检查 sender_isOnline + // 发送消息:检查 receiver_isOnline + String? isOnlineStr; + String? lastActiveTimeStr; + + if (message.direction == MessageDirection.RECEIVE) { + isOnlineStr = attributes['sender_isOnline'] as String?; + lastActiveTimeStr = attributes['sender_lastActiveTime'] as String?; + } else { + isOnlineStr = attributes['receiver_isOnline'] as String?; + lastActiveTimeStr = attributes['receiver_lastActiveTime'] as String?; + } + + if (isOnlineStr == 'true') { + // 进一步检查最后活跃时间(5分钟内认为在线) + if (lastActiveTimeStr != null) { + try { + final lastActiveTime = int.parse(lastActiveTimeStr); + final now = DateTime.now().millisecondsSinceEpoch; + final diff = now - lastActiveTime; + // 5分钟内认为在线(5 * 60 * 1000 毫秒) + return diff < 5 * 60 * 1000; + } catch (e) { + // 解析失败,使用 isOnline 字段 + return true; + } + } else { + return true; + } + } + + return false; + } catch (e) { + if (Get.isLogEnable) { + Get.log('⚠️ [ConversationController] 检查用户在线状态失败: $e'); + } + return false; + } + } } \ No newline at end of file diff --git a/lib/pages/message/conversation_tab.dart b/lib/pages/message/conversation_tab.dart index e6405b4..f5424d9 100644 --- a/lib/pages/message/conversation_tab.dart +++ b/lib/pages/message/conversation_tab.dart @@ -44,14 +44,43 @@ class _ConversationTabState extends State ); } - return ListView.builder( - padding: const EdgeInsets.only(top: 8), - itemCount: controller.conversations.length, - itemBuilder: (context, index) { - final conversation = controller.conversations[index]; - return _buildConversationItem(conversation); - }, - ); + // 监听筛选类型变化,获取筛选后的会话列表 + // 使用 Obx 监听筛选类型变化,触发 FutureBuilder 重建 + return Obx(() { + return FutureBuilder>( + future: controller.getFilteredConversations(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + final filteredConversations = snapshot.data ?? []; + + if (filteredConversations.isEmpty) { + return Center( + child: Text( + controller.filterType.value == FilterType.none + ? '暂无会话' + : '暂无符合条件的会话', + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.only(top: 8), + itemCount: filteredConversations.length, + itemBuilder: (context, index) { + final conversation = filteredConversations[index]; + return _buildConversationItem(conversation); + }, + ); + }, + ); + }); }), ), ], diff --git a/lib/pages/message/message_page.dart b/lib/pages/message/message_page.dart index f9937a9..979a1d9 100644 --- a/lib/pages/message/message_page.dart +++ b/lib/pages/message/message_page.dart @@ -144,6 +144,8 @@ class _MessagePageState extends State with AutomaticKeepAliveClient overlay.size.height - (buttonPosition.dy + buttonSize.height + 4), // bottom ); + final ConversationController conversationController = Get.find(); + showMenu( context: context, position: position, @@ -158,9 +160,15 @@ class _MessagePageState extends State with AutomaticKeepAliveClient icon: Assets.imagesLastMsgIcon, text: '最后聊天时间', showDivider: true, + isSelected: conversationController.filterType.value == FilterType.lastChatTime, onTap: () { Navigator.pop(context); - // TODO: 实现按最后聊天时间排序 + // 如果已选中,则取消筛选;否则设置筛选 + if (conversationController.filterType.value == FilterType.lastChatTime) { + conversationController.setFilterType(FilterType.none); + } else { + conversationController.setFilterType(FilterType.lastChatTime); + } }, ), ), @@ -170,9 +178,15 @@ class _MessagePageState extends State with AutomaticKeepAliveClient icon: Assets.imagesUnreadIcon, text: '未读消息', showDivider: true, + isSelected: conversationController.filterType.value == FilterType.unread, onTap: () { Navigator.pop(context); - // TODO: 实现按未读消息筛选 + // 如果已选中,则取消筛选;否则设置筛选 + if (conversationController.filterType.value == FilterType.unread) { + conversationController.setFilterType(FilterType.none); + } else { + conversationController.setFilterType(FilterType.unread); + } }, ), ), @@ -182,9 +196,15 @@ class _MessagePageState extends State with AutomaticKeepAliveClient icon: Assets.imagesOnlineMsgIcon, text: '当前在线', showDivider: false, + isSelected: conversationController.filterType.value == FilterType.online, onTap: () { Navigator.pop(context); - // TODO: 实现按在线状态筛选 + // 如果已选中,则取消筛选;否则设置筛选 + if (conversationController.filterType.value == FilterType.online) { + conversationController.setFilterType(FilterType.none); + } else { + conversationController.setFilterType(FilterType.online); + } }, ), ), @@ -198,6 +218,7 @@ class _MessagePageState extends State with AutomaticKeepAliveClient required String text, required bool showDivider, required VoidCallback onTap, + bool isSelected = false, }) { return InkWell( onTap: onTap, @@ -206,6 +227,7 @@ class _MessagePageState extends State with AutomaticKeepAliveClient children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), + color: isSelected ? Colors.blue.withOpacity(0.1) : Colors.transparent, child: Row( children: [ Image.asset( @@ -214,13 +236,22 @@ class _MessagePageState extends State with AutomaticKeepAliveClient height: 20, ), const SizedBox(width: 12), - Text( - text, - style: const TextStyle( - fontSize: 14, - color: Colors.black, + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: 14, + color: isSelected ? Colors.blue : Colors.black, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + ), ), ), + if (isSelected) + const Icon( + Icons.check, + size: 18, + color: Colors.blue, + ), ], ), ),