From b045b3b7840e16d5e2d399ee5c01cabce2786821 Mon Sep 17 00:00:00 2001 From: zhx Date: Sat, 23 May 2026 20:43:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/chat/message_chat_page.dart | 31 ++- .../chat/system/message_system_page.dart | 47 ++++- .../repositories/sc_user_repository_impl.dart | 190 +++++++++++++++--- 3 files changed, 221 insertions(+), 47 deletions(-) diff --git a/lib/modules/chat/message_chat_page.dart b/lib/modules/chat/message_chat_page.dart index 6257849..909dd07 100644 --- a/lib/modules/chat/message_chat_page.dart +++ b/lib/modules/chat/message_chat_page.dart @@ -1275,11 +1275,15 @@ class _MessageItem extends StatelessWidget { ), ), ), + if (cpStatusLineText != null) _buildCpStatusLine(cpStatusLineText), if (cpRelationFormedCard != null) - cpRelationFormedCard - else if (cpStatusLineText != null) - _buildCpStatusLine(cpStatusLineText) - else + Padding( + padding: EdgeInsets.only( + top: cpStatusLineText == null ? 0 : 10.w, + ), + child: cpRelationFormedCard, + ) + else if (cpStatusLineText == null) Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -1647,12 +1651,14 @@ class _MessageItem extends StatelessWidget { return null; } final state = _cpInviteStateFromData(data); - if (state == _CpInviteMessageState.pending || - state == _CpInviteMessageState.accepted) { + if (state == _CpInviteMessageState.pending) { return null; } final localizations = SCAppLocalizations.of(context)!; final relationType = _cpRelationTypeFromMessageData(data); + if (state == _CpInviteMessageState.accepted) { + return _cpAcceptedStatusLineText(relationType); + } if (state == _CpInviteMessageState.expired) { if (relationType != "CP") { return "The ${scCpRelationPossessiveLabel(relationType)} invitation timed out."; @@ -1670,6 +1676,19 @@ class _MessageItem extends StatelessWidget { : "You declined the other party's $relationLabel invitation."; } + 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; + } + } + void _showCpInviteDialogFromMessage( Map data, SCSystemInvitMessageRes? cpInvite, diff --git a/lib/modules/chat/system/message_system_page.dart b/lib/modules/chat/system/message_system_page.dart index 315197d..ef7ee02 100644 --- a/lib/modules/chat/system/message_system_page.dart +++ b/lib/modules/chat/system/message_system_page.dart @@ -370,11 +370,15 @@ class _MessageItem extends StatelessWidget { ), ), ), + if (cpStatusLineText != null) _buildCpStatusLine(cpStatusLineText), if (cpRelationFormedCard != null) - cpRelationFormedCard - else if (cpStatusLineText != null) - _buildCpStatusLine(cpStatusLineText) - else + Padding( + padding: EdgeInsets.only( + top: cpStatusLineText == null ? 0 : 10.w, + ), + child: cpRelationFormedCard, + ) + else if (cpStatusLineText == null) Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -834,10 +838,6 @@ class _MessageItem extends StatelessWidget { if (data == null) { return null; } - final noticeType = data["noticeType"]?.toString() ?? ""; - if (noticeType == SCSysytemMessageType.CP_BUILD.name) { - return null; - } final state = _cpInviteStateFromData(data); if (state == _CpInviteMessageState.pending || !_isCpInvitePayload(data)) { return null; @@ -846,10 +846,10 @@ class _MessageItem extends StatelessWidget { if (state == _CpInviteMessageState.expired) { return localizations.closeFriendInviteTimedOut; } - final inviterView = _isCurrentUserCpInviter(data); if (state == _CpInviteMessageState.accepted) { - return null; + return _cpAcceptedStatusLineText(_cpRelationTypeFromMessageData(data)); } + final inviterView = _isCurrentUserCpInviter(data); return inviterView ? localizations.otherDeclinedCloseFriendInvite : localizations.youDeclinedCloseFriendInvite; @@ -1147,6 +1147,20 @@ class _MessageItem extends StatelessWidget { ], fallback: ""); } + String _cpRelationTypeFromMessageData(Map data) { + final invite = _cpInviteFromMessageData(data); + final content = _cpContentMap(data); + return scNormalizeCpRelationType( + _firstNonBlankString([ + invite?.relationType, + data["relationType"]?.toString(), + data["relation_type"]?.toString(), + content["relationType"]?.toString(), + content["relation_type"]?.toString(), + ], fallback: "CP"), + ); + } + Map _cpContentMap(Map data) { final content = data["content"]; try { @@ -1202,6 +1216,19 @@ class _MessageItem extends StatelessWidget { } } + 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, diff --git a/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart b/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart index 59ff854..8c40f72 100644 --- a/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart +++ b/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart @@ -33,14 +33,25 @@ import 'package:yumi/shared/business_logic/models/res/sc_sign_in_reward_res.dart import 'package:yumi/shared/business_logic/models/res/sc_sign_in_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_violation_handle_res.dart'; import 'package:yumi/shared/business_logic/repositories/user_repository.dart'; +import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/remote/net/api.dart' show BaseNetworkClient; import 'package:yumi/shared/data_sources/sources/remote/net/network_client.dart'; import 'package:yumi/shared/tools/sc_mobile_login_context.dart'; import 'package:yumi/shared/tools/sc_room_profile_cache.dart'; +class _CpRequestCacheEntry { + const _CpRequestCacheEntry(this.value, this.createdAt); + + final T value; + final DateTime createdAt; + + bool isFresh(Duration ttl) => DateTime.now().difference(createdAt) < ttl; +} + class SCAccountRepository implements SocialChatUserRepository { static SCAccountRepository? _instance; + static const Duration _cpRelationRequestCacheTtl = Duration(seconds: 2); SCAccountRepository._internal(); @@ -48,6 +59,12 @@ class SCAccountRepository implements SocialChatUserRepository { return _instance ??= SCAccountRepository._internal(); } + final Map>> _cpPairsCache = {}; + final Map>> _cpPairsInFlight = {}; + final Map> _cpCabinCache = {}; + final Map> _cpCabinInFlight = {}; + int _cpRelationCacheEpoch = 0; + void _cpDebugLog(String message) { if (kDebugMode) { debugPrint('[CP][API] $message'); @@ -78,6 +95,52 @@ class SCAccountRepository implements SocialChatUserRepository { 'levels=${config.levels.map((level) => "{level=${level.level}, name=${level.levelName}, required=${level.requiredIntimacyValue}, rights=${level.rights.length}, unlocked=${level.unlocked}}").toList()}}'; } + String _currentCpCacheSessionKey() { + final currentUserId = + AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? ''; + if (currentUserId.isNotEmpty) { + return currentUserId; + } + final token = AccountStorage().getToken().trim(); + return token.isEmpty ? 'anonymous' : token; + } + + String _normalizedCpRelationType(String relationType) { + final normalized = relationType.trim().toUpperCase(); + return normalized.isEmpty ? 'CP' : normalized; + } + + String _cpPairsCacheKey(String relationType) { + return '${_currentCpCacheSessionKey()}|${_normalizedCpRelationType(relationType)}'; + } + + String _cpCabinCacheKey(String userId, String relationType) { + return '${_currentCpCacheSessionKey()}|${userId.trim()}|${_normalizedCpRelationType(relationType)}'; + } + + T? _freshCpCacheValue( + Map> cache, + String key, + ) { + final entry = cache[key]; + if (entry == null) { + return null; + } + if (entry.isFresh(_cpRelationRequestCacheTtl)) { + return entry.value; + } + cache.remove(key); + return null; + } + + void _clearCpRelationRequestCache() { + _cpRelationCacheEpoch++; + _cpPairsCache.clear(); + _cpPairsInFlight.clear(); + _cpCabinCache.clear(); + _cpCabinInFlight.clear(); + } + @override Future getUser(String id) async { final result = await http.get( @@ -1272,23 +1335,55 @@ class SCAccountRepository implements SocialChatUserRepository { ///user/cp-relationship/pair @override Future> cpRelationshipPairs({String relationType = 'CP'}) async { - _cpDebugLog('pair request relationType=$relationType'); - try { - final result = await http.get>( - "/user/cp-relationship/pair", - queryParams: {"relationType": relationType}, - fromJson: - (json) => (json as List).map((e) => CPRes.fromJson(e)).toList(), - ); - _cpDebugLog( - 'pair response relationType=$relationType count=${result.length} ' - 'pairs=${result.map(_cpPairSummary).toList()}', - ); - return result; - } catch (error) { - _cpDebugLog('pair failed relationType=$relationType error=$error'); - rethrow; + final normalizedRelationType = _normalizedCpRelationType(relationType); + final cacheKey = _cpPairsCacheKey(normalizedRelationType); + final cached = _freshCpCacheValue(_cpPairsCache, cacheKey); + if (cached != null) { + _cpDebugLog('pair cache hit relationType=$normalizedRelationType'); + return cached; } + final inFlight = _cpPairsInFlight[cacheKey]; + if (inFlight != null) { + _cpDebugLog('pair join in-flight relationType=$normalizedRelationType'); + return inFlight; + } + + _cpDebugLog('pair request relationType=$normalizedRelationType'); + final requestEpoch = _cpRelationCacheEpoch; + late final Future> trackedRequest; + trackedRequest = http + .get>( + "/user/cp-relationship/pair", + queryParams: {"relationType": normalizedRelationType}, + fromJson: + (json) => (json as List).map((e) => CPRes.fromJson(e)).toList(), + ) + .then((result) { + if (requestEpoch == _cpRelationCacheEpoch) { + _cpPairsCache[cacheKey] = _CpRequestCacheEntry( + result, + DateTime.now(), + ); + } + _cpDebugLog( + 'pair response relationType=$normalizedRelationType count=${result.length} ' + 'pairs=${result.map(_cpPairSummary).toList()}', + ); + return result; + }) + .catchError((error) { + _cpDebugLog( + 'pair failed relationType=$normalizedRelationType error=$error', + ); + throw error; + }) + .whenComplete(() { + if (_cpPairsInFlight[cacheKey] == trackedRequest) { + _cpPairsInFlight.remove(cacheKey); + } + }); + _cpPairsInFlight[cacheKey] = trackedRequest; + return trackedRequest; } ///user/cp-relationship/send-apply @@ -1308,6 +1403,7 @@ class SCAccountRepository implements SocialChatUserRepository { allowNullBody: true, fromJson: (json) => json, ); + _clearCpRelationRequestCache(); _cpDebugLog('sendApply success data=$parm'); return true; } catch (error) { @@ -1330,6 +1426,7 @@ class SCAccountRepository implements SocialChatUserRepository { allowNullBody: true, fromJson: (json) => json, ); + _clearCpRelationRequestCache(); _cpDebugLog('processApply success data=$parm'); return true; } catch (error) { @@ -1355,6 +1452,7 @@ class SCAccountRepository implements SocialChatUserRepository { allowNullBody: true, fromJson: (json) => json, ); + _clearCpRelationRequestCache(); _cpDebugLog('dismissApply success query=$parm'); return true; } catch (error) { @@ -1413,22 +1511,52 @@ class SCAccountRepository implements SocialChatUserRepository { String userId, { String relationType = 'CP', }) async { - final query = {"userId": userId, "relationType": relationType}; - _cpDebugLog('cabin request query=$query'); - try { - final result = await http.get( - "/user/cp/cabin", - queryParams: query, - fromJson: (json) => SCCpCabinRes.fromJson(json), - ); - _cpDebugLog( - 'cabin response query=$query result=${_cpCabinSummary(result)}', - ); - return result; - } catch (error) { - _cpDebugLog('cabin failed query=$query error=$error'); - rethrow; + final normalizedRelationType = _normalizedCpRelationType(relationType); + final query = {"userId": userId, "relationType": normalizedRelationType}; + final cacheKey = _cpCabinCacheKey(userId, normalizedRelationType); + final cached = _freshCpCacheValue(_cpCabinCache, cacheKey); + if (cached != null) { + _cpDebugLog('cabin cache hit query=$query'); + return cached; } + final inFlight = _cpCabinInFlight[cacheKey]; + if (inFlight != null) { + _cpDebugLog('cabin join in-flight query=$query'); + return inFlight; + } + + _cpDebugLog('cabin request query=$query'); + final requestEpoch = _cpRelationCacheEpoch; + late final Future trackedRequest; + trackedRequest = http + .get( + "/user/cp/cabin", + queryParams: query, + fromJson: (json) => SCCpCabinRes.fromJson(json), + ) + .then((result) { + if (requestEpoch == _cpRelationCacheEpoch) { + _cpCabinCache[cacheKey] = _CpRequestCacheEntry( + result, + DateTime.now(), + ); + } + _cpDebugLog( + 'cabin response query=$query result=${_cpCabinSummary(result)}', + ); + return result; + }) + .catchError((error) { + _cpDebugLog('cabin failed query=$query error=$error'); + throw error; + }) + .whenComplete(() { + if (_cpCabinInFlight[cacheKey] == trackedRequest) { + _cpCabinInFlight.remove(cacheKey); + } + }); + _cpCabinInFlight[cacheKey] = trackedRequest; + return trackedRequest; } ///user/cp/cabin/value