火箭进房兜底+im发送时机提前

This commit is contained in:
roxy 2026-05-16 01:38:20 +08:00
parent 06d4b62190
commit d26cfa20c3
2 changed files with 358 additions and 7 deletions

View File

@ -500,6 +500,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
void setVoiceRoomRouteVisible(bool visible) {
_voiceRoomRouteVisible = visible;
if (visible) {
final roomId = (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
if (roomId.isNotEmpty) {
rtmProvider?.handleVoiceRoomReadyForRocketRewards(roomId);
}
}
}
void _setRoomStartupLoading() {
@ -2694,6 +2700,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
roomId: roomId,
);
_loadRoomSecondaryData();
rtmProvider?.handleVoiceRoomReadyForRocketRewards(roomId);
} catch (error, stackTrace) {
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
return;

View File

@ -106,6 +106,18 @@ class _LuckGiftPushQueueEntry {
}
}
class _PendingRoomRocketRewardPopup {
const _PendingRoomRocketRewardPopup({
required this.roomId,
required this.receivedAt,
this.initialRoomRecords = const <SCRoomRocketRewardRecordRes>[],
});
final String roomId;
final int receivedAt;
final List<SCRoomRocketRewardRecordRes> initialRoomRecords;
}
class RealTimeMessagingManager extends ChangeNotifier {
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
@ -121,12 +133,20 @@ class RealTimeMessagingManager extends ChangeNotifier {
static const int _roomRocketLaunchRecentTtlMs = 90000;
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
static const int _roomRocketRegionBroadcastRecentTtlMs = 90000;
static const int _roomRocketRewardPendingTtlMs = 120000;
static const int _roomRocketRewardProbeTtlMs = 120000;
static const Duration _createRoomGroupTimeout = Duration(seconds: 12);
static const List<Duration> _roomRocketRewardPopupRetryDelays = [
Duration(milliseconds: 700),
Duration(seconds: 2),
Duration(seconds: 5),
];
static const List<Duration> _roomRocketRoomReadyRewardRetryDelays = [
Duration(milliseconds: 300),
Duration(milliseconds: 900),
Duration(seconds: 2),
Duration(seconds: 4),
];
BuildContext? context;
@ -239,7 +259,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
final Map<String, int> _recentRoomRocketLaunchLooseTimes = <String, int>{};
final Map<String, int> _recentRoomRocketRegionBroadcastTimes =
<String, int>{};
final _pendingRoomRocketRewardPopups =
<String, _PendingRoomRocketRewardPopup>{};
final Map<String, int> _recentRoomRocketRewardProbeTimes = <String, int>{};
Timer? _roomRocketRewardPopupRetryTimer;
Timer? _roomRocketRoomReadyRewardRetryTimer;
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
String? roomRedPacketBroadcastGroupId;
String? roomRedPacketBroadcastRegionCode;
@ -1941,6 +1965,30 @@ class RealTimeMessagingManager extends ChangeNotifier {
currentContext,
listen: false,
);
final isCurrentVisibleRoom =
rtcProvider.shouldShowRoomVisualEffects &&
rtcProvider.isVoiceRoomRouteVisible;
final launch = _buildRoomRocketLaunchFromStatusPayload(
rtcProvider: rtcProvider,
payload: data,
roomId: roomId,
);
if (_shouldHandleRoomRocketLaunchFromStatusPayload(
rtcProvider: rtcProvider,
payload: data,
launch: launch,
)) {
if (isCurrentVisibleRoom) {
_handleCurrentVisibleRoomRocketLaunch(
rtcProvider: rtcProvider,
launch: launch,
publishRegionBroadcast: true,
);
} else {
_markRoomRocketRewardProbe(launch.roomId);
_publishRoomRocketRegionBroadcastIfNeeded(launch);
}
}
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
}
@ -1971,9 +2019,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
rtcProvider.shouldShowRoomVisualEffects &&
rtcProvider.isVoiceRoomRouteVisible;
if (isCurrentVisibleRoom) {
if (!isRegionBroadcast) {
_publishRoomRocketRegionBroadcastIfNeeded(launch);
}
_handleCurrentVisibleRoomRocketLaunch(
rtcProvider: rtcProvider,
launch: launch,
@ -1982,8 +2027,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
}
if (!isCurrentVisibleRoom) {
_markRoomRocketRewardProbe(launch.roomId);
}
if (isCurrentRoom && !isCurrentVisibleRoom && !isRegionBroadcast) {
_publishRoomRocketRegionBroadcastIfNeeded(launch);
return;
}
OverlayManager().addMessage(
@ -1999,18 +2046,34 @@ class RealTimeMessagingManager extends ChangeNotifier {
final roomId = _payloadRoomId(data);
final currentRoomId = _currentVoiceRoomId();
final resolvedRoomId = roomId.isEmpty ? currentRoomId : roomId;
if (resolvedRoomId.isEmpty ||
(currentRoomId.isNotEmpty && resolvedRoomId != currentRoomId)) {
if (resolvedRoomId.isEmpty) {
return;
}
final initialRoomRecords = _roomRocketRewardRecordsFromPayload(data);
if (currentRoomId.isNotEmpty && resolvedRoomId != currentRoomId) {
_cachePendingRoomRocketRewardPopup(
resolvedRoomId,
initialRoomRecords: initialRoomRecords,
);
return;
}
if (!_isCurrentVoiceRoom(resolvedRoomId)) {
_cachePendingRoomRocketRewardPopup(
resolvedRoomId,
initialRoomRecords: initialRoomRecords,
);
return;
}
if (!_isCurrentVisibleVoiceRoom(resolvedRoomId)) {
_cachePendingRoomRocketRewardPopup(
resolvedRoomId,
initialRoomRecords: initialRoomRecords,
);
return;
}
final initialRoomRecords = _roomRocketRewardRecordsFromPayload(data);
_pendingRoomRocketRewardPopups.remove(resolvedRoomId);
_recentRoomRocketRewardProbeTimes.remove(resolvedRoomId);
unawaited(
_showRoomRocketRewardPopupDialog(
resolvedRoomId,
@ -2019,9 +2082,110 @@ class RealTimeMessagingManager extends ChangeNotifier {
);
}
void handleVoiceRoomReadyForRocketRewards(String roomId, {int attempt = 0}) {
final normalizedRoomId = roomId.trim();
if (normalizedRoomId.isEmpty) {
return;
}
final now = DateTime.now().millisecondsSinceEpoch;
_pruneRoomRocketRewardFallbacks(now);
final pending = _pendingRoomRocketRewardPopups[normalizedRoomId];
final hasProbe =
_recentRoomRocketRewardProbeTimes[normalizedRoomId] != null;
if (pending == null && !hasProbe) {
return;
}
if (!_isCurrentVisibleVoiceRoom(normalizedRoomId)) {
_scheduleRoomRocketReadyRewardRetry(normalizedRoomId, attempt);
return;
}
if (pending == null && attempt == 0) {
_scheduleRoomRocketReadyRewardRetry(normalizedRoomId, attempt);
return;
}
_pendingRoomRocketRewardPopups.remove(normalizedRoomId);
_recentRoomRocketRewardProbeTimes.remove(normalizedRoomId);
_roomRocketRoomReadyRewardRetryTimer?.cancel();
_roomRocketRoomReadyRewardRetryTimer = null;
unawaited(
_showRoomRocketRewardPopupDialog(
normalizedRoomId,
initialRoomRecords:
pending?.initialRoomRecords ??
const <SCRoomRocketRewardRecordRes>[],
),
);
}
void _scheduleRoomRocketReadyRewardRetry(String roomId, int attempt) {
if (attempt >= _roomRocketRoomReadyRewardRetryDelays.length) {
return;
}
_roomRocketRoomReadyRewardRetryTimer?.cancel();
_roomRocketRoomReadyRewardRetryTimer = Timer(
_roomRocketRoomReadyRewardRetryDelays[attempt],
() {
_roomRocketRoomReadyRewardRetryTimer = null;
handleVoiceRoomReadyForRocketRewards(roomId, attempt: attempt + 1);
},
);
}
void _cachePendingRoomRocketRewardPopup(
String roomId, {
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
const <SCRoomRocketRewardRecordRes>[],
}) {
final normalizedRoomId = roomId.trim();
if (normalizedRoomId.isEmpty) {
return;
}
final now = DateTime.now().millisecondsSinceEpoch;
_pruneRoomRocketRewardFallbacks(now);
final existing = _pendingRoomRocketRewardPopups[normalizedRoomId];
final records =
initialRoomRecords.isNotEmpty
? List<SCRoomRocketRewardRecordRes>.unmodifiable(initialRoomRecords)
: existing?.initialRoomRecords ??
const <SCRoomRocketRewardRecordRes>[];
_pendingRoomRocketRewardPopups[normalizedRoomId] =
_PendingRoomRocketRewardPopup(
roomId: normalizedRoomId,
receivedAt: now,
initialRoomRecords: records,
);
}
void _markRoomRocketRewardProbe(String roomId) {
final normalizedRoomId = roomId.trim();
if (normalizedRoomId.isEmpty) {
return;
}
final now = DateTime.now().millisecondsSinceEpoch;
_pruneRoomRocketRewardFallbacks(now);
_recentRoomRocketRewardProbeTimes[normalizedRoomId] = now;
}
void _pruneRoomRocketRewardFallbacks(int now) {
_pendingRoomRocketRewardPopups.removeWhere(
(_, pending) => now - pending.receivedAt > _roomRocketRewardPendingTtlMs,
);
_recentRoomRocketRewardProbeTimes.removeWhere(
(_, timestamp) => now - timestamp > _roomRocketRewardProbeTtlMs,
);
}
void _clearRoomRocketRewardFallbackState() {
_pendingRoomRocketRewardPopups.clear();
_recentRoomRocketRewardProbeTimes.clear();
_roomRocketRoomReadyRewardRetryTimer?.cancel();
_roomRocketRoomReadyRewardRetryTimer = null;
}
void _handleCurrentVisibleRoomRocketLaunch({
required RealTimeCommunicationManager rtcProvider,
required RoomRocketLaunchBroadcastMessage launch,
bool publishRegionBroadcast = false,
}) {
if (!_isCurrentVisibleVoiceRoom(launch.roomId)) {
return;
@ -2049,6 +2213,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
launch,
),
);
if (publishRegionBroadcast) {
_publishRoomRocketRegionBroadcastIfNeeded(launch);
}
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
}
unawaited(_showRoomRocketRewardPopupDialog(launch.roomId));
@ -2318,6 +2485,147 @@ class RealTimeMessagingManager extends ChangeNotifier {
return ['rocket_launch_loose', launch.roomId, launch.safeLevel].join('|');
}
bool _shouldHandleRoomRocketLaunchFromStatusPayload({
required RealTimeCommunicationManager rtcProvider,
required Map<String, dynamic> payload,
required RoomRocketLaunchBroadcastMessage launch,
}) {
final currentPercent = _roomRocketPayloadPercent(payload);
if (currentPercent < 99.5) {
return false;
}
final previousPercent = _roomRocketStatusPercent(
rtcProvider.roomRocketStatus,
);
if (previousPercent < 99.5) {
return true;
}
final launchKey = _roomRocketLaunchNoticeKey(launch);
if (launchKey.isEmpty) {
return false;
}
_pruneRecentRoomRocketLaunches(DateTime.now().millisecondsSinceEpoch);
final looseLaunchKey = _roomRocketLaunchLooseKey(launch);
return !_recentRoomRocketLaunchTimes.containsKey(launchKey) &&
(looseLaunchKey.isEmpty ||
!_recentRoomRocketLaunchLooseTimes.containsKey(looseLaunchKey));
}
RoomRocketLaunchBroadcastMessage _buildRoomRocketLaunchFromStatusPayload({
required RealTimeCommunicationManager rtcProvider,
required Map<String, dynamic> payload,
required String roomId,
}) {
final roomProfile = rtcProvider.currenRoom?.roomProfile?.roomProfile;
final level = _roomRocketPayloadLevel(
payload,
rtcProvider.roomRocketStatus?.currentLevel ??
rtcProvider.roomRocketStatus?.level?.toInt() ??
1,
);
return RoomRocketLaunchBroadcastMessage(
roomId: roomId,
roomAccount: _firstNonBlank([
roomProfile?.roomAccount,
_payloadText(payload['roomAccount'] ?? payload['room_account']),
]),
roomName: _firstNonBlank([
roomProfile?.roomName,
_payloadText(payload['roomName'] ?? payload['room_name']),
]),
roomCoverUrl: _firstNonBlank([
roomProfile?.roomCover,
_payloadAssetText(payload['roomCoverUrl'] ?? payload['room_cover_url']),
_payloadAssetText(payload['roomCover'] ?? payload['room_cover']),
_payloadAssetText(payload['roomAvatar'] ?? payload['room_avatar']),
_payloadAssetText(payload['roomIcon'] ?? payload['room_icon']),
_payloadAssetText(payload['coverUrl'] ?? payload['cover_url']),
_payloadAssetText(payload['cover']),
_payloadAssetText(_broadcastPayloadMap(payload['room'])['coverUrl']),
_payloadAssetText(_broadcastPayloadMap(payload['room'])['cover']),
_payloadAssetText(_broadcastPayloadMap(payload['room'])['avatar']),
_payloadAssetText(
_broadcastPayloadMap(payload['roomInfo'])['coverUrl'],
),
_payloadAssetText(_broadcastPayloadMap(payload['roomInfo'])['cover']),
_payloadAssetText(_broadcastPayloadMap(payload['roomInfo'])['avatar']),
]),
roundNo:
_payloadNum(payload['roundNo'] ?? payload['round_no'])?.toInt() ?? 0,
level: level,
rocketIconUrl: _firstNonBlank([
_rocketIconFromPayload(payload),
_rocketIconUrlForLevel(level),
]),
rocketAnimationUrl: _rocketAnimationFromPayload(payload),
durationSeconds: _roomRocketPayloadNoticeSeconds(payload),
triggerUser: const RoomRocketLaunchTriggerUser(
userId: '',
nickname: '',
avatar: '',
account: '',
countryCode: '',
countryName: '',
),
triggerUserId: _payloadText(
payload['triggerUserId'] ?? payload['trigger_user_id'],
),
launchNo: _payloadText(payload['launchNo'] ?? payload['launch_no']),
);
}
double _roomRocketStatusPercent(dynamic status) {
if (status == null) {
return 0;
}
final displayPercent = _payloadNum(status.displayPercent);
if (displayPercent != null) {
final raw = displayPercent.toDouble();
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
return percent.clamp(0, 100).toDouble();
}
final energyPercent = _payloadNum(status.energyPercent);
if (energyPercent != null) {
final raw = energyPercent.toDouble();
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
return percent.clamp(0, 100).toDouble();
}
final currentEnergy = _payloadNum(status.currentEnergy);
final maxEnergy = _payloadNum(status.maxEnergy ?? status.needEnergy);
if (currentEnergy == null || maxEnergy == null || maxEnergy <= 0) {
return 0;
}
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
}
double _roomRocketPayloadPercent(Map<String, dynamic> payload) {
final value = _payloadNum(
payload['displayPercent'] ??
payload['display_percent'] ??
payload['energyPercent'] ??
payload['energy_percent'] ??
payload['percent'],
);
if (value != null) {
final raw = value.toDouble();
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
return percent.clamp(0, 100).toDouble();
}
final currentEnergy = _payloadNum(
payload['currentEnergy'] ?? payload['current_energy'],
);
final maxEnergy = _payloadNum(
payload['maxEnergy'] ??
payload['max_energy'] ??
payload['needEnergy'] ??
payload['need_energy'],
);
if (currentEnergy == null || maxEnergy == null || maxEnergy <= 0) {
return 0;
}
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
}
int _roomRocketPayloadLevel(Map<String, dynamic> payload, int fallback) {
final value = _payloadNum(
payload['currentLevel'] ??
@ -2331,6 +2639,25 @@ class RealTimeMessagingManager extends ChangeNotifier {
return (value?.toInt() ?? fallback).clamp(1, 99).toInt();
}
int _roomRocketPayloadNoticeSeconds(Map<String, dynamic> payload) {
final seconds =
_payloadNum(
payload['countdownSeconds'] ??
payload['countdown_seconds'] ??
payload['launchDelaySeconds'] ??
payload['launch_delay_seconds'] ??
payload['delaySeconds'] ??
payload['delay_seconds'] ??
payload['durationSeconds'] ??
payload['duration_seconds'],
)?.toInt() ??
10;
if (seconds > 0 && seconds <= 60) {
return seconds;
}
return 10;
}
String _rocketIconFromPayload(Map<String, dynamic> payload) {
return _firstNonBlank([
_payloadAssetText(payload['rocketIconUrl']),
@ -2342,6 +2669,22 @@ class RealTimeMessagingManager extends ChangeNotifier {
]);
}
String _rocketAnimationFromPayload(Map<String, dynamic> payload) {
return _firstNonBlank([
_payloadAssetText(payload['rocketAnimationUrl']),
_payloadAssetText(payload['rocket_animation_url']),
_payloadAssetText(payload['rocketSvgaUrl']),
_payloadAssetText(payload['rocket_svga_url']),
_payloadAssetText(payload['animationUrl']),
_payloadAssetText(payload['animation_url']),
_payloadAssetText(payload['launchAnimationUrl']),
_payloadAssetText(payload['launch_animation_url']),
_payloadAssetText(payload['svgaUrl']),
_payloadAssetText(payload['sourceUrl']),
_payloadAssetText(payload['source_url']),
]);
}
String _payloadRoomId(Map<String, dynamic> payload) {
return _firstNonBlank([
_payloadText(payload['roomId']),
@ -2464,6 +2807,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
logout() async {
roomRedPacketBroadcastGroupId = null;
roomRedPacketBroadcastRegionCode = null;
_clearRoomRocketRewardFallbackState();
RoomRedPacketOpenDialog.clearClaimedState();
RoomRedPacketPendingCache.instance.clear();
V2TimCallback logoutRes = await TencentImSDKPlugin.v2TIMManager.logout();