修改了一部分
This commit is contained in:
parent
ea296e461b
commit
51083ef57f
@ -66,15 +66,10 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
_initializePages();
|
_initializePages();
|
||||||
unawaited(_loadTaskClaimableCount());
|
unawaited(_loadTaskClaimableCount());
|
||||||
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
|
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||||
Provider.of<RtcProvider>(
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
context,
|
rtcProvider.initializeRealTimeCommunicationManager(context);
|
||||||
listen: false,
|
|
||||||
).initializeRealTimeCommunicationManager(context);
|
|
||||||
unawaited(
|
unawaited(
|
||||||
Provider.of<RtcProvider>(
|
rtcProvider.prewarmRtcEngine().catchError((error, stackTrace) {}),
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).prewarmRtcEngine().catchError((error, stackTrace) {}),
|
|
||||||
);
|
);
|
||||||
Provider.of<RtmProvider>(context, listen: false).init(context);
|
Provider.of<RtmProvider>(context, listen: false).init(context);
|
||||||
Provider.of<SocialChatUserProfileManager>(
|
Provider.of<SocialChatUserProfileManager>(
|
||||||
@ -89,9 +84,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
|||||||
WakelockPlus.enable();
|
WakelockPlus.enable();
|
||||||
_showEntryDialogs();
|
_showEntryDialogs();
|
||||||
});
|
});
|
||||||
if (DataPersistence.getLastTimeRoomId().isNotEmpty) {
|
unawaited(rtcProvider.cleanupPersistedVoiceRoomSessionBeforeEntry());
|
||||||
unawaited(DataPersistence.setLastTimeRoomId(""));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -331,9 +331,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Timer? _onlineUsersPollingTimer;
|
Timer? _onlineUsersPollingTimer;
|
||||||
Timer? _roomEntryEffectTimer;
|
Timer? _roomEntryEffectTimer;
|
||||||
Timer? _roomRocketLaunchAnimationTimer;
|
Timer? _roomRocketLaunchAnimationTimer;
|
||||||
|
String? _lastRoomRocketLaunchNoticeKey;
|
||||||
Timer? _roomRedPacketPresenceTimer;
|
Timer? _roomRedPacketPresenceTimer;
|
||||||
Future<void>? _rtcEnginePrewarmTask;
|
Future<void>? _rtcEnginePrewarmTask;
|
||||||
Future<void>? _pendingRoomSwitchRtcLeaveTask;
|
Future<void>? _pendingRoomSwitchRtcLeaveTask;
|
||||||
|
Future<void>? _persistedRoomCleanupTask;
|
||||||
bool _isRefreshingMicList = false;
|
bool _isRefreshingMicList = false;
|
||||||
bool _isRefreshingOnlineUsers = false;
|
bool _isRefreshingOnlineUsers = false;
|
||||||
bool _disableMicListRefreshForCurrentSession = false;
|
bool _disableMicListRefreshForCurrentSession = false;
|
||||||
@ -1721,6 +1723,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await cleanupPersistedVoiceRoomSessionBeforeEntry(targetRoomId: roomId);
|
||||||
|
if (!context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
|
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
|
||||||
if (_currentRoomIsEntryPreview ||
|
if (_currentRoomIsEntryPreview ||
|
||||||
_roomStartupStatus == RoomStartupStatus.loading) {
|
_roomStartupStatus == RoomStartupStatus.loading) {
|
||||||
@ -2372,12 +2378,83 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _retryExitNetworkRequest('quit stale entered room $staleRoomId', () {
|
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) {
|
if (!didQuit) {
|
||||||
throw StateError('quitRoom returned false');
|
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) {
|
void updateRoomRocketConfigurationStatus(SCRoomRocketStatusRes res) {
|
||||||
|
final previousStatus = roomRocketStatus;
|
||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
///刷新房间火箭信息
|
///刷新房间火箭信息
|
||||||
@ -2710,8 +2789,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final previousStatus = roomRocketStatus;
|
||||||
roomRocketStatus = res;
|
roomRocketStatus = res;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleRoomRocketLaunchBroadcast(
|
void handleRoomRocketLaunchBroadcast(
|
||||||
@ -2759,6 +2840,114 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return null;
|
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({
|
void _playRoomRocketLaunchAnimation({
|
||||||
required String animationUrl,
|
required String animationUrl,
|
||||||
required RoomRocketLaunchBroadcastMessage launch,
|
required RoomRocketLaunchBroadcastMessage launch,
|
||||||
@ -3066,7 +3255,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
exitRtmProvider?.cleanRoomData();
|
exitRtmProvider?.cleanRoomData();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
_clearData();
|
_clearData(clearPersistedRoomMarker: false);
|
||||||
if (!isLogout && navigationContext != null) {
|
if (!isLogout && navigationContext != null) {
|
||||||
VoiceRoomRoute.popVoiceRoomToPrevious(navigationContext);
|
VoiceRoomRoute.popVoiceRoomToPrevious(navigationContext);
|
||||||
}
|
}
|
||||||
@ -3104,7 +3293,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
exitRtmProvider?.cleanRoomData();
|
exitRtmProvider?.cleanRoomData();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
_clearData();
|
_clearData(clearPersistedRoomMarker: false);
|
||||||
final roomRtcLeaveTask = _leaveRoomRtcForExit();
|
final roomRtcLeaveTask = _leaveRoomRtcForExit();
|
||||||
_pendingRoomSwitchRtcLeaveTask = roomRtcLeaveTask;
|
_pendingRoomSwitchRtcLeaveTask = roomRtcLeaveTask;
|
||||||
unawaited(
|
unawaited(
|
||||||
@ -3169,16 +3358,34 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (_isCurrentRoomId(roomId)) {
|
if (_isCurrentRoomId(roomId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final didQuit = await SCAccountRepository().quitRoom(roomId);
|
final didQuit = await SCAccountRepository().quitRoom(
|
||||||
|
roomId,
|
||||||
|
silentErrorToast: true,
|
||||||
|
);
|
||||||
if (!didQuit) {
|
if (!didQuit) {
|
||||||
throw StateError('quitRoom returned false');
|
throw StateError('quitRoom returned false');
|
||||||
}
|
}
|
||||||
|
await _clearPersistedRoomMarkerIfMatches(roomId);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await Future.wait(tasks);
|
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(
|
Future<void> _retryExitNetworkRequest(
|
||||||
String name,
|
String name,
|
||||||
Future<void> Function() action,
|
Future<void> Function() action,
|
||||||
@ -3257,7 +3464,13 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
if (!isPreviewOnlyFailure && roomId.isNotEmpty) {
|
if (!isPreviewOnlyFailure && roomId.isNotEmpty) {
|
||||||
await SCChatRoomRepository().roomRedPacketPresenceLeave(roomId);
|
await SCChatRoomRepository().roomRedPacketPresenceLeave(roomId);
|
||||||
await SCAccountRepository().quitRoom(roomId);
|
final didQuit = await SCAccountRepository().quitRoom(
|
||||||
|
roomId,
|
||||||
|
silentErrorToast: true,
|
||||||
|
);
|
||||||
|
if (didQuit) {
|
||||||
|
await _clearPersistedRoomMarkerIfMatches(roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
} finally {
|
} finally {
|
||||||
@ -3271,13 +3484,14 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///清空列表数据
|
///清空列表数据
|
||||||
void _clearData() {
|
void _clearData({bool clearPersistedRoomMarker = true}) {
|
||||||
_roomEntryRequestSerial += 1;
|
_roomEntryRequestSerial += 1;
|
||||||
_stopVoiceRoomForegroundService();
|
_stopVoiceRoomForegroundService();
|
||||||
_roomEntryEffectTimer?.cancel();
|
_roomEntryEffectTimer?.cancel();
|
||||||
_roomEntryEffectTimer = null;
|
_roomEntryEffectTimer = null;
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
_lastRoomRocketLaunchNoticeKey = null;
|
||||||
_stopRoomRedPacketPresenceHeartbeat();
|
_stopRoomRedPacketPresenceHeartbeat();
|
||||||
_previewRoomSeatCount = null;
|
_previewRoomSeatCount = null;
|
||||||
_finishRoomStartupSeatLoading(executePendingAction: false);
|
_finishRoomStartupSeatLoading(executePendingAction: false);
|
||||||
@ -3320,7 +3534,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_isExitingCurrentVoiceRoomSession = false;
|
_isExitingCurrentVoiceRoomSession = false;
|
||||||
isMic = true;
|
isMic = true;
|
||||||
isMusicPlaying = false;
|
isMusicPlaying = false;
|
||||||
DataPersistence.setLastTimeRoomId("");
|
if (clearPersistedRoomMarker) {
|
||||||
|
DataPersistence.setLastTimeRoomId("");
|
||||||
|
DataPersistence.setLastTimeRoomUserId("");
|
||||||
|
}
|
||||||
SCGiftVapSvgaManager().stopPlayback();
|
SCGiftVapSvgaManager().stopPlayback();
|
||||||
SCRoomUtils.closeAllDialogs();
|
SCRoomUtils.closeAllDialogs();
|
||||||
SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults();
|
SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults();
|
||||||
|
|||||||
@ -1862,6 +1862,36 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
return value.toString().trim();
|
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) {
|
num? _payloadNum(dynamic value) {
|
||||||
if (value is num) {
|
if (value is num) {
|
||||||
return value;
|
return value;
|
||||||
@ -1886,6 +1916,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
currentContext,
|
currentContext,
|
||||||
listen: false,
|
listen: false,
|
||||||
);
|
);
|
||||||
|
_publishRoomRocketLaunchNoticeFromStatusPayload(
|
||||||
|
rtcProvider: rtcProvider,
|
||||||
|
payload: data,
|
||||||
|
roomId: roomId,
|
||||||
|
);
|
||||||
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
|
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2047,15 +2082,155 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
launch.rocketIconUrl,
|
launch.rocketIconUrl,
|
||||||
_rocketIconUrlForLevel(launch.safeLevel),
|
_rocketIconUrlForLevel(launch.safeLevel),
|
||||||
]),
|
]),
|
||||||
giftBatchId: [
|
giftBatchId: _roomRocketLaunchNoticeKey(launch),
|
||||||
'rocket_launch_notice',
|
|
||||||
launch.launchNo,
|
|
||||||
launch.roomId,
|
|
||||||
launch.safeLevel,
|
|
||||||
].join('|'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
List<String> _payloadStringList(dynamic value) {
|
||||||
if (value is Iterable) {
|
if (value is Iterable) {
|
||||||
return value
|
return value
|
||||||
|
|||||||
@ -78,7 +78,7 @@ abstract class SocialChatUserRepository {
|
|||||||
Future anchorHeartbeat(String roomId);
|
Future anchorHeartbeat(String roomId);
|
||||||
|
|
||||||
///退出房间
|
///退出房间
|
||||||
Future<bool> quitRoom(String roomId);
|
Future<bool> quitRoom(String roomId, {bool silentErrorToast = false});
|
||||||
|
|
||||||
///修改房间信息
|
///修改房间信息
|
||||||
Future<SCEditRoomInfoRes> editRoomInfo(
|
Future<SCEditRoomInfoRes> editRoomInfo(
|
||||||
|
|||||||
@ -123,7 +123,7 @@ String _stringValue(dynamic value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _roomCoverUrl(Map<String, dynamic> data) {
|
String _roomCoverUrl(Map<String, dynamic> data) {
|
||||||
return _firstStringValue([
|
return _firstAssetString([
|
||||||
data['roomCoverUrl'],
|
data['roomCoverUrl'],
|
||||||
data['roomCover'],
|
data['roomCover'],
|
||||||
data['roomAvatar'],
|
data['roomAvatar'],
|
||||||
@ -137,7 +137,7 @@ String _roomCoverUrl(Map<String, dynamic> data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _rocketIconUrl(Map<String, dynamic> data) {
|
String _rocketIconUrl(Map<String, dynamic> data) {
|
||||||
return _firstStringValue([
|
return _firstAssetString([
|
||||||
data['rocket_icon_url'],
|
data['rocket_icon_url'],
|
||||||
data['rocketIconUrl'],
|
data['rocketIconUrl'],
|
||||||
data['rocketIcon'],
|
data['rocketIcon'],
|
||||||
@ -159,7 +159,7 @@ String _rocketIconUrl(Map<String, dynamic> data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _rocketAnimationUrl(Map<String, dynamic> data) {
|
String _rocketAnimationUrl(Map<String, dynamic> data) {
|
||||||
return _firstStringValue([
|
return _firstAssetString([
|
||||||
data['rocket_animation_url'],
|
data['rocket_animation_url'],
|
||||||
data['rocketAnimationUrl'],
|
data['rocketAnimationUrl'],
|
||||||
data['rocketSvgaUrl'],
|
data['rocketSvgaUrl'],
|
||||||
@ -167,28 +167,49 @@ String _rocketAnimationUrl(Map<String, dynamic> data) {
|
|||||||
data['svgaUrl'],
|
data['svgaUrl'],
|
||||||
data['rocketLaunchSvgaUrl'],
|
data['rocketLaunchSvgaUrl'],
|
||||||
data['launchAnimationUrl'],
|
data['launchAnimationUrl'],
|
||||||
|
data['launchAnimation'],
|
||||||
data['launchSvgaUrl'],
|
data['launchSvgaUrl'],
|
||||||
|
data['rocketAnimation'],
|
||||||
|
data['animation'],
|
||||||
|
data['animationResource'],
|
||||||
|
data['rocketAnimationResource'],
|
||||||
|
data['launchAnimationResource'],
|
||||||
data['sourceUrl'],
|
data['sourceUrl'],
|
||||||
_asMap(data['rocket'])['rocket_animation_url'],
|
_asMap(data['rocket'])['rocket_animation_url'],
|
||||||
_asMap(data['rocket'])['rocketAnimationUrl'],
|
_asMap(data['rocket'])['rocketAnimationUrl'],
|
||||||
_asMap(data['rocket'])['rocketSvgaUrl'],
|
_asMap(data['rocket'])['rocketSvgaUrl'],
|
||||||
_asMap(data['rocket'])['animationUrl'],
|
_asMap(data['rocket'])['animationUrl'],
|
||||||
|
_asMap(data['rocket'])['rocketAnimation'],
|
||||||
|
_asMap(data['rocket'])['animation'],
|
||||||
|
_asMap(data['rocket'])['animationResource'],
|
||||||
_asMap(data['rocket'])['svgaUrl'],
|
_asMap(data['rocket'])['svgaUrl'],
|
||||||
_asMap(data['levelInfo'])['rocket_animation_url'],
|
_asMap(data['levelInfo'])['rocket_animation_url'],
|
||||||
_asMap(data['levelInfo'])['rocketAnimationUrl'],
|
_asMap(data['levelInfo'])['rocketAnimationUrl'],
|
||||||
_asMap(data['levelInfo'])['rocketSvgaUrl'],
|
_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'])['rocket_animation_url'],
|
||||||
_asMap(data['rocketLevel'])['rocketAnimationUrl'],
|
_asMap(data['rocketLevel'])['rocketAnimationUrl'],
|
||||||
_asMap(data['rocketLevel'])['rocketSvgaUrl'],
|
_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'])['rocket_animation_url'],
|
||||||
_asMap(data['config'])['rocketAnimationUrl'],
|
_asMap(data['config'])['rocketAnimationUrl'],
|
||||||
_asMap(data['config'])['rocketSvgaUrl'],
|
_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) {
|
for (final value in values) {
|
||||||
final text = _stringValue(value);
|
final text = _assetString(value);
|
||||||
if (text.isNotEmpty) {
|
if (text.isNotEmpty) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
@ -196,6 +217,36 @@ String _firstStringValue(Iterable<dynamic> values) {
|
|||||||
return '';
|
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}) {
|
int _intValue(dynamic value, {int fallback = 0}) {
|
||||||
if (value is int) {
|
if (value is int) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@ -195,6 +195,14 @@ class DataPersistence {
|
|||||||
return getString("last_time_room_id");
|
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 {
|
static Future<bool> setPlayGiftMusic(bool muteMusic) async {
|
||||||
return await setBool("play_gift_music", muteMusic);
|
return await setBool("play_gift_music", muteMusic);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -229,10 +229,17 @@ class SCAccountRepository implements SocialChatUserRepository {
|
|||||||
|
|
||||||
///live/room/quit
|
///live/room/quit
|
||||||
@override
|
@override
|
||||||
Future<bool> quitRoom(String roomId) async {
|
Future<bool> quitRoom(String roomId, {bool silentErrorToast = false}) async {
|
||||||
final result = await http.get<bool>(
|
final result = await http.get<bool>(
|
||||||
"3e451c31ab651a577a3b7796b32041e7",
|
"3e451c31ab651a577a3b7796b32041e7",
|
||||||
queryParams: {"roomId": roomId},
|
queryParams: {"roomId": roomId},
|
||||||
|
extra:
|
||||||
|
silentErrorToast
|
||||||
|
? const {
|
||||||
|
BaseNetworkClient.silentErrorToastKey: true,
|
||||||
|
BaseNetworkClient.suppressAuthLogoutKey: true,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
fromJson: (json) => json as bool,
|
fromJson: (json) => json as bool,
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -31,9 +31,9 @@ class SCChatRoomHelper {
|
|||||||
try {
|
try {
|
||||||
final currentRoomId =
|
final currentRoomId =
|
||||||
Provider.of<RealTimeCommunicationManager>(
|
Provider.of<RealTimeCommunicationManager>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
|
).currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
|
||||||
'';
|
'';
|
||||||
return currentRoomId.isNotEmpty && currentRoomId == normalizedRoomId;
|
return currentRoomId.isNotEmpty && currentRoomId == normalizedRoomId;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@ -294,6 +294,9 @@ class SCChatRoomHelper {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
DataPersistence.setLastTimeRoomId(roomId);
|
DataPersistence.setLastTimeRoomId(roomId);
|
||||||
|
DataPersistence.setLastTimeRoomUserId(
|
||||||
|
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void closeAllDialogs() async {
|
static void closeAllDialogs() async {
|
||||||
|
|||||||
@ -788,7 +788,11 @@ class _RocketCanvas extends StatelessWidget {
|
|||||||
top: s(753),
|
top: s(753),
|
||||||
width: s(50),
|
width: s(50),
|
||||||
height: s(50),
|
height: s(50),
|
||||||
child: _CrewAvatar(scale: scale, member: crew[index]),
|
child: _CrewAvatar(
|
||||||
|
scale: scale,
|
||||||
|
member: crew[index],
|
||||||
|
rank: index,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: s(315),
|
left: s(315),
|
||||||
@ -1039,43 +1043,54 @@ class _LevelBadge extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CrewAvatar 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 double scale;
|
||||||
final RoomRocketCrewMember member;
|
final RoomRocketCrewMember member;
|
||||||
|
final int rank;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Positioned(
|
||||||
_RocketAssets.bottomAvatarRing,
|
left: 6.25 * scale,
|
||||||
width: 40 * scale,
|
top: 5 * scale,
|
||||||
height: 40 * scale,
|
width: 37.5 * scale,
|
||||||
fit: BoxFit.fill,
|
height: 37.5 * scale,
|
||||||
),
|
child: ClipOval(child: _avatarImage()),
|
||||||
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.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 {
|
class _RocketImage extends StatelessWidget {
|
||||||
@ -1867,8 +1882,12 @@ class _RocketAssets {
|
|||||||
'sc_images/room/rocket/bottom_strip_active.png';
|
'sc_images/room/rocket/bottom_strip_active.png';
|
||||||
static const String bottomTrophy = 'sc_images/room/rocket/bottom_trophy.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 backArrow = 'sc_images/room/rocket/back_arrow.png';
|
||||||
static const String bottomAvatarRing =
|
static const String rankFrame1 =
|
||||||
'sc_images/room/rocket/bottom_avatar_ring.png';
|
'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 pillBg = 'sc_images/room/rocket/pill_bg.png';
|
||||||
static const String progressFull = 'sc_images/room/rocket/progress_full.png';
|
static const String progressFull = 'sc_images/room/rocket/progress_full.png';
|
||||||
static const String progressEmpty =
|
static const String progressEmpty =
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:yumi/app/config/app_config.dart';
|
import 'package:yumi/app/config/app_config.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_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';
|
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -58,4 +59,24 @@ void main() {
|
|||||||
expect(released, isTrue);
|
expect(released, isTrue);
|
||||||
timer?.cancel();
|
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 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 已执行 `flutter build apk --debug`,结果通过,产物为 `build/app/outputs/flutter-apk/app-debug.apk`。
|
||||||
- [x] 2026-05-15 复跑迁移单测通过;同时修复火箭奖励记录模型 `rocketIconUrl` getter 缺失导致的测试编译错误。
|
- [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