yumi-flutter/lib/modules/chat/system/message_system_page.dart
2026-05-25 17:05:30 +08:00

2281 lines
81 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<MessageSystemPage> {
final ScrollController _scrollController = ScrollController();
final RefreshController _refreshController = RefreshController();
RtmProvider? rtmProvider;
V2TimConversation? currentConversation;
List<V2TimMessage> currentConversationMessageList = [];
final Map<String, _CpInviteMessageState> _localCpInviteStates = {};
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
@override
void initState() {
super.initState();
rtmProvider = Provider.of<RtmProvider>(context, listen: false);
currentConversation = widget.conversation;
rtmProvider?.onNewMessageCurrentConversationListener = _onNewMessage;
loadMsg();
}
@override
void dispose() {
rtmProvider?.onNewMessageCurrentConversationListener = null;
super.dispose();
}
Future<void> loadMsg() async {
V2TimValueCallback<List<V2TimMessage>> v2timValueCallback =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.getC2CHistoryMessageList(
userID: currentConversation!.userID!,
count: 100,
lastMsgID: null,
);
List<V2TimMessage> messages = v2timValueCallback.data ?? [];
// List<V2TimMessage> 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<SocialChatUserProfileManager>(
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<List<V2TimMessage>> v2timValueCallback =
await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.getC2CHistoryMessageList(
userID: currentConversation!.userID ?? "",
count: 30,
lastMsgID: currentConversationMessageList.last.msgID,
);
List<V2TimMessage> messages =
v2timValueCallback.data as List<V2TimMessage>;
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<V2TimMessage> currentConversationMessageList = [];
final Map<String, _CpInviteMessageState> 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: <Widget>[
Visibility(
visible: showTime,
child: Container(
margin: EdgeInsets.only(bottom: 12.w),
child: Row(
children: <Widget>[
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: <Widget>[
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: <Widget>[
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<String, dynamic> 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<String, dynamic> data,
SCSystemInvitMessageRes? cpInvite,
RoomCpInviteActionState actionState,
) {
unawaited(_openCpInviteDialogFromMessageAsync(data, cpInvite, actionState));
}
Future<void> _openCpInviteDialogFromMessageAsync(
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic>? _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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> data) {
return _localCpInviteStateForApplyId(_cpApplyIdFromData(data));
}
_CpInviteMessageState? _localCpInviteStateForApplyId(String applyId) {
final normalizedApplyId = applyId.trim();
if (normalizedApplyId.isEmpty) {
return null;
}
return localCpInviteStates[normalizedApplyId];
}
void _rememberCpInviteState(
Map<String, dynamic> data,
_CpInviteMessageState state,
) {
if (state == _CpInviteMessageState.pending) {
return;
}
final applyId = _cpApplyIdFromData(data);
if (applyId.isEmpty) {
return;
}
onCpInviteStateChanged?.call(applyId, state);
}
_CpInviteMessageState _cpInviteStateFromData(Map<String, dynamic> data) {
final content = _cpContentMap(data);
final values = <String>[
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<dynamic> 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<String, dynamic> data) {
final content = _cpContentMap(data);
final text =
<String>[
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<String, dynamic> data) {
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
if (currentProfile == null) {
return false;
}
final content = _cpContentMap(data);
final inviterIds =
<String>[
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<String, dynamic> 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<dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> _cpContentMap(Map<String, dynamic> 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 <String, dynamic>{};
}
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<String, dynamic> 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<String?> 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<SCAppGeneralManager>(
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<void> _acceptCpInviteDirect(
String id,
SCSystemInvitMessageRes? cpInvite,
Map<String, dynamic>? cpData,
) async {
if (!await _shouldProcessCpInvite(id, cpInvite, cpData)) {
return;
}
if (!context.mounted) {
return;
}
final profileManager = Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
);
final success = await profileManager.cpRlationshipProcessApply(
context,
id,
true,
);
if (success && context.mounted) {
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
unawaited(profileManager.refreshLoadedCpProfiles());
}
}
Future<void> _rejectCpInviteDirect(
String id,
SCSystemInvitMessageRes? cpInvite,
Map<String, dynamic>? cpData,
) async {
if (!await _shouldProcessCpInvite(id, cpInvite, cpData)) {
return;
}
if (!context.mounted) {
return;
}
final profileManager = Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
);
final success = await profileManager.cpRlationshipProcessApply(
context,
id,
false,
);
if (success && context.mounted) {
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
unawaited(profileManager.refreshLoadedCpProfiles());
}
}
Future<bool> _shouldProcessCpInvite(
String applyId,
SCSystemInvitMessageRes? cpInvite,
Map<String, dynamic>? 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<String, dynamic>? 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<SocialChatUserProfileManager>(
context,
listen: false,
).inviteHostOpt(context, id, "1");
} else if (type == SCSysytemMessageType.BD_SEND_INVITE_AGENT.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteAgentOpt(context, id, "1");
} else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteBDOpt(context, id, "1");
} else if (type ==
SCSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteBDLeader(context, id, "1");
} else if (type ==
SCSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteRechargeAgent(context, id, "1");
}
},
);
},
);
}
void rejectOpt(
String id,
String type, {
SCSystemInvitMessageRes? cpInvite,
Map<String, dynamic>? 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<SocialChatUserProfileManager>(
context,
listen: false,
).inviteHostOpt(context, id, "2");
} else if (type == SCSysytemMessageType.BD_SEND_INVITE_AGENT.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteAgentOpt(context, id, "2");
} else if (type == SCSysytemMessageType.BD_LEADER_INVITE_BD.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteBDOpt(context, id, "2");
} else if (type ==
SCSysytemMessageType.BD_LEADER_INVITE_BD_LEADER.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteBDLeader(context, id, "2");
} else if (type ==
SCSysytemMessageType.ADMIN_INVITE_RECHARGE_AGENT.name) {
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).inviteRechargeAgent(context, id, "2");
}
},
);
},
);
}
}