又一版
This commit is contained in:
parent
51083ef57f
commit
be6b2f8f88
@ -58,7 +58,6 @@
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.org.yumiparty
|
||||
|
||||
import android.content.Intent
|
||||
import com.android.installreferrer.api.InstallReferrerClient
|
||||
import com.android.installreferrer.api.InstallReferrerStateListener
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
@ -59,6 +60,11 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
installReferrerClient?.endConnection()
|
||||
installReferrerClient = null
|
||||
|
||||
@ -54,7 +54,9 @@ class VoiceRoomForegroundService : Service() {
|
||||
packageManager.getLaunchIntentForPackage(packageName)
|
||||
?: Intent(this, MainActivity::class.java)
|
||||
).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
|
||||
@ -559,10 +559,9 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
||||
|
||||
///礼物上飘动画
|
||||
_floatingGiftListener(Msg msg) {
|
||||
if (!Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).shouldShowRoomVisualEffects) {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
rtcProvider.scheduleRoomRocketStatusRefreshForGift();
|
||||
if (!rtcProvider.shouldShowRoomVisualEffects) {
|
||||
return;
|
||||
}
|
||||
if (Provider.of<GiftProvider>(context, listen: false).hideLGiftAnimal) {
|
||||
|
||||
@ -48,6 +48,7 @@ import '../../shared/data_sources/models/enum/sc_heartbeat_status.dart';
|
||||
import '../../shared/data_sources/models/enum/sc_room_info_event_type.dart';
|
||||
import '../../shared/data_sources/models/enum/sc_room_roles_type.dart';
|
||||
import '../../shared/business_logic/models/res/sc_is_follow_room_res.dart';
|
||||
import '../../shared/business_logic/models/res/sc_room_rocket_api_res.dart';
|
||||
import '../../shared/business_logic/models/res/sc_room_red_packet_config_res.dart';
|
||||
import '../../shared/business_logic/models/res/sc_room_red_packet_list_res.dart';
|
||||
import '../../shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||||
@ -56,6 +57,7 @@ import '../../shared/business_logic/models/res/sc_vip_res.dart';
|
||||
import '../../shared/tools/sc_room_profile_cache.dart';
|
||||
import '../../ui_kit/components/sc_float_ichart.dart';
|
||||
import '../../ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
|
||||
import '../../ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
|
||||
import '../../ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
||||
|
||||
typedef OnSoundVoiceChange = Function(num index, int volum);
|
||||
@ -292,6 +294,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
|
||||
static const String _roomRocketPagLaunchDialogTag =
|
||||
'showRoomRocketPagLaunchDialog';
|
||||
static const Duration _roomRocketGiftRefreshDelay = Duration(
|
||||
milliseconds: 700,
|
||||
);
|
||||
static const int _roomPasswordNotTrueErrorCode = 9005;
|
||||
|
||||
static const Duration _micListPollingInterval = Duration(seconds: 2);
|
||||
@ -331,6 +336,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
Timer? _onlineUsersPollingTimer;
|
||||
Timer? _roomEntryEffectTimer;
|
||||
Timer? _roomRocketLaunchAnimationTimer;
|
||||
Timer? _roomRocketGiftRefreshTimer;
|
||||
String? _lastRoomRocketLaunchNoticeKey;
|
||||
Timer? _roomRedPacketPresenceTimer;
|
||||
Future<void>? _rtcEnginePrewarmTask;
|
||||
@ -477,6 +483,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_roomEntryEffectTimer = null;
|
||||
_roomRocketLaunchAnimationTimer?.cancel();
|
||||
_roomRocketLaunchAnimationTimer = null;
|
||||
_roomRocketGiftRefreshTimer?.cancel();
|
||||
_roomRocketGiftRefreshTimer = null;
|
||||
}
|
||||
_roomVisualEffectsEnabled = enabled;
|
||||
notifyListeners();
|
||||
@ -2753,6 +2761,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
(_) => RoomRocketRewardDialogLoader(
|
||||
roomId: roomId,
|
||||
initialPopupRecords: res.records,
|
||||
userProfileResolver: roomRocketRewardUserProfileForRecord,
|
||||
onClose: () {
|
||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
||||
if (recordIds.isNotEmpty) {
|
||||
@ -2795,6 +2804,19 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_publishRoomRocketLaunchNoticeIfNeeded(previousStatus, res);
|
||||
}
|
||||
|
||||
void scheduleRoomRocketStatusRefreshForGift({String? roomId}) {
|
||||
final resolvedRoomId =
|
||||
(roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (resolvedRoomId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_roomRocketGiftRefreshTimer?.cancel();
|
||||
_roomRocketGiftRefreshTimer = Timer(_roomRocketGiftRefreshDelay, () {
|
||||
_roomRocketGiftRefreshTimer = null;
|
||||
unawaited(refreshRoomRocketStatus(roomId: resolvedRoomId));
|
||||
});
|
||||
}
|
||||
|
||||
void handleRoomRocketLaunchBroadcast(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
@ -3000,6 +3022,63 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
RoomRocketRewardUserProfile? roomRocketRewardUserProfileForRecord(
|
||||
SCRoomRocketRewardRecordRes record,
|
||||
) {
|
||||
final keys =
|
||||
<String>{
|
||||
record.userId.trim(),
|
||||
record.account.trim(),
|
||||
}.where((item) => item.isNotEmpty).toSet();
|
||||
if (keys.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
for (final profile in _roomRocketKnownUserProfiles()) {
|
||||
if (_roomRocketProfileMatches(profile, keys)) {
|
||||
return RoomRocketRewardUserProfile(
|
||||
userId: profile.id?.trim() ?? '',
|
||||
account: profile.account?.trim() ?? '',
|
||||
nickname: profile.userNickname?.trim() ?? '',
|
||||
avatarUrl: profile.userAvatar?.trim() ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterable<SocialChatUserProfile> _roomRocketKnownUserProfiles() sync* {
|
||||
final currentUser = AccountStorage().getCurrentUser()?.userProfile;
|
||||
if (currentUser != null) {
|
||||
yield currentUser;
|
||||
}
|
||||
final ownerProfile = currenRoom?.roomProfile?.userProfile;
|
||||
if (ownerProfile != null) {
|
||||
yield ownerProfile;
|
||||
}
|
||||
for (final user in onlineUsers) {
|
||||
yield user;
|
||||
}
|
||||
for (final seat in roomWheatMap.values) {
|
||||
final user = seat.user;
|
||||
if (user != null) {
|
||||
yield user;
|
||||
}
|
||||
}
|
||||
for (final user in SCRoomUtils.roomUsersMap.values) {
|
||||
yield user;
|
||||
}
|
||||
}
|
||||
|
||||
bool _roomRocketProfileMatches(
|
||||
SocialChatUserProfile profile,
|
||||
Set<String> keys,
|
||||
) {
|
||||
return [profile.id, profile.account, profile.inRoomId].any((value) {
|
||||
final text = value?.trim() ?? '';
|
||||
return text.isNotEmpty && keys.contains(text);
|
||||
});
|
||||
}
|
||||
|
||||
///获取在线用户
|
||||
Future<void> fetchOnlineUsersList({bool notifyIfUnchanged = true}) async {
|
||||
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||
@ -3491,6 +3570,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_roomEntryEffectTimer = null;
|
||||
_roomRocketLaunchAnimationTimer?.cancel();
|
||||
_roomRocketLaunchAnimationTimer = null;
|
||||
_roomRocketGiftRefreshTimer?.cancel();
|
||||
_roomRocketGiftRefreshTimer = null;
|
||||
_lastRoomRocketLaunchNoticeKey = null;
|
||||
_stopRoomRedPacketPresenceHeartbeat();
|
||||
_previewRoomSeatCount = null;
|
||||
|
||||
@ -50,6 +50,7 @@ import 'package:yumi/shared/data_sources/models/message/big_broadcast_group_mess
|
||||
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_broad_cast_luck_gift_push.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_api_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/broad_cast_mic_change_push.dart'
|
||||
hide Data;
|
||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||
@ -60,6 +61,7 @@ import 'package:yumi/ui_kit/widgets/room/invite/invite_room_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_models.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_open_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_pending_cache.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_api_mapper.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
@ -116,6 +118,13 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
static const int _luckyGiftBurstMergeWindowMs = 1800;
|
||||
static const int _luckyGiftBurstRecentEventTtlMs = 5000;
|
||||
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
|
||||
static const int _roomRocketLaunchRecentTtlMs = 90000;
|
||||
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
|
||||
static const List<Duration> _roomRocketRewardPopupRetryDelays = [
|
||||
Duration(milliseconds: 700),
|
||||
Duration(seconds: 2),
|
||||
Duration(seconds: 5),
|
||||
];
|
||||
|
||||
BuildContext? context;
|
||||
|
||||
@ -224,6 +233,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
Timer? _luckGiftPushPlayTimer;
|
||||
int _luckGiftPushPlaybackToken = 0;
|
||||
final Map<String, int> _recentLuckGiftPushEventTimes = <String, int>{};
|
||||
final Map<String, int> _recentRoomRocketLaunchTimes = <String, int>{};
|
||||
final Map<String, int> _recentRoomRocketLaunchLooseTimes = <String, int>{};
|
||||
Timer? _roomRocketRewardPopupRetryTimer;
|
||||
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
|
||||
String? roomRedPacketBroadcastGroupId;
|
||||
String? roomRedPacketBroadcastRegionCode;
|
||||
@ -1904,7 +1916,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
|
||||
void _handleVoiceRoomRocketStatusUpdate(dynamic payload) {
|
||||
final data = _broadcastPayloadMap(payload);
|
||||
final roomId = _payloadText(data['roomId']);
|
||||
final roomId = _payloadRoomId(data);
|
||||
if (!_isCurrentVoiceRoom(roomId)) {
|
||||
return;
|
||||
}
|
||||
@ -1916,11 +1928,21 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
currentContext,
|
||||
listen: false,
|
||||
);
|
||||
_publishRoomRocketLaunchNoticeFromStatusPayload(
|
||||
final launch = _buildRoomRocketLaunchFromStatusPayload(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
roomId: roomId,
|
||||
);
|
||||
if (_shouldHandleRoomRocketLaunchFromStatusPayload(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
launch: launch,
|
||||
)) {
|
||||
_handleCurrentVisibleRoomRocketLaunch(
|
||||
rtcProvider: rtcProvider,
|
||||
launch: launch,
|
||||
);
|
||||
}
|
||||
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
|
||||
}
|
||||
|
||||
@ -1948,16 +1970,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
rtcProvider.shouldShowRoomVisualEffects &&
|
||||
rtcProvider.isVoiceRoomRouteVisible;
|
||||
if (isCurrentVisibleRoom) {
|
||||
addMsg(
|
||||
_buildRoomRocketLaunchNoticeMsg(
|
||||
_firstNonBlank([
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
||||
launch.roomAccount,
|
||||
]),
|
||||
launch,
|
||||
),
|
||||
_handleCurrentVisibleRoomRocketLaunch(
|
||||
rtcProvider: rtcProvider,
|
||||
launch: launch,
|
||||
);
|
||||
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1970,7 +1986,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
|
||||
void _handleVoiceRoomRocketRewardPopup(dynamic payload) {
|
||||
final data = _broadcastPayloadMap(payload);
|
||||
final roomId = _payloadText(data['roomId']);
|
||||
final roomId = _payloadRoomId(data);
|
||||
final currentRoomId = _currentVoiceRoomId();
|
||||
final resolvedRoomId = roomId.isEmpty ? currentRoomId : roomId;
|
||||
if (resolvedRoomId.isEmpty ||
|
||||
@ -1981,13 +1997,45 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
final userIds = _payloadStringList(data['userIds']);
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? '';
|
||||
if (currentUserId.isEmpty || !userIds.contains(currentUserId)) {
|
||||
return;
|
||||
final initialRoomRecords = _roomRocketRewardRecordsFromPayload(data);
|
||||
unawaited(
|
||||
_showRoomRocketRewardPopupDialog(
|
||||
resolvedRoomId,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleCurrentVisibleRoomRocketLaunch({
|
||||
required RealTimeCommunicationManager rtcProvider,
|
||||
required RoomRocketLaunchBroadcastMessage launch,
|
||||
}) {
|
||||
final launchKey = _roomRocketLaunchNoticeKey(launch);
|
||||
final looseLaunchKey = _roomRocketLaunchLooseKey(launch);
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
_pruneRecentRoomRocketLaunches(now);
|
||||
final alreadyHandled =
|
||||
(launchKey.isNotEmpty &&
|
||||
_recentRoomRocketLaunchTimes.containsKey(launchKey)) ||
|
||||
(looseLaunchKey.isNotEmpty &&
|
||||
_recentRoomRocketLaunchLooseTimes.containsKey(looseLaunchKey));
|
||||
if (!alreadyHandled && launchKey.isNotEmpty) {
|
||||
_recentRoomRocketLaunchTimes[launchKey] = now;
|
||||
if (looseLaunchKey.isNotEmpty) {
|
||||
_recentRoomRocketLaunchLooseTimes[looseLaunchKey] = now;
|
||||
}
|
||||
addMsg(
|
||||
_buildRoomRocketLaunchNoticeMsg(
|
||||
_firstNonBlank([
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
||||
launch.roomAccount,
|
||||
]),
|
||||
launch,
|
||||
),
|
||||
);
|
||||
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
|
||||
}
|
||||
unawaited(_showRoomRocketRewardPopupDialog(resolvedRoomId));
|
||||
unawaited(_showRoomRocketRewardPopupDialog(launch.roomId));
|
||||
}
|
||||
|
||||
String _rocketIconUrlForLevel(int level) {
|
||||
@ -2008,16 +2056,29 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return '';
|
||||
}
|
||||
|
||||
Future<void> _showRoomRocketRewardPopupDialog(String roomId) async {
|
||||
Future<void> _showRoomRocketRewardPopupDialog(
|
||||
String roomId, {
|
||||
int attempt = 0,
|
||||
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
}) async {
|
||||
try {
|
||||
final res = await SCChatRoomRepository().roomRocketRewardPopups(roomId);
|
||||
if (res.records.isEmpty) {
|
||||
if (res.records.isEmpty && initialRoomRecords.isEmpty) {
|
||||
_scheduleRoomRocketRewardPopupRetry(
|
||||
roomId,
|
||||
attempt,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
);
|
||||
return;
|
||||
}
|
||||
_roomRocketRewardPopupRetryTimer?.cancel();
|
||||
_roomRocketRewardPopupRetryTimer = null;
|
||||
final recordIds = res.records
|
||||
.map((item) => item.id)
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
final userProfileResolver = _roomRocketRewardUserProfileResolver();
|
||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
||||
SmartDialog.show(
|
||||
tag: _roomRocketRewardDialogTag,
|
||||
@ -2030,6 +2091,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
(_) => RoomRocketRewardDialogLoader(
|
||||
roomId: roomId,
|
||||
initialPopupRecords: res.records,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
userProfileResolver: userProfileResolver,
|
||||
onClose: () {
|
||||
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
|
||||
if (recordIds.isNotEmpty) {
|
||||
@ -2040,7 +2103,83 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
} catch (_) {
|
||||
_scheduleRoomRocketRewardPopupRetry(
|
||||
roomId,
|
||||
attempt,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleRoomRocketRewardPopupRetry(
|
||||
String roomId,
|
||||
int attempt, {
|
||||
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
}) {
|
||||
if (attempt >= _roomRocketRewardPopupRetryDelays.length ||
|
||||
!_isCurrentVoiceRoom(roomId)) {
|
||||
return;
|
||||
}
|
||||
_roomRocketRewardPopupRetryTimer?.cancel();
|
||||
_roomRocketRewardPopupRetryTimer = Timer(
|
||||
_roomRocketRewardPopupRetryDelays[attempt],
|
||||
() {
|
||||
_roomRocketRewardPopupRetryTimer = null;
|
||||
unawaited(
|
||||
_showRoomRocketRewardPopupDialog(
|
||||
roomId,
|
||||
attempt: attempt + 1,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
RoomRocketRewardUserProfileResolver? _roomRocketRewardUserProfileResolver() {
|
||||
final currentContext = context;
|
||||
if (currentContext == null || !currentContext.mounted) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||
currentContext,
|
||||
listen: false,
|
||||
);
|
||||
return rtcProvider.roomRocketRewardUserProfileForRecord;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<SCRoomRocketRewardRecordRes> _roomRocketRewardRecordsFromPayload(
|
||||
Map<String, dynamic> payload,
|
||||
) {
|
||||
final candidates = [
|
||||
payload['roomRecords'],
|
||||
payload['roomRewardRecords'],
|
||||
payload['room_reward_records'],
|
||||
payload['allRecords'],
|
||||
payload['allRewardRecords'],
|
||||
payload['all_reward_records'],
|
||||
payload['rewardRecords'],
|
||||
payload['reward_records'],
|
||||
payload['rewardRecordList'],
|
||||
payload['recordList'],
|
||||
payload['records'],
|
||||
payload['list'],
|
||||
payload['items'],
|
||||
payload['rows'],
|
||||
];
|
||||
for (final candidate in candidates) {
|
||||
final records = SCRoomRocketRewardRecordRes.listFromJson(candidate);
|
||||
if (records.isNotEmpty) {
|
||||
return records;
|
||||
}
|
||||
}
|
||||
return const <SCRoomRocketRewardRecordRes>[];
|
||||
}
|
||||
|
||||
SCFloatingMessage _buildRoomRocketLaunchFloatingMessage(
|
||||
@ -2095,47 +2234,87 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
void _publishRoomRocketLaunchNoticeFromStatusPayload({
|
||||
String _roomRocketLaunchLooseKey(RoomRocketLaunchBroadcastMessage launch) {
|
||||
if (launch.roomId.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
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 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 roomProfile = rtcProvider.currenRoom?.roomProfile?.roomProfile;
|
||||
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']),
|
||||
),
|
||||
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']),
|
||||
]),
|
||||
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']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -2160,7 +2339,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
double _roomRocketPayloadPercent(Map<String, dynamic> payload) {
|
||||
final value = _payloadNum(
|
||||
payload['displayPercent'] ??
|
||||
payload['display_percent'] ??
|
||||
payload['energyPercent'] ??
|
||||
payload['energy_percent'] ??
|
||||
payload['percent'],
|
||||
);
|
||||
if (value != null) {
|
||||
@ -2168,9 +2349,14 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final percent = raw > 0 && raw <= 1 ? raw * 100 : raw;
|
||||
return percent.clamp(0, 100).toDouble();
|
||||
}
|
||||
final currentEnergy = _payloadNum(payload['currentEnergy']);
|
||||
final currentEnergy = _payloadNum(
|
||||
payload['currentEnergy'] ?? payload['current_energy'],
|
||||
);
|
||||
final maxEnergy = _payloadNum(
|
||||
payload['maxEnergy'] ?? payload['needEnergy'],
|
||||
payload['maxEnergy'] ??
|
||||
payload['max_energy'] ??
|
||||
payload['needEnergy'] ??
|
||||
payload['need_energy'],
|
||||
);
|
||||
if (currentEnergy == null || maxEnergy == null || maxEnergy <= 0) {
|
||||
return 0;
|
||||
@ -2181,8 +2367,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
int _roomRocketPayloadLevel(Map<String, dynamic> payload, int fallback) {
|
||||
final value = _payloadNum(
|
||||
payload['currentLevel'] ??
|
||||
payload['current_level'] ??
|
||||
payload['level'] ??
|
||||
payload['rocketLevel'] ??
|
||||
payload['rocket_level'] ??
|
||||
payload['from_level'] ??
|
||||
payload['fromLevel'],
|
||||
);
|
||||
return (value?.toInt() ?? fallback).clamp(1, 99).toInt();
|
||||
@ -2192,8 +2381,12 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final seconds =
|
||||
_payloadNum(
|
||||
payload['countdownSeconds'] ??
|
||||
payload['countdown_seconds'] ??
|
||||
payload['launchDelaySeconds'] ??
|
||||
payload['launch_delay_seconds'] ??
|
||||
payload['delaySeconds'] ??
|
||||
payload['delay_seconds'] ??
|
||||
payload['duration_seconds'] ??
|
||||
payload['durationSeconds'],
|
||||
)?.toInt() ??
|
||||
10;
|
||||
@ -2206,6 +2399,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
String _rocketIconFromPayload(Map<String, dynamic> payload) {
|
||||
return _firstNonBlank([
|
||||
_payloadAssetText(payload['rocketIconUrl']),
|
||||
_payloadAssetText(payload['rocket_icon_url']),
|
||||
_payloadAssetText(payload['rocketIcon']),
|
||||
_payloadAssetText(payload['rocketUrl']),
|
||||
_payloadAssetText(payload['rocketImageUrl']),
|
||||
@ -2213,6 +2407,49 @@ 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']),
|
||||
_payloadText(payload['room_id']),
|
||||
_payloadText(payload['roomID']),
|
||||
_payloadText(payload['voiceRoomId']),
|
||||
_payloadText(payload['voice_room_id']),
|
||||
_payloadText(payload['chatRoomId']),
|
||||
_payloadText(payload['chat_room_id']),
|
||||
_payloadText(_broadcastPayloadMap(payload['room'])['id']),
|
||||
_payloadText(_broadcastPayloadMap(payload['room'])['roomId']),
|
||||
_payloadText(_broadcastPayloadMap(payload['room'])['room_id']),
|
||||
_payloadText(_broadcastPayloadMap(payload['roomInfo'])['id']),
|
||||
_payloadText(_broadcastPayloadMap(payload['roomInfo'])['roomId']),
|
||||
_payloadText(_broadcastPayloadMap(payload['roomInfo'])['room_id']),
|
||||
]);
|
||||
}
|
||||
|
||||
void _pruneRecentRoomRocketLaunches(int now) {
|
||||
_recentRoomRocketLaunchTimes.removeWhere(
|
||||
(_, timestamp) => now - timestamp > _roomRocketLaunchRecentTtlMs,
|
||||
);
|
||||
_recentRoomRocketLaunchLooseTimes.removeWhere(
|
||||
(_, timestamp) => now - timestamp > _roomRocketLaunchLooseRecentTtlMs,
|
||||
);
|
||||
}
|
||||
|
||||
String _roomRocketLaunchNoticeKeyForValues({
|
||||
required String roomId,
|
||||
required int level,
|
||||
@ -2398,37 +2635,35 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
} else if (type == "ROCKET_ENERGY_LAUNCH") {
|
||||
///火箭触发飘屏
|
||||
var fdata = data["data"];
|
||||
final launchRoomId = _payloadText(fdata["roomId"]);
|
||||
final payload = _broadcastPayloadMap(fdata);
|
||||
final launchRoomId = _payloadRoomId(payload);
|
||||
if (_isCurrentHiddenVoiceRoom(launchRoomId)) {
|
||||
return;
|
||||
}
|
||||
final rocketIconUrl = _firstNonBlank([
|
||||
_payloadText(fdata["rocketIconUrl"]),
|
||||
_payloadText(fdata["rocketIcon"]),
|
||||
_payloadText(fdata["rocketUrl"]),
|
||||
_payloadText(fdata["rocketImageUrl"]),
|
||||
_payloadText(fdata["giftUrl"]),
|
||||
_payloadText(fdata["giftIcon"]),
|
||||
_rocketIconFromPayload(payload),
|
||||
_payloadAssetText(payload["giftUrl"]),
|
||||
_payloadAssetText(payload["giftIcon"]),
|
||||
]);
|
||||
SCFloatingMessage msg = SCFloatingMessage(
|
||||
type: 3,
|
||||
roomId: launchRoomId,
|
||||
rocketLevel: fdata["fromLevel"],
|
||||
rocketLevel: _roomRocketPayloadLevel(payload, 1),
|
||||
userAvatarUrl: _firstNonBlank([
|
||||
_payloadText(fdata["roomCoverUrl"]),
|
||||
_payloadText(fdata["roomCover"]),
|
||||
_payloadText(fdata["roomAvatar"]),
|
||||
_payloadText(fdata["roomIcon"]),
|
||||
_payloadText(fdata["coverUrl"]),
|
||||
_payloadText(fdata["cover"]),
|
||||
_payloadText(fdata["userAvatar"]),
|
||||
_payloadAssetText(payload["roomCoverUrl"]),
|
||||
_payloadAssetText(payload["roomCover"]),
|
||||
_payloadAssetText(payload["roomAvatar"]),
|
||||
_payloadAssetText(payload["roomIcon"]),
|
||||
_payloadAssetText(payload["coverUrl"]),
|
||||
_payloadAssetText(payload["cover"]),
|
||||
_payloadAssetText(payload["userAvatar"]),
|
||||
]),
|
||||
userName: _firstNonBlank([
|
||||
_payloadText(fdata["roomName"]),
|
||||
_payloadText(fdata["roomAccount"]),
|
||||
_payloadText(fdata["nickname"]),
|
||||
_payloadText(payload["roomName"]),
|
||||
_payloadText(payload["roomAccount"]),
|
||||
_payloadText(payload["nickname"]),
|
||||
]),
|
||||
userId: fdata["actualAccount"],
|
||||
userId: _payloadText(payload["actualAccount"]),
|
||||
giftUrl: rocketIconUrl,
|
||||
fallbackUrl: rocketIconUrl,
|
||||
priority: 1000,
|
||||
@ -3167,6 +3402,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
roomGiftMsgList.clear();
|
||||
roomChatMsgList.clear();
|
||||
_resetLuckGiftBurstQueue();
|
||||
_recentRoomRocketLaunchTimes.clear();
|
||||
_recentRoomRocketLaunchLooseTimes.clear();
|
||||
_roomRocketRewardPopupRetryTimer?.cancel();
|
||||
_roomRocketRewardPopupRetryTimer = null;
|
||||
onNewMessageListenerGroupMap.forEach((k, v) {
|
||||
v = null;
|
||||
});
|
||||
|
||||
@ -604,17 +604,41 @@ class SCRoomRocketKingUserRes {
|
||||
|
||||
factory SCRoomRocketKingUserRes.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
final profile = _asMap(data['userProfile'] ?? data['user']);
|
||||
final profile = _asMap(
|
||||
data['userProfile'] ??
|
||||
data['user'] ??
|
||||
data['userInfo'] ??
|
||||
data['profile'],
|
||||
);
|
||||
return SCRoomRocketKingUserRes(
|
||||
rank: _intValue(data['rank'] ?? data['ranking']),
|
||||
userId: _stringValue(data['userId'] ?? profile['id']),
|
||||
userAvatar: _stringValue(
|
||||
data['userAvatar'] ??
|
||||
data['avatar'] ??
|
||||
data['avatarUrl'] ??
|
||||
profile['userAvatar'] ??
|
||||
profile['avatar'],
|
||||
),
|
||||
userAvatar: _firstAssetString([
|
||||
data['userAvatar'],
|
||||
data['userAvatarUrl'],
|
||||
data['avatar'],
|
||||
data['avatarUrl'],
|
||||
data['headImg'],
|
||||
data['headUrl'],
|
||||
data['photo'],
|
||||
data['portrait'],
|
||||
data['profileImageUrl'],
|
||||
data['profilePicture'],
|
||||
data['avatarResource'],
|
||||
data['userAvatarResource'],
|
||||
profile['userAvatar'],
|
||||
profile['userAvatarUrl'],
|
||||
profile['avatar'],
|
||||
profile['avatarUrl'],
|
||||
profile['headImg'],
|
||||
profile['headUrl'],
|
||||
profile['photo'],
|
||||
profile['portrait'],
|
||||
profile['profileImageUrl'],
|
||||
profile['profilePicture'],
|
||||
profile['avatarResource'],
|
||||
profile['userAvatarResource'],
|
||||
]),
|
||||
userNickname: _stringValue(
|
||||
data['userNickname'] ??
|
||||
data['nickname'] ??
|
||||
@ -771,7 +795,7 @@ class SCRoomRocketRewardPopupRes {
|
||||
factory SCRoomRocketRewardPopupRes.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
return SCRoomRocketRewardPopupRes(
|
||||
records: SCRoomRocketRewardRecordRes.listFromJson(data['records']),
|
||||
records: _rewardRecordList(data),
|
||||
cursor: _intValue(data['cursor'], fallback: 1),
|
||||
limit: _intValue(data['limit'], fallback: 10),
|
||||
emptyReason: _stringValue(data['emptyReason']),
|
||||
@ -804,7 +828,7 @@ class SCRoomRocketRewardRecordsRes {
|
||||
factory SCRoomRocketRewardRecordsRes.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
return SCRoomRocketRewardRecordsRes(
|
||||
records: SCRoomRocketRewardRecordRes.listFromJson(data['records']),
|
||||
records: _rewardRecordList(data),
|
||||
cursor: _intValue(data['cursor'], fallback: 1),
|
||||
limit: _intValue(data['limit'], fallback: 10),
|
||||
emptyReason: _stringValue(data['emptyReason']),
|
||||
@ -855,39 +879,222 @@ class SCRoomRocketRewardRecordRes {
|
||||
|
||||
factory SCRoomRocketRewardRecordRes.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
final user = _asMap(data['user']);
|
||||
final userInfo = _asMap(data['userInfo'] ?? data['user_info']);
|
||||
final receiver = _asMap(data['receiver']);
|
||||
final receiveUser = _asMap(data['receiveUser'] ?? data['receive_user']);
|
||||
final acceptUser = _asMap(data['acceptUser'] ?? data['accept_user']);
|
||||
final recipient = _asMap(
|
||||
data['recipient'] ?? data['targetUser'] ?? data['target_user'],
|
||||
);
|
||||
final winner = _asMap(
|
||||
data['winner'] ?? data['winnerUser'] ?? data['winner_user'],
|
||||
);
|
||||
final profile = _asMap(
|
||||
_firstPayload([
|
||||
data['userProfile'],
|
||||
data['user_profile'],
|
||||
user['userProfile'],
|
||||
user['user_profile'],
|
||||
user['profile'],
|
||||
userInfo['userProfile'],
|
||||
userInfo['user_profile'],
|
||||
userInfo['profile'],
|
||||
receiver['userProfile'],
|
||||
receiver['user_profile'],
|
||||
receiver['profile'],
|
||||
receiveUser['userProfile'],
|
||||
receiveUser['user_profile'],
|
||||
receiveUser['profile'],
|
||||
acceptUser['userProfile'],
|
||||
acceptUser['user_profile'],
|
||||
acceptUser['profile'],
|
||||
recipient['userProfile'],
|
||||
recipient['user_profile'],
|
||||
recipient['profile'],
|
||||
winner['userProfile'],
|
||||
winner['user_profile'],
|
||||
winner['profile'],
|
||||
userInfo,
|
||||
data['profile'],
|
||||
user,
|
||||
receiver,
|
||||
receiveUser,
|
||||
acceptUser,
|
||||
recipient,
|
||||
winner,
|
||||
]),
|
||||
);
|
||||
return SCRoomRocketRewardRecordRes(
|
||||
id: _stringValue(data['id']),
|
||||
launchNo: _stringValue(data['launchNo']),
|
||||
roomId: _stringValue(data['roomId']),
|
||||
dayKey: _stringValue(data['dayKey']),
|
||||
roundNo: _intValue(data['roundNo']),
|
||||
launchNo: _stringValue(data['launchNo'] ?? data['launch_no']),
|
||||
roomId: _stringValue(data['roomId'] ?? data['room_id']),
|
||||
dayKey: _stringValue(data['dayKey'] ?? data['day_key']),
|
||||
roundNo: _intValue(data['roundNo'] ?? data['round_no']),
|
||||
level: _intValue(data['level']),
|
||||
userId: _stringValue(data['userId']),
|
||||
userAvatar: _stringValue(
|
||||
data['userAvatar'] ??
|
||||
data['avatar'] ??
|
||||
data['avatarUrl'] ??
|
||||
_asMap(data['userProfile'] ?? data['user'])['userAvatar'] ??
|
||||
_asMap(data['userProfile'] ?? data['user'])['avatar'],
|
||||
userId: _stringValue(
|
||||
_firstPayload([
|
||||
data['userId'],
|
||||
data['user_id'],
|
||||
data['uid'],
|
||||
data['receiverUserId'],
|
||||
data['receiveUserId'],
|
||||
data['acceptUserId'],
|
||||
data['recipientUserId'],
|
||||
data['targetUserId'],
|
||||
data['winnerUserId'],
|
||||
data['receiver_user_id'],
|
||||
data['receive_user_id'],
|
||||
data['accept_user_id'],
|
||||
data['recipient_user_id'],
|
||||
data['target_user_id'],
|
||||
data['winner_user_id'],
|
||||
profile['id'],
|
||||
profile['userId'],
|
||||
profile['user_id'],
|
||||
profile['uid'],
|
||||
]),
|
||||
),
|
||||
userAvatar: _firstAssetString([
|
||||
data['userAvatar'],
|
||||
data['user_avatar'],
|
||||
data['userAvatarUrl'],
|
||||
data['user_avatar_url'],
|
||||
data['receiverAvatar'],
|
||||
data['receiver_avatar'],
|
||||
data['receiverUserAvatar'],
|
||||
data['receiver_user_avatar'],
|
||||
data['receiveUserAvatar'],
|
||||
data['receive_user_avatar'],
|
||||
data['acceptUserAvatar'],
|
||||
data['accept_user_avatar'],
|
||||
data['recipientAvatar'],
|
||||
data['recipient_avatar'],
|
||||
data['targetUserAvatar'],
|
||||
data['target_user_avatar'],
|
||||
data['winnerAvatar'],
|
||||
data['winner_avatar'],
|
||||
data['avatar'],
|
||||
data['avatarUrl'],
|
||||
data['avatar_url'],
|
||||
data['headImg'],
|
||||
data['head_img'],
|
||||
data['headUrl'],
|
||||
data['head_url'],
|
||||
data['headIcon'],
|
||||
data['head_icon'],
|
||||
data['photo'],
|
||||
data['photoUrl'],
|
||||
data['photo_url'],
|
||||
data['portrait'],
|
||||
data['profileImageUrl'],
|
||||
data['profile_image_url'],
|
||||
data['profilePicture'],
|
||||
data['profile_picture'],
|
||||
data['avatarResource'],
|
||||
data['avatar_resource'],
|
||||
data['userAvatarResource'],
|
||||
data['user_avatar_resource'],
|
||||
profile['userAvatar'],
|
||||
profile['user_avatar'],
|
||||
profile['userAvatarUrl'],
|
||||
profile['user_avatar_url'],
|
||||
profile['avatar'],
|
||||
profile['avatarUrl'],
|
||||
profile['avatar_url'],
|
||||
profile['headImg'],
|
||||
profile['head_img'],
|
||||
profile['headUrl'],
|
||||
profile['head_url'],
|
||||
profile['headIcon'],
|
||||
profile['head_icon'],
|
||||
profile['photo'],
|
||||
profile['photoUrl'],
|
||||
profile['photo_url'],
|
||||
profile['portrait'],
|
||||
profile['profileImageUrl'],
|
||||
profile['profile_image_url'],
|
||||
profile['profilePicture'],
|
||||
profile['profile_picture'],
|
||||
profile['avatarResource'],
|
||||
profile['avatar_resource'],
|
||||
profile['userAvatarResource'],
|
||||
profile['user_avatar_resource'],
|
||||
profile['imageUrl'],
|
||||
profile['image_url'],
|
||||
profile['image'],
|
||||
profile['iconUrl'],
|
||||
profile['icon_url'],
|
||||
profile['icon'],
|
||||
]),
|
||||
userNickname: _stringValue(
|
||||
data['userNickname'] ??
|
||||
data['user_nickname'] ??
|
||||
data['userName'] ??
|
||||
data['user_name'] ??
|
||||
data['receiverNickname'] ??
|
||||
data['receiver_nickname'] ??
|
||||
data['receiveNickname'] ??
|
||||
data['receive_nickname'] ??
|
||||
data['acceptNickname'] ??
|
||||
data['accept_nickname'] ??
|
||||
data['recipientNickname'] ??
|
||||
data['recipient_nickname'] ??
|
||||
data['targetNickname'] ??
|
||||
data['target_nickname'] ??
|
||||
data['winnerNickname'] ??
|
||||
data['winner_nickname'] ??
|
||||
data['nickname'] ??
|
||||
data['nickName'] ??
|
||||
_asMap(data['userProfile'] ?? data['user'])['userNickname'] ??
|
||||
_asMap(data['userProfile'] ?? data['user'])['nickname'] ??
|
||||
_asMap(data['userProfile'] ?? data['user'])['nickName'],
|
||||
data['nick_name'] ??
|
||||
data['nick'] ??
|
||||
data['displayName'] ??
|
||||
data['display_name'] ??
|
||||
data['name'] ??
|
||||
profile['userNickname'] ??
|
||||
profile['user_nickname'] ??
|
||||
profile['userName'] ??
|
||||
profile['user_name'] ??
|
||||
profile['nickname'] ??
|
||||
profile['nickName'] ??
|
||||
profile['nick_name'] ??
|
||||
profile['nick'] ??
|
||||
profile['displayName'] ??
|
||||
profile['display_name'] ??
|
||||
profile['name'],
|
||||
),
|
||||
account: _stringValue(
|
||||
data['account'] ??
|
||||
_asMap(data['userProfile'] ?? data['user'])['account'],
|
||||
_firstPayload([
|
||||
data['account'],
|
||||
data['actualAccount'],
|
||||
data['receiverAccount'],
|
||||
data['receiveAccount'],
|
||||
data['acceptAccount'],
|
||||
data['recipientAccount'],
|
||||
data['targetAccount'],
|
||||
data['winnerAccount'],
|
||||
data['actual_account'],
|
||||
data['receiver_account'],
|
||||
data['receive_account'],
|
||||
data['accept_account'],
|
||||
data['recipient_account'],
|
||||
data['target_account'],
|
||||
data['winner_account'],
|
||||
profile['account'],
|
||||
profile['actualAccount'],
|
||||
profile['actual_account'],
|
||||
]),
|
||||
),
|
||||
rewardScene: _stringValue(data['rewardScene']),
|
||||
rewardType: _stringValue(data['rewardType']),
|
||||
rewardItemId: _stringValue(data['rewardItemId']),
|
||||
rewardName: _stringValue(data['rewardName']),
|
||||
rewardCover: _stringValue(data['rewardCover']),
|
||||
rewardAmount: _rewardAmountValue(data['rewardAmount']) ?? 0,
|
||||
rewardScene: _stringValue(data['rewardScene'] ?? data['reward_scene']),
|
||||
rewardType: _stringValue(data['rewardType'] ?? data['reward_type']),
|
||||
rewardItemId: _stringValue(
|
||||
data['rewardItemId'] ?? data['reward_item_id'],
|
||||
),
|
||||
rewardName: _stringValue(data['rewardName'] ?? data['reward_name']),
|
||||
rewardCover: _stringValue(data['rewardCover'] ?? data['reward_cover']),
|
||||
rewardAmount:
|
||||
_rewardAmountValue(data['rewardAmount'] ?? data['reward_amount']) ??
|
||||
0,
|
||||
rocketIconUrl: _firstAssetString([
|
||||
data['rocket_icon_url'],
|
||||
data['rocketIconUrl'],
|
||||
@ -903,12 +1110,12 @@ class SCRoomRocketRewardRecordRes {
|
||||
_asMap(data['config'])['rocketIconUrl'],
|
||||
_asMap(data['config'])['rocketIcon'],
|
||||
]),
|
||||
expireDays: _dayValue(data['expireDays']),
|
||||
grantStatus: _stringValue(data['grantStatus']),
|
||||
popupStatus: _stringValue(data['popupStatus']),
|
||||
grantTime: _stringValue(data['grantTime']),
|
||||
createTime: _stringValue(data['createTime']),
|
||||
errorMessage: _stringValue(data['errorMessage']),
|
||||
expireDays: _dayValue(data['expireDays'] ?? data['expire_days']),
|
||||
grantStatus: _stringValue(data['grantStatus'] ?? data['grant_status']),
|
||||
popupStatus: _stringValue(data['popupStatus'] ?? data['popup_status']),
|
||||
grantTime: _stringValue(data['grantTime'] ?? data['grant_time']),
|
||||
createTime: _stringValue(data['createTime'] ?? data['create_time']),
|
||||
errorMessage: _stringValue(data['errorMessage'] ?? data['error_message']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -943,10 +1150,7 @@ class SCRoomRocketRewardRecordRes {
|
||||
if (account.trim().isNotEmpty) {
|
||||
return account;
|
||||
}
|
||||
if (userId.trim().isNotEmpty) {
|
||||
return 'ID:$userId';
|
||||
}
|
||||
return 'You';
|
||||
return 'User';
|
||||
}
|
||||
|
||||
String get displayAmount {
|
||||
@ -988,13 +1192,73 @@ class SCRoomRocketRewardRecordRes {
|
||||
}
|
||||
|
||||
static List<SCRoomRocketRewardRecordRes> listFromJson(dynamic value) {
|
||||
if (value is! Iterable) {
|
||||
return const <SCRoomRocketRewardRecordRes>[];
|
||||
}
|
||||
return _rewardRecordList(value);
|
||||
}
|
||||
}
|
||||
|
||||
List<SCRoomRocketRewardRecordRes> _rewardRecordList(dynamic value) {
|
||||
if (value is Iterable && value is! String) {
|
||||
return value
|
||||
.map(SCRoomRocketRewardRecordRes.fromJson)
|
||||
.toList(growable: false);
|
||||
}
|
||||
final data = _asMap(value);
|
||||
if (data.isEmpty) {
|
||||
return const <SCRoomRocketRewardRecordRes>[];
|
||||
}
|
||||
if (_looksLikeRewardRecord(data)) {
|
||||
return [SCRoomRocketRewardRecordRes.fromJson(data)];
|
||||
}
|
||||
final candidates = [
|
||||
data['roomRecords'],
|
||||
data['roomRewardRecords'],
|
||||
data['room_reward_records'],
|
||||
data['allRecords'],
|
||||
data['allRewardRecords'],
|
||||
data['all_reward_records'],
|
||||
data['rewardRecords'],
|
||||
data['reward_records'],
|
||||
data['rewardRecordList'],
|
||||
data['recordList'],
|
||||
data['records'],
|
||||
data['list'],
|
||||
data['items'],
|
||||
data['rows'],
|
||||
data['content'],
|
||||
data['data'],
|
||||
];
|
||||
for (final candidate in candidates) {
|
||||
if (identical(candidate, value)) {
|
||||
continue;
|
||||
}
|
||||
final records = _rewardRecordList(candidate);
|
||||
if (records.isNotEmpty) {
|
||||
return records;
|
||||
}
|
||||
}
|
||||
return const <SCRoomRocketRewardRecordRes>[];
|
||||
}
|
||||
|
||||
bool _looksLikeRewardRecord(Map<String, dynamic> data) {
|
||||
const rewardKeys = [
|
||||
'rewardScene',
|
||||
'reward_scene',
|
||||
'rewardType',
|
||||
'reward_type',
|
||||
'rewardItemId',
|
||||
'reward_item_id',
|
||||
'rewardName',
|
||||
'reward_name',
|
||||
'rewardCover',
|
||||
'reward_cover',
|
||||
'rewardAmount',
|
||||
'reward_amount',
|
||||
'grantStatus',
|
||||
'grant_status',
|
||||
'popupStatus',
|
||||
'popup_status',
|
||||
];
|
||||
return rewardKeys.any(data.containsKey);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _asMap(dynamic value) {
|
||||
@ -1032,14 +1296,32 @@ String _assetString(dynamic value) {
|
||||
final map = _asMap(value);
|
||||
return _firstAssetString([
|
||||
map['sourceUrl'],
|
||||
map['source_url'],
|
||||
map['url'],
|
||||
map['cdnUrl'],
|
||||
map['cdn_url'],
|
||||
map['ossUrl'],
|
||||
map['oss_url'],
|
||||
map['resourceUrl'],
|
||||
map['resource_url'],
|
||||
map['fileUrl'],
|
||||
map['file_url'],
|
||||
map['thumbUrl'],
|
||||
map['thumb_url'],
|
||||
map['thumbnail'],
|
||||
map['small'],
|
||||
map['medium'],
|
||||
map['large'],
|
||||
map['originUrl'],
|
||||
map['origin_url'],
|
||||
map['src'],
|
||||
map['path'],
|
||||
map['cover'],
|
||||
map['imageUrl'],
|
||||
map['image_url'],
|
||||
map['image'],
|
||||
map['svgUrl'],
|
||||
map['svg_url'],
|
||||
map['svg'],
|
||||
map['value'],
|
||||
]);
|
||||
@ -1048,14 +1330,32 @@ String _assetString(dynamic value) {
|
||||
if (map.isNotEmpty) {
|
||||
return _firstAssetString([
|
||||
map['sourceUrl'],
|
||||
map['source_url'],
|
||||
map['url'],
|
||||
map['cdnUrl'],
|
||||
map['cdn_url'],
|
||||
map['ossUrl'],
|
||||
map['oss_url'],
|
||||
map['resourceUrl'],
|
||||
map['resource_url'],
|
||||
map['fileUrl'],
|
||||
map['file_url'],
|
||||
map['thumbUrl'],
|
||||
map['thumb_url'],
|
||||
map['thumbnail'],
|
||||
map['small'],
|
||||
map['medium'],
|
||||
map['large'],
|
||||
map['originUrl'],
|
||||
map['origin_url'],
|
||||
map['src'],
|
||||
map['path'],
|
||||
map['cover'],
|
||||
map['imageUrl'],
|
||||
map['image_url'],
|
||||
map['image'],
|
||||
map['svgUrl'],
|
||||
map['svg_url'],
|
||||
map['svg'],
|
||||
map['value'],
|
||||
]);
|
||||
|
||||
@ -17,12 +17,15 @@ class RoomRocketLaunchBroadcastMessage {
|
||||
factory RoomRocketLaunchBroadcastMessage.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
return RoomRocketLaunchBroadcastMessage(
|
||||
roomId: _stringValue(data['roomId']),
|
||||
roomAccount: _stringValue(data['roomAccount']),
|
||||
roomName: _stringValue(data['roomName']),
|
||||
roomId: _roomId(data),
|
||||
roomAccount: _stringValue(data['roomAccount'] ?? data['room_account']),
|
||||
roomName: _stringValue(data['roomName'] ?? data['room_name']),
|
||||
roomCoverUrl: _roomCoverUrl(data),
|
||||
roundNo: _intValue(data['roundNo']),
|
||||
level: _intValue(data['level'], fallback: 1),
|
||||
roundNo: _intValue(data['roundNo'] ?? data['round_no']),
|
||||
level: _intValue(
|
||||
data['level'] ?? data['rocketLevel'] ?? data['rocket_level'],
|
||||
fallback: 1,
|
||||
),
|
||||
rocketIconUrl: _rocketIconUrl(data),
|
||||
rocketAnimationUrl: _rocketAnimationUrl(data),
|
||||
durationSeconds: _firstIntValue([
|
||||
@ -36,9 +39,13 @@ class RoomRocketLaunchBroadcastMessage {
|
||||
_asMap(data['config'])['countdownSeconds'],
|
||||
_asMap(data['config'])['launchDelaySeconds'],
|
||||
], fallback: 10),
|
||||
triggerUser: RoomRocketLaunchTriggerUser.fromJson(data['triggerUser']),
|
||||
triggerUserId: _stringValue(data['triggerUserId']),
|
||||
launchNo: _stringValue(data['launchNo']),
|
||||
triggerUser: RoomRocketLaunchTriggerUser.fromJson(
|
||||
data['triggerUser'] ?? data['trigger_user'],
|
||||
),
|
||||
triggerUserId: _stringValue(
|
||||
data['triggerUserId'] ?? data['trigger_user_id'],
|
||||
),
|
||||
launchNo: _stringValue(data['launchNo'] ?? data['launch_no']),
|
||||
);
|
||||
}
|
||||
|
||||
@ -88,9 +95,13 @@ class RoomRocketLaunchTriggerUser {
|
||||
factory RoomRocketLaunchTriggerUser.fromJson(dynamic json) {
|
||||
final data = _asMap(json);
|
||||
return RoomRocketLaunchTriggerUser(
|
||||
userId: _stringValue(data['userId']),
|
||||
nickname: _stringValue(data['nickname']),
|
||||
avatar: _stringValue(data['avatar']),
|
||||
userId: _stringValue(data['userId'] ?? data['user_id']),
|
||||
nickname: _stringValue(data['nickname'] ?? data['userNickname']),
|
||||
avatar: _firstAssetString([
|
||||
data['avatar'],
|
||||
data['avatarUrl'],
|
||||
data['userAvatar'],
|
||||
]),
|
||||
account: _stringValue(data['account']),
|
||||
countryCode: _stringValue(data['countryCode']),
|
||||
countryName: _stringValue(data['countryName']),
|
||||
@ -122,13 +133,36 @@ String _stringValue(dynamic value) {
|
||||
return value.toString().trim();
|
||||
}
|
||||
|
||||
String _roomId(Map<String, dynamic> data) {
|
||||
return _firstAssetString([
|
||||
data['roomId'],
|
||||
data['room_id'],
|
||||
data['roomID'],
|
||||
data['voiceRoomId'],
|
||||
data['voice_room_id'],
|
||||
data['chatRoomId'],
|
||||
data['chat_room_id'],
|
||||
_asMap(data['room'])['id'],
|
||||
_asMap(data['room'])['roomId'],
|
||||
_asMap(data['room'])['room_id'],
|
||||
_asMap(data['roomInfo'])['id'],
|
||||
_asMap(data['roomInfo'])['roomId'],
|
||||
_asMap(data['roomInfo'])['room_id'],
|
||||
]);
|
||||
}
|
||||
|
||||
String _roomCoverUrl(Map<String, dynamic> data) {
|
||||
return _firstAssetString([
|
||||
data['roomCoverUrl'],
|
||||
data['room_cover_url'],
|
||||
data['roomCover'],
|
||||
data['room_cover'],
|
||||
data['roomAvatar'],
|
||||
data['room_avatar'],
|
||||
data['roomIcon'],
|
||||
data['room_icon'],
|
||||
data['coverUrl'],
|
||||
data['cover_url'],
|
||||
data['cover'],
|
||||
_asMap(data['room'])['coverUrl'],
|
||||
_asMap(data['room'])['cover'],
|
||||
@ -162,11 +196,17 @@ String _rocketAnimationUrl(Map<String, dynamic> data) {
|
||||
return _firstAssetString([
|
||||
data['rocket_animation_url'],
|
||||
data['rocketAnimationUrl'],
|
||||
data['rocket_animation'],
|
||||
data['rocketSvgaUrl'],
|
||||
data['rocket_svga_url'],
|
||||
data['animationUrl'],
|
||||
data['animation_url'],
|
||||
data['svgaUrl'],
|
||||
data['svga_url'],
|
||||
data['rocketLaunchSvgaUrl'],
|
||||
data['rocket_launch_svga_url'],
|
||||
data['launchAnimationUrl'],
|
||||
data['launch_animation_url'],
|
||||
data['launchAnimation'],
|
||||
data['launchSvgaUrl'],
|
||||
data['rocketAnimation'],
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
||||
|
||||
int? _resolveMemCacheDimension(double? logicalSize) {
|
||||
@ -39,10 +40,12 @@ class CustomCachedImage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final normalizedUrl = normalizeImageResourceUrl(imageUrl);
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
imageUrl: normalizedUrl,
|
||||
httpHeaders: buildNetworkImageHeaders(normalizedUrl),
|
||||
width: width,
|
||||
height: height,
|
||||
memCacheWidth: _resolveMemCacheDimension(width),
|
||||
|
||||
@ -151,16 +151,12 @@ class _FloatingRoomRocketScreenWidgetState
|
||||
duration: Duration(milliseconds: 350),
|
||||
onDebounce: () {
|
||||
final roomId = widget.message.roomId?.trim() ?? '';
|
||||
final currentContext = navigatorKey.currentState?.context;
|
||||
if (roomId.isEmpty ||
|
||||
currentContext == null ||
|
||||
!currentContext.mounted ||
|
||||
SCRoomUtils.isCurrentRoom(roomId, currentContext)) {
|
||||
final currentContext =
|
||||
navigatorKey.currentState?.context ?? context;
|
||||
if (roomId.isEmpty || !currentContext.mounted) {
|
||||
return;
|
||||
}
|
||||
if (roomId.isNotEmpty) {
|
||||
SCRoomUtils.goRoom(roomId, currentContext, fromFloting: true);
|
||||
}
|
||||
SCRoomUtils.goRoom(roomId, currentContext, fromFloting: true);
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -209,7 +205,7 @@ class _FloatingRoomRocketScreenWidgetState
|
||||
Positioned(
|
||||
left: 65.w,
|
||||
top: 9.w,
|
||||
width: 126.w,
|
||||
width: 112.w,
|
||||
child: Text(
|
||||
roomName,
|
||||
maxLines: 1,
|
||||
@ -227,7 +223,7 @@ class _FloatingRoomRocketScreenWidgetState
|
||||
Positioned(
|
||||
left: 65.w,
|
||||
top: 31.w,
|
||||
width: 126.w,
|
||||
width: 112.w,
|
||||
child: Text(
|
||||
SCAppLocalizations.of(context)?.launchRocket ??
|
||||
'Launch Rocket',
|
||||
@ -244,14 +240,14 @@ class _FloatingRoomRocketScreenWidgetState
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 201.w,
|
||||
top: 8.w,
|
||||
width: 40.w,
|
||||
height: 40.w,
|
||||
left: 191.w,
|
||||
top: -2.w,
|
||||
width: 60.w,
|
||||
height: 60.w,
|
||||
child: _RocketIcon(url: rocketIconUrl),
|
||||
),
|
||||
Positioned(
|
||||
left: 249.w,
|
||||
left: 262.w,
|
||||
top: 14.w,
|
||||
width: 42.w,
|
||||
height: 28.w,
|
||||
@ -315,7 +311,7 @@ class _RocketIcon extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
padding: EdgeInsets.all(7.5.w),
|
||||
child:
|
||||
url.trim().isEmpty
|
||||
? Image.asset(
|
||||
@ -324,8 +320,8 @@ class _RocketIcon extends StatelessWidget {
|
||||
)
|
||||
: netImage(
|
||||
url: url,
|
||||
width: 30.w,
|
||||
height: 30.w,
|
||||
width: 45.w,
|
||||
height: 45.w,
|
||||
fit: BoxFit.contain,
|
||||
noDefaultImg: true,
|
||||
errorWidget: Image.asset(
|
||||
|
||||
@ -7,6 +7,23 @@ import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_note_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_record_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog.dart';
|
||||
|
||||
typedef RoomRocketRewardUserProfileResolver =
|
||||
RoomRocketRewardUserProfile? Function(SCRoomRocketRewardRecordRes record);
|
||||
|
||||
class RoomRocketRewardUserProfile {
|
||||
const RoomRocketRewardUserProfile({
|
||||
required this.userId,
|
||||
required this.account,
|
||||
required this.nickname,
|
||||
required this.avatarUrl,
|
||||
});
|
||||
|
||||
final String userId;
|
||||
final String account;
|
||||
final String nickname;
|
||||
final String avatarUrl;
|
||||
}
|
||||
|
||||
class RoomRocketApiMapper {
|
||||
const RoomRocketApiMapper._();
|
||||
|
||||
@ -257,25 +274,93 @@ class RoomRocketApiMapper {
|
||||
}
|
||||
|
||||
static List<RoomRocketRewardWinner> winnersFromRecords(
|
||||
List<SCRoomRocketRewardRecordRes> records,
|
||||
) {
|
||||
List<SCRoomRocketRewardRecordRes> records, {
|
||||
RoomRocketRewardUserProfileResolver? userProfileResolver,
|
||||
}) {
|
||||
final grouped = <String, List<SCRoomRocketRewardRecordRes>>{};
|
||||
for (final record in records) {
|
||||
final key = record.userId.isNotEmpty ? record.userId : record.launchNo;
|
||||
final key = _rewardWinnerKey(record);
|
||||
grouped.putIfAbsent(key, () => []).add(record);
|
||||
}
|
||||
return grouped.entries
|
||||
.map((entry) {
|
||||
final first = entry.value.first;
|
||||
final profile = userProfileResolver?.call(first);
|
||||
return RoomRocketRewardWinner(
|
||||
nickname: first.displayName,
|
||||
avatarUrl: first.userAvatar.isEmpty ? null : first.userAvatar,
|
||||
nickname: _rewardWinnerName(entry.value, profile),
|
||||
avatarUrl: _rewardWinnerAvatar(entry.value, profile),
|
||||
rewards: rewardItemsFromRecords(entry.value),
|
||||
);
|
||||
})
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
static String _rewardWinnerName(
|
||||
List<SCRoomRocketRewardRecordRes> records,
|
||||
RoomRocketRewardUserProfile? profile,
|
||||
) {
|
||||
final profileName = profile?.nickname.trim() ?? '';
|
||||
if (profileName.isNotEmpty) {
|
||||
return profileName;
|
||||
}
|
||||
for (final record in records) {
|
||||
final nickname = record.userNickname.trim();
|
||||
if (nickname.isNotEmpty) {
|
||||
return nickname;
|
||||
}
|
||||
}
|
||||
for (final record in records) {
|
||||
final account = record.account.trim();
|
||||
if (account.isNotEmpty) {
|
||||
return account;
|
||||
}
|
||||
}
|
||||
return records.isEmpty ? 'User' : records.first.displayName;
|
||||
}
|
||||
|
||||
static String? _rewardWinnerAvatar(
|
||||
List<SCRoomRocketRewardRecordRes> records,
|
||||
RoomRocketRewardUserProfile? profile,
|
||||
) {
|
||||
final profileAvatar = profile?.avatarUrl.trim() ?? '';
|
||||
if (profileAvatar.isNotEmpty) {
|
||||
return profileAvatar;
|
||||
}
|
||||
for (final record in records) {
|
||||
final avatar = record.userAvatar.trim();
|
||||
if (avatar.isNotEmpty) {
|
||||
return avatar;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String _rewardWinnerKey(SCRoomRocketRewardRecordRes record) {
|
||||
final userId = record.userId.trim();
|
||||
if (userId.isNotEmpty) {
|
||||
return 'user:$userId';
|
||||
}
|
||||
final account = record.account.trim();
|
||||
if (account.isNotEmpty) {
|
||||
return 'account:$account';
|
||||
}
|
||||
final profileKey = [
|
||||
record.userNickname.trim(),
|
||||
record.userAvatar.trim(),
|
||||
].where((item) => item.isNotEmpty).join('|');
|
||||
if (profileKey.isNotEmpty) {
|
||||
return 'profile:$profileKey';
|
||||
}
|
||||
final recordKey = [
|
||||
record.launchNo.trim(),
|
||||
record.id.trim(),
|
||||
].where((item) => item.isNotEmpty).join('|');
|
||||
if (recordKey.isNotEmpty) {
|
||||
return 'record:$recordKey';
|
||||
}
|
||||
return 'record:${identityHashCode(record)}';
|
||||
}
|
||||
|
||||
static List<RoomRocketRecordItem> recordItemsFromRecords(
|
||||
List<SCRoomRocketRewardRecordRes> records,
|
||||
) {
|
||||
|
||||
@ -16,12 +16,16 @@ class RoomRocketRewardDialogLoader extends StatefulWidget {
|
||||
required this.roomId,
|
||||
this.selectedReward,
|
||||
this.initialPopupRecords = const <SCRoomRocketRewardRecordRes>[],
|
||||
this.initialRoomRecords = const <SCRoomRocketRewardRecordRes>[],
|
||||
this.userProfileResolver,
|
||||
this.onClose,
|
||||
});
|
||||
|
||||
final String roomId;
|
||||
final RoomRocketRewardItem? selectedReward;
|
||||
final List<SCRoomRocketRewardRecordRes> initialPopupRecords;
|
||||
final List<SCRoomRocketRewardRecordRes> initialRoomRecords;
|
||||
final RoomRocketRewardUserProfileResolver? userProfileResolver;
|
||||
final VoidCallback? onClose;
|
||||
|
||||
@override
|
||||
@ -47,8 +51,14 @@ class _RoomRocketRewardDialogLoaderState
|
||||
if (widget.initialPopupRecords.isNotEmpty) {
|
||||
_popupRecords.addAll(widget.initialPopupRecords);
|
||||
_rewards = RoomRocketApiMapper.rewardItemsFromRecords(_popupRecords);
|
||||
_winners = RoomRocketApiMapper.winnersFromRecords(_popupRecords);
|
||||
}
|
||||
if (widget.initialRoomRecords.isNotEmpty) {
|
||||
_roomRecords.addAll(widget.initialRoomRecords);
|
||||
}
|
||||
_winners = RoomRocketApiMapper.winnersFromRecords(
|
||||
_visibleRoomRewardRecords(),
|
||||
userProfileResolver: widget.userProfileResolver,
|
||||
);
|
||||
unawaited(_load(reset: true));
|
||||
}
|
||||
|
||||
@ -89,13 +99,25 @@ class _RoomRocketRewardDialogLoaderState
|
||||
..addAll(nextPopupRecords);
|
||||
_roomRecords
|
||||
..clear()
|
||||
..addAll(nextRoomRecords);
|
||||
..addAll(
|
||||
_mergeRewardRecords([
|
||||
...widget.initialRoomRecords,
|
||||
...nextRoomRecords,
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
_roomRecords.addAll(nextRoomRecords);
|
||||
final mergedRoomRecords = _mergeRewardRecords([
|
||||
..._roomRecords,
|
||||
...nextRoomRecords,
|
||||
]);
|
||||
_roomRecords
|
||||
..clear()
|
||||
..addAll(mergedRoomRecords);
|
||||
}
|
||||
_rewards = RoomRocketApiMapper.rewardItemsFromRecords(_popupRecords);
|
||||
_winners = RoomRocketApiMapper.winnersFromRecords(
|
||||
_visibleRoomRewardRecords(),
|
||||
userProfileResolver: widget.userProfileResolver,
|
||||
);
|
||||
_hasMore = nextRoomRecords.length >= _rewardDialogPageSize;
|
||||
if (_hasMore) {
|
||||
@ -108,6 +130,7 @@ class _RoomRocketRewardDialogLoaderState
|
||||
'loaded cursor=$requestCursor '
|
||||
'popupCount=${nextPopupRecords.length} '
|
||||
'popupTotal=${_popupRecords.length} '
|
||||
'initialRoomCount=${widget.initialRoomRecords.length} '
|
||||
'roomCount=${nextRoomRecords.length} '
|
||||
'roomTotal=${_roomRecords.length} '
|
||||
'visibleRoomTotal=${_visibleRoomRewardRecords().length} '
|
||||
@ -181,6 +204,20 @@ class _RoomRocketRewardDialogLoaderState
|
||||
return matched.isEmpty ? _roomRecords : matched;
|
||||
}
|
||||
|
||||
List<SCRoomRocketRewardRecordRes> _mergeRewardRecords(
|
||||
List<SCRoomRocketRewardRecordRes> records,
|
||||
) {
|
||||
final result = <SCRoomRocketRewardRecordRes>[];
|
||||
final seen = <String>{};
|
||||
for (final record in records) {
|
||||
final id = record.id.trim();
|
||||
if (id.isEmpty || seen.add(id)) {
|
||||
result.add(record);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoomRocketRewardDialog(
|
||||
|
||||
@ -62,16 +62,17 @@ void main() {
|
||||
|
||||
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': {
|
||||
'id': 'room-1',
|
||||
'cover': {'url': 'https://cdn.example.com/room/cover.png'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(launch.roomId, 'room-1');
|
||||
expect(
|
||||
launch.rocketAnimationUrl,
|
||||
'https://cdn.example.com/rocket/launch.pag',
|
||||
@ -79,4 +80,36 @@ void main() {
|
||||
expect(launch.rocketIconUrl, 'https://cdn.example.com/rocket/icon.png');
|
||||
expect(launch.roomCoverUrl, 'https://cdn.example.com/room/cover.png');
|
||||
});
|
||||
|
||||
test('extracts launch data from snake case broadcast payloads', () {
|
||||
final launch = RoomRocketLaunchBroadcastMessage.fromJson({
|
||||
'room_id': 'room-1',
|
||||
'room_account': '10001',
|
||||
'room_name': 'Rocket Room',
|
||||
'round_no': 3,
|
||||
'rocket_level': 2,
|
||||
'launch_no': 'launch-3',
|
||||
'rocket_animation_url': 'https://cdn.example.com/rocket/launch.pag',
|
||||
'rocket_icon_url': 'https://cdn.example.com/rocket/icon.png',
|
||||
'trigger_user': {
|
||||
'user_id': 'u1',
|
||||
'userAvatar': {'sourceUrl': 'https://cdn.example.com/avatar.png'},
|
||||
},
|
||||
});
|
||||
|
||||
expect(launch.isValid, isTrue);
|
||||
expect(launch.roomId, 'room-1');
|
||||
expect(launch.roomAccount, '10001');
|
||||
expect(launch.roomName, 'Rocket Room');
|
||||
expect(launch.roundNo, 3);
|
||||
expect(launch.safeLevel, 2);
|
||||
expect(launch.launchNo, 'launch-3');
|
||||
expect(
|
||||
launch.rocketAnimationUrl,
|
||||
'https://cdn.example.com/rocket/launch.pag',
|
||||
);
|
||||
expect(launch.rocketIconUrl, 'https://cdn.example.com/rocket/icon.png');
|
||||
expect(launch.triggerUser.userId, 'u1');
|
||||
expect(launch.triggerUser.avatar, 'https://cdn.example.com/avatar.png');
|
||||
});
|
||||
}
|
||||
|
||||
@ -175,6 +175,117 @@ void main() {
|
||||
expect(recordRewards.single.displayText, '7d');
|
||||
});
|
||||
|
||||
test('parses rocket reward winner avatars from nested resource payloads', () {
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords([
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r1',
|
||||
'userId': 'u1',
|
||||
'userInfo': {
|
||||
'nickname': 'winner',
|
||||
'avatar': {'sourceUrl': 'https://example.com/avatar.png'},
|
||||
},
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(winners.single.nickname, 'winner');
|
||||
expect(winners.single.avatarUrl, 'https://example.com/avatar.png');
|
||||
});
|
||||
|
||||
test('parses rocket reward winner profile from receiver aliases', () {
|
||||
final record = SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r1',
|
||||
'acceptUserId': 'u1',
|
||||
'acceptNickname': 'winner',
|
||||
'acceptUserAvatar': {'cdn_url': 'https://example.com/avatar.png'},
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
});
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords([record]);
|
||||
|
||||
expect(record.userId, 'u1');
|
||||
expect(record.displayName, 'winner');
|
||||
expect(winners.single.nickname, 'winner');
|
||||
expect(winners.single.avatarUrl, 'https://example.com/avatar.png');
|
||||
});
|
||||
|
||||
test('hydrates rocket reward winners from room user profiles', () {
|
||||
final records = [
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r1',
|
||||
'userId': '2042274349',
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
}),
|
||||
];
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords(
|
||||
records,
|
||||
userProfileResolver:
|
||||
(_) => const RoomRocketRewardUserProfile(
|
||||
userId: 'u1',
|
||||
account: '2042274349',
|
||||
nickname: 'winner',
|
||||
avatarUrl: 'https://example.com/avatar.png',
|
||||
),
|
||||
);
|
||||
|
||||
expect(records.single.displayName, 'User');
|
||||
expect(winners.single.nickname, 'winner');
|
||||
expect(winners.single.avatarUrl, 'https://example.com/avatar.png');
|
||||
});
|
||||
|
||||
test('keeps nested rocket reward users separated by profile id', () {
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords([
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r1',
|
||||
'launchNo': 'launch-1',
|
||||
'userInfo': {'id': 'u1', 'nickname': 'winner 1'},
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
}),
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r2',
|
||||
'launchNo': 'launch-1',
|
||||
'userInfo': {'id': 'u2', 'nickname': 'winner 2'},
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(winners, hasLength(2));
|
||||
expect(winners.map((item) => item.nickname), ['winner 1', 'winner 2']);
|
||||
});
|
||||
|
||||
test('parses room reward records from alternate response keys', () {
|
||||
final records =
|
||||
SCRoomRocketRewardRecordsRes.fromJson({
|
||||
'room_reward_records': [
|
||||
{
|
||||
'id': 'r1',
|
||||
'launch_no': 'launch-1',
|
||||
'room_id': 'room-1',
|
||||
'user_info': {'id': 'u1', 'user_nickname': 'winner 1'},
|
||||
'reward_type': 'GOLD',
|
||||
'reward_amount': 100,
|
||||
},
|
||||
{
|
||||
'id': 'r2',
|
||||
'launch_no': 'launch-1',
|
||||
'room_id': 'room-1',
|
||||
'user_info': {'id': 'u2', 'user_nickname': 'winner 2'},
|
||||
'reward_type': 'GOLD',
|
||||
'reward_amount': 200,
|
||||
},
|
||||
],
|
||||
}).records;
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords(records);
|
||||
|
||||
expect(records.map((item) => item.userId), ['u1', 'u2']);
|
||||
expect(winners, hasLength(2));
|
||||
expect(winners.last.rewards.single.amount, '200');
|
||||
});
|
||||
|
||||
test('parses rocket level contents response from go api', () {
|
||||
final contents = SCRoomRocketLevelContentsRes.fromJson({
|
||||
'configured': true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user