diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 94023227867f..82a85c4cd622 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -227,7 +227,8 @@ class EmojiPickerMenu extends Component { * @param {Object} emojiObject */ addToFrequentAndSelectEmoji(emoji, emojiObject) { - EmojiUtils.addToFrequentlyUsedEmojis(emojiObject); + const frequentEmojiList = EmojiUtils.getFrequentlyUsedEmojis(emojiObject); + User.updateFrequentlyUsedEmojis(frequentEmojiList); this.props.onEmojiSelected(emoji, emojiObject); } diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js index ed8592fadc8f..caace14505ef 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js @@ -70,7 +70,8 @@ class EmojiPickerMenu extends Component { * @param {Object} emojiObject */ addToFrequentAndSelectEmoji(emoji, emojiObject) { - EmojiUtils.addToFrequentlyUsedEmojis(emojiObject); + const frequentEmojiList = EmojiUtils.getFrequentlyUsedEmojis(emojiObject); + User.updateFrequentlyUsedEmojis(frequentEmojiList); this.props.onEmojiSelected(emoji, emojiObject); } diff --git a/src/libs/EmojiUtils.js b/src/libs/EmojiUtils.js index f8954e389d57..a3133ce21006 100644 --- a/src/libs/EmojiUtils.js +++ b/src/libs/EmojiUtils.js @@ -4,7 +4,6 @@ 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'; @@ -170,10 +169,11 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis) { } /** - * Update the frequently used emojis list by usage and sync with API + * Get the updated frequently used emojis list by usage * @param {Object|Object[]} newEmoji + * @return {Object[]} */ -function addToFrequentlyUsedEmojis(newEmoji) { +function getFrequentlyUsedEmojis(newEmoji) { let frequentEmojiList = [...frequentlyUsedEmojis]; const maxFrequentEmojiCount = CONST.EMOJI_FREQUENT_ROW_COUNT * CONST.EMOJI_NUM_PER_ROW - 1; @@ -196,7 +196,7 @@ function addToFrequentlyUsedEmojis(newEmoji) { frequentEmojiList.sort((a, b) => b.count - a.count || b.lastUpdatedAt - a.lastUpdatedAt); }); - User.updateFrequentlyUsedEmojis(frequentEmojiList); + return frequentEmojiList; } /** @@ -219,20 +219,18 @@ 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 - * @returns {String} + * @returns {Object} */ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE) { let newText = text; + const emojis = []; const emojiData = text.match(CONST.REGEX.EMOJI_NAME); if (!emojiData || emojiData.length === 0) { - return text; + return {text: newText, emojis}; } - const emojis = []; for (let i = 0; i < emojiData.length; i++) { const name = emojiData[i].slice(1, -1); const checkEmoji = emojisTrie.search(name); @@ -253,11 +251,7 @@ function replaceEmojis(text, isSmallScreenWidth = false, preferredSkinTone = CON } } - // Add all replaced emojis to the frequently used emojis list - if (!_.isEmpty(emojis)) { - addToFrequentlyUsedEmojis(emojis); - } - return newText; + return {text: newText, emojis}; } /** @@ -293,4 +287,28 @@ function suggestEmojis(text, limit = 5) { return []; } -export {getHeaderEmojis, mergeEmojisWithFrequentlyUsedEmojis, addToFrequentlyUsedEmojis, containsOnlyEmojis, replaceEmojis, suggestEmojis, trimEmojiUnicode, getEmojiCodeWithSkinColor}; +/** + * Retrieve preferredSkinTone as Number to prevent legacy 'default' String value + * + * @param {Number | String} val + * @returns {Number} + */ +const getPreferredSkinToneIndex = (val) => { + if (!_.isNull(val) && Number.isInteger(Number(val))) { + return val; + } + + return CONST.EMOJI_DEFAULT_SKIN_TONE; +}; + +export { + getHeaderEmojis, + mergeEmojisWithFrequentlyUsedEmojis, + getFrequentlyUsedEmojis, + containsOnlyEmojis, + replaceEmojis, + suggestEmojis, + trimEmojiUnicode, + getEmojiCodeWithSkinColor, + getPreferredSkinToneIndex, +}; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 6e8e5e0f962b..9dc3f232bb82 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -21,6 +21,7 @@ import * as ReportActionsUtils from '../ReportActionsUtils'; import * as OptionsListUtils from '../OptionsListUtils'; import * as Localize from '../Localize'; import * as CollectionUtils from '../CollectionUtils'; +import * as EmojiUtils from '../EmojiUtils'; let currentUserEmail; let currentUserAccountID; @@ -41,13 +42,7 @@ let preferredSkinTone; Onyx.connect({ key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, callback: (val) => { - // the preferred skin tone is sometimes still "default", although it - // was changed that "default" has become -1. - if (!_.isNull(val) && Number.isInteger(Number(val))) { - preferredSkinTone = val; - } else { - preferredSkinTone = -1; - } + preferredSkinTone = EmojiUtils.getPreferredSkinToneIndex(val); }, }); diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index 2f64c61e177b..8d59035873c3 100644 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -600,7 +600,8 @@ class ReportActionCompose extends React.Component { }, suggestedEmojis: [], })); - EmojiUtils.addToFrequentlyUsedEmojis(emojiObject); + const frequentEmojiList = EmojiUtils.getFrequentlyUsedEmojis(emojiObject); + User.updateFrequentlyUsedEmojis(frequentEmojiList); } /** @@ -692,7 +693,12 @@ class ReportActionCompose extends React.Component { * @param {Boolean} shouldDebounceSaveComment */ updateComment(comment, shouldDebounceSaveComment) { - const newComment = EmojiUtils.replaceEmojis(comment, this.props.isSmallScreenWidth, this.props.preferredSkinTone); + const {text: newComment = '', emojis = []} = EmojiUtils.replaceEmojis(comment, this.props.isSmallScreenWidth, this.props.preferredSkinTone); + + if (!_.isEmpty(emojis)) { + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); + } + this.setState((prevState) => { const newState = { isCommentEmpty: !!newComment.match(/^(\s)*$/), @@ -1193,6 +1199,7 @@ export default compose( }, preferredSkinTone: { key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + selector: EmojiUtils.getPreferredSkinToneIndex, }, reports: { key: ONYXKEYS.COLLECTION.REPORT, diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index ad76d776b110..797c95c66165 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -32,6 +32,7 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState'; import * as ComposerUtils from '../../../libs/ComposerUtils'; import * as ComposerActions from '../../../libs/actions/Composer'; +import * as User from '../../../libs/actions/User'; const propTypes = { /** All the data of the action */ @@ -144,7 +145,12 @@ class ReportActionItemMessageEdit extends React.Component { * @param {String} draft */ updateDraft(draft) { - const newDraft = EmojiUtils.replaceEmojis(draft, this.props.isSmallScreenWidth, this.props.preferredSkinTone); + const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(draft, this.props.isSmallScreenWidth, this.props.preferredSkinTone); + + if (!_.isEmpty(emojis)) { + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); + } + this.setState((prevState) => { const newState = {draft: newDraft}; if (draft !== newDraft) { diff --git a/tests/unit/EmojiTest.js b/tests/unit/EmojiTest.js index 66a21d124cb3..8fc592ab0fc4 100644 --- a/tests/unit/EmojiTest.js +++ b/tests/unit/EmojiTest.js @@ -1,6 +1,7 @@ import _ from 'underscore'; import moment from 'moment'; import Onyx from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import Emoji from '../../assets/emojis'; import * as EmojiUtils from '../../src/libs/EmojiUtils'; import ONYXKEYS from '../../src/ONYXKEYS'; @@ -102,22 +103,22 @@ describe('EmojiTest', () => { it('replaces an emoji code with an emoji and a space on mobile', () => { const text = 'Hi :smile:'; - expect(EmojiUtils.replaceEmojis(text, true)).toBe('Hi 😄 '); + expect(lodashGet(EmojiUtils.replaceEmojis(text, true), 'text')).toBe('Hi 😄 '); }); it('will not add a space after the last emoji if there is text after it', () => { const text = 'Hi :smile::wave:no space after last emoji'; - expect(EmojiUtils.replaceEmojis(text)).toBe('Hi 😄👋no space after last emoji'); + expect(lodashGet(EmojiUtils.replaceEmojis(text), 'text')).toBe('Hi 😄👋no space after last emoji'); }); it('will not add a space after the last emoji when there is text after it on mobile', () => { const text = 'Hi :smile::wave:no space after last emoji'; - expect(EmojiUtils.replaceEmojis(text, true)).toBe('Hi 😄👋no space after last emoji'); + expect(lodashGet(EmojiUtils.replaceEmojis(text, true), 'text')).toBe('Hi 😄👋no space after last emoji'); }); it("will not add a space after the last emoji if we're not on mobile", () => { const text = 'Hi :smile:'; - expect(EmojiUtils.replaceEmojis(text)).toBe('Hi 😄'); + expect(lodashGet(EmojiUtils.replaceEmojis(text), 'text')).toBe('Hi 😄'); }); it('suggests emojis when typing emojis prefix after colon', () => { @@ -197,7 +198,7 @@ describe('EmojiTest', () => { const currentTime = moment().unix(); const smileEmoji = {code: '😄', name: 'smile'}; const newEmoji = [smileEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(newEmoji)); // Then the new emoji should be at the last item of the list const expectedSmileEmoji = {...smileEmoji, count: 1, lastUpdatedAt: currentTime}; @@ -242,7 +243,7 @@ describe('EmojiTest', () => { // When add an emoji that exists in the list const currentTime = moment().unix(); const newEmoji = [smileEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(newEmoji)); // Then the count should be increased and put into the very front of the other emoji within the same count const expectedFrequentlyEmojisList = [frequentlyEmojisList[0], {...smileEmoji, count: 2, lastUpdatedAt: currentTime}, ...frequentlyEmojisList.slice(1, -1)]; @@ -283,7 +284,7 @@ describe('EmojiTest', () => { // When add multiple emojis that either exist or not exist in the list const currentTime = moment().unix(); const newEmoji = [smileEmoji, zzzEmoji, impEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(newEmoji)); // Then the count should be increased for existing emoji and sorted descending by count and lastUpdatedAt const expectedFrequentlyEmojisList = [ @@ -452,7 +453,7 @@ describe('EmojiTest', () => { // When add new emojis const currentTime = moment().unix(); const newEmoji = [bookEmoji, smileEmoji, zzzEmoji, impEmoji, smileEmoji]; - EmojiUtils.addToFrequentlyUsedEmojis(newEmoji); + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(newEmoji)); // Then the last emojis from the list should be replaced with the most recent new emoji (smile) const expectedFrequentlyEmojisList = [