修改了一部分
This commit is contained in:
parent
ea296e461b
commit
51083ef57f
@ -66,15 +66,10 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
_initializePages();
|
||||
unawaited(_loadTaskClaimableCount());
|
||||
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).initializeRealTimeCommunicationManager(context);
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
rtcProvider.initializeRealTimeCommunicationManager(context);
|
||||
unawaited(
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).prewarmRtcEngine().catchError((error, stackTrace) {}),
|
||||
rtcProvider.prewarmRtcEngine().catchError((error, stackTrace) {}),
|
||||
);
|
||||
Provider.of<RtmProvider>(context, listen: false).init(context);
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
@ -89,9 +84,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
WakelockPlus.enable();
|
||||
_showEntryDialogs();
|
||||
});
|
||||
if (DataPersistence.getLastTimeRoomId().isNotEmpty) {
|
||||
unawaited(DataPersistence.setLastTimeRoomId(""));
|
||||
}
|
||||
unawaited(rtcProvider.cleanupPersistedVoiceRoomSessionBeforeEntry());
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -331,9 +331,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
Timer? _onlineUsersPollingTimer;
|
||||
Timer? _roomEntryEffectTimer;
|
||||
Timer? _roomRocketLaunchAnimationTimer;
|
||||
String? _lastRoomRocketLaunchNoticeKey;
|
||||
Timer? _roomRedPacketPresenceTimer;
|
||||
Future<void>? _rtcEnginePrewarmTask;
|
||||
Future<void>? _pendingRoomSwitchRtcLeaveTask;
|
||||
Future<void>? _persistedRoomCleanupTask;
|
||||
bool _isRefreshingMicList = false;
|
||||
bool _isRefreshingOnlineUsers = false;
|
||||
bool _disableMicListRefreshForCurrentSession = false;
|
||||
@ -1721,6 +1723,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
await cleanupPersistedVoiceRoomSessionBeforeEntry(targetRoomId: roomId);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
|
||||
if (_currentRoomIsEntryPreview ||
|
||||
_roomStartupStatus == RoomStartupStatus.loading) {
|
||||
@ -2372,12 +2378,83 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
await _retryExitNetworkRequest('quit stale entered room $staleRoomId', () {
|
||||
return SCAccountRepository().quitRoom(staleRoomId).then((didQuit) {
|
||||
return SCAccountRepository()
|
||||
.quitRoom(staleRoomId, silentErrorToast: true)
|
||||
.then((didQuit) {
|
||||
if (!didQuit) {
|
||||
throw StateError('quitRoom returned false');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> cleanupPersistedVoiceRoomSessionBeforeEntry({
|
||||
String? targetRoomId,
|
||||
}) {
|
||||
final runningTask = _persistedRoomCleanupTask;
|
||||
if (runningTask != null) {
|
||||
return runningTask;
|
||||
}
|
||||
final task = _cleanupPersistedVoiceRoomSessionBeforeEntry(
|
||||
targetRoomId: targetRoomId,
|
||||
);
|
||||
_persistedRoomCleanupTask = task;
|
||||
return task.whenComplete(() {
|
||||
if (identical(_persistedRoomCleanupTask, task)) {
|
||||
_persistedRoomCleanupTask = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _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) {
|
||||
await _clearPersistedRoomMarkerIfMatches(staleRoomId);
|
||||
return;
|
||||
}
|
||||
|
||||
var didClean = false;
|
||||
await _retryExitNetworkRequest(
|
||||
'quit persisted room $staleRoomId',
|
||||
() async {
|
||||
final didQuit = await SCAccountRepository().quitRoom(
|
||||
staleRoomId,
|
||||
silentErrorToast: true,
|
||||
);
|
||||
if (!didQuit) {
|
||||
throw StateError('quitRoom returned false');
|
||||
}
|
||||
});
|
||||
});
|
||||
didClean = true;
|
||||
},
|
||||
);
|
||||
if (didClean) {
|
||||
await _clearPersistedRoomMarkerIfMatches(staleRoomId);
|
||||
if ((targetRoomId ?? "").trim() != staleRoomId) {
|
||||
await SCHeartbeatUtils.scheduleHeartbeat(
|
||||
SCHeartbeatStatus.ONLINE.name,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///初始化房间相关
|
||||
@ -2694,8 +2771,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
|
||||
///更新房间火箭信息
|
||||
void updateRoomRocketConfigurationStatus(SCRoomRocketStatusRes res) {
|
||||
final previousStatus = roomRocketStatus;
|
||||
roomRocketStatus = res;
|
||||
notifyListeners();
|
||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||
}
|
||||
|
||||
///刷新房间火箭信息
|
||||
@ -2710,8 +2789,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
||||
return;
|
||||
}
|
||||
final previousStatus = roomRocketStatus;
|
||||
roomRocketStatus = res;
|
||||
notifyListeners();
|
||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||
}
|
||||
|
||||
void handleRoomRocketLaunchBroadcast(
|
||||
@ -2759,6 +2840,114 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
void _publishRoomRocketLaunchNoticeIfNeeded(
|
||||
SCRoomRocketStatusRes? previous,
|
||||
SCRoomRocketStatusRes current,
|
||||
) {
|
||||
if (!_shouldPublishRoomRocketLaunchNotice(previous, current)) {
|
||||
return;
|
||||
}
|
||||
final roomId =
|
||||
(current.roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||||
.trim();
|
||||
final groupId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "").trim();
|
||||
if (roomId.isEmpty || groupId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final level = _roomRocketNoticeLevel(current);
|
||||
final noticeKey = _roomRocketLaunchNoticeKey(
|
||||
roomId: roomId,
|
||||
level: level,
|
||||
roundNo: current.roundNo ?? 0,
|
||||
);
|
||||
if (_lastRoomRocketLaunchNoticeKey == noticeKey) {
|
||||
return;
|
||||
}
|
||||
_lastRoomRocketLaunchNoticeKey = noticeKey;
|
||||
rtmProvider?.addMsg(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: _roomRocketLaunchNoticeSeconds(current).toString(),
|
||||
type: SCRoomMsgType.rocketLaunchNotice,
|
||||
number: _roomRocketLaunchNoticeSeconds(current),
|
||||
rocketIconUrl: _rocketIconUrlForStatus(current, level),
|
||||
giftBatchId: noticeKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _shouldPublishRoomRocketLaunchNotice(
|
||||
SCRoomRocketStatusRes? previous,
|
||||
SCRoomRocketStatusRes current,
|
||||
) {
|
||||
if (previous == null) {
|
||||
return false;
|
||||
}
|
||||
final currentRoomId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (currentRoomId.isNotEmpty &&
|
||||
(current.roomId ?? currentRoomId).trim() != currentRoomId) {
|
||||
return false;
|
||||
}
|
||||
return _roomRocketStatusPercent(previous) < 99.5 &&
|
||||
_roomRocketStatusPercent(current) >= 99.5;
|
||||
}
|
||||
|
||||
double _roomRocketStatusPercent(SCRoomRocketStatusRes? status) {
|
||||
if (status == null) {
|
||||
return 0;
|
||||
}
|
||||
final value = status.displayPercent ?? status.energyPercent;
|
||||
if (value != null) {
|
||||
final raw = value.toDouble();
|
||||
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||
return percent.clamp(0, 100).toDouble();
|
||||
}
|
||||
final currentEnergy = status.currentEnergy ?? 0;
|
||||
final maxEnergy = status.maxEnergy ?? status.needEnergy ?? 0;
|
||||
if (maxEnergy <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
|
||||
}
|
||||
|
||||
int _roomRocketNoticeLevel(SCRoomRocketStatusRes status) {
|
||||
final level = status.currentLevel ?? status.level?.toInt() ?? 1;
|
||||
return level.clamp(1, 99).toInt();
|
||||
}
|
||||
|
||||
int _roomRocketLaunchNoticeSeconds(SCRoomRocketStatusRes status) {
|
||||
final seconds = status.resetRemainingSeconds ?? 0;
|
||||
if (seconds > 0 && seconds <= 60) {
|
||||
return seconds;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
String _rocketIconUrlForStatus(SCRoomRocketStatusRes status, int level) {
|
||||
for (final item in status.levels ?? const []) {
|
||||
if (item.level == level && item.rocketIconUrl.trim().isNotEmpty) {
|
||||
return item.rocketIconUrl;
|
||||
}
|
||||
}
|
||||
for (final item in roomRocketStatus?.levels ?? const []) {
|
||||
if (item.level == level && item.rocketIconUrl.trim().isNotEmpty) {
|
||||
return item.rocketIconUrl;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String _roomRocketLaunchNoticeKey({
|
||||
required String roomId,
|
||||
required int level,
|
||||
required int roundNo,
|
||||
}) {
|
||||
final roundKey = roundNo > 0 ? roundNo.toString() : 'full';
|
||||
return ['rocket_launch_notice', roomId, level, roundKey].join('|');
|
||||
}
|
||||
|
||||
void _playRoomRocketLaunchAnimation({
|
||||
required String animationUrl,
|
||||
required RoomRocketLaunchBroadcastMessage launch,
|
||||
@ -3066,7 +3255,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
exitRtmProvider?.cleanRoomData();
|
||||
} catch (e) {}
|
||||
|
||||
_clearData();
|
||||
_clearData(clearPersistedRoomMarker: false);
|
||||
if (!isLogout && navigationContext != null) {
|
||||
VoiceRoomRoute.popVoiceRoomToPrevious(navigationContext);
|
||||
}
|
||||
@ -3104,7 +3293,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
exitRtmProvider?.cleanRoomData();
|
||||
} catch (e) {}
|
||||
|
||||
_clearData();
|
||||
_clearData(clearPersistedRoomMarker: false);
|
||||
final roomRtcLeaveTask = _leaveRoomRtcForExit();
|
||||
_pendingRoomSwitchRtcLeaveTask = roomRtcLeaveTask;
|
||||
unawaited(
|
||||
@ -3169,16 +3358,34 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
if (_isCurrentRoomId(roomId)) {
|
||||
return;
|
||||
}
|
||||
final didQuit = await SCAccountRepository().quitRoom(roomId);
|
||||
final didQuit = await SCAccountRepository().quitRoom(
|
||||
roomId,
|
||||
silentErrorToast: true,
|
||||
);
|
||||
if (!didQuit) {
|
||||
throw StateError('quitRoom returned false');
|
||||
}
|
||||
await _clearPersistedRoomMarkerIfMatches(roomId);
|
||||
}),
|
||||
);
|
||||
}
|
||||
await Future.wait(tasks);
|
||||
}
|
||||
|
||||
Future<void> _clearPersistedRoomMarkerIfMatches(String roomId) async {
|
||||
final normalizedRoomId = roomId.trim();
|
||||
if (normalizedRoomId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (DataPersistence.getLastTimeRoomId().trim() != normalizedRoomId) {
|
||||
return;
|
||||
}
|
||||
await Future.wait([
|
||||
DataPersistence.setLastTimeRoomId(""),
|
||||
DataPersistence.setLastTimeRoomUserId(""),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> _retryExitNetworkRequest(
|
||||
String name,
|
||||
Future<void> Function() action,
|
||||
@ -3257,7 +3464,13 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
if (!isPreviewOnlyFailure && roomId.isNotEmpty) {
|
||||
await SCChatRoomRepository().roomRedPacketPresenceLeave(roomId);
|
||||
await SCAccountRepository().quitRoom(roomId);
|
||||
final didQuit = await SCAccountRepository().quitRoom(
|
||||
roomId,
|
||||
silentErrorToast: true,
|
||||
);
|
||||
if (didQuit) {
|
||||
await _clearPersistedRoomMarkerIfMatches(roomId);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
@ -3271,13 +3484,14 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
///清空列表数据
|
||||
void _clearData() {
|
||||
void _clearData({bool clearPersistedRoomMarker = true}) {
|
||||
_roomEntryRequestSerial += 1;
|
||||
_stopVoiceRoomForegroundService();
|
||||
_roomEntryEffectTimer?.cancel();
|
||||
_roomEntryEffectTimer = null;
|
||||
_roomRocketLaunchAnimationTimer?.cancel();
|
||||
_roomRocketLaunchAnimationTimer = null;
|
||||
_lastRoomRocketLaunchNoticeKey = null;
|
||||
_stopRoomRedPacketPresenceHeartbeat();
|
||||
_previewRoomSeatCount = null;
|
||||
_finishRoomStartupSeatLoading(executePendingAction: false);
|
||||
@ -3320,7 +3534,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_isExitingCurrentVoiceRoomSession = false;
|
||||
isMic = true;
|
||||
isMusicPlaying = false;
|
||||
DataPersistence.setLastTimeRoomId("");
|
||||
if (clearPersistedRoomMarker) {
|
||||
DataPersistence.setLastTimeRoomId("");
|
||||
DataPersistence.setLastTimeRoomUserId("");
|
||||
}
|
||||
SCGiftVapSvgaManager().stopPlayback();
|
||||
SCRoomUtils.closeAllDialogs();
|
||||
SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults();
|
||||
|
||||
@ -1862,6 +1862,36 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return value.toString().trim();
|
||||
}
|
||||
|
||||
String _payloadAssetText(dynamic value) {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
if (value is Map) {
|
||||
final data = _broadcastPayloadMap(value);
|
||||
return _firstNonBlank([
|
||||
_payloadAssetText(data['sourceUrl']),
|
||||
_payloadAssetText(data['url']),
|
||||
_payloadAssetText(data['resourceUrl']),
|
||||
_payloadAssetText(data['fileUrl']),
|
||||
_payloadAssetText(data['src']),
|
||||
_payloadAssetText(data['path']),
|
||||
_payloadAssetText(data['cover']),
|
||||
_payloadAssetText(data['imageUrl']),
|
||||
_payloadAssetText(data['svgUrl']),
|
||||
_payloadAssetText(data['svg']),
|
||||
_payloadAssetText(data['value']),
|
||||
]);
|
||||
}
|
||||
if (value is Iterable && value is! String) {
|
||||
return _firstNonBlank(value.map(_payloadAssetText));
|
||||
}
|
||||
final text = _payloadText(value);
|
||||
if (text == '{}' || text == '[]' || text.toLowerCase() == 'null') {
|
||||
return '';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
num? _payloadNum(dynamic value) {
|
||||
if (value is num) {
|
||||
return value;
|
||||
@ -1886,6 +1916,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
currentContext,
|
||||
listen: false,
|
||||
);
|
||||
_publishRoomRocketLaunchNoticeFromStatusPayload(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
roomId: roomId,
|
||||
);
|
||||
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
|
||||
}
|
||||
|
||||
@ -2047,15 +2082,155 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
launch.rocketIconUrl,
|
||||
_rocketIconUrlForLevel(launch.safeLevel),
|
||||
]),
|
||||
giftBatchId: [
|
||||
'rocket_launch_notice',
|
||||
launch.launchNo,
|
||||
launch.roomId,
|
||||
launch.safeLevel,
|
||||
].join('|'),
|
||||
giftBatchId: _roomRocketLaunchNoticeKey(launch),
|
||||
);
|
||||
}
|
||||
|
||||
String _roomRocketLaunchNoticeKey(RoomRocketLaunchBroadcastMessage launch) {
|
||||
return _roomRocketLaunchNoticeKeyForValues(
|
||||
roomId: launch.roomId,
|
||||
level: launch.safeLevel,
|
||||
roundNo: launch.roundNo,
|
||||
launchNo: launch.launchNo,
|
||||
);
|
||||
}
|
||||
|
||||
void _publishRoomRocketLaunchNoticeFromStatusPayload({
|
||||
required RealTimeCommunicationManager rtcProvider,
|
||||
required Map<String, dynamic> payload,
|
||||
required String roomId,
|
||||
}) {
|
||||
final previousPercent = _roomRocketStatusPercent(
|
||||
rtcProvider.roomRocketStatus,
|
||||
);
|
||||
final currentPercent = _roomRocketPayloadPercent(payload);
|
||||
if (previousPercent >= 99.5 || currentPercent < 99.5) {
|
||||
return;
|
||||
}
|
||||
final groupId =
|
||||
(rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? '')
|
||||
.trim();
|
||||
if (groupId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final level = _roomRocketPayloadLevel(
|
||||
payload,
|
||||
rtcProvider.roomRocketStatus?.currentLevel ??
|
||||
rtcProvider.roomRocketStatus?.level?.toInt() ??
|
||||
1,
|
||||
);
|
||||
addMsg(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: _roomRocketPayloadNoticeSeconds(payload).toString(),
|
||||
type: SCRoomMsgType.rocketLaunchNotice,
|
||||
number: _roomRocketPayloadNoticeSeconds(payload),
|
||||
rocketIconUrl: _firstNonBlank([
|
||||
_rocketIconFromPayload(payload),
|
||||
_rocketIconUrlForLevel(level),
|
||||
]),
|
||||
giftBatchId: _roomRocketLaunchNoticeKeyForValues(
|
||||
roomId: roomId,
|
||||
level: level,
|
||||
roundNo: _payloadNum(payload['roundNo'])?.toInt() ?? 0,
|
||||
launchNo: _payloadText(payload['launchNo']),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _roomRocketStatusPercent(dynamic status) {
|
||||
if (status == null) {
|
||||
return 0;
|
||||
}
|
||||
final value = status.displayPercent ?? status.energyPercent;
|
||||
if (value is num) {
|
||||
final raw = value.toDouble();
|
||||
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||
return percent.clamp(0, 100).toDouble();
|
||||
}
|
||||
final currentEnergy = status.currentEnergy;
|
||||
final maxEnergy = status.maxEnergy ?? status.needEnergy;
|
||||
if (currentEnergy is! num || maxEnergy is! num || maxEnergy <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((currentEnergy / maxEnergy) * 100).clamp(0, 100).toDouble();
|
||||
}
|
||||
|
||||
double _roomRocketPayloadPercent(Map<String, dynamic> payload) {
|
||||
final value = _payloadNum(
|
||||
payload['displayPercent'] ??
|
||||
payload['energyPercent'] ??
|
||||
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']);
|
||||
final maxEnergy = _payloadNum(
|
||||
payload['maxEnergy'] ?? payload['needEnergy'],
|
||||
);
|
||||
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'] ??
|
||||
payload['level'] ??
|
||||
payload['rocketLevel'] ??
|
||||
payload['fromLevel'],
|
||||
);
|
||||
return (value?.toInt() ?? fallback).clamp(1, 99).toInt();
|
||||
}
|
||||
|
||||
int _roomRocketPayloadNoticeSeconds(Map<String, dynamic> payload) {
|
||||
final seconds =
|
||||
_payloadNum(
|
||||
payload['countdownSeconds'] ??
|
||||
payload['launchDelaySeconds'] ??
|
||||
payload['delaySeconds'] ??
|
||||
payload['durationSeconds'],
|
||||
)?.toInt() ??
|
||||
10;
|
||||
if (seconds > 0 && seconds <= 60) {
|
||||
return seconds;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
String _rocketIconFromPayload(Map<String, dynamic> payload) {
|
||||
return _firstNonBlank([
|
||||
_payloadAssetText(payload['rocketIconUrl']),
|
||||
_payloadAssetText(payload['rocketIcon']),
|
||||
_payloadAssetText(payload['rocketUrl']),
|
||||
_payloadAssetText(payload['rocketImageUrl']),
|
||||
_payloadAssetText(payload['iconUrl']),
|
||||
]);
|
||||
}
|
||||
|
||||
String _roomRocketLaunchNoticeKeyForValues({
|
||||
required String roomId,
|
||||
required int level,
|
||||
required int roundNo,
|
||||
String launchNo = '',
|
||||
}) {
|
||||
final roundKey =
|
||||
roundNo > 0
|
||||
? roundNo.toString()
|
||||
: (launchNo.trim().isNotEmpty ? launchNo : 'full');
|
||||
return [
|
||||
'rocket_launch_notice',
|
||||
roomId,
|
||||
level.clamp(1, 99).toInt(),
|
||||
roundKey,
|
||||
].join('|');
|
||||
}
|
||||
|
||||
List<String> _payloadStringList(dynamic value) {
|
||||
if (value is Iterable) {
|
||||
return value
|
||||
|
||||
@ -78,7 +78,7 @@ abstract class SocialChatUserRepository {
|
||||
Future anchorHeartbeat(String roomId);
|
||||
|
||||
///退出房间
|
||||
Future<bool> quitRoom(String roomId);
|
||||
Future<bool> quitRoom(String roomId, {bool silentErrorToast = false});
|
||||
|
||||
///修改房间信息
|
||||
Future<SCEditRoomInfoRes> editRoomInfo(
|
||||
|
||||
@ -123,7 +123,7 @@ String _stringValue(dynamic value) {
|
||||
}
|
||||
|
||||
String _roomCoverUrl(Map<String, dynamic> data) {
|
||||
return _firstStringValue([
|
||||
return _firstAssetString([
|
||||
data['roomCoverUrl'],
|
||||
data['roomCover'],
|
||||
data['roomAvatar'],
|
||||
@ -137,7 +137,7 @@ String _roomCoverUrl(Map<String, dynamic> data) {
|
||||
}
|
||||
|
||||
String _rocketIconUrl(Map<String, dynamic> data) {
|
||||
return _firstStringValue([
|
||||
return _firstAssetString([
|
||||
data['rocket_icon_url'],
|
||||
data['rocketIconUrl'],
|
||||
data['rocketIcon'],
|
||||
@ -159,7 +159,7 @@ String _rocketIconUrl(Map<String, dynamic> data) {
|
||||
}
|
||||
|
||||
String _rocketAnimationUrl(Map<String, dynamic> data) {
|
||||
return _firstStringValue([
|
||||
return _firstAssetString([
|
||||
data['rocket_animation_url'],
|
||||
data['rocketAnimationUrl'],
|
||||
data['rocketSvgaUrl'],
|
||||
@ -167,28 +167,49 @@ String _rocketAnimationUrl(Map<String, dynamic> data) {
|
||||
data['svgaUrl'],
|
||||
data['rocketLaunchSvgaUrl'],
|
||||
data['launchAnimationUrl'],
|
||||
data['launchAnimation'],
|
||||
data['launchSvgaUrl'],
|
||||
data['rocketAnimation'],
|
||||
data['animation'],
|
||||
data['animationResource'],
|
||||
data['rocketAnimationResource'],
|
||||
data['launchAnimationResource'],
|
||||
data['sourceUrl'],
|
||||
_asMap(data['rocket'])['rocket_animation_url'],
|
||||
_asMap(data['rocket'])['rocketAnimationUrl'],
|
||||
_asMap(data['rocket'])['rocketSvgaUrl'],
|
||||
_asMap(data['rocket'])['animationUrl'],
|
||||
_asMap(data['rocket'])['rocketAnimation'],
|
||||
_asMap(data['rocket'])['animation'],
|
||||
_asMap(data['rocket'])['animationResource'],
|
||||
_asMap(data['rocket'])['svgaUrl'],
|
||||
_asMap(data['levelInfo'])['rocket_animation_url'],
|
||||
_asMap(data['levelInfo'])['rocketAnimationUrl'],
|
||||
_asMap(data['levelInfo'])['rocketSvgaUrl'],
|
||||
_asMap(data['levelInfo'])['animationUrl'],
|
||||
_asMap(data['levelInfo'])['rocketAnimation'],
|
||||
_asMap(data['levelInfo'])['animation'],
|
||||
_asMap(data['levelInfo'])['animationResource'],
|
||||
_asMap(data['rocketLevel'])['rocket_animation_url'],
|
||||
_asMap(data['rocketLevel'])['rocketAnimationUrl'],
|
||||
_asMap(data['rocketLevel'])['rocketSvgaUrl'],
|
||||
_asMap(data['rocketLevel'])['animationUrl'],
|
||||
_asMap(data['rocketLevel'])['rocketAnimation'],
|
||||
_asMap(data['rocketLevel'])['animation'],
|
||||
_asMap(data['rocketLevel'])['animationResource'],
|
||||
_asMap(data['config'])['rocket_animation_url'],
|
||||
_asMap(data['config'])['rocketAnimationUrl'],
|
||||
_asMap(data['config'])['rocketSvgaUrl'],
|
||||
_asMap(data['config'])['animationUrl'],
|
||||
_asMap(data['config'])['rocketAnimation'],
|
||||
_asMap(data['config'])['animation'],
|
||||
_asMap(data['config'])['animationResource'],
|
||||
]);
|
||||
}
|
||||
|
||||
String _firstStringValue(Iterable<dynamic> values) {
|
||||
String _firstAssetString(Iterable<dynamic> values) {
|
||||
for (final value in values) {
|
||||
final text = _stringValue(value);
|
||||
final text = _assetString(value);
|
||||
if (text.isNotEmpty) {
|
||||
return text;
|
||||
}
|
||||
@ -196,6 +217,36 @@ String _firstStringValue(Iterable<dynamic> values) {
|
||||
return '';
|
||||
}
|
||||
|
||||
String _assetString(dynamic value) {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
if (value is Map) {
|
||||
final map = _asMap(value);
|
||||
return _firstAssetString([
|
||||
map['sourceUrl'],
|
||||
map['url'],
|
||||
map['resourceUrl'],
|
||||
map['fileUrl'],
|
||||
map['src'],
|
||||
map['path'],
|
||||
map['cover'],
|
||||
map['imageUrl'],
|
||||
map['svgUrl'],
|
||||
map['svg'],
|
||||
map['value'],
|
||||
]);
|
||||
}
|
||||
if (value is Iterable && value is! String) {
|
||||
return _firstAssetString(value);
|
||||
}
|
||||
final text = _stringValue(value);
|
||||
if (text == '{}' || text == '[]' || text.toLowerCase() == 'null') {
|
||||
return '';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
int _intValue(dynamic value, {int fallback = 0}) {
|
||||
if (value is int) {
|
||||
return value;
|
||||
|
||||
@ -195,6 +195,14 @@ class DataPersistence {
|
||||
return getString("last_time_room_id");
|
||||
}
|
||||
|
||||
static Future<bool> setLastTimeRoomUserId(String id) async {
|
||||
return await setString("last_time_room_user_id", id);
|
||||
}
|
||||
|
||||
static String getLastTimeRoomUserId() {
|
||||
return getString("last_time_room_user_id");
|
||||
}
|
||||
|
||||
static Future<bool> setPlayGiftMusic(bool muteMusic) async {
|
||||
return await setBool("play_gift_music", muteMusic);
|
||||
}
|
||||
|
||||
@ -229,10 +229,17 @@ class SCAccountRepository implements SocialChatUserRepository {
|
||||
|
||||
///live/room/quit
|
||||
@override
|
||||
Future<bool> quitRoom(String roomId) async {
|
||||
Future<bool> quitRoom(String roomId, {bool silentErrorToast = false}) async {
|
||||
final result = await http.get<bool>(
|
||||
"3e451c31ab651a577a3b7796b32041e7",
|
||||
queryParams: {"roomId": roomId},
|
||||
extra:
|
||||
silentErrorToast
|
||||
? const {
|
||||
BaseNetworkClient.silentErrorToastKey: true,
|
||||
BaseNetworkClient.suppressAuthLogoutKey: true,
|
||||
}
|
||||
: null,
|
||||
fromJson: (json) => json as bool,
|
||||
);
|
||||
return result;
|
||||
|
||||
@ -31,9 +31,9 @@ class SCChatRoomHelper {
|
||||
try {
|
||||
final currentRoomId =
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
|
||||
context,
|
||||
listen: false,
|
||||
).currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
|
||||
'';
|
||||
return currentRoomId.isNotEmpty && currentRoomId == normalizedRoomId;
|
||||
} catch (_) {
|
||||
@ -294,6 +294,9 @@ class SCChatRoomHelper {
|
||||
),
|
||||
);
|
||||
DataPersistence.setLastTimeRoomId(roomId);
|
||||
DataPersistence.setLastTimeRoomUserId(
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
static void closeAllDialogs() async {
|
||||
|
||||
@ -788,7 +788,11 @@ class _RocketCanvas extends StatelessWidget {
|
||||
top: s(753),
|
||||
width: s(50),
|
||||
height: s(50),
|
||||
child: _CrewAvatar(scale: scale, member: crew[index]),
|
||||
child: _CrewAvatar(
|
||||
scale: scale,
|
||||
member: crew[index],
|
||||
rank: index,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: s(315),
|
||||
@ -1039,43 +1043,54 @@ class _LevelBadge extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _CrewAvatar extends StatelessWidget {
|
||||
const _CrewAvatar({required this.scale, required this.member});
|
||||
const _CrewAvatar({
|
||||
required this.scale,
|
||||
required this.member,
|
||||
required this.rank,
|
||||
});
|
||||
|
||||
final double scale;
|
||||
final RoomRocketCrewMember member;
|
||||
final int rank;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
_RocketAssets.bottomAvatarRing,
|
||||
width: 40 * scale,
|
||||
height: 40 * scale,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
ClipOval(
|
||||
child: SizedBox(
|
||||
width: 42 * scale,
|
||||
height: 42 * scale,
|
||||
child:
|
||||
member.asset != null || member.imageUrl != null
|
||||
? _RocketImage(
|
||||
asset: member.asset,
|
||||
imageUrl: member.imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.asset(
|
||||
_RocketAssets.defaultAvatar,
|
||||
fit: BoxFit.cover,
|
||||
gaplessPlayback: true,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 6.25 * scale,
|
||||
top: 5 * scale,
|
||||
width: 37.5 * scale,
|
||||
height: 37.5 * scale,
|
||||
child: ClipOval(child: _avatarImage()),
|
||||
),
|
||||
Positioned.fill(child: Image.asset(_frameAsset, fit: BoxFit.contain)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String get _frameAsset {
|
||||
return switch (rank) {
|
||||
0 => _RocketAssets.rankFrame1,
|
||||
1 => _RocketAssets.rankFrame2,
|
||||
_ => _RocketAssets.rankFrame3,
|
||||
};
|
||||
}
|
||||
|
||||
Widget _avatarImage() {
|
||||
if (member.asset != null || member.imageUrl != null) {
|
||||
return _RocketImage(
|
||||
asset: member.asset,
|
||||
imageUrl: member.imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}
|
||||
return Image.asset(
|
||||
_RocketAssets.defaultAvatar,
|
||||
fit: BoxFit.cover,
|
||||
gaplessPlayback: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RocketImage extends StatelessWidget {
|
||||
@ -1867,8 +1882,12 @@ class _RocketAssets {
|
||||
'sc_images/room/rocket/bottom_strip_active.png';
|
||||
static const String bottomTrophy = 'sc_images/room/rocket/bottom_trophy.png';
|
||||
static const String backArrow = 'sc_images/room/rocket/back_arrow.png';
|
||||
static const String bottomAvatarRing =
|
||||
'sc_images/room/rocket/bottom_avatar_ring.png';
|
||||
static const String rankFrame1 =
|
||||
'sc_images/room/rocket/rocket_rank_frame_1.png';
|
||||
static const String rankFrame2 =
|
||||
'sc_images/room/rocket/rocket_rank_frame_2.png';
|
||||
static const String rankFrame3 =
|
||||
'sc_images/room/rocket/rocket_rank_frame_3.png';
|
||||
static const String pillBg = 'sc_images/room/rocket/pill_bg.png';
|
||||
static const String progressFull = 'sc_images/room/rocket/progress_full.png';
|
||||
static const String progressEmpty =
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yumi/app/config/app_config.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
||||
|
||||
void main() {
|
||||
@ -58,4 +59,24 @@ void main() {
|
||||
expect(released, isTrue);
|
||||
timer?.cancel();
|
||||
});
|
||||
|
||||
test('extracts launch pag urls from nested resource payloads', () {
|
||||
final launch = RoomRocketLaunchBroadcastMessage.fromJson({
|
||||
'roomId': 'room-1',
|
||||
'rocketAnimationResource': {
|
||||
'sourceUrl': 'https://cdn.example.com/rocket/launch.pag',
|
||||
},
|
||||
'rocketIcon': {'url': 'https://cdn.example.com/rocket/icon.png'},
|
||||
'room': {
|
||||
'cover': {'url': 'https://cdn.example.com/room/cover.png'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
launch.rocketAnimationUrl,
|
||||
'https://cdn.example.com/rocket/launch.pag',
|
||||
);
|
||||
expect(launch.rocketIconUrl, 'https://cdn.example.com/rocket/icon.png');
|
||||
expect(launch.roomCoverUrl, 'https://cdn.example.com/room/cover.png');
|
||||
});
|
||||
}
|
||||
|
||||
2
需求进度.md
2
需求进度.md
@ -13,6 +13,8 @@
|
||||
- [x] 2026-05-15 Android 打包链路已继续收口:`pag-1.0.7` 原包缺 Android namespace、Kotlin JVM target 不一致且仍引用 Flutter v1 embedding,已切为本地 patched package 并补齐 `namespace/jvmTarget/v2 embedding` 兼容,避免火箭 PAG 接入后阻断 APK 构建。
|
||||
- [x] 2026-05-15 已执行 `flutter build apk --debug`,结果通过,产物为 `build/app/outputs/flutter-apk/app-debug.apk`。
|
||||
- [x] 2026-05-15 复跑迁移单测通过;同时修复火箭奖励记录模型 `rocketIconUrl` getter 缺失导致的测试编译错误。
|
||||
- [x] 2026-05-15 已修复杀进程后跨房间上麦残留:启动和进入新房间前不再直接清空 `last_time_room_id`,会先静默调用旧房间 `quitRoom`,成功后再清理本地标记,避免 A/B 房间同时展示同一用户麦位。
|
||||
- [x] 2026-05-15 已验证异常退出残留修复相关改动:定向 `flutter analyze --no-fatal-infos --no-fatal-warnings` 无 error,TRTC 迁移单测 `flutter test test/room_rtc_types_test.dart test/trtc_room_rtc_engine_adapter_test.dart` 通过。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user