Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/components/EmojiPicker/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
48 changes: 33 additions & 15 deletions src/libs/EmojiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand All @@ -196,7 +196,7 @@ function addToFrequentlyUsedEmojis(newEmoji) {
frequentEmojiList.sort((a, b) => b.count - a.count || b.lastUpdatedAt - a.lastUpdatedAt);
});

User.updateFrequentlyUsedEmojis(frequentEmojiList);
return frequentEmojiList;
}

/**
Expand All @@ -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);
Expand All @@ -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};
}

/**
Expand Down Expand Up @@ -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,
};
9 changes: 2 additions & 7 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
},
});

Expand Down
11 changes: 9 additions & 2 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,8 @@ class ReportActionCompose extends React.Component {
},
suggestedEmojis: [],
}));
EmojiUtils.addToFrequentlyUsedEmojis(emojiObject);
const frequentEmojiList = EmojiUtils.getFrequentlyUsedEmojis(emojiObject);
User.updateFrequentlyUsedEmojis(frequentEmojiList);
}

/**
Expand Down Expand Up @@ -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)*$/),
Expand Down Expand Up @@ -1193,6 +1199,7 @@ export default compose(
},
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
selector: EmojiUtils.getPreferredSkinToneIndex,
},
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
Expand Down
8 changes: 7 additions & 1 deletion src/pages/home/report/ReportActionItemMessageEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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) {
Expand Down
17 changes: 9 additions & 8 deletions tests/unit/EmojiTest.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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)];
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand Down