3071 lines
104 KiB
Dart
3071 lines
104 KiB
Dart
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);
|
||
}
|
||
}
|