import 'dart:async'; import 'dart:convert'; import 'package:extended_image/extended_image.dart' show ExtendedImage, ExtendedRawImage, ExtendedImageState, LoadState; import 'package:extended_text/extended_text.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/tools/sc_system_message_utils.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.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/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_custom_elem.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'; import 'package:tencent_cloud_chat_sdk/models/v2_tim_text_elem.dart'; 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/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'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; 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/rtm_manager.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/modules/store/store_route.dart'; 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 '../../../shared/data_sources/models/enum/sc_sysytem_message_type.dart'; import '../../../shared/business_logic/models/res/sc_system_invit_message_res.dart'; enum _CpInviteMessageState { pending, expired, rejected, accepted } class MessageSystemPage extends StatefulWidget { final V2TimConversation? conversation; final bool shrinkWrap; //是否是房间内聊天界面。 final bool inRoom; const MessageSystemPage({ Key? key, this.conversation, this.shrinkWrap = false, this.inRoom = false, }) : super(key: key); @override _MessageSystemPageState createState() => _MessageSystemPageState(); } class _MessageSystemPageState extends State { final ScrollController _scrollController = ScrollController(); final RefreshController _refreshController = RefreshController(); RtmProvider? rtmProvider; V2TimConversation? currentConversation; List currentConversationMessageList = []; final Map _localCpInviteStates = {}; BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy; @override void initState() { super.initState(); rtmProvider = Provider.of(context, listen: false); currentConversation = widget.conversation; rtmProvider?.onNewMessageCurrentConversationListener = _onNewMessage; loadMsg(); } @override void dispose() { rtmProvider?.onNewMessageCurrentConversationListener = null; super.dispose(); } Future loadMsg() async { V2TimValueCallback> v2timValueCallback = await TencentImSDKPlugin.v2TIMManager .getMessageManager() .getC2CHistoryMessageList( userID: currentConversation!.userID!, count: 100, lastMsgID: null, ); List messages = v2timValueCallback.data ?? []; // List messages = await FTIM.getMessageManager().getMessages(conversation: currentConversation); currentConversationMessageList ??= []; currentConversationMessageList?.clear(); for (var msg in messages) { if (!msg.isSelf! && msg.isPeerRead!) { TencentImSDKPlugin.v2TIMManager .getMessageManager() .sendMessageReadReceipts(messageIDList: [msg.msgID!]); } } currentConversationMessageList.insertAll(0, messages); setState(() {}); _scrollController.jumpTo(0.0); } _onNewMessage(V2TimMessage? message, {String? msgId}) async { if (message == null) { return; } if (message.userID != currentConversation?.userID) return; int? index; for (var element in currentConversationMessageList) { if (msgId != null) { if (msgId == element.msgID) { element.status = MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL; setState(() {}); continue; } } else { if (element.msgID == message.msgID) { index = currentConversationMessageList.indexOf(element); continue; } } } if (msgId != null) { return; } if (index != null) { currentConversationMessageList.removeAt(index!); currentConversationMessageList.insert(index!, message); } else { currentConversationMessageList.insert(0, message); } _refreshCpStateForMessage(message); await TencentImSDKPlugin.v2TIMManager .getMessageManager() .markC2CMessageAsRead(userID: currentConversation!.userID!); //发送已读回执 await TencentImSDKPlugin.v2TIMManager .getMessageManager() .sendMessageReadReceipts(messageIDList: [message.msgID!]); setState(() {}); // await FTIM.getContactManager().setReadMessage(currentConversation); } void _refreshCpStateForMessage(V2TimMessage message) { if (message.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { return; } final content = message.customElem?.data; if (content == null || content.isEmpty) { return; } try { final data = jsonDecode(content); final noticeType = data["noticeType"]?.toString(); if (noticeType == SCSysytemMessageType.CP_DISMISS.name || noticeType == SCSysytemMessageType.CP_BLESS.name) { Provider.of( context, listen: false, ).refreshLoadedCpProfiles(); } } catch (_) {} } @override Widget build(BuildContext context) { return Stack( children: [ Image.asset( _strategy.getSCMessageChatPageRoomSettingBackground(), width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, fit: BoxFit.fill, ), Scaffold( backgroundColor: Colors.transparent, resizeToAvoidBottomInset: false, appBar: SocialChatStandardAppBar( title: SCAppLocalizations.of(context)!.system, actions: [], ), body: SafeArea(top: false, child: _msgList()), ), ], ); } Widget _msgList() { return SmartRefresher( enablePullDown: false, enablePullUp: true, onLoading: () async { if (currentConversationMessageList.isNotEmpty) { // 拉取单聊历史消息 // 首次拉取,lastMsgID 设置为 null // 再次拉取时,lastMsgID 可以使用返回的消息列表中的最后一条消息的id V2TimValueCallback> v2timValueCallback = await TencentImSDKPlugin.v2TIMManager .getMessageManager() .getC2CHistoryMessageList( userID: currentConversation!.userID ?? "", count: 30, lastMsgID: currentConversationMessageList.last.msgID, ); List messages = v2timValueCallback.data as List; currentConversationMessageList.addAll(messages); if (messages.length == 30) { _refreshController.loadComplete(); } else { _refreshController.loadNoData(); } } else { _refreshController.loadNoData(); } setState(() {}); }, footer: CustomFooter( height: 1, builder: (context, mode) { return SizedBox( height: 1, width: 1, child: SizedBox(height: 1, width: 1), ); }, ), controller: _refreshController, child: Container( child: CustomScrollView( shrinkWrap: currentConversationMessageList.length > 10 ? false : true, controller: _scrollController, reverse: true, physics: const ClampingScrollPhysics(), // 禁用回弹效果 slivers: [ SliverList( delegate: SliverChildBuilderDelegate( (c, i) => _MessageItem( message: currentConversationMessageList[i], preMessage: i < currentConversationMessageList.length - 1 ? currentConversationMessageList[i + 1] : null, currentConversationMessageList: currentConversationMessageList, localCpInviteStates: _localCpInviteStates, onCpInviteStateChanged: _setLocalCpInviteState, updateCall: () { setState(() {}); }, ), childCount: currentConversationMessageList.length, ), ), ], ), ), ); } void _setLocalCpInviteState(String applyId, _CpInviteMessageState state) { final normalizedApplyId = applyId.trim(); if (normalizedApplyId.isEmpty) { return; } _localCpInviteStates[normalizedApplyId] = state; if (mounted) { setState(() {}); } } } class _MessageItem extends StatelessWidget { static bool _isOpeningCpInviteDialog = false; final V2TimMessage message; final V2TimMessage? preMessage; List currentConversationMessageList = []; final Map localCpInviteStates; final void Function(String applyId, _CpInviteMessageState state)? onCpInviteStateChanged; Function updateCall; ///上一条 late BuildContext context; _MessageItem({ required this.message, this.preMessage, required this.currentConversationMessageList, this.localCpInviteStates = const {}, this.onCpInviteStateChanged, required this.updateCall, }); @override Widget build(BuildContext context) { this.context = context; bool showTime = true; int timestamp = (message.timestamp ?? 0) * 1000; int preTimestamp = (preMessage?.timestamp ?? 0) * 1000; if (preMessage != null) { ///5分钟以内 不显示 if (DateTime.fromMillisecondsSinceEpoch(timestamp) .difference(DateTime.fromMillisecondsSinceEpoch(preTimestamp)) .inMilliseconds < 1000 * 60 * 5) { showTime = false; } } String time = SCMDateUtils.formatMessageTime( context, DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0), ); String noticeType = ""; String tagHead = ""; if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { V2TimCustomElem customElem = message.customElem!; String content = customElem.data ?? ""; try { final data = jsonDecode(content); noticeType = data["noticeType"]?.toString() ?? ""; if (noticeType == SCSysytemMessageType.CP_LOVE_LETTER.name || noticeType == SCSysytemMessageType.CP_DISMISS.name || noticeType == SCSysytemMessageType.CP_BLESS.name) { var bean = SCSystemInvitMessageRes.fromJson( jsonDecode(data["content"]), ); tagHead = bean.userAvatar ?? ""; } } catch (_) {} } final cpRelationFormedCard = _cpRelationFormedMessageCard(); final cpStatusLineText = _cpStatusLineTextForMessage(); return Container( margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 8.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, // textDirection: message.isSelf ? TextDirection.rtl : TextDirection.ltr, children: [ Visibility( visible: showTime, child: Container( margin: EdgeInsets.only(bottom: 12.w), child: Row( children: [ Spacer(), Container( alignment: Alignment.center, padding: EdgeInsets.symmetric( horizontal: width(15), vertical: 8.w, ), decoration: BoxDecoration( color: Colors.black12, borderRadius: BorderRadius.all( Radius.circular(height(13)), ), ), child: Text( time, style: TextStyle( fontSize: sp(12), color: Colors.white, fontWeight: FontWeight.w400, decoration: TextDecoration.none, height: 1, ), ), ), Spacer(), ], ), ), ), if (cpStatusLineText != null) _buildCpStatusLine(cpStatusLineText), if (cpRelationFormedCard != null) Padding( padding: EdgeInsets.only( top: cpStatusLineText == null ? 0 : 10.w, ), child: cpRelationFormedCard, ) else if (cpStatusLineText == null) Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, textDirection: message.isSelf! ? TextDirection.rtl : TextDirection.ltr, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () {}, child: noticeType == SCSysytemMessageType.CP_LOVE_LETTER.name && tagHead.isNotEmpty ? netImage( url: tagHead, width: 45.w, shape: BoxShape.circle, ) : ExtendedImage.asset( SCGlobalConfig.businessLogicStrategy .getMessagePageSystemMessageIcon(), width: 45.w, shape: BoxShape.circle, fit: BoxFit.cover, ), ), // 他人消息的阴影间隔是10dp Container(width: 8.w), Expanded( child: Row( textDirection: TextDirection.ltr, children: [ Expanded( child: Container( alignment: AlignmentDirectional.topStart, margin: EdgeInsets.only(top: 4.w), child: _msg(), ), ), ], ), ), ], ), ], ), ); } Widget _buildCpStatusLine(String text) { return SizedBox( width: double.infinity, child: Center( child: Text( text, textAlign: TextAlign.center, style: TextStyle( fontSize: sp(14), height: 1.2, color: const Color(0xff999999), fontWeight: FontWeight.w700, decoration: TextDecoration.none, ), ), ), ); } Widget _buildCpBuildMessage( Map data, Function(BuildContext ct, String content) showMsgItemMenu, SCSystemInvitMessageRes? cpInvite, ) { final state = _cpInviteStateForMessage(data); final actionState = _cpInviteActionState(state); final subtitle = _cpInviteMessageSubtitle(state); final giftCover = cpInvite?.giftCover?.trim() ?? ""; return Builder( builder: (ct) { return GestureDetector( onLongPress: () { showMsgItemMenu(ct, ""); }, onTap: () { _openCpInviteDialogFromMessage(data, cpInvite, actionState); }, child: Container( width: 251.w, height: 64.w, decoration: const BoxDecoration(color: Color(0xff071D1A)), child: Stack( children: [ Positioned( left: 0, top: 6.w, child: Image.asset( 'sc_images/room/cp_invite/sc_cp_system_notice_bg.png', width: 251.w, height: 53.w, fit: BoxFit.fill, ), ), Positioned( left: 14.w, top: 15.w, child: _buildCpInviteGiftThumb(giftCover), ), Positioned( left: 75.w, top: 18.w, child: SizedBox( width: 160.w, child: Text( SCAppLocalizations.of( context, )!.closeFriendInvitationTitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: const Color(0xffFF2A70), fontSize: 10.sp, fontWeight: FontWeight.w500, height: 1, decoration: TextDecoration.none, ), ), ), ), Positioned( left: 75.w, top: 35.w, child: SizedBox( width: 160.w, child: Text( subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: const Color(0xff67576F), fontSize: 8.sp, fontWeight: FontWeight.w400, height: 1, decoration: TextDecoration.none, ), ), ), ), ], ), ), ); }, ); } void _openCpInviteDialogFromMessage( Map data, SCSystemInvitMessageRes? cpInvite, RoomCpInviteActionState actionState, ) { unawaited(_openCpInviteDialogFromMessageAsync(data, cpInvite, actionState)); } Future _openCpInviteDialogFromMessageAsync( Map data, SCSystemInvitMessageRes? cpInvite, RoomCpInviteActionState actionState, ) async { if (_isOpeningCpInviteDialog || SmartDialog.checkExist(tag: RoomCpInviteDialog.dialogTag)) { return; } _isOpeningCpInviteDialog = true; try { var latestState = _cpInviteStateForMessage(data); if (actionState != RoomCpInviteActionState.pending || latestState != _CpInviteMessageState.pending) { _showCpInviteDialogFromMessage( data, cpInvite, actionState == RoomCpInviteActionState.pending ? _cpInviteActionState(latestState) : actionState, ); return; } latestState = await _latestCpInviteStateForMessage(data); if (!context.mounted) { return; } if (latestState != _CpInviteMessageState.pending) { _rememberCpInviteState(data, latestState); updateCall(); } _showCpInviteDialogFromMessage( data, cpInvite, _cpInviteActionState(latestState), ); } finally { _isOpeningCpInviteDialog = false; } } Widget _buildCpInviteGiftThumb(String giftCover) { final child = giftCover.isNotEmpty ? netImage( url: giftCover, width: 49.w, height: 49.w, fit: BoxFit.cover, defaultImg: 'sc_images/room/cp_profile/sc_cp_profile_gift_fallback.png', ) : Image.asset( 'sc_images/room/cp_profile/sc_cp_profile_gift_fallback.png', width: 49.w, height: 49.w, fit: BoxFit.cover, ); return ClipRRect(borderRadius: BorderRadius.circular(6.w), child: child); } Widget? _cpRelationFormedMessageCard() { final data = _customDataFromMessage(message); if (data == null || !_isCpInvitePayload(data)) { return null; } if (_cpInviteStateFromData(data) != _CpInviteMessageState.accepted) { return null; } final cpInvite = _cpInviteFromMessageData(data); final content = _cpContentMap(data); final currentProfile = AccountStorage().getCurrentUser()?.userProfile; final currentName = _cpDisplayName( nickname: currentProfile?.userNickname, account: currentProfile?.account, fallback: currentProfile?.id, ); final inviterName = _cpDisplayName( nickname: _firstNonBlankString([ cpInvite?.userNickname, data["applyNickname"]?.toString(), data["applyUserNickname"]?.toString(), data["userNickname"]?.toString(), data["senderNickname"]?.toString(), content["applyNickname"]?.toString(), content["applyUserNickname"]?.toString(), content["userNickname"]?.toString(), content["senderNickname"]?.toString(), ], fallback: ""), account: _firstNonBlankString([ cpInvite?.account, data["account"]?.toString(), data["applyUserAccount"]?.toString(), content["account"]?.toString(), content["applyUserAccount"]?.toString(), ], fallback: ""), actualAccount: _firstNonBlankString([ cpInvite?.actualAccount, data["actualAccount"]?.toString(), data["applyUserId"]?.toString(), content["actualAccount"]?.toString(), content["applyUserId"]?.toString(), ], fallback: ""), fallback: message.sender, ); final inviterAvatar = _firstNonBlankString([ cpInvite?.userAvatar, data["applyUserAvatar"]?.toString(), data["userAvatar"]?.toString(), data["senderAvatar"]?.toString(), content["applyUserAvatar"]?.toString(), content["userAvatar"]?.toString(), content["senderAvatar"]?.toString(), ], fallback: ""); final receiverName = _cpDisplayName( nickname: _firstNonBlankString([ data["acceptNickname"]?.toString(), data["acceptUserNickname"]?.toString(), data["receiverNickname"]?.toString(), data["receiverUserNickname"]?.toString(), content["acceptNickname"]?.toString(), content["acceptUserNickname"]?.toString(), content["receiverNickname"]?.toString(), content["receiverUserNickname"]?.toString(), ], fallback: ""), account: _firstNonBlankString([ data["acceptAccount"]?.toString(), data["receiverAccount"]?.toString(), content["acceptAccount"]?.toString(), content["receiverAccount"]?.toString(), ], fallback: ""), actualAccount: _firstNonBlankString([ data["acceptActualAccount"]?.toString(), data["acceptUserId"]?.toString(), data["receiverActualAccount"]?.toString(), data["receiverUserId"]?.toString(), content["acceptActualAccount"]?.toString(), content["acceptUserId"]?.toString(), content["receiverActualAccount"]?.toString(), content["receiverUserId"]?.toString(), ], fallback: ""), fallback: currentName, ); final receiverAvatar = _firstNonBlankString([ data["acceptUserAvatar"]?.toString(), data["receiverAvatar"]?.toString(), data["receiverUserAvatar"]?.toString(), content["acceptUserAvatar"]?.toString(), content["receiverAvatar"]?.toString(), content["receiverUserAvatar"]?.toString(), ], fallback: currentProfile?.userAvatar ?? ""); final isInviter = _isCurrentUserCpInviter(data); final relationType = scNormalizeCpRelationType( _firstNonBlankString([ cpInvite?.relationType, data["relationType"]?.toString(), data["relation_type"]?.toString(), content["relationType"]?.toString(), content["relation_type"]?.toString(), ], fallback: "CP"), ); return Builder( builder: (ct) { return GestureDetector( onLongPress: () => _showMsgItemMenu(ct, ""), child: SizedBox( width: double.infinity, child: Center( child: RoomCpRelationFormedCard( leftName: isInviter ? currentName : inviterName, rightName: isInviter ? receiverName : currentName, leftAvatar: isInviter ? currentProfile?.userAvatar ?? "" : inviterAvatar, rightAvatar: isInviter ? receiverAvatar : currentProfile?.userAvatar ?? "", relationType: relationType, ), ), ), ); }, ); } void _showCpInviteDialogFromMessage( Map data, SCSystemInvitMessageRes? cpInvite, RoomCpInviteActionState actionState, ) { final effectiveActionState = actionState == RoomCpInviteActionState.pending ? _cpInviteActionState(_cpInviteStateForMessage(data)) : actionState; final currentProfile = AccountStorage().getCurrentUser()?.userProfile; final currentName = _cpDisplayName( nickname: currentProfile?.userNickname, account: currentProfile?.account, fallback: currentProfile?.id, ); final inviterName = _cpDisplayName( nickname: cpInvite?.userNickname, account: cpInvite?.account, actualAccount: cpInvite?.actualAccount, ); final isInviter = _isCurrentUserCpInviter(data); final content = _cpContentMap(data); final receiverName = isInviter ? _cpDisplayName( nickname: _firstNonBlankString([ data["acceptNickname"]?.toString(), data["acceptUserNickname"]?.toString(), content["acceptNickname"]?.toString(), content["acceptUserNickname"]?.toString(), ], fallback: ""), account: _firstNonBlankString([ data["acceptAccount"]?.toString(), content["acceptAccount"]?.toString(), ], fallback: ""), actualAccount: _firstNonBlankString([ data["acceptActualAccount"]?.toString(), content["acceptActualAccount"]?.toString(), data["acceptUserId"]?.toString(), content["acceptUserId"]?.toString(), ], fallback: ""), fallback: "User", ) : currentName; final relationType = scNormalizeCpRelationType(cpInvite?.relationType); final applyId = _cpApplyIdFromData(data); RoomCpInviteDialog.show( context, inviter: RoomCpInviteDialogUser( name: inviterName, avatarUrl: cpInvite?.userAvatar ?? "", headdress: isInviter ? currentProfile?.getHeaddress()?.sourceUrl ?? "" : _firstNonBlankString([ cpInvite?.userHeaddress, data["userHeaddress"]?.toString(), data["userAvatarFrame"]?.toString(), content["userHeaddress"]?.toString(), content["userAvatarFrame"]?.toString(), ], fallback: ""), headdressCover: isInviter ? currentProfile?.getHeaddress()?.cover ?? "" : _firstNonBlankString([ cpInvite?.userHeaddressCover, data["userHeaddressCover"]?.toString(), data["userAvatarFrameCover"]?.toString(), content["userHeaddressCover"]?.toString(), content["userAvatarFrameCover"]?.toString(), ], fallback: ""), ), receiver: RoomCpInviteDialogUser( name: receiverName, avatarUrl: isInviter ? _firstNonBlankString([ data["acceptUserAvatar"]?.toString(), content["acceptUserAvatar"]?.toString(), ], fallback: "") : currentProfile?.userAvatar ?? "", headdress: isInviter ? _firstNonBlankString([ data["acceptHeaddress"]?.toString(), data["acceptAvatarFrame"]?.toString(), content["acceptHeaddress"]?.toString(), content["acceptAvatarFrame"]?.toString(), ], fallback: "") : currentProfile?.getHeaddress()?.sourceUrl ?? "", headdressCover: isInviter ? _firstNonBlankString([ data["acceptHeaddressCover"]?.toString(), data["acceptAvatarFrameCover"]?.toString(), content["acceptHeaddressCover"]?.toString(), content["acceptAvatarFrameCover"]?.toString(), ], fallback: "") : currentProfile?.getHeaddress()?.cover ?? "", ), style: isInviter ? RoomCpInviteDialogStyle.waiting : RoomCpInviteDialogStyle.incoming, actionState: effectiveActionState, countdownSeconds: _cpInviteSecondsLeft(data), descriptionText: _cpInviteDescriptionText(relationType), onAccept: !isInviter && effectiveActionState == RoomCpInviteActionState.pending && applyId.isNotEmpty ? () => acceptOpt( applyId, SCSysytemMessageType.CP_BUILD.name, cpInvite: cpInvite, cpData: data, ) : null, onReject: !isInviter && effectiveActionState == RoomCpInviteActionState.pending && applyId.isNotEmpty ? () => rejectOpt( applyId, SCSysytemMessageType.CP_BUILD.name, cpInvite: cpInvite, cpData: data, ) : null, ); } String? _cpStatusLineTextForMessage() { final data = _customDataFromMessage(message); if (data == null) { return null; } final state = _cpInviteStateForMessage(data); if (_shouldRenderCpInviteCardForData(data, state)) { return null; } if (state == _CpInviteMessageState.pending || !_isCpInvitePayload(data)) { return null; } final localizations = SCAppLocalizations.of(context)!; if (state == _CpInviteMessageState.expired) { return localizations.closeFriendInviteTimedOut; } if (state == _CpInviteMessageState.accepted) { return _cpAcceptedStatusLineText(_cpRelationTypeFromMessageData(data)); } final inviterView = _isCurrentUserCpInviter(data); return inviterView ? localizations.otherDeclinedCloseFriendInvite : localizations.youDeclinedCloseFriendInvite; } Map? _customDataFromMessage(V2TimMessage msg) { if (msg.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { return null; } final raw = msg.customElem?.data; if (raw == null || raw.trim().isEmpty) { return null; } try { final decoded = jsonDecode(raw); if (decoded is Map) { return decoded.map((key, value) => MapEntry(key.toString(), value)); } } catch (_) {} return null; } _CpInviteMessageState _cpInviteStateForMessage(Map data) { final localState = _localCpInviteStateForData(data); if (localState != null) { return localState; } final explicitState = _cpInviteStateFromConversation(data); if (explicitState != _CpInviteMessageState.pending) { return explicitState; } return _cpInviteSecondsLeft(data) <= 0 ? _CpInviteMessageState.expired : _CpInviteMessageState.pending; } Future<_CpInviteMessageState> _latestCpInviteStateForMessage( Map data, ) async { final localState = _cpInviteStateForMessage(data); if (localState != _CpInviteMessageState.pending) { return localState; } final applyId = _cpApplyIdFromData(data); if (applyId.isEmpty) { return localState; } try { final status = await SCAccountRepository().cpRelationshipApplyStatus( applyId, ); final backendState = _cpInviteStateFromBackendStatus(status); if (backendState != null) { return backendState; } } catch (_) {} return _cpInviteSecondsLeft(data) <= 0 ? _CpInviteMessageState.expired : localState; } _CpInviteMessageState? _cpInviteStateFromBackendStatus(String? status) { final text = status?.trim().toUpperCase() ?? ""; if (text.isEmpty || text == "WAIT" || text == "WAITING") { return text.isEmpty ? null : _CpInviteMessageState.pending; } if (text.contains("EXPIRE") || text.contains("TIMEOUT") || text.contains("OVERDUE")) { return _CpInviteMessageState.expired; } if (text.contains("REFUSE") || text.contains("REJECT") || text.contains("DECLIN") || text.contains("DENY")) { return _CpInviteMessageState.rejected; } if (text.contains("AGREE") || text.contains("ACCEPT") || text.contains("APPROVE") || text.contains("ESTABLISH")) { return _CpInviteMessageState.accepted; } return null; } _CpInviteMessageState _cpInviteStateFromConversation( Map currentData, ) { final applyId = _cpApplyIdFromData(currentData); final localState = _localCpInviteStateForApplyId(applyId); if (localState != null) { return localState; } final currentState = _cpInviteStateFromData(currentData); if (currentState != _CpInviteMessageState.pending || applyId.isEmpty) { return currentState; } final currentTimestamp = message.timestamp ?? 0; for (final item in currentConversationMessageList) { if ((item.timestamp ?? 0) < currentTimestamp) { continue; } final data = _customDataFromMessage(item); if (data == null || _cpApplyIdFromData(data) != applyId) { continue; } final state = _cpInviteStateFromData(data); if (state != _CpInviteMessageState.pending) { return state; } } return _CpInviteMessageState.pending; } _CpInviteMessageState? _localCpInviteStateForData(Map data) { return _localCpInviteStateForApplyId(_cpApplyIdFromData(data)); } _CpInviteMessageState? _localCpInviteStateForApplyId(String applyId) { final normalizedApplyId = applyId.trim(); if (normalizedApplyId.isEmpty) { return null; } return localCpInviteStates[normalizedApplyId]; } void _rememberCpInviteState( Map data, _CpInviteMessageState state, ) { if (state == _CpInviteMessageState.pending) { return; } final applyId = _cpApplyIdFromData(data); if (applyId.isEmpty) { return; } onCpInviteStateChanged?.call(applyId, state); } _CpInviteMessageState _cpInviteStateFromData(Map data) { final content = _cpContentMap(data); final values = [ data["noticeType"]?.toString() ?? "", data["type"]?.toString() ?? "", data["noticeAction"]?.toString() ?? "", data["action"]?.toString() ?? "", data["status"]?.toString() ?? "", data["applyStatus"]?.toString() ?? "", data["processStatus"]?.toString() ?? "", data["result"]?.toString() ?? "", data["state"]?.toString() ?? "", content["noticeType"]?.toString() ?? "", content["type"]?.toString() ?? "", content["noticeAction"]?.toString() ?? "", content["action"]?.toString() ?? "", content["status"]?.toString() ?? "", content["applyStatus"]?.toString() ?? "", content["processStatus"]?.toString() ?? "", 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; } if (values.contains("REJECT") || values.contains("DECLIN") || values.contains("REFUSE") || values.contains("DENY")) { return _CpInviteMessageState.rejected; } if (values.contains("ACCEPT") || values.contains("AGREE") || values.contains("APPROVE") || values.contains("ESTABLISH")) { return _CpInviteMessageState.accepted; } 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 = [ data["noticeType"]?.toString() ?? "", data["type"]?.toString() ?? "", data["relationType"]?.toString() ?? "", content["noticeType"]?.toString() ?? "", content["type"]?.toString() ?? "", content["relationType"]?.toString() ?? "", ].join("|").toUpperCase(); return text.contains("CP") || text.contains("BROTHER") || text.contains("SISTER"); } bool _isCurrentUserCpInviter(Map data) { final currentProfile = AccountStorage().getCurrentUser()?.userProfile; if (currentProfile == null) { return false; } final content = _cpContentMap(data); final inviterIds = [ data["applyUserId"]?.toString() ?? "", data["applyUserAccount"]?.toString() ?? "", data["account"]?.toString() ?? "", data["actualAccount"]?.toString() ?? "", content["applyUserId"]?.toString() ?? "", content["applyUserAccount"]?.toString() ?? "", content["account"]?.toString() ?? "", content["actualAccount"]?.toString() ?? "", ] .map((value) => value.trim()) .where((value) => value.isNotEmpty) .toSet(); return inviterIds.contains(currentProfile.id?.trim() ?? "") || inviterIds.contains(currentProfile.account?.trim() ?? ""); } int _cpInviteSecondsLeft(Map data) { final content = _cpContentMap(data); final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; final explicitExpireAt = _firstEpochSeconds([ data["expireTime"], data["expiredTime"], data["expireAt"], data["expiredAt"], data["timeoutAt"], data["endTime"], data["dismissEndTime"], content["expireTime"], content["expiredTime"], content["expireAt"], content["expiredAt"], content["timeoutAt"], content["endTime"], content["dismissEndTime"], ]); if (explicitExpireAt != null) { return (explicitExpireAt - now).clamp(0, 86400).toInt(); } final startAt = _firstEpochSeconds([ data["createTime"], data["createdAt"], data["sendTime"], content["createTime"], content["createdAt"], content["sendTime"], message.timestamp, ]) ?? now; return (startAt + 86400 - now).clamp(0, 86400).toInt(); } int? _firstEpochSeconds(Iterable values) { for (final value in values) { final text = value?.toString().trim() ?? ""; if (text.isEmpty) { continue; } final parsed = num.tryParse(text); if (parsed == null || parsed <= 0) { continue; } final seconds = parsed > 9999999999 ? parsed ~/ 1000 : parsed.toInt(); return seconds; } return null; } String _cpApplyIdFromData(Map data) { final content = _cpContentMap(data); return _firstNonBlankString([ data["expand"]?.toString(), data["applyId"]?.toString(), data["apply_id"]?.toString(), data["id"]?.toString(), content["expand"]?.toString(), content["applyId"]?.toString(), content["apply_id"]?.toString(), content["id"]?.toString(), ], fallback: ""); } String _cpRelationTypeFromMessageData(Map data) { final invite = _cpInviteFromMessageData(data); final content = _cpContentMap(data); return scNormalizeCpRelationType( _firstNonBlankString([ invite?.relationType, data["relationType"]?.toString(), data["relation_type"]?.toString(), content["relationType"]?.toString(), content["relation_type"]?.toString(), ], fallback: "CP"), ); } Map _cpContentMap(Map data) { final content = data["content"]; try { if (content is String && content.trim().isNotEmpty) { final decoded = jsonDecode(content); if (decoded is Map) { return decoded.map((key, value) => MapEntry(key.toString(), value)); } } if (content is Map) { return content.map((key, value) => MapEntry(key.toString(), value)); } } catch (_) {} return const {}; } RoomCpInviteActionState _cpInviteActionState(_CpInviteMessageState state) { switch (state) { case _CpInviteMessageState.expired: return RoomCpInviteActionState.expired; case _CpInviteMessageState.rejected: return RoomCpInviteActionState.rejected; case _CpInviteMessageState.accepted: return RoomCpInviteActionState.accepted; case _CpInviteMessageState.pending: return RoomCpInviteActionState.pending; } } String _cpInviteMessageSubtitle(_CpInviteMessageState state) { final localizations = SCAppLocalizations.of(context)!; switch (state) { case _CpInviteMessageState.expired: return localizations.closeFriendInviteTimedOut; case _CpInviteMessageState.rejected: return localizations.closeFriendRequestDeclined; case _CpInviteMessageState.accepted: return localizations.closeFriendsHaveBecome; case _CpInviteMessageState.pending: return localizations.closeFriendInvitationSubtitle; } } String _cpInviteDescriptionText(String relationType) { switch (scNormalizeCpRelationType(relationType)) { case "BROTHER": return 'Invite you to become Brothers'; case "SISTERS": return 'Invite you to become Sisters'; case "CP": default: return 'Invite you to become Couple'; } } String _cpAcceptedStatusLineText(String relationType) { final localizations = SCAppLocalizations.of(context)!; switch (scNormalizeCpRelationType(relationType)) { case "BROTHER": return "Have become sworn brothers"; case "SISTERS": return "Have become sisters"; case "CP": default: return localizations.closeFriendsHaveBecome; } } String _cpDisplayName({ String? nickname, String? account, String? actualAccount, String? fallback, }) { return _firstNonBlankString([nickname, actualAccount, account, fallback]); } bool _shouldRenderCpInviteCardForData( Map data, [ _CpInviteMessageState? state, ]) { if (!_isCpInvitePayload(data)) { return false; } final applyId = _cpApplyIdFromData(data); if (applyId.isEmpty || _cpInviteFromMessageData(data) == null) { return false; } final effectiveState = state ?? _cpInviteStateForMessage(data); return effectiveState != _CpInviteMessageState.accepted; } String _firstNonBlankString( Iterable values, { String fallback = "User", }) { for (final value in values) { final text = value?.trim() ?? ""; if (text.isNotEmpty) { return text; } } return fallback; } _msg() { return _text(); } ///文本消息 _text() { String content = ""; if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) { V2TimTextElem textElem = message.textElem!; content = textElem.text ?? ""; } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE) { content = SCAppLocalizations.of(context)!.image; } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) { content = SCAppLocalizations.of(context)!.video; } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND) { content = SCAppLocalizations.of(context)!.sound; } else if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) { V2TimCustomElem customElem = message.customElem!; content = customElem.data ?? ""; final data = jsonDecode(content); String type = data["noticeType"]; if (type == SCSysytemMessageType.GIVE_AWAY_PROPS.name) { String title = data["title"] ?? ""; String expand = data["expand"] ?? ""; return Builder( builder: (ct) { return GestureDetector( child: Container( decoration: BoxDecoration( color: Color(0xff18F2B1).withOpacity(0.1), borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(8.w), bottomLeft: Radius.circular(8.w), bottomRight: Radius.circular(8), ), ), padding: EdgeInsets.all(8.0.w), child: Column( children: [ text( SCAppLocalizations.of(context)!.propMessagePrompt, maxLines: 1, // specialTextSpanBuilder: MySpecialTextSpanBuilder(), fontSize: sp(16), textColor: Colors.white, fontWeight: FontWeight.bold, ), ExtendedText( data["content"], // specialTextSpanBuilder: MySpecialTextSpanBuilder(), style: TextStyle(fontSize: sp(14), color: Colors.white), ), expand.isNotEmpty ? Row( children: [ SizedBox(width: 10.w), netImage( url: expand, width: 45.w, height: 45.w, fit: BoxFit.contain, ), ], ) : Container(), ], ), ), onTap: () { if (title.isNotEmpty) { var dTitle = jsonDecode(title); String propType = dTitle["propType"] ?? ""; if ("ACTIVITY" == propType || "ADMINISTRSCOR" == propType || "ACHIEVEMENT" == propType) { } else { SCNavigatorUtils.push( context, StoreRoute.bags, replace: false, ); } } else { SCNavigatorUtils.push( context, StoreRoute.bags, replace: false, ); } }, onLongPress: () { _showMsgItemMenu(ct, ""); }, ); }, ); } else if (type == SCSysytemMessageType.AGENT_SEND_INVITE_HOST.name) { return SCSystemMessageUtils.buildAgentSendInviteHostMessage( data, (ct, content) { _showMsgItemMenu(ct, content); }, (String id) { rejectOpt(id, type); }, (String id) { acceptOpt(id, type); }, context, ); } else if (type == SCSysytemMessageType.BD_SEND_INVITE_AGENT.name) { return SCSystemMessageUtils.buildBdSendInviteAgentMessage( data, (ct, content) { _showMsgItemMenu(ct, content); }, (String id) { rejectOpt(id, type); }, (String id) { acceptOpt(id, type); }, context, ); } else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD.name) { return SCSystemMessageUtils.buildBdLeaderInviteBdMessage( data, (ct, content) { _showMsgItemMenu(ct, content); }, (String id) { rejectOpt(id, type); }, (String id) { acceptOpt(id, type); }, context, ); } else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) { return SCSystemMessageUtils.buildBdLeaderInviteBdLeaderMessage( data, (ct, content) { _showMsgItemMenu(ct, content); }, (String id) { rejectOpt(id, type); }, (String id) { acceptOpt(id, type); }, context, ); } else if (type == SCSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) { return SCSystemMessageUtils.buildAdminInviteRechargeAgentMessage( data, (ct, content) { _showMsgItemMenu(ct, content); }, (String id) { rejectOpt(id, type); }, (String id) { acceptOpt(id, type); }, context, ); } else if (_shouldRenderCpInviteCardForData(data)) { final cpInvite = _cpInviteFromMessageData(data); return _buildCpBuildMessage(data, (ct, content) { _showMsgItemMenu(ct, content); }, cpInvite); } else if (type == SCSysytemMessageType.CP_LOVE_LETTER.name) { return SCSystemMessageUtils.buildCPLoveLetterMessage(data, ( ct, content, ) { _showMsgItemMenu(ct, content); }, context); } else if (type == SCSysytemMessageType.CP_DISMISS.name || type == SCSysytemMessageType.CP_BLESS.name) { return SCSystemMessageUtils.buildCPNoticeMessage(data, (ct, content) { _showMsgItemMenu(ct, content); }, context); } else if (type == SCSysytemMessageType.USER_COINS_RECEIVED.name) { return SCSystemMessageUtils.buildUserCoinsReceivedMessage(data, ( ct, content, ) { _showMsgItemMenu(ct, content); }, context); } else if (type == SCSysytemMessageType.COIN_SELLER_COINS_RECEIVED.name) { return SCSystemMessageUtils.buildUserCoinsReceivedMessage(data, ( ct, content, ) { _showMsgItemMenu(ct, content); }, context); } else if (_isRegisterRewardGrantedType(type)) { return SCSystemMessageUtils.buildRegisterRewardGrantedMessage(data, ( ct, content, ) { _showMsgItemMenu(ct, content); }, context); } else if (type == SCSysytemMessageType.USER_SPECIAL_AUDIT_RESULTS.name) { if (data["content"] != null) { return Builder( builder: (ct) { return GestureDetector( onLongPress: () { _showMsgItemMenu(ct, ""); }, onTap: () {}, child: Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow(blurRadius: 2.w, color: Colors.black26), ], borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(8.w), bottomLeft: Radius.circular(8.w), bottomRight: Radius.circular(8), ), ), padding: EdgeInsets.all(8.0.w), child: ExtendedText( data["content"], // specialTextSpanBuilder: MySpecialTextSpanBuilder(), style: TextStyle(fontSize: sp(14), color: Colors.black54), ), ), ); }, ); } } else if (type == SCSysytemMessageType.USER_FOLLOW.name) { String expand = data["expand"] ?? ""; if (data["content"] != null) { var bean = SCSystemInvitMessageRes.fromJson( jsonDecode(data["content"]), ); return Builder( builder: (ct) { return GestureDetector( onLongPress: () { _showMsgItemMenu(ct, ""); }, child: Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow(blurRadius: 2.w, color: Colors.black26), ], borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(8.w), bottomLeft: Radius.circular(8.w), bottomRight: Radius.circular(8), ), ), padding: EdgeInsets.all(8.0.w), child: Column( children: [ Row( children: [ head(url: bean.userAvatar ?? "", width: 65.w), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ netImage( url: Provider.of( context, listen: false, ) .findCountryByName( bean.countryName ?? "", ) ?.nationalFlag ?? "", borderRadius: BorderRadius.all( Radius.circular(3.w), ), width: 19.w, height: 14.w, ), SizedBox(width: 3.w), Container( constraints: BoxConstraints( maxWidth: 160.w, ), child: text( bean.userNickname ?? "", fontWeight: FontWeight.bold, textColor: Colors.black, fontSize: 16.sp, ), ), ], ), GestureDetector( child: Container( padding: EdgeInsets.symmetric(vertical: 8.w), decoration: BoxDecoration( image: DecorationImage( image: AssetImage( SCGlobalConfig.businessLogicStrategy .getIdBackgroundImage( bean.account != bean.actualAccount, ), ), fit: BoxFit.fitWidth, ), ), child: Row( textDirection: TextDirection.ltr, children: [ SizedBox(width: 38.w), text( bean.actualAccount ?? "", fontSize: 12.sp, textColor: Colors.white, fontWeight: FontWeight.bold, ), SizedBox(width: 5.w), Image.asset( SCGlobalConfig.businessLogicStrategy .getCopyIdIcon(), width: 12.w, height: 12.w, ), SizedBox(width: 8.w), ], ), ), onTap: () { Clipboard.setData( ClipboardData( text: bean.actualAccount ?? "", ), ); SCTts.show( SCAppLocalizations.of( context, )!.copiedToClipboard, ); }, ), ], ), ], ), SizedBox(height: 3.w), GestureDetector( child: Container( alignment: AlignmentDirectional.center, height: 25.w, width: 65.w, decoration: BoxDecoration( color: Color(0xffB464FF), borderRadius: BorderRadius.all( Radius.circular(15.0), ), ), child: text( SCAppLocalizations.of(context)!.follow, fontSize: 14.sp, fontWeight: FontWeight.w600, textColor: Colors.white, ), ), onTap: () { SCAccountRepository().followNew(expand).then(( result, ) { SCTts.show( SCAppLocalizations.of(context)!.followSucc, ); }); }, ), Divider( color: Colors.black12, thickness: 0.5, height: 28.w, ), ExtendedText( SCAppLocalizations.of(context)!.followedYou, // specialTextSpanBuilder: MySpecialTextSpanBuilder(), style: TextStyle( fontSize: sp(14), color: Colors.black54, ), ), SizedBox(height: 5.w), ], ), ), ); }, ); } } else if (type == SCSysytemMessageType.VIOLSCION_WARNING.name) { if (data["content"] != null) { return Builder( builder: (ct) { return GestureDetector( onLongPress: () { _showMsgItemMenu(ct, ""); }, onTap: () {}, child: Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow(blurRadius: 2.w, color: Colors.black26), ], borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(8.w), bottomLeft: Radius.circular(8.w), bottomRight: Radius.circular(8), ), ), padding: EdgeInsets.all(8.0.w), child: ExtendedText( data["content"], // specialTextSpanBuilder: MySpecialTextSpanBuilder(), style: TextStyle(fontSize: sp(14), color: Colors.black54), ), ), ); }, ); } } else if (type == SCSysytemMessageType.VIOLSCION_ADJUST.name) { if (data["content"] != null) { return Builder( builder: (ct) { return GestureDetector( onLongPress: () { _showMsgItemMenu(ct, ""); }, onTap: () {}, child: Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow(blurRadius: 2.w, color: Colors.black26), ], borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(8.w), bottomLeft: Radius.circular(8.w), bottomRight: Radius.circular(8), ), ), padding: EdgeInsets.all(8.0.w), child: ExtendedText( data["content"], // specialTextSpanBuilder: MySpecialTextSpanBuilder(), style: TextStyle(fontSize: sp(14), color: Colors.black54), ), ), ); }, ); } } if (content.isEmpty) { if (customElem.extension == 'sendGift') { content = SCAppLocalizations.of(context)!.gift2; } else { content = SCAppLocalizations.of(context)!.receivedAMessage; } } } return Builder( builder: (ct) { return GestureDetector( onLongPress: () { _showMsgItemMenu(ct, ""); }, child: Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [BoxShadow(blurRadius: 2.w, color: Colors.black26)], borderRadius: BorderRadius.only( topLeft: Radius.circular(0), topRight: Radius.circular(8.w), bottomLeft: Radius.circular(8.w), bottomRight: Radius.circular(8), ), ), padding: EdgeInsets.all(8.0.w), child: ExtendedText( content, // specialTextSpanBuilder: MySpecialTextSpanBuilder(), style: TextStyle(fontSize: sp(14), color: Colors.black54), ), ), ); }, ); } bool _isRegisterRewardGrantedType(String type) { return type.trim().toUpperCase().replaceAll(' ', '_') == SCSysytemMessageType.REGISTER_REWARD_GRANTED.name; } void _showMsgItemMenu(BuildContext ct, String content) { SmartDialog.showAttach( tag: "showMsgItemMenu", targetContext: ct, alignment: Alignment.topCenter, maskColor: Colors.transparent, animationType: SmartAnimationType.fade, scalePointBuilder: (selfSize) => Offset(selfSize.width, 10), builder: (_) { return Container( margin: EdgeInsets.only(bottom: 10.w), decoration: BoxDecoration( color: Color(0xffdcdddf), borderRadius: BorderRadius.circular(10), ), padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), child: Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { SmartDialog.dismiss(tag: "showMsgItemMenu"); _delete(); }, child: Container( margin: EdgeInsets.symmetric(horizontal: 8.w), child: Column( children: [ Image.asset( SCGlobalConfig.businessLogicStrategy .getSCMessageChatPageMessageMenuDeleteIcon(), width: 20.w, ), SizedBox(height: 3.w), text( SCAppLocalizations.of(context)!.delete, fontSize: 12.sp, textColor: Color(0xff777777), ), ], ), ), ), ], ), ); }, ); } ///删除消息 void _delete() { SmartDialog.show( tag: "showDeleteDialog", alignment: Alignment.bottomCenter, debounce: true, animationType: SmartAnimationType.fade, builder: (_) { return SafeArea( child: Container( height: 168.w, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(8.0), topRight: Radius.circular(8.0), ), ), child: Column( children: [ SizedBox(height: 10.w), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ text( SCAppLocalizations.of(context)!.tips, fontSize: 16.sp, textColor: Colors.black, fontWeight: FontWeight.bold, ), ], ), SizedBox(height: 5.w), Divider(height: height(1), color: SocialChatTheme.dividerColor), GestureDetector( child: Container( padding: EdgeInsets.symmetric(vertical: 10.w), color: Colors.transparent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ text( SCAppLocalizations.of(context)!.deleteFromMyDevice, fontSize: 14.sp, textColor: Colors.black, ), ], ), ), onTap: () async { V2TimCallback deleteMessageFromLocalStorage = await TencentImSDKPlugin.v2TIMManager .getMessageManager() .deleteMessageFromLocalStorage( msgID: message.msgID ?? "", ); if (deleteMessageFromLocalStorage.code == 0) { //删除成功 int index = 0; for (var value in currentConversationMessageList) { if (value.msgID == message.msgID) { break; } index = index + 1; } currentConversationMessageList.removeAt(index); updateCall(); SmartDialog.dismiss(tag: "showDeleteDialog"); } }, ), Divider(height: height(1), color: SocialChatTheme.dividerColor), GestureDetector( onTap: () async { V2TimCallback deleteMessageFromLocalStorage = await TencentImSDKPlugin.v2TIMManager .getMessageManager() .deleteMessages(msgIDs: [message.msgID ?? ""]); if (deleteMessageFromLocalStorage.code == 0) { //删除成功 int index = 0; for (var value in currentConversationMessageList) { if (value.msgID == message.msgID) { break; } index = index + 1; } currentConversationMessageList.removeAt(index); updateCall(); SmartDialog.dismiss(tag: "showDeleteDialog"); } }, child: Container( padding: EdgeInsets.symmetric(vertical: 10.w), color: Colors.transparent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ text( SCAppLocalizations.of(context)!.deleteOnAllDevices, fontSize: 14.sp, textColor: Colors.black, ), ], ), ), ), Divider(height: height(1), color: SocialChatTheme.dividerColor), GestureDetector( onTap: () async { SmartDialog.dismiss(tag: "showDeleteDialog"); }, child: Container( padding: EdgeInsets.symmetric(vertical: 10.w), color: Colors.transparent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ text( SCAppLocalizations.of(context)!.cancel, fontSize: 14.sp, textColor: Colors.black, ), ], ), ), ), ], ), ), ); }, ); } SCSystemInvitMessageRes? _cpInviteFromMessageData(dynamic data) { try { return SCSystemInvitMessageRes.fromMessageData(data); } catch (_) {} return null; } Future _acceptCpInviteDirect( String id, SCSystemInvitMessageRes? cpInvite, Map? cpData, ) async { if (!await _shouldProcessCpInvite(id, cpInvite, cpData)) { return; } if (!context.mounted) { return; } final profileManager = Provider.of( context, listen: false, ); final success = await profileManager.cpRlationshipProcessApply( context, id, true, ); if (success && context.mounted) { SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); unawaited(profileManager.refreshLoadedCpProfiles()); } } Future _rejectCpInviteDirect( String id, SCSystemInvitMessageRes? cpInvite, Map? cpData, ) async { if (!await _shouldProcessCpInvite(id, cpInvite, cpData)) { return; } if (!context.mounted) { return; } final profileManager = Provider.of( context, listen: false, ); final success = await profileManager.cpRlationshipProcessApply( context, id, false, ); if (success && context.mounted) { SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); unawaited(profileManager.refreshLoadedCpProfiles()); } } Future _shouldProcessCpInvite( String applyId, SCSystemInvitMessageRes? cpInvite, Map? data, ) async { final latestState = data == null ? await _latestCpInviteStateForApplyId(applyId) : await _latestCpInviteStateForMessage(data); if (latestState == _CpInviteMessageState.pending) { return true; } if (!context.mounted) { return false; } if (data != null) { _showCpInviteDialogFromMessage( data, cpInvite, _cpInviteActionState(latestState), ); } else { SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag); if (latestState == _CpInviteMessageState.expired) { SCTts.show(SCAppLocalizations.of(context)!.closeFriendInviteTimedOut); } } updateCall(); return false; } Future<_CpInviteMessageState> _latestCpInviteStateForApplyId( String applyId, ) async { if (applyId.trim().isEmpty) { return _CpInviteMessageState.pending; } try { final status = await SCAccountRepository().cpRelationshipApplyStatus( applyId, ); return _cpInviteStateFromBackendStatus(status) ?? _CpInviteMessageState.pending; } catch (_) { return _CpInviteMessageState.pending; } } void acceptOpt( String id, String type, { SCSystemInvitMessageRes? cpInvite, Map? cpData, }) { if (type == SCSysytemMessageType.CP_BUILD.name) { unawaited(_acceptCpInviteDirect(id, cpInvite, cpData)); return; } SmartDialog.show( tag: "showConfirmDialog", alignment: Alignment.center, debounce: true, animationType: SmartAnimationType.fade, builder: (_) { return MsgDialog( title: SCAppLocalizations.of(context)!.tips, msg: SCAppLocalizations.of(context)!.confirmAcceptTheInvitation, btnText: SCAppLocalizations.of(context)!.confirm, onEnsure: () async { if (type == SCSysytemMessageType.AGENT_SEND_INVITE_HOST.name) { Provider.of( context, listen: false, ).inviteHostOpt(context, id, "1"); } else if (type == SCSysytemMessageType.BD_SEND_INVITE_AGENT.name) { Provider.of( context, listen: false, ).inviteAgentOpt(context, id, "1"); } else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD.name) { Provider.of( context, listen: false, ).inviteBDOpt(context, id, "1"); } else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) { Provider.of( context, listen: false, ).inviteBDLeader(context, id, "1"); } else if (type == SCSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) { Provider.of( context, listen: false, ).inviteRechargeAgent(context, id, "1"); } }, ); }, ); } void rejectOpt( String id, String type, { SCSystemInvitMessageRes? cpInvite, Map? cpData, }) { if (type == SCSysytemMessageType.CP_BUILD.name) { unawaited(_rejectCpInviteDirect(id, cpInvite, cpData)); return; } SmartDialog.show( tag: "showConfirmDialog", alignment: Alignment.center, debounce: true, animationType: SmartAnimationType.fade, builder: (_) { return MsgDialog( title: SCAppLocalizations.of(context)!.tips, msg: SCAppLocalizations.of(context)!.confirmDeclineTheInvitation, btnText: SCAppLocalizations.of(context)!.confirm, onEnsure: () async { if (type == SCSysytemMessageType.AGENT_SEND_INVITE_HOST.name) { Provider.of( context, listen: false, ).inviteHostOpt(context, id, "2"); } else if (type == SCSysytemMessageType.BD_SEND_INVITE_AGENT.name) { Provider.of( context, listen: false, ).inviteAgentOpt(context, id, "2"); } else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD.name) { Provider.of( context, listen: false, ).inviteBDOpt(context, id, "2"); } else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) { Provider.of( context, listen: false, ).inviteBDLeader(context, id, "2"); } else if (type == SCSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) { Provider.of( context, listen: false, ).inviteRechargeAgent(context, id, "2"); } }, ); }, ); } }