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() ? (
-
-
- ) : (
-
-
-
- )}
-
+
{this.props.isLoading ? (
<>
@@ -258,7 +216,6 @@ SidebarLinks.propTypes = propTypes;
SidebarLinks.defaultProps = defaultProps;
export default compose(
withLocalize,
- withCurrentUserPersonalDetails,
withWindowDimensions,
withCurrentReportID,
withOnyx({
diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
index 698a72a205cc..3d54306b6248 100644
--- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
+++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js
@@ -3,8 +3,6 @@ import {View} from 'react-native';
import styles from '../../../../styles/styles';
import SidebarLinksData from '../SidebarLinksData';
import ScreenWrapper from '../../../../components/ScreenWrapper';
-import Navigation from '../../../../libs/Navigation/Navigation';
-import ROUTES from '../../../../ROUTES';
import Timing from '../../../../libs/actions/Timing';
import CONST from '../../../../CONST';
import Performance from '../../../../libs/Performance';
@@ -17,13 +15,6 @@ const propTypes = {
...windowDimensionsPropTypes,
};
-/**
- * Function called when avatar is clicked
- */
-const navigateToSettings = () => {
- Navigation.navigate(ROUTES.SETTINGS);
-};
-
/**
* Function called when a pinned chat is selected.
*/
@@ -50,7 +41,6 @@ function BaseSidebarScreen(props) {
diff --git a/src/pages/home/sidebar/SignInButton.js b/src/pages/home/sidebar/SignInButton.js
new file mode 100644
index 000000000000..24b90d778445
--- /dev/null
+++ b/src/pages/home/sidebar/SignInButton.js
@@ -0,0 +1,33 @@
+/* eslint-disable rulesdir/onyx-props-must-have-default */
+import React from 'react';
+import {View} from 'react-native';
+import PressableWithoutFeedback from '../../../components/Pressable/PressableWithoutFeedback';
+import Button from '../../../components/Button';
+import styles from '../../../styles/styles';
+import * as Session from '../../../libs/actions/Session';
+import useLocalize from '../../../hooks/useLocalize';
+import CONST from '../../../CONST';
+
+function SignInButton() {
+ const {translate} = useLocalize();
+
+ return (
+
+
+
+
+
+ );
+}
+
+SignInButton.displayName = 'SignInButton';
+export default SignInButton;
diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js
new file mode 100644
index 000000000000..2599ac6b6942
--- /dev/null
+++ b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js
@@ -0,0 +1,63 @@
+/* eslint-disable rulesdir/onyx-props-must-have-default */
+import React from 'react';
+import PropTypes from 'prop-types';
+import {withOnyx} from 'react-native-onyx';
+import lodashGet from 'lodash/get';
+import withCurrentUserPersonalDetails from '../../../components/withCurrentUserPersonalDetails';
+import personalDetailsPropType from '../../personalDetailsPropType';
+import PressableAvatarWithIndicator from './PressableAvatarWithIndicator';
+import AvatarWithOptionalStatus from './AvatarWithOptionalStatus';
+import SignInButton from './SignInButton';
+import * as Session from '../../../libs/actions/Session';
+import Permissions from '../../../libs/Permissions';
+import compose from '../../../libs/compose';
+import ONYXKEYS from '../../../ONYXKEYS';
+
+const propTypes = {
+ /** The personal details of the person who is logged in */
+ currentUserPersonalDetails: personalDetailsPropType,
+
+ /** Whether the create menu is open or not */
+ isCreateMenuOpen: PropTypes.bool,
+
+ /** Beta features list */
+ betas: PropTypes.arrayOf(PropTypes.string),
+};
+
+const defaultProps = {
+ betas: [],
+ isCreateMenuOpen: false,
+ currentUserPersonalDetails: {
+ status: {emojiCode: ''},
+ },
+};
+
+function SignInOrAvatarWithOptionalStatus({currentUserPersonalDetails, isCreateMenuOpen, betas}) {
+ const statusEmojiCode = lodashGet(currentUserPersonalDetails, 'status.emojiCode', '');
+ const emojiStatus = Permissions.canUseCustomStatus(betas) ? statusEmojiCode : '';
+
+ if (Session.isAnonymousUser()) {
+ return ;
+ }
+ if (emojiStatus) {
+ return (
+
+ );
+ }
+ return ;
+}
+
+SignInOrAvatarWithOptionalStatus.propTypes = propTypes;
+SignInOrAvatarWithOptionalStatus.defaultProps = defaultProps;
+SignInOrAvatarWithOptionalStatus.displayName = 'SignInOrAvatarWithOptionalStatus';
+export default compose(
+ withCurrentUserPersonalDetails,
+ withOnyx({
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ }),
+)(SignInOrAvatarWithOptionalStatus);
diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js
index d896fc2fed63..0d7e1c09454d 100644
--- a/src/pages/settings/Profile/CustomStatus/StatusPage.js
+++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js
@@ -37,6 +37,12 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) {
const customStatus = draftEmojiCode ? `${draftEmojiCode} ${draftText}` : `${currentUserEmojiCode || ''} ${currentUserStatusText || ''}`;
const hasDraftStatus = !!draftEmojiCode || !!draftText;
+ const clearStatus = () => {
+ User.clearCustomStatus();
+ User.clearDraftCustomStatus();
+ };
+
+ const navigateBackToSettingsPage = useCallback(() => Navigation.goBack(ROUTES.SETTINGS_PROFILE, false, true), []);
const updateStatus = useCallback(() => {
const endOfDay = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss');
User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay});
@@ -44,12 +50,6 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) {
User.clearDraftCustomStatus();
Navigation.goBack(ROUTES.SETTINGS_PROFILE);
}, [defaultText, defaultEmoji]);
-
- const clearStatus = () => {
- User.clearCustomStatus();
- User.clearDraftCustomStatus();
- };
-
const footerComponent = useMemo(
() =>
hasDraftStatus ? (
@@ -67,7 +67,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) {
return (
Navigation.goBack(ROUTES.SETTINGS_PROFILE)}
+ onBackButtonPress={navigateBackToSettingsPage}
backgroundColor={themeColors.midtone}
image={MobileBackgroundImage}
footer={footerComponent}
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 03ff84eb0665..d893777734e1 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -3786,6 +3786,27 @@ const styles = {
transform: [{rotate: '90deg'}],
},
+ emojiStatusLHN: {
+ fontSize: 22,
+ },
+ sidebarStatusAvatarContainer: {
+ height: 44,
+ width: 84,
+ backgroundColor: themeColors.componentBG,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ borderRadius: 42,
+ paddingHorizontal: 2,
+ marginVertical: -2,
+ marginRight: -2,
+ },
+ sidebarStatusAvatar: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+
moneyRequestViewImage: {
...spacing.mh5,
...spacing.mv3,
@@ -3822,7 +3843,6 @@ const styles = {
...flex.flex1,
borderRadius: variables.componentBorderRadiusLarge,
},
-
userReportStatusEmoji: {
fontSize: variables.fontSizeNormal,
marginRight: 4,