yumi-flutter/lib/services/audio/rtm_manager.dart
roxy bd6d35ee1b 🚀 UI
2026-05-14 17:10:53 +08:00

3071 lines
104 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 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_debouncer/flutter_debouncer.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/shared/tools/sc_message_utils.dart';
import 'package:yumi/shared/tools/sc_path_utils.dart';
import 'package:yumi/shared/tools/sc_room_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_status.dart';
import 'package:tencent_cloud_chat_sdk/manager/v2_tim_group_manager.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_image_elem.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_msg_create_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
import 'package:yumi/app/constants/sc_room_msg_type.dart';
import 'package:yumi/shared/tools/sc_lk_event_bus.dart';
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
import 'package:yumi/shared/data_sources/sources/local/file_cache_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import 'package:yumi/shared/data_sources/models/message/big_broadcast_group_message.dart';
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
import 'package:yumi/shared/business_logic/models/res/sc_broad_cast_luck_gift_push.dart';
import 'package:yumi/shared/business_logic/models/res/broad_cast_mic_change_push.dart'
hide Data;
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_public_message_page_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_theme_list_res.dart';
import 'package:yumi/services/general/sc_app_general_manager.dart';
import 'package:yumi/ui_kit/widgets/room/invite/invite_room_dialog.dart';
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_models.dart';
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_open_dialog.dart';
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_pending_cache.dart';
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog.dart';
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/services/gift/gift_system_manager.dart';
import '../../shared/data_sources/models/enum/sc_gift_type.dart';
import '../../shared/data_sources/models/enum/sc_room_roles_type.dart';
typedef RoomNewMsgListener = Function(Msg msg);
typedef OnNewMessageListener = Function(V2TimMessage? message, {String? msgId});
typedef OnNewANMessageListener = Function(Records? message);
typedef OnRevokeMessageListener = Function(String msgId);
typedef OnNewGroupMessageListener =
Function(String groupID, V2TimMessage message);
typedef OnMessageRecvC2CReadListener = Function(List<String> messageIDList);
typedef RtmProvider = RealTimeMessagingManager;
class _LuckGiftPushQueueEntry {
_LuckGiftPushQueueEntry({
required this.reward,
required this.eventKey,
required this.mergeKey,
required this.updatedAt,
}) {
if (eventKey.isNotEmpty) {
eventKeys.add(eventKey);
}
}
SCBroadCastLuckGiftPush reward;
final String eventKey;
final String mergeKey;
int updatedAt;
final Set<String> eventKeys = <String>{};
void merge(SCBroadCastLuckGiftPush incoming, String incomingEventKey) {
reward = reward.mergeReward(incoming);
updatedAt = DateTime.now().millisecondsSinceEpoch;
if (incomingEventKey.isNotEmpty) {
eventKeys.add(incomingEventKey);
}
}
}
class RealTimeMessagingManager extends ChangeNotifier {
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
static const int _giftComboMergeWindowMs = 3000;
static const int _maxLuckGiftPushQueueLength = 12;
static const int _luckyGiftFloatMinMultiple = 5;
static const int _luckyGiftBurstMinMultiple = 10;
static const int _luckyGiftBurstMinAwardAmount = 5000;
static const int _luckyGiftBurstMergeDelayMs = 600;
static const int _luckyGiftBurstMergeWindowMs = 1800;
static const int _luckyGiftBurstRecentEventTtlMs = 5000;
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
BuildContext? context;
void _giftFxLog(String message) {}
void _enqueueGiftFloatingMessage(
SCFloatingMessage message, {
String? dedupKey,
}) {
if (SCGlobalConfig.isLowPerformanceDevice) {
OverlayManager().addLowPerformanceCompensationMessage(
message,
dedupKey: dedupKey,
);
return;
}
OverlayManager().addMessage(message);
}
String _giftFloatingDedupKey({
required String? roomId,
required String? senderId,
required String? receiverId,
required String? giftId,
required num? quantity,
}) {
return [
"gift",
roomId ?? "",
senderId ?? "",
receiverId ?? "",
giftId ?? "",
quantity ?? 0,
].join("|");
}
String _entryFloatingDedupKey({
required String? roomId,
required String? userId,
required String? resourceId,
}) {
return ["entry", roomId ?? "", userId ?? "", resourceId ?? ""].join("|");
}
String _resolveEntryCompensationCover(PropsResources? resource) {
return _firstNonBlank([
resource?.roomSendCoverUrl,
resource?.roomOpenedUrl,
resource?.cover,
resource?.roomNotOpenedUrl,
]);
}
String _firstNonBlank(Iterable<String?> values) {
for (final value in values) {
final text = value?.trim();
if (text != null && text.isNotEmpty) {
return text;
}
}
return "";
}
///消息列表
List<Msg> roomAllMsgList = [];
List<Msg> roomChatMsgList = [];
List<Msg> roomGiftMsgList = [];
RoomNewMsgListener? msgAllListener;
RoomNewMsgListener? msgChatListener;
RoomNewMsgListener? msgGiftListener;
RoomNewMsgListener? msgFloatingGiftListener;
RoomNewMsgListener? msgLuckyGiftRewardTickerListener;
RoomNewMsgListener? msgUserJoinListener;
/// 当前会话
V2TimConversation? currentConversation;
/// 会话列表缓存
Map<String, V2TimConversation> conversationMap = {};
///消息已读监听
OnMessageRecvC2CReadListener? onMessageRecvC2CReadListener;
///消息被撤回监听
OnRevokeMessageListener? onRevokeMessageListener;
///新消息监听 单聊
OnNewMessageListener? onNewMessageCurrentConversationListener;
OnNewANMessageListener? onNewActivityMessageCurrentConversationListener;
OnNewANMessageListener? onNewNotifcationMessageCurrentConversationListener;
///新消息监听 群聊
Map<String, OnNewGroupMessageListener?> onNewMessageListenerGroupMap = {};
int allUnReadCount = 0;
int messageUnReadCount = 0;
int systemUnReadCount = 0;
int customerUnReadCount = 0;
int activityUnReadCount = 0;
int notifcationUnReadCount = 0;
SCBroadCastLuckGiftPush? currentPlayingLuckGift;
final Queue<_LuckGiftPushQueueEntry> _luckGiftPushQueue = Queue();
String? _currentLuckGiftPushKey;
Set<String> _currentLuckGiftPushEventKeys = <String>{};
Timer? _luckGiftPushPlayTimer;
int _luckGiftPushPlaybackToken = 0;
final Map<String, int> _recentLuckGiftPushEventTimes = <String, int>{};
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
String? roomRedPacketBroadcastGroupId;
String? roomRedPacketBroadcastRegionCode;
Debouncer debouncer = Debouncer();
List<V2TimConversation> conversationList = [];
///客服
SocialChatUserProfile? customerInfo;
bool _isSystemConversation(V2TimConversation? conversation) {
if (conversation == null) {
return false;
}
return SCGlobalConfig.isSystemConversationId(conversation.conversationID) ||
SCGlobalConfig.isSystemUserId(conversation.userID);
}
int _systemUnreadCount() {
int count = 0;
for (final conversation in conversationMap.values) {
if (_isSystemConversation(conversation)) {
count += conversation.unreadCount ?? 0;
}
}
return count;
}
V2TimConversation getPreferredSystemConversation() {
final systemConversations =
conversationMap.values.where(_isSystemConversation).toList()
..sort((e1, e2) {
final time1 = e1.lastMessage?.timestamp ?? 0;
final time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1);
});
if (systemConversations.isNotEmpty) {
return systemConversations.first;
}
return V2TimConversation(
type: ConversationType.V2TIM_C2C,
userID: SCGlobalConfig.primarySystemUserId,
conversationID: SCGlobalConfig.primarySystemConversationId,
);
}
void getConversationList() {
List<V2TimConversation> list = conversationMap.values.toList();
list.removeWhere((element) {
if (_isSystemConversation(element)) {
return true;
}
if (element.conversationID == "c2c_${customerInfo?.id}") {
return true;
}
if (element.conversationID == "c2c_atyou-newsletter") {
///删除这个会话,后台乱发的联系人。。防止无效未读数
clearC2CHistoryMessage(element.conversationID, false);
return true;
}
if (element.lastMessage == null) {
return true;
}
return false;
});
list.sort((e1, e2) {
int time1 = e1.lastMessage?.timestamp ?? 0;
int time2 = e2.lastMessage?.timestamp ?? 0;
return time2.compareTo(time1);
});
conversationList = list;
systemUnReadCount = _systemUnreadCount();
customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
notifyListeners();
}
init(BuildContext context) async {
this.context = context;
V2TimSDKListener sdkListener = V2TimSDKListener(
onConnectFailed: (int code, String error) {
// 连接失败的回调函数
// code 错误码
// error 错误信息
},
onConnectSuccess: () {
// SDK 已经成功连接到腾讯云服务器
},
onConnecting: () {
// SDK 正在连接到腾讯云服务器
},
onKickedOffline: () {
// 当前用户被踢下线,此时可以 UI 提示用户,并再次调用 V2TIMManager 的 login() 函数重新登录。
},
onSelfInfoUpdated: (V2TimUserFullInfo info) {
// 登录用户的资料发生了更新
// info登录用户的资料
},
onUserSigExpired: () {
// 在线时票据过期:此时您需要生成新的 userSig 并再次调用 V2TIMManager 的 login() 函数重新登录。
},
onUserStatusChanged: (List<V2TimUserStatus> userStatusList) {
//用户状态变更通知
//userStatusList 用户状态变化的用户列表
//收到通知的情况:订阅过的用户发生了状态变更(包括在线状态和自定义状态),会触发该回调
//在 IM 控制台打开了好友状态通知开关,即使未主动订阅,当好友状态发生变更时,也会触发该回调
//同一个账号多设备登录,当其中一台设备修改了自定义状态,所有设备都会收到该回调
},
);
V2TimValueCallback<bool> initSDKRes = await TencentImSDKPlugin.v2TIMManager
.initSDK(
sdkAppID: int.parse(SCGlobalConfig.tencentImAppid), // SDKAppID
loglevel: LogLevelEnum.V2TIM_LOG_ALL, // 日志登记等级
listener: sdkListener, // 事件监听器
);
if (initSDKRes.code == 0) {}
try {
customerInfo = await SCConfigRepositoryImp().customerService();
} catch (e) {}
/// 登录
await loginTencetRtm(context);
/// 初始化会话列表
// _onRefreshConversationSub = FTIM.getContactManager().addRefreshConversationListener(_onRefreshConversation);
TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.addConversationListener(
listener: V2TimConversationListener(
onNewConversation: (conversationList) {
// _onRefreshConversation(conversationList);
initConversation();
},
onTotalUnreadMessageCountChanged: (int totalUnreadCount) {
messageUnReadCount = totalUnreadCount;
systemUnReadCount = _systemUnreadCount();
customerUnReadCount =
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0;
allUnReadCount =
messageUnReadCount +
notifcationUnReadCount +
activityUnReadCount;
notifyListeners();
},
),
);
TencentImSDKPlugin.v2TIMManager.addGroupListener(
listener: V2TimGroupListener(
onMemberEnter: (String groupID, List<V2TimGroupMemberInfo> memberList) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
);
if (groupID ==
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount) {
rtcProvider.fetchOnlineUsersList(notifyIfUnchanged: false);
}
},
onMemberLeave: (String groupID, V2TimGroupMemberInfo member) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
);
rtcProvider.removOnlineUser(groupID, member.userID!);
if (groupID ==
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount) {
rtcProvider.fetchOnlineUsersList(notifyIfUnchanged: false);
}
},
onMemberKicked: (
String groupID,
V2TimGroupMemberInfo opUser,
List<V2TimGroupMemberInfo> memberList,
) {
///踢出房间
if (memberList.isNotEmpty) {
if (memberList.first.userID ==
AccountStorage().getCurrentUser()?.userProfile?.id) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
);
unawaited(
rtcProvider.handleSelfMicRemovedByRemote(refreshMicList: false),
);
///退出房间
rtcProvider.exitCurrentVoiceRoomSession(false).whenComplete(() {
SCRoomUtils.closeAllDialogs();
SmartDialog.show(
tag: "showConfirmDialog",
alignment: Alignment.center,
debounce: true,
animationType: SmartAnimationType.fade,
builder: (_) {
return MsgDialog(
title: SCAppLocalizations.of(context)!.tips,
msg: SCAppLocalizations.of(context)!.kickRoomTips,
btnText: SCAppLocalizations.of(context)!.confirm,
onEnsure: () {},
);
},
);
});
}
}
},
),
);
/// 新消息监听
// FTIM.getMessageManager().addNewMessagesListener(_onNewMessage);
TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener(
listener: V2TimAdvancedMsgListener(
onRecvC2CReadReceipt: (List<V2TimMessageReceipt> receiptList) {
//会话已读回调
},
onRecvMessageModified: (V2TimMessage message) {
// msg 为被修改之后的消息对象
},
onRecvMessageReadReceipts: (List<V2TimMessageReceipt> receiptList) {
//群聊/单聊已读回调
List<String> messageIDList = [];
for (var element in receiptList) {
messageIDList.add(element.msgID!);
}
onMessageRecvC2CReadListener?.call(messageIDList);
},
onRecvMessageRevoked: (String messageId) {
// 在本地维护的消息中处理被对方撤回的消息
TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.deleteMessages(msgIDs: [messageId])
.then((result) {
onRevokeMessageListener?.call(messageId);
for (var ms in conversationList) {
if (ms.lastMessage?.msgID == messageId) {
ms.lastMessage?.status =
MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED;
break;
}
}
notifyListeners();
});
},
onRecvNewMessage: (V2TimMessage message) async {
_onNewMessage(message);
},
onSendMessageProgress: (V2TimMessage message, int progress) {
//文件上传进度回调
},
),
);
getAllUnReadCount();
joinBigBroadcastGroup();
unawaited(syncRoomRedPacketBroadcastGroup());
}
/// 初始化会话
/// 打开聊天界面的时候调用
int sTime = 0;
Future<bool> startConversation(V2TimConversation conversation) async {
assert(conversation != null);
int eTime = DateTime.now().millisecondsSinceEpoch;
if (eTime - sTime > 5000) {
sTime = eTime;
currentConversation = conversation;
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.markC2CMessageAsRead(userID: conversation.userID!);
notifyListeners();
return true;
} else {
return false;
}
}
///初始话会话
Future initConversation() async {
V2TimValueCallback<V2TimConversationResult> convList =
await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.getConversationList(nextSeq: '0', count: 100);
List<V2TimConversation?>? conversationList =
convList.data?.conversationList;
conversationMap.clear();
if (conversationList != null) {
for (V2TimConversation? conversation in conversationList) {
conversationMap[conversation!.conversationID] = conversation;
}
}
getConversationList();
}
// /// 所有消息未读数
// int getAllUnReadCount() {
// allUnReadCount = 0;
// for (var value in conversationList) {
// allUnReadCount += value.unreadCount ?? 0;
// }
// // for (var value in systemConversationList) {
// // i += value.unreadMessageNum;
// // }
// notifyListeners();
// return allUnReadCount;
// }
/// 所有消息未读数
void getAllUnReadCount() async {
V2TimValueCallback<int> res =
await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.getTotalUnreadMessageCount();
if (res.code == 0) {
messageUnReadCount = res.data ?? 0;
allUnReadCount =
messageUnReadCount + activityUnReadCount + notifcationUnReadCount;
notifyListeners();
// 这里可以先用初始值更新UI
}
}
///登录IM
Future loginTencetRtm(BuildContext context) async {
SocialChatLoginRes? userModel = AccountStorage().getCurrentUser();
bool logined = false;
while (!logined && userModel != null) {
await Future.delayed(Duration(milliseconds: 550));
try {
if (userModel.userSig != null) {
V2TimCallback res = await TencentImSDKPlugin.v2TIMManager.login(
userID: userModel.userProfile?.id ?? "",
userSig: userModel.userSig ?? "",
);
if (res.code == 0) {
isLogout = false;
// 登录成功逻辑
logined = true;
await initConversation();
unawaited(syncRoomRedPacketBroadcastGroup());
} else {
// 登录失败逻辑
SCTts.show('tim login fail');
}
} else {
SCTts.show('tim login fail');
}
} catch (e) {
SCTts.show('timm login fail${e.toString()}');
}
userModel = AccountStorage().getCurrentUser();
}
}
_onNewMessage(V2TimMessage message) {
if (message.groupID != null) {
final groupId = message.groupID!;
///全服通知 / 语区广播群
if (groupId == SCGlobalConfig.bigBroadcastGroup ||
groupId == roomRedPacketBroadcastGroupId) {
_newBroadCastMsgRecv(groupId, message);
return;
}
if (_shouldRouteUntrackedRoomRedPacketBroadcast(groupId, message)) {
_newBroadCastMsgRecv(groupId, message, forceRegionBroadcast: true);
return;
}
///群消息
for (var element in onNewMessageListenerGroupMap.values) {
element?.call(groupId, message);
}
} else {
///单聊消息
_onNew1v1Message(message);
}
}
_onNew1v1Message(V2TimMessage? message, {String? msgId}) async {
for (var element in conversationList) {
if (message?.userID == element.userID) {
element.lastMessage = message;
if (onNewMessageCurrentConversationListener == null) {
element.unreadCount = element.unreadCount! + 1;
}
}
}
if (message?.userID == customerInfo?.id) {
if (onNewMessageCurrentConversationListener == null) {
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount =
(conversationMap["c2c_${customerInfo?.id}"]?.unreadCount ?? 0) + 1;
}
}
systemUnReadCount = _systemUnreadCount();
notifyListeners();
onNewMessageCurrentConversationListener?.call(message, msgId: msgId);
}
void _onRefreshConversation(List<V2TimConversation> conversations) {
for (V2TimConversation conversation in conversations) {
conversationMap[conversation.conversationID] = conversation;
}
getConversationList();
}
///创建房间im群聊
Future<V2TimValueCallback<String>> createRoomGroup(
String groupID,
String groupName,
) {
return V2TIMGroupManager().createGroup(
groupID: groupID,
groupType: GroupType.AVChatRoom,
groupName: groupName,
);
}
///加入房间的im群聊
Future<V2TimCallback> joinRoomGroup(String groupID, String message) async {
_resetLuckGiftBurstQueue();
var joinResult = await TencentImSDKPlugin.v2TIMManager.joinGroup(
groupID: groupID,
message: message,
);
if (joinResult.code == 0 || joinResult.code == 10013) {
onNewMessageListenerGroupMap[groupID] = _newGroupMsg;
}
return joinResult;
}
Future<void> syncRoomRedPacketBroadcastGroup() async {
for (var attempt = 0; attempt < 3 && !isLogout; attempt++) {
try {
final group = await SCChatRoomRepository().roomRedPacketImGroup();
if (!group.canJoin) {
return;
}
final newGroupId = group.groupId.trim();
if (newGroupId == roomRedPacketBroadcastGroupId) {
roomRedPacketBroadcastRegionCode = group.regionCode.trim();
return;
}
final joinResult = await TencentImSDKPlugin.v2TIMManager.joinGroup(
groupID: newGroupId,
message: "",
);
if (joinResult.code == 0 || joinResult.code == 10013) {
final oldGroupId = roomRedPacketBroadcastGroupId;
roomRedPacketBroadcastGroupId = newGroupId;
roomRedPacketBroadcastRegionCode = group.regionCode.trim();
if (oldGroupId != null &&
oldGroupId.isNotEmpty &&
oldGroupId != newGroupId) {
unawaited(quitGroup(oldGroupId));
}
return;
}
} catch (e) {}
await Future.delayed(Duration(milliseconds: 550));
}
}
///发送文本消息
Future dispatchMessage(
Msg msg, {
bool showEmoticons = true,
bool addLocal = true,
}) async {
if (addLocal) {
addMsg(msg);
}
// 发送消息到腾讯云IM
if (msg.groupId != null) {
await _sendTencentMessage(msg);
}
notifyListeners();
}
// 发送腾讯云消息
Future<void> _sendTencentMessage(Msg msg) async {
try {
switch (msg.type) {
case SCRoomMsgType.text:
case SCRoomMsgType.shangMai:
case SCRoomMsgType.emoticons:
case SCRoomMsgType.xiaMai:
case SCRoomMsgType.killXiaMai:
case SCRoomMsgType.roomMusic:
case SCRoomMsgType.roomRoleChange:
case SCRoomMsgType.roomSettingUpdate:
case SCRoomMsgType.roomBGUpdate:
case SCRoomMsgType.qcfj:
case SCRoomMsgType.fengMai:
case SCRoomMsgType.jieFeng:
case SCRoomMsgType.joinRoom:
case SCRoomMsgType.gift:
case SCRoomMsgType.bsm:
case SCRoomMsgType.roomDice:
case SCRoomMsgType.roomRPS:
case SCRoomMsgType.roomLuckNumber:
case SCRoomMsgType.image:
case SCRoomMsgType.roomGameClose:
case SCRoomMsgType.roomGameCreate:
case SCRoomMsgType.luckGiftAnimOther:
case SCRoomMsgType.roomRedPacketClaim:
_sendRoomMessage(msg);
break;
default:
break;
}
} catch (e) {
// 处理发送失败的情况
}
}
///发送单聊文本消息
Future<void> sendC2CTextMsg(
String msg,
V2TimConversation toConversation,
) async {
// 创建文本消息
V2TimValueCallback<V2TimMsgCreateInfoResult> createTextMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createTextMessage(
text: msg, // 文本信息
);
if (createTextMessageRes.code == 0) {
// 文本信息创建成功
String id = createTextMessageRes.data!.id!;
// 发送文本消息
// 在sendMessage时若只填写receiver则发个人用户单聊消息
// 若只填写groupID则发群组消息
// 若填写了receiver与groupID则发群内的个人用户消息在群聊中显示只有指定receiver能看见
String receiveId = toConversation.userID!;
V2TimValueCallback<V2TimMessage> sendMessageRes = await TencentImSDKPlugin
.v2TIMManager
.getMessageManager()
.sendMessage(
id: id, // 创建的messageid
receiver: toConversation.userID!, // 接收人id
needReadReceipt: true,
groupID: '', // 是否需要已读回执
);
if (sendMessageRes.code == 0) {
// 发送成功
_onNew1v1Message(sendMessageRes.data);
} else {
SCTts.show(
'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}',
);
}
}
}
///发送单聊自定义消息
Future<void> sendC2CCustomMsg(
String msg,
V2TimConversation toConversation,
String extension,
) async {
// 创建文本消息
V2TimValueCallback<V2TimMsgCreateInfoResult> createCustomMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createCustomMessage(
data: msg, // 文本信息
extension: extension,
);
if (createCustomMessageRes.code == 0) {
// 文本信息创建成功
String id = createCustomMessageRes.data!.id!;
// 发送文本消息
// 在sendMessage时若只填写receiver则发个人用户单聊消息
// 若只填写groupID则发群组消息
// 若填写了receiver与groupID则发群内的个人用户消息在群聊中显示只有指定receiver能看见
String receiveId = toConversation.userID!;
V2TimValueCallback<V2TimMessage> sendMessageRes = await TencentImSDKPlugin
.v2TIMManager
.getMessageManager()
.sendMessage(
id: id,
// 创建的messageid
receiver: toConversation.userID!,
// 接收人id
needReadReceipt: true,
isSupportMessageExtension: true,
groupID: '', // 是否需要已读回执
);
if (sendMessageRes.code == 0) {
// 发送成功
_onNew1v1Message(sendMessageRes.data);
} else {
SCTts.show(
'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}',
);
}
}
}
///发送单聊图片消息
Future<void> sendImageMsg({
List<File>? selectedList,
File? file,
required V2TimConversation conversation,
}) async {
if (file != null) {
File newFile = await SCMessageUtils.createImageElem(file);
V2TimValueCallback<V2TimMsgCreateInfoResult> createImageMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createImageMessage(imagePath: newFile.path);
if (createImageMessageRes.code == 0) {
String id = createImageMessageRes.data!.id!;
V2TimValueCallback<V2TimMessage> sendMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.sendMessage(
id: id,
receiver: conversation.userID!,
needReadReceipt: true,
groupID: '',
);
if (sendMessageRes.code == 0) {
// 发送成功
_onNew1v1Message(sendMessageRes.data);
} else {
SCTts.show(
'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}',
);
}
}
} else {
if (selectedList != null) {
for (File entity in selectedList) {
String id = "";
V2TimMessage? message;
//判断是视频或者图片消息
if (SCPathUtils.getFileType(entity.path) == "image") {
V2TimValueCallback<V2TimMsgCreateInfoResult> createImageMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createImageMessage(imagePath: entity.path);
if (createImageMessageRes.code == 0) {
message = createImageMessageRes.data?.messageInfo;
id = createImageMessageRes.data!.id!;
// 创建图片
File newFile = await SCMessageUtils.createImageElem(entity);
//发送
V2TimImageElem elem = V2TimImageElem(path: newFile.path);
message?.imageElem = elem;
}
} else if (SCPathUtils.getFileType(entity.path) == "video_pic") {
if (entity.lengthSync() > 50000000) {
SCTts.show(
SCAppLocalizations.of(context!)!.theVideoSizeCannotExceed,
);
return;
}
// 复制一份视频
String md5Str1 = keyToMd5(entity.path);
File newFile = File(
"${FileCacheManager.videoCachePath}/$md5Str1.mp4",
);
if (!newFile.existsSync()) {
await entity.copy(newFile.path);
}
// 创建缩略图
String? thumbImagePath = await SCMessageUtils.generateFileThumbnail(
newFile.path,
128,
);
V2TimValueCallback<V2TimMsgCreateInfoResult> createVideoMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createVideoMessage(
videoFilePath: entity.path,
type: "mp4",
duration: 0,
snapshotPath: thumbImagePath ?? "",
);
if (createVideoMessageRes.code == 0) {
message = createVideoMessageRes.data?.messageInfo;
id = createVideoMessageRes.data!.id!;
}
}
// 消息设置
message?.isSelf = true;
// message.conversation = conversation;
message?.status = MessageStatus.V2TIM_MSG_STATUS_SENDING;
// String id = await FTIM.getMessageManager().sendMessage(message);
// message.msgId = id;
V2TimValueCallback<V2TimMessage> sendMessageRes =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.sendMessage(
id: id,
receiver: conversation.userID!,
needReadReceipt: true,
groupID: '',
);
if (sendMessageRes.code == 0) {
// 发送成功
_onNew1v1Message(sendMessageRes.data);
} else {
SCTts.show(
'create fail,code:${sendMessageRes.code},${sendMessageRes.desc}',
);
}
}
}
}
notifyListeners();
}
// 发送消息(房间)
Future<void> _sendRoomMessage(Msg msg) async {
try {
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
_giftFxLog(
'send room msg start '
'type=${msg.type} '
'groupId=${msg.groupId} '
'msg=${msg.msg} '
'giftId=${msg.gift?.id} '
'giftPhoto=${msg.gift?.giftPhoto}',
);
}
if (msg.type == SCRoomMsgType.roomSettingUpdate) {}
var user = msg.user?.copyWith();
var toUser = msg.toUser?.copyWith();
user?.cleanWearHonor();
user?.cleanWearBadge();
user?.cleanPhotos();
toUser?.cleanWearHonor();
toUser?.cleanWearBadge();
toUser?.cleanUseProps();
toUser?.cleanPhotos();
msg.needUpDataUserInfo =
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).needUpDataUserInfo;
msg.user = user;
msg.toUser = toUser;
final textMsg = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createCustomMessage(data: jsonEncode(msg.toJson()));
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
_giftFxLog(
'send room msg createCustomMessage '
'type=${msg.type} '
'code=${textMsg.code} '
'id=${textMsg.data?.id} '
'desc=${textMsg.desc}',
);
}
if (textMsg.code != 0) return;
final sendResult = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.sendMessage(
id: textMsg.data!.id!,
groupID: msg.groupId!,
receiver: '',
);
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
_giftFxLog(
'send room msg result '
'type=${msg.type} '
'code=${sendResult.code} '
'msgId=${sendResult.data?.msgID} '
'groupId=${sendResult.data?.groupID} '
'desc=${sendResult.desc}',
);
}
if (sendResult.code == 0) {}
} catch (e) {
throw Exception("create fail: $e");
}
}
/// 添加消息
void seedSystemRoomTips(String tips, {String groupId = ""}) {
final normalizedTips = tips.trim();
if (normalizedTips.isEmpty) {
return;
}
for (final msg in roomAllMsgList) {
if (msg.type == SCRoomMsgType.systemTips && msg.msg == normalizedTips) {
if ((msg.groupId ?? "").isEmpty && groupId.isNotEmpty) {
msg.groupId = groupId;
}
return;
}
}
addMsg(
Msg(
groupId: groupId,
msg: normalizedTips,
type: SCRoomMsgType.systemTips,
),
);
}
Future<void> loadRoomHistoryMessages(String groupId, {int count = 20}) async {
final normalizedGroupId = groupId.trim();
if (normalizedGroupId.isEmpty) {
return;
}
try {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.getGroupHistoryMessageList(
groupID: normalizedGroupId,
count: count < 10 ? 10 : count,
lastMsgID: null,
);
final messages = result.data ?? const <V2TimMessage>[];
if (!_isCurrentVoiceRoomGroup(normalizedGroupId)) {
return;
}
for (final message in messages.reversed) {
_addRoomHistoryMessage(normalizedGroupId, message);
}
} catch (error) {}
}
void _addRoomHistoryMessage(String groupId, V2TimMessage message) {
try {
final customData = message.customElem?.data;
if (customData == null || customData.trim().isEmpty) {
return;
}
final decoded = json.decode(customData);
if (decoded is! Map) {
return;
}
final data = decoded.map((key, value) => MapEntry(key.toString(), value));
if (data['type'] == SCRoomMsgType.roomRedPacket) {
final packetData = _broadcastPayloadMap(data['data']);
if (packetData.isEmpty) {
return;
}
if (!_isSameRoomRedPacketRegion(packetData)) {
return;
}
RoomRedPacketPendingCache.instance.upsertPayload(packetData);
addMsg(_buildRoomRedPacketMsg(groupId, packetData));
return;
}
final msg = Msg.fromJson(data);
if ((msg.groupId ?? '').trim().isEmpty) {
msg.groupId = groupId;
}
if (_shouldAddRoomHistoryMessage(msg)) {
addMsg(msg);
}
} catch (error) {}
}
bool _shouldAddRoomHistoryMessage(Msg msg) {
switch (msg.type) {
case SCRoomMsgType.text:
case SCRoomMsgType.image:
return true;
case SCRoomMsgType.roomRedPacketClaim:
return true;
case SCRoomMsgType.emoticons:
return (msg.number ?? -1) < 0;
default:
return false;
}
}
addMsg(Msg msg) {
final mergedGiftMsg = _mergeGiftMessageIfNeeded(msg);
if (mergedGiftMsg != null) {
msgAllListener?.call(mergedGiftMsg);
msgGiftListener?.call(mergedGiftMsg);
if (msg.type == SCRoomMsgType.gift) {
msgFloatingGiftListener?.call(msg);
}
notifyListeners();
return;
}
if (msg.type == SCRoomMsgType.roomRedPacket) {
_cacheRoomRedPacketMessage(msg);
}
if (msg.type == SCRoomMsgType.roomRedPacket &&
_hasRoomRedPacketMessage(msg)) {
return;
}
if (msg.type == SCRoomMsgType.roomRedPacketClaim &&
_hasRoomRedPacketClaimMessage(msg)) {
return;
}
roomAllMsgList.insert(0, msg);
if (roomAllMsgList.length > 250) {
roomAllMsgList.removeAt(roomAllMsgList.length - 1);
}
msgAllListener?.call(msg);
if (msg.type == SCRoomMsgType.text) {
roomChatMsgList.insert(0, msg);
if (roomChatMsgList.length > 250) {
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
}
msgChatListener?.call(msg);
} else if (msg.type == SCRoomMsgType.image ||
(msg.type == SCRoomMsgType.emoticons && (msg.number ?? -1) < 0)) {
roomChatMsgList.insert(0, msg);
if (roomChatMsgList.length > 250) {
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
}
msgChatListener?.call(msg);
} else if (msg.type == SCRoomMsgType.roomRedPacket) {
roomChatMsgList.insert(0, msg);
if (roomChatMsgList.length > 250) {
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
}
msgChatListener?.call(msg);
} else if (msg.type == SCRoomMsgType.roomRedPacketClaim) {
roomChatMsgList.insert(0, msg);
if (roomChatMsgList.length > 250) {
roomChatMsgList.removeAt(roomChatMsgList.length - 1);
}
msgChatListener?.call(msg);
} else if (msg.type == SCRoomMsgType.gift ||
msg.type == SCRoomMsgType.luckGiftAnimOther) {
roomGiftMsgList.insert(0, msg);
if (roomGiftMsgList.length > 250) {
roomGiftMsgList.removeAt(roomGiftMsgList.length - 1);
}
msgGiftListener?.call(msg);
if (msg.type == SCRoomMsgType.gift) {
msgFloatingGiftListener?.call(msg);
}
}
}
void _cacheRoomRedPacketMessage(Msg msg) {
try {
final decoded = jsonDecode(msg.msg ?? '');
if (decoded is Map<String, dynamic>) {
RoomRedPacketPendingCache.instance.upsertPayload(decoded);
} else if (decoded is Map) {
RoomRedPacketPendingCache.instance.upsertPayload(
decoded.map((key, value) => MapEntry(key.toString(), value)),
);
}
} catch (_) {}
}
Msg _buildRoomRedPacketMsg(String groupId, dynamic data) {
final payload =
data is Map
? data.map((key, value) => MapEntry(key.toString(), value))
: <String, dynamic>{};
final sender = SocialChatUserProfile(
id: payload['actualAccount']?.toString() ?? payload['userId']?.toString(),
userNickname:
payload['userNickname']?.toString() ??
payload['userName']?.toString(),
userAvatar: payload['userAvatar']?.toString(),
);
return Msg(
groupId: groupId,
msg: jsonEncode(payload),
type: SCRoomMsgType.roomRedPacket,
user: sender,
);
}
bool _hasRoomRedPacketMessage(Msg msg) {
final key = _roomRedPacketMessageKey(msg);
if (key == null || key.isEmpty) {
return false;
}
return roomAllMsgList.any(
(item) =>
item.type == SCRoomMsgType.roomRedPacket &&
_roomRedPacketMessageKey(item) == key,
);
}
String? _roomRedPacketMessageKey(Msg msg) {
if (msg.type != SCRoomMsgType.roomRedPacket) {
return null;
}
try {
final decoded = jsonDecode(msg.msg ?? '');
if (decoded is Map) {
for (final key in const ['packetId', 'packetNo', 'id']) {
final value = decoded[key]?.toString().trim();
if (value != null && value.isNotEmpty) {
return value;
}
}
}
} catch (_) {
return null;
}
return null;
}
bool _hasRoomRedPacketClaimMessage(Msg msg) {
final key = _roomRedPacketClaimMessageKey(msg);
if (key == null || key.isEmpty) {
return false;
}
return roomAllMsgList.any(
(item) =>
item.type == SCRoomMsgType.roomRedPacketClaim &&
_roomRedPacketClaimMessageKey(item) == key,
);
}
String? _roomRedPacketClaimMessageKey(Msg msg) {
if (msg.type != SCRoomMsgType.roomRedPacketClaim) {
return null;
}
final stableKey = (msg.giftBatchId ?? '').trim();
if (stableKey.isNotEmpty) {
return stableKey;
}
final userId = (msg.user?.id ?? '').trim();
final amount = msg.number?.toString() ?? '';
final time = msg.time?.toString() ?? '';
if (userId.isEmpty || amount.isEmpty || time.isEmpty) {
return null;
}
return ['legacy', userId, msg.msg ?? '', amount, time].join('|');
}
Msg? _mergeGiftMessageIfNeeded(Msg incoming) {
if (incoming.type != SCRoomMsgType.gift &&
incoming.type != SCRoomMsgType.luckGiftAnimOther) {
return null;
}
final mergeTarget = _findMergeableGiftMessage(incoming);
if (mergeTarget == null) {
return null;
}
if (_shouldAccumulateGiftMessage(mergeTarget, incoming)) {
mergeTarget.number = (mergeTarget.number ?? 0) + (incoming.number ?? 0);
} else if ((mergeTarget.number ?? 0) <= 0) {
mergeTarget.number = incoming.number;
}
mergeTarget.time = DateTime.now().millisecondsSinceEpoch;
if ((incoming.msg ?? "").trim().isNotEmpty) {
mergeTarget.msg = incoming.msg;
}
if ((mergeTarget.giftBatchId ?? "").trim().isEmpty &&
(incoming.giftBatchId ?? "").trim().isNotEmpty) {
mergeTarget.giftBatchId = incoming.giftBatchId;
}
if ((mergeTarget.targetUserIds ?? const <String>[]).isEmpty &&
(incoming.targetUserIds ?? const <String>[]).isNotEmpty) {
mergeTarget.targetUserIds = List<String>.from(incoming.targetUserIds!);
}
_moveMessageToFront(roomGiftMsgList, mergeTarget);
_moveMessageToFront(roomAllMsgList, mergeTarget);
return mergeTarget;
}
Msg? _findMergeableGiftMessage(Msg incoming) {
final now = DateTime.now().millisecondsSinceEpoch;
for (final existing in roomGiftMsgList) {
if ((existing.time ?? 0) <= 0 ||
now - (existing.time ?? 0) > _giftComboMergeWindowMs) {
continue;
}
if (_isSameGiftComboMessage(existing, incoming)) {
return existing;
}
}
return null;
}
bool _isSameGiftComboMessage(Msg existing, Msg incoming) {
if (existing.type != incoming.type ||
existing.groupId != incoming.groupId ||
existing.user?.id != incoming.user?.id ||
existing.gift?.id != incoming.gift?.id) {
return false;
}
final existingBatchId = (existing.giftBatchId ?? "").trim();
final incomingBatchId = (incoming.giftBatchId ?? "").trim();
if (existingBatchId.isNotEmpty && existingBatchId == incomingBatchId) {
return true;
}
if (existing.toUser?.id == incoming.toUser?.id) {
return true;
}
final existingTargetKey = _giftTargetGroupKey(existing);
final incomingTargetKey = _giftTargetGroupKey(incoming);
return existingTargetKey.isNotEmpty &&
existingTargetKey == incomingTargetKey;
}
bool _shouldAccumulateGiftMessage(Msg existing, Msg incoming) {
final existingBatchId = (existing.giftBatchId ?? "").trim();
final incomingBatchId = (incoming.giftBatchId ?? "").trim();
if (existingBatchId.isNotEmpty && existingBatchId == incomingBatchId) {
return false;
}
final existingTargetKey = _giftTargetGroupKey(existing);
final incomingTargetKey = _giftTargetGroupKey(incoming);
if (existing.toUser?.id != incoming.toUser?.id &&
existingTargetKey.isNotEmpty &&
existingTargetKey == incomingTargetKey) {
return false;
}
return true;
}
String _giftTargetGroupKey(Msg msg) {
final targetUserIds = <String>[];
void addTargetUserId(String? userId) {
final normalizedUserId = (userId ?? "").trim();
if (normalizedUserId.isEmpty ||
targetUserIds.contains(normalizedUserId)) {
return;
}
targetUserIds.add(normalizedUserId);
}
for (final userId in msg.targetUserIds ?? const <String>[]) {
addTargetUserId(userId);
}
addTargetUserId(msg.toUser?.id);
if (targetUserIds.length <= 1) {
return "";
}
targetUserIds.sort();
return targetUserIds.join(",");
}
void _moveMessageToFront(List<Msg> messages, Msg target) {
final index = messages.indexOf(target);
if (index <= 0) {
return;
}
messages.removeAt(index);
messages.insert(0, target);
}
bool _shouldHighlightLuckyGiftReward(SCBroadCastLuckGiftPush broadCastRes) {
final rewardData = broadCastRes.data;
if (rewardData == null) {
return false;
}
if (rewardData.isBigReward) {
return true;
}
return (rewardData.multiple ?? 0) >= 5;
}
bool _isLuckyGiftInCurrentRoom(SCBroadCastLuckGiftPush broadCastRes) {
if (context == null) {
return false;
}
final currentRoomId =
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom?.roomProfile?.roomProfile?.id ??
'';
final roomId = broadCastRes.data?.roomId ?? '';
return currentRoomId.isNotEmpty &&
roomId.isNotEmpty &&
currentRoomId == roomId;
}
SCFloatingMessage _buildLuckyGiftFloatingMessage(
SCBroadCastLuckGiftPush broadCastRes,
) {
final rewardData = broadCastRes.data;
final resolvedGiftUrl = _resolveLuckyGiftGiftPhoto(rewardData);
return SCFloatingMessage(
type: 0,
userId: rewardData?.sendUserId,
roomId: rewardData?.roomId,
toUserId: rewardData?.acceptUserId,
userAvatarUrl: rewardData?.userAvatar,
userName: rewardData?.nickname,
toUserName: rewardData?.acceptNickname,
giftUrl: resolvedGiftUrl,
giftId: rewardData?.giftId,
number: rewardData?.giftQuantity,
coins: rewardData?.awardAmount,
multiple: rewardData?.multiple,
priority: 1000,
);
}
void _handleLuckyGiftGlobalNews(
SCBroadCastLuckGiftPush broadCastRes, {
required String source,
}) {
final rewardData = broadCastRes.data;
if (rewardData == null) {
return;
}
if (_isLuckyGiftInCurrentRoom(broadCastRes)) {
addluckGiftPushQueue(broadCastRes);
}
if (!rewardData.shouldShowGlobalNews ||
(rewardData.multiple ?? 0) < _luckyGiftFloatMinMultiple) {
return;
}
if (source == 'broadcast' && _isLuckyGiftInCurrentRoom(broadCastRes)) {
_giftFxLog(
'skip global lucky gift overlay '
'reason=current_room_already_receives_group_msg '
'roomId=${rewardData.roomId} '
'giftId=${rewardData.giftId}',
);
return;
}
OverlayManager().addMessage(_buildLuckyGiftFloatingMessage(broadCastRes));
}
void _handleRoomLuckyGiftMessage(SCBroadCastLuckGiftPush broadCastRes) {
final rewardData = broadCastRes.data;
if (rewardData == null) {
return;
}
final resolvedGiftUrl = _resolveLuckyGiftGiftPhoto(rewardData);
final roomMsg = Msg(
groupId: '',
msg: '',
type: SCRoomMsgType.gameLuckyGift,
);
roomMsg.gift = SocialChatGiftRes(
id: rewardData.giftId,
giftPhoto: resolvedGiftUrl,
giftTab: 'LUCK',
);
roomMsg.number = 0;
roomMsg.awardAmount = rewardData.awardAmount;
roomMsg.user = SocialChatUserProfile(
id: rewardData.sendUserId,
userNickname: rewardData.nickname,
userAvatar: rewardData.userAvatar,
);
roomMsg.toUser = SocialChatUserProfile(
id: rewardData.acceptUserId,
userNickname: rewardData.acceptNickname,
);
addMsg(roomMsg);
msgLuckyGiftRewardTickerListener?.call(roomMsg);
if (_shouldHighlightLuckyGiftReward(broadCastRes)) {
final highlightMsg = Msg(
groupId: '',
msg: '${rewardData.multiple ?? 0}',
type: SCRoomMsgType.gameLuckyGift_5,
);
highlightMsg.awardAmount = rewardData.awardAmount;
highlightMsg.gift = SocialChatGiftRes(
id: rewardData.giftId,
giftPhoto: resolvedGiftUrl,
giftTab: 'LUCK',
);
highlightMsg.user = SocialChatUserProfile(
id: rewardData.sendUserId,
userNickname: rewardData.nickname,
);
addMsg(highlightMsg);
}
addluckGiftPushQueue(broadCastRes);
_handleLuckyGiftGlobalNews(broadCastRes, source: 'room_group');
if (rewardData.sendUserId ==
AccountStorage().getCurrentUser()?.userProfile?.id) {
Provider.of<GiftProvider>(
context!,
listen: false,
).updateLuckyRewardAmount(roomMsg.awardAmount ?? 0);
}
}
String _resolveLuckyGiftGiftPhoto(Data? rewardData) {
final fallbackUrl = (rewardData?.giftCover ?? '').trim();
final giftId = (rewardData?.giftId ?? '').trim();
final currentContext = context;
if (giftId.isEmpty || currentContext == null || !currentContext.mounted) {
_giftFxLog(
'resolve lucky float gift photo skipped '
'giftId=$giftId '
'fallbackUrl=$fallbackUrl '
'reason=${giftId.isEmpty ? "empty_gift_id" : "invalid_context"}',
);
return fallbackUrl;
}
final appGeneralManager = Provider.of<SCAppGeneralManager>(
currentContext,
listen: false,
);
final giftById = appGeneralManager.getGiftById(giftId);
final giftByStandardId = appGeneralManager.getGiftByStandardId(giftId);
final localGift = giftById ?? giftByStandardId;
final localGiftPhoto = (localGift?.giftPhoto ?? '').trim();
_giftFxLog(
'resolve lucky float gift photo '
'giftId=$giftId '
'fallbackUrl=$fallbackUrl '
'giftById.id=${giftById?.id} '
'giftById.standardId=${giftById?.standardId} '
'giftById.photo=${giftById?.giftPhoto} '
'giftByStandardId.id=${giftByStandardId?.id} '
'giftByStandardId.standardId=${giftByStandardId?.standardId} '
'giftByStandardId.photo=${giftByStandardId?.giftPhoto} '
'resolvedUrl=${localGiftPhoto.isNotEmpty ? localGiftPhoto : fallbackUrl}',
);
if (localGiftPhoto.isNotEmpty) {
return localGiftPhoto;
}
unawaited(appGeneralManager.giftList());
return fallbackUrl;
}
void _handleRegionGiftBroadcast(dynamic data) {
final payload = _broadcastPayloadMap(data);
if (payload.isEmpty) {
return;
}
if (!_isSameRegionBroadcastPayload(payload)) {
final payloadRegion = _payloadText(payload['regionCode']);
return;
}
final roomId = _payloadText(payload['roomId']);
if (roomId.isEmpty) {
return;
}
if (_isCurrentVoiceRoom(roomId)) {
return;
}
final giftId = _payloadText(payload['giftId']);
final quantityValue =
_payloadNum(payload['giftQuantity']) ??
_payloadNum(payload['quantity']) ??
_payloadNum(payload['giftCount']) ??
1;
final quantity = quantityValue <= 0 ? 1 : quantityValue;
final giftCandy = _payloadNum(payload['giftCandy']) ?? 0;
final actualAmount = _payloadNum(payload['actualAmount']);
final amount =
actualAmount != null && actualAmount > 0
? actualAmount
: giftCandy * quantity;
final acceptUserIds = _payloadStringList(payload['acceptUserIds']);
final toUserId = _firstNonBlank([
acceptUserIds.isNotEmpty ? acceptUserIds.first : '',
_payloadText(payload['acceptUserId']),
_payloadText(payload['acceptAccount']),
_payloadText(payload['toUserId']),
]);
final senderId = _firstNonBlank([
_payloadText(payload['actualAccount']),
_payloadText(payload['sendUserId']),
_payloadText(payload['userId']),
_payloadText(payload['account']),
]);
final senderName = _firstNonBlank([
_payloadText(payload['userNickname']),
_payloadText(payload['sendUserName']),
_payloadText(payload['nickname']),
_payloadText(payload['userName']),
]);
final receiverName = _firstNonBlank([
_payloadText(payload['acceptNickname']),
_payloadText(payload['acceptUserNickname']),
_payloadText(payload['toUserName']),
]);
final giftUrl = _resolveRegionGiftPhoto(payload);
final message = SCFloatingMessage(
type: 1,
roomId: roomId,
userId: senderId,
toUserId: toUserId,
userName: senderName,
toUserName: receiverName,
userAvatarUrl: _payloadText(payload['userAvatar']),
giftUrl: giftUrl,
giftId: giftId,
number: quantity,
coins: amount,
broadcastScope: SCFloatingMessage.broadcastScopeRegion,
priority: 900,
);
_enqueueGiftFloatingMessage(
message,
dedupKey: _giftFloatingDedupKey(
roomId: roomId,
senderId: senderId,
receiverId: toUserId,
giftId: giftId,
quantity: quantity,
),
);
}
String _resolveRegionGiftPhoto(Map<String, dynamic> payload) {
final giftId = _payloadText(payload['giftId']);
final fallbackUrl = _firstNonBlank([
_payloadText(payload['giftPhoto']),
_payloadText(payload['giftCover']),
_payloadText(payload['giftUrl']),
_payloadText(payload['giftIcon']),
_payloadText(payload['giftImage']),
_payloadText(payload['cover']),
]);
final currentContext = context;
if (giftId.isEmpty || currentContext == null || !currentContext.mounted) {
return fallbackUrl;
}
final appGeneralManager = Provider.of<SCAppGeneralManager>(
currentContext,
listen: false,
);
final localGift = appGeneralManager.getGiftByIdOrStandardId(giftId);
final localGiftPhoto = (localGift?.giftPhoto ?? '').trim();
if (localGiftPhoto.isNotEmpty) {
return localGiftPhoto;
}
unawaited(appGeneralManager.giftList());
return fallbackUrl;
}
bool _isCurrentVoiceRoom(String roomId) {
final currentRoomId = _currentVoiceRoomId();
return currentRoomId.isNotEmpty && currentRoomId == roomId.trim();
}
bool _isCurrentVisibleVoiceRoom(String roomId) {
final normalizedRoomId = roomId.trim();
final currentContext = context;
if (normalizedRoomId.isEmpty ||
currentContext == null ||
!currentContext.mounted) {
return false;
}
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
);
final currentRoomId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
return rtcProvider.shouldShowRoomVisualEffects &&
rtcProvider.isVoiceRoomRouteVisible &&
currentRoomId.isNotEmpty &&
currentRoomId == normalizedRoomId;
}
String _currentVoiceRoomId() {
final currentContext = context;
if (currentContext == null || !currentContext.mounted) {
return '';
}
return Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
).currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
'';
}
String _currentVoiceRoomGroupId() {
final currentContext = context;
if (currentContext == null || !currentContext.mounted) {
return '';
}
return Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
).currenRoom?.roomProfile?.roomProfile?.roomAccount?.trim() ??
'';
}
bool _isCurrentVoiceRoomGroup(String groupId) {
final currentGroupId = _currentVoiceRoomGroupId();
return currentGroupId.isNotEmpty && currentGroupId == groupId.trim();
}
bool _shouldRouteUntrackedRoomRedPacketBroadcast(
String groupId,
V2TimMessage message,
) {
if (groupId == _currentVoiceRoomGroupId()) {
return false;
}
final customData = message.customElem?.data;
if (customData == null || customData.isEmpty) {
return false;
}
try {
final decoded = json.decode(customData);
final payload = _broadcastPayloadMap(decoded);
return payload['type'] == SCRoomMsgType.roomRedPacket;
} catch (_) {
return false;
}
}
Map<String, dynamic> _broadcastPayloadMap(dynamic data) {
if (data is Map<String, dynamic>) {
return data;
}
if (data is Map) {
return data.map((key, value) => MapEntry(key.toString(), value));
}
if (data is String && data.trim().isNotEmpty) {
try {
return _broadcastPayloadMap(json.decode(data));
} catch (_) {
return <String, dynamic>{};
}
}
return <String, dynamic>{};
}
String _payloadText(dynamic value) {
if (value == null) {
return '';
}
return value.toString().trim();
}
num? _payloadNum(dynamic value) {
if (value is num) {
return value;
}
if (value is String) {
return num.tryParse(value.trim());
}
return null;
}
void _handleVoiceRoomRocketStatusUpdate(dynamic payload) {
final data = _broadcastPayloadMap(payload);
final roomId = _payloadText(data['roomId']);
if (!_isCurrentVoiceRoom(roomId)) {
return;
}
final currentContext = context;
if (currentContext == null || !currentContext.mounted) {
return;
}
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
);
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
}
void _handleVoiceRoomRocketLaunchBroadcast(dynamic payload) {
final launch = RoomRocketLaunchBroadcastMessage.fromJson(
_broadcastPayloadMap(payload),
);
if (!launch.isValid) {
return;
}
final currentContext = context;
if (currentContext != null && currentContext.mounted) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
);
final currentRoomId =
(rtcProvider.currenRoom?.roomProfile?.roomProfile?.id ?? '').trim();
if (currentRoomId == launch.roomId) {
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
return;
}
}
OverlayManager().addMessage(_buildRoomRocketLaunchFloatingMessage(launch));
}
void _handleVoiceRoomRocketRewardPopup(dynamic payload) {
final data = _broadcastPayloadMap(payload);
final roomId = _payloadText(data['roomId']);
final userIds = _payloadStringList(data['userIds']);
final currentUserId =
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? '';
if (currentUserId.isEmpty || !userIds.contains(currentUserId)) {
return;
}
final currentRoomId = _currentVoiceRoomId();
if (roomId.isNotEmpty &&
currentRoomId.isNotEmpty &&
roomId != currentRoomId) {
return;
}
unawaited(_showRoomRocketRewardPopupDialog(roomId));
}
Future<void> _showRoomRocketRewardPopupDialog(String roomId) async {
try {
final res = await SCChatRoomRepository().roomRocketRewardPopups(roomId);
if (res.records.isEmpty) {
return;
}
final recordIds = res.records
.map((item) => item.id)
.where((item) => item.isNotEmpty)
.toList(growable: false);
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
SmartDialog.show(
tag: _roomRocketRewardDialogTag,
alignment: Alignment.center,
debounce: true,
animationType: SmartAnimationType.fade,
maskColor: Colors.transparent,
clickMaskDismiss: false,
builder:
(_) => RoomRocketRewardDialog(
rewards: RoomRocketApiMapper.rewardItemsFromRecords(res.records),
winners: RoomRocketApiMapper.winnersFromRecords(res.records),
loadingText: '',
useMockFallback: false,
onClose: () {
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
if (recordIds.isNotEmpty) {
unawaited(
SCChatRoomRepository().ackRoomRocketRewardPopups(recordIds),
);
}
},
),
);
} catch (_) {}
}
SCFloatingMessage _buildRoomRocketLaunchFloatingMessage(
RoomRocketLaunchBroadcastMessage launch,
) {
return SCFloatingMessage(
type: 3,
roomId: launch.roomId,
rocketLevel: launch.safeLevel,
userAvatarUrl: launch.triggerUser.avatar,
userName: launch.triggerUser.nickname,
userId: _firstNonBlank([
launch.triggerUser.account,
launch.triggerUser.userId,
launch.triggerUserId,
]),
giftId: launch.launchNo,
durationSeconds: launch.durationSeconds,
priority: 1000,
);
}
List<String> _payloadStringList(dynamic value) {
if (value is Iterable) {
return value
.map((item) => _payloadText(item))
.where((item) => item.isNotEmpty)
.toList();
}
if (value is String) {
final textValue = value.trim();
if (textValue.isEmpty) {
return const [];
}
if (textValue.startsWith('[')) {
try {
final decoded = json.decode(textValue);
if (decoded is Iterable) {
return _payloadStringList(decoded);
}
} catch (_) {}
}
return textValue
.split(',')
.map((item) => item.trim())
.where((item) => item.isNotEmpty)
.toList();
}
return const [];
}
void _showCurrentRoomRedPacketDialog(dynamic payload) {
final currentContext = context;
if (currentContext == null || !currentContext.mounted) {
return;
}
final payloadMap = _broadcastPayloadMap(payload);
final payloadRoomId = _payloadText(payloadMap['roomId']);
if (!_isCurrentVisibleVoiceRoom(payloadRoomId)) {
return;
}
if (_isCurrentUserRoomRedPacketPayload(payload) &&
_isDelayedRoomRedPacketPayload(payload)) {
return;
}
final dialogData = RoomRedPacketUiData.fromJson(payloadMap);
if (!dialogData.hasPacketId ||
!dialogData.isOpenable ||
RoomRedPacketOpenDialog.isClaimedLocally(dialogData.packetId)) {
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!currentContext.mounted) {
return;
}
RoomRedPacketOpenDialog.show(currentContext, dialogData);
});
}
bool _isCurrentUserRoomRedPacketPayload(dynamic payload) {
final map = _broadcastPayloadMap(payload);
final senderId = _firstNonBlank([
_payloadText(map['actualAccount']),
_payloadText(map['userId']),
_payloadText(map['sendUserId']),
_payloadText(map['account']),
]);
final currentUserId =
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? '';
return senderId.isNotEmpty &&
currentUserId.isNotEmpty &&
senderId == currentUserId;
}
bool isLogout = false;
logout() async {
roomRedPacketBroadcastGroupId = null;
roomRedPacketBroadcastRegionCode = null;
RoomRedPacketOpenDialog.clearClaimedState();
RoomRedPacketPendingCache.instance.clear();
V2TimCallback logoutRes = await TencentImSDKPlugin.v2TIMManager.logout();
TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.removeAdvancedMsgListener();
if (logoutRes.code == 0) {
isLogout = true;
}
}
///全服广播消息
_newBroadCastMsgRecv(
String groupID,
V2TimMessage message, {
bool forceRegionBroadcast = false,
}) async {
try {
String? customData = message.customElem?.data;
if (customData != null && customData.isNotEmpty) {
final isRegionBroadcastGroup =
forceRegionBroadcast || groupID == roomRedPacketBroadcastGroupId;
final data = json.decode(customData);
var type = data["type"];
if (type == "SYS_ACTIVITY") {
if (onNewActivityMessageCurrentConversationListener != null) {
var recode = Records.fromJson(data["data"]);
onNewActivityMessageCurrentConversationListener?.call(recode);
} else {
activityUnReadCount = activityUnReadCount + 1;
allUnReadCount =
messageUnReadCount +
notifcationUnReadCount +
activityUnReadCount;
notifyListeners();
}
} else if (type == "SYS_ANNOUNCEMENT") {
if (onNewNotifcationMessageCurrentConversationListener != null) {
var recode = Records.fromJson(data["data"]);
onNewNotifcationMessageCurrentConversationListener?.call(recode);
} else {
notifcationUnReadCount = notifcationUnReadCount + 1;
allUnReadCount =
messageUnReadCount +
notifcationUnReadCount +
activityUnReadCount;
notifyListeners();
}
} else if (type == "GAME_BAISHUN_WIN") {
if (SCGlobalConfig.isReview) {
///审核状态不播放动画
return;
}
var fdata = data["data"];
var winCoins = fdata["currencyDiff"];
if (winCoins > 14999) {
///达到5000才飘屏
SCFloatingMessage msg = SCFloatingMessage(
type: 2,
userId: fdata["account"],
userAvatarUrl: fdata["userAvatar"],
userName: fdata["userNickname"],
giftUrl: fdata["gameUrl"],
roomId: fdata["roomId"],
coins: fdata["currencyDiff"],
);
OverlayManager().addMessage(msg);
}
} else if (type == "GAME_LUCKY_GIFT") {
final broadCastRes = SCBroadCastLuckGiftPush.fromJson(data);
_giftFxLog(
'recv GAME_LUCKY_GIFT broadcast '
'giftId=${broadCastRes.data?.giftId} '
'roomId=${broadCastRes.data?.roomId} '
'sendUserId=${broadCastRes.data?.sendUserId} '
'acceptUserId=${broadCastRes.data?.acceptUserId} '
'giftQuantity=${broadCastRes.data?.giftQuantity} '
'awardAmount=${broadCastRes.data?.awardAmount} '
'multiple=${broadCastRes.data?.multiple} '
'multipleType=${broadCastRes.data?.multipleType} '
'globalNews=${broadCastRes.data?.globalNews}',
);
_handleLuckyGiftGlobalNews(broadCastRes, source: 'broadcast');
} else if (type == "REGISTER_REWARD_GRANTED") {
await DataPersistence.setPendingRegisterRewardDialog(true);
await DataPersistence.clearAwaitRegisterRewardSocket();
eventBus.fire(RegisterRewardGrantedEvent(data: data["data"]));
} else if (type == "ROCKET_ENERGY_LAUNCH") {
///火箭触发飘屏
var fdata = data["data"];
SCFloatingMessage msg = SCFloatingMessage(
type: 3,
roomId: fdata["roomId"],
rocketLevel: fdata["fromLevel"],
userAvatarUrl: fdata["userAvatar"],
userName: fdata["nickname"],
userId: fdata["actualAccount"],
priority: 1000,
);
OverlayManager().addMessage(msg);
} else if (type == SCRoomMsgType.voiceRoomRocketLaunchBroadcast) {
_handleVoiceRoomRocketLaunchBroadcast(data["data"]);
} else if (type == SCRoomMsgType.voiceRoomRocketStatusUpdate) {
_handleVoiceRoomRocketStatusUpdate(data["data"]);
} else if (type == SCRoomMsgType.voiceRoomRocketRewardPopup) {
_handleVoiceRoomRocketRewardPopup(data["data"]);
} else if (type == SCRoomMsgType.roomRedPacket) {
///红包触发飘屏
var fData = data["data"];
final payload = _broadcastPayloadMap(fData);
final packetRoomId = _payloadText(payload["roomId"]);
if (isRegionBroadcastGroup &&
!_isDelayedRoomRedPacketPayload(fData)) {
return;
}
if (!_isSameRoomRedPacketRegion(fData)) {
return;
}
RoomRedPacketPendingCache.instance.upsertPayload(fData);
SCFloatingMessage msg = SCFloatingMessage(
type: 4,
roomId: fData["roomId"],
userAvatarUrl: fData["userAvatar"],
userName: fData["userNickname"],
userId: fData["actualAccount"],
toUserId: fData["packetId"],
coins: fData["totalAmount"],
number: fData["totalCount"],
broadcastScope:
isRegionBroadcastGroup
? SCFloatingMessage.broadcastScopeRegion
: '',
priority: 1000,
);
if (_isCurrentVisibleVoiceRoom(packetRoomId)) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
);
rtcProvider.loadRoomRedPacketList(1);
addMsg(
_buildRoomRedPacketMsg(
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount ??
"",
fData,
),
);
_showCurrentRoomRedPacketDialog(fData);
}
OverlayManager().addMessage(msg);
} else if (type == SCRoomMsgType.sendGift) {
if (isRegionBroadcastGroup) {
_handleRegionGiftBroadcast(data["data"]);
}
} else if (type == SCRoomMsgType.inviteRoom) {
///邀请进入房间
var fdata = data["data"];
SCFloatingMessage msg = SCFloatingMessage.fromJson(fdata);
if (msg.toUserId ==
AccountStorage().getCurrentUser()?.userProfile?.id &&
msg.roomId !=
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom?.roomProfile?.roomProfile?.id) {
SmartDialog.dismiss(tag: "showInviteRoom");
SmartDialog.show(
tag: "showInviteRoom",
alignment: Alignment.center,
animationType: SmartAnimationType.fade,
builder: (_) {
return InviteRoomDialog(msg);
},
);
}
}
}
} catch (e) {}
}
_newGroupMsg(String groupID, V2TimMessage message) {
if (groupID !=
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom?.roomProfile?.roomProfile?.roomAccount) {
return;
}
try {
String? customData = message.customElem?.data;
if (customData != null && customData.isNotEmpty) {
// 直接处理字符串格式的自定义数据
final data = json.decode(customData);
if (data["type"] == SCRoomMsgType.voiceRoomRocketLaunchBroadcast) {
_handleVoiceRoomRocketLaunchBroadcast(data["data"]);
return;
}
if (data["type"] == SCRoomMsgType.voiceRoomRocketStatusUpdate) {
_handleVoiceRoomRocketStatusUpdate(data["data"]);
return;
}
if (data["type"] == SCRoomMsgType.voiceRoomRocketRewardPopup) {
_handleVoiceRoomRocketRewardPopup(data["data"]);
return;
}
if (data["type"] == SCRoomMsgType.roomRedPacket) {
///房间红包
var fData = data["data"];
if (!_isSameRoomRedPacketRegion(fData)) {
return;
}
RoomRedPacketPendingCache.instance.upsertPayload(fData);
SCFloatingMessage msg = SCFloatingMessage(
type: 4,
roomId: fData["roomId"],
userAvatarUrl: fData["userAvatar"],
userName: fData["userNickname"],
userId: fData["actualAccount"],
toUserId: fData["packetId"],
coins: fData["totalAmount"],
number: fData["totalCount"],
priority: 1000,
);
final packetRoomId = _payloadText(fData["roomId"]);
if (packetRoomId.isNotEmpty &&
packetRoomId != _currentVoiceRoomId()) {
return;
}
final isCurrentVisibleRoom = _isCurrentVisibleVoiceRoom(packetRoomId);
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).loadRoomRedPacketList(1);
addMsg(_buildRoomRedPacketMsg(groupID, fData));
if (isCurrentVisibleRoom) {
_showCurrentRoomRedPacketDialog(fData);
OverlayManager().addMessage(msg);
}
return;
}
Msg msg = Msg.fromJson(data);
if (msg.type == SCRoomMsgType.sendGift ||
msg.type == SCRoomMsgType.gameBurstCrystalSprint ||
msg.type == SCRoomMsgType.gameBurstCrystalBox) {
///这个消息暂时不监听
return;
}
if (msg.type == SCRoomMsgType.bsm) {
if (msg.toUser?.id ==
AccountStorage().getCurrentUser()?.userProfile?.id) {
SmartDialog.show(
tag: "showConfirmDialog",
alignment: Alignment.center,
debounce: true,
animationType: SmartAnimationType.fade,
builder: (_) {
return MsgDialog(
title: SCAppLocalizations.of(context!)!.tips,
msg: SCAppLocalizations.of(
context!,
)!.invitesYouToTheMicrophone(msg.msg ?? ""),
btnText: SCAppLocalizations.of(context!)!.confirm,
onEnsure: () {
///上麦
num index =
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).findWheat();
if (index > -1) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).shangMai(
index,
eventType: "INVITE",
inviterId: msg.role,
);
}
},
);
},
);
}
return;
}
if (msg.type == SCRoomMsgType.killXiaMai) {
///踢下麦
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
);
if (msg.msg == AccountStorage().getCurrentUser()?.userProfile?.id) {
unawaited(
rtcProvider.handleSelfMicRemovedByRemote(refreshMicList: false),
);
}
rtcProvider.retrieveMicrophoneList(notifyIfUnchanged: false);
return;
}
if (msg.type == SCRoomMsgType.roomSettingUpdate) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).loadRoomInfo(msg.msg ?? "");
return;
}
if (msg.type == SCRoomMsgType.roomBGUpdate) {
SCRoomThemeListRes res;
if ((msg.msg ?? "").isNotEmpty) {
res = SCRoomThemeListRes.fromJson(jsonDecode(msg.msg!));
} else {
res = SCRoomThemeListRes();
}
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).updateRoomBG(res);
return;
}
if (msg.type == SCRoomMsgType.emoticons) {
if ((msg.number ?? -1) >= 0) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).starPlayEmoji(msg);
return;
}
}
if (msg.type == SCRoomMsgType.micChange) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).micChange(BroadCastMicChangePush.fromJson(data).data?.mics);
} else if (msg.type == SCRoomMsgType.roomMusic) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).handleRoomMusicPublishingMessage(msg);
} else if (msg.type == SCRoomMsgType.shangMai ||
msg.type == SCRoomMsgType.xiaMai ||
msg.type == SCRoomMsgType.fengMai ||
msg.type == SCRoomMsgType.jieFeng) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).retrieveMicrophoneList(notifyIfUnchanged: false);
} else if (msg.type == SCRoomMsgType.refreshOnlineUser) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).fetchOnlineUsersList(notifyIfUnchanged: false);
} else if (msg.type == SCRoomMsgType.gameLuckyGift) {
var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data);
_giftFxLog(
'recv GAME_LUCKY_GIFT '
'giftId=${broadCastRes.data?.giftId} '
'roomId=${broadCastRes.data?.roomId} '
'sendUserId=${broadCastRes.data?.sendUserId} '
'acceptUserId=${broadCastRes.data?.acceptUserId} '
'giftQuantity=${broadCastRes.data?.giftQuantity} '
'awardAmount=${broadCastRes.data?.awardAmount} '
'multiple=${broadCastRes.data?.multiple} '
'multipleType=${broadCastRes.data?.multipleType} '
'globalNews=${broadCastRes.data?.globalNews}',
);
_handleRoomLuckyGiftMessage(broadCastRes);
} else {
if (msg.type == SCRoomMsgType.joinRoom) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
);
final shouldShowRoomVisualEffects =
rtcProvider.shouldShowRoomVisualEffects;
if (msg.user != null) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).addOnlineUser(msg.groupId ?? "", msg.user!);
}
rtcProvider.republishCurrentUserRoomMusicStateIfNeeded();
if (msgUserJoinListener != null) {
msgUserJoinListener!(msg);
}
///坐骑
final entryResource = msg.user?.getMountains();
if (entryResource != null) {
if (SCGlobalConfig.allowsHighCostAnimations &&
SCGlobalConfig.isEntryVehicleAnimation &&
shouldShowRoomVisualEffects) {
SCGiftVapSvgaManager().play(
entryResource.sourceUrl ?? "",
priority: SCGiftVapSvgaManager.entryEffectPriority,
type: SCGiftVapSvgaManager.entryEffectType,
);
} else if (SCGlobalConfig.isLowPerformanceDevice &&
shouldShowRoomVisualEffects) {
final roomId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id ?? "";
final coverUrl = _resolveEntryCompensationCover(entryResource);
OverlayManager().addLowPerformanceCompensationMessage(
SCFloatingMessage(
type: 5,
priority: 950,
userId: msg.user?.id ?? "",
roomId: roomId,
userAvatarUrl: msg.user?.userAvatar ?? "",
userName: msg.user?.userNickname ?? "",
giftUrl: coverUrl,
fallbackUrl: coverUrl,
),
dedupKey: _entryFloatingDedupKey(
roomId: roomId,
userId: msg.user?.id,
resourceId: entryResource.id,
),
);
}
}
} else if (msg.type == SCRoomMsgType.gift) {
final gift = msg.gift;
if (gift == null) {
_giftFxLog(
'recv gift msg skipped reason=no_gift '
'fromUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'quantity=${msg.number}',
);
} else {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
);
final special = gift.special ?? "";
final giftSourceUrl = gift.giftSourceUrl ?? "";
final hasSource = giftSourceUrl.isNotEmpty;
final hasAnimation = scGiftHasAnimationSpecial(special);
final hasGlobalGift = special.contains(
SCGiftType.GLOBAL_GIFT.name,
);
final hasFullScreenEffect = scGiftHasFullScreenEffect(special);
_giftFxLog(
'recv gift msg '
'fromUserId=${msg.user?.id} '
'fromUserName=${msg.user?.userNickname} '
'toUserId=${msg.toUser?.id} '
'toUserName=${msg.toUser?.userNickname} '
'giftId=${gift.id} '
'giftName=${gift.giftName} '
'giftSourceUrl=$giftSourceUrl '
'special=$special '
'hasSource=$hasSource '
'hasAnimation=$hasAnimation '
'hasGlobalGift=$hasGlobalGift '
'hasFullScreenEffect=$hasFullScreenEffect '
'effectsEnabled=${SCGlobalConfig.isGiftSpecialEffects}',
);
if (giftSourceUrl.isNotEmpty && special.isNotEmpty) {
if (scGiftHasFullScreenEffect(special)) {
if (SCGlobalConfig.allowsHighCostAnimations &&
SCGlobalConfig.isGiftSpecialEffects &&
rtcProvider.shouldShowRoomVisualEffects) {
_giftFxLog(
'trigger player play path=$giftSourceUrl '
'giftId=${gift.id} giftName=${gift.giftName}',
);
SCGiftVapSvgaManager().play(giftSourceUrl);
} else if (SCGlobalConfig.isLowPerformanceDevice &&
rtcProvider.shouldShowRoomVisualEffects) {
final roomId =
(msg.msg?.trim().isNotEmpty ?? false)
? msg.msg ?? ""
: rtcProvider
.currenRoom
?.roomProfile
?.roomProfile
?.id ??
"";
final quantity = msg.number ?? 0;
final coins = quantity * (gift.giftCandy ?? 0);
_enqueueGiftFloatingMessage(
SCFloatingMessage(
type: 1,
priority: 900,
userId: msg.user?.id ?? "",
toUserId: msg.toUser?.id ?? "",
userAvatarUrl: msg.user?.userAvatar ?? "",
userName: msg.user?.userNickname ?? "",
toUserName: msg.toUser?.userNickname ?? "",
toUserAvatarUrl: msg.toUser?.userAvatar ?? "",
giftUrl: gift.giftPhoto,
giftId: gift.id,
number: quantity,
coins: coins,
roomId: roomId,
),
dedupKey: _giftFloatingDedupKey(
roomId: roomId,
senderId: msg.user?.id,
receiverId: msg.toUser?.id,
giftId: gift.id,
quantity: quantity,
),
);
} else {
_giftFxLog(
'skip player play because visual effects disabled '
'giftId=${gift.id} '
'isGiftSpecialEffects=${SCGlobalConfig.isGiftSpecialEffects} '
'roomVisible=${rtcProvider.shouldShowRoomVisualEffects}',
);
}
} else {
_giftFxLog(
'skip player play because special does not include '
'${SCGiftType.ANIMSCION.name}/$kSCGiftAnimationSpecialAlias/${SCGiftType.GLOBAL_GIFT.name} '
'giftId=${gift.id} special=${gift.special}',
);
}
} else {
_giftFxLog(
'skip player play because giftSourceUrl or special is empty '
'giftId=${gift.id} '
'giftSourceUrl=${gift.giftSourceUrl} '
'special=${gift.special}',
);
}
if (rtcProvider
.currenRoom
?.roomProfile
?.roomSetting
?.showHeartbeat ??
false) {
debouncer.debounce(
duration: Duration(milliseconds: 350),
onDebounce: () {
rtcProvider.requestGiftTriggeredMicRefresh();
},
);
}
final coins = (msg.number ?? 0) * (gift.giftCandy ?? 0);
if (coins > 9999) {
_enqueueGiftFloatingMessage(
SCFloatingMessage(
type: 1,
userId: msg.user?.id ?? "",
toUserId: msg.toUser?.id ?? "",
userAvatarUrl: msg.user?.userAvatar ?? "",
userName: msg.user?.userNickname ?? "",
toUserName: msg.toUser?.userNickname ?? "",
toUserAvatarUrl: msg.toUser?.userAvatar ?? "",
giftUrl: gift.giftPhoto,
giftId: gift.id,
number: msg.number,
coins: coins,
roomId: msg.msg,
),
dedupKey: _giftFloatingDedupKey(
roomId: msg.msg,
senderId: msg.user?.id,
receiverId: msg.toUser?.id,
giftId: gift.id,
quantity: msg.number,
),
);
}
}
} else if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
final hideLGiftAnimal =
Provider.of<GiftProvider>(
context!,
listen: false,
).hideLGiftAnimal;
if (hideLGiftAnimal) {
_giftFxLog(
'recv LUCK_GIFT_ANIM_OTHER skipped '
'reason=hideLGiftAnimal '
'giftPhoto=${msg.gift?.giftPhoto}',
);
} else {
final targetUserIds =
(jsonDecode(msg.msg ?? "") as List)
.map((e) => e as String)
.toList();
_giftFxLog(
'recv LUCK_GIFT_ANIM_OTHER '
'giftPhoto=${msg.gift?.giftPhoto} '
'sendUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'quantity=${msg.number} '
'targetUserIds=${targetUserIds.join(",")}',
);
eventBus.fire(
GiveRoomLuckWithOtherEvent(
msg.gift?.giftPhoto ?? "",
targetUserIds,
),
);
if (msg.user != null && msg.toUser != null && msg.gift != null) {
_giftFxLog(
'trigger floating gift listener from LUCK_GIFT_ANIM_OTHER '
'sendUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'quantity=${msg.number} '
'giftId=${msg.gift?.id}',
);
msgFloatingGiftListener?.call(msg);
} else {
_giftFxLog(
'skip floating gift listener from LUCK_GIFT_ANIM_OTHER '
'reason=incomplete_msg '
'sendUserId=${msg.user?.id} '
'toUserId=${msg.toUser?.id} '
'giftId=${msg.gift?.id} '
'quantity=${msg.number}',
);
}
}
} else if (msg.type == SCRoomMsgType.roomRoleChange) {
///房间身份变动
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).retrieveMicrophoneList();
if (msg.toUser?.id ==
AccountStorage().getCurrentUser()?.userProfile?.id) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom?.entrants?.setRoles(msg.msg);
if (msg.msg == SCRoomRolesType.TOURIST.name &&
!(Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).currenRoom?.roomProfile?.roomSetting?.touristMike ??
false)) {
///如果变成了游客,房间又是禁止游客上麦,需要下麦
num index = Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).userOnMaiInIndex(
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
);
if (index > -1) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).xiaMai(index);
}
}
}
} else if (msg.type == SCRoomMsgType.roomDice) {
if ((msg.number ?? -1) > -1) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).starPlayEmoji(msg);
}
} else if (msg.type == SCRoomMsgType.roomRPS) {
if ((msg.number ?? -1) > -1) {
Provider.of<RealTimeCommunicationManager>(
context!,
listen: false,
).starPlayEmoji(msg);
}
}
addMsg(msg);
}
}
} catch (e) {
throw Exception("message parser fail: $e");
}
}
///加入全服广播群
joinBigBroadcastGroup() async {
bool joined = false;
while (!isLogout && !joined) {
await Future.delayed(Duration(milliseconds: 550));
try {
var joinResult = await TencentImSDKPlugin.v2TIMManager.joinGroup(
groupID: SCGlobalConfig.bigBroadcastGroup,
message: "",
);
if (joinResult.code == 0 || joinResult.code == 10013) {
joined = true;
}
} catch (e) {
SCTts.show('broadcastGroup join fail${e.toString()}');
}
}
}
///发送全服消息
sendBigBroadcastGroup(BigBroadcastGroupMessage msg) async {
try {
final textMsg = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createCustomMessage(data: jsonEncode(msg.toJson()));
if (textMsg.code != 0) return;
final sendResult = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.sendMessage(
id: textMsg.data!.id!,
groupID: SCGlobalConfig.bigBroadcastGroup,
receiver: '',
);
if (sendResult.code == 0) {}
} catch (e) {
throw Exception("create fail: $e");
}
}
Future<void> sendRoomRedPacketBroadcast(
Map<String, dynamic> payload, {
String roomGroupId = '',
bool includeRegionBroadcast = false,
}) async {
if (payload.isEmpty) {
return;
}
final targetGroupIds = <String>{};
final normalizedRoomGroupId = roomGroupId.trim();
if (normalizedRoomGroupId.isNotEmpty) {
targetGroupIds.add(normalizedRoomGroupId);
}
if (includeRegionBroadcast) {
var regionGroupId = roomRedPacketBroadcastGroupId?.trim() ?? '';
if (regionGroupId.isEmpty) {
await syncRoomRedPacketBroadcastGroup();
regionGroupId = roomRedPacketBroadcastGroupId?.trim() ?? '';
}
if (regionGroupId.isNotEmpty) {
targetGroupIds.add(regionGroupId);
}
}
if (targetGroupIds.isEmpty) {
return;
}
final message = <String, dynamic>{
'type': SCRoomMsgType.roomRedPacket,
'data': payload,
};
final data = jsonEncode(message);
for (final groupId in targetGroupIds) {
await _sendRoomRedPacketCustomMessage(groupId, data, payload);
}
}
Future<void> _sendRoomRedPacketCustomMessage(
String groupId,
String data,
Map<String, dynamic> payload,
) async {
try {
final textMsg = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createCustomMessage(data: data);
if (textMsg.code != 0 || textMsg.data?.id == null) {
return;
}
final sendResult = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.sendMessage(id: textMsg.data!.id!, groupID: groupId, receiver: '');
} catch (error) {}
}
Future quitGroup(String groupID) async {
if (groupID.isEmpty) {
return;
}
try {
await TencentImSDKPlugin.v2TIMManager.quitGroup(groupID: groupID);
} finally {
await deleteConversationById("group_$groupID");
}
}
bool _isSameRoomRedPacketRegion(dynamic payload) {
return _isSameRegionBroadcastPayload(payload);
}
bool _isDelayedRoomRedPacketPayload(dynamic payload) {
final map = _broadcastPayloadMap(payload);
final packetMode = _payloadText(map['packetMode']).toUpperCase();
if (packetMode == 'DELAYED') {
return true;
}
if (packetMode == 'IMMEDIATE') {
return false;
}
return _payloadText(map['claimStartTime']).isNotEmpty;
}
bool _isSameRegionBroadcastPayload(dynamic payload) {
final map = _broadcastPayloadMap(payload);
final packetRegion = map['regionCode']?.toString().trim();
final currentRegion = roomRedPacketBroadcastRegionCode?.trim();
if (packetRegion == null ||
packetRegion.isEmpty ||
currentRegion == null ||
currentRegion.isEmpty) {
return true;
}
return packetRegion.toUpperCase() == currentRegion.toUpperCase();
}
///清屏
void clearMessage() {
roomAllMsgList.clear();
roomChatMsgList.clear();
roomGiftMsgList.clear();
msgChatListener?.call(Msg(groupId: "-1000", msg: "", type: ""));
msgAllListener?.call(Msg(groupId: "-1000", msg: "", type: ""));
msgGiftListener?.call(Msg(groupId: "-1000", msg: "", type: ""));
notifyListeners();
}
cleanRoomData() {
roomAllMsgList.clear();
roomGiftMsgList.clear();
roomChatMsgList.clear();
_resetLuckGiftBurstQueue();
onNewMessageListenerGroupMap.forEach((k, v) {
v = null;
});
onNewMessageListenerGroupMap.clear();
}
void addluckGiftPushQueue(SCBroadCastLuckGiftPush broadCastRes) {
if (SCGlobalConfig.isLuckGiftSpecialEffects) {
if (!shouldPlayLuckyGiftBurst(broadCastRes.data)) {
return;
}
final now = DateTime.now().millisecondsSinceEpoch;
_pruneRecentLuckGiftPushEvents(now);
final eventKey = _luckyGiftPushEventKey(broadCastRes);
if (_hasSeenLuckGiftPushEvent(eventKey, now)) {
return;
}
final mergeKey = _luckyGiftPushMergeKey(broadCastRes);
final mergeTarget = _findMergeableLuckGiftPushEntry(mergeKey, now);
if (mergeTarget != null) {
mergeTarget.merge(broadCastRes, eventKey);
_rememberLuckGiftPushEvent(eventKey, now);
_giftFxLog(
'merge lucky gift burst '
'mergeKey=$mergeKey '
'awardAmount=${mergeTarget.reward.data?.awardAmount} '
'giftQuantity=${mergeTarget.reward.data?.giftQuantity} '
'multiple=${mergeTarget.reward.data?.multiple} '
'eventCount=${mergeTarget.eventKeys.length}',
);
_scheduleLuckGiftBackCoins(restart: true);
return;
}
while (_luckGiftPushQueue.length >= _maxLuckGiftPushQueueLength) {
_luckGiftPushQueue.removeFirst();
}
_luckGiftPushQueue.add(
_LuckGiftPushQueueEntry(
reward: broadCastRes,
eventKey: eventKey,
mergeKey: mergeKey,
updatedAt: now,
),
);
_rememberLuckGiftPushEvent(eventKey, now);
_scheduleLuckGiftBackCoins();
}
}
static bool shouldPlayLuckyGiftBurst(Data? rewardData) {
if (rewardData == null) {
return false;
}
final awardAmount = rewardData.awardAmount ?? 0;
final multiple = rewardData.multiple ?? 0;
return awardAmount > _luckyGiftBurstMinAwardAmount ||
multiple >= _luckyGiftBurstMinMultiple;
}
void cleanLuckGiftBackCoins() {
_resetLuckGiftBurstQueue();
}
void _resetLuckGiftBurstQueue() {
_luckGiftPushQueue.clear();
currentPlayingLuckGift = null;
_currentLuckGiftPushKey = null;
_currentLuckGiftPushEventKeys.clear();
_luckGiftPushPlayTimer?.cancel();
_luckGiftPushPlayTimer = null;
_luckGiftPushPlaybackToken++;
_recentLuckGiftPushEventTimes.clear();
}
void _scheduleLuckGiftBackCoins({bool restart = false}) {
if (currentPlayingLuckGift != null || _luckGiftPushQueue.isEmpty) {
return;
}
if (restart) {
_luckGiftPushPlayTimer?.cancel();
_luckGiftPushPlayTimer = null;
}
if (_luckGiftPushPlayTimer?.isActive ?? false) {
return;
}
_luckGiftPushPlayTimer = Timer(
Duration(milliseconds: _luckyGiftBurstMergeDelayMs),
() {
_luckGiftPushPlayTimer = null;
playLuckGiftBackCoins();
},
);
}
void playLuckGiftBackCoins() {
if (currentPlayingLuckGift != null || _luckGiftPushQueue.isEmpty) {
return;
}
_luckGiftPushPlayTimer?.cancel();
_luckGiftPushPlayTimer = null;
final queueEntry = _luckGiftPushQueue.removeFirst();
currentPlayingLuckGift = queueEntry.reward;
_currentLuckGiftPushKey = queueEntry.eventKey;
_currentLuckGiftPushEventKeys = Set<String>.from(queueEntry.eventKeys);
final playbackToken = ++_luckGiftPushPlaybackToken;
notifyListeners();
Future.delayed(
Duration(milliseconds: _luckyGiftBurstPlaybackWatchdogMs),
() {
completeLuckGiftBackCoins(playbackToken);
},
);
}
void completeLuckGiftBackCoins(int playbackToken) {
if (playbackToken != _luckGiftPushPlaybackToken ||
currentPlayingLuckGift == null) {
return;
}
currentPlayingLuckGift = null;
_currentLuckGiftPushKey = null;
_currentLuckGiftPushEventKeys.clear();
notifyListeners();
_scheduleLuckGiftBackCoins();
}
bool _hasSeenLuckGiftPushEvent(String eventKey, int now) {
if (eventKey.isEmpty) {
return false;
}
if (_currentLuckGiftPushKey == eventKey ||
_currentLuckGiftPushEventKeys.contains(eventKey)) {
return true;
}
for (final queued in _luckGiftPushQueue) {
if (queued.eventKeys.contains(eventKey)) {
return true;
}
}
final seenAt = _recentLuckGiftPushEventTimes[eventKey];
return seenAt != null && now - seenAt <= _luckyGiftBurstRecentEventTtlMs;
}
void _rememberLuckGiftPushEvent(String eventKey, int now) {
if (eventKey.isEmpty) {
return;
}
_recentLuckGiftPushEventTimes[eventKey] = now;
}
void _pruneRecentLuckGiftPushEvents(int now) {
_recentLuckGiftPushEventTimes.removeWhere(
(_, seenAt) => now - seenAt > _luckyGiftBurstRecentEventTtlMs,
);
}
_LuckGiftPushQueueEntry? _findMergeableLuckGiftPushEntry(
String mergeKey,
int now,
) {
if (mergeKey.isEmpty) {
return null;
}
final queuedEntries = _luckGiftPushQueue.toList(growable: false);
for (final queued in queuedEntries.reversed) {
if (queued.mergeKey == mergeKey &&
now - queued.updatedAt <= _luckyGiftBurstMergeWindowMs) {
return queued;
}
}
return null;
}
String _luckyGiftPushEventKey(SCBroadCastLuckGiftPush broadCastRes) {
final rewardData = broadCastRes.data;
if (rewardData == null) {
return '';
}
return '${rewardData.roomId ?? ""}|'
'${rewardData.giftId ?? ""}|'
'${rewardData.sendUserId ?? ""}|'
'${rewardData.acceptUserId ?? ""}|'
'${rewardData.giftQuantity ?? 0}|'
'${rewardData.awardAmount ?? 0}|'
'${rewardData.multiple ?? 0}|'
'${rewardData.normalizedMultipleType}|'
'${rewardData.balance ?? 0}';
}
String _luckyGiftPushMergeKey(SCBroadCastLuckGiftPush broadCastRes) {
final rewardData = broadCastRes.data;
if (rewardData == null) {
return '';
}
return '${rewardData.roomId ?? ""}|'
'${rewardData.giftId ?? ""}|'
'${rewardData.sendUserId ?? ""}|'
'${rewardData.acceptUserId ?? ""}';
}
void updateNotificationCount(int count) {
notifcationUnReadCount = 0;
allUnReadCount =
messageUnReadCount + notifcationUnReadCount + activityUnReadCount;
notifyListeners();
}
void updateActivityCount(int count) {
activityUnReadCount = 0;
allUnReadCount =
messageUnReadCount + notifcationUnReadCount + activityUnReadCount;
notifyListeners();
}
void updateSystemCount(int count) {
for (final conversation in conversationMap.values) {
if (_isSystemConversation(conversation)) {
conversation.unreadCount = 0;
}
}
systemUnReadCount = 0;
notifyListeners();
}
void updateCustomerCount(int count) {
conversationMap["c2c_${customerInfo?.id}"]?.unreadCount = 0;
customerUnReadCount = 0;
notifyListeners();
}
Future<void> deleteConversationById(
String conversationID, {
bool needShowToast = false,
}) async {
if (conversationID.isEmpty) {
return;
}
V2TimCallback deleteConversationRes = await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.deleteConversation(conversationID: conversationID);
if (deleteConversationRes.code == 0 && needShowToast && context != null) {
SCTts.show(SCAppLocalizations.of(context!)!.operationSuccessful);
}
await initConversation();
}
void clearC2CHistoryMessage(String conversationID, bool needShowToast) async {
await deleteConversationById(conversationID, needShowToast: needShowToast);
}
}