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

Display status emojis in the LHN (including the user's own status) #24414

Merged
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b7b890d
add status details to personalDetailsSelector
perunt Aug 11, 2023
8e2e717
add status next to user name
perunt Aug 11, 2023
cdeb9c2
add status to sidebar link
perunt Aug 11, 2023
2550706
remove unused code
perunt Aug 11, 2023
329ef93
add time in status
perunt Aug 11, 2023
276bfbf
Merge branch 'main' of https://github.com/margelo/expensify-app-fork …
perunt Aug 14, 2023
112d340
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 14, 2023
33ca589
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 14, 2023
9942d91
split status and profile buttons
perunt Aug 15, 2023
1095ae4
fix goBack on Status page
perunt Aug 15, 2023
0b640c7
add translation
perunt Aug 15, 2023
8eb30c0
fix margin
perunt Aug 15, 2023
3719c5d
clean AvatarWithIndicator
perunt Aug 15, 2023
d875895
update isPlainDirectMessage
perunt Aug 15, 2023
049fdf1
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 15, 2023
eb0e965
add canUseCustomStatus to mocks permissions
perunt Aug 15, 2023
62274c7
change translation
perunt Aug 16, 2023
127bebb
return genericEditFailureMessage
perunt Aug 16, 2023
2303985
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 17, 2023
f86e1db
create SignInOrAvatarWithOptionalStatus
perunt Aug 17, 2023
acd581c
create PressableAvatarWithIndicator
perunt Aug 17, 2023
17a9c88
clean SidebarLinks
perunt Aug 17, 2023
ca3fcc1
update isPlainDirectMessage
perunt Aug 17, 2023
924bf6e
update isStatusVisible
perunt Aug 17, 2023
0cbe2b3
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 17, 2023
5361750
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 18, 2023
8c1546a
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 20, 2023
416ce3d
add comments
perunt Aug 21, 2023
2216ec6
create separate component for SignInButton
perunt Aug 21, 2023
9126f58
rename isPlainDirectMessage to isOneOnOneChat
perunt Aug 22, 2023
5088e86
a bit more optimizations
perunt Aug 22, 2023
a866499
Merge branch 'main' of https://github.com/Expensify/App into feat/##2…
perunt Aug 22, 2023
4aea8a5
add participantsList check
perunt Aug 23, 2023
77dd8b2
add one more check for isOneOnOneChat
perunt Aug 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/components/LHNOptionsList/OptionRowLHN.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,

Expand All @@ -54,6 +61,7 @@ const defaultProps = {
style: null,
optionItem: null,
isFocused: false,
betas: [],
};

function OptionRowLHN(props) {
Expand Down Expand Up @@ -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 (
<OfflineWithFeedback
pendingAction={optionItem.pendingAction}
Expand Down Expand Up @@ -202,6 +217,14 @@ function OptionRowLHN(props) {
optionItem.isChatRoom || optionItem.isPolicyExpenseChat || optionItem.isTaskReport || optionItem.isThread || optionItem.isMoneyRequestReport
}
/>
{isStatusVisible && (
<Tooltip
text={statusContent}
shiftVertical={-4}
>
<Text style={styles.ml1}>{emojiCode}</Text>
</Tooltip>
)}
</View>
{optionItem.alternateText ? (
<Text
Expand Down
1 change: 1 addition & 0 deletions src/components/LHNOptionsList/OptionRowLHNData.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2503,6 +2503,25 @@ 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) {
return (
!isThread(report) &&
!isChatRoom(report) &&
!isExpenseRequest(report) &&
!isMoneyRequestReport(report) &&
!isPolicyExpenseChat(report) &&
!isTaskReport(report) && //
isDM(report) &&
!isIOUReport(report)
);
}

/**
* 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.
Expand Down Expand Up @@ -3398,6 +3417,7 @@ export {
shouldDisableSettings,
shouldDisableRename,
hasSingleParticipant,
isOneOnOneChat,
getTransactionReportName,
getTransactionDetails,
getTaskAssigneeChatOnyxData,
Expand Down
7 changes: 7 additions & 0 deletions src/libs/SidebarUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions src/libs/__mocks__/Permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
65 changes: 65 additions & 0 deletions src/pages/home/sidebar/AvatarWithOptionalStatus.js
Original file line number Diff line number Diff line change
@@ -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 (
<View style={styles.sidebarStatusAvatarContainer}>
<PressableWithoutFeedback
accessibilityLabel={translate('sidebarScreen.buttonMySettings')}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
onPress={showStatusPage}
style={styles.flex1}
>
<View style={styles.sidebarStatusAvatar}>
<Text
style={styles.emojiStatusLHN}
numberOfLines={1}
>
{emojiStatus}
</Text>
</View>
</PressableWithoutFeedback>
<PressableAvatarWithIndicator isCreateMenuOpen={isCreateMenuOpen} />
</View>
);
}

AvatarWithOptionalStatus.propTypes = propTypes;
AvatarWithOptionalStatus.defaultProps = defaultProps;
AvatarWithOptionalStatus.displayName = 'AvatarWithOptionalStatus';
export default AvatarWithOptionalStatus;
64 changes: 64 additions & 0 deletions src/pages/home/sidebar/PressableAvatarWithIndicator.js
Original file line number Diff line number Diff line change
@@ -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,
perunt marked this conversation as resolved.
Show resolved Hide resolved
};

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 (
<PressableWithoutFeedback
accessibilityLabel={translate('sidebarScreen.buttonMySettings')}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
onPress={showSettingsPage}
>
<OfflineWithFeedback pendingAction={lodashGet(currentUserPersonalDetails, 'pendingFields.avatar', null)}>
<AvatarWithIndicator
source={UserUtils.getAvatar(currentUserPersonalDetails.avatar, currentUserPersonalDetails.accountID)}
tooltipText={translate('common.settings')}
/>
</OfflineWithFeedback>
</PressableWithoutFeedback>
);
}

PressableAvatarWithIndicator.propTypes = propTypes;
PressableAvatarWithIndicator.defaultProps = defaultProps;
perunt marked this conversation as resolved.
Show resolved Hide resolved
PressableAvatarWithIndicator.displayName = 'PressableAvatarWithIndicator';
export default withCurrentUserPersonalDetails(PressableAvatarWithIndicator);
47 changes: 2 additions & 45 deletions src/pages/home/sidebar/SidebarLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,26 @@ 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';
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 */
Expand All @@ -58,8 +53,6 @@ const propTypes = {

isLoading: PropTypes.bool.isRequired,

currentUserPersonalDetails: personalDetailsPropType,

priorityMode: PropTypes.oneOf(_.values(CONST.PRIORITY_MODE)),

/** The top most report id */
Expand All @@ -75,9 +68,6 @@ const propTypes = {
};

const defaultProps = {
currentUserPersonalDetails: {
avatar: '',
},
priorityMode: CONST.PRIORITY_MODE.DEFAULT,
currentReportID: '',
report: {},
Expand All @@ -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) {
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -204,29 +184,7 @@ class SidebarLinks extends React.PureComponent {
<Icon src={Expensicons.MagnifyingGlass} />
</PressableWithoutFeedback>
</Tooltip>
<PressableWithoutFeedback
accessibilityLabel={this.props.translate('sidebarScreen.buttonMySettings')}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
onPress={Session.checkIfActionIsAllowed(this.showSettingsPage)}
>
{Session.isAnonymousUser() ? (
<View style={styles.signInButtonAvatar}>
<Button
medium
success
text={this.props.translate('common.signIn')}
onPress={() => Session.signOutAndRedirectToSignIn()}
/>
</View>
) : (
<OfflineWithFeedback pendingAction={lodashGet(this.props.currentUserPersonalDetails, 'pendingFields.avatar', null)}>
<AvatarWithIndicator
source={UserUtils.getAvatar(this.props.currentUserPersonalDetails.avatar, this.props.currentUserPersonalDetails.accountID)}
tooltipText={this.props.translate('common.settings')}
/>
</OfflineWithFeedback>
)}
</PressableWithoutFeedback>
<SignInOrAvatarWithOptionalStatus isCreateMenuOpen={this.props.isCreateMenuOpen} />
Copy link
Contributor

@WoLewicki WoLewicki Sep 12, 2023

Choose a reason for hiding this comment

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

@perunt could you elaborate where does this.props.isCreateMenuOpen come from? Or is it always undefined?

Copy link
Contributor Author

@perunt perunt Sep 12, 2023

Choose a reason for hiding this comment

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

Hmm, tbh I didn't dig where it comes from but now I can see that it was undefined
I see that it was removed (isCreateMenuOpen) without cleaning it inside the component
image

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok thanks for the input.

</View>
{this.props.isLoading ? (
<>
Expand Down Expand Up @@ -258,7 +216,6 @@ SidebarLinks.propTypes = propTypes;
SidebarLinks.defaultProps = defaultProps;
export default compose(
withLocalize,
withCurrentUserPersonalDetails,
withWindowDimensions,
withCurrentReportID,
withOnyx({
Expand Down
Loading