Skip to content

Commit

Permalink
Merge pull request Expensify#32090 from anyongjitiger/main
Browse files Browse the repository at this point in the history
fix: Avatar changes when from offline to online
  • Loading branch information
Julesssss authored Dec 15, 2023
2 parents 121ca59 + fe5a7d9 commit ab138ee
Showing 1 changed file with 37 additions and 14 deletions.
51 changes: 37 additions & 14 deletions src/libs/UserUtils.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -13,6 +17,12 @@ type AvatarSource = React.FC<SvgProps> | string;

type LoginListIndicator = ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS> | '';

let allPersonalDetails: OnyxEntry<Record<string, PersonalDetails>>;
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.
*
Expand Down Expand Up @@ -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<SvgProps> {
function getDefaultAvatar(accountID = -1, avatarURL?: string): React.FC<SvgProps> {
if (accountID <= 0) {
return FallbackAvatar;
}
Expand All @@ -83,22 +100,35 @@ function getDefaultAvatar(accountID = -1): React.FC<SvgProps> {

// 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`;
}
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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
*/
Expand Down

0 comments on commit ab138ee

Please sign in to comment.