修复飘窗bug

This commit is contained in:
roxy 2026-05-15 23:59:47 +08:00
parent be6b2f8f88
commit 74e8162c1f
11 changed files with 457 additions and 73 deletions

View File

@ -11,6 +11,7 @@ import 'package:yumi/modules/user/settings/settings_route.dart';
import 'package:yumi/modules/user/vip/vip_route.dart';
import 'package:yumi/modules/wallet/wallet_route.dart';
import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_user_counter_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
@ -117,11 +118,36 @@ class _MePage2State extends State<MePage2> {
status.active == false || status.levelInt <= 0
? '0'
: status.levelInt.toString();
if (profile.vipLevel == nextVipLevel) return;
profileManager.syncCurrentUserProfile(
final nextProfile = socialChatProfileWithAvatarFrameResource(
profile.copyWith(vipLevel: nextVipLevel),
_avatarFramePropsFromVipStatus(status),
expireTime: _expireTimeFromVipStatus(status),
);
profileManager.syncCurrentUserProfile(nextProfile);
}
PropsResources? _avatarFramePropsFromVipStatus(SCVipStatusRes status) {
if (!status.isActive) {
return null;
}
final resource = status.avatarFrame;
return socialChatAvatarFrameProps(
id: resource?.resourceId,
name: resource?.name,
sourceUrl: resource?.sourceResourceUrl,
cover: resource?.coverUrl ?? resource?.cover,
code: "VIP_AVATAR_FRAME",
);
}
String? _expireTimeFromVipStatus(SCVipStatusRes status) {
final expireAt = status.expireAt?.trim();
if (expireAt == null || expireAt.isEmpty) {
return null;
}
final normalized =
expireAt.contains("T") ? expireAt : expireAt.replaceFirst(" ", "T");
return DateTime.tryParse(normalized)?.millisecondsSinceEpoch.toString();
}
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
@ -783,7 +809,9 @@ class _MePage2State extends State<MePage2> {
),
);
} else if (kDebugMode) {
debugPrint('[MePage2] hide Manager Center because yumiManager=$isYumiManager');
debugPrint(
'[MePage2] hide Manager Center because yumiManager=$isYumiManager',
);
}
return _buildMenuCard(items);
}

View File

@ -297,6 +297,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
static const Duration _roomRocketGiftRefreshDelay = Duration(
milliseconds: 700,
);
static const List<Duration> _roomRocketPostLaunchRefreshDelays = [
Duration(milliseconds: 800),
Duration(seconds: 2),
Duration(seconds: 5),
Duration(seconds: 10),
];
static const int _roomPasswordNotTrueErrorCode = 9005;
static const Duration _micListPollingInterval = Duration(seconds: 2);
@ -337,6 +343,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
Timer? _roomEntryEffectTimer;
Timer? _roomRocketLaunchAnimationTimer;
Timer? _roomRocketGiftRefreshTimer;
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
String? _lastRoomRocketLaunchNoticeKey;
Timer? _roomRedPacketPresenceTimer;
Future<void>? _rtcEnginePrewarmTask;
@ -485,6 +492,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_roomRocketLaunchAnimationTimer = null;
_roomRocketGiftRefreshTimer?.cancel();
_roomRocketGiftRefreshTimer = null;
_cancelRoomRocketPostLaunchStatusRefresh();
}
_roomVisualEffectsEnabled = enabled;
notifyListeners();
@ -2112,14 +2120,22 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
final existingVipLevel = _positiveProfileVipLevelHint(currentProfile);
final vipLevel =
existingVipLevel ?? await _loadCurrentVipLevelForColoredId();
final needsVipStatus =
existingVipLevel == null || currentProfile.getHeaddress() == null;
final vipStatus = needsVipStatus ? await _loadCurrentVipStatus() : null;
final vipLevel = existingVipLevel ?? _vipLevelFromStatus(vipStatus);
if (vipLevel == null || vipLevel.isEmpty) {
return;
}
final updatedProfile = currentProfile.copyWith(vipLevel: vipLevel);
if (!_profileHasPositiveVipLevelHint(currentProfile)) {
final updatedProfile = socialChatProfileWithAvatarFrameResource(
currentProfile.copyWith(vipLevel: vipLevel),
_avatarFramePropsFromVipStatus(vipStatus),
expireTime: _expireTimeFromVipStatus(vipStatus),
);
if (!_profileHasPositiveVipLevelHint(currentProfile) ||
currentProfile.getHeaddress()?.sourceUrl !=
updatedProfile.getHeaddress()?.sourceUrl) {
AccountStorage().setCurrentUser(
currentUser.copyWith(userProfile: updatedProfile),
);
@ -2162,18 +2178,18 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
}
Future<String?> _loadCurrentVipLevelForColoredId() async {
Future<SCVipStatusRes?> _loadCurrentVipStatus() async {
final repository = SCVipRepositoryImp();
try {
final home = await repository.vipHome();
final homeVipLevel = _vipLevelFromStatus(home.state);
if (homeVipLevel != null) {
return homeVipLevel;
final state = home.state;
if (state != null && state.levelInt > 0) {
return state;
}
} catch (_) {}
try {
return _vipLevelFromStatus(await repository.vipStatus());
return repository.vipStatus();
} catch (_) {
return null;
}
@ -2190,6 +2206,30 @@ class RealTimeCommunicationManager extends ChangeNotifier {
return level > 0 ? level.toString() : '0';
}
PropsResources? _avatarFramePropsFromVipStatus(SCVipStatusRes? status) {
if (status?.isActive != true) {
return null;
}
final resource = status?.avatarFrame;
return socialChatAvatarFrameProps(
id: resource?.resourceId,
name: resource?.name,
sourceUrl: resource?.sourceResourceUrl,
cover: resource?.coverUrl ?? resource?.cover,
code: "VIP_AVATAR_FRAME",
);
}
String? _expireTimeFromVipStatus(SCVipStatusRes? status) {
final expireAt = status?.expireAt?.trim();
if (expireAt == null || expireAt.isEmpty) {
return null;
}
final normalized =
expireAt.contains("T") ? expireAt : expireAt.replaceFirst(" ", "T");
return DateTime.tryParse(normalized)?.millisecondsSinceEpoch.toString();
}
JoinRoomRes? _mergeKnownVipIntoCurrentRoom(
JoinRoomRes? room,
SocialChatUserProfile currentProfile,
@ -2764,6 +2804,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
userProfileResolver: roomRocketRewardUserProfileForRecord,
onClose: () {
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
scheduleRoomRocketPostLaunchStatusRefresh(roomId: roomId);
if (recordIds.isNotEmpty) {
unawaited(
SCChatRoomRepository().ackRoomRocketRewardPopups(
@ -2817,6 +2858,32 @@ class RealTimeCommunicationManager extends ChangeNotifier {
});
}
void scheduleRoomRocketPostLaunchStatusRefresh({String? roomId}) {
final resolvedRoomId =
(roomId ?? currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
if (resolvedRoomId.isEmpty) {
return;
}
_cancelRoomRocketPostLaunchStatusRefresh();
for (final delay in _roomRocketPostLaunchRefreshDelays) {
final timer = Timer(delay, () {
if (resolvedRoomId !=
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim()) {
return;
}
unawaited(refreshRoomRocketStatus(roomId: resolvedRoomId));
});
_roomRocketPostLaunchRefreshTimers.add(timer);
}
}
void _cancelRoomRocketPostLaunchStatusRefresh() {
for (final timer in _roomRocketPostLaunchRefreshTimers) {
timer.cancel();
}
_roomRocketPostLaunchRefreshTimers.clear();
}
void handleRoomRocketLaunchBroadcast(
RoomRocketLaunchBroadcastMessage launch,
) {
@ -2845,11 +2912,13 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_roomRocketLaunchAnimationTimer = null;
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
scheduleRoomRocketPostLaunchStatusRefresh(roomId: launch.roomId);
});
return;
}
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
scheduleRoomRocketPostLaunchStatusRefresh(roomId: launch.roomId);
}
String? _rocketLaunchAnimationUrlForLevel(int level) {
@ -2992,6 +3061,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_roomRocketLaunchAnimationTimer?.cancel();
_roomRocketLaunchAnimationTimer = null;
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
scheduleRoomRocketPostLaunchStatusRefresh(
roomId: launch.roomId,
);
},
),
);
@ -3572,6 +3644,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
_roomRocketLaunchAnimationTimer = null;
_roomRocketGiftRefreshTimer?.cancel();
_roomRocketGiftRefreshTimer = null;
_cancelRoomRocketPostLaunchStatusRefresh();
_lastRoomRocketLaunchNoticeKey = null;
_stopRoomRedPacketPresenceHeartbeat();
_previewRoomSeatCount = null;

View File

@ -1946,7 +1946,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
}
void _handleVoiceRoomRocketLaunchBroadcast(dynamic payload) {
void _handleVoiceRoomRocketLaunchBroadcast(
dynamic payload, {
bool isRegionBroadcast = false,
}) {
final launch = RoomRocketLaunchBroadcastMessage.fromJson(
_broadcastPayloadMap(payload),
);
@ -1978,10 +1981,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
}
if (isCurrentRoom && !isCurrentVisibleRoom) {
if (isCurrentRoom && !isCurrentVisibleRoom && !isRegionBroadcast) {
return;
}
OverlayManager().addMessage(_buildRoomRocketLaunchFloatingMessage(launch));
OverlayManager().addMessage(
_buildRoomRocketLaunchFloatingMessage(
launch,
isRegionBroadcast: isRegionBroadcast,
),
);
}
void _handleVoiceRoomRocketRewardPopup(dynamic payload) {
@ -2095,6 +2103,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
userProfileResolver: userProfileResolver,
onClose: () {
SmartDialog.dismiss(tag: _roomRocketRewardDialogTag);
_scheduleRoomRocketPostLaunchStatusRefresh(roomId);
if (recordIds.isNotEmpty) {
unawaited(
SCChatRoomRepository().ackRoomRocketRewardPopups(recordIds),
@ -2154,6 +2163,19 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
}
void _scheduleRoomRocketPostLaunchStatusRefresh(String roomId) {
final currentContext = context;
if (currentContext == null || !currentContext.mounted) {
return;
}
try {
Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
).scheduleRoomRocketPostLaunchStatusRefresh(roomId: roomId);
} catch (_) {}
}
List<SCRoomRocketRewardRecordRes> _roomRocketRewardRecordsFromPayload(
Map<String, dynamic> payload,
) {
@ -2183,8 +2205,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
SCFloatingMessage _buildRoomRocketLaunchFloatingMessage(
RoomRocketLaunchBroadcastMessage launch,
) {
RoomRocketLaunchBroadcastMessage launch, {
bool isRegionBroadcast = false,
}) {
return SCFloatingMessage(
type: 3,
roomId: launch.roomId,
@ -2203,6 +2226,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
fallbackUrl: launch.rocketIconUrl,
giftId: launch.launchNo,
durationSeconds: launch.durationSeconds,
broadcastScope:
isRegionBroadcast ? SCFloatingMessage.broadcastScopeRegion : '',
priority: 1000,
);
}
@ -2637,7 +2662,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
var fdata = data["data"];
final payload = _broadcastPayloadMap(fdata);
final launchRoomId = _payloadRoomId(payload);
if (_isCurrentHiddenVoiceRoom(launchRoomId)) {
if (!isRegionBroadcastGroup &&
_isCurrentHiddenVoiceRoom(launchRoomId)) {
return;
}
final rocketIconUrl = _firstNonBlank([
@ -2666,11 +2692,18 @@ class RealTimeMessagingManager extends ChangeNotifier {
userId: _payloadText(payload["actualAccount"]),
giftUrl: rocketIconUrl,
fallbackUrl: rocketIconUrl,
broadcastScope:
isRegionBroadcastGroup
? SCFloatingMessage.broadcastScopeRegion
: '',
priority: 1000,
);
OverlayManager().addMessage(msg);
} else if (type == SCRoomMsgType.voiceRoomRocketLaunchBroadcast) {
_handleVoiceRoomRocketLaunchBroadcast(data["data"]);
_handleVoiceRoomRocketLaunchBroadcast(
data["data"],
isRegionBroadcast: isRegionBroadcastGroup,
);
} else if (type == SCRoomMsgType.voiceRoomRocketStatusUpdate) {
_handleVoiceRoomRocketStatusUpdate(data["data"]);
} else if (type == SCRoomMsgType.voiceRoomRocketRewardPopup) {

View File

@ -165,20 +165,31 @@ class SocialChatUserProfileManager extends ChangeNotifier {
}
final profileVipLevel = profile.vipLevel?.trim();
if (profileVipLevel != null && profileVipLevel.isNotEmpty) {
if (profileVipLevel != null &&
profileVipLevel.isNotEmpty &&
profile.getHeaddress() != null) {
return profile;
}
final vipStatus = await _loadCurrentVipStatus();
final currentVipLevel = currentUserProfile?.vipLevel?.trim();
final resolvedVipLevel =
currentVipLevel != null && currentVipLevel.isNotEmpty
profileVipLevel != null && profileVipLevel.isNotEmpty
? profileVipLevel
: currentVipLevel != null && currentVipLevel.isNotEmpty
? currentVipLevel
: await _loadCurrentVipLevel();
if (resolvedVipLevel == null || resolvedVipLevel.isEmpty) {
return profile;
: _vipLevelFromStatus(vipStatus);
var nextProfile = profile;
if (resolvedVipLevel != null && resolvedVipLevel.isNotEmpty) {
nextProfile = nextProfile.copyWith(vipLevel: resolvedVipLevel);
}
return profile.copyWith(vipLevel: resolvedVipLevel);
return socialChatProfileWithAvatarFrameResource(
nextProfile,
_avatarFramePropsFromVipStatus(vipStatus),
expireTime: _expireTimeFromVipStatus(vipStatus),
);
}
bool _isCurrentUserId(String userId) {
@ -188,23 +199,47 @@ class SocialChatUserProfileManager extends ChangeNotifier {
currentUserId == userId;
}
Future<String?> _loadCurrentVipLevel() async {
Future<SCVipStatusRes?> _loadCurrentVipStatus() async {
final repository = SCVipRepositoryImp();
try {
final home = await repository.vipHome();
final homeVipLevel = _vipLevelFromStatus(home.state);
if (homeVipLevel != null) {
return homeVipLevel;
final state = home.state;
if (state != null && state.levelInt > 0) {
return state;
}
} catch (_) {}
try {
return _vipLevelFromStatus(await repository.vipStatus());
return repository.vipStatus();
} catch (_) {
return null;
}
}
PropsResources? _avatarFramePropsFromVipStatus(SCVipStatusRes? status) {
if (status?.isActive != true) {
return null;
}
final resource = status?.avatarFrame;
return socialChatAvatarFrameProps(
id: resource?.resourceId,
name: resource?.name,
sourceUrl: resource?.sourceResourceUrl,
cover: resource?.coverUrl ?? resource?.cover,
code: "VIP_AVATAR_FRAME",
);
}
String? _expireTimeFromVipStatus(SCVipStatusRes? status) {
final expireAt = status?.expireAt?.trim();
if (expireAt == null || expireAt.isEmpty) {
return null;
}
final normalized =
expireAt.contains("T") ? expireAt : expireAt.replaceFirst(" ", "T");
return DateTime.tryParse(normalized)?.millisecondsSinceEpoch.toString();
}
String? _vipLevelFromStatus(SCVipStatusRes? status) {
if (status == null) {
return null;

View File

@ -492,8 +492,9 @@ class SocialChatUserProfile {
PropsResources? getHeaddress() {
PropsResources? pr;
_useProps?.forEach((value) {
if (value.propsResources?.type == SCPropsType.AVATAR_FRAME.name) {
pr = value.propsResources;
final resource = value.propsResources;
if (socialChatIsAvatarFrameResource(resource)) {
pr = resource;
}
});
return pr;
@ -1249,6 +1250,9 @@ class PropsResources {
}
PropsResources.fromJson(dynamic json) {
if (json is! Map) {
return;
}
_amount = json['amount'];
_code = json['code'];
_cover = json['cover'] ?? json['coverUrl'];
@ -1264,9 +1268,12 @@ class PropsResources {
_roomNotOpenedUrl = json['roomNotOpenedUrl'];
_roomOpenedUrl = json['roomOpenedUrl'];
_roomSendCoverUrl = json['roomSendCoverUrl'];
_sourceUrl = json['sourceUrl'] ?? json['resourceUrl'] ?? json['url'];
_sysOrigin = json['sysOrigin'];
_type = json['type'];
_sourceUrl =
_isAvatarFrameType(_type)
? json['sourceUrl']
: json['sourceUrl'] ?? json['resourceUrl'] ?? json['url'];
_updateTime = json['updateTime'];
}
@ -1395,6 +1402,90 @@ class PropsResources {
}
}
bool socialChatIsAvatarFrameResource(PropsResources? resource) {
if (!_isAvatarFrameType(resource?.type)) {
return false;
}
final sourceUrl = resource?.sourceUrl?.trim() ?? "";
if (sourceUrl.isEmpty) {
return false;
}
return !_looksLikeEntryEffectResource(sourceUrl) &&
!_looksLikeEntryEffectResource(resource?.cover ?? "");
}
PropsResources? socialChatAvatarFrameProps({
String? id,
String? name,
String? sourceUrl,
String? cover,
String? code,
}) {
final resource = PropsResources(
id: id,
name: name,
code: code,
sourceUrl: sourceUrl,
cover: cover,
type: SCPropsType.AVATAR_FRAME.name,
);
return socialChatIsAvatarFrameResource(resource) ? resource : null;
}
SocialChatUserProfile socialChatProfileWithAvatarFrameResource(
SocialChatUserProfile profile,
PropsResources? avatarFrame, {
String? expireTime,
}) {
if (!socialChatIsAvatarFrameResource(avatarFrame)) {
return profile;
}
final currentHeaddress = profile.getHeaddress();
if (currentHeaddress != null) {
return profile;
}
final useProps = <UseProps>[
...?profile.useProps?.where(
(item) => !_isAvatarFrameType(item.propsResources?.type),
),
UseProps(
expireTime: expireTime ?? "4102444800000",
propsResources: avatarFrame,
userId: profile.id,
),
];
return profile.copyWith(useProps: useProps);
}
bool _isAvatarFrameType(String? type) {
return type?.trim().toUpperCase() == SCPropsType.AVATAR_FRAME.name;
}
bool _looksLikeEntryEffectResource(String value) {
if (value.trim().isEmpty) {
return false;
}
final text = _decodeResourceText(value).toLowerCase();
final entryResourcePattern = RegExp(
r'(^|[\/_\-.?=&])(?:entry|entrance|ride|mountain|mountains)([\/_\-.?=&]|$)',
);
return entryResourcePattern.hasMatch(text) ||
text.contains('entryeffect') ||
text.contains('进场') ||
text.contains('坐骑');
}
String _decodeResourceText(String value) {
try {
return Uri.decodeFull(value);
} catch (_) {
return value;
}
}
/// account : ""
/// expiredTime : 0

View File

@ -87,19 +87,24 @@ class SCRoomRocketStatusRes {
}
SCRoomRocketStatusRes.fromJson(dynamic json) {
_roomId = json['roomId'];
_date = json['date'] ?? json['dayKey'];
_roundNo = _intValue(json['roundNo']);
_displayRound = _boolValue(json['displayRound']);
_currentLevel = _intValue(json['currentLevel']);
_needEnergy = _intValue(json['needEnergy']);
_displayPercent = _numValue(json['displayPercent']);
_roomId = json['roomId'] ?? json['room_id'];
_date = json['date'] ?? json['dayKey'] ?? json['day_key'];
_roundNo = _intValue(json['roundNo'] ?? json['round_no']);
_displayRound = _boolValue(json['displayRound'] ?? json['display_round']);
_currentLevel = _intValue(json['currentLevel'] ?? json['current_level']);
_needEnergy = _intValue(json['needEnergy'] ?? json['need_energy']);
_displayPercent = _numValue(
json['displayPercent'] ?? json['display_percent'],
);
_shake = _boolValue(json['shake']);
_level = json['level'] ?? _currentLevel;
_levelName = json['levelName'];
_currentEnergy = _intValue(json['currentEnergy']);
_maxEnergy = _intValue(json['maxEnergy']) ?? _needEnergy;
_energyPercent = _numValue(json['energyPercent']) ?? _displayPercent;
_level = json['level'] ?? json['rocket_level'] ?? _currentLevel;
_levelName = json['levelName'] ?? json['level_name'];
_currentEnergy = _intValue(json['currentEnergy'] ?? json['current_energy']);
_maxEnergy =
_intValue(json['maxEnergy'] ?? json['max_energy']) ?? _needEnergy;
_energyPercent =
_numValue(json['energyPercent'] ?? json['energy_percent']) ??
_displayPercent;
_status = json['status'];
_statusDesc = json['statusDesc'];
_levels = _levelList(json['levels']);
@ -124,6 +129,8 @@ class SCRoomRocketStatusRes {
_firstPayload([
json['rocketKings'],
json['rocket_kings'],
json['rocketKingUsers'],
json['rocket_king_users'],
json['kings'],
json['podium'],
]),
@ -146,18 +153,28 @@ class SCRoomRocketStatusRes {
final resetRemainingMillis = _intValue(
json['resetRemainingMillis'] ??
json['resetRemainMillis'] ??
json['reset_remaining_millis'] ??
json['reset_remain_millis'] ??
json['remainingMillis'] ??
json['remaining_millis'] ??
json['remainMillis'],
);
_resetRemainingSeconds =
_intValue(
json['resetRemainingSeconds'] ??
json['resetRemainSeconds'] ??
json['reset_remaining_seconds'] ??
json['reset_remain_seconds'] ??
json['remainingSeconds'] ??
json['remaining_seconds'] ??
json['remainSeconds'] ??
json['remain_seconds'] ??
json['countdownSeconds'] ??
json['countdown_seconds'] ??
json['resetCountdownSeconds'] ??
json['reset_countdown_seconds'] ??
json['secondsToReset'] ??
json['seconds_to_reset'] ??
json['resetSeconds'],
) ??
(resetRemainingMillis == null

View File

@ -712,11 +712,20 @@ class OverlayManager {
return message.type == 4 && message.isRegionBroadcast;
}
bool _isRegionRoomRocketMessage(SCFloatingMessage message) {
return message.type == 3 && message.isRegionBroadcast;
}
bool _isCrossRoomRegionFloatingMessage(SCFloatingMessage message) {
return _isRegionRedPacketMessage(message) ||
_isRegionRoomRocketMessage(message);
}
bool _shouldDeferRegionRedPacketMessage(
BuildContext context,
SCFloatingMessage message,
) {
if (!_isRegionRedPacketMessage(message) ||
if (!_isCrossRoomRegionFloatingMessage(message) ||
SCGlobalConfig.isFloatingBroadcastClosed) {
return false;
}
@ -828,20 +837,21 @@ class OverlayManager {
messageRoomId.isNotEmpty &&
currentRoomId == messageRoomId;
if (isCurrentVisibleRoom) {
// RTM IM
return message.type == 4;
// / RTM IM
return message.type == 4 || message.type == 3;
}
final isCurrentRoomMessage =
currentRoomId.isNotEmpty &&
messageRoomId.isNotEmpty &&
currentRoomId == messageRoomId;
if (message.type == 4 && isCurrentRoomMessage) {
if ((message.type == 4 || message.type == 3) && isCurrentRoomMessage) {
return false;
}
if (message.type == 4 && rtcProvider.shouldShowRoomVisualEffects) {
if ((message.type == 4 || message.type == 3) &&
rtcProvider.shouldShowRoomVisualEffects) {
return true;
}
if (message.type == 4) {
if (message.type == 4 || message.type == 3) {
return _homeRootTabsVisible;
}
if (SCGlobalConfig.isFloatingBroadcastRoomOnly) {
@ -860,7 +870,8 @@ class OverlayManager {
while (_messageQueue.isNotEmpty) {
final message = _messageQueue.removeFirst();
if (!message.isRegionBroadcast || _isRegionRedPacketMessage(message)) {
if (!message.isRegionBroadcast ||
_isCrossRoomRegionFloatingMessage(message)) {
newQueue.add(message);
}
}

View File

@ -60,7 +60,7 @@ class AccountStorage {
PropsResources? getHeaddress() {
PropsResources? pr;
_currentUser?.userProfile?.useProps?.forEach((value) {
if (value.propsResources?.type == SCPropsType.AVATAR_FRAME.name) {
if (socialChatIsAvatarFrameResource(value.propsResources)) {
///
if (int.parse(value.expireTime ?? "0") >
DateTime.now().millisecondsSinceEpoch) {

View File

@ -198,17 +198,10 @@ class _RoomRocketEntryButtonState extends State<_RoomRocketEntryButton>
SizedBox(
width: 42.w,
height: 8.w,
child:
widget.progressUrl.trim().isEmpty
? _RocketEntryProgressBar(percent: widget.progressPercent)
: ClipRRect(
borderRadius: BorderRadius.circular(5.w),
child: _RocketEntryImage(
source: widget.progressUrl,
loading: false,
fit: BoxFit.fill,
),
),
child: _RocketEntryProgressBar(
percent: widget.progressPercent,
imageUrl: widget.progressUrl,
),
),
],
),
@ -218,13 +211,29 @@ class _RoomRocketEntryButtonState extends State<_RoomRocketEntryButton>
}
class _RocketEntryProgressBar extends StatelessWidget {
const _RocketEntryProgressBar({required this.percent});
const _RocketEntryProgressBar({required this.percent, this.imageUrl = ''});
final double percent;
final String imageUrl;
@override
Widget build(BuildContext context) {
final value = (percent / 100).clamp(0.0, 1.0).toDouble();
final progressImageUrl = imageUrl.trim();
final fill =
progressImageUrl.isEmpty
? const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFB8FFE6), Color(0xFF1BD091)],
),
),
)
: _RocketEntryImage(
source: progressImageUrl,
loading: false,
fit: BoxFit.fill,
);
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.w),
@ -241,13 +250,7 @@ class _RocketEntryProgressBar extends StatelessWidget {
child: FractionallySizedBox(
widthFactor: value,
heightFactor: 1,
child: const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFB8FFE6), Color(0xFF1BD091)],
),
),
),
child: fill,
),
),
),

View File

@ -90,6 +90,38 @@ void main() {
expect(status.rocketKingsForLevel(2), hasLength(3));
});
test('parses refreshed rocket status from snake case payloads', () {
final status = SCRoomRocketStatusRes.fromJson({
'room_id': 'room-1',
'current_level': 2,
'display_percent': 0,
'current_energy': 0,
'need_energy': 200,
'round_no': 4,
'levels': [
{
'level': 1,
'rocket_icon_url': 'icon-1',
'progress_bar_url': 'progress-1',
},
{
'level': 2,
'rocket_icon_url': 'icon-2',
'progress_bar_url': 'progress-2',
},
],
});
final levels = RoomRocketApiMapper.levelsFromStatus(status);
expect(status.roomId, 'room-1');
expect(status.currentLevel, 2);
expect(status.displayPercent, 0);
expect(status.needEnergy, 200);
expect(status.roundNo, 4);
expect(levels?.last.imageUrl, 'icon-2');
expect(levels?.last.progressImageUrl, 'progress-2');
});
test(
'uses top-level reward preview for the response level instead of current level',
() {

View File

@ -6,6 +6,7 @@ import 'package:yumi/shared/business_logic/models/res/room_gift_rank_res.dart';
import 'package:yumi/shared/business_logic/models/res/room_member_res.dart';
import 'package:yumi/shared/business_logic/models/res/room_res.dart';
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
void main() {
group('VIP colored ID', () {
@ -181,5 +182,65 @@ void main() {
expect(mic.user?.vipLevelForColoredId, 5);
expect(mic.user?.shouldShowColoredSpecialIdText(), isTrue);
});
test('keeps avatar frame separate from entry effect resources', () {
final profile = SocialChatUserProfile.fromJson({
'account': '10001',
'useProps': [
{
'propsResources': {
'type': 'AVATAR_FRAME',
'url': 'https://example.test/entry/effect.svga',
},
},
{
'propsResources': {
'type': 'RIDE',
'sourceUrl': 'https://example.test/ride/effect.svga',
},
},
],
});
expect(profile.getHeaddress(), isNull);
});
test('accepts avatar frame from explicit sourceUrl only', () {
final profile = SocialChatUserProfile.fromJson({
'account': '10001',
'useProps': [
{
'propsResources': {
'type': 'AVATAR_FRAME',
'sourceUrl': 'https://example.test/avatar/frame.svga',
},
},
],
});
expect(profile.getHeaddress()?.sourceUrl, contains('avatar/frame.svga'));
});
test('merges active VIP avatar frame into profile props', () {
final profile = SocialChatUserProfile.fromJson({'id': '10001'});
final vipFrame = SCVipResourceRes.fromJson({
'resourceId': 'vip-frame-1',
'url': 'https://example.test/vip/avatar_frame.svga',
'coverUrl': 'https://example.test/vip/avatar_frame.png',
});
final merged = socialChatProfileWithAvatarFrameResource(
profile,
socialChatAvatarFrameProps(
id: vipFrame.resourceId,
sourceUrl: vipFrame.sourceResourceUrl,
cover: vipFrame.coverUrl,
code: 'VIP_AVATAR_FRAME',
),
);
expect(merged.getHeaddress()?.sourceUrl, contains('avatar_frame.svga'));
expect(merged.getHeaddress()?.cover, contains('avatar_frame.png'));
});
});
}