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 _maxLowPerformanceCompensationQueueLength = 8; static const int _maxDeferredRegionRedPacketCount = 12; static const Duration _compensationDedupWindow = Duration(seconds: 3); 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 _stackedFloatingOrder = [6, 5, 1, 4, 3]; static const double _stackedFloatingBaseTop = 70; static const double _stackedFloatingGap = 8; final SCPriorityQueue _messageQueue = SCPriorityQueue( (a, b) => b.priority.compareTo(a.priority), ); final List _deferredRegionRedPacketMessages = []; final Map _recentCompensationKeys = {}; final Map _recentFloatingKeys = {}; final Map _stackedOverlayEntries = {}; final Map _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 addLowPerformanceCompensationMessage( SCFloatingMessage message, { String? dedupKey, }) { if (_isDisposed || _isSuppressed || !SCGlobalConfig.isLowPerformanceDevice || SCGlobalConfig.isFloatingBroadcastClosed) { return; } final key = dedupKey ?? _compensationKeyFor(message); if (_isRecentCompensationDuplicate(key)) { return; } _rememberCompensationKey(key); _trimLowPerformanceCompensationQueue(); _enqueueMessage( message, debugLabel: 'low_performance_compensation_${message.type ?? -1}', ); } void _enqueueMessage( SCFloatingMessage message, { required String debugLabel, }) { unawaited(_warmMessageImages(message)); SCRoomEffectScheduler().scheduleDeferredEffect( debugLabel: debugLabel, action: () { if (_isDisposed || _isSuppressed) { return; } _messageQueue.add(message); _safeScheduleNext(); }, ); } bool _isRecentCompensationDuplicate(String key) { _pruneRecentCompensationKeys(); final recentTime = _recentCompensationKeys[key]; if (recentTime == null) { return false; } return DateTime.now().difference(recentTime) < _compensationDedupWindow; } void _rememberCompensationKey(String key) { _recentCompensationKeys[key] = DateTime.now(); _pruneRecentCompensationKeys(); } void _pruneRecentCompensationKeys() { final now = DateTime.now(); _recentCompensationKeys.removeWhere( (_, time) => now.difference(time) > _compensationDedupWindow, ); } 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; } String _compensationKeyFor(SCFloatingMessage message) { return [ message.type, message.roomId, message.userId, message.toUserId, message.giftId, message.number, message.giftUrl, ].join('|'); } void _trimLowPerformanceCompensationQueue() { if (_messageQueue.length < _maxLowPerformanceCompensationQueueLength) { return; } final retained = _messageQueue.unorderedElements.cast().toList() ..sort((a, b) => b.priority.compareTo(a.priority)); _messageQueue.clear(); for (final message in retained.take( _maxLowPerformanceCompensationQueueLength - 1, )) { _messageQueue.add(message); } } Future _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 _warmLuckyGiftBannerSvga() async { try { await SCSvgaAssetWidget.preloadAsset( FloatingLuckGiftScreenWidget.backgroundSvgaAssetPath, ); } catch (error) { // Preload failure is non-fatal; the banner will retry during rendering. } } Future _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? 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.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; } final defaultEffectsEnabled = !SCGlobalConfig.isLowPerformanceDevice; 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(); _recentCompensationKeys.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( (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.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( 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( 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( (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 = []; _stackedMessages.forEach((slotKey, message) { if (test(message)) { matchedSlotKeys.add(slotKey); } }); if (matchedSlotKeys.isEmpty) { return; } for (final slotKey in matchedSlotKeys) { _removeStackedMessage(slotKey); } _markStackedEntriesNeedsBuild(); } }