diff --git a/lib/app/constants/sc_global_config.dart b/lib/app/constants/sc_global_config.dart index 56a6e53..36935da 100644 --- a/lib/app/constants/sc_global_config.dart +++ b/lib/app/constants/sc_global_config.dart @@ -2,9 +2,12 @@ import 'package:yumi/app/config/app_config.dart'; import 'package:yumi/app/config/business_logic_strategy.dart'; class SCGlobalConfig { + static const Set _knownLowPerformanceModels = {"redmi 14c"}; + static String get apiHost => AppConfig.current.apiHost; static String get imgHost => AppConfig.current.imgHost; - static String get privacyAgreementUrl => AppConfig.current.privacyAgreementUrl; + static String get privacyAgreementUrl => + AppConfig.current.privacyAgreementUrl; static String get userAgreementUrl => AppConfig.current.userAgreementUrl; // 以下URL从AppConfig获取 @@ -18,8 +21,10 @@ class SCGlobalConfig { static String get gamesKingUrl => AppConfig.current.gamesKingUrl; // 应用下载链接需要根据Flavor调整 - static String get appDownloadUrlGoogle => AppConfig.current.appDownloadUrlGoogle; - static String get appDownloadUrlApple => AppConfig.current.appDownloadUrlApple; + static String get appDownloadUrlGoogle => + AppConfig.current.appDownloadUrlGoogle; + static String get appDownloadUrlApple => + AppConfig.current.appDownloadUrlApple; ///语言 static String lang = "en"; @@ -33,14 +38,17 @@ class SCGlobalConfig { static String model = "SM-G9550"; static String sysVersion = "9"; static int sdkInt = 28; + static int processorCount = 0; + static bool isLowRamDevice = false; + static bool _isLowPerformanceDevice = false; ///高于这个值才播放特效,避免低性能手机卡顿 static int maxSdkNoAnim = 27; ///渠道SocialChat static String channel = "Google"; - static String origin = "LIKEI"; - static String originChild = "LIKEI"; + static String origin = "LIKEI"; + static String originChild = "LIKEI"; static String get tencentImAppid => AppConfig.current.tencentImAppid; static String get agoraRtcAppid => AppConfig.current.agoraRtcAppid; @@ -48,54 +56,55 @@ class SCGlobalConfig { static num get gameAppid => AppConfig.current.gameAppid; static String get gameAppChannel => AppConfig.current.gameAppChannel; - ///全服广播大群 - static String get bigBroadcastGroup => AppConfig.current.bigBroadcastGroup; - - static String get imAdmin => AppConfig.current.imAdmin; - static const Set _extraSystemUserIds = {"yuminotice"}; - - static String _normalizeConversationUserId(String conversationId) { - return conversationId.startsWith("c2c_") - ? conversationId.replaceFirst("c2c_", "") - : conversationId; - } - - static Set get systemUserIds { - return { - "administrator", - _normalizeConversationUserId(imAdmin), - ..._extraSystemUserIds, - }.where((element) => element.isNotEmpty).toSet(); - } - - static Set get systemConversationIds { - return { - "administrator", - ...systemUserIds - .where((element) => element != "administrator") - .map((element) => "c2c_$element"), - }; - } - - static String get primarySystemUserId => _normalizeConversationUserId(imAdmin); - static String get primarySystemConversationId => "c2c_$primarySystemUserId"; - - static bool isSystemConversationId(String? conversationId) { - if (conversationId == null || conversationId.isEmpty) { - return false; - } - return systemConversationIds.contains(conversationId); - } - - static bool isSystemUserId(String? userId) { - if (userId == null || userId.isEmpty) { - return false; - } - return systemUserIds.contains(userId); - } - - ///财富榜单 - static String get wealthRankUrl => AppConfig.current.wealthRankUrl; + ///全服广播大群 + static String get bigBroadcastGroup => AppConfig.current.bigBroadcastGroup; + + static String get imAdmin => AppConfig.current.imAdmin; + static const Set _extraSystemUserIds = {"yuminotice"}; + + static String _normalizeConversationUserId(String conversationId) { + return conversationId.startsWith("c2c_") + ? conversationId.replaceFirst("c2c_", "") + : conversationId; + } + + static Set get systemUserIds { + return { + "administrator", + _normalizeConversationUserId(imAdmin), + ..._extraSystemUserIds, + }.where((element) => element.isNotEmpty).toSet(); + } + + static Set get systemConversationIds { + return { + "administrator", + ...systemUserIds + .where((element) => element != "administrator") + .map((element) => "c2c_$element"), + }; + } + + static String get primarySystemUserId => + _normalizeConversationUserId(imAdmin); + static String get primarySystemConversationId => "c2c_$primarySystemUserId"; + + static bool isSystemConversationId(String? conversationId) { + if (conversationId == null || conversationId.isEmpty) { + return false; + } + return systemConversationIds.contains(conversationId); + } + + static bool isSystemUserId(String? userId) { + if (userId == null || userId.isEmpty) { + return false; + } + return systemUserIds.contains(userId); + } + + ///财富榜单 + static String get wealthRankUrl => AppConfig.current.wealthRankUrl; ///魅力榜 static String get charmRankUrl => AppConfig.current.charmRankUrl; @@ -119,17 +128,60 @@ class SCGlobalConfig { ///入场秀 static bool _isEntryVehicleAnimation = true; static bool get isEntryVehicleAnimation => _isEntryVehicleAnimation; - static set isEntryVehicleAnimation(bool value) => _isEntryVehicleAnimation = value; + static set isEntryVehicleAnimation(bool value) => + _isEntryVehicleAnimation = value; ///全局飘屏 static bool _isFloatingAnimationInGlobal = true; static bool get isFloatingAnimationInGlobal => _isFloatingAnimationInGlobal; - static set isFloatingAnimationInGlobal(bool value) => _isFloatingAnimationInGlobal = value; + static set isFloatingAnimationInGlobal(bool value) => + _isFloatingAnimationInGlobal = value; ///幸运礼物特效开关 static bool _isLuckGiftSpecialEffects = true; static bool get isLuckGiftSpecialEffects => _isLuckGiftSpecialEffects; - static set isLuckGiftSpecialEffects(bool value) => _isLuckGiftSpecialEffects = value; + static set isLuckGiftSpecialEffects(bool value) => + _isLuckGiftSpecialEffects = value; + + static bool get isLowPerformanceDevice => _isLowPerformanceDevice; + + static bool get allowsHighCostAnimations => + !_isLowPerformanceDevice && sdkInt > maxSdkNoAnim; + + static int get recommendedImageCacheBytes => + _isLowPerformanceDevice ? 40 * 1024 * 1024 : 100 * 1024 * 1024; + + static int get recommendedImageCacheEntries => + _isLowPerformanceDevice ? 80 : 200; + + static void applyDevicePerformanceProfile({ + required bool isLowRamDevice, + required int processorCount, + }) { + final normalizedModel = model.toLowerCase().replaceAll('-', ' '); + final matchesKnownLowPerformanceModel = _knownLowPerformanceModels.any( + normalizedModel.contains, + ); + SCGlobalConfig.isLowRamDevice = isLowRamDevice; + SCGlobalConfig.processorCount = processorCount; + _isLowPerformanceDevice = + isLowRamDevice || + processorCount <= 6 || + matchesKnownLowPerformanceModel; + resetVisualEffectSwitchesToRecommendedDefaults(); + } + + static bool clampVisualEffectPreference(bool enabled) { + return !_isLowPerformanceDevice && enabled; + } + + static void resetVisualEffectSwitchesToRecommendedDefaults() { + final enabled = !_isLowPerformanceDevice; + isEntryVehicleAnimation = enabled; + isGiftSpecialEffects = enabled; + isFloatingAnimationInGlobal = enabled; + isLuckGiftSpecialEffects = enabled; + } /// 获取当前业务逻辑策略 static BusinessLogicStrategy get businessLogicStrategy { diff --git a/lib/main.dart b/lib/main.dart index 5612ff2..ff686eb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -92,6 +92,14 @@ Future _prepareAppShell() async { SCKeybordUtil.init(); // 使用重试机制初始化存储 await _initStore(); + await SCDeviceIdUtils.initDeviceinfo(); + _configureImageCache(); +} + +void _configureImageCache() { + final imageCache = PaintingBinding.instance.imageCache; + imageCache.maximumSizeBytes = SCGlobalConfig.recommendedImageCacheBytes; + imageCache.maximumSize = SCGlobalConfig.recommendedImageCacheEntries; } void _installErrorHandlers() { @@ -286,7 +294,6 @@ class _YumiApplicationState extends State { // 将非首帧必需的初始化统一延后到首帧后 WidgetsBinding.instance.addPostFrameCallback((_) { _initRouter(); - SCDeviceIdUtils.initDeviceinfo(); unawaited(_initLink()); }); } @@ -344,9 +351,9 @@ class _YumiApplicationState extends State { style: TextStyle(fontSize: 12.sp, color: Color(0xff999999)), ); } - return Container( + return SizedBox( height: kTextTabBarHeight + ScreenUtil().statusBarHeight, - child: Center(child: body), + child: SizedBox.expand(child: Center(child: body)), ); }, ); @@ -399,9 +406,10 @@ class _YumiApplicationState extends State { fit: StackFit.expand, children: [ child ?? const SizedBox.shrink(), - const Positioned.fill( - child: VapPlusSvgaPlayer(tag: "room_gift"), - ), + if (SCGlobalConfig.allowsHighCostAnimations) + const Positioned.fill( + child: VapPlusSvgaPlayer(tag: "room_gift"), + ), ], ); }, diff --git a/lib/modules/gift/gift_page.dart b/lib/modules/gift/gift_page.dart index 2f6fdbd..8412eda 100644 --- a/lib/modules/gift/gift_page.dart +++ b/lib/modules/gift/gift_page.dart @@ -15,6 +15,7 @@ import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/config/business_logic_strategy.dart'; import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; +import 'package:yumi/shared/data_sources/sources/remote/net/api.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/main.dart'; @@ -101,6 +102,74 @@ class _GiftPageState extends State debugPrint('[GiftFX][Send] $message'); } + String _describeGiftSendError(Object error) { + if (error is DioException) { + final requestPath = error.requestOptions.path; + final statusCode = error.response?.statusCode; + final responseData = error.response?.data; + return 'dioType=${error.type} ' + 'statusCode=$statusCode ' + 'path=$requestPath ' + 'message=${error.message} ' + 'error=${error.error} ' + 'response=$responseData'; + } + + if (error is NotSuccessException) { + return 'notSuccess message=${error.message}'; + } + + return 'type=${error.runtimeType} error=$error'; + } + + void _showLocalLuckyGiftFeedback( + List acceptUsers, { + required SocialChatGiftRes gift, + required int quantity, + }) { + final rtmProvider = Provider.of( + navigatorKey.currentState!.context, + listen: false, + ); + final currentUser = AccountStorage().getCurrentUser()?.userProfile; + if (currentUser == null) { + _giftFxLog( + 'local lucky feedback skipped reason=no_current_user giftId=${gift.id}', + ); + return; + } + + for (final acceptUser in acceptUsers) { + final targetUser = acceptUser.user; + if (targetUser == null) { + _giftFxLog( + 'local lucky feedback skipped reason=no_target_user giftId=${gift.id}', + ); + continue; + } + _giftFxLog( + 'local lucky feedback trigger ' + 'giftId=${gift.id} ' + 'giftName=${gift.giftName} ' + 'toUserId=${targetUser.id} ' + 'toUserName=${targetUser.userNickname} ' + 'quantity=$quantity', + ); + rtmProvider.msgFloatingGiftListener?.call( + Msg( + groupId: + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, + msg: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", + type: SCRoomMsgType.luckGiftAnimOther, + gift: gift, + user: currentUser, + toUser: targetUser, + number: quantity, + ), + ); + } + } + void _applyGiftSelection(SocialChatGiftRes? gift, {bool notify = true}) { checkedGift = gift; if (gift != null && @@ -331,6 +400,80 @@ class _GiftPageState extends State } } + bool _usesLuckyGiftEndpoint(SocialChatGiftRes? gift) { + final giftTab = (gift?.giftTab ?? '').trim(); + return giftTab == "LUCK" || + giftTab == SCGiftType.LUCKY_GIFT.name || + giftTab == SCGiftType.MAGIC.name; + } + + bool _hasValidLuckyGiftStandardId(SocialChatGiftRes gift) { + final standardId = (gift.standardId ?? '').trim(); + return standardId.isNotEmpty && standardId != '0'; + } + + String _resolveGiftSendErrorMessage(Object error) { + String sanitize(String message) { + var value = message.trim(); + const prefixes = [ + 'Exception: ', + 'DioException: ', + 'DioException [unknown]: ', + ]; + for (final prefix in prefixes) { + if (value.startsWith(prefix)) { + value = value.substring(prefix.length).trim(); + } + } + return value; + } + + if (error is DioException) { + final responseData = error.response?.data; + if (responseData is Map) { + final responseMessage = sanitize( + responseData['errorMsg']?.toString() ?? '', + ); + if (responseMessage.isNotEmpty) { + return responseMessage; + } + } + + if (error.error is NotSuccessException) { + final responseMessage = sanitize( + (error.error as NotSuccessException).message, + ); + if (responseMessage.isNotEmpty) { + return responseMessage; + } + } + + final dioMessage = sanitize(error.message ?? ''); + if (dioMessage.isNotEmpty) { + return dioMessage; + } + + final nestedMessage = sanitize(error.error?.toString() ?? ''); + if (nestedMessage.isNotEmpty) { + return nestedMessage; + } + } + + if (error is NotSuccessException) { + final responseMessage = sanitize(error.message); + if (responseMessage.isNotEmpty) { + return responseMessage; + } + } + + final fallbackMessage = sanitize(error.toString()); + if (fallbackMessage.isNotEmpty) { + return fallbackMessage; + } + + return 'Gift sending failed, please try again.'; + } + @override Widget build(BuildContext context) { return Consumer( @@ -926,7 +1069,7 @@ class _GiftPageState extends State } ///赠送礼物 - void giveGifts() { + void giveGifts() async { List acceptUserIds = []; List acceptUsers = []; @@ -951,60 +1094,164 @@ class _GiftPageState extends State } } if (acceptUserIds.isEmpty) { + _giftFxLog( + 'tap send aborted reason=no_accept_users ' + 'giftId=${checkedGift?.id} ' + 'giftName=${checkedGift?.giftName} ' + 'giftTab=${checkedGift?.giftTab} ' + 'giveType=$giveType', + ); SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectTheRecipient); return; } - if (checkedGift == null) { + final selectedGift = checkedGift; + if (selectedGift == null) { + _giftFxLog( + 'tap send aborted reason=no_selected_gift ' + 'acceptUserIds=${acceptUserIds.join(",")} ' + 'giveType=$giveType', + ); return; } - SCChatRoomRepository() - .giveGift( - acceptUserIds, - checkedGift!.id ?? "", - number, - false, - roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", - ) - .then((result) { - _giftFxLog( - 'giveGift success giftId=${checkedGift?.id} ' - 'giftName=${checkedGift?.giftName} ' - 'giftSourceUrl=${checkedGift?.giftSourceUrl} ' - 'special=${checkedGift?.special} ' - 'giftTab=${checkedGift?.giftTab} ' - 'number=$number ' - 'acceptUserIds=${acceptUserIds.join(",")} ' - 'roomId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id}', - ); - // SCTts.show(SCAppLocalizations.of(context)!.giftGivingSuccessful); - sendGiftMsg(acceptUsers); - Provider.of( - context, - listen: false, - ).updateBalance(result); - }) - .catchError((e) { - _giftFxLog( - 'giveGift failed giftId=${checkedGift?.id} ' - 'giftName=${checkedGift?.giftName} ' - 'error=$e', - ); - }); + final selectedNumber = number; + final roomId = rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? ""; + final roomAccount = + rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? ""; + final isLuckyGiftRequest = _usesLuckyGiftEndpoint(selectedGift); + final requestName = isLuckyGiftRequest ? 'giveLuckyGift' : 'giveGift'; + final senderId = AccountStorage().getCurrentUser()?.userProfile?.id ?? ""; + final senderName = + AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? ""; + final stopwatch = Stopwatch()..start(); + + _giftFxLog( + 'tap send start request=$requestName ' + 'senderId=$senderId ' + 'senderName=$senderName ' + 'giftId=${selectedGift.id} ' + 'giftName=${selectedGift.giftName} ' + 'giftTab=${selectedGift.giftTab} ' + 'special=${selectedGift.special} ' + 'standardId=${selectedGift.standardId} ' + 'giftCandy=${selectedGift.giftCandy} ' + 'number=$selectedNumber ' + 'acceptCount=${acceptUserIds.length} ' + 'acceptUserIds=${acceptUserIds.join(",")} ' + 'roomId=$roomId ' + 'roomAccount=$roomAccount ' + 'giveType=$giveType ' + 'giftType=$giftType', + ); + + if (isLuckyGiftRequest && !_hasValidLuckyGiftStandardId(selectedGift)) { + const configError = + 'Gift configuration unavailable, please try another gift.'; + _giftFxLog( + '$requestName skipped giftId=${selectedGift.id} ' + 'giftName=${selectedGift.giftName} ' + 'giftTab=${selectedGift.giftTab} ' + 'standardId=${selectedGift.standardId} ' + 'reason=invalid_standard_id', + ); + SCTts.show(configError); + return; + } + + try { + final repository = SCChatRoomRepository(); + _giftFxLog( + 'calling repository.$requestName ' + 'giftId=${selectedGift.id} ' + 'roomId=$roomId ' + 'acceptUserIds=${acceptUserIds.join(",")} ' + 'quantity=$selectedNumber', + ); + final result = + isLuckyGiftRequest + ? await repository.giveLuckyGift( + acceptUserIds, + selectedGift.id ?? "", + selectedNumber, + false, + roomId: roomId, + ) + : await repository.giveGift( + acceptUserIds, + selectedGift.id ?? "", + selectedNumber, + false, + roomId: roomId, + ); + _giftFxLog( + '$requestName success giftId=${selectedGift.id} ' + 'giftName=${selectedGift.giftName} ' + 'giftSourceUrl=${selectedGift.giftSourceUrl} ' + 'special=${selectedGift.special} ' + 'giftTab=${selectedGift.giftTab} ' + 'standardId=${selectedGift.standardId} ' + 'number=$selectedNumber ' + 'acceptUserIds=${acceptUserIds.join(",")} ' + 'roomId=$roomId ' + 'balance=$result ' + 'elapsedMs=${stopwatch.elapsedMilliseconds}', + ); + // SCTts.show(SCAppLocalizations.of(context)!.giftGivingSuccessful); + if (isLuckyGiftRequest) { + _showLocalLuckyGiftFeedback( + acceptUsers, + gift: selectedGift, + quantity: selectedNumber, + ); + await sendLuckGiftAnimOtherMsg( + acceptUsers, + gift: selectedGift, + quantity: selectedNumber, + ); + } else { + sendGiftMsg(acceptUsers, gift: selectedGift, quantity: selectedNumber); + } + if (mounted) { + Provider.of( + context, + listen: false, + ).updateBalance(result); + } + } catch (e) { + final errorMessage = _resolveGiftSendErrorMessage(e); + final errorDetails = _describeGiftSendError(e); + _giftFxLog( + '$requestName failed giftId=${selectedGift.id} ' + 'giftName=${selectedGift.giftName} ' + 'giftTab=${selectedGift.giftTab} ' + 'standardId=${selectedGift.standardId} ' + 'error=$e ' + 'resolvedError=$errorMessage ' + 'details={$errorDetails} ' + 'elapsedMs=${stopwatch.elapsedMilliseconds}', + ); + if (mounted) { + SCTts.show(errorMessage); + } + } } - void sendGiftMsg(List acceptUsers) { + void sendGiftMsg( + List acceptUsers, { + required SocialChatGiftRes gift, + required int quantity, + }) { ///发送一条IM消息 for (var u in acceptUsers) { - final special = checkedGift?.special ?? ""; - final giftSourceUrl = checkedGift?.giftSourceUrl ?? ""; + final special = gift.special ?? ""; + final giftSourceUrl = gift.giftSourceUrl ?? ""; final hasSource = giftSourceUrl.isNotEmpty; final hasAnimation = scGiftHasAnimationSpecial(special); final hasGlobalGift = special.contains(SCGiftType.GLOBAL_GIFT.name); final hasFullScreenEffect = scGiftHasFullScreenEffect(special); _giftFxLog( 'dispatch gift msg ' - 'giftId=${checkedGift?.id} ' - 'giftName=${checkedGift?.giftName} ' + 'giftId=${gift.id} ' + 'giftName=${gift.giftName} ' 'toUserId=${u.user?.id} ' 'toUserName=${u.user?.userNickname} ' 'giftSourceUrl=$giftSourceUrl ' @@ -1022,10 +1269,10 @@ class _GiftPageState extends State Msg( groupId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, - gift: checkedGift, + gift: gift, user: AccountStorage().getCurrentUser()?.userProfile, toUser: u.user, - number: number, + number: quantity, type: SCRoomMsgType.gift, role: Provider.of( @@ -1049,7 +1296,7 @@ class _GiftPageState extends State }, ); } - num coins = checkedGift!.giftCandy! * number; + num coins = (gift.giftCandy ?? 0) * quantity; if (coins > 9999) { var fMsg = SCFloatingMessage( type: 1, @@ -1059,42 +1306,42 @@ class _GiftPageState extends State AccountStorage().getCurrentUser()?.userProfile?.userNickname ?? "", toUserName: u.user?.userNickname ?? "", - giftUrl: checkedGift?.giftPhoto ?? "", + giftUrl: gift.giftPhoto ?? "", roomId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", coins: coins, - number: number, + number: quantity, ); OverlayManager().addMessage(fMsg); } - if (checkedGift!.giftSourceUrl != null && checkedGift!.special != null) { - if (scGiftHasFullScreenEffect(checkedGift!.special)) { + if (gift.giftSourceUrl != null && gift.special != null) { + if (scGiftHasFullScreenEffect(gift.special)) { if (SCGlobalConfig.isGiftSpecialEffects) { _giftFxLog( 'local trigger player play ' - 'path=${checkedGift!.giftSourceUrl} ' - 'giftId=${checkedGift?.id} ' - 'giftName=${checkedGift?.giftName}', + 'path=${gift.giftSourceUrl} ' + 'giftId=${gift.id} ' + 'giftName=${gift.giftName}', ); - SCGiftVapSvgaManager().play(checkedGift!.giftSourceUrl!); + SCGiftVapSvgaManager().play(gift.giftSourceUrl!); } else { _giftFxLog( 'skip local play because isGiftSpecialEffects=false ' - 'giftId=${checkedGift?.id}', + 'giftId=${gift.id}', ); } } else { _giftFxLog( 'skip local play because special does not include ' '${SCGiftType.ANIMSCION.name}/$kSCGiftAnimationSpecialAlias/${SCGiftType.GLOBAL_GIFT.name} ' - 'giftId=${checkedGift?.id} special=${checkedGift?.special}', + 'giftId=${gift.id} special=${gift.special}', ); } } else { _giftFxLog( 'skip local play because giftSourceUrl or special is null ' - 'giftId=${checkedGift?.id} ' - 'giftSourceUrl=${checkedGift?.giftSourceUrl} ' - 'special=${checkedGift?.special}', + 'giftId=${gift.id} ' + 'giftSourceUrl=${gift.giftSourceUrl} ' + 'special=${gift.special}', ); } } @@ -1152,18 +1399,48 @@ class _GiftPageState extends State } ///发送一条消息,幸运礼物,房间里所有人都能看到礼物飘向麦位的动画 - void sendLuckGiftAnimOtherMsg(List acceptUsers) { - Provider.of( + Future sendLuckGiftAnimOtherMsg( + List acceptUsers, { + required SocialChatGiftRes gift, + required int quantity, + }) async { + final targetUserIds = + acceptUsers + .map((u) => u.user?.id ?? "") + .where((id) => id.isNotEmpty) + .toList(); + final firstTargetUser = + acceptUsers.isNotEmpty ? acceptUsers.first.user : null; + _giftFxLog( + 'dispatch lucky anim msg ' + 'giftId=${gift.id} ' + 'giftName=${gift.giftName} ' + 'giftPhoto=${gift.giftPhoto} ' + 'quantity=$quantity ' + 'firstTargetUserId=${firstTargetUser?.id} ' + 'groupId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount} ' + 'roomId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id} ' + 'targetUserIds=${targetUserIds.join(",")}', + ); + await Provider.of( navigatorKey.currentState!.context, listen: false, ).dispatchMessage( Msg( groupId: rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount, - gift: checkedGift, + gift: gift, + user: AccountStorage().getCurrentUser()?.userProfile, + toUser: firstTargetUser, + number: quantity, type: SCRoomMsgType.luckGiftAnimOther, msg: jsonEncode(acceptUsers.map((u) => u.user?.id).toList()), ), - addLocal: false, + addLocal: true, + ); + _giftFxLog( + 'dispatch lucky anim msg finished ' + 'giftId=${gift.id} ' + 'targetUserIds=${targetUserIds.join(",")}', ); } } diff --git a/lib/modules/room/voice_room_page.dart b/lib/modules/room/voice_room_page.dart index eba464b..279924a 100644 --- a/lib/modules/room/voice_room_page.dart +++ b/lib/modules/room/voice_room_page.dart @@ -20,6 +20,7 @@ import 'package:yumi/ui_kit/widgets/room/room_head_widget.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import 'package:yumi/ui_kit/widgets/room/room_online_user_widget.dart'; import 'package:yumi/ui_kit/widgets/room/room_play_widget.dart'; +import 'package:yumi/shared/data_sources/models/enum/sc_gift_type.dart'; import '../../ui_kit/components/sc_float_ichart.dart'; import '../../ui_kit/widgets/room/seat/room_seat_widget.dart'; @@ -32,7 +33,7 @@ class VoiceRoomPage extends StatefulWidget { const VoiceRoomPage({super.key}); @override - _VoiceRoomPageState createState() => _VoiceRoomPageState(); + State createState() => _VoiceRoomPageState(); } class _VoiceRoomPageState extends State @@ -258,12 +259,15 @@ class _VoiceRoomPageState extends State return; } var giftModel = LGiftModel(); + final giftTab = (msg.gift?.giftTab ?? '').trim(); giftModel.labelId = "${msg.gift?.id}${msg.user?.id}${msg.toUser?.id}"; giftModel.sendUserName = msg.user?.userNickname ?? ""; giftModel.sendToUserName = msg.toUser?.userNickname ?? ""; giftModel.sendUserPic = msg.user?.userAvatar ?? ""; giftModel.giftPic = msg.gift?.giftPhoto ?? ""; giftModel.giftCount = msg.number ?? 0; + giftModel.isLuckyGift = + giftTab == "LUCK" || giftTab == SCGiftType.LUCKY_GIFT.name; Provider.of( context, listen: false, diff --git a/lib/modules/room_game/views/baishun_game_page.dart b/lib/modules/room_game/views/baishun_game_page.dart index 60a2d96..036e77d 100644 --- a/lib/modules/room_game/views/baishun_game_page.dart +++ b/lib/modules/room_game/views/baishun_game_page.dart @@ -430,7 +430,7 @@ class _BaishunGamePageState extends State { ), child: Container( width: ScreenUtil().screenWidth, - height: ScreenUtil().screenHeight * 0.56, + height: ScreenUtil().screenHeight * 0.75, color: const Color(0xFF081915), child: Column( children: [ diff --git a/lib/services/audio/rtc_manager.dart b/lib/services/audio/rtc_manager.dart index dc65103..1ae2168 100644 --- a/lib/services/audio/rtc_manager.dart +++ b/lib/services/audio/rtc_manager.dart @@ -135,25 +135,25 @@ class RealTimeCommunicationManager extends ChangeNotifier { onUserOffline: (connection, remoteUid, reason) { print('rtc用户 $remoteUid 离开了频道 (原因: ${reason})'); }, - onTokenPrivilegeWillExpire: ( - RtcConnection connection, - String token, - ) async { - var rtcToken = await SCAccountRepository().getRtcToken( - currenRoom?.roomProfile?.roomProfile?.id ?? "", - AccountStorage().getCurrentUser()?.userProfile?.id ?? "", - isPublisher: isOnMai(), - ); - engine?.renewToken(rtcToken.rtcToken ?? ""); - }, + onTokenPrivilegeWillExpire: ( + RtcConnection connection, + String token, + ) async { + var rtcToken = await SCAccountRepository().getRtcToken( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + isPublisher: isOnMai(), + ); + engine?.renewToken(rtcToken.rtcToken ?? ""); + }, onAudioVolumeIndication: initializeAudioVolumeIndicationCallback, ), ); - var rtcToken = await SCAccountRepository().getRtcToken( - currenRoom?.roomProfile?.roomProfile?.id ?? "", - AccountStorage().getCurrentUser()?.userProfile?.id ?? "", - isPublisher: false, - ); + var rtcToken = await SCAccountRepository().getRtcToken( + currenRoom?.roomProfile?.roomProfile?.id ?? "", + AccountStorage().getCurrentUser()?.userProfile?.id ?? "", + isPublisher: false, + ); await engine?.joinChannel( token: rtcToken.rtcToken ?? "", channelId: currenRoom?.roomProfile?.roomProfile?.id ?? "", @@ -635,10 +635,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { DataPersistence.setLastTimeRoomId(""); SCGiftVapSvgaManager().stopPlayback(); SCRoomUtils.closeAllDialogs(); - SCGlobalConfig.isEntryVehicleAnimation = true; - SCGlobalConfig.isGiftSpecialEffects = true; - SCGlobalConfig.isFloatingAnimationInGlobal = true; - SCGlobalConfig.isLuckGiftSpecialEffects = true; + SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults(); } void toggleRemoteAudioMuteForAllUsers() { diff --git a/lib/services/audio/rtm_manager.dart b/lib/services/audio/rtm_manager.dart index a0cdc30..65d7879 100644 --- a/lib/services/audio/rtm_manager.dart +++ b/lib/services/audio/rtm_manager.dart @@ -784,6 +784,16 @@ class RealTimeMessagingManager extends ChangeNotifier { // 发送消息(房间) Future _sendRoomMessage(Msg msg) async { try { + if (msg.type == SCRoomMsgType.luckGiftAnimOther) { + _giftFxLog( + 'send room msg start ' + 'type=${msg.type} ' + 'groupId=${msg.groupId} ' + 'msg=${msg.msg} ' + 'giftId=${msg.gift?.id} ' + 'giftPhoto=${msg.gift?.giftPhoto}', + ); + } if (msg.type == SCRoomMsgType.roomSettingUpdate) { debugPrint( "[Room Cover Sync] send roomSettingUpdate groupId=${msg.groupId ?? ""} roomId=${msg.msg ?? ""}", @@ -809,6 +819,16 @@ class RealTimeMessagingManager extends ChangeNotifier { .getMessageManager() .createCustomMessage(data: jsonEncode(msg.toJson())); + if (msg.type == SCRoomMsgType.luckGiftAnimOther) { + _giftFxLog( + 'send room msg createCustomMessage ' + 'type=${msg.type} ' + 'code=${textMsg.code} ' + 'id=${textMsg.data?.id} ' + 'desc=${textMsg.desc}', + ); + } + if (textMsg.code != 0) return; final sendResult = await TencentImSDKPlugin.v2TIMManager @@ -818,6 +838,16 @@ class RealTimeMessagingManager extends ChangeNotifier { groupID: msg.groupId!, receiver: '', ); + if (msg.type == SCRoomMsgType.luckGiftAnimOther) { + _giftFxLog( + 'send room msg result ' + 'type=${msg.type} ' + 'code=${sendResult.code} ' + 'msgId=${sendResult.data?.msgID} ' + 'groupId=${sendResult.data?.groupID} ' + 'desc=${sendResult.desc}', + ); + } if (sendResult.code == 0) {} } catch (e) { throw Exception("create fail: $e"); @@ -847,14 +877,17 @@ class RealTimeMessagingManager extends ChangeNotifier { roomChatMsgList.removeAt(roomChatMsgList.length - 1); } msgChatListener?.call(msg); - } else if (msg.type == SCRoomMsgType.gift) { + } else if (msg.type == SCRoomMsgType.gift || + msg.type == SCRoomMsgType.luckGiftAnimOther) { roomGiftMsgList.insert(0, msg); if (roomGiftMsgList.length > 250) { print('大于200条消息'); roomGiftMsgList.removeAt(roomGiftMsgList.length - 1); } msgGiftListener?.call(msg); - msgFloatingGiftListener?.call(msg); + if (msg.type == SCRoomMsgType.gift) { + msgFloatingGiftListener?.call(msg); + } } } @@ -1119,6 +1152,17 @@ class RealTimeMessagingManager extends ChangeNotifier { ).fetchOnlineUsersList(); } else if (msg.type == SCRoomMsgType.gameLuckyGift) { var broadCastRes = SCBroadCastLuckGiftPush.fromJson(data); + _giftFxLog( + 'recv GAME_LUCKY_GIFT ' + 'giftId=${broadCastRes.data?.giftId} ' + 'roomId=${broadCastRes.data?.roomId} ' + 'sendUserId=${broadCastRes.data?.sendUserId} ' + 'acceptUserId=${broadCastRes.data?.acceptUserId} ' + 'giftQuantity=${broadCastRes.data?.giftQuantity} ' + 'awardAmount=${broadCastRes.data?.awardAmount} ' + 'multiple=${broadCastRes.data?.multiple} ' + 'multipleType=${broadCastRes.data?.multipleType}', + ); msg.gift = SocialChatGiftRes(giftPhoto: broadCastRes.data?.giftCover); msg.awardAmount = broadCastRes.data?.awardAmount; msg.user = SocialChatUserProfile( @@ -1279,21 +1323,56 @@ class RealTimeMessagingManager extends ChangeNotifier { ); } } else if (msg.type == SCRoomMsgType.luckGiftAnimOther) { - if (Provider.of( - context!, - listen: false, - ).hideLGiftAnimal) { - return; + final hideLGiftAnimal = + Provider.of( + context!, + listen: false, + ).hideLGiftAnimal; + if (hideLGiftAnimal) { + _giftFxLog( + 'recv LUCK_GIFT_ANIM_OTHER skipped ' + 'reason=hideLGiftAnimal ' + 'giftPhoto=${msg.gift?.giftPhoto}', + ); + } else { + final targetUserIds = + (jsonDecode(msg.msg ?? "") as List) + .map((e) => e as String) + .toList(); + _giftFxLog( + 'recv LUCK_GIFT_ANIM_OTHER ' + 'giftPhoto=${msg.gift?.giftPhoto} ' + 'sendUserId=${msg.user?.id} ' + 'toUserId=${msg.toUser?.id} ' + 'quantity=${msg.number} ' + 'targetUserIds=${targetUserIds.join(",")}', + ); + eventBus.fire( + GiveRoomLuckWithOtherEvent( + msg.gift?.giftPhoto ?? "", + targetUserIds, + ), + ); + if (msg.user != null && msg.toUser != null && msg.gift != null) { + _giftFxLog( + 'trigger floating gift listener from LUCK_GIFT_ANIM_OTHER ' + 'sendUserId=${msg.user?.id} ' + 'toUserId=${msg.toUser?.id} ' + 'quantity=${msg.number} ' + 'giftId=${msg.gift?.id}', + ); + msgFloatingGiftListener?.call(msg); + } else { + _giftFxLog( + 'skip floating gift listener from LUCK_GIFT_ANIM_OTHER ' + 'reason=incomplete_msg ' + 'sendUserId=${msg.user?.id} ' + 'toUserId=${msg.toUser?.id} ' + 'giftId=${msg.gift?.id} ' + 'quantity=${msg.number}', + ); + } } - eventBus.fire( - GiveRoomLuckWithOtherEvent( - msg.gift?.giftPhoto ?? "", - (jsonDecode(msg.msg ?? "") as List) - .map((e) => e as String) - .toList(), - ), - ); - return; } else if (msg.type == SCRoomMsgType.roomRoleChange) { ///房间身份变动 Provider.of( diff --git a/lib/shared/data_sources/sources/local/user_manager.dart b/lib/shared/data_sources/sources/local/user_manager.dart index 66ac927..dfc677f 100644 --- a/lib/shared/data_sources/sources/local/user_manager.dart +++ b/lib/shared/data_sources/sources/local/user_manager.dart @@ -1,158 +1,150 @@ -import 'dart:convert'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; -import 'package:yumi/shared/tools/sc_message_utils.dart'; -import 'package:yumi/shared/tools/sc_room_utils.dart'; -import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; -import 'package:yumi/main.dart'; -import 'package:provider/provider.dart'; -import 'package:yumi/app/routes/sc_fluro_navigator.dart'; -import 'package:yumi/shared/business_logic/models/res/login_res.dart'; -import 'package:yumi/modules/auth/login_route.dart'; -import 'package:yumi/services/audio/rtc_manager.dart'; -import 'package:yumi/services/audio/rtm_manager.dart'; -import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart'; - -import '../../../tools/sc_heartbeat_utils.dart'; -import '../../models/enum/sc_props_type.dart'; - -typedef UserManager = AccountStorage; - -class AccountStorage { - static AccountStorage? _instance; - - AccountStorage._internal(); - - factory AccountStorage() { - return _instance ??= AccountStorage._internal(); - } - - String token = ""; - SocialChatLoginRes? _currentUser; - - ///获取当前用户 - SocialChatLoginRes? getCurrentUser() { - if (_currentUser != null) { - return _currentUser; - } - var userJson = DataPersistence.getCurrentUser(); - if (userJson.isNotEmpty) { - _currentUser = SocialChatLoginRes.fromJson(jsonDecode(userJson)); - } - return _currentUser; - } - - ///同步优化数据 - void setCurrentUser(SocialChatLoginRes user) { - _currentUser = user; - setToken(_currentUser!.token ?? ""); - String userJson = jsonEncode(_currentUser?.toJson()); - if (userJson.isNotEmpty) { - DataPersistence.setCurrentUser(userJson); - } - } - - ///获取自己佩戴的头饰信息 - PropsResources? getHeaddress() { - PropsResources? pr; - _currentUser?.userProfile?.useProps?.forEach((value) { - if (value.propsResources?.type == SCPropsType.AVATAR_FRAME.name) { - ///判断有没有过期 - if (int.parse(value.expireTime ?? "0") > - DateTime - .now() - .millisecondsSinceEpoch) { - pr = value.propsResources; - } - } - }); - return pr; - } - - ///获取自己佩戴的气泡框 - PropsResources? getChatbox() { - PropsResources? pr; - _currentUser?.userProfile?.useProps?.forEach((value) { - if (value.propsResources?.type == SCPropsType.CHAT_BUBBLE.name) { - ///判断有没有过期 - if (int.parse(value.expireTime ?? "0") > - DateTime - .now() - .millisecondsSinceEpoch) { - pr = value.propsResources; - } - } - }); - return pr; - } - - ///获取自己佩戴的坐骑信息 - PropsResources? getMountains() { - PropsResources? pr; - _currentUser?.userProfile?.useProps?.forEach((value) { - if (value.propsResources?.type == SCPropsType.RIDE.name) { - ///判断有没有过期 - if (int.parse(value.expireTime ?? "0") > - DateTime - .now() - .millisecondsSinceEpoch) { - pr = value.propsResources; - } - } - }); - return pr; - } - - ///获取自己资料卡信息 - PropsResources? getDataCard() { - PropsResources? pr; - _currentUser?.userProfile?.useProps?.forEach((value) { - if (value.propsResources?.type == SCPropsType.DATA_CARD.name) { - ///判断有没有过期 - if (int.parse(value.expireTime ?? "0") > - DateTime.now().millisecondsSinceEpoch) { - pr = value.propsResources; - } - } - }); - return pr; - } - - String getToken() { - if (token.isNotEmpty) { - return token; - } - token = DataPersistence.getToken(); - return token; - } - - void setToken(String tk) { - token = tk; - DataPersistence.setToken(token); - } - - void _cleanUser() { - token = ""; - _currentUser = null; - DataPersistence.setToken(""); - DataPersistence.setCurrentUser(""); - } - - ///退出登录 - void logout(BuildContext context) { - _cleanUser(); - SCHeartbeatUtils.cancelTimer(); - Provider.of(context, listen: false).exitCurrentVoiceRoomSession(true); - Provider.of(context, listen: false).logout(); - SCNavigatorUtils.push(context, LoginRouter.login, clearStack: true); - SCRoomUtils.closeAllDialogs(); - SCMessageUtils.redPacketFutureCache.clear(); - SCGlobalConfig.isEntryVehicleAnimation = true; - SCGlobalConfig.isGiftSpecialEffects = true; - SCGlobalConfig.isFloatingAnimationInGlobal = true; - SCGlobalConfig.isLuckGiftSpecialEffects = true; - OverlayManager().dispose(); - } -} +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:yumi/app/constants/sc_global_config.dart'; +import 'package:yumi/shared/tools/sc_message_utils.dart'; +import 'package:yumi/shared/tools/sc_room_utils.dart'; +import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart'; +import 'package:provider/provider.dart'; +import 'package:yumi/app/routes/sc_fluro_navigator.dart'; +import 'package:yumi/shared/business_logic/models/res/login_res.dart'; +import 'package:yumi/modules/auth/login_route.dart'; +import 'package:yumi/services/audio/rtc_manager.dart'; +import 'package:yumi/services/audio/rtm_manager.dart'; +import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart'; + +import '../../../tools/sc_heartbeat_utils.dart'; +import '../../models/enum/sc_props_type.dart'; + +typedef UserManager = AccountStorage; + +class AccountStorage { + static AccountStorage? _instance; + + AccountStorage._internal(); + + factory AccountStorage() { + return _instance ??= AccountStorage._internal(); + } + + String token = ""; + SocialChatLoginRes? _currentUser; + + ///获取当前用户 + SocialChatLoginRes? getCurrentUser() { + if (_currentUser != null) { + return _currentUser; + } + var userJson = DataPersistence.getCurrentUser(); + if (userJson.isNotEmpty) { + _currentUser = SocialChatLoginRes.fromJson(jsonDecode(userJson)); + } + return _currentUser; + } + + ///同步优化数据 + void setCurrentUser(SocialChatLoginRes user) { + _currentUser = user; + setToken(_currentUser!.token ?? ""); + String userJson = jsonEncode(_currentUser?.toJson()); + if (userJson.isNotEmpty) { + DataPersistence.setCurrentUser(userJson); + } + } + + ///获取自己佩戴的头饰信息 + PropsResources? getHeaddress() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == SCPropsType.AVATAR_FRAME.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + ///获取自己佩戴的气泡框 + PropsResources? getChatbox() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == SCPropsType.CHAT_BUBBLE.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + ///获取自己佩戴的坐骑信息 + PropsResources? getMountains() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == SCPropsType.RIDE.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + ///获取自己资料卡信息 + PropsResources? getDataCard() { + PropsResources? pr; + _currentUser?.userProfile?.useProps?.forEach((value) { + if (value.propsResources?.type == SCPropsType.DATA_CARD.name) { + ///判断有没有过期 + if (int.parse(value.expireTime ?? "0") > + DateTime.now().millisecondsSinceEpoch) { + pr = value.propsResources; + } + } + }); + return pr; + } + + String getToken() { + if (token.isNotEmpty) { + return token; + } + token = DataPersistence.getToken(); + return token; + } + + void setToken(String tk) { + token = tk; + DataPersistence.setToken(token); + } + + void _cleanUser() { + token = ""; + _currentUser = null; + DataPersistence.setToken(""); + DataPersistence.setCurrentUser(""); + } + + ///退出登录 + void logout(BuildContext context) { + _cleanUser(); + SCHeartbeatUtils.cancelTimer(); + Provider.of( + context, + listen: false, + ).exitCurrentVoiceRoomSession(true); + Provider.of(context, listen: false).logout(); + SCNavigatorUtils.push(context, LoginRouter.login, clearStack: true); + SCRoomUtils.closeAllDialogs(); + SCMessageUtils.redPacketFutureCache.clear(); + SCGlobalConfig.resetVisualEffectSwitchesToRecommendedDefaults(); + OverlayManager().dispose(); + } +} diff --git a/lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart b/lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart index e0e3c0c..6086ad7 100644 --- a/lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart +++ b/lib/shared/data_sources/sources/repositories/sc_room_repository_imp.dart @@ -32,6 +32,7 @@ import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.dart'; import 'package:yumi/shared/business_logic/repositories/room_repository.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; +import 'package:yumi/shared/data_sources/sources/remote/net/api.dart'; import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart'; import 'package:yumi/shared/tools/sc_room_profile_cache.dart'; @@ -44,6 +45,10 @@ class SCChatRoomRepository implements SocialChatRoomRepository { return _instance ??= SCChatRoomRepository._internal(); } + void _giftRepoLog(String message) { + debugPrint('[GiftFX][Repo] $message'); + } + bool _isBrokenLocalMediaUrl(String? url) { return (url ?? "").contains("/external/oss/local/"); } @@ -314,6 +319,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository { final result = await http.post( "daa4f379ff3e429e55c318b6af7291e8", data: params, + extra: const {BaseNetworkClient.silentErrorToastKey: true}, fromJson: (json) => json as double, ); return result; @@ -617,11 +623,27 @@ class SCChatRoomRepository implements SocialChatRoomRepository { params["accepts"] = accepts.toJson(); } + _giftRepoLog( + 'request giveLuckyGift endpoint=/gift/give/lucky-gift ' + 'giftId=$giftId ' + 'roomId=$roomId ' + 'quantity=$quantity ' + 'checkCombo=$checkCombo ' + 'acceptUserIds=${acceptUserIds.join(",")} ' + 'payload=$params', + ); final result = await http.post( "6ddf7ebecfa97adf23c6303877c7763ce591ff710a7abc4fe1e335d16efee21b", data: params, + extra: const {BaseNetworkClient.silentErrorToastKey: true}, fromJson: (json) => json as double, ); + _giftRepoLog( + 'response giveLuckyGift endpoint=/gift/give/lucky-gift ' + 'giftId=$giftId ' + 'roomId=$roomId ' + 'balance=$result', + ); return result; } diff --git a/lib/shared/tools/sc_deviceId_utils.dart b/lib/shared/tools/sc_deviceId_utils.dart index 482d69d..27f126e 100644 --- a/lib/shared/tools/sc_deviceId_utils.dart +++ b/lib/shared/tools/sc_deviceId_utils.dart @@ -7,25 +7,33 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:uuid/uuid.dart'; class SCDeviceIdUtils { - static void initDeviceinfo() { - PackageInfo.fromPlatform().then((info) { - SCGlobalConfig.version = info.version; - SCGlobalConfig.build = info.buildNumber; - if (Platform.isAndroid) { - SCGlobalConfig.channel = "Google"; - DeviceInfoPlugin().androidInfo.then((dev) { - SCGlobalConfig.model = dev.model; - SCGlobalConfig.sysVersion = dev.version.release; - SCGlobalConfig.sdkInt = dev.version.sdkInt; - }); - } else if (Platform.isIOS) { - SCGlobalConfig.channel = "AppStore"; - DeviceInfoPlugin().iosInfo.then((dev) { - SCGlobalConfig.model = dev.model; - SCGlobalConfig.sysVersion = dev.systemVersion; - }); - } - }); + static Future initDeviceinfo() async { + final info = await PackageInfo.fromPlatform(); + SCGlobalConfig.version = info.version; + SCGlobalConfig.build = info.buildNumber; + + final processorCount = Platform.numberOfProcessors; + + if (Platform.isAndroid) { + SCGlobalConfig.channel = "Google"; + final dev = await DeviceInfoPlugin().androidInfo; + SCGlobalConfig.model = dev.model; + SCGlobalConfig.sysVersion = dev.version.release; + SCGlobalConfig.sdkInt = dev.version.sdkInt; + SCGlobalConfig.applyDevicePerformanceProfile( + isLowRamDevice: dev.isLowRamDevice, + processorCount: processorCount, + ); + } else if (Platform.isIOS) { + SCGlobalConfig.channel = "AppStore"; + final dev = await DeviceInfoPlugin().iosInfo; + SCGlobalConfig.model = dev.model; + SCGlobalConfig.sysVersion = dev.systemVersion; + SCGlobalConfig.applyDevicePerformanceProfile( + isLowRamDevice: false, + processorCount: processorCount, + ); + } } // 获取持久化的唯一设备ID diff --git a/lib/shared/tools/sc_gift_vap_svga_manager.dart b/lib/shared/tools/sc_gift_vap_svga_manager.dart index 98c4e08..ff61636 100644 --- a/lib/shared/tools/sc_gift_vap_svga_manager.dart +++ b/lib/shared/tools/sc_gift_vap_svga_manager.dart @@ -286,10 +286,11 @@ class SCGiftVapSvgaManager { 'play request path=$path ext=${SCPathUtils.getFileExtension(path)} ' 'priority=$priority type=$type ' 'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} ' + 'lowPerformance=${SCGlobalConfig.isLowPerformanceDevice} ' 'disposed=$_dis playing=$_play queueBefore=${_tq.length} ' 'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}', ); - if (SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim) { + if (SCGlobalConfig.allowsHighCostAnimations) { if (_dis) { _log('play ignored because manager is disposed path=$path'); return; @@ -307,8 +308,9 @@ class SCGiftVapSvgaManager { } } else { _log( - 'play ignored because sdkInt <= maxSdkNoAnim ' - 'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim}', + 'play ignored because performance profile disabled heavy animations ' + 'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} ' + 'lowPerformance=${SCGlobalConfig.isLowPerformanceDevice}', ); } } diff --git a/lib/shared/tools/sc_room_utils.dart b/lib/shared/tools/sc_room_utils.dart index 2ee806e..173a664 100644 --- a/lib/shared/tools/sc_room_utils.dart +++ b/lib/shared/tools/sc_room_utils.dart @@ -1,302 +1,279 @@ -import 'dart:math'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:yumi/shared/business_logic/models/res/login_res.dart'; -import 'package:provider/provider.dart'; -import 'package:yumi/app_localizations.dart'; -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/modules/wallet/wallet_route.dart'; -import 'package:yumi/modules/room/voice_room_route.dart'; -import 'package:yumi/services/audio/rtc_manager.dart'; -import 'package:yumi/services/auth/user_profile_manager.dart'; -import 'package:yumi/ui_kit/components/sc_float_ichart.dart'; -import 'package:yumi/ui_kit/components/text/sc_text.dart'; -import 'package:yumi/ui_kit/components/sc_tts.dart'; -import 'package:yumi/app/constants/sc_global_config.dart'; -import 'package:yumi/app/routes/sc_fluro_navigator.dart'; -import 'package:yumi/shared/tools/sc_lk_dialog_util.dart'; - - -typedef SCRoomUtils = SCChatRoomHelper; - -class SCChatRoomHelper { - static Map roomUsersMap = {}; - - static void goRoom(String roomId, - BuildContext context, { - bool needOpenRedenvelope = false, - bool fromFloting = false, - String redPackId = "", - }) { - if (Provider - .of(context, listen: false) - .currenRoom != null) { - if (SCFloatIchart().isShow()) { - ///房间最小化了 - if (Provider - .of( - context, - listen: false, - ) - .currenRoom - ?.roomProfile - ?.roomProfile - ?.id == - roomId) { - SCRoomUtils.openCurrentRoom( - context, - needOpenRedenvelope: needOpenRedenvelope, - redPackId: redPackId, - ); - } else { - showEnterRoomConfirm( - roomId, - context, - needOpenRedenvelope: needOpenRedenvelope, - fromFloting: fromFloting, - redPackId: redPackId, - ); - } - } else { - if (Provider - .of( - context, - listen: false, - ) - .currenRoom - ?.roomProfile - ?.roomProfile - ?.id == - roomId) { - - } else { - showEnterRoomConfirm( - roomId, - context, - needOpenRedenvelope: needOpenRedenvelope, - fromFloting: fromFloting, - redPackId: redPackId, - ); - } - } - } else { - showEnterRoomConfirm( - roomId, - context, - needOpenRedenvelope: needOpenRedenvelope, - fromFloting: fromFloting, - redPackId: redPackId, - ); - } - } - - static void openCurrentRoom(BuildContext context, { - bool needOpenRedenvelope = false, - String? redPackId, - }) { - SCFloatIchart().remove(); - Provider.of(context, listen: false) - .loadRoomInfo( - Provider - .of( - context, - listen: false, - ) - .currenRoom - ?.roomProfile - ?.roomProfile - ?.id ?? - "", - ); - Provider.of(context, listen: false) - .fetchUserProfileData(); - Provider.of(context, listen: false) - .retrieveMicrophoneList(); - Provider - .of(context, listen: false) - .closeFullGame = true; - SCNavigatorUtils.push( - context, - '${VoiceRoomRoute.voiceRoom}?id=${Provider - .of(context, listen: false) - .currenRoom - ?.roomProfile - ?.roomProfile - ?.id}', - ); - - } - - static double getCurrenProgress(int currentEnergy, - int maxEnergy, - double maxWidth,) { - var progress = (currentEnergy / maxEnergy); - var curren = progress * maxWidth; - if (curren > maxWidth) { - return maxWidth; - } else { - return curren; - } - } - - static void goRecharge(BuildContext context) { - SmartDialog.dismiss(tag: "showGoToRecharge"); - SmartDialog.show( - tag: "showGoToRecharge", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return Container( - width: ScreenUtil().screenWidth * 0.75, - height: 120.w, - padding: EdgeInsets.symmetric(horizontal: 10.w), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(12.w)), - ), - child: Column( - children: [ - SizedBox(height: 8.w), - Row( - children: [ - Spacer(), - GestureDetector( - child: Icon( - Icons.close, - color: Colors.black, - size: 20.w, - ), - onTap: () { - SmartDialog.dismiss(tag: "showGoToRecharge"); - }, - ), - SizedBox(width: 10.w), - ], - ), - Row( - children: [ - Expanded( - child: text( - SCAppLocalizations.of( - context, - )!.insufhcientGoldsGoToRecharge, - textColor: Colors.black, - textAlign: TextAlign.center, - fontSize: 13.sp, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - SizedBox(height: 10.w), - GestureDetector( - child: Container( - alignment: Alignment.center, - height: 38.w, - width: 125.w, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Color(0xFFFEB219), - Color(0xFFFF9326), - ], - ), - borderRadius: BorderRadius.all(Radius.circular(8.w)), - ), - child: text( - SCAppLocalizations.of(context)!.goToRecharge, - textColor: Colors.white, - fontSize: 13.sp, - fontWeight: FontWeight.w600, - ), - ), - onTap: () { - closeAllDialogs(); - SCNavigatorUtils.push( - context, - WalletRoute.recharge, - replace: false, - ); - }, - ), - ], - ), - ); - }, - ); - } - - static int getRandomInt(int min, int max) { - final random = Random(); - return min + random.nextInt(max - min + 1); - } - - ///房间游客是否可以发消息 - static bool touristCanMsg(BuildContext context) { - ///游客可以发送文字 - if (Provider - .of( - context, - listen: false, - ) - .currenRoom - ?.roomProfile - ?.roomSetting - ?.touristMsg ?? - false) {} else { - if (Provider.of(context, listen: false) - .isTourists()) { - SCTts.show(SCAppLocalizations.of(context)!.touristsCannotSendMessages); - return false; - } - } - return true; - } - - static void roomSCGlobalConfig(String roomId) { - SCGlobalConfig.isGiftSpecialEffects = DataPersistence.getBool( - "${AccountStorage() - .getCurrentUser() - ?.userProfile - ?.account}-GiftSpecialEffects", - defaultValue: true, - ); - SCGlobalConfig.isEntryVehicleAnimation = DataPersistence.getBool( - "${AccountStorage() - .getCurrentUser() - ?.userProfile - ?.account}-EntryVehicleAnimation", - defaultValue: true, - ); - SCGlobalConfig.isFloatingAnimationInGlobal = DataPersistence.getBool( - "${AccountStorage() - .getCurrentUser() - ?.userProfile - ?.account}-FloatingAnimationInGlobal", - defaultValue: true, - ); - SCGlobalConfig.isLuckGiftSpecialEffects = DataPersistence.getBool( - "${AccountStorage() - .getCurrentUser() - ?.userProfile - ?.account}-LuckGiftSpecialEffects", - defaultValue: true, - ); - DataPersistence.setLastTimeRoomId(roomId); - } - - static void closeAllDialogs() async { - // 循环检查并关闭弹窗,直到不存在任何弹窗 - while (SmartDialog.checkExist( - dialogTypes: {SmartAllDialogType.custom, SmartAllDialogType.attach}, - )) { - SmartDialog.dismiss(); - await Future.delayed(const Duration(milliseconds: 50)); - } - } - - -} +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:yumi/shared/business_logic/models/res/login_res.dart'; +import 'package:provider/provider.dart'; +import 'package:yumi/app_localizations.dart'; +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/modules/wallet/wallet_route.dart'; +import 'package:yumi/modules/room/voice_room_route.dart'; +import 'package:yumi/services/audio/rtc_manager.dart'; +import 'package:yumi/services/auth/user_profile_manager.dart'; +import 'package:yumi/ui_kit/components/sc_float_ichart.dart'; +import 'package:yumi/ui_kit/components/text/sc_text.dart'; +import 'package:yumi/ui_kit/components/sc_tts.dart'; +import 'package:yumi/app/constants/sc_global_config.dart'; +import 'package:yumi/app/routes/sc_fluro_navigator.dart'; +import 'package:yumi/shared/tools/sc_lk_dialog_util.dart'; + +typedef SCRoomUtils = SCChatRoomHelper; + +class SCChatRoomHelper { + static Map roomUsersMap = {}; + + static void goRoom( + String roomId, + BuildContext context, { + bool needOpenRedenvelope = false, + bool fromFloting = false, + String redPackId = "", + }) { + if (Provider.of( + context, + listen: false, + ).currenRoom != + null) { + if (SCFloatIchart().isShow()) { + ///房间最小化了 + if (Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id == + roomId) { + SCRoomUtils.openCurrentRoom( + context, + needOpenRedenvelope: needOpenRedenvelope, + redPackId: redPackId, + ); + } else { + showEnterRoomConfirm( + roomId, + context, + needOpenRedenvelope: needOpenRedenvelope, + fromFloting: fromFloting, + redPackId: redPackId, + ); + } + } else { + if (Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id == + roomId) { + } else { + showEnterRoomConfirm( + roomId, + context, + needOpenRedenvelope: needOpenRedenvelope, + fromFloting: fromFloting, + redPackId: redPackId, + ); + } + } + } else { + showEnterRoomConfirm( + roomId, + context, + needOpenRedenvelope: needOpenRedenvelope, + fromFloting: fromFloting, + redPackId: redPackId, + ); + } + } + + static void openCurrentRoom( + BuildContext context, { + bool needOpenRedenvelope = false, + String? redPackId, + }) { + SCFloatIchart().remove(); + Provider.of( + context, + listen: false, + ).loadRoomInfo( + Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomProfile?.id ?? + "", + ); + Provider.of( + context, + listen: false, + ).fetchUserProfileData(); + Provider.of( + context, + listen: false, + ).retrieveMicrophoneList(); + Provider.of(context, listen: false) + .closeFullGame = true; + SCNavigatorUtils.push( + context, + '${VoiceRoomRoute.voiceRoom}?id=${Provider.of(context, listen: false).currenRoom?.roomProfile?.roomProfile?.id}', + ); + } + + static double getCurrenProgress( + int currentEnergy, + int maxEnergy, + double maxWidth, + ) { + var progress = (currentEnergy / maxEnergy); + var curren = progress * maxWidth; + if (curren > maxWidth) { + return maxWidth; + } else { + return curren; + } + } + + static void goRecharge(BuildContext context) { + SmartDialog.dismiss(tag: "showGoToRecharge"); + SmartDialog.show( + tag: "showGoToRecharge", + alignment: Alignment.center, + animationType: SmartAnimationType.fade, + builder: (_) { + return Container( + width: ScreenUtil().screenWidth * 0.75, + height: 120.w, + padding: EdgeInsets.symmetric(horizontal: 10.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(12.w)), + ), + child: Column( + children: [ + SizedBox(height: 8.w), + Row( + children: [ + Spacer(), + GestureDetector( + child: Icon(Icons.close, color: Colors.black, size: 20.w), + onTap: () { + SmartDialog.dismiss(tag: "showGoToRecharge"); + }, + ), + SizedBox(width: 10.w), + ], + ), + Row( + children: [ + Expanded( + child: text( + SCAppLocalizations.of( + context, + )!.insufhcientGoldsGoToRecharge, + textColor: Colors.black, + textAlign: TextAlign.center, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SizedBox(height: 10.w), + GestureDetector( + child: Container( + alignment: Alignment.center, + height: 38.w, + width: 125.w, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFFFEB219), Color(0xFFFF9326)], + ), + borderRadius: BorderRadius.all(Radius.circular(8.w)), + ), + child: text( + SCAppLocalizations.of(context)!.goToRecharge, + textColor: Colors.white, + fontSize: 13.sp, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + closeAllDialogs(); + SCNavigatorUtils.push( + context, + WalletRoute.recharge, + replace: false, + ); + }, + ), + ], + ), + ); + }, + ); + } + + static int getRandomInt(int min, int max) { + final random = Random(); + return min + random.nextInt(max - min + 1); + } + + ///房间游客是否可以发消息 + static bool touristCanMsg(BuildContext context) { + ///游客可以发送文字 + if (Provider.of( + context, + listen: false, + ).currenRoom?.roomProfile?.roomSetting?.touristMsg ?? + false) { + } else { + if (Provider.of( + context, + listen: false, + ).isTourists()) { + SCTts.show(SCAppLocalizations.of(context)!.touristsCannotSendMessages); + return false; + } + } + return true; + } + + static void roomSCGlobalConfig(String roomId) { + final defaultEffectsEnabled = !SCGlobalConfig.isLowPerformanceDevice; + SCGlobalConfig + .isGiftSpecialEffects = SCGlobalConfig.clampVisualEffectPreference( + DataPersistence.getBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-GiftSpecialEffects", + defaultValue: defaultEffectsEnabled, + ), + ); + SCGlobalConfig + .isEntryVehicleAnimation = SCGlobalConfig.clampVisualEffectPreference( + DataPersistence.getBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-EntryVehicleAnimation", + defaultValue: defaultEffectsEnabled, + ), + ); + SCGlobalConfig + .isFloatingAnimationInGlobal = SCGlobalConfig.clampVisualEffectPreference( + DataPersistence.getBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-FloatingAnimationInGlobal", + defaultValue: defaultEffectsEnabled, + ), + ); + SCGlobalConfig + .isLuckGiftSpecialEffects = SCGlobalConfig.clampVisualEffectPreference( + DataPersistence.getBool( + "${AccountStorage().getCurrentUser()?.userProfile?.account}-LuckGiftSpecialEffects", + defaultValue: defaultEffectsEnabled, + ), + ); + DataPersistence.setLastTimeRoomId(roomId); + } + + static void closeAllDialogs() async { + // 循环检查并关闭弹窗,直到不存在任何弹窗 + while (SmartDialog.checkExist( + dialogTypes: {SmartAllDialogType.custom, SmartAllDialogType.attach}, + )) { + SmartDialog.dismiss(); + await Future.delayed(const Duration(milliseconds: 50)); + } + } +} diff --git a/lib/ui_kit/components/custom_cached_image.dart b/lib/ui_kit/components/custom_cached_image.dart index 04052c1..7d0f220 100644 --- a/lib/ui_kit/components/custom_cached_image.dart +++ b/lib/ui_kit/components/custom_cached_image.dart @@ -2,6 +2,21 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart'; +int? _resolveMemCacheDimension(double? logicalSize) { + if (logicalSize == null || !logicalSize.isFinite || logicalSize <= 0) { + return null; + } + final devicePixelRatio = + WidgetsBinding + .instance + .platformDispatcher + .implicitView + ?.devicePixelRatio ?? + 2.0; + final pixelSize = (logicalSize * devicePixelRatio).round(); + return pixelSize.clamp(1, 1920).toInt(); +} + class CustomCachedImage extends StatelessWidget { final String imageUrl; final double? width; @@ -30,6 +45,8 @@ class CustomCachedImage extends StatelessWidget { imageUrl: imageUrl, width: width, height: height, + memCacheWidth: _resolveMemCacheDimension(width), + memCacheHeight: _resolveMemCacheDimension(height), fit: fit, placeholder: (context, url) => placeholder ?? _defaultPlaceholder(), errorWidget: diff --git a/lib/ui_kit/components/sc_compontent.dart b/lib/ui_kit/components/sc_compontent.dart index 1450ecf..2b70ba1 100644 --- a/lib/ui_kit/components/sc_compontent.dart +++ b/lib/ui_kit/components/sc_compontent.dart @@ -18,6 +18,22 @@ import '../../shared/data_sources/models/enum/sc_room_roles_type.dart'; const String kRoomCoverDefaultImg = "sc_images/general/sc_no_data.png"; +int? _resolveImageCacheDimension(double? logicalSize) { + if (logicalSize == null || !logicalSize.isFinite || logicalSize <= 0) { + return null; + } + final devicePixelRatio = + WidgetsBinding + .instance + .platformDispatcher + .implicitView + ?.devicePixelRatio ?? + 2.0; + final maxDimension = SCGlobalConfig.isLowPerformanceDevice ? 1280 : 1920; + final pixelSize = (logicalSize * devicePixelRatio).round(); + return pixelSize.clamp(1, maxDimension).toInt(); +} + String resolveRoomCoverUrl(String? roomId, String? roomCover) { final cache = SCRoomProfileCache.getRoomProfile(roomId ?? ""); return SCRoomProfileCache.preferCachedValue(cache["roomCover"], roomCover) ?? @@ -133,7 +149,7 @@ Widget head({ headdress != null && SCPathUtils.getFileExtension(headdress).toLowerCase() == ".svga" && - SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim + SCGlobalConfig.allowsHighCostAnimations ? IgnorePointer( child: SVGAHeadwearWidget( width: width, @@ -145,7 +161,7 @@ Widget head({ headdress != null && SCPathUtils.getFileExtension(headdress).toLowerCase() == ".mp4" && - SCGlobalConfig.sdkInt > SCGlobalConfig.maxSdkNoAnim + SCGlobalConfig.allowsHighCostAnimations ? SizedBox( width: width, height: width, @@ -187,6 +203,8 @@ Widget netImage({ url, width: width, height: height ?? width, + cacheWidth: _resolveImageCacheDimension(width), + cacheHeight: _resolveImageCacheDimension(height ?? width), headers: buildNetworkImageHeaders(url), fit: fit ?? BoxFit.cover, cache: true, @@ -195,6 +213,7 @@ Widget netImage({ clearMemoryCacheWhenDispose: false, clearMemoryCacheIfFailed: true, border: border, + filterQuality: FilterQuality.low, gaplessPlayback: true, loadStateChanged: (ExtendedImageState state) { if (state.extendedImageLoadState == LoadState.completed) { diff --git a/lib/ui_kit/widgets/room/anim/l_gift_animal_view.dart b/lib/ui_kit/widgets/room/anim/l_gift_animal_view.dart index 80209c3..dfb7541 100644 --- a/lib/ui_kit/widgets/room/anim/l_gift_animal_view.dart +++ b/lib/ui_kit/widgets/room/anim/l_gift_animal_view.dart @@ -7,9 +7,10 @@ import 'package:provider/provider.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/services/gift/gift_animation_manager.dart'; - ///房间礼物滚屏动画 class LGiftAnimalPage extends StatefulWidget { + const LGiftAnimalPage({super.key}); + @override State createState() { return _GiftAnimalPageState(); @@ -18,632 +19,286 @@ class LGiftAnimalPage extends StatefulWidget { class _GiftAnimalPageState extends State with TickerProviderStateMixin { + static const Set _luckyGiftMilestones = {10, 20, 50, 100, 200, 300}; + @override Widget build(BuildContext context) { - return Container( - height: 305.w, + return SizedBox( + height: 332.w, + child: Consumer( + builder: (context, ref, child) { + return Stack( + clipBehavior: Clip.none, + children: List.generate( + 4, + (index) => _buildGiftTickerItem(context, ref, index), + ), + ); + }, + ), + ); + } + + Widget _buildGiftTickerItem( + BuildContext context, + GiftAnimationManager ref, + int index, + ) { + final gift = ref.giftMap[index]; + if (gift == null || ref.animationControllerList.length <= index) { + return const SizedBox.shrink(); + } + final bean = ref.animationControllerList[index]; + return AnimatedBuilder( + animation: bean.controller, + builder: (context, child) { + return Container( + margin: bean.verticalAnimation.value, + width: ScreenUtil().screenWidth * 0.78, + child: _buildGiftTickerCard(context, gift, bean.sizeAnimation.value), + ); + }, + ); + } + + Widget _buildGiftTickerCard( + BuildContext context, + LGiftModel gift, + double animatedSize, + ) { + return SizedBox( + height: 52.w, child: Stack( + alignment: AlignmentDirectional.centerStart, + clipBehavior: Clip.none, children: [ - Consumer( - builder: (context, ref, child) { - return ref.giftMap[0] != null - ? AnimatedBuilder( - animation: ref.animationControllerList[0].controller, - builder: (context, child) { - return Container( - margin: ref.animationControllerList[0].verticalAnimation.value, - width: ScreenUtil().screenWidth * 0.75, - child: Stack( - alignment: AlignmentDirectional.centerStart, - clipBehavior: Clip.none, - children: [ - Container( - padding: EdgeInsetsDirectional.only( - top: 5.w, - ), - width: ScreenUtil().screenWidth * 0.65, - height: - 35.w, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage( - getBg(""), - ), - fit: BoxFit.fill, + Container( + width: ScreenUtil().screenWidth * 0.67, + height: 42.w, + padding: EdgeInsetsDirectional.only( + start: 3.w, + end: 76.w, + top: 3.w, + bottom: 3.w, + ), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(getBg("")), + fit: BoxFit.fill, + ), + ), + child: Row( + children: [ + netImage( + url: gift.sendUserPic, + shape: BoxShape.circle, + width: 26.w, + height: 26.w, + ), + SizedBox(width: 4.w), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + socialchatNickNameText( + maxWidth: 88.w, + gift.sendUserName, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + type: "", + needScroll: gift.sendUserName.characters.length > 8, + ), + SizedBox(height: 1.w), + Row( + children: [ + Text( + "${SCAppLocalizations.of(context)!.sendTo} ", + style: TextStyle( + fontSize: 10.sp, + color: Colors.white, + height: 1.0, ), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 3.w), - netImage( - url: ref.giftMap[0]!.sendUserPic, - shape: BoxShape.circle, - width: 25.w, - height: 25.w, + Flexible( + child: Text( + gift.sendToUserName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10.sp, + color: const Color(0xFFFFD400), + height: 1.0, ), - SizedBox(width: 4.w), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - socialchatNickNameText( - maxWidth: 82.w, - ref.giftMap[0]!.sendUserName ?? "", - fontSize: 12.sp, - fontWeight: FontWeight.w500, - type: "", - needScroll: - (ref - .giftMap[0]! - .sendUserName - .characters - .length ?? - 0) > - 8, - ), - //SizedBox(width: 2.w,), - Row( - children: [ - Text( - "${SCAppLocalizations.of(context)!.sendTo} ", - style: TextStyle( - fontSize: 10.sp, - color: Colors.white, - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 62.w, - ), - child: Text( - "${ref.giftMap[0]?.sendToUserName}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10.sp, - color:Color(0xFFFFD400), - ), - ), - ), - ], - ), - ], - ), - SizedBox(width: 4.w), - ], + ), ), - ), - Positioned( - left: 155.w, - child: Row( - children: [ - netImage( - url: ref.giftMap[0]!.giftPic, - fit: BoxFit.cover, - borderRadius: BorderRadius.circular(4.w), - width: 32.w, - height: 32.w, - ), - SizedBox(width: 12.w), - Text.rich( - TextSpan( - children: [ - TextSpan( - text: "x", - style: TextStyle( - fontSize: - ref - .animationControllerList[0] - .sizeAnimation - .value - .w, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - TextSpan( - text: - "${ref.giftMap[0]!.giftCount}", - style: TextStyle( - fontSize: - ref - .animationControllerList[0] - .sizeAnimation - .value, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ); - }, - ) - : Container(); - }, + ], + ), + ], + ), + ), + ], + ), ), - Consumer( - builder: (context, ref, child) { - return ref.giftMap[1] != null - ? AnimatedBuilder( - animation: ref.animationControllerList[1].controller, - builder: (context, child) { - return Container( - margin: ref.animationControllerList[1].verticalAnimation.value, - width: ScreenUtil().screenWidth * 0.75, - child: Stack( - alignment: AlignmentDirectional.centerStart, - clipBehavior: Clip.none, - children: [ - Container( - padding: EdgeInsetsDirectional.only( - top: - 6.w, - ), - width: ScreenUtil().screenWidth * 0.65, - height: - 35.w, - decoration: BoxDecoration( - // borderRadius: BorderRadius.circular(52), - // gradient: LinearGradient( - // colors: [ - // Color(0xffFFE400), - // Color(0xffFFE400).withOpacity(0.7), - // Color(0xffFFE400).withOpacity(0), - // ] - // ), - image: DecorationImage( - image: AssetImage( - getBg(""), - ), - fit: BoxFit.fill, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 3.w), - netImage( - url: ref.giftMap[1]!.sendUserPic, - shape: BoxShape.circle, - width: 25.w, - height: 25.w, - ), - SizedBox(width: 4.w), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - socialchatNickNameText( - maxWidth: 82.w, - ref.giftMap[1]!.sendUserName ?? "", - fontSize: 12.sp, - fontWeight: FontWeight.w500, - type: "", - needScroll: - (ref - .giftMap[1]! - .sendUserName - .characters - .length ?? - 0) > - 8, - ), - //SizedBox(width: 2.w,), - Row( - children: [ - Text( - "${SCAppLocalizations.of(context)!.sendTo} ", - style: TextStyle( - fontSize: 10.sp, - color: Colors.white, - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 62.w, - ), - child: Text( - "${ref.giftMap[1]?.sendToUserName}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10.sp, - color:Color(0xFFFFD400), - ), - ), - ), - ], - ), - ], - ), - SizedBox(width: 4.w), - ], - ), - ), - Positioned( - left: 155.w, - child: Row( - children: [ - netImage( - url: ref.giftMap[1]!.giftPic, - fit: BoxFit.cover, - borderRadius: BorderRadius.circular(4.w), - width: 32.w, - height: 32.w, - ), - SizedBox(width: 12.w), - Text.rich( - TextSpan( - children: [ - TextSpan( - text: "x", - style: TextStyle( - fontSize: - ref - .animationControllerList[1] - .sizeAnimation - .value - .w, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - TextSpan( - text: - "${ref.giftMap[1]!.giftCount}", - style: TextStyle( - fontSize: - ref - .animationControllerList[1] - .sizeAnimation - .value, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), - ), - ], + PositionedDirectional( + end: 0, + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: ScreenUtil().screenWidth * 0.30, + minHeight: 40.w, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + netImage( + url: gift.giftPic, + fit: BoxFit.cover, + borderRadius: BorderRadius.circular(4.w), + width: 34.w, + height: 34.w, + ), + SizedBox(width: 8.w), + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: _buildGiftCountLabel(gift, animatedSize), ), - ); - }, - ) - : Container(); - }, - ), - Consumer( - builder: (context, ref, child) { - return ref.giftMap[2] != null - ? AnimatedBuilder( - animation: ref.animationControllerList[2].controller, - builder: (context, child) { - return Container( - margin: ref.animationControllerList[2].verticalAnimation.value, - width: ScreenUtil().screenWidth * 0.75, - child: Stack( - alignment: AlignmentDirectional.centerStart, - clipBehavior: Clip.none, - children: [ - Container( - padding: EdgeInsetsDirectional.only( - top: - 6.w, - ), - width: ScreenUtil().screenWidth * 0.65, - height: - 35.w, - decoration: BoxDecoration( - // borderRadius: BorderRadius.circular(52), - // gradient: LinearGradient( - // colors: [ - // Color(0xffFFE400), - // Color(0xffFFE400).withOpacity(0.7), - // Color(0xffFFE400).withOpacity(0), - // ] - // ), - image: DecorationImage( - image: AssetImage( - getBg(""), - ), - fit: BoxFit.fill, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 3.w), - netImage( - url: ref.giftMap[2]!.sendUserPic, - shape: BoxShape.circle, - width: 25.w, - height: 25.w, - ), - SizedBox(width: 4.w), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - socialchatNickNameText( - maxWidth: 82.w, - ref.giftMap[2]!.sendUserName ?? "", - fontSize: 12.sp, - fontWeight: FontWeight.w500, - type: "", - needScroll: - (ref - .giftMap[2]! - .sendUserName - .characters - .length ?? - 0) > - 8, - ), - //SizedBox(width: 2.w,), - Row( - children: [ - Text( - "${SCAppLocalizations.of(context)!.sendTo} ", - style: TextStyle( - fontSize: 10.sp, - color: Colors.white, - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 62.w, - ), - child: Text( - "${ref.giftMap[2]?.sendToUserName}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10.sp, - color:Color(0xFFFFD400), - ), - ), - ), - ], - ), - ], - ), - SizedBox(width: 4.w), - ], - ), - ), - Positioned( - left: 155.w, - child: Row( - children: [ - netImage( - url: ref.giftMap[2]!.giftPic, - fit: BoxFit.cover, - borderRadius: BorderRadius.circular(4.w), - width: 32.w, - height: 32.w, - ), - SizedBox(width: 12.w), - Text.rich( - TextSpan( - children: [ - TextSpan( - text: "x", - style: TextStyle( - fontSize: - ref - .animationControllerList[2] - .sizeAnimation - .value - .w, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - TextSpan( - text: - "${ref.giftMap[2]!.giftCount}", - style: TextStyle( - fontSize: - ref - .animationControllerList[2] - .sizeAnimation - .value, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ); - }, - ) - : Container(); - }, - ), - Consumer( - builder: (context, ref, child) { - return ref.giftMap[3] != null - ? AnimatedBuilder( - animation: ref.animationControllerList[3].controller, - builder: (context, child) { - return Container( - margin: ref.animationControllerList[3].verticalAnimation.value, - width: ScreenUtil().screenWidth * 0.75, - child: Stack( - alignment: AlignmentDirectional.centerStart, - clipBehavior: Clip.none, - children: [ - Container( - padding: EdgeInsetsDirectional.only( - top: - 6.w, - ), - width: ScreenUtil().screenWidth * 0.65, - height: - 35.w, - decoration: BoxDecoration( - // borderRadius: BorderRadius.circular(52), - // gradient: LinearGradient( - // colors: [ - // Color(0xffFFE400), - // Color(0xffFFE400).withOpacity(0.7), - // Color(0xffFFE400).withOpacity(0), - // ] - // ), - image: DecorationImage( - image: AssetImage( - getBg(""), - ), - fit: BoxFit.fill, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 3.w), - netImage( - url: ref.giftMap[3]!.sendUserPic, - shape: BoxShape.circle, - width: 25.w, - height: 25.w, - ), - SizedBox(width: 4.w), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - socialchatNickNameText( - maxWidth: 82.w, - ref.giftMap[3]!.sendUserName ?? "", - fontSize: 12.sp, - fontWeight: FontWeight.w500, - type: "", - needScroll: - (ref - .giftMap[3]! - .sendUserName - .characters - .length ?? - 0) > - 8, - ), - //SizedBox(width: 2.w,), - Row( - children: [ - Text( - "${SCAppLocalizations.of(context)!.sendTo} ", - style: TextStyle( - fontSize: 10.sp, - color: Colors.white, - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 62.w, - ), - child: Text( - "${ref.giftMap[3]?.sendToUserName}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 10.sp, - color:Color(0xFFFFD400), - ), - ), - ), - ], - ), - ], - ), - SizedBox(width: 4.w), - ], - ), - ), - Positioned( - left: 155.w, - child: Row( - children: [ - netImage( - url: ref.giftMap[3]!.giftPic, - fit: BoxFit.cover, - borderRadius: BorderRadius.circular(4.w), - width: 32.w, - height: 32.w, - ), - SizedBox(width: 12.w), - Text.rich( - TextSpan( - children: [ - TextSpan( - text: "x", - style: TextStyle( - fontSize: - ref - .animationControllerList[3] - .sizeAnimation - .value - .w, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - TextSpan( - text: - "${ref.giftMap[3]!.giftCount}", - style: TextStyle( - fontSize: - ref - .animationControllerList[3] - .sizeAnimation - .value, - fontStyle: FontStyle.italic, - color: Color(0xFFFFD400), - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ); - }, - ) - : Container(); - }, + ), + ], + ), + ), ), ], ), ); } + Widget _buildGiftCountLabel(LGiftModel gift, double animatedSize) { + final isMilestone = _isLuckyGiftMilestone(gift); + final xFontSize = animatedSize * (isMilestone ? 1.1 : 1.0); + final countFontSize = animatedSize * (isMilestone ? 1.45 : 1.0); + return Directionality( + textDirection: TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "x", + style: TextStyle( + fontSize: xFontSize, + fontStyle: FontStyle.italic, + color: + isMilestone + ? const Color(0xFFFFE08A) + : const Color(0xFFFFD400), + fontWeight: FontWeight.bold, + height: 1, + shadows: + isMilestone + ? const [ + Shadow( + color: Color(0xCC7A3E00), + blurRadius: 8, + offset: Offset(0, 2), + ), + ] + : null, + ), + ), + SizedBox(width: 2.w), + isMilestone + ? _buildMilestoneGiftCountText( + _giftCountText(gift.giftCount), + fontSize: countFontSize, + ) + : Text( + _giftCountText(gift.giftCount), + style: TextStyle( + fontSize: countFontSize, + fontStyle: FontStyle.italic, + color: const Color(0xFFFFD400), + fontWeight: FontWeight.bold, + height: 1, + ), + ), + ], + ), + ); + } + + Widget _buildMilestoneGiftCountText( + String countText, { + required double fontSize, + }) { + return Stack( + children: [ + Text( + countText, + style: TextStyle( + fontSize: fontSize, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w900, + height: 1, + foreground: + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1.8 + ..color = const Color(0xFF7A3E00), + ), + ), + ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: + (bounds) => const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFFFF6C7), + Color(0xFFFFE07A), + Color(0xFFFFB800), + ], + ).createShader(bounds), + child: Text( + countText, + style: TextStyle( + fontSize: fontSize, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w900, + color: Colors.white, + height: 1, + shadows: const [ + Shadow( + color: Color(0xCC6A3700), + blurRadius: 10, + offset: Offset(0, 3), + ), + ], + ), + ), + ), + ], + ); + } + + bool _isLuckyGiftMilestone(LGiftModel gift) { + if (!gift.isLuckyGift) { + return false; + } + final count = gift.giftCount; + if (count % 1 != 0) { + return false; + } + return _luckyGiftMilestones.contains(count.toInt()); + } + + String _giftCountText(num count) { + return count % 1 == 0 ? count.toInt().toString() : count.toString(); + } + @override void dispose() { Provider.of( @@ -770,7 +425,9 @@ class LGiftModel { //一次发送礼物的数量 num giftCount = 0; + //是否幸运礼物,用于特殊档位样式 + bool isLuckyGift = false; + //id String labelId = ""; - } diff --git a/lib/ui_kit/widgets/room/room_msg_item.dart b/lib/ui_kit/widgets/room/room_msg_item.dart index f57fe05..a71d55f 100644 --- a/lib/ui_kit/widgets/room/room_msg_item.dart +++ b/lib/ui_kit/widgets/room/room_msg_item.dart @@ -58,7 +58,8 @@ class _MsgItemState extends State { } ///礼物 - if (widget.msg.type == SCRoomMsgType.gift) { + if (widget.msg.type == SCRoomMsgType.gift || + widget.msg.type == SCRoomMsgType.luckGiftAnimOther) { return _buildGiftMsg(context); } diff --git a/需求进度.md b/需求进度.md index f98e3a9..035815c 100644 --- a/需求进度.md +++ b/需求进度.md @@ -13,6 +13,10 @@ - 本轮按需求暂未处理网络链路上的启动等待,例如审核态检查或远端启动页配置请求。 ## 已完成模块 +- 已修复礼物页幸运礼物发送链路:当前 `gift_page` 会按礼物类型分流,普通礼物继续走 `/gift/batch`,`LUCKY_GIFT/LUCK/MAGIC` 改走已存在的 `/gift/give/lucky-gift`;幸运/魔法礼物成功后不再本地伪造普通 `GIFT` 消息,而是改发房间内 `LUCK_GIFT_ANIM_OTHER` 动画消息,避免和后端幸运礼物开奖/补发逻辑打架;同时送礼失败已补用户提示,且对 `standardId` 缺失的幸运礼物增加了前置拦截,避免继续点发送却只有日志没有反馈。 +- 已继续补齐幸运礼物前端可见反馈:根据真机日志确认 `giveLuckyGift` 和腾讯 IM 房间消息发送都已成功,当前问题收敛为发送端没有本地回显、且 `LUCK_GIFT_ANIM_OTHER` 收到后未真正接入房间礼物动画;现已在发送成功后先本地触发一轮房间礼物上飘反馈,并把收到的 `LUCK_GIFT_ANIM_OTHER` 直接接入现有房间礼物动画 listener,确保发送端和房间内其它端都能看到即时反应。排障期间临时加过的发送并发锁已在后续需求确认后撤回,不再限制幸运礼物连点。 +- 已按最新确认调整幸运礼物点击策略:撤回前一轮为排障临时加上的发送并发锁,恢复幸运礼物可连续点击;当前连点时每次请求仍走 `/gift/give/lucky-gift`,并继续通过本地回显 + `LUCK_GIFT_ANIM_OTHER` 接入现有房间礼物动画 listener 的方式提供与普通礼物一致的即时播报体验。 +- 已继续优化房间礼物播报 UI:礼物上飘条目高度与内部约束已重新收紧,修复底部 `bottom overflow`;同时为幸运礼物新增 10/20/50/100/200/300 连击特殊样式,这些档位的连击数字会以更大的“霸气金”渐变字显示,普通礼物与非里程碑连击保持原样式。 - 已修复 `Me -> Friends/Fans 列表 -> 他人主页 -> 返回 Me` 后个人资料串号的问题:`MePage2` 现只读取当前登录用户资料,`SocialChatUserProfileManager.userProfile` 继续只承载“当前查看中的详情页用户”,同时移除了 `syncCurrentUserProfile()` 对详情态的覆盖,避免查看他人资料后把 `Me` 页错误渲染成对方信息。 - 已按本轮动效替换需求完成首页底部 tab 动效接入,并同步将本轮新增本地 `.svga` 资源统一改成项目命名风格:当前 `Home / Explore / Message / Me` 已分别使用 `sc_icon_home_anim.svga / sc_icon_explore_anim.svga / sc_icon_message_anim.svga / sc_icon_me_anim.svga`,并继续保留原有 png 作为失败兜底;后续新增本地动效资源默认也按 `sc_icon_*_anim` 规则命名。 - 已将语言房右侧 `game` 悬浮入口从 emoji 占位替换为本地动效资源,并按最新反馈移除外层圆形 `container`:当前直接使用 `sc_icon_room_game_entry_anim.svga` 本体作为入口展示,尺寸与原入口占位一致;若 SVGA 加载失败,会自动回退到项目原有 `sc_icon_botton_game.png`。 @@ -47,6 +51,7 @@ - 已继续定位“游戏画面可见但触摸无响应”的 Flutter 侧原因:当前实现此前一直把 BAISHUN H5 页面放在 `showGeneralDialog` 底部弹层里承载,同时 App 启动时又全局锁定了 `portraitUp`;对于 `FishingStar` 这类横版游戏,这会叠加出“平台视图在弹层容器中的命中不稳定 + iPhone 端实际不支持横屏”的问题。现已改为用独立路由承载 BAISHUN 游戏页,并按启动数据里的 `orientation` 动态切换横竖屏;同时 iOS `Info.plist` 已补充手机横屏支持,便于继续验证横版游戏触摸是否恢复。上述调整仍属于本轮 BAISHUN 联调修正,不是新的正式产品交互。 - 已按最新联调反馈回撤 BAISHUN 页面的横屏适配:当前后续不会接横屏游戏,因此已去掉游戏页按 `orientation` 切换系统方向的逻辑,并撤回 iPhone 端新增的横屏声明,只保留独立路由承载 BAISHUN 页面这一项 Flutter 容器修正。同时已继续增强临时调试,在 H5 侧补充 `pointerdown / touchstart / mousedown / click` 事件回传,用于下一轮真机直接确认“点击是否真的进到了 H5 DOM”;这些输入事件日志依旧只用于本轮排障,后续需要删除。 - 已按当前收尾需求移除 BAISHUN 临时调试面板:`BS DEBUG` 组件和相关调试入口现已从游戏页删除,不再作为正式功能保留;同时游戏页展示高度已恢复为接近半屏的底部弹出样式,回到最初设定的房内游戏视觉形态。 +- 已继续微调 BAISHUN 游戏页高度:由于真实 H5 内容本身就是接近半屏,且顶部还需要预留拖拽条和标题栏空间,当前底部游戏页容器高度已从上一版的 `0.56` 屏高上调到 `0.62`,避免视觉上看起来比预期半屏更矮。 - 创建并持续维护进度跟踪文件。 - 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。 - 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。