Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for viewing group avatar photo #48757

Merged
merged 14 commits into from
Sep 17, 2024
6 changes: 6 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@
| 'description'
| 'writeCapability'
| 'avatarUrl'
| 'avatarFileName'
| 'invoiceReceiver'
| 'isHidden'
> & {
Expand Down Expand Up @@ -1536,7 +1537,7 @@
*/
function isExpenseRequest(report: OnyxInputOrEntry<Report>): boolean {
if (isThread(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);

Check failure on line 1540 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead
const parentReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`];
return isExpenseReport(parentReport) && !isEmptyObject(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction);
}
Expand All @@ -1549,7 +1550,7 @@
*/
function isIOURequest(report: OnyxInputOrEntry<Report>): boolean {
if (isThread(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);

Check failure on line 1553 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead
const parentReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`];
return isIOUReport(parentReport) && !isEmptyObject(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction);
}
Expand All @@ -1562,7 +1563,7 @@
*/
function isTrackExpenseReport(report: OnyxInputOrEntry<Report>): boolean {
if (isThread(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);

Check failure on line 1566 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead
return !isEmptyObject(parentReportAction) && ReportActionsUtils.isTrackExpenseAction(parentReportAction);
}
return false;
Expand Down Expand Up @@ -2211,7 +2212,7 @@
return [fallbackIcon];
}
if (isExpenseRequest(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);

Check failure on line 2215 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead
const workspaceIcon = getWorkspaceIcon(report, policy);
const memberIcon = {
source: personalDetails?.[parentReportAction?.actorAccountID ?? -1]?.avatar ?? FallbackAvatar,
Expand All @@ -2224,7 +2225,7 @@
return [memberIcon, workspaceIcon];
}
if (isChatThread(report)) {
const parentReportAction = ReportActionsUtils.getParentReportAction(report);

Check failure on line 2228 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead

const actorAccountID = getReportActionActorAccountID(parentReportAction, report);
const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[actorAccountID ?? -1], '', false);
Expand Down Expand Up @@ -3105,7 +3106,7 @@
const transactionID = moneyRequestReport ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : 0;
const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction);

const parentReportAction = ReportActionsUtils.getParentReportAction(moneyRequestReport);

Check failure on line 3109 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead

const isRequestIOU = isIOUReport(moneyRequestReport);
const isHoldActionCreator = isHoldCreator(transaction, reportAction.childReportID ?? '-1');
Expand Down Expand Up @@ -3712,7 +3713,7 @@
}

let formattedName: string | undefined;
const parentReportAction = parentReportActionParam ?? ReportActionsUtils.getParentReportAction(report);

Check failure on line 3716 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead
const parentReportActionMessage = ReportActionsUtils.getReportActionMessage(parentReportAction);

if (parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) {
Expand Down Expand Up @@ -4248,7 +4249,7 @@

// These parameters are not saved on the reportAction, but are used to display the task in the UI
// Added when we fetch the reportActions on a report
reportAction.reportAction.originalMessage = {

Check failure on line 4252 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'originalMessage' is deprecated. Used in old report actions before migration. Replaced by using getOriginalMessage function
html: ReportActionsUtils.getReportActionHtml(reportAction.reportAction),
taskReportID: ReportActionsUtils.getReportActionMessage(reportAction.reportAction)?.taskReportID,
whisperedTo: [],
Expand Down Expand Up @@ -5078,6 +5079,7 @@
parentReportID = '',
description = '',
avatarUrl = '',
avatarFileName = '',
optimisticReportID = '',
): OptimisticChatReport {
const isWorkspaceChatType = chatType && isWorkspaceChat(chatType);
Expand Down Expand Up @@ -5118,6 +5120,7 @@
description,
writeCapability,
avatarUrl,
avatarFileName,
};

if (chatType === CONST.REPORT.CHAT_TYPE.INVOICE) {
Expand All @@ -5135,6 +5138,7 @@
participantAccountIDs: number[],
reportName: string,
avatarUri: string,
avatarFilename: string,
optimisticReportID?: string,
notificationPreference?: NotificationPreference,
) {
Expand All @@ -5153,6 +5157,7 @@
undefined,
undefined,
avatarUri,
avatarFilename,
optimisticReportID,
);
}
Expand Down Expand Up @@ -5562,6 +5567,7 @@
undefined,
undefined,
undefined,
undefined,
expenseReportId,
);
const expenseChatReportID = expenseChatData.reportID;
Expand Down Expand Up @@ -5989,7 +5995,7 @@
// This can also happen for anyone accessing a public room or archived room for which they don't have access to the underlying policy.
// Optionally exclude reports that do not belong to currently active workspace

const parentReportAction = ReportActionsUtils.getParentReportAction(report);

Check failure on line 5998 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'getParentReportAction' is deprecated. Use Onyx.connect() or withOnyx() instead

if (
!report?.reportID ||
Expand Down
16 changes: 13 additions & 3 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,8 @@ function updateGroupChatAvatar(reportID: string, file?: File | CustomRNImageMani
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
avatarUrl: file?.uri ?? '',
avatarUrl: file ? file?.uri ?? '' : null,
avatarFileName: file ? file?.name ?? '' : null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
avatarFileName: file ? file?.name ?? '' : null,
avatarFileName: file?.name ?? '',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we set avatarFileName to an empty string when file is undefined, the avatarFileName will remain an empty string. For example, when removing the avatar, the avatarFileName will stay as an empty string, but it should be set to null to effectively remove the avatarFileName.

To test this, go to the avatar picker’s popover menu and select "Remove Avatar." Then, check the Onyx value of avatarFileName. While the avatarUrl will be removed, the avatarFileName will remain an empty string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the avatarUrl will be removed, the avatarFileName will remain an empty string

How is that the url is being removed but the filename is not? given that both fallback to an empty string, or is it because the former is cleared from BE onyx response?

Either way, let's set both to null

pendingFields: {
avatar: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
Expand All @@ -708,12 +709,14 @@ function updateGroupChatAvatar(reportID: string, file?: File | CustomRNImageMani
},
];

const fetchedReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
avatarUrl: ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.avatarUrl ?? null,
avatarUrl: fetchedReport?.avatarUrl ?? null,
avatarFileName: fetchedReport?.avatarFileName ?? null,
pendingFields: {
avatar: null,
},
Expand Down Expand Up @@ -1011,7 +1014,14 @@ function navigateToAndOpenReport(
if (isEmptyObject(chat)) {
if (isGroupChat) {
// If we are creating a group chat then participantAccountIDs is expected to contain currentUserAccountID
newChat = ReportUtils.buildOptimisticGroupChatReport(participantAccountIDs, reportName ?? '', avatarUri ?? '', optimisticReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS);
newChat = ReportUtils.buildOptimisticGroupChatReport(
participantAccountIDs,
reportName ?? '',
avatarUri ?? '',
avatarFile?.name ?? '',
optimisticReportID,
CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
);
} else {
newChat = ReportUtils.buildOptimisticChatReport(
[...participantAccountIDs, currentUserAccountID],
Expand Down
64 changes: 32 additions & 32 deletions src/pages/ReportAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import React, {useMemo} from 'react';
import {useOnyx} from 'react-native-onyx';
import AttachmentModal from '@components/AttachmentModal';
import Navigation from '@libs/Navigation/Navigation';
import type {AuthScreensParamList} from '@libs/Navigation/types';
Expand All @@ -10,50 +9,51 @@ import * as UserUtils from '@libs/UserUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Policy, Report} from '@src/types/onyx';

type ReportAvatarOnyxProps = {
report: OnyxEntry<Report>;
isLoadingApp: OnyxEntry<boolean>;
policies: OnyxCollection<Policy>;
};
type ReportAvatarProps = StackScreenProps<AuthScreensParamList, typeof SCREENS.REPORT_AVATAR>;

type ReportAvatarProps = ReportAvatarOnyxProps & StackScreenProps<AuthScreensParamList, typeof SCREENS.REPORT_AVATAR>;
function ReportAvatar({route}: ReportAvatarProps) {
const reportIDFromRoute = route.params?.reportID ?? '-1';
const policyIDFromRoute = route.params?.policyID ?? '-1';
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyIDFromRoute}`);
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true});

function ReportAvatar({report = {} as Report, route, policies, isLoadingApp = true}: ReportAvatarProps) {
const policyID = route.params.policyID ?? '-1';
const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
const policyName = ReportUtils.getPolicyName(report, false, policy);
const avatarURL = ReportUtils.getWorkspaceIcon(report).source;
const attachment = useMemo(() => {
if (ReportUtils.isGroupChat(report) && !ReportUtils.isThread(report)) {
return {
source: report?.avatarUrl ? UserUtils.getFullSizeAvatar(report.avatarUrl, 0) : ReportUtils.getDefaultGroupAvatar(report?.reportID ?? ''),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

report?.avatarUrl should be enough as you are nut supposed to be able to access this page if report does not have a custom avatar

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still access it using a URL, for example: dev.new.expensify.com:8082/r/12345678/avatar.

headerTitle: ReportUtils.getReportName(report),
originalFileName: report?.avatarFileName ?? '',
isWorkspaceAvatar: false,
};
}

return {
source: UserUtils.getFullSizeAvatar(ReportUtils.getWorkspaceIcon(report).source, 0),
headerTitle: ReportUtils.getPolicyName(report, false, policy),
// In the case of default workspace avatar, originalFileName prop takes policyID as value to get the color of the avatar
originalFileName: policy?.originalFileName ?? policy?.id ?? report?.policyID ?? '',
isWorkspaceAvatar: true,
};
}, [report, policy]);

return (
<AttachmentModal
headerTitle={policyName}
headerTitle={attachment.headerTitle}
defaultOpen
source={UserUtils.getFullSizeAvatar(avatarURL, 0)}
source={attachment.source}
onModalClose={() => {
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '-1'));
}}
isWorkspaceAvatar
isWorkspaceAvatar={attachment.isWorkspaceAvatar}
maybeIcon
// In the case of default workspace avatar, originalFileName prop takes policyID as value to get the color of the avatar
originalFileName={policy?.originalFileName ?? policy?.id ?? report?.policyID}
originalFileName={attachment.originalFileName}
shouldShowNotFoundPage={!report?.reportID && !isLoadingApp}
isLoading={(!report?.reportID || !policy?.id) && !!isLoadingApp}
/>
);
}

ReportAvatar.displayName = 'ReportAvatar';

export default withOnyx<ReportAvatarProps, ReportAvatarOnyxProps>({
report: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '-1'}`,
},
isLoadingApp: {
key: ONYXKEYS.IS_LOADING_APP,
},
policies: {
key: ONYXKEYS.COLLECTION.POLICY,
},
})(ReportAvatar);
export default ReportAvatar;
2 changes: 1 addition & 1 deletion src/pages/ReportDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
isUsingDefaultAvatar={!report.avatarUrl}
size={CONST.AVATAR_SIZE.XLARGE}
avatarStyle={styles.avatarXLarge}
shouldDisableViewPhoto
onViewPhotoPress={() => Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID ?? '-1'))}
onImageRemoved={() => {
// Calling this without a file will remove the avatar
Report.updateGroupChatAvatar(report.reportID ?? '');
Expand Down
2 changes: 2 additions & 0 deletions src/pages/home/ReportScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
isOptimisticReport: reportOnyx?.isOptimisticReport,
lastMentionedTime: reportOnyx?.lastMentionedTime,
avatarUrl: reportOnyx?.avatarUrl,
avatarFileName: reportOnyx?.avatarFileName,
permissions,
invoiceReceiver: reportOnyx?.invoiceReceiver,
policyAvatar: reportOnyx?.policyAvatar,
Expand Down Expand Up @@ -248,6 +249,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
reportOnyx?.isOptimisticReport,
reportOnyx?.lastMentionedTime,
reportOnyx?.avatarUrl,
reportOnyx?.avatarFileName,
permissions,
reportOnyx?.invoiceReceiver,
reportOnyx?.policyAvatar,
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<
/** The URL of the Group Chat report custom avatar */
avatarUrl?: string;

/** The filename of the avatar */
avatarFileName?: string;

/** The specific type of chat */
chatType?: ValueOf<typeof CONST.REPORT.CHAT_TYPE>;

Expand Down
Loading