飘窗
This commit is contained in:
parent
95dcd7e625
commit
ccb5579a85
@ -120,6 +120,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
|
||||
static const int _roomRocketLaunchRecentTtlMs = 90000;
|
||||
static const int _roomRocketLaunchLooseRecentTtlMs = 12000;
|
||||
static const int _roomRocketRegionBroadcastRecentTtlMs = 90000;
|
||||
static const List<Duration> _roomRocketRewardPopupRetryDelays = [
|
||||
Duration(milliseconds: 700),
|
||||
Duration(seconds: 2),
|
||||
@ -235,6 +236,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final Map<String, int> _recentLuckGiftPushEventTimes = <String, int>{};
|
||||
final Map<String, int> _recentRoomRocketLaunchTimes = <String, int>{};
|
||||
final Map<String, int> _recentRoomRocketLaunchLooseTimes = <String, int>{};
|
||||
final Map<String, int> _recentRoomRocketRegionBroadcastTimes =
|
||||
<String, int>{};
|
||||
Timer? _roomRocketRewardPopupRetryTimer;
|
||||
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
|
||||
String? roomRedPacketBroadcastGroupId;
|
||||
@ -1936,16 +1939,19 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
payload: data,
|
||||
roomId: roomId,
|
||||
);
|
||||
if (isCurrentVisibleRoom &&
|
||||
_shouldHandleRoomRocketLaunchFromStatusPayload(
|
||||
final shouldHandleLaunch = _shouldHandleRoomRocketLaunchFromStatusPayload(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
launch: launch,
|
||||
);
|
||||
if (shouldHandleLaunch) {
|
||||
_publishRoomRocketRegionBroadcastIfNeeded(launch);
|
||||
if (isCurrentVisibleRoom) {
|
||||
_handleCurrentVisibleRoomRocketLaunch(
|
||||
rtcProvider: rtcProvider,
|
||||
payload: data,
|
||||
launch: launch,
|
||||
)) {
|
||||
_handleCurrentVisibleRoomRocketLaunch(
|
||||
rtcProvider: rtcProvider,
|
||||
launch: launch,
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
unawaited(rtcProvider.refreshRoomRocketStatus(roomId: roomId));
|
||||
}
|
||||
@ -1977,6 +1983,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
rtcProvider.shouldShowRoomVisualEffects &&
|
||||
rtcProvider.isVoiceRoomRouteVisible;
|
||||
if (isCurrentVisibleRoom) {
|
||||
if (!isRegionBroadcast) {
|
||||
_publishRoomRocketRegionBroadcastIfNeeded(launch);
|
||||
}
|
||||
_handleCurrentVisibleRoomRocketLaunch(
|
||||
rtcProvider: rtcProvider,
|
||||
launch: launch,
|
||||
@ -1986,6 +1995,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
if (isCurrentRoom && !isCurrentVisibleRoom && !isRegionBroadcast) {
|
||||
_publishRoomRocketRegionBroadcastIfNeeded(launch);
|
||||
return;
|
||||
}
|
||||
OverlayManager().addMessage(
|
||||
@ -2056,6 +2066,47 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
unawaited(_showRoomRocketRewardPopupDialog(launch.roomId));
|
||||
}
|
||||
|
||||
void _publishRoomRocketRegionBroadcastIfNeeded(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
if (!launch.isValid ||
|
||||
!_shouldCurrentUserPublishRoomRocketRegionBroadcast(launch)) {
|
||||
return;
|
||||
}
|
||||
final key = _roomRocketLaunchNoticeKey(launch);
|
||||
if (key.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
_recentRoomRocketRegionBroadcastTimes.removeWhere(
|
||||
(_, timestamp) => now - timestamp > _roomRocketRegionBroadcastRecentTtlMs,
|
||||
);
|
||||
if (_recentRoomRocketRegionBroadcastTimes.containsKey(key)) {
|
||||
return;
|
||||
}
|
||||
_recentRoomRocketRegionBroadcastTimes[key] = now;
|
||||
unawaited(sendRoomRocketLaunchBroadcast(launch));
|
||||
}
|
||||
|
||||
bool _shouldCurrentUserPublishRoomRocketRegionBroadcast(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
final currentUser = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final currentKeys = <String>{
|
||||
currentUser?.id?.trim() ?? '',
|
||||
currentUser?.account?.trim() ?? '',
|
||||
}..removeWhere((item) => item.isEmpty);
|
||||
final triggerKeys = <String>{
|
||||
launch.triggerUserId.trim(),
|
||||
launch.triggerUser.userId.trim(),
|
||||
launch.triggerUser.account.trim(),
|
||||
}..removeWhere((item) => item.isEmpty);
|
||||
if (triggerKeys.isNotEmpty) {
|
||||
return currentKeys.any(triggerKeys.contains);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String _rocketIconUrlForLevel(int level) {
|
||||
final currentContext = context;
|
||||
if (currentContext == null || !currentContext.mounted) {
|
||||
@ -2228,10 +2279,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
type: 3,
|
||||
roomId: launch.roomId,
|
||||
rocketLevel: launch.safeLevel,
|
||||
userAvatarUrl: _firstNonBlank([
|
||||
launch.roomCoverUrl,
|
||||
launch.triggerUser.avatar,
|
||||
]),
|
||||
userAvatarUrl: launch.roomCoverUrl,
|
||||
userName: _firstNonBlank([launch.roomName, launch.roomAccount]),
|
||||
userId: _firstNonBlank([
|
||||
launch.triggerUser.account,
|
||||
@ -2334,6 +2382,18 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
roomProfile?.roomCover,
|
||||
_payloadAssetText(payload['roomCoverUrl'] ?? payload['room_cover_url']),
|
||||
_payloadAssetText(payload['roomCover'] ?? payload['room_cover']),
|
||||
_payloadAssetText(payload['roomAvatar'] ?? payload['room_avatar']),
|
||||
_payloadAssetText(payload['roomIcon'] ?? payload['room_icon']),
|
||||
_payloadAssetText(payload['coverUrl'] ?? payload['cover_url']),
|
||||
_payloadAssetText(payload['cover']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['room'])['coverUrl']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['room'])['cover']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['room'])['avatar']),
|
||||
_payloadAssetText(
|
||||
_broadcastPayloadMap(payload['roomInfo'])['coverUrl'],
|
||||
),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['roomInfo'])['cover']),
|
||||
_payloadAssetText(_broadcastPayloadMap(payload['roomInfo'])['avatar']),
|
||||
]),
|
||||
roundNo:
|
||||
_payloadNum(payload['roundNo'] ?? payload['round_no'])?.toInt() ?? 0,
|
||||
@ -3377,6 +3437,81 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendRoomRocketLaunchBroadcast(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) async {
|
||||
if (!launch.isValid) {
|
||||
return;
|
||||
}
|
||||
var regionGroupId = roomRedPacketBroadcastGroupId?.trim() ?? '';
|
||||
if (regionGroupId.isEmpty) {
|
||||
await syncRoomRedPacketBroadcastGroup();
|
||||
regionGroupId = roomRedPacketBroadcastGroupId?.trim() ?? '';
|
||||
}
|
||||
if (regionGroupId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final message = <String, dynamic>{
|
||||
'type': SCRoomMsgType.voiceRoomRocketLaunchBroadcast,
|
||||
'data': _roomRocketLaunchBroadcastPayload(launch),
|
||||
};
|
||||
await _sendRoomRocketLaunchCustomMessage(
|
||||
regionGroupId,
|
||||
jsonEncode(message),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _roomRocketLaunchBroadcastPayload(
|
||||
RoomRocketLaunchBroadcastMessage launch,
|
||||
) {
|
||||
final launchNo = _firstNonBlank([
|
||||
launch.launchNo,
|
||||
_roomRocketLaunchNoticeKey(launch),
|
||||
]);
|
||||
return <String, dynamic>{
|
||||
'roomId': launch.roomId,
|
||||
'roomAccount': launch.roomAccount,
|
||||
'roomName': launch.roomName,
|
||||
'roomCoverUrl': launch.roomCoverUrl,
|
||||
'roundNo': launch.roundNo,
|
||||
'level': launch.safeLevel,
|
||||
'rocketLevel': launch.safeLevel,
|
||||
'rocketIconUrl': launch.rocketIconUrl,
|
||||
'rocketAnimationUrl': launch.rocketAnimationUrl,
|
||||
'durationSeconds': launch.durationSeconds,
|
||||
'countdownSeconds': launch.durationSeconds,
|
||||
'triggerUserId': launch.triggerUserId,
|
||||
'triggerUser': <String, dynamic>{
|
||||
'userId': launch.triggerUser.userId,
|
||||
'nickname': launch.triggerUser.nickname,
|
||||
'avatar': launch.triggerUser.avatar,
|
||||
'account': launch.triggerUser.account,
|
||||
'countryCode': launch.triggerUser.countryCode,
|
||||
'countryName': launch.triggerUser.countryName,
|
||||
},
|
||||
'launchNo': launchNo,
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _sendRoomRocketLaunchCustomMessage(
|
||||
String groupId,
|
||||
String data,
|
||||
) async {
|
||||
try {
|
||||
final textMsg = await TencentImSDKPlugin.v2TIMManager
|
||||
.getMessageManager()
|
||||
.createCustomMessage(data: data);
|
||||
if (textMsg.code != 0 || textMsg.data?.id == null) {
|
||||
return;
|
||||
}
|
||||
await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
|
||||
id: textMsg.data!.id!,
|
||||
groupID: groupId,
|
||||
receiver: '',
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _sendRoomRedPacketCustomMessage(
|
||||
String groupId,
|
||||
String data,
|
||||
@ -3453,6 +3588,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
_resetLuckGiftBurstQueue();
|
||||
_recentRoomRocketLaunchTimes.clear();
|
||||
_recentRoomRocketLaunchLooseTimes.clear();
|
||||
_recentRoomRocketRegionBroadcastTimes.clear();
|
||||
_roomRocketRewardPopupRetryTimer?.cancel();
|
||||
_roomRocketRewardPopupRetryTimer = null;
|
||||
onNewMessageListenerGroupMap.forEach((k, v) {
|
||||
|
||||
@ -167,6 +167,9 @@ String _roomCoverUrl(Map<String, dynamic> data) {
|
||||
_asMap(data['room'])['coverUrl'],
|
||||
_asMap(data['room'])['cover'],
|
||||
_asMap(data['room'])['avatar'],
|
||||
_asMap(data['roomInfo'])['coverUrl'],
|
||||
_asMap(data['roomInfo'])['cover'],
|
||||
_asMap(data['roomInfo'])['avatar'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -492,12 +492,29 @@ class OverlayManager {
|
||||
if (previousMessage != null) {
|
||||
visualTop +=
|
||||
_stackedVisualHeightFor(previousMessage) + _stackedFloatingGap;
|
||||
} else if (_shouldReserveStackedSlot(message, key)) {
|
||||
visualTop += _stackedReservedVisualHeightFor(key) + _stackedFloatingGap;
|
||||
}
|
||||
}
|
||||
final wrapperTop = visualTop - _stackedInternalTopFor(message);
|
||||
return wrapperTop < 0 ? 0 : wrapperTop;
|
||||
}
|
||||
|
||||
bool _shouldReserveStackedSlot(SCFloatingMessage message, int slotKey) {
|
||||
return _homeRootTabsVisible &&
|
||||
_isRegionRoomRocketMessage(message) &&
|
||||
slotKey == 4;
|
||||
}
|
||||
|
||||
double _stackedReservedVisualHeightFor(int slotKey) {
|
||||
switch (slotKey) {
|
||||
case 4:
|
||||
return 66;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
double _stackedVisualHeightFor(SCFloatingMessage message) {
|
||||
switch (message.type) {
|
||||
case 0:
|
||||
|
||||
@ -289,6 +289,8 @@ class RoomRocketApiMapper {
|
||||
return RoomRocketRewardWinner(
|
||||
nickname: _rewardWinnerName(entry.value, profile),
|
||||
avatarUrl: _rewardWinnerAvatar(entry.value, profile),
|
||||
isTop1: _hasTop1Reward(entry.value),
|
||||
isIgniter: _hasIgniteReward(entry.value),
|
||||
rewards: rewardItemsFromRecords(entry.value),
|
||||
);
|
||||
})
|
||||
@ -335,6 +337,33 @@ class RoomRocketApiMapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool _hasTop1Reward(List<SCRoomRocketRewardRecordRes> records) {
|
||||
return records.any((record) {
|
||||
final scene = _normalizedRewardScene(record.rewardScene);
|
||||
return scene.contains('TOP1') ||
|
||||
scene.contains('TOPONE') ||
|
||||
scene.contains('RANK1') ||
|
||||
scene.contains('RANKONE') ||
|
||||
scene.contains('FIRST') ||
|
||||
scene.contains('KING');
|
||||
});
|
||||
}
|
||||
|
||||
static bool _hasIgniteReward(List<SCRoomRocketRewardRecordRes> records) {
|
||||
return records.any((record) {
|
||||
final scene = _normalizedRewardScene(record.rewardScene);
|
||||
return scene.contains('IGNITE') ||
|
||||
scene.contains('SETOFF') ||
|
||||
scene.contains('LAUNCH') ||
|
||||
scene.contains('TRIGGER') ||
|
||||
scene.contains('LIGHT');
|
||||
});
|
||||
}
|
||||
|
||||
static String _normalizedRewardScene(String scene) {
|
||||
return scene.trim().toUpperCase().replaceAll(RegExp(r'[\s_\-]+'), '');
|
||||
}
|
||||
|
||||
static String _rewardWinnerKey(SCRoomRocketRewardRecordRes record) {
|
||||
final userId = record.userId.trim();
|
||||
if (userId.isNotEmpty) {
|
||||
|
||||
@ -192,6 +192,8 @@ class RoomRocketRewardWinner {
|
||||
this.avatarAsset,
|
||||
this.avatarUrl,
|
||||
this.frameAsset,
|
||||
this.isTop1 = false,
|
||||
this.isIgniter = false,
|
||||
this.rewards = const <RoomRocketRewardItem>[],
|
||||
});
|
||||
|
||||
@ -199,6 +201,8 @@ class RoomRocketRewardWinner {
|
||||
final String? avatarAsset;
|
||||
final String? avatarUrl;
|
||||
final String? frameAsset;
|
||||
final bool isTop1;
|
||||
final bool isIgniter;
|
||||
final List<RoomRocketRewardItem> rewards;
|
||||
|
||||
RoomRocketRewardWinner withRewards(List<RoomRocketRewardItem> nextRewards) {
|
||||
@ -207,6 +211,8 @@ class RoomRocketRewardWinner {
|
||||
avatarAsset: avatarAsset,
|
||||
avatarUrl: avatarUrl,
|
||||
frameAsset: frameAsset,
|
||||
isTop1: isTop1,
|
||||
isIgniter: isIgniter,
|
||||
rewards: nextRewards,
|
||||
);
|
||||
}
|
||||
@ -442,11 +448,7 @@ class _RewardDialogCanvas extends StatelessWidget {
|
||||
if (index >= winners.length) {
|
||||
return _LoadMoreRow(scale: scale);
|
||||
}
|
||||
return _WinnerRow(
|
||||
scale: scale,
|
||||
winner: winners[index],
|
||||
showRocketBadge: index == 0,
|
||||
);
|
||||
return _WinnerRow(scale: scale, winner: winners[index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -595,15 +597,10 @@ class _RewardDetailCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _WinnerRow extends StatelessWidget {
|
||||
const _WinnerRow({
|
||||
required this.scale,
|
||||
required this.winner,
|
||||
required this.showRocketBadge,
|
||||
});
|
||||
const _WinnerRow({required this.scale, required this.winner});
|
||||
|
||||
final double scale;
|
||||
final RoomRocketRewardWinner winner;
|
||||
final bool showRocketBadge;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -626,21 +623,36 @@ class _WinnerRow extends StatelessWidget {
|
||||
height: 39 * scale,
|
||||
child: _WinnerAvatar(scale: scale, winner: winner),
|
||||
),
|
||||
if (showRocketBadge)
|
||||
if (winner.isTop1)
|
||||
Positioned(
|
||||
left: 40 * scale,
|
||||
top: 26 * scale,
|
||||
left: 17 * scale,
|
||||
top: 39 * scale,
|
||||
width: 39 * scale,
|
||||
height: 9 * scale,
|
||||
child: Text(
|
||||
'top1',
|
||||
textScaler: TextScaler.noScaling,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.visible,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: const Color(0xFF75F3CB),
|
||||
fontSize: 7 * scale,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (winner.isIgniter)
|
||||
Positioned(
|
||||
left: 45 * scale,
|
||||
top: -1 * scale,
|
||||
width: 14 * scale,
|
||||
height: 14 * scale,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(3 * scale),
|
||||
child: Transform.rotate(
|
||||
angle: math.pi / 4,
|
||||
child: Image.asset(
|
||||
_RewardDialogAssets.rocketBody,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Image.asset(
|
||||
_RewardDialogAssets.rocketBody,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
|
||||
@ -267,6 +267,32 @@ void main() {
|
||||
expect(winners.single.avatarUrl, 'https://example.com/avatar.png');
|
||||
});
|
||||
|
||||
test('marks rocket reward winners by reward scene', () {
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords([
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r1',
|
||||
'userId': 'top-user',
|
||||
'userNickname': 'top winner',
|
||||
'rewardScene': 'TOP1',
|
||||
'rewardType': 'GOLD',
|
||||
'rewardAmount': 100,
|
||||
}),
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
'id': 'r2',
|
||||
'userId': 'ignite-user',
|
||||
'userNickname': 'igniter',
|
||||
'rewardScene': 'SET_OFF',
|
||||
'rewardType': 'PROPS',
|
||||
'rewardAmount': 1,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(winners.first.isTop1, isTrue);
|
||||
expect(winners.first.isIgniter, isFalse);
|
||||
expect(winners.last.isTop1, isFalse);
|
||||
expect(winners.last.isIgniter, isTrue);
|
||||
});
|
||||
|
||||
test('keeps nested rocket reward users separated by profile id', () {
|
||||
final winners = RoomRocketApiMapper.winnersFromRecords([
|
||||
SCRoomRocketRewardRecordRes.fromJson({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user