From cbb811a18aba137ce7daf93e86153b1a144b18b1 Mon Sep 17 00:00:00 2001 From: roxy Date: Fri, 22 May 2026 20:41:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...kotlin-compiler-9255113358677119818.salive | 0 lib/modules/chat/message_chat_page.dart | 115 +- .../chat/system/message_system_page.dart | 239 +--- lib/modules/gift/gift_page.dart | 59 +- .../my_items/chatbox/bags_chatbox_page.dart | 38 - .../data_card/bags_data_card_page.dart | 38 - .../headdress/bags_headdress_page.dart | 38 - lib/services/audio/rtm_manager.dart | 1094 ++--------------- .../res/sc_first_recharge_reward_res.dart | 135 +- .../first_recharge/first_recharge_dialog.dart | 18 +- .../room/room_recharge_bottom_sheet.dart | 48 +- 需求进度.md | 24 + 12 files changed, 365 insertions(+), 1481 deletions(-) delete mode 100644 android/.kotlin/sessions/kotlin-compiler-9255113358677119818.salive diff --git a/android/.kotlin/sessions/kotlin-compiler-9255113358677119818.salive b/android/.kotlin/sessions/kotlin-compiler-9255113358677119818.salive deleted file mode 100644 index e69de29..0000000 diff --git a/lib/modules/chat/message_chat_page.dart b/lib/modules/chat/message_chat_page.dart index 811b734..6c3469f 100644 --- a/lib/modules/chat/message_chat_page.dart +++ b/lib/modules/chat/message_chat_page.dart @@ -49,7 +49,6 @@ import 'package:yumi/ui_kit/components/dialog/dialog_base.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; import 'package:yumi/app/constants/sc_global_config.dart'; -import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/app/config/business_logic_strategy.dart'; import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; @@ -61,12 +60,10 @@ 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/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; -import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/modules/index/main_route.dart'; import 'package:yumi/ui_kit/widgets/sc_nine_patch_image.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_dialog.dart'; -import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart'; import '../../shared/business_logic/models/res/sc_system_invit_message_res.dart'; @@ -1764,91 +1761,14 @@ class _MessageItem extends StatelessWidget { return; } SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); - final currentProfile = AccountStorage().getCurrentUser()?.userProfile; - final rtmProvider = Provider.of(context, listen: false); - if (agree) { - unawaited( - rtmProvider.sendCpRelationEstablishedSystemMessage( - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - ), - ); - _sendCpRelationEstablishedRoomNotice( - invite: invite, - currentProfile: currentProfile, - ); - } else { - unawaited( - rtmProvider.sendCpInviteRejectedSystemMessage( - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - ), - ); - } + _cpMessageLog( + 'process submitted applyId=$applyId agree=$agree ' + 'wait_backend_result=true', + ); unawaited(profileManager.refreshLoadedCpProfiles()); updateCall(); } - void _sendCpRelationEstablishedRoomNotice({ - required SCSystemInvitMessageRes invite, - required SocialChatUserProfile? currentProfile, - }) { - try { - final rtcProvider = Provider.of(context, listen: false); - final groupId = - rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount - ?.trim() ?? - ""; - if (groupId.isEmpty) { - return; - } - final currentName = _cpDisplayName( - nickname: currentProfile?.userNickname, - account: currentProfile?.account, - fallback: currentProfile?.id, - ); - final inviteName = _cpDisplayName( - nickname: invite.userNickname, - account: invite.account, - actualAccount: invite.actualAccount, - ); - final relationType = scNormalizeCpRelationType(invite.relationType); - Provider.of(context, listen: false).dispatchMessage( - Msg( - groupId: groupId, - msg: scBuildCpRelationEstablishedNotice( - userName1: inviteName, - userName2: currentName, - relationType: relationType, - ), - type: SCRoomMsgType.cpSystemNotice, - noticeAction: "RELATION_ESTABLISHED", - user: currentProfile, - toUser: SocialChatUserProfile( - account: _firstCpInviteValue([ - invite.actualAccount, - invite.account, - ]), - userNickname: inviteName, - userAvatar: invite.userAvatar ?? "", - ), - targetUserIds: _compactCpTargetIds([ - currentProfile?.id, - currentProfile?.account, - invite.actualAccount, - invite.account, - ]), - role: relationType, - ), - addLocal: true, - ); - } catch (error) { - _cpMessageLog('send room established notice skipped error=$error'); - } - } - Map? _customDataFromMessage(V2TimMessage msg) { if (msg.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { return null; @@ -1932,6 +1852,15 @@ class _MessageItem extends StatelessWidget { content["result"]?.toString() ?? "", content["state"]?.toString() ?? "", ].map((value) => value.trim().toUpperCase()).join("|"); + final expiredValue = _cpInviteBoolValue([ + data["expired"], + data["isExpired"], + content["expired"], + content["isExpired"], + ]); + if (expiredValue == true) { + return _CpInviteMessageState.expired; + } final agreeValue = _cpInviteBoolValue([ data["agree"], data["accepted"], @@ -2157,24 +2086,6 @@ class _MessageItem extends StatelessWidget { return "User"; } - List _compactCpTargetIds(Iterable values) { - return values - .map((value) => value?.trim() ?? "") - .where((value) => value.isNotEmpty) - .toSet() - .toList(); - } - - String? _firstCpInviteValue(Iterable values) { - for (final value in values) { - final text = value?.trim() ?? ""; - if (text.isNotEmpty) { - return text; - } - } - return null; - } - String _firstNonBlankString( Iterable values, { String fallback = "", diff --git a/lib/modules/chat/system/message_system_page.dart b/lib/modules/chat/system/message_system_page.dart index 84abd24..bc76f77 100644 --- a/lib/modules/chat/system/message_system_page.dart +++ b/lib/modules/chat/system/message_system_page.dart @@ -26,7 +26,6 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'; import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart'; import 'package:yumi/app_localizations.dart'; -import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart'; import 'package:yumi/ui_kit/components/dialog/dialog_base.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; @@ -35,7 +34,6 @@ import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/shared/tools/sc_date_utils.dart'; import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart'; import 'package:yumi/services/general/sc_app_general_manager.dart'; -import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; @@ -44,11 +42,9 @@ import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/config/business_logic_strategy.dart'; import 'package:yumi/ui_kit/widgets/room/cp/room_cp_relation_formed_dialog.dart'; import 'package:yumi/ui_kit/widgets/room/cp/room_cp_invite_dialog.dart'; -import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import '../../../shared/data_sources/models/enum/sc_sysytem_message_type.dart'; import '../../../shared/business_logic/models/res/sc_system_invit_message_res.dart'; -import '../../../shared/business_logic/models/res/login_res.dart'; enum _CpInviteMessageState { pending, expired, rejected, accepted } @@ -103,10 +99,7 @@ class _MessageSystemPageState extends State { count: 100, lastMsgID: null, ); - List messages = - (v2timValueCallback.data ?? []) - .where((message) => !_isCpBuildSystemMessage(message)) - .toList(); + List messages = v2timValueCallback.data ?? []; // List messages = await FTIM.getMessageManager().getMessages(conversation: currentConversation); currentConversationMessageList ??= []; currentConversationMessageList?.clear(); @@ -128,14 +121,6 @@ class _MessageSystemPageState extends State { return; } if (message.userID != currentConversation?.userID) return; - if (_isCpBuildSystemMessage(message)) { - debugPrint( - '[CP][Invite] skip system conversation CP_BUILD ' - 'msgId=${message.msgID}', - ); - return; - } - int? index; for (var element in currentConversationMessageList) { if (msgId != null) { @@ -193,26 +178,6 @@ class _MessageSystemPageState extends State { } catch (_) {} } - bool _isCpBuildSystemMessage(V2TimMessage message) { - if (message.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { - return false; - } - final content = message.customElem?.data; - if (content == null || content.trim().isEmpty) { - return false; - } - try { - final data = jsonDecode(content); - if (data is! Map) { - return false; - } - final noticeType = (data["noticeType"] ?? data["type"])?.toString(); - return noticeType == SCSysytemMessageType.CP_BUILD.name; - } catch (_) { - return false; - } - } - @override Widget build(BuildContext context) { return Stack( @@ -938,6 +903,29 @@ class _MessageItem extends StatelessWidget { content["result"]?.toString() ?? "", content["state"]?.toString() ?? "", ].map((value) => value.trim().toUpperCase()).join("|"); + final expiredValue = _cpInviteBoolValue([ + data["expired"], + data["isExpired"], + content["expired"], + content["isExpired"], + ]); + if (expiredValue == true) { + return _CpInviteMessageState.expired; + } + final agreeValue = _cpInviteBoolValue([ + data["agree"], + data["accepted"], + data["isAgree"], + content["agree"], + content["accepted"], + content["isAgree"], + ]); + if (agreeValue == true) { + return _CpInviteMessageState.accepted; + } + if (agreeValue == false) { + return _CpInviteMessageState.rejected; + } if (values.contains("TIMEOUT") || values.contains("EXPIRE")) { return _CpInviteMessageState.expired; } @@ -956,6 +944,30 @@ class _MessageItem extends StatelessWidget { return _CpInviteMessageState.pending; } + bool? _cpInviteBoolValue(Iterable values) { + for (final value in values) { + if (value is bool) { + return value; + } + final text = value?.toString().trim().toLowerCase() ?? ""; + if (text.isEmpty) { + continue; + } + if (text == "true" || text == "1" || text == "yes" || text == "agree") { + return true; + } + if (text == "false" || + text == "0" || + text == "no" || + text == "reject" || + text == "decline" || + text == "refuse") { + return false; + } + } + return null; + } + bool _isCpInvitePayload(Map data) { final content = _cpContentMap(data); final text = @@ -1848,136 +1860,22 @@ class _MessageItem extends StatelessWidget { return null; } - String _cpNoticeDisplayName({ - String? nickname, - String? account, - String? actualAccount, - String? fallback, - }) { - for (final value in [nickname, actualAccount, account, fallback]) { - final text = value?.trim() ?? ""; - if (text.isNotEmpty) { - return text; - } - } - return "User"; - } - - void _handleCpInviteAccepted( - String applyId, - SCSystemInvitMessageRes? invite, - ) { - if (invite == null) { - return; - } - final currentProfile = AccountStorage().getCurrentUser()?.userProfile; - final currentName = _cpNoticeDisplayName( - nickname: currentProfile?.userNickname, - account: currentProfile?.account, - fallback: currentProfile?.id, - ); - final inviteName = _cpNoticeDisplayName( - nickname: invite.userNickname, - account: invite.account, - actualAccount: invite.actualAccount, - ); - final relationType = scNormalizeCpRelationType(invite.relationType); - unawaited( - Provider.of( - context, - listen: false, - ).sendCpRelationEstablishedSystemMessage( - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - ), - ); - _sendCpRelationEstablishedRoomNotice( - leftName: inviteName, - rightName: currentName, - relationType: relationType, - currentProfile: currentProfile, - inviteAccount: invite.account, - inviteActualAccount: invite.actualAccount, - inviteAvatar: invite.userAvatar ?? "", - ); - } - - void _sendCpRelationEstablishedRoomNotice({ - required String leftName, - required String rightName, - required String relationType, - required SocialChatUserProfile? currentProfile, - required String? inviteAccount, - required String? inviteActualAccount, - required String inviteAvatar, - }) { - try { - final rtcProvider = Provider.of(context, listen: false); - final groupId = - rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount - ?.trim() ?? - ""; - if (groupId.isEmpty) { - return; - } - Provider.of(context, listen: false).dispatchMessage( - Msg( - groupId: groupId, - msg: scBuildCpRelationEstablishedNotice( - userName1: leftName, - userName2: rightName, - relationType: relationType, - ), - type: SCRoomMsgType.cpSystemNotice, - noticeAction: "RELATION_ESTABLISHED", - user: currentProfile, - toUser: SocialChatUserProfile( - account: _firstCpNoticeValue([inviteActualAccount, inviteAccount]), - userNickname: leftName, - userAvatar: inviteAvatar, - ), - targetUserIds: _compactCpTargetIds([ - currentProfile?.id, - currentProfile?.account, - inviteActualAccount, - inviteAccount, - ]), - role: relationType, - ), - addLocal: true, - ); - } catch (_) {} - } - - List _compactCpTargetIds(Iterable values) { - return values - .map((value) => value?.trim() ?? "") - .where((value) => value.isNotEmpty) - .toSet() - .toList(); - } - - String? _firstCpNoticeValue(Iterable values) { - for (final value in values) { - final text = value?.trim() ?? ""; - if (text.isNotEmpty) { - return text; - } - } - return null; - } - Future _acceptCpInviteDirect( String id, SCSystemInvitMessageRes? cpInvite, ) async { - final success = await Provider.of( + final profileManager = Provider.of( context, listen: false, - ).cpRlationshipProcessApply(context, id, true); + ); + final success = await profileManager.cpRlationshipProcessApply( + context, + id, + true, + ); if (success && context.mounted) { - _handleCpInviteAccepted(id, cpInvite); + SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); + unawaited(profileManager.refreshLoadedCpProfiles()); } } @@ -1985,21 +1883,18 @@ class _MessageItem extends StatelessWidget { String id, SCSystemInvitMessageRes? cpInvite, ) async { - final success = await Provider.of( + final profileManager = Provider.of( context, listen: false, - ).cpRlationshipProcessApply(context, id, false); - if (success && cpInvite != null && context.mounted) { - unawaited( - Provider.of( - context, - listen: false, - ).sendCpInviteRejectedSystemMessage( - applyId: id, - invite: cpInvite, - currentProfile: AccountStorage().getCurrentUser()?.userProfile, - ), - ); + ); + final success = await profileManager.cpRlationshipProcessApply( + context, + id, + false, + ); + if (success && context.mounted) { + SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); + unawaited(profileManager.refreshLoadedCpProfiles()); } } diff --git a/lib/modules/gift/gift_page.dart b/lib/modules/gift/gift_page.dart index a0d6214..0bedbbf 100644 --- a/lib/modules/gift/gift_page.dart +++ b/lib/modules/gift/gift_page.dart @@ -25,7 +25,6 @@ import 'package:yumi/main.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app/constants/sc_room_msg_type.dart'; import 'package:yumi/app/constants/sc_screen.dart'; -import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/tools/sc_dialog_utils.dart'; import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart'; import 'package:yumi/shared/business_logic/models/res/gift_res.dart'; @@ -36,7 +35,6 @@ import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; -import 'package:yumi/modules/wallet/wallet_route.dart'; import 'package:yumi/modules/gift/gift_tab_page.dart'; import 'package:yumi/modules/gift/cp_rights/cp_rights_guide_page.dart'; import 'package:yumi/services/gift/room_gift_combo_send_controller.dart'; @@ -870,14 +868,7 @@ class _GiftPageState extends State with TickerProviderStateMixin { children: [ SizedBox(width: 15.w), GestureDetector( - onTap: () { - SmartDialog.dismiss(tag: "showGiftControl"); - SCNavigatorUtils.push( - navigatorKey.currentState!.context, - WalletRoute.recharge, - replace: false, - ); - }, + onTap: _openRoomRechargeBottomSheet, child: Container( padding: EdgeInsets.symmetric( vertical: 8.w, @@ -1569,7 +1560,6 @@ class _GiftPageState extends State with TickerProviderStateMixin { animationCount: request.clickCount, ); } - _syncLocalCpInviteAfterGift(request); await _refreshGiftBackpack(); _refreshCpStateAfterGift(request, profileManager); return; @@ -1635,7 +1625,6 @@ class _GiftPageState extends State with TickerProviderStateMixin { ); } profileManager?.updateBalance(result); - _syncLocalCpInviteAfterGift(request); _refreshCpStateAfterGift(request, profileManager); } catch (e) { final errorMessage = @@ -1679,41 +1668,6 @@ class _GiftPageState extends State with TickerProviderStateMixin { unawaited(profileManager?.refreshLoadedCpProfiles()); } - void _syncLocalCpInviteAfterGift(_GiftSendRequest request) { - final relationType = request.cpRelationType; - if (relationType == null || request.acceptUserIds.isEmpty) { - return; - } - final context = navigatorKey.currentState?.context; - if (context == null) { - return; - } - final receiverId = request.acceptUserIds.first.trim(); - if (receiverId.isEmpty) { - return; - } - final receiverProfile = - request.acceptUsers.isNotEmpty - ? request.acceptUsers.first.user - : SocialChatUserProfile(id: receiverId, account: receiverId); - _giftFxLog( - 'sync local CP invite after gift ' - 'receiver=$receiverId relationType=$relationType giftId=${request.gift.id}', - ); - unawaited( - Provider.of( - context, - listen: false, - ).insertLocalOutgoingCpGiftInvite( - receiverId: receiverId, - receiverProfile: receiverProfile, - relationType: relationType, - gift: request.gift, - giftCount: request.quantity, - ), - ); - } - Future> _giveBackpackGiftToRecipients( SCChatRoomRepository repository, _GiftSendRequest request, @@ -2044,6 +1998,17 @@ class _GiftPageState extends State with TickerProviderStateMixin { }); } + void _openRoomRechargeBottomSheet() { + SmartDialog.dismiss(tag: "showGiftControl"); + Future.delayed(const Duration(milliseconds: 80), () { + final navigator = navigatorKey.currentState; + if (navigator == null || !navigator.mounted) { + return; + } + RoomRechargeBottomSheet.show(navigator.context); + }); + } + void _openCpRightsGuide() { SmartDialog.dismiss(tag: "showGiftControl"); Future.delayed(const Duration(milliseconds: 80), () { diff --git a/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart b/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart index 8a83641..6057619 100644 --- a/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart +++ b/lib/modules/user/my_items/chatbox/bags_chatbox_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; @@ -14,7 +13,6 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repositor import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; -import 'package:yumi/ui_kit/widgets/bag/props_bag_chatbox_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/countdown_timer.dart'; import 'package:yumi/modules/store/store_route.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; @@ -237,27 +235,6 @@ class _BagsChatboxPageState ), ), ), - PositionedDirectional( - bottom: 5.w, - end: 5.w, - child: GestureDetector( - onTap: () { - selecteChatbox = res; - setState(() {}); - _showDetail(res); - }, - behavior: HitTestBehavior.opaque, - child: Container( - padding: EdgeInsets.all(5.w), - child: Image.asset( - "sc_images/store/sc_icon_shop_item_search.png", - width: 18.w, - height: 18.w, - color: Colors.white, - ), - ), - ), - ), ], ), ), @@ -305,21 +282,6 @@ class _BagsChatboxPageState } } - void _showDetail(BagsListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsBagChatboxDetailDialog(res); - }, - onDismiss: () { - myChatbox = AccountStorage().getChatbox(); - setState(() {}); - }, - ); - } - Future _use(BagsListRes res, bool unload) async { SCLoadingManager.show(context: context); final localizations = SCAppLocalizations.of(context)!; diff --git a/lib/modules/user/my_items/data_card/bags_data_card_page.dart b/lib/modules/user/my_items/data_card/bags_data_card_page.dart index b2c10a6..900cb52 100644 --- a/lib/modules/user/my_items/data_card/bags_data_card_page.dart +++ b/lib/modules/user/my_items/data_card/bags_data_card_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/app_localizations.dart'; @@ -18,7 +17,6 @@ import 'package:yumi/ui_kit/components/sc_page_list.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; -import 'package:yumi/ui_kit/widgets/bag/props_bag_data_card_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/countdown_timer.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; @@ -249,27 +247,6 @@ class BagsDataCardPageState ), ), ), - PositionedDirectional( - bottom: 5.w, - end: 5.w, - child: GestureDetector( - onTap: () { - selectedDataCard = res; - setState(() {}); - _showDetail(res); - }, - behavior: HitTestBehavior.opaque, - child: Container( - padding: EdgeInsets.all(5.w), - child: Image.asset( - "sc_images/store/sc_icon_shop_item_search.png", - width: 18.w, - height: 18.w, - color: Colors.white, - ), - ), - ), - ), ], ), ), @@ -326,21 +303,6 @@ class BagsDataCardPageState } } - void _showDetail(BagsListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsBagDataCardDetailDialog(res); - }, - onDismiss: () { - myDataCard = AccountStorage().getDataCard(); - setState(() {}); - }, - ); - } - Future _use(BagsListRes res, bool unload) async { SCLoadingManager.show(context: context); final profileManager = Provider.of( diff --git a/lib/modules/user/my_items/headdress/bags_headdress_page.dart b/lib/modules/user/my_items/headdress/bags_headdress_page.dart index f0df158..688760b 100644 --- a/lib/modules/user/my_items/headdress/bags_headdress_page.dart +++ b/lib/modules/user/my_items/headdress/bags_headdress_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; @@ -16,7 +15,6 @@ import 'package:yumi/ui_kit/components/sc_page_list.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; -import 'package:yumi/ui_kit/widgets/bag/props_bag_headdress_detail_dialog.dart'; import 'package:yumi/ui_kit/widgets/countdown_timer.dart'; import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart'; import 'package:provider/provider.dart'; @@ -237,27 +235,6 @@ class _BagsHeaddressPageState ), ), ), - PositionedDirectional( - bottom: 5.w, - end: 5.w, - child: GestureDetector( - onTap: () { - selecteHeaddress = res; - setState(() {}); - _showDetail(res); - }, - behavior: HitTestBehavior.opaque, - child: Container( - padding: EdgeInsets.all(5.w), - child: Image.asset( - "sc_images/store/sc_icon_shop_item_search.png", - width: 13.w, - height: 13.w, - color: Colors.white, - ), - ), - ), - ), ], ), ), @@ -306,21 +283,6 @@ class _BagsHeaddressPageState } } - void _showDetail(BagsListRes res) { - SmartDialog.show( - tag: "showPropsDetail", - alignment: Alignment.center, - animationType: SmartAnimationType.fade, - builder: (_) { - return PropsBagHeaddressDetailDialog(res); - }, - onDismiss: () { - myHeaddress = AccountStorage().getHeaddress(); - setState(() {}); - }, - ); - } - Future _use(BagsListRes res, bool unload) async { SCLoadingManager.show(context: context); final localizations = SCAppLocalizations.of(context)!; diff --git a/lib/services/audio/rtm_manager.dart b/lib/services/audio/rtm_manager.dart index 1465bb0..8df7f25 100644 --- a/lib/services/audio/rtm_manager.dart +++ b/lib/services/audio/rtm_manager.dart @@ -28,13 +28,11 @@ import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart'; import 'package:tencent_cloud_chat_sdk/enum/conversation_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/group_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart'; -import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart'; import 'package:tencent_cloud_chat_sdk/enum/message_status.dart'; import 'package:tencent_cloud_chat_sdk/manager/v2_tim_group_manager.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_result.dart'; -import 'package:tencent_cloud_chat_sdk/models/v2_tim_custom_elem.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_image_elem.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; @@ -91,7 +89,7 @@ typedef OnNewGroupMessageListener = typedef OnMessageRecvC2CReadListener = Function(List messageIDList); typedef RtmProvider = RealTimeMessagingManager; -enum _CpInviteProcessResult { accepted, rejected } +enum _CpInviteProcessResult { accepted, rejected, expired } class _LuckGiftPushQueueEntry { _LuckGiftPushQueueEntry({ @@ -296,8 +294,7 @@ class RealTimeMessagingManager extends ChangeNotifier { Timer? _roomRocketRewardPopupRetryTimer; Timer? _roomRocketRoomReadyRewardRetryTimer; final Set _shownCpInviteMessageKeys = {}; - final Set _forwardedCpInviteC2CMessageKeys = {}; - final Set _echoedCpInviteC2CMessageKeys = {}; + final Set _shownCpInviteResultMessageKeys = {}; int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken; String? roomRedPacketBroadcastGroupId; String? roomRedPacketBroadcastRegionCode; @@ -879,7 +876,7 @@ class RealTimeMessagingManager extends ChangeNotifier { return; } final noticeType = (data["noticeType"] ?? data["type"])?.toString() ?? ""; - if (_handleCpInviteResultMessage(data)) { + if (_handleCpInviteResultMessage(data, msgId: message?.msgID)) { return; } if (noticeType != SCSysytemMessageType.CP_BUILD.name) { @@ -923,29 +920,6 @@ class RealTimeMessagingManager extends ChangeNotifier { fallback: currentProfile.id, ); final relationType = scNormalizeCpRelationType(invite.relationType); - _persistBackendCpInviteToC2CConversationIfNeeded( - sourceMessage: message, - data: data, - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - isSender: isSender, - ); - _forwardCpInviteC2CMessageIfNeeded( - sourceMessage: message, - data: data, - applyId: applyId, - invite: invite, - isSender: isSender, - ); - _echoCpInviteC2CMessageToSenderIfNeeded( - sourceMessage: message, - data: data, - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - isSender: isSender, - ); if (isSender) { final receiver = _cpInviteReceiverFromMessageData(data); _cpInviteLog( @@ -1050,11 +1024,23 @@ class RealTimeMessagingManager extends ChangeNotifier { ""; } - bool _handleCpInviteResultMessage(Map data) { + bool _handleCpInviteResultMessage( + Map data, { + String? msgId, + }) { final result = _cpInviteResultFromMessageData(data); if (result == null) { return false; } + final applyId = _cpInviteApplyIdFromData(data); + final resultMessageKey = applyId.isNotEmpty ? applyId : msgId ?? ""; + final resultKey = + resultMessageKey.isNotEmpty ? '${result.name}_$resultMessageKey' : ""; + if (resultKey.isNotEmpty && + !_shownCpInviteResultMessageKeys.add(resultKey)) { + _cpInviteLog('skip duplicated CP invite result key=$resultKey'); + return true; + } final invite = _cpInviteFromMessageData(data); final relationType = scNormalizeCpRelationType( _firstCpInviteValue([ @@ -1079,16 +1065,98 @@ class RealTimeMessagingManager extends ChangeNotifier { ); } final currentProfile = AccountStorage().getCurrentUser()?.userProfile; - final isCurrentUserInviter = - invite != null && - currentProfile != null && - _cpInviteSenderMatchesCurrent(invite, currentProfile); - if (result == _CpInviteProcessResult.rejected && isCurrentUserInviter) { - _showCpInviteRejectedDialog(relationType); + if (context != null && invite != null && currentProfile != null) { + unawaited( + _showCpInviteResultDialog( + context: context, + data: data, + invite: invite, + currentProfile: currentProfile, + result: result, + relationType: relationType, + ), + ); + } else { + SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); } return true; } + Future _showCpInviteResultDialog({ + required BuildContext context, + required Map data, + required SCSystemInvitMessageRes invite, + required SocialChatUserProfile currentProfile, + required _CpInviteProcessResult result, + required String relationType, + }) async { + final isSender = _cpInviteSenderMatchesCurrent(invite, currentProfile); + final currentName = _cpInviteDisplayName( + nickname: currentProfile.userNickname, + account: currentProfile.account, + fallback: currentProfile.id, + ); + final inviterName = _cpInviteDisplayName( + nickname: invite.userNickname, + account: invite.account, + actualAccount: invite.actualAccount, + ); + _cpInviteLog( + 'show CP invite result dialog result=${result.name} ' + 'relationType=$relationType isSender=$isSender', + ); + await RoomCpInviteDialog.show( + context, + inviter: RoomCpInviteDialogUser( + name: isSender ? currentName : inviterName, + avatarUrl: + isSender + ? currentProfile.userAvatar ?? "" + : invite.userAvatar ?? "", + headdress: + isSender + ? currentProfile.getHeaddress()?.sourceUrl ?? "" + : invite.userHeaddress ?? "", + headdressCover: + isSender + ? currentProfile.getHeaddress()?.cover ?? "" + : invite.userHeaddressCover ?? "", + ), + receiver: + isSender + ? _cpInviteReceiverFromMessageData(data) + : RoomCpInviteDialogUser( + name: currentName, + avatarUrl: currentProfile.userAvatar ?? "", + headdress: currentProfile.getHeaddress()?.sourceUrl ?? "", + headdressCover: currentProfile.getHeaddress()?.cover ?? "", + ), + style: + isSender + ? RoomCpInviteDialogStyle.waiting + : RoomCpInviteDialogStyle.incoming, + actionState: _cpInviteActionStateForResult(result), + countdownSeconds: _cpInviteCountdownSeconds(invite), + descriptionText: + isSender + ? _cpInviteWaitingDescriptionText(relationType) + : _cpInviteDescriptionText(relationType), + ); + } + + RoomCpInviteActionState _cpInviteActionStateForResult( + _CpInviteProcessResult result, + ) { + switch (result) { + case _CpInviteProcessResult.accepted: + return RoomCpInviteActionState.accepted; + case _CpInviteProcessResult.rejected: + return RoomCpInviteActionState.rejected; + case _CpInviteProcessResult.expired: + return RoomCpInviteActionState.expired; + } + } + _CpInviteProcessResult? _cpInviteResultFromMessageData( Map data, ) { @@ -1115,6 +1183,15 @@ class RealTimeMessagingManager extends ChangeNotifier { content["state"]?.toString() ?? "", ].map((value) => value.trim().toUpperCase()).toList(); + final expiredValue = _cpInviteBoolValue([ + data["expired"], + data["isExpired"], + content["expired"], + content["isExpired"], + ]); + if (expiredValue == true) { + return _CpInviteProcessResult.expired; + } final agreeValue = _cpInviteBoolValue([ data["agree"], data["accepted"], @@ -1130,6 +1207,11 @@ class RealTimeMessagingManager extends ChangeNotifier { return _CpInviteProcessResult.rejected; } final joined = values.join("|"); + if (joined.contains("TIMEOUT") || + joined.contains("EXPIRE") || + joined.contains("OVERDUE")) { + return _CpInviteProcessResult.expired; + } if (joined.contains("REJECT") || joined.contains("DECLIN") || joined.contains("REFUSE") || @@ -1196,66 +1278,23 @@ class RealTimeMessagingManager extends ChangeNotifier { context, listen: false, ); - final rtcProvider = Provider.of(context, listen: false); final success = await profileManager.cpRlationshipProcessApply( context, applyId, agree, ); - if (!success || !agree) { + if (!success) { _cpInviteLog( 'process CP_BUILD done applyId=$applyId agree=$agree success=$success', ); - if (success && !agree) { - unawaited( - sendCpInviteRejectedSystemMessage(applyId: applyId, invite: invite), - ); - _sendIncomingCpInviteRejectedNotice(invite, rtcProvider); - } return; } - _handleIncomingCpInviteAccepted(applyId, invite, rtcProvider); - } - - void _handleIncomingCpInviteAccepted( - String applyId, - SCSystemInvitMessageRes invite, - RtcProvider rtcProvider, - ) { - final currentProfile = AccountStorage().getCurrentUser()?.userProfile; - final currentName = _cpInviteDisplayName( - nickname: currentProfile?.userNickname, - account: currentProfile?.account, - fallback: currentProfile?.id, - ); - final inviterName = _cpInviteDisplayName( - nickname: invite.userNickname, - account: invite.account, - actualAccount: invite.actualAccount, - ); - final relationType = scNormalizeCpRelationType(invite.relationType); - RoomCpRelationFormedEffect.show( - leftName: inviterName, - rightName: currentName, - leftAvatar: invite.userAvatar ?? "", - rightAvatar: currentProfile?.userAvatar ?? "", - relationType: relationType, - ); - unawaited( - sendCpRelationEstablishedSystemMessage( - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - ), - ); - _sendIncomingCpRelationEstablishedNotice( - leftName: inviterName, - rightName: currentName, - relationType: relationType, - currentProfile: currentProfile, - invite: invite, - rtcProvider: rtcProvider, + _cpInviteLog( + 'process CP_BUILD submitted applyId=$applyId agree=$agree ' + 'wait_backend_result=true', ); + SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); + unawaited(profileManager.refreshLoadedCpProfiles()); } Future sendCpRelationEstablishedSystemMessage({ @@ -1263,102 +1302,10 @@ class RealTimeMessagingManager extends ChangeNotifier { required SCSystemInvitMessageRes invite, SocialChatUserProfile? currentProfile, }) async { - final receiverId = - _firstCpInviteValue([invite.actualAccount, invite.account]) ?? ""; - if (receiverId.isEmpty) { - _cpInviteLog('skip established C2C message reason=no_receiver'); - return; - } - final profile = - currentProfile ?? AccountStorage().getCurrentUser()?.userProfile; - final relationType = scNormalizeCpRelationType(invite.relationType); - final acceptName = _cpInviteDisplayName( - nickname: profile?.userNickname, - account: profile?.account, - fallback: profile?.id, - ); - final data = { - "noticeType": SCSysytemMessageType.CP_BUILD.name, - "noticeAction": "RELATION_ESTABLISHED", - "action": "RELATION_ESTABLISHED", - "status": "ACCEPTED", - "result": "ACCEPTED", - "state": "ACCEPTED", - "expand": applyId, - "applyId": applyId, - "relationType": relationType, - "account": invite.account, - "actualAccount": invite.actualAccount, - "userNickname": invite.userNickname, - "userAvatar": invite.userAvatar, - "userHeaddress": invite.userHeaddress, - "userHeaddressCover": invite.userHeaddressCover, - "acceptUserId": profile?.id, - "acceptAccount": profile?.account, - "acceptActualAccount": profile?.id, - "acceptNickname": acceptName, - "acceptUserNickname": profile?.userNickname, - "acceptUserAvatar": profile?.userAvatar, - "acceptHeaddress": profile?.getHeaddress()?.sourceUrl, - "acceptHeaddressCover": profile?.getHeaddress()?.cover, - "content": jsonEncode({...invite.toJson(), "relationType": relationType}), - }; _cpInviteLog( - 'send established C2C message receiver=$receiverId ' - 'applyId=$applyId relationType=$relationType', + 'skip client established C2C send applyId=$applyId ' + 'reason=backend_dispatches_cp_invite_results', ); - try { - final createCustomMessageRes = await TencentImSDKPlugin.v2TIMManager - .getMessageManager() - .createCustomMessage( - data: jsonEncode(data), - extension: SCSysytemMessageType.CP_BUILD.name, - ); - if (createCustomMessageRes.code != 0) { - _cpInviteLog( - 'create established C2C message failed ' - 'code=${createCustomMessageRes.code} ' - 'desc=${createCustomMessageRes.desc}', - ); - return; - } - final id = createCustomMessageRes.data?.id; - if (id == null || id.isEmpty) { - _cpInviteLog('create established C2C message failed reason=no_id'); - return; - } - final sendMessageRes = await TencentImSDKPlugin.v2TIMManager - .getMessageManager() - .sendMessage( - id: id, - receiver: receiverId, - needReadReceipt: true, - isSupportMessageExtension: true, - groupID: '', - ); - if (sendMessageRes.code == 0) { - _cpInviteLog( - 'send established C2C message success ' - 'receiver=$receiverId applyId=$applyId ' - 'msgId=${sendMessageRes.data?.msgID} ' - 'userID=${sendMessageRes.data?.userID} ' - 'sender=${sendMessageRes.data?.sender} ' - 'self=${sendMessageRes.data?.isSelf}', - ); - _onNew1v1Message(sendMessageRes.data, fallbackUserId: receiverId); - } else { - _cpInviteLog( - 'send established C2C message failed ' - 'receiver=$receiverId applyId=$applyId ' - 'code=${sendMessageRes.code} desc=${sendMessageRes.desc}', - ); - } - } catch (error) { - _cpInviteLog( - 'send established C2C message error ' - 'receiver=$receiverId applyId=$applyId error=$error', - ); - } } Future sendCpInviteRejectedSystemMessage({ @@ -1366,156 +1313,9 @@ class RealTimeMessagingManager extends ChangeNotifier { required SCSystemInvitMessageRes invite, SocialChatUserProfile? currentProfile, }) async { - final receiverId = - _firstCpInviteValue([invite.actualAccount, invite.account]) ?? ""; - if (receiverId.isEmpty) { - _cpInviteLog('skip rejected C2C message reason=no_receiver'); - return; - } - final profile = - currentProfile ?? AccountStorage().getCurrentUser()?.userProfile; - final relationType = scNormalizeCpRelationType(invite.relationType); - final rejectName = _cpInviteDisplayName( - nickname: profile?.userNickname, - account: profile?.account, - fallback: profile?.id, - ); - final data = { - "noticeType": SCSysytemMessageType.CP_BUILD.name, - "noticeAction": "RELATION_REJECTED", - "action": "RELATION_REJECTED", - "status": "REJECTED", - "result": "REJECTED", - "state": "REJECTED", - "agree": false, - "expand": applyId, - "applyId": applyId, - "relationType": relationType, - "account": invite.account, - "actualAccount": invite.actualAccount, - "userNickname": invite.userNickname, - "userAvatar": invite.userAvatar, - "userHeaddress": invite.userHeaddress, - "userHeaddressCover": invite.userHeaddressCover, - "acceptUserId": profile?.id, - "acceptAccount": profile?.account, - "acceptActualAccount": profile?.id, - "acceptNickname": rejectName, - "acceptUserNickname": profile?.userNickname, - "acceptUserAvatar": profile?.userAvatar, - "acceptHeaddress": profile?.getHeaddress()?.sourceUrl, - "acceptHeaddressCover": profile?.getHeaddress()?.cover, - "content": jsonEncode({...invite.toJson(), "relationType": relationType}), - }; _cpInviteLog( - 'send rejected C2C message receiver=$receiverId ' - 'applyId=$applyId relationType=$relationType', - ); - await _sendCpBuildCustomMessage( - receiverId: receiverId, - data: data, - debugName: 'rejected', - applyId: applyId, - ); - } - - void _forwardCpInviteC2CMessageIfNeeded({ - required V2TimMessage? sourceMessage, - required Map data, - required String applyId, - required SCSystemInvitMessageRes invite, - required bool isSender, - }) { - if (!isSender || sourceMessage?.isSelf == true) { - return; - } - final noticeSource = data["noticeSource"]?.toString() ?? ""; - if (noticeSource == "RECEIVER_ECHO" || noticeSource == "LOCAL_OUTGOING") { - return; - } - final normalizedApplyId = applyId.trim(); - if (normalizedApplyId.isEmpty || - !_forwardedCpInviteC2CMessageKeys.add(normalizedApplyId)) { - return; - } - final receiverId = _cpInviteReceiverIdFromMessageData(data); - if (receiverId.isEmpty) { - _forwardedCpInviteC2CMessageKeys.remove(normalizedApplyId); - _cpInviteLog( - 'skip forwarding CP_BUILD C2C reason=no_receiver ' - 'applyId=$normalizedApplyId', - ); - return; - } - _cpInviteLog( - 'forward backend CP_BUILD to C2C ' - 'receiver=$receiverId applyId=$normalizedApplyId', - ); - unawaited( - sendCpInviteBuildMessage( - applyId: normalizedApplyId, - invite: invite, - receiverId: receiverId, - receiverProfile: _cpInviteReceiverProfileFromMessageData(data), - ), - ); - } - - void _echoCpInviteC2CMessageToSenderIfNeeded({ - required V2TimMessage? sourceMessage, - required Map data, - required String applyId, - required SCSystemInvitMessageRes invite, - required SocialChatUserProfile currentProfile, - required bool isSender, - }) { - if (isSender || sourceMessage?.isSelf == true) { - return; - } - final normalizedApplyId = applyId.trim(); - if (normalizedApplyId.isEmpty || - !_echoedCpInviteC2CMessageKeys.add(normalizedApplyId)) { - return; - } - final inviterId = _cpInviteInviterIdFromMessageData(data, invite); - if (inviterId.isEmpty) { - _echoedCpInviteC2CMessageKeys.remove(normalizedApplyId); - _cpInviteLog( - 'skip echoing CP_BUILD C2C reason=no_inviter ' - 'applyId=$normalizedApplyId', - ); - return; - } - final relationType = scNormalizeCpRelationType(invite.relationType); - final echoData = {...data}; - echoData["noticeType"] = SCSysytemMessageType.CP_BUILD.name; - echoData["noticeSource"] = "RECEIVER_ECHO"; - echoData["expand"] = normalizedApplyId; - echoData["applyId"] = normalizedApplyId; - echoData["relationType"] = relationType; - echoData["acceptUserId"] ??= currentProfile.id; - echoData["acceptAccount"] ??= currentProfile.account; - echoData["acceptActualAccount"] ??= currentProfile.id; - echoData["acceptNickname"] ??= _cpInviteDisplayName( - nickname: currentProfile.userNickname, - account: currentProfile.account, - fallback: currentProfile.id, - ); - echoData["acceptUserNickname"] ??= currentProfile.userNickname; - echoData["acceptUserAvatar"] ??= currentProfile.userAvatar; - echoData["acceptHeaddress"] ??= currentProfile.getHeaddress()?.sourceUrl; - echoData["acceptHeaddressCover"] ??= currentProfile.getHeaddress()?.cover; - _cpInviteLog( - 'echo backend CP_BUILD to sender C2C ' - 'sender=$inviterId applyId=$normalizedApplyId relationType=$relationType', - ); - unawaited( - _sendCpBuildCustomMessage( - receiverId: inviterId, - data: echoData, - debugName: 'invite-echo', - applyId: normalizedApplyId, - ), + 'skip client rejected C2C send applyId=$applyId ' + 'reason=backend_dispatches_cp_invite_results', ); } @@ -1574,636 +1374,6 @@ class RealTimeMessagingManager extends ChangeNotifier { ""; } - SocialChatUserProfile? _cpInviteReceiverProfileFromMessageData( - Map data, - ) { - final content = _cpInviteContentMap(data); - final id = _cpInviteReceiverIdFromMessageData(data); - final account = _firstCpInviteValue([ - data["acceptAccount"]?.toString(), - data["receiverAccount"]?.toString(), - content["acceptAccount"]?.toString(), - content["receiverAccount"]?.toString(), - ]); - final nickname = _firstCpInviteValue([ - data["acceptNickname"]?.toString(), - data["acceptUserNickname"]?.toString(), - data["receiverNickname"]?.toString(), - data["receiverUserNickname"]?.toString(), - content["acceptNickname"]?.toString(), - content["acceptUserNickname"]?.toString(), - content["receiverNickname"]?.toString(), - content["receiverUserNickname"]?.toString(), - ]); - final avatar = _firstCpInviteValue([ - data["acceptUserAvatar"]?.toString(), - data["acceptAvatar"]?.toString(), - data["receiverUserAvatar"]?.toString(), - data["receiverAvatar"]?.toString(), - content["acceptUserAvatar"]?.toString(), - content["acceptAvatar"]?.toString(), - content["receiverUserAvatar"]?.toString(), - content["receiverAvatar"]?.toString(), - ]); - if (id.isEmpty && - (account ?? "").isEmpty && - (nickname ?? "").isEmpty && - (avatar ?? "").isEmpty) { - return null; - } - return SocialChatUserProfile( - id: id.isNotEmpty ? id : null, - account: account, - userNickname: nickname, - userAvatar: avatar, - ); - } - - Future sendCpInviteBuildMessage({ - required String applyId, - required SCSystemInvitMessageRes invite, - required String receiverId, - SocialChatUserProfile? receiverProfile, - }) async { - final targetId = receiverId.trim(); - if (targetId.isEmpty) { - _cpInviteLog('skip invite C2C message reason=no_receiver'); - return; - } - final relationType = scNormalizeCpRelationType(invite.relationType); - final receiverName = _cpInviteDisplayName( - nickname: receiverProfile?.userNickname, - account: receiverProfile?.account, - fallback: receiverProfile?.id ?? targetId, - ); - final data = { - "noticeType": SCSysytemMessageType.CP_BUILD.name, - "noticeAction": "INVITE_PENDING", - "action": "INVITE_PENDING", - "status": "PENDING", - "result": "PENDING", - "state": "PENDING", - "expand": applyId, - "applyId": applyId, - "relationType": relationType, - "account": invite.account, - "actualAccount": invite.actualAccount, - "userNickname": invite.userNickname, - "userAvatar": invite.userAvatar, - "userHeaddress": invite.userHeaddress, - "userHeaddressCover": invite.userHeaddressCover, - "acceptUserId": receiverProfile?.id ?? targetId, - "acceptAccount": receiverProfile?.account ?? targetId, - "acceptActualAccount": receiverProfile?.id ?? targetId, - "acceptNickname": receiverName, - "acceptUserNickname": receiverProfile?.userNickname, - "acceptUserAvatar": receiverProfile?.userAvatar, - "acceptHeaddress": receiverProfile?.getHeaddress()?.sourceUrl, - "acceptHeaddressCover": receiverProfile?.getHeaddress()?.cover, - "content": jsonEncode({...invite.toJson(), "relationType": relationType}), - }; - _cpInviteLog( - 'send invite C2C message receiver=$targetId ' - 'applyId=$applyId relationType=$relationType', - ); - await _sendCpBuildCustomMessage( - receiverId: targetId, - data: data, - debugName: 'invite', - applyId: applyId, - ); - } - - Future insertLocalOutgoingCpGiftInvite({ - required String receiverId, - required String relationType, - required SocialChatGiftRes gift, - required int giftCount, - SocialChatUserProfile? receiverProfile, - }) async { - final targetId = receiverId.trim(); - final currentProfile = AccountStorage().getCurrentUser()?.userProfile; - final senderId = currentProfile?.id?.trim() ?? ""; - if (targetId.isEmpty || senderId.isEmpty) { - _cpInviteLog( - 'skip local outgoing invite reason=missing_user ' - 'sender=$senderId receiver=$targetId', - ); - return; - } - final normalizedRelationType = scNormalizeCpRelationType(relationType); - final applyId = - 'local_${normalizedRelationType}_${senderId}_${targetId}_' - '${DateTime.now().microsecondsSinceEpoch}'; - final receiverName = _cpInviteDisplayName( - nickname: receiverProfile?.userNickname, - account: receiverProfile?.account, - fallback: receiverProfile?.id ?? targetId, - ); - final invite = SCSystemInvitMessageRes( - account: currentProfile?.account, - actualAccount: senderId, - userNickname: currentProfile?.userNickname, - userAvatar: currentProfile?.userAvatar, - userHeaddress: currentProfile?.getHeaddress()?.sourceUrl, - userHeaddressCover: currentProfile?.getHeaddress()?.cover, - relationType: normalizedRelationType, - dismissEndTime: DateTime.now().millisecondsSinceEpoch + 86400000, - giftCover: gift.giftPhoto, - giftCount: giftCount, - ); - final inviteJson = invite.toJson(); - inviteJson["gift"] = gift.toJson(); - final data = { - "noticeType": SCSysytemMessageType.CP_BUILD.name, - "noticeAction": "INVITE_PENDING", - "noticeSource": "LOCAL_OUTGOING", - "action": "INVITE_PENDING", - "status": "PENDING", - "result": "PENDING", - "state": "PENDING", - "expand": applyId, - "applyId": applyId, - "relationType": normalizedRelationType, - "account": currentProfile?.account, - "actualAccount": senderId, - "applyUserId": senderId, - "applyActualAccount": senderId, - "userNickname": currentProfile?.userNickname, - "userAvatar": currentProfile?.userAvatar, - "userHeaddress": currentProfile?.getHeaddress()?.sourceUrl, - "userHeaddressCover": currentProfile?.getHeaddress()?.cover, - "acceptUserId": receiverProfile?.id ?? targetId, - "acceptAccount": receiverProfile?.account ?? targetId, - "acceptActualAccount": receiverProfile?.id ?? targetId, - "acceptNickname": receiverName, - "acceptUserNickname": receiverProfile?.userNickname, - "acceptUserAvatar": receiverProfile?.userAvatar, - "acceptHeaddress": receiverProfile?.getHeaddress()?.sourceUrl, - "acceptHeaddressCover": receiverProfile?.getHeaddress()?.cover, - "gift": gift.toJson(), - "giftCover": gift.giftPhoto, - "giftCount": giftCount, - "content": jsonEncode(inviteJson), - }; - _cpInviteLog( - 'insert local outgoing CP_BUILD ' - 'receiver=$targetId applyId=$applyId relationType=$normalizedRelationType', - ); - if (_shownCpInviteMessageKeys.add(applyId)) { - unawaited( - _showOutgoingCpInviteWaitingDialog( - applyId: applyId, - invite: invite, - currentProfile: currentProfile, - receiverProfile: receiverProfile, - receiverId: targetId, - relationType: normalizedRelationType, - ), - ); - } - await _insertCpBuildC2CMessageToLocalStorage( - peerId: targetId, - senderId: senderId, - data: data, - debugName: 'local-outgoing-invite', - applyId: applyId, - ); - } - - Future _showOutgoingCpInviteWaitingDialog({ - required String applyId, - required SCSystemInvitMessageRes invite, - required SocialChatUserProfile? currentProfile, - required SocialChatUserProfile? receiverProfile, - required String receiverId, - required String relationType, - }) async { - final context = navigatorKey.currentState?.context; - if (context == null || currentProfile == null) { - _cpInviteLog( - 'skip local waiting dialog reason=no_context applyId=$applyId', - ); - return; - } - final currentName = _cpInviteDisplayName( - nickname: currentProfile.userNickname, - account: currentProfile.account, - fallback: currentProfile.id, - ); - final receiverName = _cpInviteDisplayName( - nickname: receiverProfile?.userNickname, - account: receiverProfile?.account, - fallback: receiverProfile?.id ?? receiverId, - ); - _cpInviteLog( - 'show local waiting CP_BUILD applyId=$applyId ' - 'relationType=$relationType receiver=$receiverId', - ); - await RoomCpInviteDialog.show( - context, - inviter: RoomCpInviteDialogUser( - name: currentName, - avatarUrl: currentProfile.userAvatar ?? "", - headdress: currentProfile.getHeaddress()?.sourceUrl ?? "", - headdressCover: currentProfile.getHeaddress()?.cover ?? "", - ), - receiver: RoomCpInviteDialogUser( - name: receiverName, - avatarUrl: receiverProfile?.userAvatar ?? "", - headdress: receiverProfile?.getHeaddress()?.sourceUrl ?? "", - headdressCover: receiverProfile?.getHeaddress()?.cover ?? "", - ), - style: RoomCpInviteDialogStyle.waiting, - countdownSeconds: _cpInviteCountdownSeconds(invite), - descriptionText: _cpInviteWaitingDescriptionText(relationType), - onClose: () { - _cpInviteLog('close local waiting CP_BUILD applyId=$applyId'); - }, - onTimeout: () { - _cpInviteLog('timeout local waiting CP_BUILD applyId=$applyId'); - }, - ); - } - - Future _sendCpBuildCustomMessage({ - required String receiverId, - required Map data, - required String debugName, - required String applyId, - }) async { - try { - final createCustomMessageRes = await TencentImSDKPlugin.v2TIMManager - .getMessageManager() - .createCustomMessage( - data: jsonEncode(data), - extension: SCSysytemMessageType.CP_BUILD.name, - ); - if (createCustomMessageRes.code != 0) { - _cpInviteLog( - 'create $debugName C2C message failed ' - 'code=${createCustomMessageRes.code} ' - 'desc=${createCustomMessageRes.desc}', - ); - return; - } - final id = createCustomMessageRes.data?.id; - if (id == null || id.isEmpty) { - _cpInviteLog('create $debugName C2C message failed reason=no_id'); - return; - } - final sendMessageRes = await TencentImSDKPlugin.v2TIMManager - .getMessageManager() - .sendMessage( - id: id, - receiver: receiverId, - needReadReceipt: true, - isSupportMessageExtension: true, - groupID: '', - ); - if (sendMessageRes.code == 0) { - _cpInviteLog( - 'send $debugName C2C message success ' - 'receiver=$receiverId applyId=$applyId ' - 'msgId=${sendMessageRes.data?.msgID} ' - 'userID=${sendMessageRes.data?.userID} ' - 'sender=${sendMessageRes.data?.sender} ' - 'self=${sendMessageRes.data?.isSelf}', - ); - _onNew1v1Message(sendMessageRes.data, fallbackUserId: receiverId); - } else { - _cpInviteLog( - 'send $debugName C2C message failed ' - 'receiver=$receiverId applyId=$applyId ' - 'code=${sendMessageRes.code} desc=${sendMessageRes.desc}', - ); - } - } catch (error) { - _cpInviteLog( - 'send $debugName C2C message error ' - 'receiver=$receiverId applyId=$applyId error=$error', - ); - } - } - - void _persistBackendCpInviteToC2CConversationIfNeeded({ - required V2TimMessage? sourceMessage, - required Map data, - required String applyId, - required SCSystemInvitMessageRes invite, - required SocialChatUserProfile currentProfile, - required bool isSender, - }) { - final noticeSource = data["noticeSource"]?.toString() ?? ""; - if (noticeSource == "LOCAL_OUTGOING") { - return; - } - final peerId = - isSender - ? _cpInviteReceiverIdFromMessageData(data) - : _cpInviteInviterIdFromMessageData(data, invite); - if (peerId.isEmpty || - _isCpBuildMessageAlreadyInPeerConversation(sourceMessage, peerId)) { - return; - } - final senderId = isSender ? (currentProfile.id?.trim() ?? "") : peerId; - if (senderId.isEmpty) { - return; - } - _cpInviteLog( - 'persist backend CP_BUILD to local C2C ' - 'peer=$peerId sender=$senderId applyId=$applyId relationType=${invite.relationType}', - ); - unawaited( - _insertCpBuildC2CMessageToLocalStorage( - peerId: peerId, - senderId: senderId, - data: data, - debugName: 'backend-invite', - applyId: applyId, - ), - ); - } - - bool _isCpBuildMessageAlreadyInPeerConversation( - V2TimMessage? message, - String peerId, - ) { - if (message == null || message.groupID != null) { - return false; - } - final normalizedPeerId = peerId.trim(); - if (normalizedPeerId.isEmpty) { - return false; - } - return (message.userID?.trim() == normalizedPeerId || - message.sender?.trim() == normalizedPeerId) && - !SCGlobalConfig.isSystemUserId(message.userID) && - !SCGlobalConfig.isSystemUserId(message.sender); - } - - Future _insertCpBuildC2CMessageToLocalStorage({ - required String peerId, - required String senderId, - required Map data, - required String debugName, - required String applyId, - }) async { - final normalizedPeerId = peerId.trim(); - final normalizedSenderId = senderId.trim(); - if (normalizedPeerId.isEmpty || normalizedSenderId.isEmpty) { - return null; - } - try { - final messageManager = - TencentImSDKPlugin.v2TIMManager.getMessageManager(); - final createCustomMessageRes = await messageManager.createCustomMessage( - data: jsonEncode(data), - extension: SCSysytemMessageType.CP_BUILD.name, - desc: SCSysytemMessageType.CP_BUILD.name, - ); - if (createCustomMessageRes.code != 0) { - _cpInviteLog( - 'create $debugName local C2C failed ' - 'code=${createCustomMessageRes.code} ' - 'desc=${createCustomMessageRes.desc}', - ); - return _upsertSyntheticCpBuildC2CMessage( - peerId: normalizedPeerId, - senderId: normalizedSenderId, - data: data, - debugName: debugName, - applyId: applyId, - ); - } - final createdMsgId = createCustomMessageRes.data?.id ?? ""; - if (createdMsgId.isEmpty) { - _cpInviteLog('create $debugName local C2C failed reason=no_id'); - return _upsertSyntheticCpBuildC2CMessage( - peerId: normalizedPeerId, - senderId: normalizedSenderId, - data: data, - debugName: debugName, - applyId: applyId, - ); - } - var insertMessageRes = await messageManager - .insertC2CMessageToLocalStorageV2( - userID: normalizedPeerId, - senderID: normalizedSenderId, - createdMsgID: createdMsgId, - ); - if (insertMessageRes.code != 0) { - _cpInviteLog( - 'insert $debugName local C2C v2 failed ' - 'peer=$normalizedPeerId sender=$normalizedSenderId ' - 'applyId=$applyId code=${insertMessageRes.code} ' - 'desc=${insertMessageRes.desc}', - ); - // ignore: deprecated_member_use - insertMessageRes = await messageManager.insertC2CMessageToLocalStorage( - data: jsonEncode(data), - userID: normalizedPeerId, - sender: normalizedSenderId, - isExcludedFromLastMessage: false, - ); - } - if (insertMessageRes.code != 0) { - _cpInviteLog( - 'insert $debugName local C2C failed ' - 'peer=$normalizedPeerId sender=$normalizedSenderId ' - 'applyId=$applyId code=${insertMessageRes.code} ' - 'desc=${insertMessageRes.desc}', - ); - return _upsertSyntheticCpBuildC2CMessage( - peerId: normalizedPeerId, - senderId: normalizedSenderId, - data: data, - debugName: debugName, - applyId: applyId, - ); - } - final message = - insertMessageRes.data ?? createCustomMessageRes.data?.messageInfo; - if (message == null) { - _cpInviteLog('insert $debugName local C2C failed reason=no_message'); - return _upsertSyntheticCpBuildC2CMessage( - peerId: normalizedPeerId, - senderId: normalizedSenderId, - data: data, - debugName: debugName, - applyId: applyId, - ); - } - message.userID = normalizedPeerId; - message.sender ??= normalizedSenderId; - message.groupID = null; - message.timestamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000; - message.customElem?.data ??= jsonEncode(data); - _upsertC2CConversationFromMessage( - message, - fallbackUserId: normalizedPeerId, - preferFallbackUserId: true, - ); - onNewMessageCurrentConversationListener?.call(message); - _cpInviteLog( - 'insert $debugName local C2C success ' - 'peer=$normalizedPeerId sender=$normalizedSenderId ' - 'applyId=$applyId msgId=${message.msgID} self=${message.isSelf}', - ); - return message; - } catch (error) { - _cpInviteLog( - 'insert $debugName local C2C error ' - 'peer=$normalizedPeerId sender=$normalizedSenderId ' - 'applyId=$applyId error=$error', - ); - return _upsertSyntheticCpBuildC2CMessage( - peerId: normalizedPeerId, - senderId: normalizedSenderId, - data: data, - debugName: debugName, - applyId: applyId, - ); - } - } - - V2TimMessage _upsertSyntheticCpBuildC2CMessage({ - required String peerId, - required String senderId, - required Map data, - required String debugName, - required String applyId, - }) { - final currentUserId = - AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? ""; - final encodedData = jsonEncode(data); - final message = V2TimMessage( - msgID: 'synthetic_${debugName}_$applyId', - timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - sender: senderId, - userID: peerId, - status: MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC, - elemType: MessageElemType.V2TIM_ELEM_TYPE_CUSTOM, - customElem: V2TimCustomElem( - data: encodedData, - desc: SCSysytemMessageType.CP_BUILD.name, - extension: SCSysytemMessageType.CP_BUILD.name, - ), - isSelf: currentUserId.isNotEmpty && senderId == currentUserId, - isExcludedFromUnreadCount: senderId == currentUserId, - isExcludedFromLastMessage: false, - isSupportMessageExtension: true, - ); - _upsertC2CConversationFromMessage( - message, - fallbackUserId: peerId, - preferFallbackUserId: true, - ); - onNewMessageCurrentConversationListener?.call(message); - _cpInviteLog( - 'upsert $debugName synthetic C2C ' - 'peer=$peerId sender=$senderId applyId=$applyId', - ); - return message; - } - - void _sendIncomingCpInviteRejectedNotice( - SCSystemInvitMessageRes invite, - RtcProvider rtcProvider, - ) { - final groupId = - rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount?.trim() ?? - ""; - if (groupId.isEmpty) { - _cpInviteLog('skip relation rejected notice reason=no_room'); - return; - } - final currentProfile = AccountStorage().getCurrentUser()?.userProfile; - final relationType = scNormalizeCpRelationType(invite.relationType); - _cpInviteLog( - 'send relation rejected notice relationType=$relationType ' - 'target=${invite.actualAccount ?? invite.account ?? ""}', - ); - unawaited( - dispatchMessage( - Msg( - groupId: groupId, - msg: "CP_INVITE_REJECTED", - type: SCRoomMsgType.cpSystemNotice, - noticeAction: "RELATION_REJECTED", - user: currentProfile, - toUser: SocialChatUserProfile( - account: _firstCpInviteValue([ - invite.actualAccount, - invite.account, - ]), - userNickname: _cpInviteDisplayName( - nickname: invite.userNickname, - account: invite.account, - actualAccount: invite.actualAccount, - ), - userAvatar: invite.userAvatar ?? "", - ), - targetUserIds: _compactCpInviteTargetIds([ - invite.actualAccount, - invite.account, - ]), - role: relationType, - ), - addLocal: true, - ), - ); - } - - void _sendIncomingCpRelationEstablishedNotice({ - required String leftName, - required String rightName, - required String relationType, - required SocialChatUserProfile? currentProfile, - required SCSystemInvitMessageRes invite, - required RtcProvider rtcProvider, - }) { - final groupId = - rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount?.trim() ?? - ""; - if (groupId.isEmpty) { - _cpInviteLog('skip relation established notice reason=no_room'); - return; - } - unawaited( - dispatchMessage( - Msg( - groupId: groupId, - msg: scBuildCpRelationEstablishedNotice( - userName1: leftName, - userName2: rightName, - relationType: relationType, - ), - type: SCRoomMsgType.cpSystemNotice, - noticeAction: "RELATION_ESTABLISHED", - user: currentProfile, - toUser: SocialChatUserProfile( - account: _firstCpInviteValue([ - invite.actualAccount, - invite.account, - ]), - userNickname: leftName, - userAvatar: invite.userAvatar ?? "", - ), - targetUserIds: _compactCpInviteTargetIds([ - currentProfile?.id, - currentProfile?.account, - invite.actualAccount, - invite.account, - ]), - role: relationType, - ), - addLocal: true, - ), - ); - } - int _cpInviteCountdownSeconds(SCSystemInvitMessageRes invite) { final endTime = invite.dismissEndTime; if (endTime == null || endTime <= 0) { @@ -2353,14 +1523,6 @@ class RealTimeMessagingManager extends ChangeNotifier { ); } - List _compactCpInviteTargetIds(Iterable values) { - return values - .map((value) => value?.trim() ?? "") - .where((value) => value.isNotEmpty) - .toSet() - .toList(); - } - String? _firstCpInviteValue(Iterable values) { for (final value in values) { final text = value?.trim() ?? ""; diff --git a/lib/shared/business_logic/models/res/sc_first_recharge_reward_res.dart b/lib/shared/business_logic/models/res/sc_first_recharge_reward_res.dart index ab9f44c..f8ab015 100644 --- a/lib/shared/business_logic/models/res/sc_first_recharge_reward_res.dart +++ b/lib/shared/business_logic/models/res/sc_first_recharge_reward_res.dart @@ -141,6 +141,7 @@ class SCFirstRechargeRewardItem { const SCFirstRechargeRewardItem({ required this.id, required this.type, + required this.typeName, required this.name, required this.content, required this.quantity, @@ -152,33 +153,58 @@ class SCFirstRechargeRewardItem { factory SCFirstRechargeRewardItem.fromJson(dynamic json) { final map = _asMap(json); final content = _readString(map['content']); - final explicitDays = _firstInt(map, const [ + final badgeText = _firstString(map, const [ + 'badgeText', + 'cornerText', + 'tagText', + 'durationText', + 'validityText', + ]); + final explicitDays = _firstDays(map, const [ 'days', 'day', 'durationDays', 'validDays', 'validityDays', + 'validity', + 'validDay', + 'expireDays', + 'effectiveDays', + 'duration', + 'validPeriod', ]); return SCFirstRechargeRewardItem( id: _readString(map['id']), type: _readString(map['type']), + typeName: _firstString(map, const [ + 'typeName', + 'rewardTypeName', + 'itemTypeName', + 'propTypeName', + 'goodsTypeName', + 'categoryName', + 'displayTypeName', + 'typeLabel', + 'typeDesc', + ]), name: _readString(map['name']), content: content, quantity: _readInt(map['quantity']), cover: _readString(map['cover']), - badgeText: _firstString(map, const [ - 'badgeText', - 'cornerText', - 'tagText', - 'durationText', - 'validityText', - ]), - days: explicitDays > 0 ? explicitDays : _readDaysFromText(content), + badgeText: badgeText, + days: + explicitDays > 0 + ? explicitDays + : _firstPositiveInt([ + _readDaysFromText(badgeText), + _readDaysFromText(content), + ]), ); } final String id; final String type; + final String typeName; final String name; final String content; final int quantity; @@ -193,27 +219,37 @@ class SCFirstRechargeRewardItem { normalizedType.contains('CANDY'); } - String get displayTitle { - if (isCoinReward && quantity > 0) { - return '${_formatNumber(quantity)} Coins'; + int get coinAmount { + if (!isCoinReward) { + return 0; } - return _firstNonEmpty([name, content, quantity > 0 ? 'x$quantity' : '']); + return _firstPositiveInt([ + quantity, + _readAmountFromText(content), + _readAmountFromText(name), + _readAmountFromText(badgeText), + ]); + } + + String get displayTitle { + if (isCoinReward) { + final amount = coinAmount; + return amount > 0 ? '${_formatNumber(amount)} Coins' : ''; + } + return _firstNonEmpty([ + typeName, + _formatRewardTypeName(type), + name, + content, + quantity > 0 ? 'x$quantity' : '', + ]); } String get displayBadgeText { if (isCoinReward) { return ''; } - if (badgeText.trim().isNotEmpty) { - return badgeText.trim(); - } - if (days > 0) { - return '${days}Days'; - } - if (quantity > 1) { - return 'x$quantity'; - } - return ''; + return '${days > 0 ? days : 1}d'; } } @@ -270,9 +306,18 @@ int _readInt(dynamic value) { return 0; } -int _firstInt(Map map, List keys) { +int _firstDays(Map map, List keys) { for (final key in keys) { - final value = _readInt(map[key]); + final value = _readDaysValue(map[key]); + if (value > 0) { + return value; + } + } + return 0; +} + +int _firstPositiveInt(List values) { + for (final value in values) { if (value > 0) { return value; } @@ -300,6 +345,31 @@ String _firstNonEmpty(List values) { return ''; } +int _readDaysValue(dynamic value) { + if (value is num) { + return value.toInt(); + } + final text = _readString(value); + if (text.isEmpty) { + return 0; + } + final numeric = int.tryParse(text); + if (numeric != null) { + return numeric; + } + return _readDaysFromText(text); +} + +int _readAmountFromText(String text) { + final match = RegExp(r'\d[\d,\.]*').firstMatch(text); + if (match == null) { + return 0; + } + final normalized = (match.group(0) ?? '').replaceAll(',', ''); + final value = num.tryParse(normalized); + return value == null ? 0 : value.round(); +} + String _readDiscountText(Map map) { for (final key in const [ 'discountText', @@ -370,6 +440,21 @@ int _readDaysFromText(String text) { return int.tryParse(match.group(1) ?? '') ?? 0; } +String _formatRewardTypeName(String type) { + final normalized = type.trim(); + if (normalized.isEmpty) { + return ''; + } + return normalized + .split(RegExp(r'[_\-\s]+')) + .where((segment) => segment.isNotEmpty) + .map((segment) { + final lower = segment.toLowerCase(); + return lower[0].toUpperCase() + lower.substring(1); + }) + .join(' '); +} + String _formatNumber(num value) { final text = value.toInt().toString(); final buffer = StringBuffer(); diff --git a/lib/ui_kit/widgets/first_recharge/first_recharge_dialog.dart b/lib/ui_kit/widgets/first_recharge/first_recharge_dialog.dart index 1e81e09..51f3dbb 100644 --- a/lib/ui_kit/widgets/first_recharge/first_recharge_dialog.dart +++ b/lib/ui_kit/widgets/first_recharge/first_recharge_dialog.dart @@ -88,13 +88,13 @@ class FirstRechargeDialogData { ), FirstRechargeReward( title: 'Vehicle', - badgeText: '3Days', + badgeText: '3d', cover: _FirstRechargeAssets.rewardVehicle, style: FirstRechargeRewardStyle.prop, ), FirstRechargeReward( title: 'Frame', - badgeText: '3Days', + badgeText: '3d', cover: _FirstRechargeAssets.rewardFrame, style: FirstRechargeRewardStyle.prop, ), @@ -113,13 +113,13 @@ class FirstRechargeDialogData { rewards: [ FirstRechargeReward( title: 'Avatar Frame', - badgeText: '7Days', + badgeText: '7d', cover: _FirstRechargeAssets.rewardAvatarFrame, style: FirstRechargeRewardStyle.prop, ), FirstRechargeReward( title: 'Room Frame', - badgeText: '7Days', + badgeText: '7d', cover: _FirstRechargeAssets.rewardFrame, style: FirstRechargeRewardStyle.prop, ), @@ -130,7 +130,7 @@ class FirstRechargeDialogData { ), FirstRechargeReward( title: 'Vehicle', - badgeText: '7Days', + badgeText: '7d', cover: _FirstRechargeAssets.rewardVehicle, style: FirstRechargeRewardStyle.prop, ), @@ -142,7 +142,7 @@ class FirstRechargeDialogData { ), FirstRechargeReward( title: 'Profile Frame', - badgeText: '7Days', + badgeText: '7d', cover: _FirstRechargeAssets.rewardAvatarFrame, style: FirstRechargeRewardStyle.prop, ), @@ -197,16 +197,16 @@ class FirstRechargeDialogData { static bool _isDisplayableRewardItem(SCFirstRechargeRewardItem item) { if (item.isCoinReward) { - return item.quantity > 0 || item.displayTitle.trim().isNotEmpty; + return item.coinAmount > 0; } - return item.name.trim().isNotEmpty || item.cover.trim().isNotEmpty; + return item.displayTitle.trim().isNotEmpty || item.cover.trim().isNotEmpty; } static String _rewardTitle(SCFirstRechargeRewardItem item) { if (item.isCoinReward) { return item.displayTitle; } - return item.name.trim(); + return item.displayTitle; } static String _formatPriceText(SCFirstRechargeLevelConfig level) { diff --git a/lib/ui_kit/widgets/room/room_recharge_bottom_sheet.dart b/lib/ui_kit/widgets/room/room_recharge_bottom_sheet.dart index 60c004c..cd1fdb4 100644 --- a/lib/ui_kit/widgets/room/room_recharge_bottom_sheet.dart +++ b/lib/ui_kit/widgets/room/room_recharge_bottom_sheet.dart @@ -11,7 +11,6 @@ import 'package:yumi/main.dart'; import 'package:yumi/modules/wallet/recharge/recharge_page.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/services/payment/google_payment_manager.dart'; -import 'package:yumi/shared/business_logic/models/res/sc_product_config_res.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; @@ -279,8 +278,7 @@ class _RoomRechargeBottomSheetState extends State { Widget _buildFallbackProductItem(int index) { return _RechargeProductCard( selected: index == _fallbackSelectedIndex, - amount: '150,000', - originalAmount: '100,000', + amount: '100,000', price: 'USD 4.99', onTap: () => setState(() => _fallbackSelectedIndex = index), ); @@ -294,32 +292,12 @@ class _RoomRechargeBottomSheetState extends State { final product = processor.productMap[productConfig.produc.id]; return _RechargeProductCard( selected: productConfig.isSelecte, - amount: _formatAmount(_totalCandy(product)), - originalAmount: _formatOriginalAmount(product), + amount: _formatAmount((product?.obtainCandy ?? 0).round()), price: productConfig.produc.price, onTap: () => processor.chooseProductConfig(index), ); } - String _formatOriginalAmount(SCProductConfigRes? product) { - if (product == null) { - return ''; - } - final obtainCandy = (product.obtainCandy ?? 0).round(); - final rewardCandy = (product.rewardCandy ?? 0).round(); - if (obtainCandy <= 0 || rewardCandy <= 0) { - return ''; - } - return _formatAmount(obtainCandy); - } - - int _totalCandy(SCProductConfigRes? product) { - if (product == null) { - return 0; - } - return ((product.obtainCandy ?? 0) + (product.rewardCandy ?? 0)).round(); - } - String _formatAmount(int value) { final sign = value < 0 ? '-' : ''; final digits = value.abs().toString(); @@ -415,14 +393,12 @@ class _RechargeProductCard extends StatelessWidget { const _RechargeProductCard({ required this.selected, required this.amount, - required this.originalAmount, required this.price, required this.onTap, }); final bool selected; final String amount; - final String originalAmount; final String price; final VoidCallback onTap; @@ -476,26 +452,6 @@ class _RechargeProductCard extends StatelessWidget { ), ], ), - SizedBox(height: 4.w), - Visibility( - visible: originalAmount.isNotEmpty, - maintainAnimation: true, - maintainSize: true, - maintainState: true, - child: Text( - originalAmount, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.6), - fontSize: 10.sp, - height: 1, - fontWeight: FontWeight.w400, - decoration: TextDecoration.lineThrough, - decorationColor: Colors.white.withValues(alpha: 0.6), - ), - ), - ), ], ), ), diff --git a/需求进度.md b/需求进度.md index 814265e..e4edf6a 100644 --- a/需求进度.md +++ b/需求进度.md @@ -1014,3 +1014,27 @@ - [ ] 18:06:26 检查 iOS 模拟器设备,准备进行运行验证。 - [x] 18:06:55 已确认本机可用 iPhone 17 Pro 与 iPhone 17 Pro Max 模拟器,开始用 iPhone 17 Pro 编译运行验证。 + +- [ ] 20:27:16 新增调整:首充弹窗非金币奖励文本改用类型名称,左上角天数显示为 Xd,缺省 1d。 + +- [ ] 20:27:44 已定位当前实现:非金币标题取 name,角标取 badgeText/Days;开始确认接口字段并调整模型。 + +- [ ] 20:28:12 接口文档示例未包含 typeName,按兼容字段解析类型名称并保留 type 可读兜底。 + +- [x] 20:29:02 已修改首充奖励模型与 UI 映射:非金币标题优先类型名称,角标统一 Xd 且默认 1d。 + +- [ ] 20:29:12 开始格式化并检查首充奖励模型与弹窗文件。 + +- [ ] 20:29:34 静态检查发现旧的 _firstInt 已无引用,开始移除死代码。 + +- [x] 20:29:56 已完成格式化与静态检查:首充奖励模型、弹窗文件 flutter analyze 无问题。 + +- [ ] 20:38:55 反馈修正:金币类型标题必须显示金币金额,非金币才显示类型名称;开始调整解析兜底。 + +- [ ] 20:39:21 已确认金币 quantity 为空时会误用类型兜底;开始新增 coinAmount 并限制金币 item 只显示金额。 + +- [x] 20:39:46 已修正金币标题逻辑:金币只显示解析出的金额 Coins,非金币才走类型名称。 + +- [ ] 20:39:56 开始格式化并静态检查金币金额显示修正。 + +- [x] 20:41:16 金币金额显示修正已通过格式化与 flutter analyze 检查。