bug fix
This commit is contained in:
parent
f8e13f31ba
commit
6eb7cb91df
@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -9,6 +10,7 @@ import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/tools/sc_user_utils.dart';
|
||||
import 'package:yumi/modules/index/main_route.dart';
|
||||
import 'package:yumi/main.dart' show routeObserver;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
@ -61,7 +63,7 @@ class PersonDetailPage extends StatefulWidget {
|
||||
_PersonDetailPageState createState() => _PersonDetailPageState();
|
||||
}
|
||||
|
||||
class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
class _PersonDetailPageState extends State<PersonDetailPage> with RouteAware {
|
||||
static const Color _profileBg = Color(0xff072121);
|
||||
static const Color _cardBg = Color(0xff08251E);
|
||||
static const Color _profileBorder = Color(0xffB2FBCC);
|
||||
@ -86,6 +88,7 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
// 添加滚动控制器
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
double _opacity = 0.0;
|
||||
PageRoute<dynamic>? _routeObserverRoute;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -155,6 +158,52 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
_loadGiftWall();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final route = ModalRoute.of(context);
|
||||
if (route is PageRoute<dynamic> && _routeObserverRoute != route) {
|
||||
if (_routeObserverRoute != null) {
|
||||
routeObserver.unsubscribe(this);
|
||||
}
|
||||
_routeObserverRoute = route;
|
||||
routeObserver.subscribe(this, route);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didPopNext() {
|
||||
_refreshProfileRelationsAfterResume();
|
||||
}
|
||||
|
||||
void _refreshProfileRelationsAfterResume() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final ref = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final profile = ref.userProfile;
|
||||
final loadedUserId = profile?.id?.trim() ?? "";
|
||||
final targetUserId = widget.tageId.trim();
|
||||
if (targetUserId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final hasRelationCards =
|
||||
(profile?.cpList?.isNotEmpty ?? false) ||
|
||||
(profile?.closeFriendList?.isNotEmpty ?? false);
|
||||
if (loadedUserId.isEmpty ||
|
||||
loadedUserId != targetUserId ||
|
||||
!hasRelationCards) {
|
||||
unawaited(ref.reloadUserInfoById(targetUserId));
|
||||
return;
|
||||
}
|
||||
unawaited(ref.refreshLoadedCpProfiles());
|
||||
});
|
||||
}
|
||||
|
||||
void userCounter(String userId) async {
|
||||
try {
|
||||
var userCounterList = await SCAccountRepository().userCounter(userId);
|
||||
@ -317,6 +366,8 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
routeObserver.unsubscribe(this);
|
||||
_routeObserverRoute = null;
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||
import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_relation_level_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
||||
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';
|
||||
@ -1710,6 +1711,44 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
void refreshRoomMicRelationProfilesForUsers(Set<String> userIds) {
|
||||
final roomId = (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (roomId.isEmpty || roomWheatMap.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final normalizedUserIds =
|
||||
userIds.map((userId) => userId.trim()).where((userId) {
|
||||
return userId.isNotEmpty;
|
||||
}).toSet();
|
||||
final refreshAll = normalizedUserIds.isEmpty;
|
||||
if (refreshAll) {
|
||||
_roomMicRelationCache.clear();
|
||||
_roomMicRelationLoadingUserIds.clear();
|
||||
} else {
|
||||
_roomMicRelationCache.removeWhere((userId, _) {
|
||||
return normalizedUserIds.contains(userId);
|
||||
});
|
||||
_roomMicRelationLoadingUserIds.removeWhere(normalizedUserIds.contains);
|
||||
}
|
||||
|
||||
final queuedUserIds = <String>{};
|
||||
for (final mic in roomWheatMap.values) {
|
||||
final user = mic.user;
|
||||
final userId = _roomMicRelationUserId(user);
|
||||
if (user == null || userId.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
if (!refreshAll && !normalizedUserIds.contains(userId)) {
|
||||
continue;
|
||||
}
|
||||
if (!queuedUserIds.add(userId) ||
|
||||
!_roomMicRelationLoadingUserIds.add(userId)) {
|
||||
continue;
|
||||
}
|
||||
unawaited(_hydrateRoomMicRelationProfile(roomId, userId, user));
|
||||
}
|
||||
}
|
||||
|
||||
bool _mergeCachedRoomMicRelationProfiles() {
|
||||
final now = DateTime.now();
|
||||
_roomMicRelationCache.removeWhere(
|
||||
@ -1806,7 +1845,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
relationType: normalizedType,
|
||||
cpValue: pair.cpValue ?? cabin?.cpVal?.toDouble(),
|
||||
levelText:
|
||||
pair.levelText ?? _roomMicRelationLevelTextFromCabin(cabin),
|
||||
scCpRelationLevelTextFromCabin(
|
||||
cabin,
|
||||
fallbackText: pair.levelText,
|
||||
) ??
|
||||
_roomMicRelationLevelTextFromCabin(cabin),
|
||||
);
|
||||
if (_isRoomMicRelationLocallyDismissed(enrichedPair)) {
|
||||
return;
|
||||
@ -1954,6 +1997,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
pair.cpAccount?.trim() ?? "",
|
||||
pair.relationType?.trim() ?? "",
|
||||
pair.status?.trim() ?? "",
|
||||
pair.days?.trim() ?? "",
|
||||
pair.levelText?.trim() ?? "",
|
||||
pair.cpValue?.toString() ?? "",
|
||||
pair.dismissCost?.toString() ?? "",
|
||||
].join("|");
|
||||
}
|
||||
|
||||
@ -4101,6 +4148,67 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_roomRocketPostLaunchRefreshTimers.clear();
|
||||
}
|
||||
|
||||
void advanceRoomRocketStatusAfterLaunch(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
final currentRoomId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (!launch.isValid || launch.roomId != currentRoomId) {
|
||||
return;
|
||||
}
|
||||
final currentStatus = roomRocketStatus;
|
||||
if (currentStatus == null || !currentStatus.isEnabled) {
|
||||
return;
|
||||
}
|
||||
final nextLevel = _nextRoomRocketLevelAfterLaunch(launch.safeLevel);
|
||||
final nextLevelInfo = _roomRocketLevelFor(nextLevel);
|
||||
final update = SCRoomRocketStatusRes(
|
||||
roomId: launch.roomId,
|
||||
level: nextLevel,
|
||||
currentLevel: nextLevel,
|
||||
currentEnergy: 0,
|
||||
maxEnergy: nextLevelInfo?.needEnergy,
|
||||
needEnergy: nextLevelInfo?.needEnergy,
|
||||
energyPercent: 0,
|
||||
displayPercent: 0,
|
||||
configured: currentStatus.configured,
|
||||
enabled: currentStatus.enabled,
|
||||
levels: currentStatus.levels,
|
||||
);
|
||||
roomRocketStatus = currentStatus.mergeRealtimeUpdate(
|
||||
update,
|
||||
roomId: launch.roomId,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int _nextRoomRocketLevelAfterLaunch(int launchedLevel) {
|
||||
final normalizedLevel = launchedLevel.clamp(1, 99).toInt();
|
||||
final levels = [
|
||||
...(roomRocketStatus?.levels ?? const <SCRoomRocketLevelRes>[]),
|
||||
]..sort((a, b) {
|
||||
final sortCompare = a.sort.compareTo(b.sort);
|
||||
return sortCompare != 0 ? sortCompare : a.level.compareTo(b.level);
|
||||
});
|
||||
for (final level in levels) {
|
||||
if (level.enabled && level.level > normalizedLevel) {
|
||||
return level.level.clamp(1, 99).toInt();
|
||||
}
|
||||
}
|
||||
return normalizedLevel;
|
||||
}
|
||||
|
||||
SCRoomRocketLevelRes? _roomRocketLevelFor(int level) {
|
||||
final normalizedLevel = level.clamp(1, 99).toInt();
|
||||
for (final item
|
||||
in roomRocketStatus?.levels ?? const <SCRoomRocketLevelRes>[]) {
|
||||
if (item.level == normalizedLevel) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void handleRoomRocketLaunchBroadcast(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
|
||||
@ -14,6 +14,7 @@ import 'package:yumi/shared/tools/sc_message_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_relation_broadcast_assets.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_rocket_reward_popup_ack.dart';
|
||||
@ -134,6 +135,74 @@ class _PendingRoomRocketRewardPopup {
|
||||
final List<SCRoomRocketRewardRecordRes> initialRoomRecords;
|
||||
}
|
||||
|
||||
bool roomRocketRewardPopupTargetsCurrentUser({
|
||||
required Map<String, dynamic> payload,
|
||||
required Set<String> currentUserKeys,
|
||||
}) {
|
||||
final audienceUserIds = _roomRocketRewardPopupAudienceUserIds(payload);
|
||||
if (audienceUserIds == null) {
|
||||
return true;
|
||||
}
|
||||
if (audienceUserIds.isEmpty || currentUserKeys.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return currentUserKeys.any(audienceUserIds.contains);
|
||||
}
|
||||
|
||||
Set<String>? _roomRocketRewardPopupAudienceUserIds(
|
||||
Map<String, dynamic> payload,
|
||||
) {
|
||||
for (final key in const [
|
||||
'popupUserIds',
|
||||
'popup_user_ids',
|
||||
'notifyUserIds',
|
||||
'notify_user_ids',
|
||||
'audienceUserIds',
|
||||
'audience_user_ids',
|
||||
'visibleUserIds',
|
||||
'visible_user_ids',
|
||||
]) {
|
||||
if (payload.containsKey(key)) {
|
||||
return _roomRocketPayloadStringSet(payload[key]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> _roomRocketPayloadStringSet(dynamic value) {
|
||||
if (value == null) {
|
||||
return <String>{};
|
||||
}
|
||||
if (value is Iterable && value is! String) {
|
||||
return value
|
||||
.map((item) => item?.toString().trim() ?? '')
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
if (value is Map) {
|
||||
return value.values
|
||||
.map((item) => item?.toString().trim() ?? '')
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
final raw = value.toString().trim();
|
||||
if (raw.isEmpty) {
|
||||
return <String>{};
|
||||
}
|
||||
if (raw.startsWith('[') || raw.startsWith('{')) {
|
||||
try {
|
||||
return _roomRocketPayloadStringSet(jsonDecode(raw));
|
||||
} catch (_) {
|
||||
return <String>{};
|
||||
}
|
||||
}
|
||||
return raw
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
|
||||
class RealTimeMessagingManager extends ChangeNotifier {
|
||||
static const String _roomRocketRewardDialogTag = 'showRoomRocketRewardDialog';
|
||||
|
||||
@ -146,11 +215,18 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
static const int _luckyGiftBurstMergeWindowMs = 1800;
|
||||
static const int _luckyGiftBurstRecentEventTtlMs = 5000;
|
||||
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
|
||||
static const int _cpRelationGiftRefreshMinIntervalMs = 800;
|
||||
static const List<Duration> _cpRelationGiftRefreshDelays = [
|
||||
Duration(milliseconds: 300),
|
||||
Duration(milliseconds: 1500),
|
||||
Duration(milliseconds: 3500),
|
||||
];
|
||||
static const int _roomRocketLaunchRecentTtlMs = 90000;
|
||||
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
|
||||
static const int _roomRocketRegionBroadcastRecentTtlMs = 90000;
|
||||
static const int _roomRocketRewardPendingTtlMs = 3600000;
|
||||
static const int _roomRocketRewardProbeTtlMs = 3600000;
|
||||
static const int _roomRocketEmptyRewardPopupRecentTtlMs = 120000;
|
||||
static const int _roomRocketLaunchCountdownRetainMs = 10000;
|
||||
static const int _roomRocketLaunchEffectConsumedTtlMs = 120000;
|
||||
static const Duration _createRoomGroupTimeout = Duration(seconds: 12);
|
||||
@ -296,12 +372,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final Map<String, int> _roomRocketRewardHoldUntilTimes = <String, int>{};
|
||||
final Map<String, Timer> _roomRocketRewardHoldTimers = <String, Timer>{};
|
||||
final Set<String> _roomRocketRewardPopupLoadingRoomIds = <String>{};
|
||||
final Map<String, int> _shownRoomRocketEmptyRewardPopupTimes =
|
||||
<String, int>{};
|
||||
Timer? _roomRocketRewardPopupRetryTimer;
|
||||
Timer? _roomRocketRoomReadyRewardRetryTimer;
|
||||
final Set<String> _shownCpInviteMessageKeys = <String>{};
|
||||
final Set<String> _shownCpInviteResultMessageKeys = <String>{};
|
||||
final Set<String> _shownCpInviteRejectedDialogKeys = <String>{};
|
||||
final Map<String, int> _recentCpRelationNoticeTimes = <String, int>{};
|
||||
int _lastCpRelationGiftRefreshAt = 0;
|
||||
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
|
||||
String? roomRedPacketBroadcastGroupId;
|
||||
String? roomRedPacketBroadcastRegionCode;
|
||||
@ -2693,6 +2772,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
|
||||
addMsg(Msg msg) {
|
||||
_scheduleRoomRocketStatusRefreshForGiftMessage(msg);
|
||||
_scheduleCpRelationRefreshForGiftMessage(msg);
|
||||
final mergedGiftMsg = _mergeGiftMessageIfNeeded(msg);
|
||||
if (mergedGiftMsg != null) {
|
||||
msgAllListener?.call(mergedGiftMsg);
|
||||
@ -3083,6 +3163,64 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
rtcProvider.scheduleRoomRocketStatusRefreshForGift(roomId: currentRoomId);
|
||||
}
|
||||
|
||||
void _scheduleCpRelationRefreshForGiftMessage(Msg msg) {
|
||||
if (!_isCpRelationGiftMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
if (now - _lastCpRelationGiftRefreshAt <
|
||||
_cpRelationGiftRefreshMinIntervalMs) {
|
||||
return;
|
||||
}
|
||||
_lastCpRelationGiftRefreshAt = now;
|
||||
final userIds = Set<String>.unmodifiable(_cpRelationGiftUserIds(msg));
|
||||
for (final delay in _cpRelationGiftRefreshDelays) {
|
||||
unawaited(
|
||||
Future<void>.delayed(delay, () {
|
||||
_refreshCpRelationDataAfterGift(userIds);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isCpRelationGiftMessage(Msg msg) {
|
||||
if (msg.type != SCRoomMsgType.gift &&
|
||||
msg.type != SCRoomMsgType.luckGiftAnimOther) {
|
||||
return false;
|
||||
}
|
||||
return scIsCpGift(msg.gift);
|
||||
}
|
||||
|
||||
Set<String> _cpRelationGiftUserIds(Msg msg) {
|
||||
return <String>{
|
||||
msg.user?.id?.trim() ?? '',
|
||||
msg.toUser?.id?.trim() ?? '',
|
||||
...?msg.targetUserIds?.map((userId) => userId.trim()),
|
||||
}..removeWhere((userId) => userId.isEmpty);
|
||||
}
|
||||
|
||||
void _refreshCpRelationDataAfterGift(Set<String> userIds) {
|
||||
final currentContext =
|
||||
context?.mounted == true ? context : navigatorKey.currentState?.context;
|
||||
if (currentContext == null || !currentContext.mounted) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
unawaited(
|
||||
Provider.of<SocialChatUserProfileManager>(
|
||||
currentContext,
|
||||
listen: false,
|
||||
).refreshLoadedCpProfiles(),
|
||||
);
|
||||
} catch (_) {}
|
||||
try {
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
currentContext,
|
||||
listen: false,
|
||||
).refreshRoomMicRelationProfilesForUsers(userIds);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _cacheRoomRedPacketMessage(Msg msg) {
|
||||
try {
|
||||
final decoded = jsonDecode(msg.msg ?? '');
|
||||
@ -3865,6 +4003,13 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isRoomRocketStatusUpdateType(dynamic type) {
|
||||
final value = type?.toString().trim();
|
||||
return value == SCRoomMsgType.voiceRoomRocketStatusUpdate ||
|
||||
value == SCRoomMsgType.rocketEnergyUpdate ||
|
||||
value == 'ROCKET_ENERGY_UPDATE';
|
||||
}
|
||||
|
||||
void _handleVoiceRoomRocketStatusUpdate(dynamic payload) {
|
||||
final data = _broadcastPayloadMap(payload);
|
||||
final roomId = _payloadRoomId(data);
|
||||
@ -4006,6 +4151,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_scheduleRoomRocketRewardPopupAfterHold(
|
||||
resolvedRoomId,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -4016,89 +4162,21 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_showRoomRocketRewardPopupDialog(
|
||||
resolvedRoomId,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isCurrentUserInRoomRocketRewardPopup(Map<String, dynamic> payload) {
|
||||
final userIds = _roomRocketRewardPopupUserIds(payload);
|
||||
if (userIds == null) {
|
||||
return true;
|
||||
}
|
||||
if (userIds.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final profile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final currentKeys = <String>{
|
||||
profile?.id?.trim() ?? '',
|
||||
profile?.account?.trim() ?? '',
|
||||
}..removeWhere((item) => item.isEmpty);
|
||||
if (currentKeys.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return currentKeys.any(userIds.contains);
|
||||
}
|
||||
|
||||
Set<String>? _roomRocketRewardPopupUserIds(Map<String, dynamic> payload) {
|
||||
for (final key in const [
|
||||
'userIds',
|
||||
'user_ids',
|
||||
'targetUserIds',
|
||||
'target_user_ids',
|
||||
'winnerUserIds',
|
||||
'winner_user_ids',
|
||||
'userId',
|
||||
'user_id',
|
||||
'winnerUserId',
|
||||
'winner_user_id',
|
||||
'rewardUserId',
|
||||
'reward_user_id',
|
||||
'receiverUserId',
|
||||
'receiver_user_id',
|
||||
'targetUserId',
|
||||
'target_user_id',
|
||||
'toUserId',
|
||||
'to_user_id',
|
||||
]) {
|
||||
if (payload.containsKey(key)) {
|
||||
return _payloadStringSet(payload[key]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> _payloadStringSet(dynamic value) {
|
||||
if (value == null) {
|
||||
return <String>{};
|
||||
}
|
||||
if (value is Iterable && value is! String) {
|
||||
return value
|
||||
.map((item) => item?.toString().trim() ?? '')
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
if (value is Map) {
|
||||
return value.values
|
||||
.map((item) => item?.toString().trim() ?? '')
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
final raw = value.toString().trim();
|
||||
if (raw.isEmpty) {
|
||||
return <String>{};
|
||||
}
|
||||
if (raw.startsWith('[') || raw.startsWith('{')) {
|
||||
try {
|
||||
return _payloadStringSet(jsonDecode(raw));
|
||||
} catch (_) {
|
||||
return <String>{};
|
||||
}
|
||||
}
|
||||
return raw
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.where((item) => item.isNotEmpty)
|
||||
.toSet();
|
||||
return roomRocketRewardPopupTargetsCurrentUser(
|
||||
payload: payload,
|
||||
currentUserKeys: currentKeys,
|
||||
);
|
||||
}
|
||||
|
||||
void queryVoiceRoomRocketRewardPopups(String roomId) {
|
||||
@ -4138,6 +4216,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final pendingAfterResume = _pendingRoomRocketRewardPopups[normalizedRoomId];
|
||||
final hasProbeAfterResume =
|
||||
_recentRoomRocketRewardProbeTimes[normalizedRoomId] != null;
|
||||
final allowEmptyPopup = pendingAfterResume != null || hasProbeAfterResume;
|
||||
if (pendingAfterResume == null && !hasProbeAfterResume && !forceQuery) {
|
||||
return;
|
||||
}
|
||||
@ -4162,6 +4241,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
initialRoomRecords:
|
||||
pending?.initialRoomRecords ??
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -4171,6 +4251,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
initialRoomRecords:
|
||||
pendingAfterResume?.initialRoomRecords ??
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -4338,6 +4419,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
String roomId, {
|
||||
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
bool allowEmptyPopup = false,
|
||||
}) {
|
||||
final normalizedRoomId = roomId.trim();
|
||||
if (normalizedRoomId.isEmpty) {
|
||||
@ -4356,7 +4438,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
() {
|
||||
_roomRocketRewardHoldTimers.remove(normalizedRoomId);
|
||||
if (_isRoomRocketRewardPopupHeld(normalizedRoomId)) {
|
||||
_scheduleRoomRocketRewardPopupAfterHold(normalizedRoomId);
|
||||
_scheduleRoomRocketRewardPopupAfterHold(
|
||||
normalizedRoomId,
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_isCurrentVisibleVoiceRoom(normalizedRoomId)) {
|
||||
@ -4371,6 +4456,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
initialRoomRecords:
|
||||
pending?.initialRoomRecords ??
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -4381,6 +4467,55 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
bool _markRoomRocketEmptyRewardPopupForShow(
|
||||
String roomId,
|
||||
List<SCRoomRocketRewardRecordRes> roomRecords,
|
||||
) {
|
||||
final normalizedRoomId = roomId.trim();
|
||||
if (normalizedRoomId.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
_pruneRoomRocketRewardFallbacks(now);
|
||||
final key = _roomRocketEmptyRewardPopupKey(normalizedRoomId, roomRecords);
|
||||
if (_shownRoomRocketEmptyRewardPopupTimes.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
_shownRoomRocketEmptyRewardPopupTimes[key] = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
String _roomRocketEmptyRewardPopupKey(
|
||||
String roomId,
|
||||
List<SCRoomRocketRewardRecordRes> roomRecords,
|
||||
) {
|
||||
final launchNos =
|
||||
roomRecords
|
||||
.map((record) => record.launchNo.trim())
|
||||
.where((launchNo) => launchNo.isNotEmpty)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort();
|
||||
if (launchNos.isNotEmpty) {
|
||||
return 'room_rocket_empty_reward|$roomId|launch:${launchNos.join(",")}';
|
||||
}
|
||||
final roundKeys =
|
||||
roomRecords
|
||||
.map((record) {
|
||||
final level = record.level > 0 ? record.level.toString() : '';
|
||||
final round = record.roundNo > 0 ? record.roundNo.toString() : '';
|
||||
return [level, round].where((item) => item.isNotEmpty).join(':');
|
||||
})
|
||||
.where((key) => key.isNotEmpty)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort();
|
||||
if (roundKeys.isNotEmpty) {
|
||||
return 'room_rocket_empty_reward|$roomId|round:${roundKeys.join(",")}';
|
||||
}
|
||||
return 'room_rocket_empty_reward|$roomId|recent';
|
||||
}
|
||||
|
||||
void _pruneRoomRocketRewardFallbacks(int now) {
|
||||
_pendingRoomRocketRewardPopups.removeWhere(
|
||||
(_, pending) => now - pending.receivedAt > _roomRocketRewardPendingTtlMs,
|
||||
@ -4388,11 +4523,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_recentRoomRocketRewardProbeTimes.removeWhere(
|
||||
(_, timestamp) => now - timestamp > _roomRocketRewardProbeTtlMs,
|
||||
);
|
||||
_shownRoomRocketEmptyRewardPopupTimes.removeWhere(
|
||||
(_, timestamp) =>
|
||||
now - timestamp > _roomRocketEmptyRewardPopupRecentTtlMs,
|
||||
);
|
||||
}
|
||||
|
||||
void _clearRoomRocketRewardFallbackState() {
|
||||
_pendingRoomRocketRewardPopups.clear();
|
||||
_recentRoomRocketRewardProbeTimes.clear();
|
||||
_shownRoomRocketEmptyRewardPopupTimes.clear();
|
||||
_consumedRoomRocketLaunchEffectTimes.clear();
|
||||
_hiddenCurrentRoomRocketLaunchKeys.clear();
|
||||
_roomRocketRoomReadyRewardRetryTimer?.cancel();
|
||||
@ -4484,6 +4624,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_recentRoomRocketRewardProbeTimes.remove(launch.roomId);
|
||||
_markRoomRocketLaunchEffectConsumed(launch);
|
||||
_holdRoomRocketRewardPopupUntilLaunchEnds(launch);
|
||||
rtcProvider.advanceRoomRocketStatusAfterLaunch(launch);
|
||||
rtcProvider.handleRoomRocketLaunchBroadcast(launch);
|
||||
if (shouldFetchReward) {
|
||||
_scheduleRoomRocketRewardPopupAfterHold(
|
||||
@ -4491,6 +4632,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
initialRoomRecords:
|
||||
pending?.initialRoomRecords ??
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
allowEmptyPopup: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4772,6 +4914,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
int attempt = 0,
|
||||
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
bool allowEmptyPopup = false,
|
||||
}) async {
|
||||
final normalizedRoomId = roomId.trim();
|
||||
if (normalizedRoomId.isEmpty ||
|
||||
@ -4782,6 +4925,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_scheduleRoomRocketRewardPopupAfterHold(
|
||||
normalizedRoomId,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -4792,6 +4936,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
normalizedRoomId,
|
||||
attempt: attempt,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -4814,6 +4959,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
normalizedRoomId,
|
||||
attempt,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -4823,7 +4969,17 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
if (res.records.isNotEmpty && popupRecords.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (popupRecords.isEmpty && initialRoomRecords.isEmpty) {
|
||||
if (popupRecords.isEmpty &&
|
||||
initialRoomRecords.isEmpty &&
|
||||
!allowEmptyPopup) {
|
||||
return;
|
||||
}
|
||||
if (popupRecords.isEmpty &&
|
||||
allowEmptyPopup &&
|
||||
!_markRoomRocketEmptyRewardPopupForShow(
|
||||
normalizedRoomId,
|
||||
initialRoomRecords,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
_roomRocketRewardPopupRetryTimer?.cancel();
|
||||
@ -4851,6 +5007,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
normalizedRoomId,
|
||||
attempt,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
);
|
||||
} finally {
|
||||
_roomRocketRewardPopupLoadingRoomIds.remove(normalizedRoomId);
|
||||
@ -4931,6 +5088,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
int attempt, {
|
||||
List<SCRoomRocketRewardRecordRes> initialRoomRecords =
|
||||
const <SCRoomRocketRewardRecordRes>[],
|
||||
bool allowEmptyPopup = false,
|
||||
}) {
|
||||
if (attempt >= _roomRocketRewardPopupRetryDelays.length ||
|
||||
!_isCurrentVisibleVoiceRoom(roomId)) {
|
||||
@ -4946,6 +5104,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
roomId,
|
||||
attempt: attempt + 1,
|
||||
initialRoomRecords: initialRoomRecords,
|
||||
allowEmptyPopup: allowEmptyPopup,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -4988,6 +5147,23 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _scheduleRoomRocketStatusRefreshForCurrentRoom(String roomId) {
|
||||
final normalizedRoomId = roomId.trim();
|
||||
if (normalizedRoomId.isEmpty || !_isCurrentVoiceRoom(normalizedRoomId)) {
|
||||
return;
|
||||
}
|
||||
final currentContext = context;
|
||||
if (currentContext == null || !currentContext.mounted) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
currentContext,
|
||||
listen: false,
|
||||
).scheduleRoomRocketStatusRefreshForGift(roomId: normalizedRoomId);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
List<SCRoomRocketRewardRecordRes> _roomRocketRewardRecordsFromPayload(
|
||||
Map<String, dynamic> payload,
|
||||
) {
|
||||
@ -5248,6 +5424,12 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final value = _payloadNum(
|
||||
payload['currentLevel'] ??
|
||||
payload['current_level'] ??
|
||||
payload['currentRocketLevel'] ??
|
||||
payload['current_rocket_level'] ??
|
||||
payload['rocketCurrentLevel'] ??
|
||||
payload['rocket_current_level'] ??
|
||||
payload['levelNo'] ??
|
||||
payload['level_no'] ??
|
||||
payload['level'] ??
|
||||
payload['rocketLevel'] ??
|
||||
payload['rocket_level'] ??
|
||||
@ -5548,12 +5730,13 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
priority: 1000,
|
||||
);
|
||||
OverlayManager().addMessage(msg);
|
||||
_scheduleRoomRocketStatusRefreshForCurrentRoom(launchRoomId);
|
||||
} else if (type == SCRoomMsgType.voiceRoomRocketLaunchBroadcast) {
|
||||
_handleVoiceRoomRocketLaunchBroadcast(
|
||||
data["data"],
|
||||
isRegionBroadcast: isRegionBroadcastGroup,
|
||||
);
|
||||
} else if (type == SCRoomMsgType.voiceRoomRocketStatusUpdate) {
|
||||
} else if (_isRoomRocketStatusUpdateType(type)) {
|
||||
_handleVoiceRoomRocketStatusUpdate(data["data"]);
|
||||
} else if (type == SCRoomMsgType.voiceRoomRocketRewardPopup) {
|
||||
_handleVoiceRoomRocketRewardPopup(data["data"] ?? data);
|
||||
@ -5651,7 +5834,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_handleVoiceRoomRocketLaunchBroadcast(data["data"]);
|
||||
return;
|
||||
}
|
||||
if (data["type"] == SCRoomMsgType.voiceRoomRocketStatusUpdate) {
|
||||
if (_isRoomRocketStatusUpdateType(data["type"])) {
|
||||
_handleVoiceRoomRocketStatusUpdate(data["data"]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,14 +132,32 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
|
||||
/// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。
|
||||
SocialChatUserProfile? userProfile;
|
||||
int _userInfoRequestSerial = 0;
|
||||
|
||||
void getUserInfoById(String userId) async {
|
||||
userProfile = null;
|
||||
Future<void> getUserInfoById(String userId) {
|
||||
return _loadUserInfoById(userId, clearBeforeLoad: true);
|
||||
}
|
||||
|
||||
Future<void> reloadUserInfoById(String userId) {
|
||||
return _loadUserInfoById(userId, clearBeforeLoad: false);
|
||||
}
|
||||
|
||||
Future<void> _loadUserInfoById(
|
||||
String userId, {
|
||||
required bool clearBeforeLoad,
|
||||
}) async {
|
||||
final requestSerial = ++_userInfoRequestSerial;
|
||||
if (clearBeforeLoad) {
|
||||
userProfile = null;
|
||||
notifyListeners();
|
||||
}
|
||||
final loadedProfile = await SCAccountRepository().loadUserInfo(userId);
|
||||
var mergedProfile = await _withCurrentVipLevel(loadedProfile, userId);
|
||||
mergedProfile = await _withCpCabinProfiles(mergedProfile, userId);
|
||||
if (requestSerial != _userInfoRequestSerial) {
|
||||
return;
|
||||
}
|
||||
userProfile = mergedProfile;
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -193,7 +211,41 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
);
|
||||
final cpPairs = <CPRes>[];
|
||||
final closeFriendPairs = <CPRes>[];
|
||||
_addCpRelationPairs(
|
||||
cpPairs: cpPairs,
|
||||
closeFriendPairs: closeFriendPairs,
|
||||
relations: fallbackCpPairs,
|
||||
);
|
||||
_addCpRelationPairs(
|
||||
cpPairs: cpPairs,
|
||||
closeFriendPairs: closeFriendPairs,
|
||||
relations: fallbackCloseFriendPairs,
|
||||
);
|
||||
var loadedAnyCabin = false;
|
||||
final shouldLoadCurrentUserPairs = _isCurrentUserId(userId);
|
||||
if (kDebugMode) {
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? "";
|
||||
debugPrint(
|
||||
'[CP][Profile] source userId=$userId currentUserId=$currentUserId '
|
||||
'profileCp=${profile.cpList?.length ?? 0} '
|
||||
'profileCloseFriends=${profile.closeFriendList?.length ?? 0} '
|
||||
'loadCurrentPairs=$shouldLoadCurrentUserPairs',
|
||||
);
|
||||
}
|
||||
if (shouldLoadCurrentUserPairs) {
|
||||
await Future.wait(
|
||||
_cpProfileRelationTypes.map((relationType) async {
|
||||
final pairs = await _safeLoadCpRelationshipPairs(relationType);
|
||||
_addCpRelationPairs(
|
||||
cpPairs: cpPairs,
|
||||
closeFriendPairs: closeFriendPairs,
|
||||
relations: pairs,
|
||||
fallbackRelationType: relationType,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
await Future.wait(
|
||||
_cpProfileRelationTypes.map((relationType) async {
|
||||
final cabin = await _safeLoadCpCabin(userId, relationType);
|
||||
@ -239,8 +291,9 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
}
|
||||
if (!loadedAnyCabin) {
|
||||
return profile.copyWith(
|
||||
cpList: fallbackCpPairs,
|
||||
closeFriendList: fallbackCloseFriendPairs,
|
||||
cpList: cpPairs,
|
||||
closeFriendList: closeFriendPairs,
|
||||
isCpRelation: cpPairs.isNotEmpty,
|
||||
);
|
||||
}
|
||||
return profile.copyWith(
|
||||
@ -325,6 +378,28 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
].join('|');
|
||||
}
|
||||
|
||||
Future<List<CPRes>> _safeLoadCpRelationshipPairs(String relationType) async {
|
||||
try {
|
||||
final pairs = await SCAccountRepository().cpRelationshipPairs(
|
||||
relationType: relationType,
|
||||
);
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[CP][Profile] pair loaded relationType=$relationType '
|
||||
'count=${pairs.length}',
|
||||
);
|
||||
}
|
||||
return pairs;
|
||||
} catch (error) {
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[CP][Profile] pair failed relationType=$relationType error=$error',
|
||||
);
|
||||
}
|
||||
return const <CPRes>[];
|
||||
}
|
||||
}
|
||||
|
||||
Future<SCCpCabinRes?> _safeLoadCpCabin(
|
||||
String userId,
|
||||
String relationType,
|
||||
@ -493,10 +568,12 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
bool _isCurrentUserId(String userId) {
|
||||
final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id;
|
||||
final currentUserId =
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id?.trim();
|
||||
final normalizedUserId = userId.trim();
|
||||
return currentUserId != null &&
|
||||
currentUserId.isNotEmpty &&
|
||||
currentUserId == userId;
|
||||
currentUserId == normalizedUserId;
|
||||
}
|
||||
|
||||
Future<SCVipStatusRes?> _loadCurrentVipStatus() async {
|
||||
|
||||
@ -1187,9 +1187,7 @@ class CPRes {
|
||||
source['level'] ??
|
||||
source['cpLevel'] ??
|
||||
source['cabinLevel'] ??
|
||||
source['lv'] ??
|
||||
source['name'] ??
|
||||
source['title'],
|
||||
source['lv'],
|
||||
);
|
||||
_dismissEndTime = _asInt(source['dismissEndTime']);
|
||||
_dismissRemainSeconds = _asInt(source['dismissRemainSeconds']);
|
||||
|
||||
@ -107,6 +107,12 @@ class SCRoomRocketStatusRes {
|
||||
_currentLevel = _intValue(
|
||||
json['currentLevel'] ??
|
||||
json['current_level'] ??
|
||||
json['currentRocketLevel'] ??
|
||||
json['current_rocket_level'] ??
|
||||
json['rocketCurrentLevel'] ??
|
||||
json['rocket_current_level'] ??
|
||||
json['levelNo'] ??
|
||||
json['level_no'] ??
|
||||
json['toLevel'] ??
|
||||
json['to_level'] ??
|
||||
json['nextLevel'] ??
|
||||
@ -120,6 +126,12 @@ class SCRoomRocketStatusRes {
|
||||
_level =
|
||||
json['level'] ??
|
||||
json['rocket_level'] ??
|
||||
json['currentRocketLevel'] ??
|
||||
json['current_rocket_level'] ??
|
||||
json['rocketCurrentLevel'] ??
|
||||
json['rocket_current_level'] ??
|
||||
json['levelNo'] ??
|
||||
json['level_no'] ??
|
||||
json['toLevel'] ??
|
||||
json['to_level'] ??
|
||||
json['nextLevel'] ??
|
||||
|
||||
@ -60,7 +60,11 @@ abstract class SocialChatRoomRepository {
|
||||
Future micGoDown(String roomId, num mickIndex);
|
||||
|
||||
///这个用户是否可以被踢下麦
|
||||
Future<bool> kickOffMicrophone(String roomId, String kickUserId);
|
||||
Future<bool> kickOffMicrophone(
|
||||
String roomId,
|
||||
String kickUserId, {
|
||||
num? mickIndex,
|
||||
});
|
||||
|
||||
///锁麦
|
||||
Future micLock(String roomId, num mickIndex, bool lock);
|
||||
|
||||
@ -952,6 +952,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
||||
final result = await http.post<bool>(
|
||||
"a4465894e21be08d833ef701566ffe1b6a835338046b35fb94271445c996f6e3574aff257ce7e668d08f4caccd1c6232",
|
||||
data: data,
|
||||
extra: const {BaseNetworkClient.silentErrorToastKey: true},
|
||||
fromJson: (json) => json as bool,
|
||||
);
|
||||
return result;
|
||||
|
||||
@ -22,6 +22,9 @@ String? scCpRelationLevelTextFromCabin(
|
||||
if (computedText != null) {
|
||||
return computedText;
|
||||
}
|
||||
if (currentValue != null && currentValue <= 0) {
|
||||
return 'Lv.0';
|
||||
}
|
||||
|
||||
final currentConfig = _CpRelationLevelSource.fromDynamic(
|
||||
cabin.currentCpCabinConfigCO,
|
||||
@ -51,7 +54,13 @@ String? scCpRelationLevelTextFromRightsConfig(
|
||||
level: computedLevel?.level,
|
||||
name: computedLevel?.levelName,
|
||||
);
|
||||
return computedText ?? _blankAsNull(fallbackText);
|
||||
if (computedText != null) {
|
||||
return computedText;
|
||||
}
|
||||
if (currentValue != null && currentValue <= 0) {
|
||||
return 'Lv.0';
|
||||
}
|
||||
return _blankAsNull(fallbackText);
|
||||
}
|
||||
|
||||
String scCpRelationLevelDisplayText(String? rawText) {
|
||||
@ -86,9 +95,9 @@ int? scCpRelationLevelIndexOrNull(String? levelText) {
|
||||
r'(?:Lv\.?|Level|等级|级)\s*([0-5])|([0-5])\s*(?:级|Level)',
|
||||
caseSensitive: false,
|
||||
).firstMatch(text);
|
||||
final fallbackMatch = RegExp(r'[0-5]').firstMatch(text);
|
||||
final compactMatch = RegExp(r'^[0-5]$').firstMatch(text);
|
||||
final rawLevel =
|
||||
levelMatch?.group(1) ?? levelMatch?.group(2) ?? fallbackMatch?.group(0);
|
||||
levelMatch?.group(1) ?? levelMatch?.group(2) ?? compactMatch?.group(0);
|
||||
final level = int.tryParse(rawLevel ?? '');
|
||||
return level?.clamp(0, 5).toInt();
|
||||
}
|
||||
|
||||
@ -1,11 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_api_res.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_reward_dialog_loader.dart';
|
||||
|
||||
void main() {
|
||||
group('room rocket reward popup audience', () {
|
||||
test('winner ids do not block empty reward users', () {
|
||||
final shouldShow = roomRocketRewardPopupTargetsCurrentUser(
|
||||
payload: {
|
||||
'winnerUserIds': ['user-a', 'user-b'],
|
||||
},
|
||||
currentUserKeys: {'user-c'},
|
||||
);
|
||||
|
||||
expect(shouldShow, isTrue);
|
||||
});
|
||||
|
||||
test('explicit popup ids still target popup recipients', () {
|
||||
final shouldShow = roomRocketRewardPopupTargetsCurrentUser(
|
||||
payload: {
|
||||
'popupUserIds': ['user-a', 'user-b'],
|
||||
},
|
||||
currentUserKeys: {'user-c'},
|
||||
);
|
||||
|
||||
expect(shouldShow, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('room rocket reward dialog loader', () {
|
||||
test('deduplicates reward records by id', () {
|
||||
final records = debugUniqueRoomRocketRewardRecords([
|
||||
|
||||
@ -264,6 +264,17 @@ void main() {
|
||||
expect(status.displayPercent, 0);
|
||||
});
|
||||
|
||||
test('parses realtime current rocket level aliases', () {
|
||||
final status = SCRoomRocketStatusRes.fromJson({
|
||||
'room_id': 'room-1',
|
||||
'current_rocket_level': 2,
|
||||
'display_percent': 0,
|
||||
});
|
||||
|
||||
expect(status.currentLevel, 2);
|
||||
expect(status.level, 2);
|
||||
});
|
||||
|
||||
test('merges realtime rocket status without dropping level assets', () {
|
||||
final current = SCRoomRocketStatusRes.fromJson({
|
||||
'room_id': 'room-1',
|
||||
|
||||
@ -95,6 +95,37 @@ void main() {
|
||||
|
||||
expect(scCpRelationTypeBetweenProfiles(left, right), isNull);
|
||||
});
|
||||
|
||||
test('relation version changes when cp level or value changes', () {
|
||||
final base = SocialChatUserProfile(
|
||||
id: '100',
|
||||
cpList: [
|
||||
CPRes(
|
||||
meUserId: '100',
|
||||
cpUserId: '200',
|
||||
relationType: 'CP',
|
||||
levelText: 'Lv.1',
|
||||
cpValue: 90,
|
||||
),
|
||||
],
|
||||
);
|
||||
final upgraded = base.copyWith(
|
||||
cpList: [
|
||||
CPRes(
|
||||
meUserId: '100',
|
||||
cpUserId: '200',
|
||||
relationType: 'CP',
|
||||
levelText: 'Lv.2',
|
||||
cpValue: 120,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
expect(
|
||||
scCpProfileRelationVersion(base),
|
||||
isNot(scCpProfileRelationVersion(upgraded)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('scCpRelationLevelTextFromCabin', () {
|
||||
@ -112,6 +143,15 @@ void main() {
|
||||
expect(scCpRelationLevelTextFromCabin(cabin), 'Lv.2 Sweet Love');
|
||||
});
|
||||
|
||||
test('keeps zero value relation at level zero', () {
|
||||
final cabin = SCCpCabinRes(
|
||||
cpVal: 0,
|
||||
currentCpCabinConfigCO: const {'level': 5, 'levelName': 'Soulmate'},
|
||||
);
|
||||
|
||||
expect(scCpRelationLevelTextFromCabin(cabin), 'Lv.0');
|
||||
});
|
||||
|
||||
test(
|
||||
'supports zero based CP levels and hides hardcoded simple love copy',
|
||||
() {
|
||||
@ -140,5 +180,22 @@ void main() {
|
||||
expect(scCpRelationLevelTextFromCabin(levelOneCabin), 'Lv.1');
|
||||
},
|
||||
);
|
||||
|
||||
test('does not infer cp level from unrelated digits', () {
|
||||
expect(scCpRelationLevelIndexOrNull('520 Couple'), isNull);
|
||||
expect(scCpRelationLevelIndexOrNull('Lv.5 Soulmate'), 5);
|
||||
expect(scCpRelationLevelIndexOrNull('5'), 5);
|
||||
});
|
||||
|
||||
test('does not parse generic relation name as level text', () {
|
||||
final relation = CPRes.fromJson({
|
||||
'meUserId': '100',
|
||||
'cpUserId': '200',
|
||||
'relationType': 'CP',
|
||||
'name': '520 Couple',
|
||||
});
|
||||
|
||||
expect(relation.levelText, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user