Skip to content

Commit

Permalink
Merge pull request #18701 from dukenv0307/fix/18554
Browse files Browse the repository at this point in the history
Fix/18554: Update reaction dynamically in the reaction popup
  • Loading branch information
cristipaval authored May 17, 2023
2 parents 02fff92 + 92adcb6 commit a108f42
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 23 deletions.
8 changes: 5 additions & 3 deletions src/components/Reactions/ReportActionItemReactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import emojis from '../../../assets/emojis';
import AddReactionBubble from './AddReactionBubble';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../withCurrentUserPersonalDetails';
import getPreferredEmojiCode from './getPreferredEmojiCode';
import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils';
import * as Report from '../../libs/actions/Report';
import * as ReactionList from '../../pages/home/report/ReactionList/ReactionList';
import Tooltip from '../Tooltip';
Expand Down Expand Up @@ -50,6 +49,9 @@ const propTypes = {
// eslint-disable-next-line react/forbid-prop-types
reactions: PropTypes.arrayOf(PropTypes.object).isRequired,

/** The ID of the reportAction. It is the string representation of the a 64-bit integer. */
reportActionID: PropTypes.string.isRequired,

/**
* Function to call when the user presses on an emoji.
* This can also be an emoji the user already reacted with,
Expand Down Expand Up @@ -83,9 +85,9 @@ const ReportActionItemReactions = (props) => {
const onPress = () => {
props.toggleReaction(emoji);
};

const onReactionListOpen = (event) => {
const users = PersonalDetailsUtils.getPersonalDetailsByIDs(reactionUsers, props.currentUserPersonalDetails.accountID);
ReactionList.showReactionList(event, popoverReactionListAnchor.current, users, reaction.emoji, emojiCodes, reactionCount, hasUserReacted);
ReactionList.showReactionList(event, popoverReactionListAnchor.current, reaction.emoji, props.reportActionID);
};

return (
Expand Down
155 changes: 142 additions & 13 deletions src/pages/home/report/ReactionList/PopoverReactionList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,58 @@ import React from 'react';
import {Dimensions} from 'react-native';

import lodashGet from 'lodash/get';
import lodashMap from 'lodash/map';
import lodashFilter from 'lodash/filter';
import lodashFind from 'lodash/find';
import lodashEach from 'lodash/each';
import lodashIsEqual from 'lodash/isEqual';

import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import * as Report from '../../../../libs/actions/Report';
import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent';

import BaseReactionList from './BaseReactionList';
import compose from '../../../../libs/compose';
import reportActionPropTypes from '../reportActionPropTypes';
import ONYXKEYS from '../../../../ONYXKEYS';
import getPreferredEmojiCode from '../../../../components/Reactions/getPreferredEmojiCode';
import withCurrentUserPersonalDetails from '../../../../components/withCurrentUserPersonalDetails';
import * as PersonalDetailsUtils from '../../../../libs/PersonalDetailsUtils';
import emojis from '../../../../../assets/emojis';

const propTypes = {
/** Actions from the ChatReport */
reportActions: PropTypes.shape(reportActionPropTypes),

...withLocalizePropTypes,
};

const defaultProps = {
reportActions: {},
};

/**
* Given an emoji object and a list of senders it will return an
* array of emoji codes, that represents all used variations of the
* emoji.
* @param {{ name: string, code: string, types: string[] }} emoji
* @param {Array} users
* @return {string[]}
* */
const getUniqueEmojiCodes = (emoji, users) => {
const emojiCodes = [];
lodashEach(users, (user) => {
const emojiCode = getPreferredEmojiCode(emoji, user.skinTone);

if (emojiCode && !emojiCodes.includes(emojiCode)) {
emojiCodes.push(emojiCode);
}
});
return emojiCodes;
};

class PopoverReactionList extends React.Component {
constructor(props) {
super(props);
Expand All @@ -32,19 +75,19 @@ class PopoverReactionList extends React.Component {
emojiName: '',
emojiCount: 0,
hasUserReacted: false,
reportActionID: '',
};

this.onPopoverHideActionCallback = () => {};
this.reactionListAnchor = undefined;
this.showReactionList = this.showReactionList.bind(this);

this.hideReactionList = this.hideReactionList.bind(this);
this.measureContent = this.measureContent.bind(this);
this.measureReactionListPosition = this.measureReactionListPosition.bind(this);
this.getReactionListMeasuredLocation = this.getReactionListMeasuredLocation.bind(this);

this.getSelectedReaction = this.getSelectedReaction.bind(this);
this.getReactionInformation = this.getReactionInformation.bind(this);
this.dimensionsEventListener = null;

this.contentRef = React.createRef();
}

Expand All @@ -53,9 +96,44 @@ class PopoverReactionList extends React.Component {
}

shouldComponentUpdate(nextProps, nextState) {
const prevSelectedReaction = this.getSelectedReaction(this.props.reportActions, this.state.reportActionID, this.state.emojiName);
const selectedReaction = this.getSelectedReaction(nextProps.reportActions, nextState.reportActionID, nextState.emojiName);
const previousLocale = lodashGet(this.props, 'preferredLocale', 'en');
const nextLocale = lodashGet(nextProps, 'preferredLocale', 'en');
return this.state.isPopoverVisible !== nextState.isPopoverVisible || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition || previousLocale !== nextLocale;

return (
this.state.isPopoverVisible !== nextState.isPopoverVisible ||
this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition ||
previousLocale !== nextLocale ||
(this.state.isPopoverVisible &&
(!lodashIsEqual(prevSelectedReaction, selectedReaction) ||
this.state.emojiName !== nextState.emojiName ||
this.state.emojiCount !== nextState.emojiCount ||
this.state.hasUserReacted !== nextState.hasUserReacted ||
this.state.reportActionID !== nextState.reportActionID ||
!lodashIsEqual(this.state.emojiCodes, nextState.emojiCodes) ||
!lodashIsEqual(this.state.users, nextState.users)))
);
}

componentDidUpdate() {
if (!this.state.emojiName) {
return;
}
const selectedReaction = this.getSelectedReaction(this.props.reportActions, this.state.reportActionID, this.state.emojiName);
if (!selectedReaction) {
this.setState({
isPopoverVisible: false,
});
} else {
const {emojiCount, emojiCodes, hasUserReacted, users} = this.getReactionInformation(selectedReaction);
this.setState({
users,
emojiCodes,
emojiCount,
hasUserReacted,
});
}
}

componentWillUnmount() {
Expand All @@ -81,23 +159,63 @@ class PopoverReactionList extends React.Component {
});
}

/**
* Get the selected reaction.
*
* @param {Array<Object>} reportActions
* @param {String} reportActionID
* @param {String} emojiName - Name of emoji
* @returns {Object}
*/
getSelectedReaction(reportActions, reportActionID, emojiName) {
const reportAction = lodashFind(reportActions, (action) => action.reportActionID === reportActionID);
const reactions = lodashGet(reportAction, ['message', 0, 'reactions'], []);
const reactionsWithCount = lodashFilter(reactions, (reaction) => reaction.users.length > 0);
return lodashFind(reactionsWithCount, (reaction) => reaction.emoji === emojiName);
}

/**
* Get the reaction information.
*
* @param {Object} selectedReaction
* @returns {Object}
*/
getReactionInformation(selectedReaction) {
if (!selectedReaction) {
return {
users: [],
emojiCodes: [],
emojiName: '',
emojiCount: 0,
};
}
const emojiCount = selectedReaction.users.length;
const reactionUsers = lodashMap(selectedReaction.users, (sender) => sender.accountID.toString());
const emoji = lodashFind(emojis, (e) => e.name === selectedReaction.emoji);
const emojiCodes = getUniqueEmojiCodes(emoji, selectedReaction.users);
const hasUserReacted = Report.hasAccountIDReacted(this.props.currentUserPersonalDetails.accountID, reactionUsers);
const users = PersonalDetailsUtils.getPersonalDetailsByIDs(reactionUsers);
return {
emojiCount,
emojiCodes,
hasUserReacted,
users,
};
}

/**
* Show the ReactionList modal popover.
*
* @param {Object} [event] - A press event.
* @param {Element} reactionListAnchor - reactionListAnchor
* @param {Array} users - Array of personal detail objects
* @param {String} emojiName - Name of emoji
* @param {Array} emojiCodes - The emoji codes to display in the bubble.
* @param {Number} emojiCount - Count of emoji
* @param {Boolean} hasUserReacted - whether the current user has reacted to this emoji
* @param {String} reportActionID
*/
showReactionList(event, reactionListAnchor, users, emojiName, emojiCodes, emojiCount, hasUserReacted) {
showReactionList(event, reactionListAnchor, emojiName, reportActionID) {
const nativeEvent = event.nativeEvent || {};

this.reactionListAnchor = reactionListAnchor;

const selectedReaction = this.getSelectedReaction(this.props.reportActions, reportActionID, emojiName);
const {emojiCount, emojiCodes, hasUserReacted, users} = this.getReactionInformation(selectedReaction);
this.getReactionListMeasuredLocation().then(({x, y}) => {
this.setState({
cursorRelativePosition: {
Expand All @@ -114,6 +232,7 @@ class PopoverReactionList extends React.Component {
emojiCount,
isPopoverVisible: true,
hasUserReacted,
reportActionID,
});
});
}
Expand Down Expand Up @@ -198,5 +317,15 @@ class PopoverReactionList extends React.Component {
}

PopoverReactionList.propTypes = propTypes;
PopoverReactionList.defaultProps = defaultProps;

export default withLocalize(PopoverReactionList);
export default compose(
withLocalize,
withOnyx({
reportActions: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
canEvict: false,
},
}),
withCurrentUserPersonalDetails,
)(PopoverReactionList);
10 changes: 4 additions & 6 deletions src/pages/home/report/ReactionList/ReactionList.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ const reactionListRef = React.createRef();
*
* @param {Object} [event] - a press event.
* @param {Element} reactionListPopoverAnchor - popoverAnchor
* @param {Array} users - array of users id
* @param {String} emojiName - the emoji codes to display near the bubble.
* @param {String} emojiCodes - the emoji codes to display in the bubble.
* @param {Number} emojiCount - count of emoji
* @param {Boolean} hasUserReacted - show if user has reacted
* @param {String} reportActionID
*/
function showReactionList(event, reactionListPopoverAnchor, users, emojiName, emojiCodes, emojiCount, hasUserReacted) {
function showReactionList(event, reactionListPopoverAnchor, emojiName, reportActionID) {
if (!reactionListRef.current) {
return;
}
reactionListRef.current.showReactionList(event, reactionListPopoverAnchor, users, emojiName, emojiCodes, emojiCount, hasUserReacted);

reactionListRef.current.showReactionList(event, reactionListPopoverAnchor, emojiName, reportActionID);
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class ReportActionItem extends Component {
{hasReactions && (
<View style={this.props.draftMessage ? styles.chatItemReactionsDraftRight : {}}>
<ReportActionItemReactions
reportActionID={this.props.action.reportActionID}
reactions={reactions}
toggleReaction={this.toggleReaction}
/>
Expand Down
5 changes: 4 additions & 1 deletion src/pages/home/report/ReportActionsView.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,10 @@ class ReportActionsView extends React.Component {
loadMoreChats={this.loadMoreChats}
newMarkerReportActionID={this.state.newMarkerReportActionID}
/>
<PopoverReactionList ref={ReactionList.reactionListRef} />
<PopoverReactionList
ref={ReactionList.reactionListRef}
reportID={this.props.report.reportID}
/>
<CopySelectionHelper />
</>
);
Expand Down

0 comments on commit a108f42

Please sign in to comment.