Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
f3f2dba
Merge usages of subscript & multiple avatars
JakubKorytko Jul 14, 2025
f1ef6ec
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 14, 2025
84f3fd2
Merge usages of single & multiple avatars
JakubKorytko Jul 14, 2025
1445300
Fix avatars in LHN
JakubKorytko Jul 14, 2025
248d7aa
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 15, 2025
e58d9a1
Fix avatars in Details panel
JakubKorytko Jul 15, 2025
beb2a09
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 16, 2025
842f019
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 18, 2025
a78d38a
Create ReportAvatar & centralize avatars logic
JakubKorytko Jul 18, 2025
b6b6415
Fix ReportActionItemSingleTest
JakubKorytko Jul 18, 2025
a862187
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 21, 2025
deb1caf
Add Onyx wrapper for HeaderViewTest
JakubKorytko Jul 21, 2025
bd50e0e
IOU Report handling fix in ReportActionItemSingle
JakubKorytko Jul 21, 2025
ecd988a
Remove MultipleAvatars & getAvatarsForAccountIDs
JakubKorytko Jul 22, 2025
f96c785
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 22, 2025
83ce353
TS & Prettier minor fixes
JakubKorytko Jul 22, 2025
7d917de
Fix Search avatars
JakubKorytko Jul 22, 2025
9a78476
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 23, 2025
1addb68
Fix travel previews
JakubKorytko Jul 23, 2025
76a83e9
Fix avatar size in details page
JakubKorytko Jul 23, 2025
99f551c
Create ReportAvatarTest draft
JakubKorytko Jul 23, 2025
bfb7994
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 24, 2025
c9542af
Add test cases for ReportAvatarTest
JakubKorytko Jul 24, 2025
74ac9cf
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 24, 2025
0c8559a
Fix checks
JakubKorytko Jul 24, 2025
99f9946
Add new styles for details diagonal avatars
JakubKorytko Jul 24, 2025
4f3c5fa
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 25, 2025
0477f24
Fix AnonymousReportFooter after merge
JakubKorytko Jul 25, 2025
84d28aa
Fix UI tweaks
JakubKorytko Jul 25, 2025
9ca0139
Refractor ReportAvatar props
JakubKorytko Jul 25, 2025
5bb39cb
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 25, 2025
4cd53e4
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 28, 2025
6f9782d
Fix subscript XLarge avatar position
JakubKorytko Jul 28, 2025
fc3a658
Add c3024 suggestions
JakubKorytko Jul 28, 2025
232e30d
Add Blazej suggestions
JakubKorytko Jul 28, 2025
80fb83d
Rename ReportAvatar to ReportActionAvatars
JakubKorytko Jul 28, 2025
cc7f73a
Use getIcons inside getReportActionAvatars
JakubKorytko Jul 29, 2025
fd2be75
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 29, 2025
4a3ad78
Fixes after merge
JakubKorytko Jul 29, 2025
841c90d
Break ReportActionAvatars into UI components
JakubKorytko Jul 29, 2025
191a4aa
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 30, 2025
f603a03
Fix Horizontal avatars default icons
JakubKorytko Jul 30, 2025
c35bdc8
Fix Pay Reports page & welcome text styles
JakubKorytko Jul 30, 2025
dad65f7
Change getReportActionAvatars to useReportActionAvatars
JakubKorytko Jul 30, 2025
8a3d487
Change size of the avatars in details page
JakubKorytko Jul 30, 2025
95edb14
Add ReportActionAvatars & ReportActionAvatar descriptions
JakubKorytko Jul 30, 2025
e76e765
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Jul 31, 2025
fb25491
Correct details page subscript border
JakubKorytko Jul 31, 2025
d1f63ed
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Aug 1, 2025
256458e
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Aug 1, 2025
dc711d9
Merge branch 'main' into korytko/avatars-reports
JakubKorytko Aug 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions __mocks__/reportData/reports.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CONST from '@src/CONST';
import type {Report} from '@src/types/onyx';

const usersIDs = [15593135, 51760358, 26502375];
const usersIDs = [15593135, 51760358, 26502375] as const;
const amount = 10402;
const currency = CONST.CURRENCY.USD;

Expand All @@ -20,7 +20,7 @@ const participants = usersIDs.reduce((prev, userID) => {
};
}, {});

const iouReportR14932: Report = {
const iouReportR14932 = {
currency,
participants,
total: amount,
Expand All @@ -31,9 +31,9 @@ const iouReportR14932: Report = {
parentReportActionID: PARENT_REPORT_ACTION_ID_R14932,
parentReportID: PARENT_REPORT_ID_R14932,
reportID: REPORT_ID_R14932,
lastActorAccountID: usersIDs.at(0),
ownerAccountID: usersIDs.at(0),
managerID: usersIDs.at(1),
lastActorAccountID: usersIDs[0],
ownerAccountID: usersIDs[0],
managerID: usersIDs[1],
permissions: [CONST.REPORT.PERMISSIONS.READ, CONST.REPORT.PERMISSIONS.WRITE],
policyID: CONST.POLICY.ID_FAKE,
reportName: CONST.REPORT.ACTIONS.TYPE.IOU,
Expand All @@ -60,7 +60,7 @@ const iouReportR14932: Report = {
welcomeMessage: '',
description: '',
oldPolicyName: '',
};
} satisfies Report;

const chatReportR14932: Report = {
currency,
Expand Down
16 changes: 16 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ const CONST = {
AVATAR_MIN_WIDTH_PX: 80,
AVATAR_MIN_HEIGHT_PX: 80,

REPORT_ACTION_AVATARS: {
TYPE: {
MULTIPLE: 'multiple',
MULTIPLE_DIAGONAL: 'multipleDiagonal',
MULTIPLE_HORIZONTAL: 'multipleHorizontal',
SUBSCRIPT: 'subscript',
SINGLE: 'single',
},
SORT_BY: {
ID: 'id',
NAME: 'name',
REVERSE: 'reverse',
},
},

// Maximum width and height size in px for a selected image
AVATAR_MAX_WIDTH_PX: 4096,
AVATAR_MAX_HEIGHT_PX: 4096,
Expand Down Expand Up @@ -3124,6 +3139,7 @@ const CONST = {
SMALL_SUBSCRIPT: 'small-subscript',
MID_SUBSCRIPT: 'mid-subscript',
LARGE_BORDERED: 'large-bordered',
MEDIUM_LARGE: 'medium-large',
HEADER: 'header',
MENTION_ICON: 'mention-icon',
SMALL_NORMAL: 'small-normal',
Expand Down
5 changes: 1 addition & 4 deletions src/components/AnonymousReportFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
// eslint-disable-next-line no-restricted-syntax
import {signOutAndRedirectToSignIn} from '@userActions/Session';
import type {Report} from '@src/types/onyx';
import AvatarWithDisplayName from './AvatarWithDisplayName';
Expand All @@ -23,16 +23,13 @@ function AnonymousReportFooter({isSmallSizeLayout = false, report}: AnonymousRep
const styles = useThemeStyles();
const {translate} = useLocalize();

const policy = usePolicy(report?.policyID);

return (
<View style={styles.anonymousRoomFooter(isSmallSizeLayout)}>
<View style={[styles.flexRow, styles.flexShrink1]}>
<AvatarWithDisplayName
report={report}
isAnonymous
shouldEnableDetailPageNavigation
policy={policy}
/>
</View>
<View style={styles.anonymousRoomFooterWordmarkAndLogoContainer(isSmallSizeLayout)}>
Expand Down
9 changes: 8 additions & 1 deletion src/components/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type AvatarProps = {

/** Optional account id if it's user avatar or policy id if it's workspace avatar */
avatarID?: number | string;

/** Test ID for the Avatar component */
testID?: string;
};

function Avatar({
Expand All @@ -67,6 +70,7 @@ function Avatar({
type,
name = '',
avatarID,
testID = 'Avatar',
}: AvatarProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -103,7 +107,10 @@ function Avatar({
}

return (
<View style={[containerStyles, styles.pointerEventsNone]}>
<View
style={[containerStyles, styles.pointerEventsNone]}
testID={testID}
>
{typeof avatarSource === 'string' ? (
<View style={[iconStyle, StyleUtils.getAvatarBorderStyle(size, type), iconAdditionalStyles]}>
<Image
Expand Down
109 changes: 26 additions & 83 deletions src/components/AvatarWithDisplayName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import type {ColorValue, TextStyle} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useOnyx from '@hooks/useOnyx';
import type {ReportAvatarDetails} from '@hooks/useReportAvatarDetails';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -15,7 +13,6 @@ import type {DisplayNameWithTooltips} from '@libs/ReportUtils';
import {
getChatRoomSubtitle,
getDisplayNamesWithTooltips,
getIcons,
getParentNavigationSubtitle,
getReportName,
isChatThread,
Expand All @@ -26,32 +23,24 @@ import {
isMoneyRequestReport,
isTrackExpenseReport,
navigateToDetailsPage,
shouldReportShowSubscript,
} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report} from '@src/types/onyx';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import type {Report} from '@src/types/onyx';
import {getButtonRole} from './Button/utils';
import DisplayNames from './DisplayNames';
import type DisplayNamesProps from './DisplayNames/types';
import {FallbackAvatar} from './Icon/Expensicons';
import MultipleAvatars from './MultipleAvatars';
import ParentNavigationSubtitle from './ParentNavigationSubtitle';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import SingleReportAvatar from './ReportActionItem/SingleReportAvatar';
import ReportActionAvatars from './ReportActionAvatars';
import type {TransactionListItemType} from './SelectionList/types';
import SubscriptAvatar from './SubscriptAvatar';
import Text from './Text';

type AvatarWithDisplayNameProps = {
/** The report currently being looked at */
report: OnyxEntry<Report>;

/** The policy which the user has access to and which the report is tied to */
policy?: OnyxEntry<Policy>;

/** The size of the avatar */
size?: ValueOf<typeof CONST.AVATAR_SIZE>;

Expand All @@ -75,16 +64,6 @@ type AvatarWithDisplayNameProps = {

/** Color of the secondary avatar border, usually should match the container background */
avatarBorderColor?: ColorValue;

/** If we want to override the default avatar behavior and set a single avatar, we should pass this prop. */
singleAvatarDetails?: ReportAvatarDetails;
};

const fallbackIcon: Icon = {
source: FallbackAvatar,
type: CONST.ICON_TYPE_AVATAR,
name: '',
id: -1,
};

function getCustomDisplayName(
Expand Down Expand Up @@ -164,15 +143,13 @@ function getCustomDisplayName(
}

function AvatarWithDisplayName({
policy,
report,
isAnonymous = false,
size = CONST.AVATAR_SIZE.DEFAULT,
shouldEnableDetailPageNavigation = false,
shouldEnableAvatarNavigation = true,
shouldUseCustomSearchTitleName = false,
transactions = [],
singleAvatarDetails,
openParentReportInCurrentTab = false,
avatarBorderColor: avatarBorderColorProp,
}: AvatarWithDisplayNameProps) {
Expand All @@ -190,13 +167,10 @@ function AvatarWithDisplayName({
const parentReportActionParam = report?.parentReportActionID ? parentReportActions?.[report.parentReportActionID] : undefined;
const title = getReportName(report, undefined, parentReportActionParam, personalDetails, invoiceReceiverPolicy, reportAttributes);
const subtitle = getChatRoomSubtitle(report, {isCreateExpenseFlow: true});
const parentNavigationSubtitleData = getParentNavigationSubtitle(report, policy);
const parentNavigationSubtitleData = getParentNavigationSubtitle(report);
const isMoneyRequestOrReport = isMoneyRequestReport(report) || isMoneyRequest(report) || isTrackExpenseReport(report) || isInvoiceReport(report);
const isReportArchived = useReportIsArchived(report?.reportID);
const icons = getIcons(report, personalDetails, null, '', -1, policy, invoiceReceiverPolicy, isReportArchived);
const ownerPersonalDetails = getPersonalDetailsForAccountIDs(report?.ownerAccountID ? [report.ownerAccountID] : [], personalDetails);
const displayNamesWithTooltips = getDisplayNamesWithTooltips(Object.values(ownerPersonalDetails), false);
const shouldShowSubscriptAvatar = shouldReportShowSubscript(report, isReportArchived);
const avatarBorderColor = avatarBorderColorProp ?? (isAnonymous ? theme.highlightBG : theme.componentBG);

const actorAccountID = useRef<number | null>(null);
Expand Down Expand Up @@ -245,65 +219,34 @@ function AvatarWithDisplayName({

const shouldUseFullTitle = isMoneyRequestOrReport || isAnonymous;

const getAvatar = useCallback(() => {
if (shouldShowSubscriptAvatar) {
return (
<SubscriptAvatar
backgroundColor={avatarBorderColor}
mainAvatar={icons.at(0) ?? fallbackIcon}
secondaryAvatar={icons.at(1)}
size={size}
/>
);
}

if (!singleAvatarDetails || singleAvatarDetails.shouldDisplayAllActors || !singleAvatarDetails.reportPreviewSenderID) {
return (
<MultipleAvatars
icons={icons}
size={size}
secondAvatarStyle={[StyleUtils.getBackgroundAndBorderStyle(avatarBorderColor)]}
/>
);
}

return (
<SingleReportAvatar
reportPreviewDetails={singleAvatarDetails}
personalDetails={personalDetails}
containerStyles={[styles.actionAvatar, styles.mr3]}
actorAccountID={singleAvatarDetails.reportPreviewSenderID}
/>
);
}, [StyleUtils, avatarBorderColor, icons, personalDetails, shouldShowSubscriptAvatar, singleAvatarDetails, size, styles]);

const getWrappedAvatar = useCallback(() => {
const avatar = getAvatar();

if (!shouldEnableAvatarNavigation) {
return <View accessibilityLabel={title}>{avatar}</View>;
}

return (
<View accessibilityLabel={title}>
<PressableWithoutFeedback
onPress={showActorDetails}
accessibilityLabel={title}
role={getButtonRole(true)}
>
{avatar}
</PressableWithoutFeedback>
</View>
);
}, [getAvatar, shouldEnableAvatarNavigation, showActorDetails, title]);

const WrappedAvatar = getWrappedAvatar();
const multipleAvatars = (
<ReportActionAvatars
singleAvatarContainerStyle={[styles.actionAvatar, styles.mr3]}
subscriptAvatarBorderColor={avatarBorderColor}
size={size}
secondaryAvatarContainerStyle={StyleUtils.getBackgroundAndBorderStyle(avatarBorderColor)}
reportID={report?.reportID}
/>
);

const headerView = (
<View style={[styles.appContentHeaderTitle, styles.flex1]}>
{!!report && !!title && (
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween]}>
{WrappedAvatar}
<View accessibilityLabel={title}>
{shouldEnableAvatarNavigation ? (
<PressableWithoutFeedback
onPress={showActorDetails}
accessibilityLabel={title}
role={getButtonRole(true)}
>
{multipleAvatars}
</PressableWithoutFeedback>
) : (
multipleAvatars
)}
</View>

<View style={[styles.flex1, styles.flexColumn]}>
{getCustomDisplayName(
shouldUseCustomSearchTitleName,
Expand Down
6 changes: 0 additions & 6 deletions src/components/HeaderWithBackButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ function HeaderWithBackButton({
onDownloadButtonPress = () => {},
onThreeDotsButtonPress = () => {},
report,
policy,
policyAvatar,
singleAvatarDetails,
shouldShowReportAvatarWithDisplay = false,
shouldShowBackButton = true,
shouldShowBorderBottom = false,
Expand Down Expand Up @@ -103,8 +101,6 @@ function HeaderWithBackButton({
return (
<AvatarWithDisplayName
report={report}
policy={policy}
singleAvatarDetails={singleAvatarDetails}
shouldEnableDetailPageNavigation={shouldEnableDetailPageNavigation}
openParentReportInCurrentTab={openParentReportInCurrentTab}
/>
Expand All @@ -124,7 +120,6 @@ function HeaderWithBackButton({
StyleUtils,
subTitleLink,
shouldUseHeadlineHeader,
policy,
progressBarPercentage,
report,
shouldEnableDetailPageNavigation,
Expand All @@ -140,7 +135,6 @@ function HeaderWithBackButton({
titleColor,
translate,
openParentReportInCurrentTab,
singleAvatarDetails,
]);
const ThreeDotMenuButton = useMemo(() => {
if (shouldShowThreeDotsButton) {
Expand Down
9 changes: 1 addition & 8 deletions src/components/HeaderWithBackButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import type {ReactNode} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import type {ReportAvatarDetails} from '@hooks/useReportAvatarDetails';
import type {Action} from '@hooks/useSingleExecution';
import type {StepCounterParams} from '@src/languages/params';
import type {TranslationPaths} from '@src/languages/types';
import type {AnchorPosition} from '@src/styles';
import type {Policy, Report} from '@src/types/onyx';
import type {Report} from '@src/types/onyx';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
Expand Down Expand Up @@ -116,9 +115,6 @@ type HeaderWithBackButtonProps = Partial<ChildrenProps> & {
/** Report, if we're showing the details for one and using AvatarWithDisplay */
report?: OnyxEntry<Report>;

/** The report's policy, if we're showing the details for a report and need info about it for AvatarWithDisplay */
policy?: OnyxEntry<Policy>;

/** Single execution function to prevent concurrent navigation actions */
singleExecution?: <T extends unknown[]>(action: Action<T>) => Action<T>;

Expand Down Expand Up @@ -162,9 +158,6 @@ type HeaderWithBackButtonProps = Partial<ChildrenProps> & {
shouldMinimizeMenuButton?: boolean;
/** Whether to open the parent report link in the current tab if possible */
openParentReportInCurrentTab?: boolean;

/** If we want to override the default avatar behavior and set a single avatar, we should pass this prop. */
singleAvatarDetails?: ReportAvatarDetails;
};

export type {ThreeDotsMenuItem};
Expand Down
Loading
Loading