From b7b890d8463afdebae080d3bf7d47031bb3db159 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 11 Aug 2023 12:01:12 +0200 Subject: [PATCH 01/25] add status details to personalDetailsSelector --- src/components/LHNOptionsList/OptionRowLHNData.js | 1 + src/libs/SidebarUtils.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 5b010593f1e7..21fade6eb942 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -137,6 +137,7 @@ const personalDetailsSelector = (personalDetails) => 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/SidebarUtils.js b/src/libs/SidebarUtils.js index 561d91a63a1f..665e10c2d917 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -263,6 +263,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. @@ -352,6 +353,10 @@ 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; + } return result; } From 8e2e717c592534f7c6ded1d3e6997dcb6524e406 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 11 Aug 2023 12:31:18 +0200 Subject: [PATCH 02/25] add status next to user name --- src/components/LHNOptionsList/OptionRowLHN.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index a3fbb5e41378..26af48e5f72f 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -2,6 +2,7 @@ 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'; @@ -22,12 +23,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 +60,7 @@ const defaultProps = { style: null, optionItem: null, isFocused: false, + betas: [], }; function OptionRowLHN(props) { @@ -124,6 +131,10 @@ function OptionRowLHN(props) { ); }; + const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); + const statusContent = lodashGet(optionItem, 'status.text', ''); + const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && emojiCode && ReportUtils.isShowEmojiStatus(optionItem); + return ( + {isStatusVisible && ( + + {`${emojiCode}`} + + )} {optionItem.alternateText ? ( Date: Fri, 11 Aug 2023 13:36:09 +0200 Subject: [PATCH 03/25] add status to sidebar link --- src/components/AvatarWithIndicator.js | 27 ++++++++++++++--- src/libs/ReportUtils.js | 19 ++++++++++++ src/pages/home/sidebar/SidebarLinks.js | 42 ++++++++++++++++++++------ src/styles/styles.js | 19 ++++++++++++ 4 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/components/AvatarWithIndicator.js b/src/components/AvatarWithIndicator.js index b59f67a45b7b..e8768778130d 100644 --- a/src/components/AvatarWithIndicator.js +++ b/src/components/AvatarWithIndicator.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import Avatar from './Avatar'; import styles from '../styles/styles'; import Tooltip from './Tooltip'; +import Text from './Text'; import * as UserUtils from '../libs/UserUtils'; import Indicator from './Indicator'; @@ -13,18 +14,34 @@ const propTypes = { /** To show a tooltip on hover */ tooltipText: PropTypes.string, + + /** Persons emojy status */ + emojiStatus: PropTypes.string, }; const defaultProps = { tooltipText: '', + emojiStatus: '', }; -function AvatarWithIndicator(props) { +function AvatarWithIndicator({tooltipText, emojiStatus, source}) { return ( - - - - + + + {!!emojiStatus && ( + + + {emojiStatus} + + + )} + + + + ); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index e8ef18a3ca27..39138266c8a2 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1162,6 +1162,24 @@ function isWaitingForIOUActionFromCurrentUser(report, allReportsDict = null) { return false; } +/** + * Should return true only for personal 1:1 report + * + * @param {Object} report (chatReport or iouReport) + * @returns {boolean} + */ +function isShowEmojiStatus(report) { + return ( + !report.isThread && + !report.isChatRoom && + !report.isExpenseRequest && + !report.isIOUReportOwner && + !report.isMoneyRequestReport && + !report.isPolicyExpenseChat && + !report.isTaskReport && + !report.isThread + ); +} function isWaitingForTaskCompleteFromAssignee(report) { return isTaskReport(report) && isTaskAssignee(report) && isOpenTaskReport(report); @@ -3024,4 +3042,5 @@ export { shouldDisableSettings, shouldDisableRename, hasSingleParticipant, + isShowEmojiStatus, }; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 5747ed0e1d4a..24529f192605 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -4,6 +4,7 @@ import React from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; import ONYXKEYS from '../../../ONYXKEYS'; @@ -36,6 +37,7 @@ import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import onyxSubscribe from '../../../libs/onyxSubscribe'; import personalDetailsPropType from '../../personalDetailsPropType'; import * as ReportActionContextMenu from '../report/ContextMenu/ReportActionContextMenu'; +import Permissions from '../../../libs/Permissions'; const basePropTypes = { /** Toggles the navigation menu open and closed */ @@ -59,6 +61,8 @@ const propTypes = { priorityMode: PropTypes.oneOf(_.values(CONST.PRIORITY_MODE)), + betas: PropTypes.arrayOf(PropTypes.string), + ...withLocalizePropTypes, }; @@ -67,6 +71,7 @@ const defaultProps = { avatar: '', }, priorityMode: CONST.PRIORITY_MODE.DEFAULT, + betas: [], }; class SidebarLinks extends React.PureComponent { @@ -74,7 +79,7 @@ class SidebarLinks extends React.PureComponent { super(props); this.showSearchPage = this.showSearchPage.bind(this); - this.showSettingsPage = this.showSettingsPage.bind(this); + this.onHandlePressAvatar = this.onHandlePressAvatar.bind(this); this.showReportPage = this.showReportPage.bind(this); if (this.props.isSmallScreenWidth) { @@ -124,22 +129,26 @@ class SidebarLinks extends React.PureComponent { } } - showSearchPage() { + onHandlePressAvatar() { if (this.props.isCreateMenuOpen) { - // Prevent opening Search page when click Search icon quickly after clicking FAB icon + // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon return; } - - Navigation.navigate(ROUTES.SEARCH); + const emojiCode = lodashGet(this.props.currentUserPersonalDetails, 'status.emojiCode', ''); + if (emojiCode && Permissions.canUseCustomStatus(this.props.betas)) { + Navigation.navigate(ROUTES.SETTINGS_STATUS); + } else { + Navigation.navigate(ROUTES.SETTINGS); + } } - showSettingsPage() { + showSearchPage() { if (this.props.isCreateMenuOpen) { - // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon + // Prevent opening Search page when click Search icon quickly after clicking FAB icon return; } - Navigation.navigate(ROUTES.SETTINGS); + Navigation.navigate(ROUTES.SEARCH); } /** @@ -161,6 +170,9 @@ class SidebarLinks extends React.PureComponent { } render() { + const statusEmojiCode = lodashGet(this.props.currentUserPersonalDetails, 'status.emojiCode', ''); + const emojiStatus = Permissions.canUseCustomStatus(this.props.betas) ? statusEmojiCode : ''; + return ( {Session.isAnonymousUser() ? ( @@ -207,6 +219,7 @@ class SidebarLinks extends React.PureComponent { )} @@ -230,5 +243,14 @@ class SidebarLinks extends React.PureComponent { SidebarLinks.propTypes = propTypes; SidebarLinks.defaultProps = defaultProps; -export default compose(withLocalize, withCurrentUserPersonalDetails, withWindowDimensions)(SidebarLinks); +export default compose( + withLocalize, + withCurrentUserPersonalDetails, + withWindowDimensions, + withOnyx({ + betas: { + key: ONYXKEYS.BETAS, + }, + }), +)(SidebarLinks); export {basePropTypes}; diff --git a/src/styles/styles.js b/src/styles/styles.js index 2d5514d6df39..5fce4ef3b021 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3681,6 +3681,25 @@ const styles = { rotate90: { 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, + }, + sidebarStatusAvatar: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, }; export default styles; From 2550706dce9754705e943375bf324f85b78b1fb7 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 11 Aug 2023 13:40:05 +0200 Subject: [PATCH 04/25] remove unused code --- src/components/LHNOptionsList/OptionRowLHN.js | 2 +- .../home/sidebar/SidebarScreen/BaseSidebarScreen.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 26af48e5f72f..47a6a5e47e29 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -134,7 +134,7 @@ function OptionRowLHN(props) { const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); const statusContent = lodashGet(optionItem, 'status.text', ''); const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && emojiCode && ReportUtils.isShowEmojiStatus(optionItem); - + return ( { - Navigation.navigate(ROUTES.SETTINGS); -}; - /** * Function called when a pinned chat is selected. */ @@ -50,7 +41,6 @@ function BaseSidebarScreen(props) { From 329ef939c332be03999605b3707df642820fde77 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 11 Aug 2023 17:17:43 +0200 Subject: [PATCH 05/25] add time in status --- src/components/LHNOptionsList/OptionRowLHN.js | 6 +++- src/libs/DateUtils.js | 33 +++++++++++++++++++ .../Profile/CustomStatus/StatusPage.js | 4 +-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 47a6a5e47e29..1aa173477bbe 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -6,6 +6,7 @@ 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'; @@ -132,7 +133,10 @@ function OptionRowLHN(props) { }; const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); - const statusContent = lodashGet(optionItem, 'status.text', ''); + 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.isShowEmojiStatus(optionItem); return ( diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 6be627dc643d..04bd32c5b98a 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -205,6 +205,38 @@ function getDateStringFromISOTimestamp(isoTimestamp) { return dateString; } +/** + * receive date like 2020-05-16 05:34:14 and format it to show in string like "Until 05:34 PM" + * + * @param {String} inputDate + * @returns {String} + */ +function getStatusUntilDate(inputDate) { + if (!inputDate) return ''; + + const input = moment(inputDate, 'YYYY-MM-DD HH:mm:ss'); + const now = moment(); + const endOfToday = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); + + // If the date is equal to the end of today + if (input.isSame(endOfToday)) { + return 'Until tomorrow'; + } + + // If it's a time on the same date + if (input.isSame(now, 'day')) { + return `Until ${input.format('hh:mm A')}`; + } + + // If it's further in the future than tomorrow but within the same year + if (input.isAfter(now) && input.isSame(now, 'year')) { + return `Until ${input.format('MM-DD hh:mm A')}`; + } + + // If it's in another year + return `Until ${input.format('YYYY-MM-DD hh:mm A')}`; +} + /** * @namespace DateUtils */ @@ -220,6 +252,7 @@ const DateUtils = { getDBTime, subtractMillisecondsFromDateTime, getDateStringFromISOTimestamp, + getStatusUntilDate, }; export default DateUtils; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index 9767825820cd..061997e79044 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -38,8 +38,8 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const hasDraftStatus = !!draftEmojiCode || !!draftText; const updateStatus = useCallback(() => { - const endOfDay = moment().endOf('day').toDate(); - User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay.toISOString()}); + const endOfDay = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); + User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay}); User.clearDraftCustomStatus(); Navigation.goBack(ROUTES.SETTINGS); From 9942d916a1b84a75ac6dedf393baba85a4804069 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 15 Aug 2023 11:24:11 +0200 Subject: [PATCH 06/25] split status and profile buttons --- src/pages/home/sidebar/SidebarLinks.js | 129 +++++++++++++++++-------- 1 file changed, 90 insertions(+), 39 deletions(-) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index d291d22cd3e6..1d17a353a7d5 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -32,6 +32,7 @@ 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 Text from '../../../components/Text'; import * as UserUtils from '../../../libs/UserUtils'; import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import onyxSubscribe from '../../../libs/onyxSubscribe'; @@ -92,7 +93,8 @@ class SidebarLinks extends React.PureComponent { super(props); this.showSearchPage = this.showSearchPage.bind(this); - this.onHandlePressAvatar = this.onHandlePressAvatar.bind(this); + this.showSettingsPage = this.showSettingsPage.bind(this); + this.showStatusPage = this.showStatusPage.bind(this); this.showReportPage = this.showReportPage.bind(this); if (this.props.isSmallScreenWidth) { @@ -142,19 +144,6 @@ class SidebarLinks extends React.PureComponent { } } - onHandlePressAvatar() { - if (this.props.isCreateMenuOpen) { - // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon - return; - } - const emojiCode = lodashGet(this.props.currentUserPersonalDetails, 'status.emojiCode', ''); - if (emojiCode && Permissions.canUseCustomStatus(this.props.betas)) { - Navigation.navigate(ROUTES.SETTINGS_STATUS); - } else { - Navigation.navigate(ROUTES.SETTINGS); - } - } - showSearchPage() { if (this.props.isCreateMenuOpen) { // Prevent opening Search page when click Search icon quickly after clicking FAB icon @@ -182,9 +171,94 @@ class SidebarLinks extends React.PureComponent { this.props.onLinkClick(); } - render() { + showSettingsPage() { + if (this.props.isCreateMenuOpen) { + // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon + return; + } + + Navigation.navigate(ROUTES.SETTINGS); + } + + showStatusPage() { + if (this.props.isCreateMenuOpen) { + // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon + return; + } + + Navigation.setShouldPopAllStateOnUP(); + Navigation.navigate(ROUTES.SETTINGS_STATUS); + } + + renderAvatarWithIndicator() { + return ( + + + + + + ); + } + + /** + * Render either a sign-in button or an avatar with optional status. + * @returns {React.Component} + */ + renderSignInOrAvatarWithOptionalStatus() { const statusEmojiCode = lodashGet(this.props.currentUserPersonalDetails, 'status.emojiCode', ''); const emojiStatus = Permissions.canUseCustomStatus(this.props.betas) ? statusEmojiCode : ''; + if (Session.isAnonymousUser()) { + return ( + + +