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

Fix frequently used emojis list doesn't get updated when adding emojis by typing emoji code with colon #18396

16 changes: 3 additions & 13 deletions src/components/EmojiPicker/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ const propTypes = {
/** Stores user's preferred skin tone */
preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

/** User's frequently used emojis */
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string.isRequired,
keywords: PropTypes.arrayOf(PropTypes.string),
})),

/** Props related to the dimensions of the window */
...windowDimensionsPropTypes,

Expand All @@ -47,7 +41,6 @@ const propTypes = {
const defaultProps = {
forwardedRef: () => {},
preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
frequentlyUsedEmojis: [],
};

class EmojiPickerMenu extends Component {
Expand All @@ -64,8 +57,8 @@ class EmojiPickerMenu extends Component {
// since Windows doesn't support them
const flagHeaderIndex = _.findIndex(emojis, emoji => emoji.header && emoji.code === 'flags');
this.emojis = getOperatingSystem() === CONST.OS.WINDOWS
? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis.slice(0, flagHeaderIndex), this.props.frequentlyUsedEmojis)
: EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis, this.props.frequentlyUsedEmojis);
? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis.slice(0, flagHeaderIndex))
: EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis);

// Get the header emojis along with the code, index and icon.
// index is the actual header index starting at the first emoji and counting each one
Expand Down Expand Up @@ -234,7 +227,7 @@ class EmojiPickerMenu extends Component {
* @param {Object} emojiObject
*/
addToFrequentAndSelectEmoji(emoji, emojiObject) {
EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject);
EmojiUtils.addToFrequentlyUsedEmojis(emojiObject);
this.props.onEmojiSelected(emoji, emojiObject);
}

Expand Down Expand Up @@ -569,9 +562,6 @@ export default compose(
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
},
frequentlyUsedEmojis: {
key: ONYXKEYS.FREQUENTLY_USED_EMOJIS,
},
}),
)(React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
Expand Down
14 changes: 2 additions & 12 deletions src/components/EmojiPicker/EmojiPickerMenu/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ const propTypes = {
/** Stores user's preferred skin tone */
preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

/** User's frequently used emojis */
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string.isRequired,
keywords: PropTypes.arrayOf(PropTypes.string),
})),

/** Props related to the dimensions of the window */
...windowDimensionsPropTypes,

Expand All @@ -40,7 +34,6 @@ const propTypes = {

const defaultProps = {
preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
frequentlyUsedEmojis: [],
};

class EmojiPickerMenu extends Component {
Expand All @@ -50,7 +43,7 @@ class EmojiPickerMenu extends Component {
// Ref for emoji FlatList
this.emojiList = undefined;

this.emojis = EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis, this.props.frequentlyUsedEmojis);
this.emojis = EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis);

// Get the header emojis along with the code, index and icon.
// index is the actual header index starting at the first emoji and counting each one
Expand All @@ -77,7 +70,7 @@ class EmojiPickerMenu extends Component {
* @param {Object} emojiObject
*/
addToFrequentAndSelectEmoji(emoji, emojiObject) {
EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject);
EmojiUtils.addToFrequentlyUsedEmojis(emojiObject);
this.props.onEmojiSelected(emoji, emojiObject);
}

Expand Down Expand Up @@ -190,9 +183,6 @@ export default compose(
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
},
frequentlyUsedEmojis: {
key: ONYXKEYS.FREQUENTLY_USED_EMOJIS,
},
}),
)(React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
Expand Down
69 changes: 46 additions & 23 deletions src/libs/EmojiUtils.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import _ from 'underscore';
import lodashOrderBy from 'lodash/orderBy';
import moment from 'moment';
import Str from 'expensify-common/lib/str';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
import * as User from './actions/User';
import emojisTrie from './EmojiTrie';
import FrequentlyUsed from '../../assets/images/history.svg';

let frequentlyUsedEmojis = [];
Onyx.connect({
key: ONYXKEYS.FREQUENTLY_USED_EMOJIS,
callback: val => frequentlyUsedEmojis = val,
bernhardoj marked this conversation as resolved.
Show resolved Hide resolved
});

/**
* Get the unicode code of an emoji in base 16.
* @param {String} input
Expand Down Expand Up @@ -139,10 +146,9 @@ function addSpacesToEmojiCategories(emojis) {
/**
* Get a merged array with frequently used emojis
* @param {Object[]} emojis
* @param {Object[]} frequentlyUsedEmojis
* @returns {Object[]}
*/
function mergeEmojisWithFrequentlyUsedEmojis(emojis, frequentlyUsedEmojis = []) {
function mergeEmojisWithFrequentlyUsedEmojis(emojis) {
if (frequentlyUsedEmojis.length === 0) {
return addSpacesToEmojiCategories(emojis);
}
Expand All @@ -159,29 +165,31 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis, frequentlyUsedEmojis = [])

/**
* Update the frequently used emojis list by usage and sync with API
* @param {Object[]} frequentlyUsedEmojis
* @param {Object} newEmoji
* @param {Object|Object[]} newEmoji
*/
function addToFrequentlyUsedEmojis(frequentlyUsedEmojis, newEmoji) {
let frequentEmojiList = frequentlyUsedEmojis;
let currentEmojiCount = 1;
const currentTimestamp = moment().unix();
const emojiIndex = _.findIndex(frequentEmojiList, e => e.code === newEmoji.code);
if (emojiIndex >= 0) {
currentEmojiCount = frequentEmojiList[emojiIndex].count + 1;
frequentEmojiList.splice(emojiIndex, 1);
}
const updatedEmoji = {...newEmoji, ...{count: currentEmojiCount, lastUpdatedAt: currentTimestamp}};
function addToFrequentlyUsedEmojis(newEmoji) {
bernhardoj marked this conversation as resolved.
Show resolved Hide resolved
let frequentEmojiList = [...frequentlyUsedEmojis];

const maxFrequentEmojiCount = (CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW) - 1;
const currentTimestamp = moment().unix();
_.each([].concat(newEmoji), (emoji) => {
let currentEmojiCount = 1;
const emojiIndex = _.findIndex(frequentEmojiList, e => e.code === emoji.code);
if (emojiIndex >= 0) {
currentEmojiCount = frequentEmojiList[emojiIndex].count + 1;
frequentEmojiList.splice(emojiIndex, 1);
}
const updatedEmoji = {...emoji, ...{count: currentEmojiCount, lastUpdatedAt: currentTimestamp}};

// We want to make sure the current emoji is added to the list
// Hence, we take one less than the current high frequent used emojis and if same then sorted by lastUpdatedAt
frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']);
frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount);
frequentEmojiList.push(updatedEmoji);
// We want to make sure the current emoji is added to the list
// Hence, we take one less than the current frequent used emojis
frequentEmojiList = frequentEmojiList.slice(0, maxFrequentEmojiCount);
frequentEmojiList.push(updatedEmoji);

// Sort the list by count and lastUpdatedAt in descending order
frequentEmojiList.sort((a, b) => b.count - a.count || b.lastUpdatedAt - a.lastUpdatedAt);
});

// Second sorting is required so that new emoji is properly placed at sort-ordered location
frequentEmojiList = lodashOrderBy(frequentEmojiList, ['count', 'lastUpdatedAt'], ['desc', 'desc']);
User.updateFrequentlyUsedEmojis(frequentEmojiList);
}

Expand All @@ -204,6 +212,9 @@ const getEmojiCodeWithSkinColor = (item, preferredSkinToneIndex) => {
/**
* Replace any emoji name in a text with the emoji icon.
* If we're on mobile, we also add a space after the emoji granted there's no text after it.
*
* All replaced emojis will be added to the frequently used emojis list.
*
* @param {String} text
* @param {Boolean} isSmallScreenWidth
* @param {Number} preferredSkinTone
Expand All @@ -215,10 +226,17 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON
if (!emojiData || emojiData.length === 0) {
bernhardoj marked this conversation as resolved.
Show resolved Hide resolved
return text;
}
const emojis = [];
for (let i = 0; i < emojiData.length; i++) {
const checkEmoji = emojisTrie.search(emojiData[i].slice(1, -1));
const name = emojiData[i].slice(1, -1);
const checkEmoji = emojisTrie.search(name);
if (checkEmoji && checkEmoji.metaData.code) {
let emojiReplacement = getEmojiCodeWithSkinColor(checkEmoji.metaData, preferredSkinTone);
emojis.push({
name,
code: checkEmoji.metaData.code,
types: checkEmoji.metaData.types,
});

// If this is the last emoji in the message and it's the end of the message so far,
// add a space after it so the user can keep typing easily.
Expand All @@ -228,6 +246,11 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON
newText = newText.replace(emojiData[i], emojiReplacement);
}
}

// Add all replaced emojis to the frequently used emojis list
if (!_.isEmpty(emojis)) {
addToFrequentlyUsedEmojis(emojis);
}
return newText;
}

Expand Down
12 changes: 1 addition & 11 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,6 @@ const propTypes = {
/** Stores user's preferred skin tone */
preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

/** User's frequently used emojis */
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string.isRequired,
keywords: PropTypes.arrayOf(PropTypes.string),
})),

/** The type of action that's pending */
pendingAction: PropTypes.oneOf(['add', 'update', 'delete']),

Expand All @@ -137,7 +131,6 @@ const defaultProps = {
blockedFromConcierge: {},
personalDetails: {},
preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
frequentlyUsedEmojis: [],
isComposerFullSize: false,
pendingAction: null,
...withCurrentUserPersonalDetailsDefaultProps,
Expand Down Expand Up @@ -519,7 +512,7 @@ class ReportActionCompose extends React.Component {
},
suggestedEmojis: [],
}));
EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject);
EmojiUtils.addToFrequentlyUsedEmojis(emojiObject);
}

isEmptyChat() {
Expand Down Expand Up @@ -1044,9 +1037,6 @@ export default compose(
blockedFromConcierge: {
key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE,
},
frequentlyUsedEmojis: {
key: ONYXKEYS.FREQUENTLY_USED_EMOJIS,
},
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
},
Expand Down
Loading