From 441d4953d0e1513bce57d2450ce83b2335fdb6fc Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 29 Mar 2024 09:38:20 +0100 Subject: [PATCH 1/8] simplify avatar logic and remove unnecessary calls to getAvatar() fn --- src/components/Avatar.tsx | 12 +++- src/components/AvatarWithIndicator.tsx | 7 ++- src/components/MultipleAvatars.tsx | 2 + src/components/ReportActionItem/TaskView.tsx | 2 +- .../BaseUserDetailsTooltip/index.tsx | 4 +- src/libs/OptionsListUtils.ts | 59 +++++++++---------- src/libs/ReportUtils.ts | 29 ++++----- src/libs/SidebarUtils.ts | 3 +- src/libs/UserUtils.ts | 15 ++--- src/libs/actions/Report.ts | 2 - src/pages/DetailsPage.tsx | 4 +- src/pages/ProfilePage.tsx | 6 +- src/pages/ReportParticipantsPage.tsx | 7 +-- src/pages/RoomMembersPage.tsx | 19 +++--- .../report/ReactionList/BaseReactionList.tsx | 58 +++++++++--------- .../ReportActionCompose/SuggestionMention.tsx | 4 +- .../home/report/ReportActionItemSingle.tsx | 16 +++-- .../sidebar/ProfileAvatarWithIndicator.js | 0 src/pages/workspace/WorkspaceMembersPage.tsx | 4 +- .../members/WorkspaceMemberDetailsPage.tsx | 5 +- .../WorkspaceWorkflowsApproverPage.tsx | 4 +- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 4 +- src/types/onyx/OnyxCommon.ts | 2 +- 23 files changed, 139 insertions(+), 129 deletions(-) create mode 100644 src/pages/home/sidebar/ProfileAvatarWithIndicator.js diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 358f5333bfba..6937e281da7a 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -7,6 +7,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserUtils'; +import * as UserUtils from '@libs/UserUtils'; import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; import type {AvatarType} from '@src/types/onyx/OnyxCommon'; @@ -49,6 +50,9 @@ type AvatarProps = { /** Owner of the avatar. If user, displayName. If workspace, policy name */ name?: string; + + /** Optional account id if it's user avatar */ + accountID?: number; }; function Avatar({ @@ -62,6 +66,7 @@ function Avatar({ fallbackIconTestID = '', type = CONST.ICON_TYPE_AVATAR, name = '', + accountID, }: AvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -75,16 +80,17 @@ function Avatar({ }, [source]); const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE; - const iconSize = StyleUtils.getAvatarSize(size); + const isEmptySource = !source; + const iconSize = StyleUtils.getAvatarSize(size); const imageStyle: StyleProp = [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius]; const iconStyle = imageStyles ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined; // We pass the color styles down to the SVG for the workspace and fallback avatar. - const useFallBackAvatar = imageError || source === Expensicons.FallbackAvatar || !source; + const useFallBackAvatar = imageError || isEmptySource || source === Expensicons.FallbackAvatar; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; - const avatarSource = useFallBackAvatar ? fallbackAvatar : source; + const avatarSource = useFallBackAvatar ? fallbackAvatar : UserUtils.getAvatar(source, accountID) ?? Expensicons.FallbackAvatar; let iconColors; if (isWorkspace) { diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index 42b91b3d2d71..ea29f41b0d80 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -13,6 +13,9 @@ type AvatarWithIndicatorProps = { /** URL for the avatar */ source: UserUtils.AvatarSource; + /** account id if it's user avatar */ + accountID?: number; + /** To show a tooltip on hover */ tooltipText?: string; @@ -23,7 +26,7 @@ type AvatarWithIndicatorProps = { isLoading?: boolean; }; -function AvatarWithIndicator({source, tooltipText = '', fallbackIcon = Expensicons.FallbackAvatar, isLoading = true}: AvatarWithIndicatorProps) { +function AvatarWithIndicator({source, accountID, tooltipText = '', fallbackIcon = Expensicons.FallbackAvatar, isLoading = true}: AvatarWithIndicatorProps) { const styles = useThemeStyles(); return ( @@ -35,7 +38,7 @@ function AvatarWithIndicator({source, tooltipText = '', fallbackIcon = Expensico <> diff --git a/src/components/MultipleAvatars.tsx b/src/components/MultipleAvatars.tsx index dedaba500a9c..31d3d35af58d 100644 --- a/src/components/MultipleAvatars.tsx +++ b/src/components/MultipleAvatars.tsx @@ -158,6 +158,7 @@ function MultipleAvatars({ name={icons[0].name} type={icons[0].type} fallbackIcon={icons[0].fallbackIcon} + accountID={icons[0].id} /> @@ -207,6 +208,7 @@ function MultipleAvatars({ name={icon.name} type={icon.type} fallbackIcon={icon.fallbackIcon} + accountID={icon.id} /> diff --git a/src/components/ReportActionItem/TaskView.tsx b/src/components/ReportActionItem/TaskView.tsx index 9711e126907f..e3e07ab0d7ad 100644 --- a/src/components/ReportActionItem/TaskView.tsx +++ b/src/components/ReportActionItem/TaskView.tsx @@ -156,7 +156,7 @@ function TaskView({report, shouldShowHorizontalRule, ...props}: TaskViewProps) { {title} diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index b2e243053bbc..4ea33940aa9a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -7,6 +7,7 @@ import lodashSet from 'lodash/set'; import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import type {SelectedTagOption} from '@components/TagPicker'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -303,17 +304,24 @@ function getAvatarsForAccountIDs(accountIDs: number[], personalDetails: OnyxEntr Object.entries(defaultValues).forEach((item) => { reversedDefaultValues[item[1]] = item[0]; }); - return accountIDs.map((accountID) => { - const login = reversedDefaultValues[accountID] ?? ''; - const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID, avatar: ''}; - return { - id: accountID, - source: UserUtils.getAvatar(userPersonalDetail.avatar, userPersonalDetail.accountID), - type: CONST.ICON_TYPE_AVATAR, - name: userPersonalDetail.login ?? '', - }; - }); + return accountIDs + .map((accountID) => { + const login = reversedDefaultValues[accountID] ?? ''; + const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID, avatar: ''}; + + if (!userPersonalDetail.avatar) { + return; + } + + return { + id: accountID, + source: userPersonalDetail.avatar, + type: CONST.ICON_TYPE_AVATAR, + name: userPersonalDetail.login ?? '', + }; + }) + .filter((icon): icon is OnyxCommon.Icon => icon !== undefined); } /** @@ -332,9 +340,7 @@ function getPersonalDetailsForAccountIDs(accountIDs: number[] | undefined, perso } let personalDetail: OnyxEntry = personalDetails[accountID]; if (!personalDetail) { - personalDetail = { - avatar: UserUtils.getDefaultAvatar(cleanAccountID), - } as PersonalDetails; + personalDetail = {} as PersonalDetails; } if (cleanAccountID === CONST.ACCOUNT_ID.CONCIERGE) { @@ -363,6 +369,7 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const login = detail?.login || participant.login || ''; const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail, LocalePhoneNumber.formatPhoneNumber(login)); + return { keyForList: String(detail?.accountID), login, @@ -373,7 +380,7 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant alternateText: LocalePhoneNumber.formatPhoneNumber(login) || displayName, icons: [ { - source: UserUtils.getAvatar(detail?.avatar ?? '', detail?.accountID ?? -1), + source: detail?.avatar ?? FallbackAvatar, name: login, type: CONST.ICON_TYPE_AVATAR, id: detail?.accountID, @@ -757,13 +764,7 @@ function createOption( // Disabling this line for safeness as nullish coalescing works only if the value is undefined or null // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing result.searchText = getSearchText(report, reportName, personalDetailList, !!result.isChatRoom || !!result.isPolicyExpenseChat, !!result.isThread); - result.icons = ReportUtils.getIcons( - report, - personalDetails, - UserUtils.getAvatar(personalDetail?.avatar ?? '', personalDetail?.accountID), - personalDetail?.login, - personalDetail?.accountID, - ); + result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, personalDetail?.login, personalDetail?.accountID); result.subtitle = subtitle; return result; @@ -1851,7 +1852,6 @@ function getOptions( [optimisticAccountID]: { accountID: optimisticAccountID, login: searchValue, - avatar: UserUtils.getDefaultAvatar(optimisticAccountID), }, }; userToInvite = createOption([optimisticAccountID], personalDetailsExtended, null, reportActions, { @@ -1864,10 +1864,10 @@ function getOptions( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing userToInvite.alternateText = userToInvite.alternateText || searchValue; - // If user doesn't exist, use a default avatar + // If user doesn't exist, use a fallback avatar userToInvite.icons = [ { - source: UserUtils.getAvatar('', optimisticAccountID), + source: FallbackAvatar, name: searchValue, type: CONST.ICON_TYPE_AVATAR, }, @@ -1941,17 +1941,12 @@ function getShareLogOptions(options: OptionList, searchValue = '', betas: Beta[] */ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | EmptyObject, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); + const icons = personalDetail.avatar ? [{source: personalDetail.avatar, name: personalDetail.login ?? '', type: CONST.ICON_TYPE_AVATAR, id: personalDetail.accountID}] : []; + return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), alternateText: formattedLogin || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), - icons: [ - { - source: UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), - name: personalDetail.login ?? '', - type: CONST.ICON_TYPE_AVATAR, - id: personalDetail.accountID, - }, - ], + icons, descriptiveText: amountText ?? '', login: personalDetail.login ?? '', accountID: personalDetail.accountID, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b03abbcdf9bb..695da35555eb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11,6 +11,7 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import * as Expensicons from '@components/Icon/Expensicons'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import CONST from '@src/CONST'; @@ -1629,7 +1630,7 @@ function getIconsForParticipants(participants: number[], personalDetails: OnyxCo const participantsList = participants || []; for (const accountID of participantsList) { - const avatarSource = UserUtils.getAvatar(personalDetails?.[accountID]?.avatar ?? '', accountID); + const avatarSource = personalDetails?.[accountID]?.avatar ?? FallbackAvatar; const displayNameLogin = personalDetails?.[accountID]?.displayName ? personalDetails?.[accountID]?.displayName : personalDetails?.[accountID]?.login; participantDetails.push([accountID, displayNameLogin ?? '', avatarSource, personalDetails?.[accountID]?.fallbackIcon ?? '']); } @@ -1690,12 +1691,12 @@ function getPersonalDetailsForAccountID(accountID: number): Partial = {}; const visibleReportActionItems: ReportActions = {}; @@ -414,7 +413,7 @@ function getOptionData({ result.subtitle = subtitle; result.participantsList = participantPersonalDetailList; - result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail?.avatar ?? {}, personalDetail?.accountID), '', -1, policy); + result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, '', -1, policy); result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index ce7e4963afc7..ee4a9c7a275f 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as defaultAvatars from '@components/Icon/DefaultAvatars'; -import {ConciergeAvatar, FallbackAvatar, NotificationsAvatar} from '@components/Icon/Expensicons'; +import {ConciergeAvatar, NotificationsAvatar} from '@components/Icon/Expensicons'; import CONST from '@src/CONST'; import type {LoginList} from '@src/types/onyx'; import type Login from '@src/types/onyx/Login'; @@ -82,10 +82,7 @@ function generateAccountID(searchValue: string): number { * @param [accountID] * @returns */ -function getDefaultAvatar(accountID = -1, avatarURL?: string): IconAsset { - if (accountID <= 0) { - return FallbackAvatar; - } +function getDefaultAvatar(accountID = -1, avatarURL?: string): IconAsset | undefined { if (Number(accountID) === CONST.ACCOUNT_ID.CONCIERGE) { return ConciergeAvatar; } @@ -155,7 +152,7 @@ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | * @param avatarSource - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ -function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource { +function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource | undefined { return isDefaultAvatar(avatarSource) ? getDefaultAvatar(accountID, avatarSource) : avatarSource; } @@ -163,7 +160,7 @@ function getAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSourc * Provided an avatar URL, if avatar is a default avatar, return NewDot default avatar URL. * Otherwise, return the URL pointing to a user-uploaded avatar. * - * @param avatarURL - the avatar source from user's personalDetails + * @param avatarSource - the avatar source from user's personalDetails * @param accountID - the accountID of the user */ function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number): AvatarSource { @@ -174,7 +171,7 @@ function getAvatarUrl(avatarSource: AvatarSource | undefined, accountID: number) * Avatars uploaded by users will have a _128 appended so that the asset server returns a small version. * This removes that part of the URL so the full version of the image can load. */ -function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number): AvatarSource { +function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: number): AvatarSource | undefined { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; @@ -186,7 +183,7 @@ function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: n * Small sized avatars end with _128.. This adds the _128 at the end of the * source URL (before the file type) if it doesn't exist there already. */ -function getSmallSizeAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource { +function getSmallSizeAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource | undefined { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index ac7edc38980d..c6c232c097a9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -68,7 +68,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import {doesReportBelongToWorkspace} from '@libs/ReportUtils'; import type {OptimisticAddCommentReportAction} from '@libs/ReportUtils'; import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation'; -import * as UserUtils from '@libs/UserUtils'; import Visibility from '@libs/Visibility'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; @@ -766,7 +765,6 @@ function openReport( optimisticPersonalDetails[accountID] = allPersonalDetails?.[accountID] ?? { login, accountID, - avatar: UserUtils.getDefaultAvatarURL(accountID), displayName: login, isOptimisticPersonalDetail: true, }; diff --git a/src/pages/DetailsPage.tsx b/src/pages/DetailsPage.tsx index 49b3e856c65d..338e51cb408e 100755 --- a/src/pages/DetailsPage.tsx +++ b/src/pages/DetailsPage.tsx @@ -70,7 +70,6 @@ function DetailsPage({personalDetails, route, session}: DetailsPageProps) { accountID: optimisticAccountID, login, displayName: login, - avatar: UserUtils.getDefaultAvatar(optimisticAccountID), }; } @@ -115,9 +114,10 @@ function DetailsPage({personalDetails, route, session}: DetailsPageProps) { diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 6ad3860132d2..d2967de8360a 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -25,7 +25,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {parsePhoneNumber} from '@libs/PhoneNumber'; import * as ReportUtils from '@libs/ReportUtils'; -import * as UserUtils from '@libs/UserUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import type {ProfileNavigatorParamList} from '@navigation/types'; import * as PersonalDetailsActions from '@userActions/PersonalDetails'; @@ -97,7 +96,7 @@ function ProfilePage({route}: ProfilePageProps) { const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details, undefined, undefined, isCurrentUser); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const avatar = details?.avatar || UserUtils.getDefaultAvatar(); // we can have an empty string and in this case, we need to show the default avatar + const avatar = details.avatar; const fallbackIcon = details?.fallbackIcon ?? ''; const login = details?.login ?? ''; const timezone = details?.timezone; @@ -163,9 +162,10 @@ function ProfilePage({route}: ProfilePageProps) { diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index f635499a286f..b69eb88b3244 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -26,7 +26,6 @@ import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -93,14 +92,14 @@ function ReportParticipantsPage({report, personalDetails, session}: ReportPartic alternateText: formatPhoneNumber(details?.login ?? ''), rightElement: roleBadge, pendingAction: pendingChatMember?.pendingAction, - icons: [ + icons: details?.avatar ? [ { - source: UserUtils.getAvatar(details?.avatar, accountID), + source: details.avatar, name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, }, - ], + ] : [], }); }); diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index d025a3bde265..a3005561b0ca 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -26,7 +26,6 @@ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import * as UserUtils from '@libs/UserUtils'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -207,14 +206,16 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { isDisabled: accountID === session?.accountID, text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), alternateText: details?.login ? formatPhoneNumber(details.login) : '', - icons: [ - { - source: UserUtils.getAvatar(details.avatar, accountID), - name: details.login ?? '', - type: CONST.ICON_TYPE_AVATAR, - id: Number(accountID), - }, - ], + icons: details.avatar + ? [ + { + source: details.avatar, + name: details.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + id: Number(accountID), + }, + ] + : [], pendingAction: pendingChatMember?.pendingAction, }); }); diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index aaa8ca132ee0..996868b6fd6c 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -7,7 +7,6 @@ import OptionRow from '@components/OptionRow'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; -import * as UserUtils from '@libs/UserUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -54,33 +53,38 @@ function BaseReactionList({hasUserReacted = false, users, isVisible = false, emo * so that the sticky headers function properly * */ - const renderItem: FlatListProps['renderItem'] = ({item}) => ( - { - onClose?.(); + const renderItem: FlatListProps['renderItem'] = ({item}) => { + const icons = item.avatar + ? [ + { + id: item.accountID, + source: item.avatar, + name: item.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + }, + ] + : []; + return ( + { + onClose?.(); - Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); - }} - option={{ - reportID: String(item.accountID), - text: Str.removeSMSDomain(item.displayName ?? ''), - alternateText: Str.removeSMSDomain(item.login ?? ''), - participantsList: [item], - icons: [ - { - id: item.accountID, - source: UserUtils.getAvatar(item.avatar, item.accountID), - name: item.login ?? '', - type: CONST.ICON_TYPE_AVATAR, - }, - ], - keyForList: item.login ?? String(item.accountID), - }} - /> - ); + Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); + }} + option={{ + reportID: String(item.accountID), + text: Str.removeSMSDomain(item.displayName ?? ''), + alternateText: Str.removeSMSDomain(item.login ?? ''), + participantsList: [item], + icons, + keyForList: item.login ?? String(item.accountID), + }} + /> + ); + }; return ( <> diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 147799ee118b..e1e390950730 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -5,6 +5,7 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, import {withOnyx} from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import type {Mention} from '@components/MentionSuggestions'; import MentionSuggestions from '@components/MentionSuggestions'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -238,9 +239,10 @@ function SuggestionMention( icons: [ { name: detail?.login, - source: UserUtils.getAvatar(detail?.avatar, detail?.accountID), + source: detail?.avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, fallbackIcon: detail?.fallbackIcon, + id: detail?.accountID, }, ], }); diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 1e0dc432b3fc..4f5c6ef6c602 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -3,6 +3,7 @@ import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Avatar from '@components/Avatar'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; @@ -19,7 +20,6 @@ import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Report, ReportAction} from '@src/types/onyx'; @@ -86,7 +86,8 @@ function ReportActionItemSingle({ let actorHint = (login || (displayName ?? '')).replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); const displayAllActors = useMemo(() => action?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && iouReport, [action?.actionName, iouReport]); const isWorkspaceActor = ReportUtils.isPolicyExpenseChat(report) && (!actorAccountID || displayAllActors); - let avatarSource = UserUtils.getAvatar(avatar ?? '', actorAccountID); + let avatarSource = avatar; + let avatarAccountId = actorAccountID; if (isWorkspaceActor) { displayName = ReportUtils.getPolicyName(report); @@ -99,7 +100,8 @@ function ReportActionItemSingle({ const delegateDisplayName = delegateDetails?.displayName; actorHint = `${delegateDisplayName} (${translate('reportAction.asCopilot')} ${displayName})`; displayName = actorHint; - avatarSource = UserUtils.getAvatar(delegateDetails?.avatar ?? '', Number(action.delegateAccountID)); + avatarSource = delegateDetails?.avatar; + avatarAccountId = Number(action.delegateAccountID); } // If this is a report preview, display names and avatars of both people involved @@ -112,7 +114,7 @@ function ReportActionItemSingle({ const secondaryDisplayName = ReportUtils.getDisplayNameForParticipant(secondaryAccountId); displayName = `${primaryDisplayName} & ${secondaryDisplayName}`; secondaryAvatar = { - source: UserUtils.getAvatar(secondaryUserAvatar, secondaryAccountId), + source: secondaryUserAvatar, type: CONST.ICON_TYPE_AVATAR, name: secondaryDisplayName ?? '', id: secondaryAccountId, @@ -126,11 +128,12 @@ function ReportActionItemSingle({ } else { secondaryAvatar = {name: '', source: '', type: 'avatar'}; } + const icon = { - source: avatarSource, + source: avatarSource ?? FallbackAvatar, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName ?? '', - id: isWorkspaceActor ? '' : actorAccountID, + id: isWorkspaceActor ? undefined : avatarAccountId, }; // Since the display name for a report action message is delivered with the report history as an array of fragments @@ -201,6 +204,7 @@ function ReportActionItemSingle({ type={icon.type} name={icon.name} fallbackIcon={fallbackIcon} + accountID={icon.id} /> diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.js b/src/pages/home/sidebar/ProfileAvatarWithIndicator.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index dfaf50c0bcf6..c6a744ce5580 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -12,6 +12,7 @@ import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/ButtonWithDropdownMenu/types'; import ConfirmModal from '@components/ConfirmModal'; import * as Expensicons from '@components/Icon/Expensicons'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MessagesRow from '@components/MessagesRow'; import SelectionList from '@components/SelectionList'; @@ -33,7 +34,6 @@ import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/typ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as UserUtils from '@libs/UserUtils'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -361,7 +361,7 @@ function WorkspaceMembersPage({policyMembers, personalDetails, invitedEmailsToAc rightElement: roleBadge, icons: [ { - source: UserUtils.getAvatar(details.avatar, accountID), + source: details.avatar ?? FallbackAvatar, name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 57a416b7d130..371867c1b31b 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -19,7 +19,6 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as UserUtils from '@libs/UserUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; @@ -59,7 +58,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policyMembers, policy, rou const member = policyMembers?.[accountID]; const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); - const avatar = details.avatar ?? UserUtils.getDefaultAvatar(); const fallbackIcon = details.fallbackIcon ?? ''; const displayName = details.displayName ?? ''; const isSelectedMemberOwner = policy?.owner === details.login; @@ -143,9 +141,10 @@ function WorkspaceMemberDetailsPage({personalDetails, policyMembers, policy, rou {Boolean(details.displayName ?? '') && ( diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx index 089099d12ce7..7670e8d56f6b 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsApproverPage.tsx @@ -6,6 +6,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import Badge from '@components/Badge'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import type {ListItem, Section} from '@components/SelectionList/types'; @@ -22,7 +23,6 @@ import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/typ import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as UserUtils from '@libs/UserUtils'; import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -97,7 +97,7 @@ function WorkspaceWorkflowsApproverPage({policy, policyMembers, personalDetails, rightElement: roleBadge, icons: [ { - source: UserUtils.getAvatar(details.avatar, accountID), + source: details.avatar ?? FallbackAvatar, name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx index f2d8cda48ef0..62ea4c83a34b 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx @@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx'; import Badge from '@components/Badge'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import type {ListItem, Section} from '@components/SelectionList/types'; @@ -21,7 +22,6 @@ import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as UserUtils from '@libs/UserUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; @@ -99,7 +99,7 @@ function WorkspaceWorkflowsPayerPage({route, policy, policyMembers, personalDeta rightElement: roleBadge, icons: [ { - source: UserUtils.getAvatar(details?.avatar, accountID), + source: details?.avatar ?? FallbackAvatar, name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 8b96a89a2a1b..9ac596a85777 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -34,7 +34,7 @@ type Icon = { name?: string; /** Avatar id */ - id?: number | string; + id?: number; /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ fallbackIcon?: AvatarSource; From 3248bd2356efb8ed8a8346bbc5958dda4cab970d Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 12 Apr 2024 15:56:25 +0200 Subject: [PATCH 2/8] Do not set default avatar URL for optimistic data to have consistent avatars --- src/libs/PersonalDetailsUtils.ts | 11 +++++------ src/libs/ReportUtils.ts | 5 ++--- src/libs/actions/IOU.ts | 5 ----- src/libs/actions/Task.ts | 2 +- src/pages/RoomMembersPage.tsx | 19 +++++++++---------- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index ffa0605f1eba..6a3980c58ca7 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -150,12 +150,11 @@ function getPersonalDetailsOnyxDataForOptimisticUsers(newLogins: string[], newAc newLogins.forEach((login, index) => { const accountID = newAccountIDs[index]; - personalDetailsNew[accountID] = { - login, - accountID, - avatar: UserUtils.getDefaultAvatarURL(accountID), - displayName: LocalePhoneNumber.formatPhoneNumber(login), - }; + personalDetailsNew[accountID] = { + login, + accountID, + displayName: LocalePhoneNumber.formatPhoneNumber(login), + }; /** * Cleanup the optimistic user to ensure it does not permanently persist. diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 695da35555eb..74812518f863 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -10,7 +10,6 @@ import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; -import * as Expensicons from '@components/Icon/Expensicons'; import {FallbackAvatar} from '@components/Icon/Expensicons'; import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; @@ -1818,7 +1817,7 @@ function getIcons( ): Icon[] { if (isEmptyObject(report)) { const fallbackIcon: Icon = { - source: defaultIcon ?? Expensicons.FallbackAvatar, + source: defaultIcon ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: defaultName, id: defaultAccountID, @@ -3192,7 +3191,7 @@ function buildOptimisticAddCommentReportAction(text?: string, file?: FileObject, }, ], automatic: false, - avatar: allPersonalDetails?.[accountID ?? -1]?.avatar ?? UserUtils.getDefaultAvatarURL(accountID), + avatar: allPersonalDetails?.[accountID ?? -1]?.avatar, created: DateUtils.getDBTimeWithSkew(), message: [ { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cd0264ddb6ea..1ac602b81c77 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -44,7 +44,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, TransactionDetails} from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import * as UserUtils from '@libs/UserUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import type {NavigationPartialRoute} from '@navigation/types'; import CONST from '@src/CONST'; @@ -1272,7 +1271,6 @@ function getMoneyRequestInformation( ? { [payerAccountID]: { accountID: payerAccountID, - avatar: UserUtils.getDefaultAvatarURL(payerAccountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || payerEmail), @@ -2804,7 +2802,6 @@ function createSplitsAndOnyxData( ? { [accountID]: { accountID, - avatar: UserUtils.getDefaultAvatarURL(accountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || email), @@ -3248,7 +3245,6 @@ function startSplitBill({ value: { [accountID]: { accountID, - avatar: UserUtils.getDefaultAvatarURL(accountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: LocalePhoneNumber.formatPhoneNumber(participant.displayName || email), @@ -4578,7 +4574,6 @@ function getSendMoneyParams( value: { [recipientAccountID]: { accountID: recipientAccountID, - avatar: UserUtils.getDefaultAvatarURL(recipient.accountID), // Disabling this line since participant.displayName can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing displayName: recipient.displayName || recipient.login, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 9cf7b0b78008..d0b7e3032558 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -682,7 +682,7 @@ function setAssigneeValue( // If this is an optimistic report, we likely don't have their personal details yet so we set it here optimistically as well const optimisticPersonalDetailsListAction = { accountID: assigneeAccountID, - avatar: allPersonalDetails?.[assigneeAccountID]?.avatar ?? UserUtils.getDefaultAvatarURL(assigneeAccountID), + avatar: allPersonalDetails?.[assigneeAccountID]?.avatar, displayName: allPersonalDetails?.[assigneeAccountID]?.displayName ?? assigneeEmail, login: assigneeEmail, }; diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index a3005561b0ca..3df7a697a454 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -36,6 +36,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import SearchInputManager from './workspace/SearchInputManager'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; type RoomMembersPageOnyxProps = { session: OnyxEntry; @@ -206,16 +207,14 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { isDisabled: accountID === session?.accountID, text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), alternateText: details?.login ? formatPhoneNumber(details.login) : '', - icons: details.avatar - ? [ - { - source: details.avatar, - name: details.login ?? '', - type: CONST.ICON_TYPE_AVATAR, - id: Number(accountID), - }, - ] - : [], + icons: [ + { + source: details.avatar ?? FallbackAvatar, + name: details.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + id: Number(accountID), + }, + ], pendingAction: pendingChatMember?.pendingAction, }); }); From c8fd46ba73a541159c937d14e02837485c79a39e Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 15 Apr 2024 10:32:24 +0200 Subject: [PATCH 3/8] Add small fixes to avatars logic --- src/components/AvatarWithIndicator.tsx | 2 +- src/libs/PersonalDetailsUtils.ts | 10 +++++----- src/libs/UserUtils.ts | 2 +- src/libs/actions/Task.ts | 1 - src/pages/ReportParticipantDetailsPage.tsx | 6 +++--- src/pages/ReportParticipantsPage.tsx | 7 ++++--- src/pages/RoomMembersPage.tsx | 2 +- .../report/ReportActionCompose/SuggestionMention.tsx | 1 - src/pages/home/sidebar/ProfileAvatarWithIndicator.js | 0 src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx | 4 ++-- 10 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 src/pages/home/sidebar/ProfileAvatarWithIndicator.js diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index ea29f41b0d80..1bf18afb70ff 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -11,7 +11,7 @@ import Tooltip from './Tooltip'; type AvatarWithIndicatorProps = { /** URL for the avatar */ - source: UserUtils.AvatarSource; + source?: UserUtils.AvatarSource; /** account id if it's user avatar */ accountID?: number; diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 6a3980c58ca7..961b3db13487 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -150,11 +150,11 @@ function getPersonalDetailsOnyxDataForOptimisticUsers(newLogins: string[], newAc newLogins.forEach((login, index) => { const accountID = newAccountIDs[index]; - personalDetailsNew[accountID] = { - login, - accountID, - displayName: LocalePhoneNumber.formatPhoneNumber(login), - }; + personalDetailsNew[accountID] = { + login, + accountID, + displayName: LocalePhoneNumber.formatPhoneNumber(login), + }; /** * Cleanup the optimistic user to ensure it does not permanently persist. diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index ee4a9c7a275f..205d84fff519 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -183,7 +183,7 @@ function getFullSizeAvatar(avatarSource: AvatarSource | undefined, accountID?: n * Small sized avatars end with _128.. This adds the _128 at the end of the * source URL (before the file type) if it doesn't exist there already. */ -function getSmallSizeAvatar(avatarSource: AvatarSource, accountID?: number): AvatarSource | undefined { +function getSmallSizeAvatar(avatarSource?: AvatarSource, accountID?: number): AvatarSource | undefined { const source = getAvatar(avatarSource, accountID); if (typeof source !== 'string') { return source; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index d0b7e3032558..03c6095e552c 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -15,7 +15,6 @@ import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; -import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; diff --git a/src/pages/ReportParticipantDetailsPage.tsx b/src/pages/ReportParticipantDetailsPage.tsx index 2b1411641faa..563f24635759 100644 --- a/src/pages/ReportParticipantDetailsPage.tsx +++ b/src/pages/ReportParticipantDetailsPage.tsx @@ -18,7 +18,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Report from '@libs/actions/Report'; import * as ReportUtils from '@libs/ReportUtils'; -import * as UserUtils from '@libs/UserUtils'; import Navigation from '@navigation/Navigation'; import type {ParticipantsNavigatorParamList} from '@navigation/types'; import CONST from '@src/CONST'; @@ -51,7 +50,7 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic const member = report?.participants?.[accountID]; const details = personalDetails?.[accountID] ?? ({} as PersonalDetails); - const avatar = details.avatar ?? UserUtils.getDefaultAvatar(); + const avatar = details.avatar; const fallbackIcon = details.fallbackIcon ?? ''; const displayName = details.displayName ?? ''; const isCurrentUserAdmin = ReportUtils.isGroupChatAdmin(report, currentUserPersonalDetails?.accountID); @@ -81,7 +80,8 @@ function ReportParticipantDetails({personalDetails, report, route}: ReportPartic diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index b69eb88b3244..b7c712913687 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -12,6 +12,7 @@ import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/Bu import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import TableListItem from '@components/SelectionList/TableListItem'; @@ -92,14 +93,14 @@ function ReportParticipantsPage({report, personalDetails, session}: ReportPartic alternateText: formatPhoneNumber(details?.login ?? ''), rightElement: roleBadge, pendingAction: pendingChatMember?.pendingAction, - icons: details?.avatar ? [ + icons: [ { - source: details.avatar, + source: details.avatar ?? FallbackAvatar, name: formatPhoneNumber(details?.login ?? ''), type: CONST.ICON_TYPE_AVATAR, id: accountID, }, - ] : [], + ], }); }); diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 3df7a697a454..13f95c12e1fe 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -8,6 +8,7 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; @@ -36,7 +37,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; import SearchInputManager from './workspace/SearchInputManager'; -import {FallbackAvatar} from '@components/Icon/Expensicons'; type RoomMembersPageOnyxProps = { session: OnyxEntry; diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index e1e390950730..dfd1088075ce 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -16,7 +16,6 @@ import * as LoginUtils from '@libs/LoginUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; -import * as UserUtils from '@libs/UserUtils'; import {isValidRoomName} from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.js b/src/pages/home/sidebar/ProfileAvatarWithIndicator.js deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx index e7726fb89537..b0287efb8990 100644 --- a/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx +++ b/src/pages/home/sidebar/ProfileAvatarWithIndicator.tsx @@ -5,7 +5,6 @@ import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as UserUtils from '@libs/UserUtils'; import ONYXKEYS from '@src/ONYXKEYS'; type ProfileAvatarWithIndicatorProps = { @@ -23,7 +22,8 @@ function ProfileAvatarWithIndicator({isSelected = false}: ProfileAvatarWithIndic From 6b3528a2946c75030220824e9e41c44a211529a5 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 16 Apr 2024 11:11:53 +0200 Subject: [PATCH 4/8] Add fixes after review and always generate icons property --- src/components/Avatar.tsx | 10 ++-- src/libs/OptionsListUtils.ts | 28 ++++----- .../report/ReactionList/BaseReactionList.tsx | 58 +++++++++---------- .../home/report/ReportActionItemSingle.tsx | 4 +- 4 files changed, 45 insertions(+), 55 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 6937e281da7a..9a76384cc516 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -56,7 +56,7 @@ type AvatarProps = { }; function Avatar({ - source, + source: originalSource, imageStyles, iconAdditionalStyles, containerStyles, @@ -77,20 +77,20 @@ function Avatar({ useEffect(() => { setImageError(false); - }, [source]); + }, [originalSource]); const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE; - const isEmptySource = !source; const iconSize = StyleUtils.getAvatarSize(size); const imageStyle: StyleProp = [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius]; const iconStyle = imageStyles ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined; // We pass the color styles down to the SVG for the workspace and fallback avatar. - const useFallBackAvatar = imageError || isEmptySource || source === Expensicons.FallbackAvatar; + const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, accountID); + const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; - const avatarSource = useFallBackAvatar ? fallbackAvatar : UserUtils.getAvatar(source, accountID) ?? Expensicons.FallbackAvatar; + const avatarSource = useFallBackAvatar ? fallbackAvatar : source; let iconColors; if (isWorkspace) { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 4ea33940aa9a..4a55d69267dc 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -305,23 +305,17 @@ function getAvatarsForAccountIDs(accountIDs: number[], personalDetails: OnyxEntr reversedDefaultValues[item[1]] = item[0]; }); - return accountIDs - .map((accountID) => { - const login = reversedDefaultValues[accountID] ?? ''; - const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID, avatar: ''}; + return accountIDs.map((accountID) => { + const login = reversedDefaultValues[accountID] ?? ''; + const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID, avatar: ''}; - if (!userPersonalDetail.avatar) { - return; - } - - return { - id: accountID, - source: userPersonalDetail.avatar, - type: CONST.ICON_TYPE_AVATAR, - name: userPersonalDetail.login ?? '', - }; - }) - .filter((icon): icon is OnyxCommon.Icon => icon !== undefined); + return { + id: accountID, + source: userPersonalDetail.avatar ?? FallbackAvatar, + type: CONST.ICON_TYPE_AVATAR, + name: userPersonalDetail.login ?? '', + }; + }); } /** @@ -1941,7 +1935,7 @@ function getShareLogOptions(options: OptionList, searchValue = '', betas: Beta[] */ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: PersonalDetails | EmptyObject, amountText?: string): PayeePersonalDetails { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login ?? ''); - const icons = personalDetail.avatar ? [{source: personalDetail.avatar, name: personalDetail.login ?? '', type: CONST.ICON_TYPE_AVATAR, id: personalDetail.accountID}] : []; + const icons = [{source: personalDetail.avatar ?? FallbackAvatar, name: personalDetail.login ?? '', type: CONST.ICON_TYPE_AVATAR, id: personalDetail.accountID}]; return { text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 996868b6fd6c..de1735ec97ed 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -3,6 +3,7 @@ import Str from 'expensify-common/lib/str'; import React from 'react'; import {FlatList} from 'react-native'; import type {FlatListProps} from 'react-native'; +import {FallbackAvatar} from '@components/Icon/Expensicons'; import OptionRow from '@components/OptionRow'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -53,38 +54,33 @@ function BaseReactionList({hasUserReacted = false, users, isVisible = false, emo * so that the sticky headers function properly * */ - const renderItem: FlatListProps['renderItem'] = ({item}) => { - const icons = item.avatar - ? [ - { - id: item.accountID, - source: item.avatar, - name: item.login ?? '', - type: CONST.ICON_TYPE_AVATAR, - }, - ] - : []; - return ( - { - onClose?.(); + const renderItem: FlatListProps['renderItem'] = ({item}) => ( + { + onClose?.(); - Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); - }} - option={{ - reportID: String(item.accountID), - text: Str.removeSMSDomain(item.displayName ?? ''), - alternateText: Str.removeSMSDomain(item.login ?? ''), - participantsList: [item], - icons, - keyForList: item.login ?? String(item.accountID), - }} - /> - ); - }; + Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); + }} + option={{ + reportID: String(item.accountID), + text: Str.removeSMSDomain(item.displayName ?? ''), + alternateText: Str.removeSMSDomain(item.login ?? ''), + participantsList: [item], + icons: [ + { + id: item.accountID, + source: item.avatar ?? FallbackAvatar, + name: item.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + }, + ], + keyForList: item.login ?? String(item.accountID), + }} + /> + ); return ( <> diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 4f5c6ef6c602..5fa003af69d0 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -101,7 +101,7 @@ function ReportActionItemSingle({ actorHint = `${delegateDisplayName} (${translate('reportAction.asCopilot')} ${displayName})`; displayName = actorHint; avatarSource = delegateDetails?.avatar; - avatarAccountId = Number(action.delegateAccountID); + avatarAccountId = action.delegateAccountID; } // If this is a report preview, display names and avatars of both people involved @@ -133,7 +133,7 @@ function ReportActionItemSingle({ source: avatarSource ?? FallbackAvatar, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName ?? '', - id: isWorkspaceActor ? undefined : avatarAccountId, + id: avatarAccountId, }; // Since the display name for a report action message is delivered with the report history as an array of fragments From 445a70e923134e4a19444e84b61e595fff0b6af2 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 16 Apr 2024 11:31:39 +0200 Subject: [PATCH 5/8] Fix wrong avatar on workspace invite page --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 4a55d69267dc..2db2d2047c36 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -307,7 +307,7 @@ function getAvatarsForAccountIDs(accountIDs: number[], personalDetails: OnyxEntr return accountIDs.map((accountID) => { const login = reversedDefaultValues[accountID] ?? ''; - const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID, avatar: ''}; + const userPersonalDetail = personalDetails?.[accountID] ?? {login, accountID}; return { id: accountID, From d42fc2d27e771784d4fa3758688c015a12e67176 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Thu, 18 Apr 2024 16:10:09 +0200 Subject: [PATCH 6/8] Fixes sometimes getting default avatar when source was undefined --- src/components/Avatar.tsx | 2 +- src/pages/home/report/ReportActionItemSingle.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 9a76384cc516..99c585dbe672 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -87,7 +87,7 @@ function Avatar({ // We pass the color styles down to the SVG for the workspace and fallback avatar. const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, accountID); - const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; + const useFallBackAvatar = imageError || !originalSource || !source || source === Expensicons.FallbackAvatar; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; const avatarSource = useFallBackAvatar ? fallbackAvatar : source; diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index 5fa003af69d0..fe4ac414a457 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -93,6 +93,7 @@ function ReportActionItemSingle({ displayName = ReportUtils.getPolicyName(report); actorHint = displayName; avatarSource = ReportUtils.getWorkspaceAvatar(report); + avatarAccountId = undefined; } else if (action?.delegateAccountID && personalDetails[action?.delegateAccountID]) { // We replace the actor's email, name, and avatar with the Copilot manually for now. And only if we have their // details. This will be improved upon when the Copilot feature is implemented. From 674af05630be8c35cba14235d0d7393462a99dbb Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 19 Apr 2024 09:47:29 +0200 Subject: [PATCH 7/8] add more fixes after review --- src/components/Avatar.tsx | 2 +- src/libs/UserUtils.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 99c585dbe672..9a76384cc516 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -87,7 +87,7 @@ function Avatar({ // We pass the color styles down to the SVG for the workspace and fallback avatar. const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, accountID); - const useFallBackAvatar = imageError || !originalSource || !source || source === Expensicons.FallbackAvatar; + const useFallBackAvatar = imageError || !source || source === Expensicons.FallbackAvatar; const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar; const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon'; const avatarSource = useFallBackAvatar ? fallbackAvatar : source; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index 205d84fff519..03066b21fa71 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -122,7 +122,7 @@ function getDefaultAvatarURL(accountID: string | number = ''): string { } /** - * Given a user's avatar path, returns true if user doesn't have an avatar or if URL points to a default avatar + * Given a user's avatar path, returns true if URL points to a default avatar, false otherwise * @param avatarSource - the avatar source from user's personalDetails */ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | undefined { @@ -137,11 +137,6 @@ function isDefaultAvatar(avatarSource?: AvatarSource): avatarSource is string | } } - if (!avatarSource) { - // If source is undefined, we should also use a default avatar - return true; - } - return false; } From ad3e0415f9c1b75e69c8286aafaf3995c9497af3 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 22 Apr 2024 15:37:07 +0200 Subject: [PATCH 8/8] remove unneeded variable in ProfilePage --- src/pages/ProfilePage.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index d2967de8360a..e0a4b55ce149 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -95,8 +95,6 @@ function ProfilePage({route}: ProfilePageProps) { const details: PersonalDetails | EmptyObject = personalDetails?.[accountID] ?? (ValidationUtils.isValidAccountRoute(accountID) ? {} : {accountID: 0, avatar: ''}); const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details, undefined, undefined, isCurrentUser); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const avatar = details.avatar; const fallbackIcon = details?.fallbackIcon ?? ''; const login = details?.login ?? ''; const timezone = details?.timezone; @@ -162,7 +160,7 @@ function ProfilePage({route}: ProfilePageProps) {