import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart'; import 'package:yumi/shared/business_logic/models/req/sc_user_profile_cmd.dart'; import 'package:yumi/shared/business_logic/models/res/country_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_user_identity_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart'; import 'package:yumi/shared/data_sources/models/enum/sc_props_type.dart'; import 'package:yumi/shared/tools/sc_cp_relation_level_utils.dart'; import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart'; class SocialChatUserProfileManager extends ChangeNotifier { static const List _cpProfileRelationTypes = [ 'CP', 'BROTHER', 'SISTERS', ]; SCUserProfileCmd? editUser; void updateUserAvatar(String avatar) { editUser ??= SCUserProfileCmd(); editUser!.setUserAvatar = avatar; notifyListeners(); } void updateUserSex(num sex) { editUser ??= SCUserProfileCmd(); editUser!.setUserSex = sex; } void updateUserNickname(String nickname) { editUser ??= SCUserProfileCmd(); editUser!.setUserNickname = nickname; } void updateBornYear(num bornYear) { editUser ??= SCUserProfileCmd(); editUser!.setBornYear = bornYear; } void updateBornMonth(num bornMonth) { editUser ??= SCUserProfileCmd(); editUser!.setBornMonth = bornMonth; } void updateBornDay(num bornDay) { editUser ??= SCUserProfileCmd(); editUser!.setBornDay = bornDay; } void updateAge(num age) { editUser ??= SCUserProfileCmd(); editUser!.setAge = age; } void setCountry(Country country) { editUser ??= SCUserProfileCmd(); editUser!.setCountryCode = country.alphaTwo!; editUser!.setCountryId = country.id!; editUser!.setCountryName = country.countryName!; } void resetEditUserData() { editUser = null; notifyListeners(); } SocialChatUserProfile? get currentUserProfile => AccountStorage().getCurrentUser()?.userProfile; void syncCurrentUserProfile(SocialChatUserProfile? profile) { if (profile == null) { return; } final currentUser = AccountStorage().getCurrentUser(); if (currentUser != null) { currentUser.setUserProfile(profile); AccountStorage().setCurrentUser(currentUser); } if ((userProfile?.id ?? "").isNotEmpty && userProfile?.id == profile.id) { userProfile = profile; } notifyListeners(); } void syncCurrentUserChatBubble( PropsResources? chatBubble, { String? expireTime, }) { final profile = currentUserProfile; if (profile == null) { return; } final useProps = [ ...?profile.useProps?.where( (item) => item.propsResources?.type != SCPropsType.CHAT_BUBBLE.name, ), ]; if (chatBubble != null) { useProps.add( UseProps( expireTime: expireTime ?? "4102444800000", propsResources: chatBubble, userId: profile.id, ), ); } syncCurrentUserProfile(profile.copyWith(useProps: useProps)); } Future fetchUserProfileData({ bool loadGuardCount = true, bool refreshFamilyData = false, }) async { var us = AccountStorage().getCurrentUser(); String userId = us?.userProfile?.id ?? ""; if (userId.isEmpty) { return; } var userInfo = await SCAccountRepository().loadUserInfo(userId); userInfo = await _withCurrentVipLevel(userInfo, userId); userInfo = await _withCpCabinProfiles(userInfo, userId); syncCurrentUserProfile(userInfo); } SCUserIdentityRes? userIdentity; void getUserIdentity() async { if (kDebugMode) { final profile = AccountStorage().getCurrentUser()?.userProfile; debugPrint( '[SCUserIdentity] request start userId=${profile?.id}, account=${profile?.account}', ); } try { userIdentity = await SCAccountRepository().userIdentity(); if (kDebugMode) { debugPrint( '[SCUserIdentity] manager stored admin=${userIdentity?.admin}, ' 'manager=${userIdentity?.manager}, yumiManager=${userIdentity?.yumiManager}', ); } notifyListeners(); } catch (error) { if (kDebugMode) { debugPrint('[SCUserIdentity] request failed error=$error'); } } } /// 当前正在查看的个人详情资料,不用于承载登录用户自己的 Me 页数据。 SocialChatUserProfile? userProfile; Object? userProfileLoadError; bool isUserProfileLoading = false; int _userInfoRequestSerial = 0; Future getUserInfoById(String userId) { return _loadUserInfoById(userId, clearBeforeLoad: true); } Future reloadUserInfoById(String userId) { return _loadUserInfoById(userId, clearBeforeLoad: false); } Future _loadUserInfoById( String userId, { required bool clearBeforeLoad, }) async { final requestSerial = ++_userInfoRequestSerial; final normalizedUserId = userId.trim(); if (clearBeforeLoad) { userProfile = null; } userProfileLoadError = null; isUserProfileLoading = true; notifyListeners(); if (normalizedUserId.isEmpty) { if (requestSerial == _userInfoRequestSerial) { userProfileLoadError = ArgumentError('userId is empty'); isUserProfileLoading = false; notifyListeners(); } return; } try { final loadedProfile = await SCAccountRepository().loadUserInfo( normalizedUserId, ); if (requestSerial != _userInfoRequestSerial) { return; } userProfile = loadedProfile; userProfileLoadError = null; isUserProfileLoading = false; notifyListeners(); unawaited( _hydrateUserInfoById( profile: loadedProfile, userId: normalizedUserId, requestSerial: requestSerial, ), ); } catch (error, stackTrace) { if (requestSerial != _userInfoRequestSerial) { return; } userProfile = null; userProfileLoadError = error; _debugProfileLoadFailure( scope: 'detail', userId: normalizedUserId, error: error, stackTrace: stackTrace, ); } finally { if (requestSerial == _userInfoRequestSerial && isUserProfileLoading) { isUserProfileLoading = false; notifyListeners(); } } } ///房间资料卡 RoomUserCardRes? userCardInfo; Object? userCardLoadError; bool isUserCardLoading = false; bool isUserCardRelationLoading = false; int _userCardRequestSerial = 0; int _userCardSessionSerial = 0; int? _activeUserCardSessionSerial; // List? userCountGuardResList; int beginRoomUserCardSession() { final sessionSerial = ++_userCardSessionSerial; _activeUserCardSessionSerial = sessionSerial; return sessionSerial; } Future roomUserCard( String roomId, String userId, { int? sessionSerial, }) async { final activeSessionSerial = sessionSerial ?? beginRoomUserCardSession(); _activeUserCardSessionSerial = activeSessionSerial; final requestSerial = ++_userCardRequestSerial; final normalizedRoomId = roomId.trim(); final normalizedUserId = userId.trim(); userCardInfo = null; userCardLoadError = null; isUserCardLoading = true; isUserCardRelationLoading = false; notifyListeners(); if (normalizedUserId.isEmpty) { if (requestSerial == _userCardRequestSerial) { userCardLoadError = ArgumentError('userId is empty'); isUserCardLoading = false; notifyListeners(); } return null; } try { final card = await _loadHydratedRoomUserCard( roomId: normalizedRoomId, userId: normalizedUserId, ); if (requestSerial != _userCardRequestSerial || activeSessionSerial != _activeUserCardSessionSerial) { return null; } userCardInfo = card; userCardLoadError = null; isUserCardLoading = false; isUserCardRelationLoading = false; notifyListeners(); return card; } catch (error, stackTrace) { if (requestSerial != _userCardRequestSerial) { return null; } userCardInfo = null; userCardLoadError = error; isUserCardRelationLoading = false; _debugProfileLoadFailure( scope: 'roomCard', userId: normalizedUserId, error: error, stackTrace: stackTrace, ); } finally { if (requestSerial == _userCardRequestSerial && isUserCardLoading) { isUserCardLoading = false; notifyListeners(); } } return null; } Future preloadRoomUserCardForOpen( String roomId, String userId, { int? sessionSerial, }) { return roomUserCard(roomId, userId, sessionSerial: sessionSerial); } Future _loadHydratedRoomUserCard({ required String roomId, required String userId, }) async { final card = await SCChatRoomRepository().roomUserCard(roomId, userId); return _withCurrentVipLevelForCard(card, userId); } Future _hydrateUserInfoById({ required SocialChatUserProfile profile, required String userId, required int requestSerial, }) async { try { var mergedProfile = await _withCurrentVipLevel(profile, userId); mergedProfile = await _withCpCabinProfiles(mergedProfile, userId); if (requestSerial != _userInfoRequestSerial) { return; } userProfile = mergedProfile; userProfileLoadError = null; notifyListeners(); } catch (error, stackTrace) { _debugProfileLoadFailure( scope: 'detailHydrate', userId: userId, error: error, stackTrace: stackTrace, ); } } void clearRoomUserCard({int? sessionSerial}) { if (sessionSerial != null && sessionSerial != _activeUserCardSessionSerial) { return; } _userCardRequestSerial++; _activeUserCardSessionSerial = null; userCardInfo = null; userCardLoadError = null; isUserCardLoading = false; isUserCardRelationLoading = false; } Future _withCurrentVipLevelForCard( RoomUserCardRes? card, String userId, ) async { final profile = card?.userProfile; if (card == null || profile == null) { return card; } var mergedProfile = await _withCurrentVipLevel(profile, userId); mergedProfile = await _withCpCabinProfiles(mergedProfile, userId); if (mergedProfile == profile) { return card; } return card.copyWith(userProfile: mergedProfile); } void _debugProfileLoadFailure({ required String scope, required String userId, required Object error, required StackTrace stackTrace, }) { if (kDebugMode) { debugPrint( '[ProfileLoad][$scope] failed userId=$userId error=$error\n$stackTrace', ); } } Future _withCpCabinProfiles( SocialChatUserProfile profile, String userId, ) async { if (userId.trim().isEmpty) { return profile; } final fallbackCpPairs = []; final fallbackCloseFriendPairs = []; _addCpRelationPairs( cpPairs: fallbackCpPairs, closeFriendPairs: fallbackCloseFriendPairs, relations: profile.cpList, fallbackRelationType: 'CP', ); _addCpRelationPairs( cpPairs: fallbackCpPairs, closeFriendPairs: fallbackCloseFriendPairs, relations: profile.closeFriendList, ); final cpPairs = []; final closeFriendPairs = []; _addCpRelationPairs( cpPairs: cpPairs, closeFriendPairs: closeFriendPairs, relations: fallbackCpPairs, ); _addCpRelationPairs( cpPairs: cpPairs, closeFriendPairs: closeFriendPairs, relations: fallbackCloseFriendPairs, ); var loadedAnyCabin = false; if (kDebugMode) { final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? ""; debugPrint( '[CP][Profile] source userId=$userId currentUserId=$currentUserId ' 'profileCp=${profile.cpList?.length ?? 0} ' 'profileCloseFriends=${profile.closeFriendList?.length ?? 0}', ); } await Future.wait( _cpProfileRelationTypes.map((relationType) async { final pairs = await _safeLoadCpRelationshipPairs(userId, relationType); _addCpRelationPairs( cpPairs: cpPairs, closeFriendPairs: closeFriendPairs, relations: pairs, fallbackRelationType: relationType, ); }), ); await Future.wait( _cpProfileRelationTypes.map((relationType) async { final cabin = await _safeLoadCpCabin(userId, relationType); if (cabin != null) { loadedAnyCabin = true; } final cpPair = cabin?.cpPairUserProfileCO; if (cpPair == null || !_hasCpPair(cpPair)) { return; } final normalizedType = _normalizeCpRelationType( cpPair.relationType ?? relationType, ); final cabinLevelText = scCpRelationLevelTextFromCabin( cabin, fallbackText: cpPair.levelText, ); final rightsLevelText = await _safeLoadCurrentUserCpLevelText( userId: userId, cpPair: cpPair, relationType: normalizedType, fallbackText: cabinLevelText, ); final enrichedPair = cpPair.copyWith( relationType: normalizedType, cpValue: cpPair.cpValue ?? cabin?.cpVal?.toDouble(), levelText: rightsLevelText ?? cabinLevelText, dismissCost: cpPair.dismissCost ?? _cpDismissCostFromCabin(cabin), ); _addCpRelationPair( cpPairs: cpPairs, closeFriendPairs: closeFriendPairs, relation: enrichedPair, fallbackRelationType: normalizedType, ); }), ); if (kDebugMode) { debugPrint( '[CP][Profile] hydrate userId=$userId cp=${cpPairs.length} ' 'closeFriends=${closeFriendPairs.length}', ); } if (!loadedAnyCabin) { return profile.copyWith( cpList: cpPairs, closeFriendList: closeFriendPairs, isCpRelation: cpPairs.isNotEmpty, ); } return profile.copyWith( cpList: cpPairs, closeFriendList: closeFriendPairs, isCpRelation: cpPairs.isNotEmpty, ); } void _addCpRelationPairs({ required List cpPairs, required List closeFriendPairs, required List? relations, String? fallbackRelationType, }) { for (final relation in relations ?? const []) { _addCpRelationPair( cpPairs: cpPairs, closeFriendPairs: closeFriendPairs, relation: relation, fallbackRelationType: fallbackRelationType, ); } } void _addCpRelationPair({ required List cpPairs, required List closeFriendPairs, required CPRes relation, String? fallbackRelationType, }) { if (!_hasCpPair(relation)) { return; } final relationTypeSource = (relation.relationType?.trim().isNotEmpty ?? false) ? relation.relationType : fallbackRelationType; if ((relationTypeSource ?? "").trim().isEmpty) { return; } final normalizedType = _normalizeCpRelationType(relationTypeSource); final normalizedRelation = relation.copyWith(relationType: normalizedType); final target = normalizedType == 'CP' ? cpPairs : closeFriendPairs; final relationKey = _cpRelationPairKey(normalizedRelation); final existingIndex = target.indexWhere( (item) => _cpRelationPairKey(item) == relationKey, ); if (existingIndex >= 0) { target[existingIndex] = normalizedRelation; } else { target.add(normalizedRelation); } } String _cpRelationPairKey(CPRes relation) { final type = _normalizeCpRelationType(relation.relationType); final userIds = [ relation.meUserId?.trim() ?? "", relation.cpUserId?.trim() ?? "", ]..removeWhere((value) => value.isEmpty); if (userIds.length >= 2) { userIds.sort(); return '$type|id|${userIds.join("|")}'; } final accounts = [ relation.meAccount?.trim() ?? "", relation.cpAccount?.trim() ?? "", ]..removeWhere((value) => value.isEmpty); if (accounts.length >= 2) { accounts.sort(); return '$type|account|${accounts.join("|")}'; } return [ type, relation.meUserId?.trim() ?? "", relation.meAccount?.trim() ?? "", relation.cpUserId?.trim() ?? "", relation.cpAccount?.trim() ?? "", relation.cpUserNickname?.trim() ?? "", relation.meUserNickname?.trim() ?? "", ].join('|'); } Future> _safeLoadCpRelationshipPairs( String userId, String relationType, ) async { try { final pairs = await SCAccountRepository().cpRelationshipPairs( relationType: relationType, userId: userId, ); if (kDebugMode) { debugPrint( '[CP][Profile] pair loaded userId=$userId relationType=$relationType ' 'count=${pairs.length}', ); } return pairs; } catch (error) { if (kDebugMode) { debugPrint( '[CP][Profile] pair failed userId=$userId relationType=$relationType error=$error', ); } return const []; } } Future _safeLoadCpCabin( String userId, String relationType, ) async { try { final cabin = await SCAccountRepository().cpCabin( userId, relationType: relationType, ); if (kDebugMode) { debugPrint( '[CP][Profile] cabin loaded userId=$userId relationType=$relationType ' 'hasPair=${_hasCpPair(cabin.cpPairUserProfileCO)} cpVal=${cabin.cpVal}', ); } return cabin; } catch (error) { if (kDebugMode) { debugPrint( '[CP][Profile] cabin failed userId=$userId ' 'relationType=$relationType error=$error', ); } return null; } } Future _safeLoadCurrentUserCpLevelText({ required String userId, required CPRes cpPair, required String relationType, String? fallbackText, }) async { final partnerUserId = _cpPartnerUserIdForCurrentUser(cpPair); if (partnerUserId.isEmpty) { return null; } try { final config = await SCAccountRepository().cpRightsConfig( relationType: relationType, cpUserId: partnerUserId, ); return scCpRelationLevelTextFromRightsConfig( config, fallbackText: fallbackText, ); } catch (error) { if (kDebugMode) { debugPrint( '[CP][Profile] rightsConfig failed userId=$userId ' 'partnerUserId=$partnerUserId relationType=$relationType ' 'error=$error', ); } return null; } } String _cpPartnerUserIdForCurrentUser(CPRes cpPair) { final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id?.trim() ?? ""; if (currentUserId.isEmpty) { return ""; } final meUserId = cpPair.meUserId?.trim() ?? ""; final cpUserId = cpPair.cpUserId?.trim() ?? ""; if (currentUserId == meUserId) { return cpUserId; } if (currentUserId == cpUserId) { return meUserId; } return ""; } bool _hasCpPair(CPRes? pair) { return (pair?.cpUserId?.trim().isNotEmpty ?? false) || (pair?.cpUserAvatar?.trim().isNotEmpty ?? false) || (pair?.cpUserNickname?.trim().isNotEmpty ?? false) || (pair?.meUserId?.trim().isNotEmpty ?? false) || (pair?.meUserAvatar?.trim().isNotEmpty ?? false) || (pair?.meUserNickname?.trim().isNotEmpty ?? false); } String _normalizeCpRelationType(String? relationType) { return scNormalizeCpRelationType(relationType); } int? _cpDismissCostFromCabin(SCCpCabinRes? cabin) { final directCost = cabin?.dismissCost; if (directCost != null) { return directCost; } final config = cabin?.currentCpCabinConfigCO; if (config is! Map) { return null; } return _cpIntValue( _firstCpConfigValue(config, const [ "dismissCost", "dismissGold", "dismissCoins", "dismissCoin", "cancelCost", "cancelGold", "removeCost", "partWaysCost", "consumeGold", "goldCost", "cost", "coins", "priceGold", ]), ); } dynamic _firstCpConfigValue(Map source, List keys) { for (final key in keys) { if (source.containsKey(key) && source[key] != null) { return source[key]; } } return null; } int? _cpIntValue(dynamic value) { if (value is int) { return value; } if (value is num) { return value.toInt(); } return int.tryParse(value?.toString() ?? ""); } Future _withCurrentVipLevel( SocialChatUserProfile profile, String userId, ) async { if (!_isCurrentUserId(userId)) { return profile; } final profileVipLevel = profile.vipLevel?.trim(); if (profileVipLevel != null && profileVipLevel.isNotEmpty && profile.getHeaddress() != null) { return profile; } final vipStatus = await _loadCurrentVipStatus(); final currentVipLevel = currentUserProfile?.vipLevel?.trim(); final resolvedVipLevel = profileVipLevel != null && profileVipLevel.isNotEmpty ? profileVipLevel : currentVipLevel != null && currentVipLevel.isNotEmpty ? currentVipLevel : _vipLevelFromStatus(vipStatus); var nextProfile = profile; if (resolvedVipLevel != null && resolvedVipLevel.isNotEmpty) { nextProfile = nextProfile.copyWith(vipLevel: resolvedVipLevel); } return socialChatProfileWithAvatarFrameResource( nextProfile, _avatarFramePropsFromVipStatus(vipStatus), expireTime: _expireTimeFromVipStatus(vipStatus), ); } bool _isCurrentUserId(String userId) { final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id?.trim(); final normalizedUserId = userId.trim(); return currentUserId != null && currentUserId.isNotEmpty && currentUserId == normalizedUserId; } Future _loadCurrentVipStatus() async { final repository = SCVipRepositoryImp(); try { final home = await repository.vipHome(); final state = home.state; if (state != null && state.levelInt > 0) { return state; } } catch (_) {} try { return repository.vipStatus(); } catch (_) { return null; } } PropsResources? _avatarFramePropsFromVipStatus(SCVipStatusRes? status) { if (status?.isActive != true) { return null; } final resource = status?.avatarFrame; return socialChatAvatarFrameProps( id: resource?.resourceId, name: resource?.name, sourceUrl: resource?.sourceResourceUrl, cover: resource?.coverUrl ?? resource?.cover, code: "VIP_AVATAR_FRAME", ); } String? _expireTimeFromVipStatus(SCVipStatusRes? status) { final expireAt = status?.expireAt?.trim(); if (expireAt == null || expireAt.isEmpty) { return null; } final normalized = expireAt.contains("T") ? expireAt : expireAt.replaceFirst(" ", "T"); return DateTime.tryParse(normalized)?.millisecondsSinceEpoch.toString(); } String? _vipLevelFromStatus(SCVipStatusRes? status) { if (status == null) { return null; } if (status.active == false) { return '0'; } final level = status.levelInt; return level > 0 ? level.toString() : '0'; } void followUser(String userId) async { var result = await SCAccountRepository().followUser(userId); if (result) { if (userCardInfo != null) { userCardInfo = userCardInfo?.copyWith(follow: !userCardInfo!.follow!); notifyListeners(); } } } double myBalance = 0.0; void balance() async { myBalance = await SCAccountRepository().balance(); notifyListeners(); } updateBalance(double m) { myBalance = m; notifyListeners(); } void inviteHostOpt(BuildContext ct, String id, String status) async { final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful; try { await SCAccountRepository().inviteHost(id, status); getUserIdentity(); SCTts.show(successMessage); } catch (e) {} } void inviteAgentOpt(BuildContext ct, String id, String status) async { final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful; try { await SCAccountRepository().inviteAgent(id, status); getUserIdentity(); SCTts.show(successMessage); } catch (e) {} } void inviteBDOpt(BuildContext ct, String id, String status) async { final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful; try { await SCAccountRepository().inviteBD(id, status); getUserIdentity(); SCTts.show(successMessage); } catch (e) {} } void inviteBDLeader(BuildContext ct, String id, String status) async { final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful; try { await SCAccountRepository().inviteBDLeader(id, status); getUserIdentity(); SCTts.show(successMessage); } catch (e) {} } void inviteRechargeAgent(BuildContext ct, String id, String status) async { final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful; try { await SCAccountRepository().inviteRechargeAgent(id, status); getUserIdentity(); SCTts.show(successMessage); } catch (e) {} } Future cpRlationshipProcessApply( BuildContext ct, String id, bool status, ) async { final successMessage = SCAppLocalizations.of(ct)!.operationSuccessful; try { await SCAccountRepository().cpRelationshipProcessApply(id, status); await fetchUserProfileData(loadGuardCount: false); await refreshLoadedCpProfiles(); SCTts.show(successMessage); return true; } catch (e) { return false; } } Future refreshLoadedCpProfiles() async { await _refreshLoadedCpProfilesForUserIds(null); } Future refreshLoadedCpProfilesForUsers(Set userIds) async { final normalizedUserIds = userIds.map((userId) => userId.trim()).where((userId) { return userId.isNotEmpty; }).toSet(); if (normalizedUserIds.isEmpty) { return; } await _refreshLoadedCpProfilesForUserIds(normalizedUserIds); } void removeLoadedCpRelation({ required String userId, required String peerUserId, required String relationType, }) { final normalizedUserId = userId.trim(); final normalizedPeerUserId = peerUserId.trim(); final normalizedRelationType = _normalizeCpRelationType(relationType); if (normalizedUserId.isEmpty || normalizedPeerUserId.isEmpty || normalizedRelationType.isEmpty) { return; } var changed = false; final currentProfile = currentUserProfile; if (currentProfile != null) { final nextCurrentProfile = _profileWithoutCpRelation( currentProfile, userId: normalizedUserId, peerUserId: normalizedPeerUserId, relationType: normalizedRelationType, ); if (!identical(nextCurrentProfile, currentProfile)) { final currentUser = AccountStorage().getCurrentUser(); currentUser?.setUserProfile(nextCurrentProfile); if (currentUser != null) { AccountStorage().setCurrentUser(currentUser); } changed = true; } } final detailProfile = userProfile; if (detailProfile != null) { final nextDetailProfile = _profileWithoutCpRelation( detailProfile, userId: normalizedUserId, peerUserId: normalizedPeerUserId, relationType: normalizedRelationType, ); if (!identical(nextDetailProfile, detailProfile)) { userProfile = nextDetailProfile; changed = true; } } final cardProfile = userCardInfo?.userProfile; if (cardProfile != null) { final nextCardProfile = _profileWithoutCpRelation( cardProfile, userId: normalizedUserId, peerUserId: normalizedPeerUserId, relationType: normalizedRelationType, ); if (!identical(nextCardProfile, cardProfile)) { userCardInfo = userCardInfo?.copyWith(userProfile: nextCardProfile); changed = true; } } if (changed) { notifyListeners(); } } SocialChatUserProfile _profileWithoutCpRelation( SocialChatUserProfile profile, { required String userId, required String peerUserId, required String relationType, }) { var changed = false; List? filterRelations(List? relations) { if (relations == null) { return null; } final filtered = relations.where((relation) { return !_matchesCpRelation( relation, userId: userId, peerUserId: peerUserId, relationType: relationType, ); }).toList(); if (filtered.length != relations.length) { changed = true; } return filtered; } final nextCpList = filterRelations(profile.cpList); final nextCloseFriendList = filterRelations(profile.closeFriendList); if (!changed) { return profile; } return profile.copyWith( cpList: nextCpList, closeFriendList: nextCloseFriendList, isCpRelation: nextCpList?.isNotEmpty == true, ); } bool _matchesCpRelation( CPRes relation, { required String userId, required String peerUserId, required String relationType, }) { if (_normalizeCpRelationType(relation.relationType) != relationType) { return false; } final relationUserIds = { relation.meUserId?.trim() ?? "", relation.cpUserId?.trim() ?? "", }..remove(""); return relationUserIds.contains(userId) && relationUserIds.contains(peerUserId); } Future _refreshLoadedCpProfilesForUserIds(Set? userIds) async { String refreshedUserId(String? userId) { final normalizedUserId = userId?.trim() ?? ""; if (normalizedUserId.isEmpty) { return ""; } return userIds == null || userIds.contains(normalizedUserId) ? normalizedUserId : ""; } final currentProfile = currentUserProfile; final currentUserId = refreshedUserId(currentProfile?.id); if (currentProfile != null && currentUserId.isNotEmpty) { syncCurrentUserProfile( await _withCpCabinProfiles(currentProfile, currentUserId), ); } final detailUserId = refreshedUserId(userProfile?.id); if (userProfile != null && detailUserId.isNotEmpty) { userProfile = await _withCpCabinProfiles(userProfile!, detailUserId); } final cardProfile = userCardInfo?.userProfile; final cardUserId = refreshedUserId(cardProfile?.id); if (userCardInfo != null && cardProfile != null && cardUserId.isNotEmpty) { userCardInfo = userCardInfo?.copyWith( userProfile: await _withCpCabinProfiles(cardProfile, cardUserId), ); } notifyListeners(); } }