diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 29a43bcf711d..e95b62cc2437 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -1,9 +1,13 @@ import Str from 'expensify-common/lib/str'; +import _ from 'lodash'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import {SvgProps} from 'react-native-svg'; import {ValueOf} from 'type-fest'; import * as defaultAvatars from '@components/Icon/DefaultAvatars'; import {ConciergeAvatar, FallbackAvatar} from '@components/Icon/Expensicons'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {PersonalDetails} from '@src/types/onyx'; import Login from '@src/types/onyx/Login'; import hashCode from './hashCode'; @@ -13,6 +17,12 @@ type AvatarSource = React.FC | string; type LoginListIndicator = ValueOf | ''; +let allPersonalDetails: OnyxEntry>; +Onyx.connect({ + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + callback: (val) => (allPersonalDetails = _.isEmpty(val) ? {} : val), +}); + /** * Searches through given loginList for any contact method / login with an error. * @@ -68,12 +78,19 @@ function hashText(text: string, range: number): number { return Math.abs(hashCode(text.toLowerCase())) % range; } +/** + * Generate a random accountID base on searchValue. + */ +function generateAccountID(searchValue: string): number { + return hashText(searchValue, 2 ** 32); +} + /** * Helper method to return the default avatar associated with the given accountID * @param [accountID] * @returns */ -function getDefaultAvatar(accountID = -1): React.FC { +function getDefaultAvatar(accountID = -1, avatarURL?: string): React.FC { if (accountID <= 0) { return FallbackAvatar; } @@ -83,22 +100,35 @@ function getDefaultAvatar(accountID = -1): React.FC { // There are 24 possible default avatars, so we choose which one this user has based // on a simple modulo operation of their login number. Note that Avatar count starts at 1. - const accountIDHashBucket = ((accountID % CONST.DEFAULT_AVATAR_COUNT) + 1) as AvatarRange; + // When creating a chat, we generate an avatar using an ID and the backend response will modify the ID to the actual user ID. + // But the avatar link still corresponds to the original ID-generated link. So we extract the SVG image number from the backend's link instead of using the user ID directly + let accountIDHashBucket: AvatarRange; + if (avatarURL) { + const match = avatarURL.match(/(?<=default-avatar_)\d+(?=\.)/); + const lastDigit = match && parseInt(match[0], 10); + accountIDHashBucket = lastDigit as AvatarRange; + } else { + accountIDHashBucket = ((accountID % CONST.DEFAULT_AVATAR_COUNT) + 1) as AvatarRange; + } return defaultAvatars[`Avatar${accountIDHashBucket}`]; } /** * Helper method to return default avatar URL associated with login */ -function getDefaultAvatarURL(accountID: string | number = '', isNewDot = false): string { +function getDefaultAvatarURL(accountID: string | number = ''): string { if (Number(accountID) === CONST.ACCOUNT_ID.CONCIERGE) { return CONST.CONCIERGE_ICON_URL; } + // To ensure that the avatar remains unchanged and matches the one returned by the backend, + // utilize an optimistic ID generated from the email instead of directly using the user ID. + const email = allPersonalDetails?.[accountID]?.login; + const originalOptimisticAccountID = email ? generateAccountID(email) : accountID; // Note that Avatar count starts at 1 which is why 1 has to be added to the result (or else 0 would result in a broken avatar link) - const accountIDHashBucket = (Number(accountID) % (isNewDot ? CONST.DEFAULT_AVATAR_COUNT : CONST.OLD_DEFAULT_AVATAR_COUNT)) + 1; - const avatarPrefix = isNewDot ? `default-avatar` : `avatar`; + const accountIDHashBucket = (Number(originalOptimisticAccountID) % CONST.DEFAULT_AVATAR_COUNT) + 1; + const avatarPrefix = `default-avatar`; return `${CONST.CLOUDFRONT_URL}/images/avatars/${avatarPrefix}_${accountIDHashBucket}.png`; } @@ -135,7 +165,7 @@ function isDefaultAvatar(avatarSource?: AvatarSource): boolean { * @param accountID - the accountID of the user */ function getAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource { - return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID) : avatarSource; + return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID, avatarSource as string) : avatarSource; } /** @@ -146,7 +176,7 @@ function getAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource * @param accountID - the accountID of the user */ function getAvatarUrl(avatarURL: string, accountID: number): string { - return isDefaultAvatar(avatarURL) ? getDefaultAvatarURL(accountID, true) : avatarURL; + return isDefaultAvatar(avatarURL) ? getDefaultAvatarURL(accountID) : avatarURL; } /** @@ -184,13 +214,6 @@ function getSmallSizeAvatar(avatarSource: AvatarSource, accountID?: number): Ava return `${source.substring(0, lastPeriodIndex)}_128${source.substring(lastPeriodIndex)}`; } -/** - * Generate a random accountID base on searchValue. - */ -function generateAccountID(searchValue: string): number { - return hashText(searchValue, 2 ** 32); -} - /** * Gets the secondary phone login number */