From 024bcf4ffcb1ccf4e3f76d06bfab2f9d760a3362 Mon Sep 17 00:00:00 2001 From: roxy Date: Sat, 16 May 2026 07:18:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=82=E6=AD=A5=E6=89=A7?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/services/audio/rtc_manager.dart | 123 +++++++++++++----- .../models/res/sc_room_rocket_status_res.dart | 7 +- .../rocket/room_rocket_floating_entry.dart | 10 +- test/room_rocket_status_test.dart | 15 +++ 4 files changed, 120 insertions(+), 35 deletions(-) diff --git a/lib/services/audio/rtc_manager.dart b/lib/services/audio/rtc_manager.dart index 2e0c421..bdb9d4b 100644 --- a/lib/services/audio/rtc_manager.dart +++ b/lib/services/audio/rtc_manager.dart @@ -292,6 +292,20 @@ class _PendingRoomStartupSeatAction { final String? inviterId; } +class _PersistedVoiceRoomCleanupRequest { + const _PersistedVoiceRoomCleanupRequest({ + required this.staleRoomId, + required this.targetRoomId, + required this.restoreOnlineHeartbeatWhenIdle, + this.clearMarkerOnly = false, + }); + + final String staleRoomId; + final String targetRoomId; + final bool restoreOnlineHeartbeatWhenIdle; + final bool clearMarkerOnly; +} + class RealTimeCommunicationManager extends ChangeNotifier { static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog'; static const String _roomRocketPagLaunchDialogTag = @@ -1798,10 +1812,11 @@ class RealTimeCommunicationManager extends ChangeNotifier { if (!context.mounted) { return; } - await cleanupPersistedVoiceRoomSessionBeforeEntry(targetRoomId: roomId); - if (!context.mounted) { - return; - } + final persistedRoomCleanupRequest = + _preparePersistedVoiceRoomCleanupBeforeEntry( + targetRoomId: roomId, + restoreOnlineHeartbeatWhenIdle: false, + ); if (roomId == currenRoom?.roomProfile?.roomProfile?.id) { if (_currentRoomIsEntryPreview || _roomStartupStatus == RoomStartupStatus.loading) { @@ -1810,6 +1825,9 @@ class RealTimeCommunicationManager extends ChangeNotifier { } _startVoiceRoomForegroundService(); VoiceRoomRoute.openVoiceRoom(context); + _startPersistedVoiceRoomCleanupInBackground( + persistedRoomCleanupRequest, + ); return; } @@ -1848,6 +1866,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { _refreshRoomRedPacketListAfterEntry(roomId: roomId); _startVoiceRoomForegroundService(); VoiceRoomRoute.openVoiceRoom(context); + _startPersistedVoiceRoomCleanupInBackground(persistedRoomCleanupRequest); if (shouldRejoinRoomRtc) { unawaited(_rejoinCurrentRoomRtcForSameRoomReuse(roomId)); } @@ -1883,6 +1902,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { ); _startVoiceRoomForegroundService(); VoiceRoomRoute.openVoiceRoom(context); + _startPersistedVoiceRoomCleanupInBackground(persistedRoomCleanupRequest); notifyListeners(); final entryRequestSerial = ++_roomEntryRequestSerial; if (preEnteredRoom != null) { @@ -2505,14 +2525,70 @@ class RealTimeCommunicationManager extends ChangeNotifier { Future cleanupPersistedVoiceRoomSessionBeforeEntry({ String? targetRoomId, + bool restoreOnlineHeartbeatWhenIdle = true, }) { + final cleanupRequest = _preparePersistedVoiceRoomCleanupBeforeEntry( + targetRoomId: targetRoomId, + restoreOnlineHeartbeatWhenIdle: restoreOnlineHeartbeatWhenIdle, + ); + return _runPersistedVoiceRoomCleanup(cleanupRequest); + } + + _PersistedVoiceRoomCleanupRequest? + _preparePersistedVoiceRoomCleanupBeforeEntry({ + String? targetRoomId, + bool restoreOnlineHeartbeatWhenIdle = true, + }) { + final staleRoomId = DataPersistence.getLastTimeRoomId().trim(); + if (staleRoomId.isEmpty) { + return null; + } + + final currentRoomId = + (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(); + if (currentRoomId.isNotEmpty) { + return null; + } + + final currentUserId = + (AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim(); + if (currentUserId.isEmpty || AccountStorage().getToken().trim().isEmpty) { + return null; + } + + final normalizedTargetRoomId = (targetRoomId ?? "").trim(); + if (normalizedTargetRoomId.isNotEmpty && + normalizedTargetRoomId == staleRoomId) { + return null; + } + + final staleRoomUserId = DataPersistence.getLastTimeRoomUserId().trim(); + return _PersistedVoiceRoomCleanupRequest( + staleRoomId: staleRoomId, + targetRoomId: normalizedTargetRoomId, + restoreOnlineHeartbeatWhenIdle: restoreOnlineHeartbeatWhenIdle, + clearMarkerOnly: + staleRoomUserId.isNotEmpty && staleRoomUserId != currentUserId, + ); + } + + void _startPersistedVoiceRoomCleanupInBackground( + _PersistedVoiceRoomCleanupRequest? cleanupRequest, + ) { + unawaited(_runPersistedVoiceRoomCleanup(cleanupRequest)); + } + + Future _runPersistedVoiceRoomCleanup( + _PersistedVoiceRoomCleanupRequest? cleanupRequest, + ) { + if (cleanupRequest == null) { + return Future.value(); + } final runningTask = _persistedRoomCleanupTask; if (runningTask != null) { return runningTask; } - final task = _cleanupPersistedVoiceRoomSessionBeforeEntry( - targetRoomId: targetRoomId, - ); + final task = _cleanupPersistedVoiceRoomSessionBeforeEntry(cleanupRequest); _persistedRoomCleanupTask = task; return task.whenComplete(() { if (identical(_persistedRoomCleanupTask, task)) { @@ -2521,28 +2597,11 @@ class RealTimeCommunicationManager extends ChangeNotifier { }); } - Future _cleanupPersistedVoiceRoomSessionBeforeEntry({ - String? targetRoomId, - }) async { - final staleRoomId = DataPersistence.getLastTimeRoomId().trim(); - if (staleRoomId.isEmpty) { - return; - } - - final currentRoomId = - (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(); - if (currentRoomId.isNotEmpty) { - return; - } - - final currentUserId = - (AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim(); - if (currentUserId.isEmpty || AccountStorage().getToken().trim().isEmpty) { - return; - } - - final staleRoomUserId = DataPersistence.getLastTimeRoomUserId().trim(); - if (staleRoomUserId.isNotEmpty && staleRoomUserId != currentUserId) { + Future _cleanupPersistedVoiceRoomSessionBeforeEntry( + _PersistedVoiceRoomCleanupRequest cleanupRequest, + ) async { + final staleRoomId = cleanupRequest.staleRoomId; + if (cleanupRequest.clearMarkerOnly) { await _clearPersistedRoomMarkerIfMatches(staleRoomId); return; } @@ -2563,7 +2622,11 @@ class RealTimeCommunicationManager extends ChangeNotifier { ); if (didClean) { await _clearPersistedRoomMarkerIfMatches(staleRoomId); - if ((targetRoomId ?? "").trim() != staleRoomId) { + final currentRoomId = + (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim(); + if (cleanupRequest.restoreOnlineHeartbeatWhenIdle && + cleanupRequest.targetRoomId != staleRoomId && + currentRoomId.isEmpty) { await SCHeartbeatUtils.scheduleHeartbeat( SCHeartbeatStatus.ONLINE.name, false, diff --git a/lib/shared/business_logic/models/res/sc_room_rocket_status_res.dart b/lib/shared/business_logic/models/res/sc_room_rocket_status_res.dart index 1416745..a87ab2a 100644 --- a/lib/shared/business_logic/models/res/sc_room_rocket_status_res.dart +++ b/lib/shared/business_logic/models/res/sc_room_rocket_status_res.dart @@ -286,7 +286,12 @@ class SCRoomRocketStatusRes { bool? get enabled => _enabled; - bool get isEnabled => _enabled != false; + bool get isEnabled { + final normalizedStatus = (_status ?? '').trim().toUpperCase(); + return _configured != false && + _enabled != false && + normalizedStatus != 'DISABLED'; + } int? get roundNo => _roundNo; diff --git a/lib/ui_kit/widgets/room/rocket/room_rocket_floating_entry.dart b/lib/ui_kit/widgets/room/rocket/room_rocket_floating_entry.dart index eed0fd4..7381b96 100644 --- a/lib/ui_kit/widgets/room/rocket/room_rocket_floating_entry.dart +++ b/lib/ui_kit/widgets/room/rocket/room_rocket_floating_entry.dart @@ -38,15 +38,17 @@ class RoomRocketFloatingEntry extends StatelessWidget { return const SizedBox.shrink(); } final level = _currentEntryLevel(status); + if (level == null || level.rocketIconUrl.trim().isEmpty) { + return const SizedBox.shrink(); + } final percent = _entryPercent(status); final shouldShake = - (status.shake ?? false) || - (level != null && percent > level.shakeThresholdPercent); + (status.shake ?? false) || percent > level.shakeThresholdPercent; return SCDebounceWidget( onTap: () => _showRocketDialog(context, status), child: _RoomRocketEntryButton( - iconUrl: level?.rocketIconUrl ?? '', - progressUrl: level?.progressBarUrl ?? '', + iconUrl: level.rocketIconUrl, + progressUrl: level.progressBarUrl, progressPercent: percent, shake: shouldShake, loading: false, diff --git a/test/room_rocket_status_test.dart b/test/room_rocket_status_test.dart index 23b9b73..f3fd92d 100644 --- a/test/room_rocket_status_test.dart +++ b/test/room_rocket_status_test.dart @@ -138,6 +138,21 @@ void main() { expect(RoomRocketApiMapper.levelsFromStatus(status), isNull); }); + test('treats unconfigured or disabled-status rocket as unavailable', () { + final unconfigured = SCRoomRocketStatusRes.fromJson({ + 'configured': false, + 'enabled': true, + 'status': 'ENABLED', + }); + final disabledStatus = SCRoomRocketStatusRes.fromJson({ + 'configured': true, + 'status': 'DISABLED', + }); + + expect(unconfigured.isEnabled, isFalse); + expect(disabledStatus.isEnabled, isFalse); + }); + test( 'uses top-level reward preview for the response level instead of current level', () {