Merge branch 'feature' of gitea.haiyihy.com:hy/chatapp3-flutter into feature

This commit is contained in:
zhx 2026-05-25 22:40:41 +08:00
commit 10d5e821f7
6 changed files with 586 additions and 25 deletions

View File

@ -72,6 +72,7 @@ import 'package:yumi/ui_kit/widgets/room/red_packet/room_red_packet_open_dialog.
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/floating/floating_lucky_gift_global_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_invite_dialog.dart';
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_relation_formed_effect.dart';
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
@ -3691,23 +3692,45 @@ class RealTimeMessagingManager extends ChangeNotifier {
}
SCFloatingMessage _buildLuckyGiftFloatingMessage(
SCBroadCastLuckGiftPush broadCastRes,
) {
SCBroadCastLuckGiftPush broadCastRes, {
int type = 0,
bool isRegionBroadcast = false,
}) {
final rewardData = broadCastRes.data;
final resolvedGiftUrl = _resolveLuckyGiftGiftPhoto(rewardData);
final userId = _firstNonBlank([
rewardData?.sendUserId,
rewardData?.senderUserId,
rewardData?.userId,
rewardData?.account,
]);
final userName = _firstNonBlank([
rewardData?.nickname,
rewardData?.account,
userId,
]);
final eventId = _firstNonBlank([
rewardData?.businessId,
rewardData?.orderId,
rewardData?.msg,
]);
return SCFloatingMessage(
type: 0,
userId: rewardData?.sendUserId,
type: type,
userId: userId,
roomId: rewardData?.roomId,
toUserId: rewardData?.acceptUserId,
userAvatarUrl: rewardData?.userAvatar,
userName: rewardData?.nickname,
userName: userName,
toUserName: rewardData?.acceptNickname,
giftUrl: resolvedGiftUrl,
giftId: rewardData?.giftId,
gameId: eventId,
number: rewardData?.giftQuantity,
coins: rewardData?.awardAmount,
multiple: rewardData?.multiple,
displayText: rewardData?.msg,
broadcastScope:
isRegionBroadcast ? SCFloatingMessage.broadcastScopeRegion : '',
priority: 1000,
);
}
@ -3715,6 +3738,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
void _handleLuckyGiftGlobalNews(
SCBroadCastLuckGiftPush broadCastRes, {
required String source,
bool isRegionBroadcast = false,
}) {
final rewardData = broadCastRes.data;
if (rewardData == null) {
@ -3723,8 +3747,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
if (_isLuckyGiftInCurrentRoom(broadCastRes)) {
addluckGiftPushQueue(broadCastRes);
}
if (!rewardData.shouldShowGlobalNews ||
(rewardData.multiple ?? 0) < _luckyGiftFloatMinMultiple) {
if (!rewardData.shouldShowGlobalNews) {
return;
}
if (source == 'broadcast' && _isLuckyGiftInCurrentRoom(broadCastRes)) {
@ -3736,6 +3759,19 @@ class RealTimeMessagingManager extends ChangeNotifier {
);
return;
}
if (source == 'broadcast') {
OverlayManager().addMessage(
_buildLuckyGiftFloatingMessage(
broadCastRes,
type: FloatingLuckyGiftGlobalScreenWidget.floatingType,
isRegionBroadcast: isRegionBroadcast,
),
);
return;
}
if ((rewardData.multiple ?? 0) < _luckyGiftFloatMinMultiple) {
return;
}
OverlayManager().addMessage(_buildLuckyGiftFloatingMessage(broadCastRes));
}
@ -3832,6 +3868,17 @@ class RealTimeMessagingManager extends ChangeNotifier {
return;
}
final resolvedGiftUrl = _resolveLuckyGiftGiftPhoto(rewardData);
final senderId = _firstNonBlank([
rewardData.sendUserId,
rewardData.senderUserId,
rewardData.userId,
rewardData.account,
]);
final senderName = _firstNonBlank([
rewardData.nickname,
rewardData.account,
senderId,
]);
final roomMsg = Msg(
groupId: '',
msg: '',
@ -3845,8 +3892,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
roomMsg.number = 0;
roomMsg.awardAmount = rewardData.awardAmount;
roomMsg.user = SocialChatUserProfile(
id: rewardData.sendUserId,
userNickname: rewardData.nickname,
id: senderId,
userNickname: senderName,
userAvatar: rewardData.userAvatar,
);
roomMsg.toUser = SocialChatUserProfile(
@ -3869,8 +3916,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
giftTab: 'LUCK',
);
highlightMsg.user = SocialChatUserProfile(
id: rewardData.sendUserId,
userNickname: rewardData.nickname,
id: senderId,
userNickname: senderName,
);
addMsg(highlightMsg);
}
@ -3878,8 +3925,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
addluckGiftPushQueue(broadCastRes);
_handleLuckyGiftGlobalNews(broadCastRes, source: 'room_group');
if (rewardData.sendUserId ==
AccountStorage().getCurrentUser()?.userProfile?.id) {
if (senderId == AccountStorage().getCurrentUser()?.userProfile?.id) {
Provider.of<GiftProvider>(
context!,
listen: false,
@ -5917,7 +5963,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
'multipleType=${broadCastRes.data?.multipleType} '
'globalNews=${broadCastRes.data?.globalNews}',
);
_handleLuckyGiftGlobalNews(broadCastRes, source: 'broadcast');
_handleLuckyGiftGlobalNews(
broadCastRes,
source: 'broadcast',
isRegionBroadcast: isRegionBroadcastGroup,
);
} else if (type == "REGISTER_REWARD_GRANTED") {
await DataPersistence.setPendingRegisterRewardDialog(true);
await DataPersistence.clearAwaitRegisterRewardSocket();

View File

@ -1,5 +1,5 @@
/// data : {"acceptNickname":"Sukabuliete","acceptUserId":"1957345312961527809","account":"8826602","avatarFrameCover":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-33adbddc-b68c-407d-8cbb-3c7b9878b953.png","avatarFrameSvg":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-97edd7e3-0335-4521-8eb0-5a84b009d2a9.svga","balance":1611358.0,"giftCandy":1000.0,"giftCover":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/gifts/manager-effb4f1c-872d-4abf-8797-9987bf206006.png","giftQuantity":1,"globalNews":true,"multiple":1,"multipleType":"WIN","nickname":"Sukabuliete","regionCode":"AF","roomAccount":"8826602","roomId":"1957345511792508929","sendUserId":"1957345312961527809","sysOrigin":"YUMI","userAvatar":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/avatar/0e031d7e-d269-44fe-98eb-543060d37f07.jpg"}
/// type : "GAME_LUCKY_GIFT"
// data : {"acceptNickname":"Sukabuliete","acceptUserId":"1957345312961527809","account":"8826602","avatarFrameCover":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/svga_cover/manager-33adbddc-b68c-407d-8cbb-3c7b9878b953.png","avatarFrameSvg":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/svgasource/manager-97edd7e3-0335-4521-8eb0-5a84b009d2a9.svga","balance":1611358.0,"giftCandy":1000.0,"giftCover":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/gifts/manager-effb4f1c-872d-4abf-8797-9987bf206006.png","giftQuantity":1,"globalNews":true,"multiple":1,"multipleType":"WIN","nickname":"Sukabuliete","regionCode":"AF","roomAccount":"8826602","roomId":"1957345511792508929","sendUserId":"1957345312961527809","sysOrigin":"YUMI","userAvatar":"https://tkm-yumi.oss-ap-southeast-1.aliyuncs.com/avatar/0e031d7e-d269-44fe-98eb-543060d37f07.jpg"}
// type : "GAME_LUCKY_GIFT"
class SCBroadCastLuckGiftPush {
SCBroadCastLuckGiftPush({Data? data, String? type}) {
@ -82,8 +82,15 @@ class Data {
String? roomId,
String? giftId,
String? sendUserId,
String? senderUserId,
String? userId,
String? sysOrigin,
String? userAvatar,
String? businessId,
String? orderId,
String? msg,
num? winAmount,
num? winMultiple,
}) {
_acceptNickname = acceptNickname;
_acceptUserId = acceptUserId;
@ -103,9 +110,16 @@ class Data {
_roomAccount = roomAccount;
_roomId = roomId;
_sendUserId = sendUserId;
_senderUserId = senderUserId;
_userId = userId;
_giftId = giftId;
_sysOrigin = sysOrigin;
_userAvatar = userAvatar;
_businessId = businessId;
_orderId = orderId;
_msg = msg;
_winAmount = winAmount;
_winMultiple = winMultiple;
}
Data.fromJson(dynamic json) {
@ -115,21 +129,37 @@ class Data {
_avatarFrameCover = _asString(json['avatarFrameCover']);
_avatarFrameSvg = _asString(json['avatarFrameSvg']);
_balance = _asNum(json['balance']);
_awardAmount = _asNum(json['awardAmount']);
_awardAmount = _asNum(json['awardAmount'] ?? json['winAmount']);
_giftCandy = _asNum(json['giftCandy']);
_giftCover = _asString(json['giftCover']);
_giftCover = _firstText(
_asString(json['giftCover']),
_firstText(_asString(json['giftUrl']), _asString(json['giftIcon'])),
);
_giftQuantity = _asNum(json['giftQuantity']);
_globalNews = _asBool(json['globalNews']);
_multiple = _asNum(json['multiple']);
_multiple = _asNum(json['multiple'] ?? json['winMultiple']);
_multipleType = _asString(json['multipleType']);
_nickname = _asString(json['nickname']);
_nickname = _firstText(
_asString(json['nickname']),
_firstText(_asString(json['userNickname']), _asString(json['userName'])),
);
_regionCode = _asString(json['regionCode']);
_roomAccount = _asString(json['roomAccount']);
_roomId = _asString(json['roomId']);
_sendUserId = _asString(json['sendUserId']);
_senderUserId = _asString(json['senderUserId']);
_userId = _asString(json['userId']);
_sendUserId = _firstText(
_asString(json['sendUserId']),
_firstText(_senderUserId, _userId),
);
_sysOrigin = _asString(json['sysOrigin']);
_userAvatar = _asString(json['userAvatar']);
_giftId = _asString(json['giftId']);
_businessId = _asString(json['businessId']);
_orderId = _asString(json['orderId']);
_msg = _asString(json['msg']);
_winAmount = _asNum(json['winAmount']);
_winMultiple = _asNum(json['winMultiple']);
}
String? _acceptNickname;
String? _acceptUserId;
@ -149,9 +179,16 @@ class Data {
String? _roomAccount;
String? _roomId;
String? _sendUserId;
String? _senderUserId;
String? _userId;
String? _giftId;
String? _sysOrigin;
String? _userAvatar;
String? _businessId;
String? _orderId;
String? _msg;
num? _winAmount;
num? _winMultiple;
String? get acceptNickname => _acceptNickname;
String? get acceptUserId => _acceptUserId;
String? get account => _account;
@ -171,8 +208,15 @@ class Data {
String? get roomAccount => _roomAccount;
String? get roomId => _roomId;
String? get sendUserId => _sendUserId;
String? get senderUserId => _senderUserId;
String? get userId => _userId;
String? get sysOrigin => _sysOrigin;
String? get userAvatar => _userAvatar;
String? get businessId => _businessId;
String? get orderId => _orderId;
String? get msg => _msg;
num? get winAmount => _winAmount;
num? get winMultiple => _winMultiple;
String get normalizedMultipleType =>
(_multipleType ?? '').trim().toUpperCase();
bool get shouldShowGlobalNews => _globalNews ?? false;
@ -203,8 +247,15 @@ class Data {
roomId: _firstText(_roomId, incoming.roomId),
giftId: _firstText(_giftId, incoming.giftId),
sendUserId: _firstText(_sendUserId, incoming.sendUserId),
senderUserId: _firstText(_senderUserId, incoming.senderUserId),
userId: _firstText(_userId, incoming.userId),
sysOrigin: _firstText(_sysOrigin, incoming.sysOrigin),
userAvatar: _firstText(_userAvatar, incoming.userAvatar),
businessId: _firstText(_businessId, incoming.businessId),
orderId: _firstText(_orderId, incoming.orderId),
msg: _firstText(_msg, incoming.msg),
winAmount: _sumNum(_winAmount, incoming.winAmount),
winMultiple: _maxNum(_winMultiple, incoming.winMultiple),
);
}
@ -313,9 +364,16 @@ class Data {
map['roomAccount'] = _roomAccount;
map['roomId'] = _roomId;
map['sendUserId'] = _sendUserId;
map['senderUserId'] = _senderUserId;
map['userId'] = _userId;
map['sysOrigin'] = _sysOrigin;
map['userAvatar'] = _userAvatar;
map['giftId'] = _giftId;
map['businessId'] = _businessId;
map['orderId'] = _orderId;
map['msg'] = _msg;
map['winAmount'] = _winAmount;
map['winMultiple'] = _winMultiple;
return map;
}
}

View File

@ -14,6 +14,7 @@ import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_game_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_gift_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_lucky_gift_global_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_luck_gift_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart';
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_rocket_screen_widget.dart';
@ -207,6 +208,27 @@ class OverlayManager {
}
return 'game_win_fallback|$roomId|$userId|$gameId|$coins';
}
if (message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType) {
final eventId = message.gameId?.trim() ?? '';
if (eventId.isNotEmpty) {
return 'global_lucky_gift_win|$eventId';
}
final roomId = message.roomId?.trim() ?? '';
final userId = message.userId?.trim() ?? '';
final toUserId = message.toUserId?.trim() ?? '';
final giftId = message.giftId?.trim() ?? '';
final coins = message.coins ?? 0;
final multiple = message.multiple ?? 0;
if (roomId.isEmpty &&
userId.isEmpty &&
toUserId.isEmpty &&
giftId.isEmpty &&
coins == 0 &&
multiple == 0) {
return null;
}
return 'global_lucky_gift_win_fallback|$roomId|$userId|$toUserId|$giftId|$coins|$multiple';
}
if (message.type == 6) {
final roomId = message.roomId?.trim() ?? '';
final action = message.giftId?.trim() ?? '';
@ -226,7 +248,8 @@ class OverlayManager {
}
Duration _floatingDedupWindowFor(SCFloatingMessage message) {
if (message.type == 2) {
if (message.type == 2 ||
message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType) {
return _gameWinFloatingDedupWindow;
}
if (message.type == 6) {
@ -503,6 +526,12 @@ class OverlayManager {
message: message,
onAnimationCompleted: onComplete,
);
case FloatingLuckyGiftGlobalScreenWidget.floatingType:
// Global lucky gift win.
return FloatingLuckyGiftGlobalScreenWidget(
message: message,
onAnimationCompleted: onComplete,
);
case 3:
//
return FloatingRoomRocketScreenWidget(
@ -744,6 +773,12 @@ class OverlayManager {
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(2, roomId: normalizedRoomId);
}
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(
FloatingLuckyGiftGlobalScreenWidget.floatingType,
roomId: normalizedRoomId,
);
}
if (normalizedRoomId.isNotEmpty) {
_removeMessagesByType(4, roomId: normalizedRoomId);
}
@ -803,6 +838,11 @@ class OverlayManager {
return message.type == 2 && message.isRegionBroadcast;
}
bool _isRegionLuckyGiftGlobalMessage(SCFloatingMessage message) {
return message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType &&
message.isRegionBroadcast;
}
bool _isRegionCpGiftMessage(SCFloatingMessage message) {
return message.type == 1 &&
message.isRegionBroadcast &&
@ -817,6 +857,7 @@ class OverlayManager {
return _isRegionRedPacketMessage(message) ||
_isRegionRoomRocketMessage(message) ||
_isRegionGameWinMessage(message) ||
_isRegionLuckyGiftGlobalMessage(message) ||
_isRegionCpGiftMessage(message) ||
_isRegionCpRelationMessage(message);
}
@ -948,19 +989,22 @@ class OverlayManager {
message.type == 3 ||
message.type == 6 ||
_isRegionCpGiftMessage(message);
final isGlobalWinBroadcast =
message.type == 2 ||
message.type == FloatingLuckyGiftGlobalScreenWidget.floatingType;
if (isCurrentVisibleRoom) {
// //CP RTM IM
return isGlobalStackedBroadcast || message.type == 2;
return isGlobalStackedBroadcast || isGlobalWinBroadcast;
}
final isCurrentRoomMessage =
currentRoomId.isNotEmpty &&
messageRoomId.isNotEmpty &&
currentRoomId == messageRoomId;
if ((isGlobalStackedBroadcast || message.type == 2) &&
if ((isGlobalStackedBroadcast || isGlobalWinBroadcast) &&
isCurrentRoomMessage) {
return false;
}
if (message.type == 2) {
if (isGlobalWinBroadcast) {
if (SCGlobalConfig.isFloatingBroadcastRoomOnly) {
return false;
}
@ -1024,6 +1068,8 @@ class OverlayManager {
(activeMessage.type == 0 ||
activeMessage.type == 1 ||
activeMessage.type == 2 ||
activeMessage.type ==
FloatingLuckyGiftGlobalScreenWidget.floatingType ||
activeMessage.type == 3 ||
activeMessage.type == 5 ||
(includeRedPacket && activeMessage.type == 4)) &&
@ -1064,6 +1110,7 @@ class OverlayManager {
type == 0 ||
type == 1 ||
type == 2 ||
type == FloatingLuckyGiftGlobalScreenWidget.floatingType ||
type == 3 ||
type == 5 ||
(includeRedPacket && type == 4);

View File

@ -0,0 +1,375 @@
import 'package:flutter/material.dart';
import 'package:flutter_debouncer/flutter_debouncer.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import 'package:yumi/main.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
import 'package:yumi/shared/tools/sc_room_utils.dart';
import 'package:yumi/ui_kit/widgets/room/rocket/room_rocket_pag_effect_overlay.dart';
class FloatingLuckyGiftGlobalScreenWidget extends StatefulWidget {
const FloatingLuckyGiftGlobalScreenWidget({
super.key,
required this.message,
required this.onAnimationCompleted,
});
static const int floatingType = 7;
static const double bannerWidth = 350;
static const double bannerHeight = 94;
static const String backgroundPagAssetPath =
"sc_images/room/anim/luck_gift/base_lucky_gift_res.pag";
final SCFloatingMessage message;
final VoidCallback onAnimationCompleted;
@override
State<FloatingLuckyGiftGlobalScreenWidget> createState() =>
_FloatingLuckyGiftGlobalScreenWidgetState();
}
class _FloatingLuckyGiftGlobalScreenWidgetState
extends State<FloatingLuckyGiftGlobalScreenWidget>
with TickerProviderStateMixin {
final Debouncer _debouncer = Debouncer();
late final AnimationController _controller;
late final Animation<Offset> _offsetAnimation;
late final AnimationController _swipeController;
late final Animation<Offset> _swipeAnimation;
bool _isSwipeAnimating = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
);
_swipeController = AnimationController(
duration: const Duration(milliseconds: 550),
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(1, 0),
end: const Offset(-1, 0),
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_swipeAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.5, 0),
).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn));
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed && !_isSwipeAnimating) {
widget.onAnimationCompleted();
}
});
_swipeController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
widget.onAnimationCompleted();
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
_swipeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: GestureDetector(
onTap: _handleTap,
onHorizontalDragEnd: _handleHorizontalDragEnd,
child: SlideTransition(
position: _isSwipeAnimating ? _swipeAnimation : _offsetAnimation,
child: SizedBox(
width: FloatingLuckyGiftGlobalScreenWidget.bannerWidth.w,
height: FloatingLuckyGiftGlobalScreenWidget.bannerHeight.w,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(child: _buildBackground()),
Positioned(left: 30.w, top: 22.w, child: _buildAvatar()),
Positioned(
left: 88.w,
top: 26.w,
width: 160.w,
height: 45.w,
child: _buildTextBlock(),
),
Positioned(
right: 23.w,
top: 24.w,
width: 61.w,
height: 45.w,
child: _buildMultiple(),
),
],
),
),
),
),
);
}
Widget _buildBackground() {
return RoomRocketPagPreview(
resource: FloatingLuckyGiftGlobalScreenWidget.backgroundPagAssetPath,
width: FloatingLuckyGiftGlobalScreenWidget.bannerWidth.w,
height: FloatingLuckyGiftGlobalScreenWidget.bannerHeight.w,
fit: BoxFit.fill,
clipBehavior: Clip.none,
defaultBuilder: (_) => _buildFallbackBackground(),
);
}
Widget _buildFallbackBackground() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(47.w),
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xFF2E185E), Color(0xFF6B38D6), Color(0xFF8D5CFF)],
),
border: Border.all(color: const Color(0xFFFFE7A7), width: 1.w),
),
);
}
Widget _buildAvatar() {
const fallbackAsset = "sc_images/general/sc_icon_avar_defalt.png";
final avatarUrl = (widget.message.userAvatarUrl ?? "").trim();
final avatar =
avatarUrl.isEmpty
? Image.asset(
fallbackAsset,
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
)
: Image(
image: buildCachedImageProvider(
avatarUrl,
logicalWidth: 48.w,
logicalHeight: 48.w,
),
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
errorBuilder:
(_, __, ___) => Image.asset(
fallbackAsset,
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
),
);
return Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: const Color(0xFFFFF4C2), width: 1.5.w),
),
clipBehavior: Clip.antiAlias,
child: avatar,
);
}
Widget _buildTextBlock() {
final title = _winnerName();
final subtitle = "wins ${_formatCoins(widget.message.coins)} coins";
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_singleLineText(
title,
TextStyle(
color: Colors.white,
fontSize: 19.sp,
fontWeight: FontWeight.w800,
height: 1,
decoration: TextDecoration.none,
shadows: _textShadows(),
),
),
SizedBox(height: 7.w),
_singleLineText(
subtitle,
TextStyle(
color: Colors.white,
fontSize: 13.sp,
fontWeight: FontWeight.w700,
height: 1,
decoration: TextDecoration.none,
shadows: _textShadows(),
),
),
],
);
}
Widget _buildMultiple() {
return Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"x${_formatMultiple(widget.message.multiple)}",
maxLines: 1,
textScaler: TextScaler.noScaling,
style: TextStyle(
color: Colors.white,
fontSize: 32.sp,
fontWeight: FontWeight.w800,
height: 1,
decoration: TextDecoration.none,
shadows: _textShadows(),
),
),
),
);
}
Widget _singleLineText(String text, TextStyle style) {
return SizedBox(
width: double.infinity,
child: Align(
alignment: Alignment.centerLeft,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.visible,
textScaler: TextScaler.noScaling,
style: style,
),
),
),
);
}
List<Shadow> _textShadows() {
return [
Shadow(
color: const Color(0xFF3A166F).withValues(alpha: 0.9),
blurRadius: 4.w,
offset: Offset(0, 1.w),
),
Shadow(
color: Colors.black.withValues(alpha: 0.24),
blurRadius: 6.w,
offset: Offset(0, 1.5.w),
),
];
}
void _handleTap() {
_debouncer.debounce(
duration: const Duration(milliseconds: 350),
onDebounce: () {
final currentContext = navigatorKey.currentState?.context;
if (currentContext == null || !currentContext.mounted) {
return;
}
final messageRoomId = (widget.message.roomId ?? "").trim();
if (messageRoomId.isEmpty) {
return;
}
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
currentContext,
listen: false,
);
final currentRoomId =
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
final isSameVisibleRoom =
rtcProvider.shouldShowRoomVisualEffects &&
rtcProvider.isVoiceRoomRouteVisible &&
currentRoomId.isNotEmpty &&
currentRoomId == messageRoomId;
if (isSameVisibleRoom) {
return;
}
SCRoomUtils.goRoom(messageRoomId, currentContext, fromFloting: true);
},
);
}
void _handleHorizontalDragEnd(DragEndDetails details) {
final velocity = details.primaryVelocity ?? 0;
if (velocity < 0) {
_handleSwipeLeft();
}
}
void _handleSwipeLeft() {
if (_isSwipeAnimating) {
return;
}
setState(() {
_isSwipeAnimating = true;
});
_controller.stop();
_swipeController.reset();
_swipeController.forward();
}
String _winnerName() {
return _firstNonBlank([
widget.message.userName,
widget.message.userId,
"Lucky Gift",
]);
}
String _firstNonBlank(Iterable<String?> values) {
for (final value in values) {
final text = value?.trim();
if (text != null && text.isNotEmpty) {
return text;
}
}
return "";
}
String _formatMultiple(num? value) {
final multiple = value ?? 0;
if (multiple <= 0) {
return "0";
}
if (multiple == multiple.roundToDouble()) {
return multiple.toInt().toString();
}
return multiple.toStringAsFixed(2);
}
String _formatCoins(num? value) {
final coins = value ?? 0;
if (coins >= 1000000) {
return "${(coins / 1000000).toStringAsFixed(1)}M";
}
if (coins >= 100000) {
return "${(coins / 1000).toStringAsFixed(0)}k";
}
if (coins == coins.roundToDouble()) {
return coins.toInt().toString();
}
return coins.toStringAsFixed(2);
}
}

Binary file not shown.

View File

@ -38,5 +38,36 @@ void main() {
expect(merged.data?.multiple, 30);
expect(merged.data?.multipleType, 'BIG_WIN');
});
test('parses global lucky gift win payload aliases', () {
final message = SCBroadCastLuckGiftPush.fromJson({
'type': 'GAME_LUCKY_GIFT',
'data': {
'acceptUserId': '123456',
'businessId': 'admin-sim-lucky-gift-1779714977048',
'giftId': '1',
'giftQuantity': 1,
'globalNews': true,
'msg': '123456 play lucky_gift get x 10 win 1000 coins!!!',
'multipleType': 'WIN',
'orderId': 'admin-sim-order-1779714977048',
'roomId': '123456',
'senderUserId': '123456',
'userId': '123456',
'winAmount': 1000,
'winMultiple': 10,
},
});
expect(message.data?.sendUserId, '123456');
expect(message.data?.awardAmount, 1000);
expect(message.data?.multiple, 10);
expect(message.data?.shouldShowGlobalNews, isTrue);
expect(message.data?.businessId, 'admin-sim-lucky-gift-1779714977048');
expect(
message.data?.msg,
'123456 play lucky_gift get x 10 win 1000 coins!!!',
);
});
});
}