294 lines
8.7 KiB
Dart
294 lines
8.7 KiB
Dart
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_entrance_vap_svga_manager.dart';
|
|
import 'package:yumi/services/audio/rtc_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_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/shared/data_sources/models/message/sc_floating_message.dart';
|
|
|
|
typedef FloatingScreenManager = OverlayManager;
|
|
|
|
class OverlayManager {
|
|
final SCPriorityQueue<SCFloatingMessage> _messageQueue = SCPriorityQueue(
|
|
(a, b) => b.priority.compareTo(a.priority),
|
|
);
|
|
bool _isPlaying = false;
|
|
OverlayEntry? _currentOverlayEntry;
|
|
SCFloatingMessage? _currentMessage;
|
|
|
|
bool _isProcessing = false;
|
|
bool _isDisposed = false;
|
|
|
|
static final OverlayManager _instance = OverlayManager._internal();
|
|
|
|
factory OverlayManager() => _instance;
|
|
|
|
OverlayManager._internal();
|
|
|
|
void addMessage(SCFloatingMessage message) {
|
|
if (_isDisposed) return;
|
|
if (SCGlobalConfig.isFloatingAnimationInGlobal) {
|
|
if (_tryAggregateMessage(message)) {
|
|
return;
|
|
}
|
|
_messageQueue.add(message);
|
|
_safeScheduleNext();
|
|
} else {
|
|
_messageQueue.clear();
|
|
_isPlaying = false;
|
|
_isProcessing = false;
|
|
_isDisposed = false;
|
|
}
|
|
}
|
|
|
|
bool _tryAggregateMessage(SCFloatingMessage incoming) {
|
|
if (!_supportsAggregation(incoming)) {
|
|
return false;
|
|
}
|
|
if (_currentMessage != null &&
|
|
_isSameAggregatedFloatingMessage(_currentMessage!, incoming)) {
|
|
_mergeFloatingMessage(_currentMessage!, incoming);
|
|
_currentOverlayEntry?.markNeedsBuild();
|
|
return true;
|
|
}
|
|
for (final queuedMessage in _messageQueue.unorderedElements) {
|
|
if (_isSameAggregatedFloatingMessage(queuedMessage, incoming)) {
|
|
_mergeFloatingMessage(queuedMessage, incoming);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _supportsAggregation(SCFloatingMessage message) {
|
|
return message.type == 0;
|
|
}
|
|
|
|
bool _isSameAggregatedFloatingMessage(
|
|
SCFloatingMessage existing,
|
|
SCFloatingMessage incoming,
|
|
) {
|
|
return existing.type == incoming.type &&
|
|
existing.roomId == incoming.roomId &&
|
|
existing.userId == incoming.userId &&
|
|
existing.toUserId == incoming.toUserId &&
|
|
existing.giftUrl == incoming.giftUrl;
|
|
}
|
|
|
|
void _mergeFloatingMessage(
|
|
SCFloatingMessage target,
|
|
SCFloatingMessage incoming,
|
|
) {
|
|
target.coins = (target.coins ?? 0) + (incoming.coins ?? 0);
|
|
target.number = (target.number ?? 0) + (incoming.number ?? 0);
|
|
final currentMultiple = target.multiple ?? 0;
|
|
final incomingMultiple = incoming.multiple ?? 0;
|
|
target.multiple =
|
|
currentMultiple >= incomingMultiple
|
|
? currentMultiple
|
|
: incomingMultiple;
|
|
if ((target.userAvatarUrl ?? '').isEmpty) {
|
|
target.userAvatarUrl = incoming.userAvatarUrl;
|
|
}
|
|
if ((target.userName ?? '').isEmpty) {
|
|
target.userName = incoming.userName;
|
|
}
|
|
if ((target.toUserName ?? '').isEmpty) {
|
|
target.toUserName = incoming.toUserName;
|
|
}
|
|
if ((target.giftUrl ?? '').isEmpty) {
|
|
target.giftUrl = incoming.giftUrl;
|
|
}
|
|
if ((target.roomId ?? '').isEmpty) {
|
|
target.roomId = incoming.roomId;
|
|
}
|
|
target.priority =
|
|
target.priority >= incoming.priority
|
|
? target.priority
|
|
: incoming.priority;
|
|
target.aggregateVersion += 1;
|
|
}
|
|
|
|
void _safeScheduleNext() {
|
|
if (_isProcessing || _isPlaying || _messageQueue.isEmpty) return;
|
|
_isProcessing = true;
|
|
|
|
try {
|
|
_scheduleNext();
|
|
} finally {
|
|
_isProcessing = false;
|
|
}
|
|
}
|
|
|
|
void _scheduleNext() {
|
|
if (_isPlaying || _messageQueue.isEmpty || _isDisposed) return;
|
|
|
|
try {
|
|
final context = navigatorKey.currentState?.context;
|
|
if (context == null || !context.mounted) return;
|
|
|
|
// 安全地获取第一个消息
|
|
if (_messageQueue.isEmpty) return;
|
|
final messageToProcess = _messageQueue.first;
|
|
|
|
if (messageToProcess?.type == 1) {
|
|
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
|
context,
|
|
listen: false,
|
|
);
|
|
if (rtcProvider.currenRoom == null) {
|
|
// 从队列中移除第一个元素
|
|
_messageQueue.removeFirst();
|
|
_safeScheduleNext();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 安全地移除并播放消息
|
|
final messageToPlay = _messageQueue.removeFirst();
|
|
_playMessage(messageToPlay);
|
|
} catch (e) {
|
|
debugPrint('播放悬浮消息出错: $e');
|
|
_isPlaying = false;
|
|
_safeScheduleNext();
|
|
}
|
|
}
|
|
|
|
void _playMessage(SCFloatingMessage message) {
|
|
_isPlaying = true;
|
|
_currentMessage = message;
|
|
final context = navigatorKey.currentState?.context;
|
|
if (context == null || !context.mounted) {
|
|
_isPlaying = false;
|
|
_currentMessage = null;
|
|
_safeScheduleNext();
|
|
return;
|
|
}
|
|
|
|
_currentOverlayEntry = OverlayEntry(
|
|
builder:
|
|
(_) => Align(
|
|
alignment: AlignmentDirectional.topStart,
|
|
child: Transform.translate(
|
|
offset: Offset(0, 70.w),
|
|
child: _buildScreenWidget(message),
|
|
),
|
|
),
|
|
);
|
|
|
|
Overlay.of(context).insert(_currentOverlayEntry!);
|
|
}
|
|
|
|
Widget _buildScreenWidget(SCFloatingMessage message) {
|
|
bool completed = false;
|
|
|
|
void onComplete() {
|
|
if (completed) return;
|
|
completed = true;
|
|
|
|
try {
|
|
_currentOverlayEntry?.remove();
|
|
_currentOverlayEntry = null;
|
|
_currentMessage = null;
|
|
_isPlaying = false;
|
|
_safeScheduleNext();
|
|
} catch (e) {
|
|
debugPrint('清理悬浮消息出错: $e');
|
|
_currentMessage = null;
|
|
_isPlaying = false;
|
|
_safeScheduleNext();
|
|
}
|
|
}
|
|
|
|
switch (message.type) {
|
|
case 0:
|
|
return FloatingLuckGiftScreenWidget(
|
|
key: ValueKey<String>(
|
|
'luck_${message.userId}_${message.toUserId}_${message.giftUrl}_${message.aggregateVersion}',
|
|
),
|
|
message: message,
|
|
onAnimationCompleted: onComplete,
|
|
);
|
|
case 1:
|
|
//房间礼物
|
|
return FloatingGiftScreenWidget(
|
|
message: message,
|
|
onAnimationCompleted: onComplete,
|
|
);
|
|
case 2:
|
|
//游戏中奖
|
|
return FloatingGameScreenWidget(
|
|
message: message,
|
|
onAnimationCompleted: onComplete,
|
|
);
|
|
case 3:
|
|
//火箭
|
|
return FloatingRoomRocketScreenWidget(
|
|
message: message,
|
|
onAnimationCompleted: onComplete,
|
|
);
|
|
case 4:
|
|
//红包
|
|
return FloatingRoomRedenvelopeScreenWidget(
|
|
message: message,
|
|
onAnimationCompleted: onComplete,
|
|
);
|
|
default:
|
|
onComplete();
|
|
return Container();
|
|
}
|
|
}
|
|
|
|
void activate() {
|
|
_isDisposed = false;
|
|
}
|
|
|
|
void dispose() {
|
|
_isDisposed = true;
|
|
_currentOverlayEntry?.remove();
|
|
_currentOverlayEntry = null;
|
|
_currentMessage = null;
|
|
_messageQueue.clear();
|
|
_isPlaying = false;
|
|
_isProcessing = false;
|
|
}
|
|
|
|
void removeRoom() {
|
|
// 正确地从 PriorityQueue 中移除特定类型的消息
|
|
_removeMessagesByType(1);
|
|
}
|
|
|
|
// 辅助方法:移除特定类型的消息
|
|
void _removeMessagesByType(int type) {
|
|
// 由于 PriorityQueue 没有直接的方法来移除特定元素,
|
|
// 我们需要重建队列,排除指定类型的消息
|
|
final newQueue = SCPriorityQueue<SCFloatingMessage>(
|
|
(a, b) => b.priority.compareTo(a.priority),
|
|
);
|
|
|
|
// 遍历原始队列,只添加非指定类型的消息
|
|
while (_messageQueue.isNotEmpty) {
|
|
final message = _messageQueue.removeFirst();
|
|
if (message.type != type) {
|
|
newQueue.add(message);
|
|
}
|
|
}
|
|
|
|
// 将新队列赋值给原队列
|
|
while (newQueue.isNotEmpty) {
|
|
_messageQueue.add(newQueue.removeFirst());
|
|
}
|
|
}
|
|
|
|
// 辅助方法:获取队列信息
|
|
int get queueLength => _messageQueue.length;
|
|
|
|
bool get isPlaying => _isPlaying;
|
|
}
|