diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 81eace444de4..03ae8f51bfb6 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -43,6 +43,8 @@ const propTypes = { /** Whether if it's an unauthenticated user */ isAnonymous: PropTypes.bool, + shouldEnableDetailPageNavigation: PropTypes.bool, + ...windowDimensionsPropTypes, ...withLocalizePropTypes, }; @@ -53,9 +55,15 @@ const defaultProps = { report: {}, isAnonymous: false, size: CONST.AVATAR_SIZE.DEFAULT, + shouldEnableDetailPageNavigation: false, }; -const showActorDetails = (report) => { +const showActorDetails = (report, shouldEnableDetailPageNavigation = false) => { + // We should navigate to the details page if the report is a IOU/expense report + if (shouldEnableDetailPageNavigation) { + return ReportUtils.navigateToDetailsPage(report); + } + if (ReportUtils.isExpenseReport(report)) { Navigation.navigate(ROUTES.PROFILE.getRoute(report.ownerAccountID)); return; @@ -93,12 +101,12 @@ function AvatarWithDisplayName(props) { const defaultSubscriptSize = isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : props.size; const avatarBorderColor = props.isAnonymous ? themeColors.highlightBG : themeColors.componentBG; - return ( + const headerView = ( {Boolean(props.report && title) && ( showActorDetails(props.report)} + onPress={() => showActorDetails(props.report, props.shouldEnableDetailPageNavigation)} accessibilityLabel={title} accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} > @@ -145,6 +153,21 @@ function AvatarWithDisplayName(props) { )} ); + + if (!props.shouldEnableDetailPageNavigation) { + return headerView; + } + + return ( + ReportUtils.navigateToDetailsPage(props.report)} + style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]} + accessibilityLabel={title} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} + > + {headerView} + + ); } AvatarWithDisplayName.propTypes = propTypes; AvatarWithDisplayName.displayName = 'AvatarWithDisplayName'; diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index c720529371d7..e138876c39dc 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -46,6 +46,7 @@ function HeaderWithBackButton({ horizontal: 0, }, threeDotsMenuItems = [], + shouldEnableDetailPageNavigation = false, children = null, onModalHide = () => {}, shouldOverlay = false, @@ -76,14 +77,14 @@ function HeaderWithBackButton({ )} - {shouldShowAvatarWithDisplay && ( + {shouldShowAvatarWithDisplay ? ( - )} - {!shouldShowAvatarWithDisplay && ( + ) : (
avatarSizeToStylesMap[props.size] || avatarSizeToStylesMap.default, [props.size]); + const tooltipTexts = props.shouldShowTooltip ? _.pluck(props.icons, 'name') : ['']; + const avatarSize = useMemo(() => { + if (props.isFocusMode) { + return CONST.AVATAR_SIZE.MID_SUBSCRIPT; + } + + if (props.size === CONST.AVATAR_SIZE.LARGE) { + return CONST.AVATAR_SIZE.MEDIUM; + } + + return CONST.AVATAR_SIZE.SMALLER; + }, [props.isFocusMode, props.size]); const avatarRows = useMemo(() => { // If we're not displaying avatars in rows or the number of icons is less than or equal to the max avatars in a row, return a single row @@ -247,7 +276,7 @@ function MultipleAvatars(props) { - + {props.icons.length === 2 ? ( ReportUtils.isChatThread(props.report), [props.report]); const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(props.report), [props.report]); const isArchivedRoom = useMemo(() => ReportUtils.isArchivedRoom(props.report), [props.report]); + const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(props.report), [props.report]); // eslint-disable-next-line react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(props.report), [props.report, policy]); + const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report); const canLeaveRoom = useMemo(() => ReportUtils.canLeaveRoom(props.report, !_.isEmpty(policy)), [policy, props.report]); - const participants = useMemo(() => lodashGet(props.report, 'participantAccountIDs', []), [props.report]); + const participants = useMemo(() => ReportUtils.getParticipantsIDs(props.report), [props.report]); const menuItems = useMemo(() => { const items = [ @@ -113,7 +116,7 @@ function ReportDetailsPage(props) { } // Prevent displaying private notes option for threads and task reports - if (!isThread && !ReportUtils.isTaskReport(props.report)) { + if (!isThread && !isMoneyRequestReport && !ReportUtils.isTaskReport(props.report)) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.PRIVATE_NOTES, translationKey: 'privateNotes.title', @@ -135,13 +138,15 @@ function ReportDetailsPage(props) { } return items; - }, [props.report, participants, isArchivedRoom, shouldDisableSettings, isThread, isUserCreatedPolicyRoom, canLeaveRoom]); + }, [isArchivedRoom, participants.length, shouldDisableSettings, isThread, isMoneyRequestReport, props.report, isUserCreatedPolicyRoom, canLeaveRoom]); const displayNamesWithTooltips = useMemo(() => { const hasMultipleParticipants = participants.length > 1; return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, props.personalDetails), hasMultipleParticipants); }, [participants, props.personalDetails]); + const icons = useMemo(() => ReportUtils.getIcons(props.report, props.personalDetails, props.policies), [props.report, props.personalDetails, props.policies]); + const chatRoomSubtitleText = chatRoomSubtitle ? ( - + {isMoneyRequestReport ? ( + + ) : ( + + )} @@ -185,6 +197,13 @@ function ReportDetailsPage(props) { ) : ( chatRoomSubtitleText )} + {!_.isEmpty(parentNavigationSubtitleData) && isMoneyRequestReport && ( + + )} {_.map(menuItems, (item) => { diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index 7f453d16817b..67933ebfe3e4 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -54,17 +54,8 @@ const defaultProps = { * @param {Object} translate The localize * @return {Array} */ -const getAllParticipants = (report, personalDetails, translate) => { - let participantAccountIDs = report.participantAccountIDs; - - // Build participants list for IOU report - there is a possibility that participantAccountIDs may be undefined/empty - if (ReportUtils.isIOUReport(report)) { - const managerID = report.managerID || ''; - const ownerAccountID = report.ownerAccountID || ''; - participantAccountIDs = [managerID, ownerAccountID]; - } - - return _.chain(participantAccountIDs) +const getAllParticipants = (report, personalDetails, translate) => + _.chain(ReportUtils.getParticipantsIDs(report)) .map((accountID, index) => { const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''}); const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden'); @@ -90,7 +81,6 @@ const getAllParticipants = (report, personalDetails, translate) => { }) .sortBy((participant) => participant.displayName.toLowerCase()) .value(); -}; function ReportParticipantsPage(props) { const participants = _.map(getAllParticipants(props.report, props.personalDetails, props.translate), (participant) => ({ @@ -110,7 +100,8 @@ function ReportParticipantsPage(props) { ReportUtils.isChatRoom(props.report) || ReportUtils.isPolicyExpenseChat(props.report) || ReportUtils.isChatThread(props.report) || - ReportUtils.isTaskReport(props.report) + ReportUtils.isTaskReport(props.report) || + ReportUtils.isMoneyRequestReport(props.report) ? 'common.members' : 'common.details', )} diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index 75bf5fd24f1d..e6d36ebc7070 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -1,5 +1,6 @@ import React from 'react'; import {View, ScrollView} from 'react-native'; +import _ from 'underscore'; import ScreenWrapper from '../components/ScreenWrapper'; import HeaderWithBackButton from '../components/HeaderWithBackButton'; import Navigation from '../libs/Navigation/Navigation'; @@ -40,11 +41,30 @@ const defaultProps = { class ShareCodePage extends React.Component { qrCodeRef = React.createRef(); + /** + * @param {Boolean} isReport + * @return {String|string|*} + */ + getSubtitle(isReport) { + if (ReportUtils.isExpenseReport(this.props.report)) { + return ReportUtils.getPolicyName(this.props.report); + } + if (ReportUtils.isMoneyRequestReport(this.props.report)) { + // generate subtitle from participants + return _.map(ReportUtils.getParticipantsIDs(this.props.report), (accountID) => ReportUtils.getDisplayNameForParticipant(accountID)).join(' & '); + } + + if (isReport) { + return ReportUtils.getParentNavigationSubtitle(this.props.report).workspaceName || ReportUtils.getChatRoomSubtitle(this.props.report); + } + + return this.props.formatPhoneNumber(this.props.session.email); + } + render() { const isReport = this.props.report != null && this.props.report.reportID != null; const title = isReport ? ReportUtils.getReportName(this.props.report) : this.props.currentUserPersonalDetails.displayName; - const formattedEmail = this.props.formatPhoneNumber(this.props.session.email); - const subtitle = isReport ? ReportUtils.getParentNavigationSubtitle(this.props.report).workspaceName || ReportUtils.getChatRoomSubtitle(this.props.report) : formattedEmail; + const subtitle = this.getSubtitle(isReport); const urlWithTrailingSlash = Url.addTrailingForwardSlash(this.props.environmentURL); const url = isReport ? `${urlWithTrailingSlash}${ROUTES.REPORT_WITH_ID.getRoute(this.props.report.reportID)}` diff --git a/src/pages/settings/Report/ReportSettingsPage.js b/src/pages/settings/Report/ReportSettingsPage.js index 3db6bef16e30..9f4f4d048354 100644 --- a/src/pages/settings/Report/ReportSettingsPage.js +++ b/src/pages/settings/Report/ReportSettingsPage.js @@ -58,10 +58,11 @@ function ReportSettingsPage(props) { // The workspace the report is on, null if the user isn't a member of the workspace const linkedWorkspace = useMemo(() => _.find(policies, (policy) => policy && policy.id === report.policyID), [policies, report.policyID]); const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report, linkedWorkspace), [report, linkedWorkspace]); + const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); // We only want policy owners and admins to be able to modify the welcome message. const shouldDisableWelcomeMessage = - ReportUtils.isArchivedRoom(report) || !ReportUtils.isChatRoom(report) || _.isEmpty(linkedWorkspace) || linkedWorkspace.role !== CONST.POLICY.ROLE.ADMIN; + isMoneyRequestReport || ReportUtils.isArchivedRoom(report) || !ReportUtils.isChatRoom(report) || _.isEmpty(linkedWorkspace) || linkedWorkspace.role !== CONST.POLICY.ROLE.ADMIN; const shouldDisableSettings = _.isEmpty(report) || ReportUtils.shouldDisableSettings(report) || ReportUtils.isArchivedRoom(report); const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isChatThread(report); @@ -72,7 +73,13 @@ function ReportSettingsPage(props) { const writeCapability = ReportUtils.isAdminRoom(report) ? CONST.REPORT.WRITE_CAPABILITIES.ADMINS : report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL; const writeCapabilityText = translate(`writeCapabilityPage.writeCapability.${writeCapability}`); - const shouldAllowWriteCapabilityEditing = lodashGet(linkedWorkspace, 'role', '') === CONST.POLICY.ROLE.ADMIN && !ReportUtils.isAdminRoom(report); + const shouldAllowWriteCapabilityEditing = lodashGet(linkedWorkspace, 'role', '') === CONST.POLICY.ROLE.ADMIN && !ReportUtils.isAdminRoom(report) && !isMoneyRequestReport; + + const shouldShowNotificationPref = !isMoneyRequestReport && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; + const roomNameLabel = translate(isMoneyRequestReport ? 'workspace.editor.nameInputLabel' : 'newRoomPage.roomName'); + const reportName = ReportUtils.getReportName(props.report); + + const shouldShowWriteCapability = !isMoneyRequestReport; return ( @@ -82,7 +89,7 @@ function ReportSettingsPage(props) { onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID))} /> - {report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && ( + {shouldShowNotificationPref && ( - {translate('newRoomPage.roomName')} + {roomNameLabel} - {report.reportName} + {reportName} ) : ( @@ -122,29 +129,30 @@ function ReportSettingsPage(props) { )} )} - {shouldAllowWriteCapabilityEditing ? ( - Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(report.reportID))} - /> - ) : ( - - - {translate('writeCapabilityPage.label')} - - - {writeCapabilityText} - - - )} + {shouldShowWriteCapability && + (shouldAllowWriteCapabilityEditing ? ( + Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(report.reportID))} + /> + ) : ( + + + {translate('writeCapabilityPage.label')} + + + {writeCapabilityText} + + + ))} {Boolean(linkedWorkspace) && ( diff --git a/src/styles/styles.js b/src/styles/styles.js index 7bba63c8f09f..563f075e0f91 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1855,6 +1855,13 @@ const styles = (theme) => ({ borderRadius: 18, }, + singleAvatarMedium: { + height: 52, + width: 52, + backgroundColor: theme.icon, + borderRadius: 52, + }, + secondAvatar: { position: 'absolute', right: -18, @@ -1873,6 +1880,15 @@ const styles = (theme) => ({ borderColor: 'transparent', }, + secondAvatarMedium: { + position: 'absolute', + right: -36, + bottom: -36, + borderWidth: 3, + borderRadius: 52, + borderColor: 'transparent', + }, + secondAvatarSubscript: { position: 'absolute', right: -6,