flutter/lib/IM/global_badge.dart
2025-09-03 11:25:31 +08:00

255 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/tab_bar_controller.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/conversation_view_model.dart';
import 'package:loopin/models/tab_type.dart';
import 'package:loopin/pages/chat/notify_controller/notify_no_friend_controller.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_filter.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
class GlobalBadge extends GetxController {
/// 全局未读消息总数
RxInt totalUnread = 0.obs;
/// 监听器对象(用于 add/remove
late final V2TimConversationListener _listener;
void rest() {
totalUnread.value = 0;
}
@override
void onInit() {
super.onInit();
_listener = V2TimConversationListener(
onTotalUnreadMessageCountChanged: (int count) {
logger.i('未读数发生变化$count');
totalUnread.value = count;
Get.find<TabBarController>().setBadge(TabType.chat, totalUnread.value);
},
onNewConversation: (List<V2TimConversation> conversationList) {
for (var conv in conversationList) {
logger.e('新创建会话:${conv.toLogString()}');
logger.i("新会话分组类型:${conv.conversationGroupList}");
handleCoverstion(conv);
}
},
onConversationChanged: (List<V2TimConversation> conversationList) async {
logger.w('会话变更:会话分组:${conversationList.first.conversationGroupList},会话内容${conversationList.first.toLogString()}');
final ctl = Get.find<ChatController>();
logger.w('当前会话列表内容:${ctl.chatList.length}');
final updatedIds = conversationList.map((e) => e.conversationID).toSet();
logger.w('要变更的会话id$updatedIds');
// 收集可能存在后续分页的会话
final List<V2TimConversation> willInsert = <V2TimConversation>[];
for (int i = 0; i < ctl.chatList.length; i++) {
final chatItem = ctl.chatList[i];
logger.w('需要更新的ID:${chatItem.conversation.conversationID}');
if (updatedIds.contains(chatItem.conversation.conversationID)) {
logger.w('chatList中包含:${chatItem.conversation.conversationID}');
// 从onchange中找到原始数据
final updatedConv = conversationList.firstWhere(
(c) => c.conversationID == chatItem.conversation.conversationID,
orElse: () => V2TimConversation(conversationID: ''),
);
// 同步更新
ctl.getNoFriendData(csion: chatItem.conversation);
if (updatedConv.conversationID != '' && (updatedConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false)) {
// 单独处理陌生人会话
final unread = await ImService.instance.getUnreadMessageCountByFilter(
filter: V2TimConversationFilter(
conversationGroup: ConversationType.noFriend.name,
hasUnreadCount: true,
),
);
chatItem.conversation.lastMessage = updatedConv.lastMessage;
chatItem.conversation.unreadCount = unread.data; // 获取陌生人未读总数
update();
} else if (updatedConv.conversationID != '') {
// 其他类型统一更新处理
logger.w('非陌生人消息的会话,正常更新');
chatItem.conversation = updatedConv;
update();
}
} else {
logger.e('本地会话列表中不包含的会话:$updatedIds'); // 情况1nofriend分组会话情况2:会话分页未拉取到或者分组变更
// 检测这条会话数据远端是否存在
for (var cvID in updatedIds) {
// 检测这条会话数据是否存在,如果存在说明在后面的分页中,不存在则是被删除不处理
final isReal = await ImService.instance.getConversation(conversationID: cvID);
if (isReal.success) {
final V2TimConversation realConv = isReal.data;
if (realConv.lastMessage != null) {
// 陌生人会话列表单独处理
if (Get.isRegistered<NotifyNoFriendController>()) {
logger.w('在陌生人会话列表');
final notifyCtl = Get.find<NotifyNoFriendController>();
// 有分组更新,没分组删除
if (realConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false) {
notifyCtl.updateLastMsg(conversation: realConv);
} else {
notifyCtl.del(conversation: realConv);
}
}
// 同步更新nofriend会话菜单入口的未读数量
if (chatItem.isCustomAdmin?.contains(ConversationType.noFriend.name) ?? false) {
await ctl.updateNoFriendMenu();
}
if (realConv.conversationGroupList?.contains(ConversationType.noFriend.name) ?? false) {
// 这个方法执行的逻辑:已有则刷新菜单入口数据,没有则创建菜单入口
await ctl.getNoFriendData(csion: realConv);
} else {
// 非陌生人会话,收集要插入的数据 情况2这条会话数据
willInsert.add(realConv);
}
}
}
}
}
}
// 添加收集的数据,别忘了分页获取的去重
if (willInsert.isNotEmpty) {
logger.w('收集到要插入的数据为:${willInsert.first.toLogString()}');
// willInsert 去重
final deduped = {for (var c in willInsert) c.conversationID.trim(): c}.values.toList();
// 已有会话ID集合
final existingIds = ctl.chatList.map((e) => e.conversation.conversationID.trim()).where((id) => id.isNotEmpty).toSet();
// 过滤掉已存在的会话
final filtered = deduped.where((c) => !existingIds.contains(c.conversationID.trim())).toList();
logger.w('最终需要插入的会话数量: ${filtered.length}, ids: ${filtered.map((c) => '"${(c.conversationID).trim()}"').toList()}');
final viewModelList = await ConversationViewModel.createConversationViewModel(convList: filtered);
ctl.chatList.insertAll(0, viewModelList);
}
// 如果没当前会话列表为空
if (ctl.chatList.isEmpty) {
// 重新获取一次
logger.w('重新获取会话');
ctl.initChatData();
ctl.getConversationList();
}
//重新排序
ctl.chatList.sort((a, b) {
final atime = a.conversation.lastMessage?.timestamp ?? 0;
final btime = b.conversation.lastMessage?.timestamp ?? 0;
return btime.compareTo(atime); // 降序
});
//去重
final seen = <String>{};
ctl.chatList.retainWhere((item) {
final id = item.conversation.conversationID.trim();
if (seen.contains(id)) {
return false;
} else {
seen.add(id);
return true;
}
});
ctl.chatList.refresh();
}, //changeEnd;
);
// final ctl = Get.find<ChatController>();
// ctl.getConversationList();
initUnreadCount();
_addListener();
}
// final rr = await ImService.instance.deleteConversationsFromGroup(
// conversationIDList: [cov.conversationID],
// groupName: 'noFriend',
// );
// logger.w(rr.desc);
/// 新建会话时候,根据消息的自定义属性给会话分组
void handleCoverstion(V2TimConversation cov) async {
final message = cov.lastMessage;
final isSelfSend = message!.isSelf; // 是否本人发送的消息
final typeEnum = conversationTypeFromString(message.cloudCustomData); // 会话类型
final needAdd = cov.conversationGroupList!.isEmpty == true; // 当前会话是否已加入了分组中
if (typeEnum != null && needAdd && isSelfSend == false) {
logger.i('当前会话的类型要加入的组是:$typeEnum');
// 当前会话需要进行分组,检测 组 是否存在
final hasGroupRes = await ImService.instance.getConversationGroupList();
if (hasGroupRes.success) {
final exists = hasGroupRes.data?.any((item) => item == typeEnum) ?? false;
if (!exists) {
// 组不存在创建组并把会话加入group中
await ImService.instance.createConversationGroup(
groupName: typeEnum,
conversationIDList: ['c2c_${message.sender}'],
);
logger.i('首次创建会话分组$typeEnum');
} else {
// 分组存在直接添加
await ImService.instance.addConversationsToGroup(
groupName: typeEnum,
conversationIDList: ['c2c_${message.sender}'],
);
logger.i('添加会话分组$typeEnum成功');
}
if (typeEnum == ConversationType.noFriend.name) {
//陌生人分组特殊处理 满足分组条件且已经有分组,
final ctl = Get.find<ChatController>();
// 这个方法执行的逻辑:已有则刷新菜单入口数据,没有则创建菜单入口
ctl.getNoFriendData(csion: cov);
}
}
} else {
logger.w('新会话不需分组');
}
}
/// 初始化时获取一次未读总数
void initUnreadCount() async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getTotalUnreadMessageCount();
if (res.code == 0) {
totalUnread.value = res.data ?? 0;
Get.find<TabBarController>().setBadge(TabType.chat, totalUnread.value);
final to = res.data;
logger.i('初始化未读消息数$to');
} else {
//处理安卓端重新登录后获取未读数量失败的问题
logger.e('获取初始化未读数失败:${res.desc},重新补偿获取');
Future.delayed(Duration(seconds: 1), handAndroid);
}
}
/// 处理安卓端异常的问题
handAndroid() {
initUnreadCount();
final ctl = Get.find<ChatController>();
ctl.initChatData();
ctl.getConversationList();
}
/// 添加会话未读数监听器
void _addListener() {
TencentImSDKPlugin.v2TIMManager.getConversationManager().addConversationListener(listener: _listener);
logger.i('未读数监听器注册成功');
}
/// 手动更新total
Future<void> refreshUnreadCount() async {
initUnreadCount();
}
/// 移除监听器,防止重复注册
@override
void onClose() {
logger.i(_listener);
logger.i('移除global未读监听器');
TencentImSDKPlugin.v2TIMManager.getConversationManager().removeConversationListener(listener: _listener);
super.onClose();
}
}