diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index a3fbb5e41378..eb7a5bc0e39a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -2,9 +2,11 @@ import _ from 'underscore'; import React, {useState, useRef} from 'react'; import PropTypes from 'prop-types'; import {View, StyleSheet} from 'react-native'; +import lodashGet from 'lodash/get'; import * as optionRowStyles from '../../styles/optionRowStyles'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; +import DateUtils from '../../libs/DateUtils'; import Icon from '../Icon'; import * as Expensicons from '../Icon/Expensicons'; import MultipleAvatars from '../MultipleAvatars'; @@ -22,12 +24,17 @@ import * as ContextMenuActions from '../../pages/home/report/ContextMenu/Context import * as OptionsListUtils from '../../libs/OptionsListUtils'; import * as ReportUtils from '../../libs/ReportUtils'; import useLocalize from '../../hooks/useLocalize'; +import Permissions from '../../libs/Permissions'; +import Tooltip from '../Tooltip'; const propTypes = { /** Style for hovered state */ // eslint-disable-next-line react/forbid-prop-types hoverStyle: PropTypes.object, + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), + /** The ID of the report that the option is for */ reportID: PropTypes.string.isRequired, @@ -54,6 +61,7 @@ const defaultProps = { style: null, optionItem: null, isFocused: false, + betas: [], }; function OptionRowLHN(props) { @@ -124,6 +132,13 @@ function OptionRowLHN(props) { ); }; + const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); + const statusText = lodashGet(optionItem, 'status.text', ''); + const statusClearAfterDate = lodashGet(optionItem, 'status.clearAfter', ''); + const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); + const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; + const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && !!emojiCode && ReportUtils.isOneOnOneChat(optionItem); + return ( + {isStatusVisible && ( + + {emojiCode} + + )} {optionItem.alternateText ? ( login: personalData.login, displayName: personalData.displayName, firstName: personalData.firstName, + status: personalData.status, avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID), }; return finalPersonalDetails; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 561fdf8fc53b..f411a865e1a1 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2503,6 +2503,29 @@ function isIOUOwnedByCurrentUser(report, allReportsDict = null) { return reportToLook.ownerAccountID === currentUserAccountID; } +/** + * Should return true only for personal 1:1 report + * + * @param {Object} report (chatReport or iouReport) + * @returns {boolean} + */ +function isOneOnOneChat(report) { + const isChatRoomValue = lodashGet(report, 'isChatRoom', false); + const participantsListValue = lodashGet(report, 'participantsList', []); + return ( + !isThread(report) && + !isChatRoom(report) && + !isChatRoomValue && + !isExpenseRequest(report) && + !isMoneyRequestReport(report) && + !isPolicyExpenseChat(report) && + !isTaskReport(report) && + isDM(report) && + !isIOUReport(report) && + participantsListValue.length === 1 + ); +} + /** * Assuming the passed in report is a default room, lets us know whether we can see it or not, based on permissions and * the various subsets of users we've allowed to use default rooms. @@ -3398,6 +3421,7 @@ export { shouldDisableSettings, shouldDisableRename, hasSingleParticipant, + isOneOnOneChat, getTransactionReportName, getTransactionDetails, getTaskAssigneeChatOnyxData, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index 56cf17a3fb47..3be3d81f15ae 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -259,6 +259,7 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, const subtitle = ReportUtils.getChatRoomSubtitle(report); const login = Str.removeSMSDomain(lodashGet(personalDetail, 'login', '')); + const status = lodashGet(personalDetail, 'status', ''); const formattedLogin = Str.isSMSLogin(login) ? LocalePhoneNumber.formatPhoneNumber(login) : login; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. @@ -348,6 +349,12 @@ function getOptionData(report, reportActions, personalDetails, preferredLocale, result.searchText = OptionsListUtils.getSearchText(report, reportName, participantPersonalDetailList, result.isChatRoom || result.isPolicyExpenseChat, result.isThread); result.displayNamesWithTooltips = displayNamesWithTooltips; result.isLastMessageDeletedParentAction = report.isLastMessageDeletedParentAction; + + if (status) { + result.status = status; + } + result.type = report.type; + return result; } diff --git a/src/libs/__mocks__/Permissions.js b/src/libs/__mocks__/Permissions.js index 5486d184d51b..e8496d61e027 100644 --- a/src/libs/__mocks__/Permissions.js +++ b/src/libs/__mocks__/Permissions.js @@ -14,4 +14,5 @@ export default { canUsePolicyRooms: (betas) => _.contains(betas, CONST.BETAS.POLICY_ROOMS), canUsePolicyExpenseChat: (betas) => _.contains(betas, CONST.BETAS.POLICY_EXPENSE_CHAT), canUseIOUSend: (betas) => _.contains(betas, CONST.BETAS.IOU_SEND), + canUseCustomStatus: (betas) => _.contains(betas, CONST.BETAS.CUSTOM_STATUS), }; diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.js new file mode 100644 index 000000000000..adf9cdda5cd0 --- /dev/null +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.js @@ -0,0 +1,65 @@ +/* eslint-disable rulesdir/onyx-props-must-have-default */ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; +import Text from '../../../components/Text'; +import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; +import Navigation from '../../../libs/Navigation/Navigation'; +import useLocalize from '../../../hooks/useLocalize'; +import styles from '../../../styles/styles'; +import ROUTES from '../../../ROUTES'; +import CONST from '../../../CONST'; + +const propTypes = { + /** Whether the create menu is open or not */ + isCreateMenuOpen: PropTypes.bool, + + /** Emoji status */ + emojiStatus: PropTypes.string, +}; + +const defaultProps = { + isCreateMenuOpen: false, + emojiStatus: '', +}; + +function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { + const {translate} = useLocalize(); + + const showStatusPage = useCallback(() => { + if (isCreateMenuOpen) { + // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon + return; + } + + Navigation.setShouldPopAllStateOnUP(); + Navigation.navigate(ROUTES.SETTINGS_STATUS); + }, [isCreateMenuOpen]); + + return ( + + + + + {emojiStatus} + + + + + + ); +} + +AvatarWithOptionalStatus.propTypes = propTypes; +AvatarWithOptionalStatus.defaultProps = defaultProps; +AvatarWithOptionalStatus.displayName = 'AvatarWithOptionalStatus'; +export default AvatarWithOptionalStatus; diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.js new file mode 100644 index 000000000000..ef6e663ce705 --- /dev/null +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.js @@ -0,0 +1,64 @@ +/* eslint-disable rulesdir/onyx-props-must-have-default */ +import lodashGet from 'lodash/get'; +import React, {useCallback} from 'react'; +import PropTypes from 'prop-types'; +import withCurrentUserPersonalDetails from '../../../components/withCurrentUserPersonalDetails'; +import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; +import AvatarWithIndicator from '../../../components/AvatarWithIndicator'; +import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; +import Navigation from '../../../libs/Navigation/Navigation'; +import * as UserUtils from '../../../libs/UserUtils'; +import useLocalize from '../../../hooks/useLocalize'; +import ROUTES from '../../../ROUTES'; +import CONST from '../../../CONST'; +import personalDetailsPropType from '../../personalDetailsPropType'; + +const propTypes = { + /** Whether the create menu is open or not */ + isCreateMenuOpen: PropTypes.bool, + + /** The personal details of the person who is logged in */ + currentUserPersonalDetails: personalDetailsPropType, +}; + +const defaultProps = { + isCreateMenuOpen: false, + currentUserPersonalDetails: { + pendingFields: {avatar: ''}, + accountID: '', + avatar: '', + }, +}; + +function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDetails}) { + const {translate} = useLocalize(); + + const showSettingsPage = useCallback(() => { + if (isCreateMenuOpen) { + // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon + return; + } + + Navigation.navigate(ROUTES.SETTINGS); + }, [isCreateMenuOpen]); + + return ( + + + + + + ); +} + +PressableAvatarWithIndicator.propTypes = propTypes; +PressableAvatarWithIndicator.defaultProps = defaultProps; +PressableAvatarWithIndicator.displayName = 'PressableAvatarWithIndicator'; +export default withCurrentUserPersonalDetails(PressableAvatarWithIndicator); diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 5b3f369fdce8..e54db8b2892d 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -14,16 +14,13 @@ import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; import Icon from '../../../components/Icon'; import * as Expensicons from '../../../components/Icon/Expensicons'; -import AvatarWithIndicator from '../../../components/AvatarWithIndicator'; import Tooltip from '../../../components/Tooltip'; import CONST from '../../../CONST'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import * as App from '../../../libs/actions/App'; -import withCurrentUserPersonalDetails from '../../../components/withCurrentUserPersonalDetails'; import withWindowDimensions from '../../../components/withWindowDimensions'; import LHNOptionsList from '../../../components/LHNOptionsList/LHNOptionsList'; import SidebarUtils from '../../../libs/SidebarUtils'; -import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; import Header from '../../../components/Header'; import defaultTheme from '../../../styles/themes/default'; import OptionsListSkeletonView from '../../../components/OptionsListSkeletonView'; @@ -31,14 +28,12 @@ import variables from '../../../styles/variables'; import LogoComponent from '../../../../assets/images/expensify-wordmark.svg'; import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback'; import * as Session from '../../../libs/actions/Session'; -import Button from '../../../components/Button'; -import * as UserUtils from '../../../libs/UserUtils'; import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import onyxSubscribe from '../../../libs/onyxSubscribe'; -import personalDetailsPropType from '../../personalDetailsPropType'; import * as ReportActionContextMenu from '../report/ContextMenu/ReportActionContextMenu'; import withCurrentReportID from '../../../components/withCurrentReportID'; import OptionRowLHNData from '../../../components/LHNOptionsList/OptionRowLHNData'; +import SignInOrAvatarWithOptionalStatus from './SignInOrAvatarWithOptionalStatus'; const basePropTypes = { /** Toggles the navigation menu open and closed */ @@ -58,8 +53,6 @@ const propTypes = { isLoading: PropTypes.bool.isRequired, - currentUserPersonalDetails: personalDetailsPropType, - priorityMode: PropTypes.oneOf(_.values(CONST.PRIORITY_MODE)), /** The top most report id */ @@ -75,9 +68,6 @@ const propTypes = { }; const defaultProps = { - currentUserPersonalDetails: { - avatar: '', - }, priorityMode: CONST.PRIORITY_MODE.DEFAULT, currentReportID: '', report: {}, @@ -88,7 +78,6 @@ class SidebarLinks extends React.PureComponent { super(props); this.showSearchPage = this.showSearchPage.bind(this); - this.showSettingsPage = this.showSettingsPage.bind(this); this.showReportPage = this.showReportPage.bind(this); if (this.props.isSmallScreenWidth) { @@ -147,15 +136,6 @@ class SidebarLinks extends React.PureComponent { Navigation.navigate(ROUTES.SEARCH); } - showSettingsPage() { - if (this.props.isCreateMenuOpen) { - // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon - return; - } - - Navigation.navigate(ROUTES.SETTINGS); - } - /** * Show Report page with selected report id * @@ -204,29 +184,7 @@ class SidebarLinks extends React.PureComponent { - - {Session.isAnonymousUser() ? ( - -