diff --git a/lib/services/audio/rtm_manager.dart b/lib/services/audio/rtm_manager.dart index b7fcfb3..0acda9d 100644 --- a/lib/services/audio/rtm_manager.dart +++ b/lib/services/audio/rtm_manager.dart @@ -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( 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(); diff --git a/lib/shared/business_logic/models/res/sc_broad_cast_luck_gift_push.dart b/lib/shared/business_logic/models/res/sc_broad_cast_luck_gift_push.dart index b9a08cd..57835f7 100644 --- a/lib/shared/business_logic/models/res/sc_broad_cast_luck_gift_push.dart +++ b/lib/shared/business_logic/models/res/sc_broad_cast_luck_gift_push.dart @@ -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; } } diff --git a/lib/shared/data_sources/sources/local/floating_screen_manager.dart b/lib/shared/data_sources/sources/local/floating_screen_manager.dart index 0de5dc8..b9739a9 100644 --- a/lib/shared/data_sources/sources/local/floating_screen_manager.dart +++ b/lib/shared/data_sources/sources/local/floating_screen_manager.dart @@ -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); diff --git a/lib/ui_kit/widgets/room/floating/floating_lucky_gift_global_screen_widget.dart b/lib/ui_kit/widgets/room/floating/floating_lucky_gift_global_screen_widget.dart new file mode 100644 index 0000000..9b0c82a --- /dev/null +++ b/lib/ui_kit/widgets/room/floating/floating_lucky_gift_global_screen_widget.dart @@ -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 createState() => + _FloatingLuckyGiftGlobalScreenWidgetState(); +} + +class _FloatingLuckyGiftGlobalScreenWidgetState + extends State + with TickerProviderStateMixin { + final Debouncer _debouncer = Debouncer(); + late final AnimationController _controller; + late final Animation _offsetAnimation; + late final AnimationController _swipeController; + late final Animation _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( + begin: const Offset(1, 0), + end: const Offset(-1, 0), + ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); + _swipeAnimation = Tween( + 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 _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( + 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 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); + } +} diff --git a/sc_images/room/anim/luck_gift/base_lucky_gift_res.pag b/sc_images/room/anim/luck_gift/base_lucky_gift_res.pag new file mode 100644 index 0000000..75ceafb Binary files /dev/null and b/sc_images/room/anim/luck_gift/base_lucky_gift_res.pag differ diff --git a/test/lucky_gift_reward_merge_test.dart b/test/lucky_gift_reward_merge_test.dart index 4387829..2fad92e 100644 --- a/test/lucky_gift_reward_merge_test.dart +++ b/test/lucky_gift_reward_merge_test.dart @@ -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!!!', + ); + }); }); }