飘屏动画
This commit is contained in:
parent
cbb811a18a
commit
71993f438b
@ -368,7 +368,7 @@ class _SCMessageChatPageState extends State<SCMessageChatPage> {
|
||||
}
|
||||
SCLoadingManager.show();
|
||||
Future.wait([
|
||||
SCAccountRepository().loadUserInfo(userId),
|
||||
SCAccountRepository().loadUserInfo(userId, silentErrorToast: true),
|
||||
SCAccountRepository().friendRelationCheck(userId),
|
||||
])
|
||||
.then((result) {
|
||||
@ -593,6 +593,7 @@ class _SCMessageChatPageState extends State<SCMessageChatPage> {
|
||||
(c, i) => _MessageItem(
|
||||
message: currentConversationMessageList[i],
|
||||
selfVipChatBubble: _selfVipChatBubble,
|
||||
currentConversation: currentConversation,
|
||||
preMessage:
|
||||
i > 0 ? currentConversationMessageList[i - 1] : null,
|
||||
isSystem:
|
||||
@ -1164,6 +1165,7 @@ class _MessageItem extends StatelessWidget {
|
||||
final V2TimMessage message;
|
||||
final V2TimMessage? preMessage;
|
||||
final SCVipResourceRes? selfVipChatBubble;
|
||||
final V2TimConversation? currentConversation;
|
||||
|
||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||
|
||||
@ -1179,6 +1181,7 @@ class _MessageItem extends StatelessWidget {
|
||||
_MessageItem({
|
||||
required this.message,
|
||||
this.selfVipChatBubble,
|
||||
this.currentConversation,
|
||||
this.preMessage,
|
||||
// this.userProfile,
|
||||
this.isSystem = false,
|
||||
@ -1529,12 +1532,15 @@ class _MessageItem extends StatelessWidget {
|
||||
content["acceptUserNickname"]?.toString(),
|
||||
content["receiverNickname"]?.toString(),
|
||||
content["receiverUserNickname"]?.toString(),
|
||||
friend?.userNickname,
|
||||
currentConversation?.showName,
|
||||
], fallback: ""),
|
||||
account: _firstNonBlankString([
|
||||
data["acceptAccount"]?.toString(),
|
||||
data["receiverAccount"]?.toString(),
|
||||
content["acceptAccount"]?.toString(),
|
||||
content["receiverAccount"]?.toString(),
|
||||
friend?.account,
|
||||
], fallback: ""),
|
||||
actualAccount: _firstNonBlankString([
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
@ -1545,8 +1551,10 @@ class _MessageItem extends StatelessWidget {
|
||||
content["acceptUserId"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
friend?.id,
|
||||
currentConversation?.userID,
|
||||
], fallback: ""),
|
||||
fallback: currentName,
|
||||
fallback: currentConversation?.userID ?? "User",
|
||||
);
|
||||
final receiverAvatar = _firstNonBlankString([
|
||||
data["acceptUserAvatar"]?.toString(),
|
||||
@ -1555,7 +1563,9 @@ class _MessageItem extends StatelessWidget {
|
||||
content["acceptUserAvatar"]?.toString(),
|
||||
content["receiverAvatar"]?.toString(),
|
||||
content["receiverUserAvatar"]?.toString(),
|
||||
], fallback: currentProfile?.userAvatar ?? "");
|
||||
friend?.userAvatar,
|
||||
currentConversation?.faceUrl,
|
||||
], fallback: "");
|
||||
final isInviter = _isCurrentUserCpInviter(data);
|
||||
final relationType = scNormalizeCpRelationType(
|
||||
_firstNonBlankString([
|
||||
@ -1579,12 +1589,18 @@ class _MessageItem extends StatelessWidget {
|
||||
rightName: isInviter ? receiverName : currentName,
|
||||
leftAvatar:
|
||||
isInviter
|
||||
? currentProfile?.userAvatar ?? ""
|
||||
? _firstNonBlankString([
|
||||
currentProfile?.userAvatar,
|
||||
inviterAvatar,
|
||||
])
|
||||
: inviterAvatar,
|
||||
rightAvatar:
|
||||
isInviter
|
||||
? receiverAvatar
|
||||
: currentProfile?.userAvatar ?? "",
|
||||
: _firstNonBlankString([
|
||||
currentProfile?.userAvatar,
|
||||
receiverAvatar,
|
||||
]),
|
||||
relationType: relationType,
|
||||
),
|
||||
),
|
||||
@ -1631,26 +1647,73 @@ class _MessageItem extends StatelessWidget {
|
||||
);
|
||||
final isInviter = _isCurrentUserCpInviter(data);
|
||||
final content = _cpContentMap(data);
|
||||
final receiverAvatar = _firstNonBlankString([
|
||||
data["acceptUserAvatar"]?.toString(),
|
||||
data["receiverAvatar"]?.toString(),
|
||||
data["receiverUserAvatar"]?.toString(),
|
||||
content["acceptUserAvatar"]?.toString(),
|
||||
content["receiverAvatar"]?.toString(),
|
||||
content["receiverUserAvatar"]?.toString(),
|
||||
friend?.userAvatar,
|
||||
currentConversation?.faceUrl,
|
||||
]);
|
||||
final receiverHeaddress = _firstNonBlankString([
|
||||
data["acceptHeaddress"]?.toString(),
|
||||
data["acceptAvatarFrame"]?.toString(),
|
||||
data["receiverHeaddress"]?.toString(),
|
||||
data["receiverAvatarFrame"]?.toString(),
|
||||
content["acceptHeaddress"]?.toString(),
|
||||
content["acceptAvatarFrame"]?.toString(),
|
||||
content["receiverHeaddress"]?.toString(),
|
||||
content["receiverAvatarFrame"]?.toString(),
|
||||
friend?.getHeaddress()?.sourceUrl,
|
||||
]);
|
||||
final receiverHeaddressCover = _firstNonBlankString([
|
||||
data["acceptHeaddressCover"]?.toString(),
|
||||
data["acceptAvatarFrameCover"]?.toString(),
|
||||
data["receiverHeaddressCover"]?.toString(),
|
||||
data["receiverAvatarFrameCover"]?.toString(),
|
||||
content["acceptHeaddressCover"]?.toString(),
|
||||
content["acceptAvatarFrameCover"]?.toString(),
|
||||
content["receiverHeaddressCover"]?.toString(),
|
||||
content["receiverAvatarFrameCover"]?.toString(),
|
||||
friend?.getHeaddress()?.cover,
|
||||
]);
|
||||
final receiverName =
|
||||
isInviter
|
||||
? _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(),
|
||||
friend?.userNickname,
|
||||
currentConversation?.showName,
|
||||
], fallback: ""),
|
||||
account: _firstNonBlankString([
|
||||
data["acceptAccount"]?.toString(),
|
||||
data["receiverAccount"]?.toString(),
|
||||
content["acceptAccount"]?.toString(),
|
||||
content["receiverAccount"]?.toString(),
|
||||
friend?.account,
|
||||
], fallback: ""),
|
||||
actualAccount: _firstNonBlankString([
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
content["acceptActualAccount"]?.toString(),
|
||||
data["acceptUserId"]?.toString(),
|
||||
content["acceptUserId"]?.toString(),
|
||||
data["receiverActualAccount"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
friend?.id,
|
||||
currentConversation?.userID,
|
||||
], fallback: ""),
|
||||
fallback: "User",
|
||||
fallback: currentConversation?.userID ?? "User",
|
||||
)
|
||||
: currentName;
|
||||
final relationType = scNormalizeCpRelationType(cpInvite?.relationType);
|
||||
@ -1685,29 +1748,25 @@ class _MessageItem extends StatelessWidget {
|
||||
name: receiverName,
|
||||
avatarUrl:
|
||||
isInviter
|
||||
? _firstNonBlankString([
|
||||
data["acceptUserAvatar"]?.toString(),
|
||||
content["acceptUserAvatar"]?.toString(),
|
||||
], fallback: "")
|
||||
: currentProfile?.userAvatar ?? "",
|
||||
? receiverAvatar
|
||||
: _firstNonBlankString([
|
||||
currentProfile?.userAvatar,
|
||||
receiverAvatar,
|
||||
]),
|
||||
headdress:
|
||||
isInviter
|
||||
? _firstNonBlankString([
|
||||
data["acceptHeaddress"]?.toString(),
|
||||
data["acceptAvatarFrame"]?.toString(),
|
||||
content["acceptHeaddress"]?.toString(),
|
||||
content["acceptAvatarFrame"]?.toString(),
|
||||
], fallback: "")
|
||||
: currentProfile?.getHeaddress()?.sourceUrl ?? "",
|
||||
? receiverHeaddress
|
||||
: _firstNonBlankString([
|
||||
currentProfile?.getHeaddress()?.sourceUrl,
|
||||
receiverHeaddress,
|
||||
]),
|
||||
headdressCover:
|
||||
isInviter
|
||||
? _firstNonBlankString([
|
||||
data["acceptHeaddressCover"]?.toString(),
|
||||
data["acceptAvatarFrameCover"]?.toString(),
|
||||
content["acceptHeaddressCover"]?.toString(),
|
||||
content["acceptAvatarFrameCover"]?.toString(),
|
||||
], fallback: "")
|
||||
: currentProfile?.getHeaddress()?.cover ?? "",
|
||||
? receiverHeaddressCover
|
||||
: _firstNonBlankString([
|
||||
currentProfile?.getHeaddress()?.cover,
|
||||
receiverHeaddressCover,
|
||||
]),
|
||||
),
|
||||
style:
|
||||
isInviter
|
||||
@ -1934,23 +1993,91 @@ class _MessageItem extends StatelessWidget {
|
||||
if (currentProfile == null) {
|
||||
return false;
|
||||
}
|
||||
final currentIds = _cpCurrentProfileIds(currentProfile);
|
||||
if (currentIds.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final receiverIds = _cpInviteReceiverIdentityValues(data);
|
||||
if (currentIds.any(receiverIds.contains)) {
|
||||
return false;
|
||||
}
|
||||
final inviterIds = _cpInviteInviterIdentityValues(data);
|
||||
return currentIds.any(inviterIds.contains);
|
||||
}
|
||||
|
||||
Set<String> _cpCurrentProfileIds(SocialChatUserProfile currentProfile) {
|
||||
return [currentProfile.id, currentProfile.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
}
|
||||
|
||||
Set<String> _cpInviteInviterIdentityValues(Map<String, dynamic> data) {
|
||||
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() ?? "");
|
||||
final values = <String?>[
|
||||
data["account"]?.toString(),
|
||||
data["actualAccount"]?.toString(),
|
||||
data["applyUserId"]?.toString(),
|
||||
data["applyUserAccount"]?.toString(),
|
||||
data["applyActualAccount"]?.toString(),
|
||||
data["applyUserActualAccount"]?.toString(),
|
||||
data["inviterActualAccount"]?.toString(),
|
||||
data["inviterUserId"]?.toString(),
|
||||
data["inviterId"]?.toString(),
|
||||
data["senderUserId"]?.toString(),
|
||||
data["senderId"]?.toString(),
|
||||
data["fromUserId"]?.toString(),
|
||||
content["applyUserId"]?.toString(),
|
||||
content["applyUserAccount"]?.toString(),
|
||||
content["applyActualAccount"]?.toString(),
|
||||
content["applyUserActualAccount"]?.toString(),
|
||||
content["inviterActualAccount"]?.toString(),
|
||||
content["inviterUserId"]?.toString(),
|
||||
content["inviterId"]?.toString(),
|
||||
content["senderUserId"]?.toString(),
|
||||
content["senderId"]?.toString(),
|
||||
content["fromUserId"]?.toString(),
|
||||
];
|
||||
final explicitValues =
|
||||
values.map((value) => value?.trim() ?? "").where((value) {
|
||||
return value.isNotEmpty;
|
||||
}).toSet();
|
||||
if (explicitValues.isNotEmpty) {
|
||||
return explicitValues;
|
||||
}
|
||||
return [
|
||||
content["account"]?.toString(),
|
||||
content["actualAccount"]?.toString(),
|
||||
content["userId"]?.toString(),
|
||||
].map((value) => value?.trim() ?? "").where((value) {
|
||||
return value.isNotEmpty;
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
Set<String> _cpInviteReceiverIdentityValues(Map<String, dynamic> data) {
|
||||
final content = _cpContentMap(data);
|
||||
return [
|
||||
data["acceptUserId"]?.toString(),
|
||||
data["acceptAccount"]?.toString(),
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
data["receiverId"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
data["receiverAccount"]?.toString(),
|
||||
data["receiverActualAccount"]?.toString(),
|
||||
data["targetUserId"]?.toString(),
|
||||
data["toUserId"]?.toString(),
|
||||
content["acceptUserId"]?.toString(),
|
||||
content["acceptAccount"]?.toString(),
|
||||
content["acceptActualAccount"]?.toString(),
|
||||
content["receiverId"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
content["receiverAccount"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["targetUserId"]?.toString(),
|
||||
content["toUserId"]?.toString(),
|
||||
].map((value) => value?.trim() ?? "").where((value) {
|
||||
return value.isNotEmpty;
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
int _cpInviteSecondsLeft(Map<String, dynamic> data) {
|
||||
|
||||
@ -10,7 +10,6 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository
|
||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
|
||||
enum _CpGuideTab { note, rights }
|
||||
|
||||
@ -593,7 +592,6 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
? "Lv.$levelNumber"
|
||||
: "Lv.${rows.length + 1}"),
|
||||
intimacy: _formatRightsIntimacyValue(requiredValue),
|
||||
rights: level.rights.map(_CpRightItem.fromRes).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -630,23 +628,16 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
}
|
||||
|
||||
Widget _buildRightsTable(List<_CpRightsLevel> rows) {
|
||||
const tableWidth = 351.0;
|
||||
const columnWidth = tableWidth / 2;
|
||||
const headerHeight = 39.0;
|
||||
const rightCellHeight = 77.0;
|
||||
const dividerHeight = 1.0;
|
||||
final tableHeight =
|
||||
headerHeight +
|
||||
rows.fold<double>(
|
||||
0,
|
||||
(height, row) =>
|
||||
height +
|
||||
row.rightSlots * rightCellHeight +
|
||||
(row.rightSlots - 1) * dividerHeight,
|
||||
);
|
||||
const rowHeight = 77.0;
|
||||
final tableHeight = headerHeight + rows.length * rowHeight;
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
child: Container(
|
||||
width: 351.w,
|
||||
width: tableWidth.w,
|
||||
height: tableHeight.w,
|
||||
color: Colors.white.withValues(alpha: 0.3),
|
||||
child: Stack(
|
||||
@ -654,15 +645,15 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 117.w,
|
||||
width: 117.w,
|
||||
left: columnWidth.w,
|
||||
width: columnWidth.w,
|
||||
child: ColoredBox(color: Colors.white.withValues(alpha: 0.24)),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: headerHeight.w,
|
||||
child: _tableLine(width: 351),
|
||||
child: _tableLine(width: tableWidth),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Column(
|
||||
@ -671,9 +662,8 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
height: headerHeight.w,
|
||||
child: Row(
|
||||
children: [
|
||||
_tableHeader("Level"),
|
||||
_tableHeader("Intimacy Level"),
|
||||
_tableHeader("Rights"),
|
||||
_tableHeader("Level", columnWidth),
|
||||
_tableHeader("Intimacy Level", columnWidth),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -687,9 +677,9 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tableHeader(String value) {
|
||||
Widget _tableHeader(String value, double width) {
|
||||
return SizedBox(
|
||||
width: 117.w,
|
||||
width: width.w,
|
||||
child: Center(
|
||||
child: Text(
|
||||
value,
|
||||
@ -701,41 +691,18 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
}
|
||||
|
||||
Widget _buildRightsLevelRow(_CpRightsLevel row) {
|
||||
const rightCellHeight = 77.0;
|
||||
const dividerHeight = 1.0;
|
||||
final groupHeight =
|
||||
row.rightSlots * rightCellHeight + (row.rightSlots - 1) * dividerHeight;
|
||||
const tableWidth = 351.0;
|
||||
const columnWidth = tableWidth / 2;
|
||||
const rowHeight = 77.0;
|
||||
return SizedBox(
|
||||
height: groupHeight.w,
|
||||
height: rowHeight.w,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
children: [
|
||||
_spanCell(row.level, groupHeight),
|
||||
_spanCell(row.intimacy, groupHeight),
|
||||
SizedBox(width: 117.w, height: groupHeight.w),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: 117.w,
|
||||
height: groupHeight.w,
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < row.rightSlots; i++) ...[
|
||||
SizedBox(
|
||||
height: rightCellHeight.w,
|
||||
child: Center(
|
||||
child: _buildRightCell(
|
||||
i < row.rights.length ? row.rights[i] : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (i != row.rightSlots - 1) _tableLine(width: 117),
|
||||
],
|
||||
_spanCell(row.level, width: columnWidth, height: rowHeight),
|
||||
_spanCell(row.intimacy, width: columnWidth, height: rowHeight),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -743,16 +710,20 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: _tableLine(width: 351),
|
||||
child: _tableLine(width: tableWidth),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _spanCell(String value, double height) {
|
||||
Widget _spanCell(
|
||||
String value, {
|
||||
required double width,
|
||||
required double height,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: 117.w,
|
||||
width: width.w,
|
||||
height: height.w,
|
||||
child: Center(
|
||||
child: Text(
|
||||
@ -768,61 +739,6 @@ class _CpRightsGuidePageState extends State<CpRightsGuidePage> {
|
||||
return Container(width: width.w, height: 1.w, color: Colors.white);
|
||||
}
|
||||
|
||||
Widget _buildRightCell(_CpRightItem? right) {
|
||||
final opacity = (right?.unlocked ?? false) ? 1.0 : 0.55;
|
||||
final title = right?.title.trim() ?? "";
|
||||
return Opacity(
|
||||
opacity: opacity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildRightIcon(right?.icon ?? ""),
|
||||
if (title.isNotEmpty) ...[
|
||||
SizedBox(height: 4.w),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.w),
|
||||
child: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: _bodyStyle(fontSize: 10, lineHeight: 1.1),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRightIcon(String icon) {
|
||||
if (icon.trim().isNotEmpty) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(9.w),
|
||||
child: netImage(
|
||||
url: icon,
|
||||
width: 42.w,
|
||||
height: 42.w,
|
||||
fit: BoxFit.cover,
|
||||
noDefaultImg: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
width: 42.w,
|
||||
height: 42.w,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.18),
|
||||
borderRadius: BorderRadius.circular(9.w),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.45),
|
||||
width: 1.w,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _rightsIndicatorWidth(String label, TextStyle style) {
|
||||
final painter = TextPainter(
|
||||
text: TextSpan(text: label, style: style),
|
||||
@ -1005,35 +921,8 @@ class _FaqItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _CpRightsLevel {
|
||||
const _CpRightsLevel({
|
||||
required this.level,
|
||||
required this.intimacy,
|
||||
this.rights = const [],
|
||||
});
|
||||
const _CpRightsLevel({required this.level, required this.intimacy});
|
||||
|
||||
final String level;
|
||||
final String intimacy;
|
||||
final List<_CpRightItem> rights;
|
||||
|
||||
int get rightSlots => rights.isEmpty ? 1 : rights.length;
|
||||
}
|
||||
|
||||
class _CpRightItem {
|
||||
const _CpRightItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.unlocked,
|
||||
});
|
||||
|
||||
factory _CpRightItem.fromRes(SCCpRightRes right) {
|
||||
return _CpRightItem(
|
||||
icon: right.icon?.trim() ?? "",
|
||||
title: right.title?.trim() ?? "",
|
||||
unlocked: right.unlocked ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
final String icon;
|
||||
final String title;
|
||||
final bool unlocked;
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import 'package:yumi/shared/data_sources/sources/remote/net/api.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_gift_relation_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
||||
import 'package:yumi/main.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||
@ -34,6 +35,7 @@ import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_invite_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||
import 'package:yumi/modules/gift/gift_tab_page.dart';
|
||||
import 'package:yumi/modules/gift/cp_rights/cp_rights_guide_page.dart';
|
||||
@ -1562,6 +1564,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
}
|
||||
await _refreshGiftBackpack();
|
||||
_refreshCpStateAfterGift(request, profileManager);
|
||||
_showCpGiftWaitingDialog(request);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1626,6 +1629,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
}
|
||||
profileManager?.updateBalance(result);
|
||||
_refreshCpStateAfterGift(request, profileManager);
|
||||
_showCpGiftWaitingDialog(request);
|
||||
} catch (e) {
|
||||
final errorMessage =
|
||||
request.isBackpackGiftRequest && _isBackpackQuantityError(e)
|
||||
@ -1668,6 +1672,73 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
unawaited(profileManager?.refreshLoadedCpProfiles());
|
||||
}
|
||||
|
||||
void _showCpGiftWaitingDialog(_GiftSendRequest request) {
|
||||
if (!_isCpGift(request.gift)) {
|
||||
return;
|
||||
}
|
||||
final context = navigatorKey.currentState?.context;
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final receiverProfile =
|
||||
request.acceptUsers.isNotEmpty ? request.acceptUsers.first.user : null;
|
||||
if (context == null || currentProfile == null || receiverProfile == null) {
|
||||
_giftFxLog(
|
||||
'skip local CP waiting dialog reason=missing_profile '
|
||||
'giftId=${request.gift.id} '
|
||||
'acceptUserIds=${request.acceptUserIds.join(",")}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final relationType = scNormalizeCpRelationType(request.cpRelationType);
|
||||
_giftFxLog(
|
||||
'show local CP waiting dialog '
|
||||
'giftId=${request.gift.id} '
|
||||
'relationType=$relationType '
|
||||
'receiverId=${receiverProfile.id}',
|
||||
);
|
||||
unawaited(
|
||||
RoomCpInviteDialog.show(
|
||||
context,
|
||||
inviter: RoomCpInviteDialogUser(
|
||||
name: _cpInviteDisplayName(currentProfile),
|
||||
avatarUrl: currentProfile.userAvatar ?? "",
|
||||
headdress: currentProfile.getHeaddress()?.sourceUrl ?? "",
|
||||
headdressCover: currentProfile.getHeaddress()?.cover ?? "",
|
||||
),
|
||||
receiver: RoomCpInviteDialogUser(
|
||||
name: _cpInviteDisplayName(receiverProfile),
|
||||
avatarUrl: receiverProfile.userAvatar ?? "",
|
||||
headdress: receiverProfile.getHeaddress()?.sourceUrl ?? "",
|
||||
headdressCover: receiverProfile.getHeaddress()?.cover ?? "",
|
||||
),
|
||||
style: RoomCpInviteDialogStyle.waiting,
|
||||
countdownSeconds: 86399,
|
||||
descriptionText: _cpInviteWaitingDescriptionText(relationType),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _cpInviteDisplayName(SocialChatUserProfile profile) {
|
||||
for (final value in [profile.userNickname, profile.account, profile.id]) {
|
||||
final text = value?.trim() ?? "";
|
||||
if (text.isNotEmpty) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return "User";
|
||||
}
|
||||
|
||||
String _cpInviteWaitingDescriptionText(String relationType) {
|
||||
switch (scNormalizeCpRelationType(relationType)) {
|
||||
case "BROTHER":
|
||||
return "We invite you to become Brother";
|
||||
case "SISTERS":
|
||||
return "We invite you to become Sister";
|
||||
case "CP":
|
||||
default:
|
||||
return "We invite you to become Couple";
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<MicRes>> _giveBackpackGiftToRecipients(
|
||||
SCChatRoomRepository repository,
|
||||
_GiftSendRequest request,
|
||||
|
||||
@ -295,6 +295,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
Timer? _roomRocketRoomReadyRewardRetryTimer;
|
||||
final Set<String> _shownCpInviteMessageKeys = <String>{};
|
||||
final Set<String> _shownCpInviteResultMessageKeys = <String>{};
|
||||
final Set<String> _shownCpInviteRejectedDialogKeys = <String>{};
|
||||
final Map<String, int> _recentCpRelationNoticeTimes = <String, int>{};
|
||||
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
|
||||
String? roomRedPacketBroadcastGroupId;
|
||||
String? roomRedPacketBroadcastRegionCode;
|
||||
@ -685,7 +687,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}) async {
|
||||
final cpData = _decodeCpSystemMessageData(message);
|
||||
unawaited(_handleIncomingCpBuildMessage(message));
|
||||
final cpPeerUserId = _cpInviteConversationPeerIdFromData(cpData);
|
||||
final cpPeerUserId = _cpInviteConversationPeerIdFromData(
|
||||
cpData,
|
||||
message: message,
|
||||
);
|
||||
final didSyncConversation = _upsertC2CConversationFromMessage(
|
||||
message,
|
||||
fallbackUserId: fallbackUserId ?? cpPeerUserId,
|
||||
@ -816,7 +821,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return "";
|
||||
}
|
||||
|
||||
String? _cpInviteConversationPeerIdFromData(Map<String, dynamic>? data) {
|
||||
String? _cpInviteConversationPeerIdFromData(
|
||||
Map<String, dynamic>? data, {
|
||||
V2TimMessage? message,
|
||||
}) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
@ -825,9 +833,27 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
if (invite == null || currentProfile == null) {
|
||||
return null;
|
||||
}
|
||||
if (_cpInviteSenderMatchesCurrent(invite, currentProfile)) {
|
||||
final currentIds =
|
||||
[currentProfile.id, currentProfile.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
final sender = message?.sender?.trim() ?? "";
|
||||
final userId = message?.userID?.trim() ?? "";
|
||||
final isSender = _cpInviteSenderMatchesCurrent(
|
||||
invite,
|
||||
currentProfile,
|
||||
data: data,
|
||||
);
|
||||
if (isSender) {
|
||||
if (userId.isNotEmpty && !currentIds.contains(userId)) {
|
||||
return userId;
|
||||
}
|
||||
return _cpInviteReceiverIdFromMessageData(data);
|
||||
}
|
||||
if (sender.isNotEmpty && !currentIds.contains(sender)) {
|
||||
return sender;
|
||||
}
|
||||
return _cpInviteInviterIdFromMessageData(data, invite);
|
||||
}
|
||||
|
||||
@ -908,7 +934,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
return;
|
||||
}
|
||||
final isSender = _cpInviteSenderMatchesCurrent(invite, currentProfile);
|
||||
final isSender = _cpInviteSenderMatchesCurrent(
|
||||
invite,
|
||||
currentProfile,
|
||||
data: data,
|
||||
);
|
||||
final inviterName = _cpInviteDisplayName(
|
||||
nickname: invite.userNickname,
|
||||
account: invite.account,
|
||||
@ -921,7 +951,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
final relationType = scNormalizeCpRelationType(invite.relationType);
|
||||
if (isSender) {
|
||||
final receiver = _cpInviteReceiverFromMessageData(data);
|
||||
final receiver = _cpInviteReceiverFromMessageData(
|
||||
data,
|
||||
fallbackName: _cpInviteReceiverFallbackName(data, message),
|
||||
fallbackAvatar: _cpInviteReceiverFallbackAvatar(data, message),
|
||||
);
|
||||
_cpInviteLog(
|
||||
'show waiting CP_BUILD applyId=$applyId '
|
||||
'relationType=$relationType',
|
||||
@ -952,6 +986,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
'relationType=$relationType '
|
||||
'inviter=$inviterName',
|
||||
);
|
||||
final currentReceiver = _cpInviteReceiverFromMessageData(
|
||||
data,
|
||||
fallbackName: _cpInviteReceiverFallbackName(data, null),
|
||||
fallbackAvatar: _cpInviteReceiverFallbackAvatar(data, null),
|
||||
);
|
||||
await RoomCpInviteDialog.show(
|
||||
context,
|
||||
inviter: RoomCpInviteDialogUser(
|
||||
@ -962,18 +1001,37 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
),
|
||||
receiver: RoomCpInviteDialogUser(
|
||||
name: currentName,
|
||||
avatarUrl: currentProfile.userAvatar ?? "",
|
||||
headdress: currentProfile.getHeaddress()?.sourceUrl ?? "",
|
||||
headdressCover: currentProfile.getHeaddress()?.cover ?? "",
|
||||
avatarUrl:
|
||||
_firstCpInviteValue([
|
||||
currentProfile.userAvatar,
|
||||
currentReceiver.avatarUrl,
|
||||
]) ??
|
||||
"",
|
||||
headdress:
|
||||
_firstCpInviteValue([
|
||||
currentProfile.getHeaddress()?.sourceUrl,
|
||||
currentReceiver.headdress,
|
||||
]) ??
|
||||
"",
|
||||
headdressCover:
|
||||
_firstCpInviteValue([
|
||||
currentProfile.getHeaddress()?.cover,
|
||||
currentReceiver.headdressCover,
|
||||
]) ??
|
||||
"",
|
||||
),
|
||||
style: RoomCpInviteDialogStyle.incoming,
|
||||
countdownSeconds: _cpInviteCountdownSeconds(invite),
|
||||
descriptionText: _cpInviteDescriptionText(relationType),
|
||||
onAccept: () {
|
||||
unawaited(_processIncomingCpInvite(context, applyId, invite, true));
|
||||
unawaited(
|
||||
_processIncomingCpInvite(context, applyId, invite, true, data: data),
|
||||
);
|
||||
},
|
||||
onReject: () {
|
||||
unawaited(_processIncomingCpInvite(context, applyId, invite, false));
|
||||
unawaited(
|
||||
_processIncomingCpInvite(context, applyId, invite, false, data: data),
|
||||
);
|
||||
},
|
||||
onClose: () {
|
||||
_cpInviteLog('close incoming CP_BUILD applyId=$applyId');
|
||||
@ -1066,6 +1124,29 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
if (context != null && invite != null && currentProfile != null) {
|
||||
final isSender = _cpInviteSenderMatchesCurrent(
|
||||
invite,
|
||||
currentProfile,
|
||||
data: data,
|
||||
);
|
||||
if (result == _CpInviteProcessResult.rejected) {
|
||||
if (isSender) {
|
||||
_showCpInviteRejectedDialog(relationType, dedupKey: resultKey);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (result == _CpInviteProcessResult.accepted) {
|
||||
if (isSender) {
|
||||
_dispatchCpRelationEstablishedRoomNotice(
|
||||
groupId: _currentVisibleVoiceRoomGroupId(context),
|
||||
applyId: applyId,
|
||||
data: data,
|
||||
invite: invite,
|
||||
currentProfile: currentProfile,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
unawaited(
|
||||
_showCpInviteResultDialog(
|
||||
context: context,
|
||||
@ -1090,7 +1171,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
required _CpInviteProcessResult result,
|
||||
required String relationType,
|
||||
}) async {
|
||||
final isSender = _cpInviteSenderMatchesCurrent(invite, currentProfile);
|
||||
final isSender = _cpInviteSenderMatchesCurrent(
|
||||
invite,
|
||||
currentProfile,
|
||||
data: data,
|
||||
);
|
||||
final currentName = _cpInviteDisplayName(
|
||||
nickname: currentProfile.userNickname,
|
||||
account: currentProfile.account,
|
||||
@ -1105,6 +1190,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
'show CP invite result dialog result=${result.name} '
|
||||
'relationType=$relationType isSender=$isSender',
|
||||
);
|
||||
final currentReceiver = _cpInviteReceiverFromMessageData(data);
|
||||
await RoomCpInviteDialog.show(
|
||||
context,
|
||||
inviter: RoomCpInviteDialogUser(
|
||||
@ -1124,12 +1210,31 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
),
|
||||
receiver:
|
||||
isSender
|
||||
? _cpInviteReceiverFromMessageData(data)
|
||||
? _cpInviteReceiverFromMessageData(
|
||||
data,
|
||||
fallbackName: _cpInviteReceiverFallbackName(data, null),
|
||||
fallbackAvatar: _cpInviteReceiverFallbackAvatar(data, null),
|
||||
)
|
||||
: RoomCpInviteDialogUser(
|
||||
name: currentName,
|
||||
avatarUrl: currentProfile.userAvatar ?? "",
|
||||
headdress: currentProfile.getHeaddress()?.sourceUrl ?? "",
|
||||
headdressCover: currentProfile.getHeaddress()?.cover ?? "",
|
||||
avatarUrl:
|
||||
_firstCpInviteValue([
|
||||
currentProfile.userAvatar,
|
||||
currentReceiver.avatarUrl,
|
||||
]) ??
|
||||
"",
|
||||
headdress:
|
||||
_firstCpInviteValue([
|
||||
currentProfile.getHeaddress()?.sourceUrl,
|
||||
currentReceiver.headdress,
|
||||
]) ??
|
||||
"",
|
||||
headdressCover:
|
||||
_firstCpInviteValue([
|
||||
currentProfile.getHeaddress()?.cover,
|
||||
currentReceiver.headdressCover,
|
||||
]) ??
|
||||
"",
|
||||
),
|
||||
style:
|
||||
isSender
|
||||
@ -1271,13 +1376,15 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
BuildContext context,
|
||||
String applyId,
|
||||
SCSystemInvitMessageRes invite,
|
||||
bool agree,
|
||||
) async {
|
||||
bool agree, {
|
||||
Map<String, dynamic>? data,
|
||||
}) async {
|
||||
_cpInviteLog('process CP_BUILD applyId=$applyId agree=$agree');
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final voiceRoomGroupId = _currentVisibleVoiceRoomGroupId(context);
|
||||
final success = await profileManager.cpRlationshipProcessApply(
|
||||
context,
|
||||
applyId,
|
||||
@ -1295,6 +1402,23 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
|
||||
unawaited(profileManager.refreshLoadedCpProfiles());
|
||||
if (agree) {
|
||||
_dispatchCpRelationEstablishedRoomNotice(
|
||||
groupId: voiceRoomGroupId,
|
||||
applyId: applyId,
|
||||
data: data,
|
||||
invite: invite,
|
||||
currentProfile: AccountStorage().getCurrentUser()?.userProfile,
|
||||
);
|
||||
} else {
|
||||
_dispatchCpInviteRejectedRoomNotice(
|
||||
groupId: voiceRoomGroupId,
|
||||
applyId: applyId,
|
||||
data: data,
|
||||
invite: invite,
|
||||
currentProfile: AccountStorage().getCurrentUser()?.userProfile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendCpRelationEstablishedSystemMessage({
|
||||
@ -1308,6 +1432,183 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
void _dispatchCpRelationEstablishedRoomNotice({
|
||||
required String groupId,
|
||||
required String applyId,
|
||||
required SCSystemInvitMessageRes invite,
|
||||
required SocialChatUserProfile? currentProfile,
|
||||
Map<String, dynamic>? data,
|
||||
}) {
|
||||
if (currentProfile == null) {
|
||||
return;
|
||||
}
|
||||
if (groupId.isEmpty) {
|
||||
_cpInviteLog(
|
||||
'skip room established notice reason=no_visible_voice_room '
|
||||
'applyId=$applyId',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final isSender = _cpInviteSenderMatchesCurrent(
|
||||
invite,
|
||||
currentProfile,
|
||||
data: data,
|
||||
);
|
||||
final inviter = isSender ? currentProfile : _cpInviteInviterProfile(invite);
|
||||
final receiver = isSender ? _cpInviteReceiverProfile(data) : currentProfile;
|
||||
final relationType = scNormalizeCpRelationType(
|
||||
_firstCpInviteValue([
|
||||
invite.relationType,
|
||||
data == null ? null : data["relationType"]?.toString(),
|
||||
data == null ? null : data["relation_type"]?.toString(),
|
||||
]) ??
|
||||
"",
|
||||
);
|
||||
final targetUserIds = _compactCpNoticeTargetIds([inviter, receiver]);
|
||||
_cpInviteLog(
|
||||
'dispatch room established notice applyId=$applyId '
|
||||
'relationType=$relationType groupId=$groupId '
|
||||
'isSender=$isSender targets=${targetUserIds.join(",")}',
|
||||
);
|
||||
unawaited(
|
||||
dispatchMessage(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: scBuildCpRelationEstablishedNotice(
|
||||
userName1: _roomMsgUserDisplayName(inviter),
|
||||
userName2: _roomMsgUserDisplayName(receiver),
|
||||
relationType: relationType,
|
||||
),
|
||||
type: SCRoomMsgType.cpSystemNotice,
|
||||
noticeAction: "RELATION_ESTABLISHED",
|
||||
user: receiver,
|
||||
toUser: inviter,
|
||||
targetUserIds: targetUserIds,
|
||||
giftBatchId: applyId,
|
||||
role: relationType,
|
||||
),
|
||||
addLocal: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _dispatchCpInviteRejectedRoomNotice({
|
||||
required String groupId,
|
||||
required String applyId,
|
||||
required SCSystemInvitMessageRes invite,
|
||||
required SocialChatUserProfile? currentProfile,
|
||||
Map<String, dynamic>? data,
|
||||
}) {
|
||||
if (currentProfile == null) {
|
||||
return;
|
||||
}
|
||||
if (groupId.isEmpty) {
|
||||
_cpInviteLog(
|
||||
'skip room rejected notice reason=no_visible_voice_room '
|
||||
'applyId=$applyId',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final inviter = _cpInviteInviterProfile(invite);
|
||||
final targetUserIds = _compactCpNoticeTargetIds([inviter]);
|
||||
final relationType = scNormalizeCpRelationType(
|
||||
_firstCpInviteValue([
|
||||
invite.relationType,
|
||||
data == null ? null : data["relationType"]?.toString(),
|
||||
data == null ? null : data["relation_type"]?.toString(),
|
||||
]) ??
|
||||
"",
|
||||
);
|
||||
_cpInviteLog(
|
||||
'dispatch room rejected notice applyId=$applyId '
|
||||
'relationType=$relationType groupId=$groupId '
|
||||
'targets=${targetUserIds.join(",")}',
|
||||
);
|
||||
unawaited(
|
||||
dispatchMessage(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: "",
|
||||
type: SCRoomMsgType.cpSystemNotice,
|
||||
noticeAction: "RELATION_REJECTED",
|
||||
user: currentProfile,
|
||||
toUser: inviter,
|
||||
targetUserIds: targetUserIds,
|
||||
giftBatchId: applyId,
|
||||
role: relationType,
|
||||
),
|
||||
addLocal: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _currentVisibleVoiceRoomGroupId(BuildContext context) {
|
||||
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
if (!rtcProvider.isVoiceRoomRouteVisible) {
|
||||
return "";
|
||||
}
|
||||
return (rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
SocialChatUserProfile _cpInviteInviterProfile(
|
||||
SCSystemInvitMessageRes invite,
|
||||
) {
|
||||
final userId =
|
||||
_firstCpInviteValue([invite.actualAccount, invite.account]) ?? "";
|
||||
return SocialChatUserProfile(
|
||||
id: userId,
|
||||
account:
|
||||
_firstCpInviteValue([invite.account, invite.actualAccount]) ?? userId,
|
||||
userNickname: _cpInviteDisplayName(
|
||||
nickname: invite.userNickname,
|
||||
account: invite.account,
|
||||
actualAccount: invite.actualAccount,
|
||||
fallback: userId,
|
||||
),
|
||||
userAvatar: invite.userAvatar ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
SocialChatUserProfile _cpInviteReceiverProfile(Map<String, dynamic>? data) {
|
||||
final receiverData =
|
||||
data == null
|
||||
? const <String, dynamic>{}
|
||||
: Map<String, dynamic>.from(data);
|
||||
final receiver = _cpInviteReceiverFromMessageData(receiverData);
|
||||
final userId = _cpInviteReceiverIdFromMessageData(receiverData);
|
||||
return SocialChatUserProfile(
|
||||
id: userId,
|
||||
account:
|
||||
_firstCpInviteValue([
|
||||
receiverData["acceptAccount"]?.toString(),
|
||||
receiverData["receiverAccount"]?.toString(),
|
||||
userId,
|
||||
]) ??
|
||||
userId,
|
||||
userNickname: receiver.name,
|
||||
userAvatar: receiver.avatarUrl,
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _compactCpNoticeTargetIds(
|
||||
Iterable<SocialChatUserProfile?> profiles,
|
||||
) {
|
||||
final result = <String>[];
|
||||
for (final profile in profiles) {
|
||||
for (final value in [profile?.id, profile?.account]) {
|
||||
final text = value?.trim() ?? "";
|
||||
if (text.isNotEmpty && !result.contains(text)) {
|
||||
result.add(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> sendCpInviteRejectedSystemMessage({
|
||||
required String applyId,
|
||||
required SCSystemInvitMessageRes invite,
|
||||
@ -1334,8 +1635,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
data["senderUserId"]?.toString(),
|
||||
data["senderId"]?.toString(),
|
||||
data["fromUserId"]?.toString(),
|
||||
data["userId"]?.toString(),
|
||||
content["actualAccount"]?.toString(),
|
||||
data["applyUserId"]?.toString(),
|
||||
data["account"]?.toString(),
|
||||
content["applyActualAccount"]?.toString(),
|
||||
content["applyUserActualAccount"]?.toString(),
|
||||
content["inviterActualAccount"]?.toString(),
|
||||
@ -1344,11 +1645,14 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
content["senderUserId"]?.toString(),
|
||||
content["senderId"]?.toString(),
|
||||
content["fromUserId"]?.toString(),
|
||||
content["userId"]?.toString(),
|
||||
invite.actualAccount,
|
||||
data["applyUserId"]?.toString(),
|
||||
content["applyUserId"]?.toString(),
|
||||
content["applyUserAccount"]?.toString(),
|
||||
invite.actualAccount,
|
||||
invite.account,
|
||||
data["userId"]?.toString(),
|
||||
content["actualAccount"]?.toString(),
|
||||
content["userId"]?.toString(),
|
||||
content["account"]?.toString(),
|
||||
]) ??
|
||||
"";
|
||||
}
|
||||
@ -1429,13 +1733,24 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
|
||||
bool _cpInviteSenderMatchesCurrent(
|
||||
SCSystemInvitMessageRes invite,
|
||||
SocialChatUserProfile currentProfile,
|
||||
) {
|
||||
SocialChatUserProfile currentProfile, {
|
||||
Map<String, dynamic>? data,
|
||||
}) {
|
||||
final currentIds =
|
||||
[currentProfile.id, currentProfile.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
if (data != null) {
|
||||
final receiverIds = _cpInviteReceiverIdentityValues(data);
|
||||
if (currentIds.any(receiverIds.contains)) {
|
||||
return false;
|
||||
}
|
||||
final explicitInviteIds = _cpInviteInviterIdentityValues(data);
|
||||
if (explicitInviteIds.isNotEmpty) {
|
||||
return currentIds.any(explicitInviteIds.contains);
|
||||
}
|
||||
}
|
||||
final inviteIds =
|
||||
[invite.actualAccount, invite.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
@ -1444,9 +1759,68 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return currentIds.any(inviteIds.contains);
|
||||
}
|
||||
|
||||
Set<String> _cpInviteInviterIdentityValues(Map<String, dynamic> data) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
final values = <String?>[
|
||||
data["account"]?.toString(),
|
||||
data["actualAccount"]?.toString(),
|
||||
data["applyUserId"]?.toString(),
|
||||
data["applyUserAccount"]?.toString(),
|
||||
data["applyActualAccount"]?.toString(),
|
||||
data["applyUserActualAccount"]?.toString(),
|
||||
data["inviterActualAccount"]?.toString(),
|
||||
data["inviterUserId"]?.toString(),
|
||||
data["inviterId"]?.toString(),
|
||||
data["senderUserId"]?.toString(),
|
||||
data["senderId"]?.toString(),
|
||||
data["fromUserId"]?.toString(),
|
||||
content["applyUserId"]?.toString(),
|
||||
content["applyUserAccount"]?.toString(),
|
||||
content["applyActualAccount"]?.toString(),
|
||||
content["applyUserActualAccount"]?.toString(),
|
||||
content["inviterActualAccount"]?.toString(),
|
||||
content["inviterUserId"]?.toString(),
|
||||
content["inviterId"]?.toString(),
|
||||
content["senderUserId"]?.toString(),
|
||||
content["senderId"]?.toString(),
|
||||
content["fromUserId"]?.toString(),
|
||||
];
|
||||
return values.map((value) => value?.trim() ?? "").where((value) {
|
||||
return value.isNotEmpty;
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
Set<String> _cpInviteReceiverIdentityValues(Map<String, dynamic> data) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
return [
|
||||
data["acceptUserId"]?.toString(),
|
||||
data["acceptAccount"]?.toString(),
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
data["receiverId"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
data["receiverAccount"]?.toString(),
|
||||
data["receiverActualAccount"]?.toString(),
|
||||
data["targetUserId"]?.toString(),
|
||||
data["toUserId"]?.toString(),
|
||||
content["acceptUserId"]?.toString(),
|
||||
content["acceptAccount"]?.toString(),
|
||||
content["acceptActualAccount"]?.toString(),
|
||||
content["receiverId"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
content["receiverAccount"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["targetUserId"]?.toString(),
|
||||
content["toUserId"]?.toString(),
|
||||
].map((value) => value?.trim() ?? "").where((value) {
|
||||
return value.isNotEmpty;
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
RoomCpInviteDialogUser _cpInviteReceiverFromMessageData(
|
||||
Map<String, dynamic> data,
|
||||
) {
|
||||
Map<String, dynamic> data, {
|
||||
String? fallbackName,
|
||||
String? fallbackAvatar,
|
||||
}) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
final name = _cpInviteDisplayName(
|
||||
nickname: _firstCpInviteValue([
|
||||
@ -1472,6 +1846,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
]),
|
||||
fallback: _firstCpInviteValue([
|
||||
fallbackName,
|
||||
data["acceptUserId"]?.toString(),
|
||||
data["receiverId"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
@ -1490,6 +1865,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
content["acceptUserAvatar"]?.toString(),
|
||||
content["receiverAvatar"]?.toString(),
|
||||
content["receiverUserAvatar"]?.toString(),
|
||||
fallbackAvatar,
|
||||
]) ??
|
||||
"",
|
||||
headdress:
|
||||
@ -1523,6 +1899,33 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
String? _cpInviteReceiverFallbackName(
|
||||
Map<String, dynamic> data,
|
||||
V2TimMessage? message,
|
||||
) {
|
||||
final receiverId = _cpInviteReceiverIdFromMessageData(data);
|
||||
final conversation =
|
||||
receiverId.isNotEmpty ? conversationMap["c2c_$receiverId"] : null;
|
||||
return _firstCpInviteValue([
|
||||
conversation?.showName,
|
||||
message?.friendRemark,
|
||||
message?.nickName,
|
||||
message?.nameCard,
|
||||
receiverId,
|
||||
message?.userID,
|
||||
]);
|
||||
}
|
||||
|
||||
String? _cpInviteReceiverFallbackAvatar(
|
||||
Map<String, dynamic> data,
|
||||
V2TimMessage? message,
|
||||
) {
|
||||
final receiverId = _cpInviteReceiverIdFromMessageData(data);
|
||||
final conversation =
|
||||
receiverId.isNotEmpty ? conversationMap["c2c_$receiverId"] : null;
|
||||
return _firstCpInviteValue([conversation?.faceUrl, message?.faceUrl]);
|
||||
}
|
||||
|
||||
String? _firstCpInviteValue(Iterable<String?> values) {
|
||||
for (final value in values) {
|
||||
final text = value?.trim() ?? "";
|
||||
@ -2065,9 +2468,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
if (msg.type == SCRoomMsgType.cpSystemNotice &&
|
||||
msg.noticeAction == "RELATION_REJECTED") {
|
||||
if (_isDuplicateCpRelationNotice(msg)) {
|
||||
return;
|
||||
}
|
||||
_showCpInviteRejectedDialogIfNeeded(msg);
|
||||
return;
|
||||
}
|
||||
if (msg.type == SCRoomMsgType.cpSystemNotice &&
|
||||
_isDuplicateCpRelationNotice(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
roomAllMsgList.insert(0, msg);
|
||||
if (roomAllMsgList.length > 250) {
|
||||
@ -2168,6 +2578,69 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
bool _isDuplicateCpRelationNotice(Msg msg) {
|
||||
if (msg.type != SCRoomMsgType.cpSystemNotice) {
|
||||
return false;
|
||||
}
|
||||
final action = msg.noticeAction?.trim() ?? "";
|
||||
if (action != "RELATION_ESTABLISHED" &&
|
||||
action != "RELATION_JOINED_ROOM" &&
|
||||
action != "RELATION_REJECTED") {
|
||||
return false;
|
||||
}
|
||||
final key = _cpRelationNoticeDedupKey(msg);
|
||||
if (key.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
_recentCpRelationNoticeTimes.removeWhere((_, timestamp) {
|
||||
return now - timestamp > 60000;
|
||||
});
|
||||
final lastShownAt = _recentCpRelationNoticeTimes[key];
|
||||
if (lastShownAt != null && now - lastShownAt < 60000) {
|
||||
_cpInviteLog('skip duplicated room CP notice key=$key');
|
||||
return true;
|
||||
}
|
||||
_recentCpRelationNoticeTimes[key] = now;
|
||||
return false;
|
||||
}
|
||||
|
||||
String _cpRelationNoticeDedupKey(Msg msg) {
|
||||
final explicitKey = msg.giftBatchId?.trim() ?? "";
|
||||
final action = msg.noticeAction?.trim() ?? "";
|
||||
final relationType = scNormalizeCpRelationType(msg.role);
|
||||
final ids =
|
||||
[_roomMsgUserId(msg.user), _roomMsgUserId(msg.toUser)]
|
||||
..removeWhere((value) => value.isEmpty)
|
||||
..sort();
|
||||
if (ids.isNotEmpty) {
|
||||
return [
|
||||
"cp_notice",
|
||||
action,
|
||||
msg.groupId ?? "",
|
||||
relationType,
|
||||
...ids,
|
||||
].join("|");
|
||||
}
|
||||
if (explicitKey.isNotEmpty) {
|
||||
return "cp_notice:$action:$explicitKey";
|
||||
}
|
||||
if (ids.isEmpty) {
|
||||
final names = [
|
||||
_roomMsgUserDisplayName(msg.user),
|
||||
_roomMsgUserDisplayName(msg.toUser),
|
||||
]..sort();
|
||||
ids.addAll(names);
|
||||
}
|
||||
return [
|
||||
"cp_notice",
|
||||
action,
|
||||
msg.groupId ?? "",
|
||||
relationType,
|
||||
...ids,
|
||||
].join("|");
|
||||
}
|
||||
|
||||
bool _isLiveCpRelationBroadcastMessage(Msg msg) {
|
||||
final timestamp = msg.time ?? 0;
|
||||
if (timestamp <= 0) {
|
||||
@ -2203,8 +2676,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
if (currentProfile == null ||
|
||||
_isSameRoomMsgUser(currentProfile, msg.user)) {
|
||||
if (currentProfile == null) {
|
||||
return;
|
||||
}
|
||||
final targetIds =
|
||||
@ -2214,6 +2686,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
.toSet() ??
|
||||
const <String>{};
|
||||
final isTarget =
|
||||
_isSameRoomMsgUser(currentProfile, msg.user) ||
|
||||
_isSameRoomMsgUser(currentProfile, msg.toUser) ||
|
||||
targetIds.contains(currentProfile.id?.trim() ?? "") ||
|
||||
targetIds.contains(currentProfile.account?.trim() ?? "");
|
||||
@ -2250,10 +2723,26 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
|
||||
_showCpInviteRejectedDialog(msg.role ?? "");
|
||||
_showCpInviteRejectedDialog(
|
||||
msg.role ?? "",
|
||||
dedupKey: _cpRelationRejectedDialogDedupKey(msg),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCpInviteRejectedDialog(String relationType) {
|
||||
String _cpRelationRejectedDialogDedupKey(Msg msg) {
|
||||
final explicitKey = msg.giftBatchId?.trim() ?? "";
|
||||
if (explicitKey.isNotEmpty) {
|
||||
return "rejected_$explicitKey";
|
||||
}
|
||||
return _cpRelationNoticeDedupKey(msg);
|
||||
}
|
||||
|
||||
void _showCpInviteRejectedDialog(String relationType, {String? dedupKey}) {
|
||||
final key = dedupKey?.trim() ?? "";
|
||||
if (key.isNotEmpty && !_shownCpInviteRejectedDialogKeys.add(key)) {
|
||||
_cpInviteLog('skip duplicated rejected dialog key=$key');
|
||||
return;
|
||||
}
|
||||
final context = navigatorKey.currentState?.context;
|
||||
if (context == null) {
|
||||
_cpInviteLog(
|
||||
|
||||
@ -166,8 +166,8 @@ class SCSystemInvitMessageRes {
|
||||
return null;
|
||||
}
|
||||
final source = <String, dynamic>{
|
||||
...root,
|
||||
if (content.isNotEmpty) ...content,
|
||||
...root,
|
||||
};
|
||||
final relationType =
|
||||
_firstNonBlank(source, _relationTypeKeys) ??
|
||||
|
||||
@ -736,6 +736,7 @@ class OverlayManager {
|
||||
_removeMessagesByType(1);
|
||||
_removeMessagesByType(0);
|
||||
_removeMessagesByType(5);
|
||||
_removeMessagesByType(6);
|
||||
if (normalizedRoomId.isNotEmpty) {
|
||||
_removeMessagesByType(3, roomId: normalizedRoomId);
|
||||
}
|
||||
@ -871,6 +872,9 @@ class OverlayManager {
|
||||
if (message.type == 4) {
|
||||
return _isCurrentRoomFloatingMessage(context, message);
|
||||
}
|
||||
if (message.type == 6) {
|
||||
return _isCurrentRoomFloatingMessage(context, message);
|
||||
}
|
||||
if (SCGlobalConfig.isFloatingBroadcastRoomOnly) {
|
||||
return _isCurrentRoomFloatingMessage(context, message);
|
||||
}
|
||||
@ -895,11 +899,15 @@ class OverlayManager {
|
||||
}
|
||||
final currentRoomId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||
final currentRoomGroupId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount?.trim() ??
|
||||
"";
|
||||
final messageRoomId = (message.roomId ?? "").trim();
|
||||
if (currentRoomId.isEmpty || messageRoomId.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return currentRoomId == messageRoomId;
|
||||
return currentRoomId == messageRoomId ||
|
||||
(currentRoomGroupId.isNotEmpty && currentRoomGroupId == messageRoomId);
|
||||
}
|
||||
|
||||
bool _shouldDisplayRegionBroadcastMessage(
|
||||
|
||||
@ -346,7 +346,7 @@ class _MessageConversationListPageState
|
||||
}
|
||||
_loadingConversationUserIds.add(userId);
|
||||
SCAccountRepository()
|
||||
.loadUserInfo(userId)
|
||||
.loadUserInfo(userId, silentErrorToast: true)
|
||||
.then((value) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
@ -411,7 +411,14 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
_effectiveUserId.isNotEmpty;
|
||||
|
||||
String get _effectiveUserId {
|
||||
final cpInvitePeerId = _cpInvitePeerUserIdFromLastMessage();
|
||||
final cpInviteProfile = _cpInvitePeerProfileFromLastMessage();
|
||||
final cpInvitePeerId =
|
||||
_firstCpInviteValue([
|
||||
_cpInvitePeerUserIdFromLastMessage(),
|
||||
cpInviteProfile?.id,
|
||||
cpInviteProfile?.account,
|
||||
]) ??
|
||||
"";
|
||||
if (cpInvitePeerId.isNotEmpty) {
|
||||
return cpInvitePeerId;
|
||||
}
|
||||
@ -423,6 +430,11 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
if (userAvatar.isNotEmpty) {
|
||||
return userAvatar;
|
||||
}
|
||||
final cpInviteAvatar =
|
||||
_cpInvitePeerProfileFromLastMessage()?.userAvatar ?? "";
|
||||
if (cpInviteAvatar.isNotEmpty) {
|
||||
return cpInviteAvatar;
|
||||
}
|
||||
return conversation.faceUrl ?? "";
|
||||
}
|
||||
|
||||
@ -431,6 +443,17 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
if (nickname.isNotEmpty) {
|
||||
return nickname;
|
||||
}
|
||||
final cpInviteProfile = _cpInvitePeerProfileFromLastMessage();
|
||||
final cpInviteName =
|
||||
_firstCpInviteValue([
|
||||
cpInviteProfile?.userNickname,
|
||||
cpInviteProfile?.id,
|
||||
cpInviteProfile?.account,
|
||||
]) ??
|
||||
"";
|
||||
if (cpInviteName.isNotEmpty) {
|
||||
return cpInviteName;
|
||||
}
|
||||
return conversation.showName ??
|
||||
conversation.userID ??
|
||||
conversation.groupID ??
|
||||
@ -439,7 +462,7 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
user = widget.initialUser;
|
||||
user = widget.initialUser ?? _cpInvitePeerProfileFromLastMessage();
|
||||
loadUserInfo();
|
||||
super.initState();
|
||||
}
|
||||
@ -449,6 +472,11 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.initialUser != null && widget.initialUser != user) {
|
||||
user = widget.initialUser;
|
||||
return;
|
||||
}
|
||||
final cpInviteProfile = _cpInvitePeerProfileFromLastMessage();
|
||||
if (user == null && cpInviteProfile != null) {
|
||||
user = cpInviteProfile;
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,26 +700,50 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
}
|
||||
|
||||
void loadUserInfo() {
|
||||
if (!_shouldLoadUserInfo || user != null) return;
|
||||
if (!_shouldLoadUserInfo) return;
|
||||
final userId = _effectiveUserId;
|
||||
if (userId.isEmpty) return;
|
||||
SCAccountRepository().loadUserInfo(userId).then((value) {
|
||||
if (!mounted) return;
|
||||
user = value;
|
||||
setState(() {});
|
||||
});
|
||||
SCAccountRepository()
|
||||
.loadUserInfo(userId, silentErrorToast: true)
|
||||
.then((value) {
|
||||
if (!mounted) return;
|
||||
user = value;
|
||||
setState(() {});
|
||||
})
|
||||
.catchError((_) {
|
||||
if (!mounted) return;
|
||||
user ??= _cpInvitePeerProfileFromLastMessage();
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
V2TimConversation _routeConversation() {
|
||||
final peerId = _effectiveUserId;
|
||||
if (!_isC2CConversation ||
|
||||
peerId.isEmpty ||
|
||||
peerId == (conversation.userID?.trim() ?? "")) {
|
||||
if (!_isC2CConversation || peerId.isEmpty) {
|
||||
return conversation;
|
||||
}
|
||||
final json = conversation.toJson();
|
||||
json["userID"] = peerId;
|
||||
json["conversationID"] = "c2c_$peerId";
|
||||
final isSamePeer = peerId == (conversation.userID?.trim() ?? "");
|
||||
if (!isSamePeer) {
|
||||
json["userID"] = peerId;
|
||||
json["conversationID"] = "c2c_$peerId";
|
||||
}
|
||||
final profile = _cpInvitePeerProfileFromLastMessage();
|
||||
final profileName = _firstCpInviteValue([
|
||||
profile?.userNickname,
|
||||
profile?.id,
|
||||
profile?.account,
|
||||
]);
|
||||
if ((profileName ?? "").isNotEmpty) {
|
||||
json["showName"] = profileName;
|
||||
}
|
||||
final profileAvatar = profile?.userAvatar?.trim() ?? "";
|
||||
if (profileAvatar.isNotEmpty) {
|
||||
json["faceUrl"] = profileAvatar;
|
||||
}
|
||||
if (isSamePeer && profileName == null && profileAvatar.isEmpty) {
|
||||
return conversation;
|
||||
}
|
||||
return V2TimConversation.fromJson(json);
|
||||
}
|
||||
|
||||
@ -699,10 +751,73 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
if (!_isC2CConversation) {
|
||||
return "";
|
||||
}
|
||||
final message = conversation.lastMessage;
|
||||
final data = _cpInviteCustomData(conversation.lastMessage);
|
||||
if (data == null) {
|
||||
if (data == null || !_isCpBuildInviteData(data)) {
|
||||
return "";
|
||||
}
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final currentIds =
|
||||
<String?>[currentProfile?.id, currentProfile?.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
final isCurrentInviter = _cpInviteCurrentUserIsInviter(data);
|
||||
final peerProfile = _cpInvitePeerProfileFromData(data);
|
||||
final sender = message?.sender?.trim() ?? "";
|
||||
final messageUserId =
|
||||
_firstCpInviteValue([message?.userID, conversation.userID]) ?? "";
|
||||
if (isCurrentInviter) {
|
||||
return _firstCpInviteValue([
|
||||
peerProfile?.id,
|
||||
currentIds.contains(messageUserId) ? null : messageUserId,
|
||||
_cpInviteReceiverIdFromData(data),
|
||||
peerProfile?.account,
|
||||
]) ??
|
||||
"";
|
||||
}
|
||||
return _firstCpInviteValue([
|
||||
currentIds.contains(sender) ? null : sender,
|
||||
peerProfile?.id,
|
||||
_cpInviteInviterIdFromData(data),
|
||||
peerProfile?.account,
|
||||
]) ??
|
||||
"";
|
||||
}
|
||||
|
||||
SocialChatUserProfile? _cpInvitePeerProfileFromLastMessage() {
|
||||
final data = _cpInviteCustomData(conversation.lastMessage);
|
||||
if (data == null || !_isCpBuildInviteData(data)) {
|
||||
return null;
|
||||
}
|
||||
return _cpInvitePeerProfileFromData(data);
|
||||
}
|
||||
|
||||
SocialChatUserProfile? _cpInvitePeerProfileFromData(
|
||||
Map<String, dynamic> data,
|
||||
) {
|
||||
final profile =
|
||||
_cpInviteCurrentUserIsInviter(data)
|
||||
? _cpInviteReceiverProfileFromData(data)
|
||||
: _cpInviteInviterProfileFromData(data);
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final currentIds =
|
||||
[currentProfile?.id, currentProfile?.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
final profileIds =
|
||||
[profile?.id, profile?.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
if (currentIds.any(profileIds.contains)) {
|
||||
return null;
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
bool _isCpBuildInviteData(Map<String, dynamic> data) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
final noticeType =
|
||||
_firstCpInviteValue([
|
||||
@ -712,17 +827,208 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
content["type"]?.toString(),
|
||||
])?.toUpperCase() ??
|
||||
"";
|
||||
if (!noticeType.contains(SCSysytemMessageType.CP_BUILD.name)) {
|
||||
return "";
|
||||
}
|
||||
return noticeType.contains(SCSysytemMessageType.CP_BUILD.name);
|
||||
}
|
||||
|
||||
bool _cpInviteCurrentUserIsInviter(Map<String, dynamic> data) {
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
if (currentProfile == null) {
|
||||
return false;
|
||||
}
|
||||
final currentIds =
|
||||
<String?>[currentProfile?.id, currentProfile?.account]
|
||||
[currentProfile.id, currentProfile.account]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet();
|
||||
final inviterIds = _compactCpInviteValues([
|
||||
if (currentIds.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final receiverIds = _cpInviteReceiverIdentityValues(data);
|
||||
if (currentIds.any(receiverIds.contains)) {
|
||||
return false;
|
||||
}
|
||||
final inviterIds = _cpInviteInviterIdentityValues(data);
|
||||
return currentIds.any(inviterIds.contains);
|
||||
}
|
||||
|
||||
SocialChatUserProfile? _cpInviteInviterProfileFromData(
|
||||
Map<String, dynamic> data,
|
||||
) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
final nested = _firstCpInviteMap(data, content, const [
|
||||
"applyUser",
|
||||
"applyUserProfile",
|
||||
"inviter",
|
||||
"inviterProfile",
|
||||
"sender",
|
||||
"senderProfile",
|
||||
"fromUser",
|
||||
"fromUserProfile",
|
||||
"user",
|
||||
"userProfile",
|
||||
"profile",
|
||||
]);
|
||||
return _cpInviteProfileOrNull(
|
||||
id: _firstCpInviteValue([
|
||||
nested["id"]?.toString(),
|
||||
nested["userId"]?.toString(),
|
||||
nested["actualAccount"]?.toString(),
|
||||
data["actualAccount"]?.toString(),
|
||||
data["applyActualAccount"]?.toString(),
|
||||
data["applyUserActualAccount"]?.toString(),
|
||||
data["inviterActualAccount"]?.toString(),
|
||||
data["inviterUserId"]?.toString(),
|
||||
data["inviterId"]?.toString(),
|
||||
data["senderUserId"]?.toString(),
|
||||
data["senderId"]?.toString(),
|
||||
data["fromUserId"]?.toString(),
|
||||
data["applyUserId"]?.toString(),
|
||||
content["applyActualAccount"]?.toString(),
|
||||
content["applyUserActualAccount"]?.toString(),
|
||||
content["inviterActualAccount"]?.toString(),
|
||||
content["inviterUserId"]?.toString(),
|
||||
content["inviterId"]?.toString(),
|
||||
content["senderUserId"]?.toString(),
|
||||
content["senderId"]?.toString(),
|
||||
content["fromUserId"]?.toString(),
|
||||
content["applyUserId"]?.toString(),
|
||||
]),
|
||||
account: _firstCpInviteValue([
|
||||
nested["account"]?.toString(),
|
||||
data["account"]?.toString(),
|
||||
data["applyUserAccount"]?.toString(),
|
||||
content["applyUserAccount"]?.toString(),
|
||||
]),
|
||||
nickname: _firstCpInviteValue([
|
||||
nested["userNickname"]?.toString(),
|
||||
nested["nickname"]?.toString(),
|
||||
data["applyNickname"]?.toString(),
|
||||
data["applyUserNickname"]?.toString(),
|
||||
data["inviterNickname"]?.toString(),
|
||||
data["senderNickname"]?.toString(),
|
||||
data["userNickname"]?.toString(),
|
||||
content["applyNickname"]?.toString(),
|
||||
content["applyUserNickname"]?.toString(),
|
||||
content["inviterNickname"]?.toString(),
|
||||
content["senderNickname"]?.toString(),
|
||||
]),
|
||||
avatar: _firstCpInviteValue([
|
||||
nested["userAvatar"]?.toString(),
|
||||
nested["avatar"]?.toString(),
|
||||
data["applyUserAvatar"]?.toString(),
|
||||
data["inviterAvatar"]?.toString(),
|
||||
data["senderAvatar"]?.toString(),
|
||||
data["userAvatar"]?.toString(),
|
||||
content["applyUserAvatar"]?.toString(),
|
||||
content["inviterAvatar"]?.toString(),
|
||||
content["senderAvatar"]?.toString(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
SocialChatUserProfile? _cpInviteReceiverProfileFromData(
|
||||
Map<String, dynamic> data,
|
||||
) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
final nested = _firstCpInviteMap(data, content, const [
|
||||
"acceptUser",
|
||||
"acceptUserProfile",
|
||||
"receiver",
|
||||
"receiverProfile",
|
||||
"toUser",
|
||||
"toUserProfile",
|
||||
"targetUser",
|
||||
"targetUserProfile",
|
||||
]);
|
||||
return _cpInviteProfileOrNull(
|
||||
id: _firstCpInviteValue([
|
||||
nested["id"]?.toString(),
|
||||
nested["userId"]?.toString(),
|
||||
nested["actualAccount"]?.toString(),
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
data["acceptUserId"]?.toString(),
|
||||
data["receiverActualAccount"]?.toString(),
|
||||
data["receiverId"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
data["targetUserId"]?.toString(),
|
||||
data["toUserId"]?.toString(),
|
||||
content["acceptActualAccount"]?.toString(),
|
||||
content["acceptUserId"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["receiverId"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
content["targetUserId"]?.toString(),
|
||||
content["toUserId"]?.toString(),
|
||||
]),
|
||||
account: _firstCpInviteValue([
|
||||
nested["account"]?.toString(),
|
||||
data["acceptAccount"]?.toString(),
|
||||
data["receiverAccount"]?.toString(),
|
||||
content["acceptAccount"]?.toString(),
|
||||
content["receiverAccount"]?.toString(),
|
||||
]),
|
||||
nickname: _firstCpInviteValue([
|
||||
nested["userNickname"]?.toString(),
|
||||
nested["nickname"]?.toString(),
|
||||
data["acceptNickname"]?.toString(),
|
||||
data["acceptUserNickname"]?.toString(),
|
||||
data["receiverNickname"]?.toString(),
|
||||
data["receiverUserNickname"]?.toString(),
|
||||
content["acceptNickname"]?.toString(),
|
||||
content["acceptUserNickname"]?.toString(),
|
||||
content["receiverNickname"]?.toString(),
|
||||
content["receiverUserNickname"]?.toString(),
|
||||
]),
|
||||
avatar: _firstCpInviteValue([
|
||||
nested["userAvatar"]?.toString(),
|
||||
nested["avatar"]?.toString(),
|
||||
data["acceptUserAvatar"]?.toString(),
|
||||
data["receiverAvatar"]?.toString(),
|
||||
data["receiverUserAvatar"]?.toString(),
|
||||
content["acceptUserAvatar"]?.toString(),
|
||||
content["receiverAvatar"]?.toString(),
|
||||
content["receiverUserAvatar"]?.toString(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
SocialChatUserProfile? _cpInviteProfileOrNull({
|
||||
String? id,
|
||||
String? account,
|
||||
String? nickname,
|
||||
String? avatar,
|
||||
}) {
|
||||
final hasValue = [id, account, nickname, avatar].any((value) {
|
||||
return (value?.trim() ?? "").isNotEmpty;
|
||||
});
|
||||
if (!hasValue) {
|
||||
return null;
|
||||
}
|
||||
return SocialChatUserProfile(
|
||||
id: id?.trim(),
|
||||
account: account?.trim(),
|
||||
userNickname: nickname?.trim(),
|
||||
userAvatar: avatar?.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
String _cpInviteInviterIdFromData(Map<String, dynamic> data) {
|
||||
final profile = _cpInviteInviterProfileFromData(data);
|
||||
return _firstCpInviteValue([profile?.id, profile?.account]) ?? "";
|
||||
}
|
||||
|
||||
String _cpInviteReceiverIdFromData(Map<String, dynamic> data) {
|
||||
final profile = _cpInviteReceiverProfileFromData(data);
|
||||
return _firstCpInviteValue([profile?.id, profile?.account]) ?? "";
|
||||
}
|
||||
|
||||
Set<String> _cpInviteInviterIdentityValues(Map<String, dynamic> data) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
return _compactCpInviteValues([
|
||||
data["account"]?.toString(),
|
||||
data["actualAccount"]?.toString(),
|
||||
data["applyUserId"]?.toString(),
|
||||
data["applyUserAccount"]?.toString(),
|
||||
data["applyActualAccount"]?.toString(),
|
||||
data["applyUserActualAccount"]?.toString(),
|
||||
data["inviterActualAccount"]?.toString(),
|
||||
@ -731,8 +1037,8 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
data["senderUserId"]?.toString(),
|
||||
data["senderId"]?.toString(),
|
||||
data["fromUserId"]?.toString(),
|
||||
data["userId"]?.toString(),
|
||||
content["actualAccount"]?.toString(),
|
||||
content["applyUserId"]?.toString(),
|
||||
content["applyUserAccount"]?.toString(),
|
||||
content["applyActualAccount"]?.toString(),
|
||||
content["applyUserActualAccount"]?.toString(),
|
||||
content["inviterActualAccount"]?.toString(),
|
||||
@ -741,29 +1047,67 @@ class _ConversationItemState extends State<ConversationItem> {
|
||||
content["senderUserId"]?.toString(),
|
||||
content["senderId"]?.toString(),
|
||||
content["fromUserId"]?.toString(),
|
||||
content["userId"]?.toString(),
|
||||
data["applyUserId"]?.toString(),
|
||||
content["applyUserId"]?.toString(),
|
||||
data["account"]?.toString(),
|
||||
content["account"]?.toString(),
|
||||
]);
|
||||
final isCurrentInviter = currentIds.any(inviterIds.contains);
|
||||
final values =
|
||||
isCurrentInviter
|
||||
? [
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
data["acceptUserId"]?.toString(),
|
||||
data["receiverActualAccount"]?.toString(),
|
||||
data["receiverId"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
content["acceptActualAccount"]?.toString(),
|
||||
content["acceptUserId"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["receiverId"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
]
|
||||
: inviterIds;
|
||||
return _firstCpInviteValue(values) ?? "";
|
||||
]).toSet();
|
||||
}
|
||||
|
||||
Set<String> _cpInviteReceiverIdentityValues(Map<String, dynamic> data) {
|
||||
final content = _cpInviteContentMap(data);
|
||||
return _compactCpInviteValues([
|
||||
data["acceptUserId"]?.toString(),
|
||||
data["acceptAccount"]?.toString(),
|
||||
data["acceptActualAccount"]?.toString(),
|
||||
data["receiverId"]?.toString(),
|
||||
data["receiverUserId"]?.toString(),
|
||||
data["receiverAccount"]?.toString(),
|
||||
data["receiverActualAccount"]?.toString(),
|
||||
data["targetUserId"]?.toString(),
|
||||
data["toUserId"]?.toString(),
|
||||
content["acceptUserId"]?.toString(),
|
||||
content["acceptAccount"]?.toString(),
|
||||
content["acceptActualAccount"]?.toString(),
|
||||
content["receiverId"]?.toString(),
|
||||
content["receiverUserId"]?.toString(),
|
||||
content["receiverAccount"]?.toString(),
|
||||
content["receiverActualAccount"]?.toString(),
|
||||
content["targetUserId"]?.toString(),
|
||||
content["toUserId"]?.toString(),
|
||||
]).toSet();
|
||||
}
|
||||
|
||||
Map<String, dynamic> _firstCpInviteMap(
|
||||
Map<String, dynamic> data,
|
||||
Map<String, dynamic> content,
|
||||
Iterable<String> keys,
|
||||
) {
|
||||
for (final key in keys) {
|
||||
final map = _cpInviteStringKeyMap(data[key]);
|
||||
if (map.isNotEmpty) {
|
||||
return map;
|
||||
}
|
||||
final contentMap = _cpInviteStringKeyMap(content[key]);
|
||||
if (contentMap.isNotEmpty) {
|
||||
return contentMap;
|
||||
}
|
||||
}
|
||||
return const <String, dynamic>{};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _cpInviteStringKeyMap(dynamic value) {
|
||||
if (value is Map<String, dynamic>) {
|
||||
return value;
|
||||
}
|
||||
if (value is Map) {
|
||||
return value.map((key, item) => MapEntry(key.toString(), item));
|
||||
}
|
||||
if (value is String && value.trim().isNotEmpty) {
|
||||
try {
|
||||
final decoded = jsonDecode(value);
|
||||
if (decoded is Map) {
|
||||
return decoded.map((key, item) => MapEntry(key.toString(), item));
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
return const <String, dynamic>{};
|
||||
}
|
||||
|
||||
Map<String, dynamic>? _cpInviteCustomData(V2TimMessage? message) {
|
||||
|
||||
@ -108,6 +108,22 @@ void main() {
|
||||
expect(invite?.actualAccount, '2057731345099841537');
|
||||
});
|
||||
|
||||
test('keeps top-level applicant when content carries receiver profile', () {
|
||||
final invite = SCSystemInvitMessageRes.fromMessageData({
|
||||
'account': '99004',
|
||||
'actualAccount': 'sender-long-id',
|
||||
'relationType': 'CP',
|
||||
'content': jsonEncode({
|
||||
'account': '99005',
|
||||
'actualAccount': 'receiver-long-id',
|
||||
'userNickname': 'Receiver',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(invite?.account, '99004');
|
||||
expect(invite?.actualAccount, 'sender-long-id');
|
||||
});
|
||||
|
||||
test('infers relation type from gift payload when needed', () {
|
||||
final invite = SCSystemInvitMessageRes.fromMessageData({
|
||||
'content': {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user