yumi-flutter/lib/shared/data_sources/sources/local/floating_screen_manager.dart
2026-06-02 13:57:50 +08:00

1068 lines
32 KiB
Dart

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app/constants/sc_global_config.dart';
import 'package:yumi/main.dart';
import 'package:provider/provider.dart';
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
import 'package:yumi/shared/tools/sc_entrance_vap_svga_manager.dart';
import 'package:yumi/services/audio/rtc_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/ui_kit/widgets/room/floating/floating_game_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_gift_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_lucky_gift_global_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_luck_gift_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_rocket_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_vip_entry_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_cp_relation_screen_widget.dart';
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
typedef FloatingScreenManager = OverlayManager;
class OverlayManager {
static const int _maxDeferredRegionRedPacketCount = 12;
static const Duration _redPacketFloatingDedupWindow = Duration(minutes: 30);
static const Duration _rocketFloatingFallbackDedupWindow = Duration(
seconds: 30,
);
static const Duration _gameWinFloatingDedupWindow = Duration(seconds: 10);
static const int _maxStackedFloatingVisibleCount = 2;
static const List<int> _stackedFloatingOrder = [6, 5, 1, 4, 3];
static const double _stackedFloatingBaseTop = 70;
static const double _stackedFloatingGap = 8;
final SCPriorityQueue<SCFloatingMessage> _messageQueue = SCPriorityQueue(
(a, b) => b.priority.compareTo(a.priority),
);
final List<SCFloatingMessage> _deferredRegionRedPacketMessages = [];
final Map<String, DateTime> _recentFloatingKeys = {};
final Map<int, OverlayEntry> _stackedOverlayEntries = {};
final Map<int, SCFloatingMessage> _stackedMessages = {};
bool _isPlaying = false;
OverlayEntry? _currentOverlayEntry;
SCFloatingMessage? _currentMessage;
bool _homeRootTabsVisible = false;
int _suppressedCount = 0;
bool _isProcessing = false;
bool _isDisposed = false;
static final OverlayManager _instance = OverlayManager._internal();
factory OverlayManager() => _instance;
OverlayManager._internal();
void addMessage(SCFloatingMessage message) {
if (_isDisposed || _isSuppressed) return;
if (!SCGlobalConfig.isFloatingBroadcastClosed) {
if (_isRecentFloatingDuplicate(message)) {
return;
}
_rememberFloatingMessage(message);
_enqueueMessage(
message,
debugLabel: 'floating_message_type_${message.type ?? -1}',
);
} else {
_removeActiveMessage(scheduleNext: false);
_removeAllStackedMessages();
_messageQueue.clear();
_deferredRegionRedPacketMessages.clear();
_isPlaying = false;
_isProcessing = false;
_isDisposed = false;
}
}
void _enqueueMessage(
SCFloatingMessage message, {
required String debugLabel,
}) {
unawaited(_warmMessageImages(message));
SCRoomEffectScheduler().scheduleDeferredEffect(
debugLabel: debugLabel,
action: () {
if (_isDisposed || _isSuppressed) {
return;
}
_messageQueue.add(message);
_safeScheduleNext();
},
);
}
bool _isRecentFloatingDuplicate(SCFloatingMessage message) {
_pruneRecentFloatingKeys();
final key = _floatingDedupKeyFor(message);
if (key == null) {
return false;
}
final recentTime = _recentFloatingKeys[key];
if (recentTime == null) {
return false;
}
return DateTime.now().difference(recentTime) <
_floatingDedupWindowFor(message);
}
void _rememberFloatingMessage(SCFloatingMessage message) {
final key = _floatingDedupKeyFor(message);
if (key == null) {
return;
}
_recentFloatingKeys[key] = DateTime.now();
_pruneRecentFloatingKeys();
}
void _pruneRecentFloatingKeys() {
final now = DateTime.now();
_recentFloatingKeys.removeWhere(
(_, time) => now.difference(time) > _redPacketFloatingDedupWindow,
);
}
String? _floatingDedupKeyFor(SCFloatingMessage message) {
if (message.type == 3) {
final launchNo = message.giftId?.trim() ?? '';
if (launchNo.isNotEmpty) {
return 'room_rocket_launch|$launchNo';
}
final roomId = message.roomId?.trim() ?? '';
final level = message.rocketLevel ?? 0;
if (roomId.isEmpty || level <= 0) {
return null;
}
return 'room_rocket_launch_fallback|$roomId|$level';
}
if (message.type == 4) {
final packetId = message.toUserId?.trim() ?? '';
if (packetId.isEmpty) {
return null;
}
return 'room_red_packet|$packetId';
}
if (message.type == 2) {
final roundId = message.giftId?.trim() ?? '';
final userId = message.userId?.trim() ?? '';
if (roundId.isNotEmpty) {
return 'game_win|$roundId|$userId';
}
final roomId = message.roomId?.trim() ?? '';
final gameId = message.gameId?.trim() ?? '';
final coins = message.coins ?? 0;
if (roomId.isEmpty && userId.isEmpty && gameId.isEmpty && coins == 0) {
return null;
}
return 'game_win_fallback|$roomId|$userId|$gameId|$coins';
}
if (message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType) {
final eventId = message.gameId?.trim() ?? '';
if (eventId.isNotEmpty) {
return 'global_lucky_gift_win|$eventId';
}
final roomId = message.roomId?.trim() ?? '';
final userId = message.userId?.trim() ?? '';
final toUserId = message.toUserId?.trim() ?? '';
final giftId = message.giftId?.trim() ?? '';
final coins = message.coins ?? 0;
final multiple = message.multiple ?? 0;
if (roomId.isEmpty &&
userId.isEmpty &&
toUserId.isEmpty &&
giftId.isEmpty &&
coins == 0 &&
multiple == 0) {
return null;
}
return 'global_lucky_gift_win_fallback|$roomId|$userId|$toUserId|$giftId|$coins|$multiple';
}
if (message.type == 6) {
final roomId = message.roomId?.trim() ?? '';
final action = message.giftId?.trim() ?? '';
final relationType = message.giftTab?.trim() ?? '';
final userId = message.userId?.trim() ?? '';
final toUserId = message.toUserId?.trim() ?? '';
if (roomId.isEmpty &&
action.isEmpty &&
relationType.isEmpty &&
userId.isEmpty &&
toUserId.isEmpty) {
return null;
}
return 'cp_relation|$roomId|$action|$relationType|$userId|$toUserId';
}
return null;
}
Duration _floatingDedupWindowFor(SCFloatingMessage message) {
if (message.type == 2 ||
message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType) {
return _gameWinFloatingDedupWindow;
}
if (message.type == 6) {
return _gameWinFloatingDedupWindow;
}
if (message.type == 3 && (message.giftId?.trim() ?? '').isEmpty) {
return _rocketFloatingFallbackDedupWindow;
}
return _redPacketFloatingDedupWindow;
}
Future<void> _warmMessageImages(SCFloatingMessage message) async {
unawaited(
warmImageResource(
message.userAvatarUrl ?? "",
logicalWidth: 80,
logicalHeight: 80,
),
);
unawaited(
warmImageResource(
message.toUserAvatarUrl ?? "",
logicalWidth: 80,
logicalHeight: 80,
),
);
unawaited(
warmImageResource(
message.giftUrl ?? "",
logicalWidth: message.type == 5 || message.type == 6 ? 350 : 96,
logicalHeight: message.type == 5 || message.type == 6 ? 84 : 96,
),
);
if (message.type == 0) {
unawaited(_warmLuckyGiftBannerSvga());
}
if (message.type == 2) {
unawaited(_warmGameWinBannerSvga());
}
}
Future<void> _warmLuckyGiftBannerSvga() async {
try {
await SCSvgaAssetWidget.preloadAsset(
FloatingLuckGiftScreenWidget.backgroundSvgaAssetPath,
);
} catch (error) {
// Preload failure is non-fatal; the banner will retry during rendering.
}
}
Future<void> _warmGameWinBannerSvga() async {
try {
await SCSvgaAssetWidget.preloadAsset(
FloatingGameScreenWidget.backgroundSvgaAssetPath,
);
} catch (error) {
// Preload failure is non-fatal; the banner will retry during rendering.
}
}
void _safeScheduleNext() {
if (_isSuppressed) return;
if (_isProcessing || _messageQueue.isEmpty) return;
_isProcessing = true;
try {
_scheduleNext();
} finally {
_isProcessing = false;
}
}
void _scheduleNext() {
if (_messageQueue.isEmpty || _isDisposed || _isSuppressed) {
return;
}
try {
final context = navigatorKey.currentState?.context;
if (context == null || !context.mounted) return;
while (_messageQueue.isNotEmpty) {
final messageToPlay = _removeNextPlayableMessage(context);
if (messageToPlay == null) {
return;
}
_playMessage(messageToPlay);
if (!_isStackedFloatingMessage(messageToPlay)) {
return;
}
}
} catch (e) {
_isPlaying = false;
_safeScheduleNext();
}
}
SCFloatingMessage? _removeNextPlayableMessage(BuildContext context) {
final skippedMessages = <SCFloatingMessage>[];
SCFloatingMessage? selectedMessage;
while (_messageQueue.isNotEmpty) {
final message = _messageQueue.removeFirst();
if (!_shouldDisplayMessage(context, message)) {
if (_shouldDeferRegionRedPacketMessage(context, message)) {
_deferRegionRedPacketMessage(context, message);
continue;
}
_logRegionRedPacketDrop(context, message);
continue;
}
if (!_canPlayMessage(message)) {
skippedMessages.add(message);
continue;
}
selectedMessage = message;
break;
}
for (final message in skippedMessages) {
_messageQueue.add(message);
}
return selectedMessage;
}
bool _canPlayMessage(SCFloatingMessage message) {
final slotKey = _stackSlotKeyFor(message);
if (slotKey != null) {
return !_isPlaying &&
!_stackedOverlayEntries.containsKey(slotKey) &&
_stackedOverlayEntries.length < _maxStackedFloatingVisibleCount;
}
return !_isPlaying && _stackedOverlayEntries.isEmpty;
}
void _playMessage(SCFloatingMessage message) {
if (_isStackedFloatingMessage(message)) {
_playStackedMessage(message);
return;
}
_isPlaying = true;
_currentMessage = message;
final context = navigatorKey.currentState?.context;
if (context == null || !context.mounted) {
_isPlaying = false;
_currentMessage = null;
_safeScheduleNext();
return;
}
bool completed = false;
void onComplete() {
if (completed) return;
completed = true;
try {
_currentOverlayEntry?.remove();
_currentOverlayEntry = null;
_isPlaying = false;
_currentMessage = null;
_safeScheduleNext();
} catch (e) {
_isPlaying = false;
_currentMessage = null;
_safeScheduleNext();
}
}
_currentOverlayEntry = OverlayEntry(
builder:
(_) => Align(
alignment: AlignmentDirectional.topStart,
child: Transform.translate(
offset: Offset(0, 70.w),
child: RepaintBoundary(
child: _buildScreenWidget(message, onComplete),
),
),
),
);
Overlay.of(context).insert(_currentOverlayEntry!);
}
void _playStackedMessage(SCFloatingMessage message) {
final slotKey = _stackSlotKeyFor(message);
final context = navigatorKey.currentState?.context;
if (slotKey == null || context == null || !context.mounted) {
return;
}
if (_stackedOverlayEntries.containsKey(slotKey)) {
_messageQueue.add(message);
return;
}
bool completed = false;
void onComplete() {
if (completed) return;
completed = true;
_removeStackedMessage(slotKey);
_markStackedEntriesNeedsBuild();
_safeScheduleNext();
}
_stackedMessages[slotKey] = message;
final entry = OverlayEntry(
builder:
(_) => Align(
alignment: AlignmentDirectional.topStart,
child: Transform.translate(
offset: Offset(0, _stackedWrapperTopFor(message).w),
child: RepaintBoundary(
child: _buildScreenWidget(message, onComplete),
),
),
),
);
_stackedOverlayEntries[slotKey] = entry;
Overlay.of(context).insert(entry);
_markStackedEntriesNeedsBuild();
}
Widget _buildScreenWidget(
SCFloatingMessage message,
VoidCallback onComplete,
) {
switch (message.type) {
case 0:
return FloatingLuckGiftScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 1:
//房间礼物
return FloatingGiftScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 2:
//游戏中奖
return FloatingGameScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case FloatingLuckyGiftGlobalScreenWidget.floatingType:
// Global lucky gift win.
return FloatingLuckyGiftGlobalScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 3:
//火箭
return FloatingRoomRocketScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 4:
//红包
return FloatingRoomRedenvelopeScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 5:
//VIP进房飘窗
return FloatingVipEntryScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 6:
//CP / 挚友关系飘窗
return FloatingCpRelationScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
default:
onComplete();
return Container();
}
}
int? _stackSlotKeyFor(SCFloatingMessage message) {
switch (message.type) {
case 0:
case 1:
return 1;
case 3:
return 3;
case 4:
return 4;
case 5:
return 5;
case 6:
return 6;
default:
return null;
}
}
bool _isStackedFloatingMessage(SCFloatingMessage message) {
return _stackSlotKeyFor(message) != null;
}
double _stackedWrapperTopFor(SCFloatingMessage message) {
final slotKey = _stackSlotKeyFor(message);
if (slotKey == null) {
return _stackedFloatingBaseTop;
}
var visualTop = _stackedFloatingBaseTop;
for (final key in _stackedFloatingOrder) {
if (key == slotKey) {
break;
}
final previousMessage = _stackedMessages[key];
if (previousMessage != null) {
visualTop +=
_stackedVisualHeightFor(previousMessage) + _stackedFloatingGap;
}
}
final wrapperTop = visualTop - _stackedInternalTopFor(message);
return wrapperTop < 0 ? 0 : wrapperTop;
}
double _stackedVisualHeightFor(SCFloatingMessage message) {
switch (message.type) {
case 0:
return 83;
case 1:
return scIsCpGiftTab(message.giftTab) ? 65 : 54;
case 3:
return 68;
case 4:
return 66;
case 5:
return 84;
case 6:
return FloatingCpRelationScreenWidget.bannerHeight;
default:
return 68;
}
}
double _stackedInternalTopFor(SCFloatingMessage message) {
switch (message.type) {
case 1:
return 20;
case 4:
return 42;
default:
return 0;
}
}
void _markStackedEntriesNeedsBuild() {
for (final entry in _stackedOverlayEntries.values) {
entry.markNeedsBuild();
}
}
void _removeStackedMessage(int slotKey) {
try {
_stackedOverlayEntries.remove(slotKey)?.remove();
} catch (_) {}
_stackedMessages.remove(slotKey);
}
void _removeAllStackedMessages() {
final slotKeys = List<int>.from(_stackedOverlayEntries.keys);
for (final slotKey in slotKeys) {
_removeStackedMessage(slotKey);
}
}
void activate() {
_isDisposed = false;
_loadSavedDisplayPreference();
}
void _loadSavedDisplayPreference() {
final account = AccountStorage().getCurrentUser()?.userProfile?.account;
final savedFloatingBroadcastScope = DataPersistence.getString(
SCGlobalConfig.floatingBroadcastScopeStorageKey(account),
);
if (savedFloatingBroadcastScope.isNotEmpty) {
final scope = SCGlobalConfig.clampFloatingBroadcastScope(
savedFloatingBroadcastScope,
);
SCGlobalConfig.floatingBroadcastScope = scope;
return;
}
const defaultEffectsEnabled = true;
final isLegacyFloatingAnimationInGlobal = DataPersistence.getBool(
SCGlobalConfig.legacyFloatingAnimationInGlobalStorageKey(account),
defaultValue: defaultEffectsEnabled,
);
SCGlobalConfig.floatingBroadcastScope =
SCGlobalConfig.clampVisualEffectPreference(
isLegacyFloatingAnimationInGlobal,
)
? SCGlobalConfig.floatingBroadcastScopeAll
: SCGlobalConfig.floatingBroadcastScopeOff;
}
void setHomeRootTabsVisible(bool visible) {
if (_homeRootTabsVisible == visible) {
return;
}
_homeRootTabsVisible = visible;
if (!visible) {
_removeActiveRegionBroadcastMessage();
_removeRegionBroadcastMessages();
return;
}
_restoreDeferredRegionRedPacketMessages();
_safeScheduleNext();
}
void beginSuppressFloatingScreens({String reason = 'unknown'}) {
_suppressedCount += 1;
_removeActiveMessage(scheduleNext: false);
_removeAllStackedMessages();
_messageQueue.clear();
_isPlaying = false;
_isProcessing = false;
}
void endSuppressFloatingScreens({String reason = 'unknown'}) {
if (_suppressedCount > 0) {
_suppressedCount -= 1;
}
if (!_isSuppressed) {
_safeScheduleNext();
}
}
void refreshDisplayPreference() {
if (_isDisposed) {
return;
}
if (SCGlobalConfig.isFloatingBroadcastClosed) {
_removeActiveMessage(scheduleNext: false);
_removeAllStackedMessages();
_messageQueue.clear();
_deferredRegionRedPacketMessages.clear();
return;
}
_restoreDeferredRegionRedPacketMessages();
final context = navigatorKey.currentState?.context;
final activeMessage = _currentMessage;
if (context != null &&
context.mounted &&
activeMessage != null &&
!_shouldDisplayMessage(context, activeMessage)) {
_removeActiveMessage(scheduleNext: false);
}
if (context != null && context.mounted) {
_removeHiddenStackedMessages(context);
}
_safeScheduleNext();
}
void dispose() {
_isDisposed = true;
_homeRootTabsVisible = false;
_suppressedCount = 0;
_currentOverlayEntry?.remove();
_currentOverlayEntry = null;
_currentMessage = null;
_removeAllStackedMessages();
_messageQueue.clear();
_deferredRegionRedPacketMessages.clear();
_isPlaying = false;
_isProcessing = false;
}
void removeRoom({String? roomId}) {
final normalizedRoomId = roomId?.trim() ?? '';
_removeActiveRoomMessage(
roomId: normalizedRoomId,
includeRedPacket: normalizedRoomId.isNotEmpty,
);
_removeMessagesByType(1);
_removeMessagesByType(0);
_removeMessagesByType(5);
_removeMessagesByType(6);
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(3, roomId: normalizedRoomId);
}
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(2, roomId: normalizedRoomId);
}
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(
FloatingLuckyGiftGlobalScreenWidget.floatingType,
roomId: normalizedRoomId,
);
}
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(4, roomId: normalizedRoomId);
}
}
// 辅助方法:移除特定类型的消息
void _removeMessagesByType(int type, {String? roomId}) {
final normalizedRoomId = roomId?.trim() ?? '';
// 由于 PriorityQueue 没有直接的方法来移除特定元素,
// 我们需要重建队列,排除指定类型的消息
final newQueue = SCPriorityQueue<SCFloatingMessage>(
(a, b) => b.priority.compareTo(a.priority),
);
// 遍历原始队列,只添加非指定类型的消息
while (_messageQueue.isNotEmpty) {
final message = _messageQueue.removeFirst();
final matchesType = message.type == type;
final matchesRoom =
normalizedRoomId.isEmpty ||
(message.roomId ?? "").trim() == normalizedRoomId;
if (!matchesType || !matchesRoom) {
newQueue.add(message);
}
}
// 将新队列赋值给原队列
while (newQueue.isNotEmpty) {
_messageQueue.add(newQueue.removeFirst());
}
_removeStackedMessagesWhere((message) {
final matchesType = message.type == type;
final matchesRoom =
normalizedRoomId.isEmpty ||
(message.roomId ?? "").trim() == normalizedRoomId;
return matchesType && matchesRoom;
});
}
// 辅助方法:获取队列信息
int get queueLength => _messageQueue.length;
bool get isPlaying => _isPlaying || _stackedOverlayEntries.isNotEmpty;
bool get _isSuppressed => _suppressedCount > 0;
bool _isRegionRedPacketMessage(SCFloatingMessage message) {
return message.type == 4 && message.isRegionBroadcast;
}
bool _isRegionRoomRocketMessage(SCFloatingMessage message) {
return message.type == 3 && message.isRegionBroadcast;
}
bool _isRegionGameWinMessage(SCFloatingMessage message) {
return message.type == 2 && message.isRegionBroadcast;
}
bool _isRegionLuckyGiftGlobalMessage(SCFloatingMessage message) {
return message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType &&
message.isRegionBroadcast;
}
bool _isRegionCpGiftMessage(SCFloatingMessage message) {
return message.type == 1 &&
message.isRegionBroadcast &&
scIsCpGiftTab(message.giftTab);
}
bool _isRegionCpRelationMessage(SCFloatingMessage message) {
return message.type == 6 && message.isRegionBroadcast;
}
bool _isCrossRoomRegionFloatingMessage(SCFloatingMessage message) {
return _isRegionRedPacketMessage(message) ||
_isRegionRoomRocketMessage(message) ||
_isRegionGameWinMessage(message) ||
_isRegionLuckyGiftGlobalMessage(message) ||
_isRegionCpGiftMessage(message) ||
_isRegionCpRelationMessage(message);
}
bool _shouldDeferRegionRedPacketMessage(
BuildContext context,
SCFloatingMessage message,
) {
if (!_isCrossRoomRegionFloatingMessage(message) ||
SCGlobalConfig.isFloatingBroadcastClosed) {
return false;
}
return !_shouldDisplayRegionBroadcastMessage(context, message);
}
void _deferRegionRedPacketMessage(
BuildContext context,
SCFloatingMessage message,
) {
final key = _floatingDedupKeyFor(message);
if (key != null &&
_deferredRegionRedPacketMessages.any(
(item) => _floatingDedupKeyFor(item) == key,
)) {
return;
}
while (_deferredRegionRedPacketMessages.length >=
_maxDeferredRegionRedPacketCount) {
_deferredRegionRedPacketMessages.removeAt(0);
}
_deferredRegionRedPacketMessages.add(message);
}
void _restoreDeferredRegionRedPacketMessages() {
if (_deferredRegionRedPacketMessages.isEmpty) {
return;
}
final messages = List<SCFloatingMessage>.from(
_deferredRegionRedPacketMessages,
);
_deferredRegionRedPacketMessages.clear();
for (final message in messages) {
_messageQueue.add(message);
}
}
void _logRegionRedPacketDrop(
BuildContext context,
SCFloatingMessage message,
) {
if (message.type != 4) {
return;
}
}
bool _shouldDisplayMessage(BuildContext context, SCFloatingMessage? message) {
if (message == null) {
return false;
}
if (SCGlobalConfig.isFloatingBroadcastClosed) {
return false;
}
if (message.isRegionBroadcast) {
return _shouldDisplayRegionBroadcastMessage(context, message);
}
if (message.type == 4) {
return _isCurrentRoomFloatingMessage(context, message);
}
if (message.type == 6) {
return _isCurrentRoomFloatingMessage(context, message);
}
if (SCGlobalConfig.isFloatingBroadcastRoomOnly) {
return _isCurrentRoomFloatingMessage(context, message);
}
if (message.type != 0 && message.type != 1 && message.type != 5) {
return true;
}
return _isCurrentRoomFloatingMessage(context, message);
}
bool _isCurrentRoomFloatingMessage(
BuildContext context,
SCFloatingMessage message,
) {
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
);
if (!rtcProvider.shouldShowRoomVisualEffects ||
!rtcProvider.isVoiceRoomRouteVisible) {
return false;
}
final currentRoomId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
final currentRoomGroupId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount?.trim() ??
"";
final messageRoomId = (message.roomId ?? "").trim();
if (currentRoomId.isEmpty || messageRoomId.isEmpty) {
return false;
}
return currentRoomId == messageRoomId ||
(currentRoomGroupId.isNotEmpty && currentRoomGroupId == messageRoomId);
}
bool _shouldDisplayRegionBroadcastMessage(
BuildContext context,
SCFloatingMessage message,
) {
if (SCGlobalConfig.isFloatingBroadcastClosed) {
return false;
}
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
context,
listen: false,
);
final currentRoomId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
final messageRoomId = (message.roomId ?? "").trim();
final isCurrentVisibleRoom =
rtcProvider.shouldShowRoomVisualEffects &&
rtcProvider.isVoiceRoomRouteVisible &&
currentRoomId.isNotEmpty &&
messageRoomId.isNotEmpty &&
currentRoomId == messageRoomId;
final isGlobalStackedBroadcast =
message.type == 4 ||
message.type == 3 ||
message.type == 6 ||
_isRegionCpGiftMessage(message);
final isGlobalWinBroadcast =
message.type == 2 ||
message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType;
if (isCurrentVisibleRoom) {
// 区域红包/火箭/CP 类飘窗在当前房间仍然需要展示;普通区域礼物当前房间由 RTM 层跳过,避免和房间 IM 礼物飘屏重复。
return isGlobalStackedBroadcast || isGlobalWinBroadcast;
}
final isCurrentRoomMessage =
currentRoomId.isNotEmpty &&
messageRoomId.isNotEmpty &&
currentRoomId == messageRoomId;
if ((isGlobalStackedBroadcast || isGlobalWinBroadcast) &&
isCurrentRoomMessage) {
return false;
}
if (isGlobalWinBroadcast) {
if (SCGlobalConfig.isFloatingBroadcastRoomOnly) {
return false;
}
if (rtcProvider.shouldShowRoomVisualEffects) {
return true;
}
return SCGlobalConfig.isFloatingBroadcastAll && _homeRootTabsVisible;
}
if (isGlobalStackedBroadcast && rtcProvider.shouldShowRoomVisualEffects) {
return true;
}
if (isGlobalStackedBroadcast) {
return _homeRootTabsVisible;
}
if (SCGlobalConfig.isFloatingBroadcastRoomOnly) {
return false;
}
if (rtcProvider.shouldShowRoomVisualEffects) {
return false;
}
return SCGlobalConfig.isFloatingBroadcastAll && _homeRootTabsVisible;
}
void _removeRegionBroadcastMessages() {
final newQueue = SCPriorityQueue<SCFloatingMessage>(
(a, b) => b.priority.compareTo(a.priority),
);
while (_messageQueue.isNotEmpty) {
final message = _messageQueue.removeFirst();
if (!message.isRegionBroadcast ||
_isCrossRoomRegionFloatingMessage(message)) {
newQueue.add(message);
}
}
while (newQueue.isNotEmpty) {
_messageQueue.add(newQueue.removeFirst());
}
}
void _removeActiveRegionBroadcastMessage() {
final activeMessage = _currentMessage;
if (activeMessage != null && activeMessage.isRegionBroadcast) {
_currentOverlayEntry?.remove();
_currentOverlayEntry = null;
_currentMessage = null;
_isPlaying = false;
}
_removeStackedMessagesWhere((message) => message.isRegionBroadcast);
_safeScheduleNext();
}
void _removeActiveRoomMessage({
String? roomId,
bool includeRedPacket = false,
}) {
final activeMessage = _currentMessage;
final normalizedRoomId = roomId?.trim() ?? '';
if (activeMessage != null &&
(activeMessage.type == 0 ||
activeMessage.type == 1 ||
activeMessage.type == 2 ||
activeMessage.type ==
FloatingLuckyGiftGlobalScreenWidget.floatingType ||
activeMessage.type == 3 ||
activeMessage.type == 5 ||
(includeRedPacket && activeMessage.type == 4)) &&
(normalizedRoomId.isEmpty ||
(activeMessage.roomId ?? "").trim() == normalizedRoomId)) {
_removeActiveMessage(scheduleNext: false);
}
_removeStackedRoomMessages(
roomId: normalizedRoomId,
includeRedPacket: includeRedPacket,
);
_safeScheduleNext();
}
void _removeActiveMessage({bool scheduleNext = true}) {
_currentOverlayEntry?.remove();
_currentOverlayEntry = null;
_currentMessage = null;
_isPlaying = false;
if (scheduleNext) {
_safeScheduleNext();
}
}
void _removeHiddenStackedMessages(BuildContext context) {
_removeStackedMessagesWhere(
(message) => !_shouldDisplayMessage(context, message),
);
}
void _removeStackedRoomMessages({
required String roomId,
required bool includeRedPacket,
}) {
_removeStackedMessagesWhere((message) {
final type = message.type;
final isRoomFloatingType =
type == 0 ||
type == 1 ||
type == 2 ||
type == FloatingLuckyGiftGlobalScreenWidget.floatingType ||
type == 3 ||
type == 5 ||
(includeRedPacket && type == 4);
if (!isRoomFloatingType) {
return false;
}
return roomId.isEmpty || (message.roomId ?? "").trim() == roomId;
});
}
void _removeStackedMessagesWhere(
bool Function(SCFloatingMessage message) test,
) {
final matchedSlotKeys = <int>[];
_stackedMessages.forEach((slotKey, message) {
if (test(message)) {
matchedSlotKeys.add(slotKey);
}
});
if (matchedSlotKeys.isEmpty) {
return;
}
for (final slotKey in matchedSlotKeys) {
_removeStackedMessage(slotKey);
}
_markStackedEntriesNeedsBuild();
}
}