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

Emoji Category Shortcuts Bar Tooltip #15513

Merged
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
17 changes: 17 additions & 0 deletions assets/emojis.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import Smiley from './images/emoji.svg';
import AnimalsAndNature from './images/emojiCategoryIcons/plant.svg';
import FoodAndDrink from './images/emojiCategoryIcons/hamburger.svg';
import TravelAndPlaces from './images/emojiCategoryIcons/plane.svg';
import Activities from './images/emojiCategoryIcons/soccer-ball.svg';
import Objects from './images/emojiCategoryIcons/light-bulb.svg';
import Symbols from './images/emojiCategoryIcons/peace-sign.svg';
import Flags from './images/emojiCategoryIcons/flag.svg';

/*
* This list is generated from the code here https://github.com/github/gemoji/blob/master/db/emoji.json
* Each code is then converted to hex by replacing the "U+" with "0x"
Expand Down Expand Up @@ -68,6 +77,7 @@ const emojis = [
{
code: 'smileysAndEmotion',
header: true,
icon: Smiley,
},
{
name: 'grinning',
Expand Down Expand Up @@ -6965,6 +6975,7 @@ const emojis = [
{
code: 'animalsAndNature',
header: true,
icon: AnimalsAndNature,
},
{
name: 'monkey_face',
Expand Down Expand Up @@ -8138,6 +8149,7 @@ const emojis = [
{
code: 'foodAndDrink',
header: true,
icon: FoodAndDrink,
},
{
name: 'grapes',
Expand Down Expand Up @@ -9315,6 +9327,7 @@ const emojis = [
{
code: 'travelAndPlaces',
header: true,
icon: TravelAndPlaces,
},
{
name: 'earth_africa',
Expand Down Expand Up @@ -11434,6 +11447,7 @@ const emojis = [
{
code: 'activities',
header: true,
icon: Activities,
},
{
name: 'jack_o_lantern',
Expand Down Expand Up @@ -12271,6 +12285,7 @@ const emojis = [
{
code: 'objects',
header: true,
icon: Objects,
},
{
name: 'eyeglasses',
Expand Down Expand Up @@ -14600,6 +14615,7 @@ const emojis = [
{
code: 'symbols',
header: true,
icon: Symbols,
},
{
name: 'atm',
Expand Down Expand Up @@ -16590,6 +16606,7 @@ const emojis = [
{
code: 'flags',
header: true,
icon: Flags,
},
{
name: 'checkered_flag',
Expand Down
51 changes: 18 additions & 33 deletions src/components/EmojiPicker/CategoryShortcutBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,33 @@ import PropTypes from 'prop-types';
import {View} from 'react-native';
import _ from 'underscore';
import styles from '../../styles/styles';
import FrequentlyUsed from '../../../assets/images/history.svg';
import Smiley from '../../../assets/images/emoji.svg';
import AnimalsAndNature from '../../../assets/images/emojiCategoryIcons/plant.svg';
import FoodAndDrink from '../../../assets/images/emojiCategoryIcons/hamburger.svg';
import TravelAndPlaces from '../../../assets/images/emojiCategoryIcons/plane.svg';
import Activities from '../../../assets/images/emojiCategoryIcons/soccer-ball.svg';
import Objects from '../../../assets/images/emojiCategoryIcons/light-bulb.svg';
import Symbols from '../../../assets/images/emojiCategoryIcons/peace-sign.svg';
import Flags from '../../../assets/images/emojiCategoryIcons/flag.svg';
import CategoryShortcutButton from './CategoryShortcutButton';
import getOperatingSystem from '../../libs/getOperatingSystem';
import CONST from '../../CONST';

const propTypes = {
/** The function to call when an emoji is selected */
onPress: PropTypes.func.isRequired,

/** The indices that the icons should link to */
headerIndices: PropTypes.arrayOf(PropTypes.number).isRequired,
/** The emojis consisting emoji code and indices that the icons should link to */
headerEmojis: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
icon: PropTypes.func.isRequired,
})).isRequired,
};

const CategoryShortcutBar = (props) => {
const icons = [Smiley, AnimalsAndNature, FoodAndDrink, TravelAndPlaces, Activities, Objects, Symbols, Flags];
const CategoryShortcutBar = props => (
<View style={[styles.pt2, styles.ph4, styles.flexRow]}>
{_.map(props.headerEmojis, (headerEmoji, i) => (
<CategoryShortcutButton
icon={headerEmoji.icon}
onPress={() => props.onPress(headerEmoji.index)}
key={`categoryShortcut${i}`}
code={headerEmoji.code}
/>
))}
</View>
);

// If the user has frequently used emojis, there will be 9 headers, otherwise there will be 8
// Or for Windows OS there will be 8 headers, otherwise there will be 7
if (props.headerIndices.length === 9 || (getOperatingSystem() === CONST.OS.WINDOWS && props.headerIndices.length === 8)) {
icons.unshift(FrequentlyUsed);
}

return (
<View style={[styles.pt2, styles.ph4, styles.flexRow]}>
{_.map(props.headerIndices, (headerIndex, i) => (
<CategoryShortcutButton
icon={icons[i]}
onPress={() => props.onPress(headerIndex)}
key={`categoryShortcut${i}`}
/>
))}
</View>
);
};
CategoryShortcutBar.propTypes = propTypes;
CategoryShortcutBar.displayName = 'CategoryShortcutBar';

Expand Down
19 changes: 15 additions & 4 deletions src/components/EmojiPicker/CategoryShortcutButton.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Pressable, View} from 'react-native';
import {Pressable} from 'react-native';
import Icon from '../Icon';
import Tooltip from '../Tooltip';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import variables from '../../styles/variables';
import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
import getButtonState from '../../libs/getButtonState';
import themeColors from '../../styles/themes/default';

const propTypes = {
/** The emoji code of the category header */
code: PropTypes.string.isRequired,

/** The icon representation of the category that this button links to */
icon: PropTypes.func.isRequired,

/** The function to call when an emoji is selected */
onPress: PropTypes.func.isRequired,

...withLocalizePropTypes,
};

class CategoryShortcutButton extends PureComponent {
Expand All @@ -36,18 +43,22 @@ class CategoryShortcutButton extends PureComponent {
this.state.isHighlighted && styles.emojiItemHighlighted,
])}
>
<View style={styles.alignSelfCenter}>
<Tooltip
containerStyles={[styles.flex1, styles.alignSelfStretch, styles.alignItemsCenter, styles.justifyContentCenter]}
text={this.props.translate(`emojiPicker.headers.${this.props.code}`)}
shiftVertical={-4}
>
<Icon
fill={themeColors.icon}
src={this.props.icon}
height={variables.iconSizeNormal}
width={variables.iconSizeNormal}
/>
</View>
</Tooltip>
</Pressable>
);
}
}
CategoryShortcutButton.propTypes = propTypes;

export default CategoryShortcutButton;
export default withLocalize(CategoryShortcutButton);
9 changes: 5 additions & 4 deletions src/components/EmojiPicker/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ class EmojiPickerMenu extends Component {
? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis.slice(0, flagHeaderIndex), this.props.frequentlyUsedEmojis)
: EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis, this.props.frequentlyUsedEmojis);

// This is the actual header index starting at the first emoji and counting each one
this.headerIndices = EmojiUtils.getHeaderIndices(this.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
this.headerEmojis = EmojiUtils.getHeaderEmojis(this.emojis);

// This is the indices of each header's Row
// The positions are static, and are calculated as index/numColumns (8 in our case)
// This is because each row of 8 emojis counts as one index to the flatlist
this.headerRowIndices = _.map(this.headerIndices, headerIndex => Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW));
this.headerRowIndices = _.map(this.headerEmojis, headerEmoji => Math.floor(headerEmoji.index / CONST.EMOJI_NUM_PER_ROW));

this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300);
this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this);
Expand Down Expand Up @@ -491,7 +492,7 @@ class EmojiPickerMenu extends Component {
)}
{!isFiltered && (
<CategoryShortcutBar
headerIndices={this.headerIndices}
headerEmojis={this.headerEmojis}
onPress={this.scrollToHeader}
/>
)}
Expand Down
9 changes: 5 additions & 4 deletions src/components/EmojiPicker/EmojiPickerMenu/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ class EmojiPickerMenu extends Component {

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

// This is the actual header index starting at the first emoji and counting each one
this.headerIndices = EmojiUtils.getHeaderIndices(this.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
this.headerEmojis = EmojiUtils.getHeaderEmojis(this.emojis);

// This is the indices of each header's Row
// The positions are static, and are calculated as index/numColumns (8 in our case)
// This is because each row of 8 emojis counts as one index to the flatlist
this.headerRowIndices = _.map(this.headerIndices, headerIndex => Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW));
this.headerRowIndices = _.map(this.headerEmojis, headerEmoji => Math.floor(headerEmoji.index / CONST.EMOJI_NUM_PER_ROW));

this.renderItem = this.renderItem.bind(this);
this.isMobileLandscape = this.isMobileLandscape.bind(this);
Expand Down Expand Up @@ -151,7 +152,7 @@ class EmojiPickerMenu extends Component {
<View style={styles.emojiPickerContainer}>
<View>
<CategoryShortcutBar
headerIndices={this.headerIndices}
headerEmojis={this.headerEmojis}
onPress={this.scrollToHeader}
/>
</View>
Expand Down
12 changes: 7 additions & 5 deletions src/libs/EmojiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Str from 'expensify-common/lib/str';
import CONST from '../CONST';
import * as User from './actions/User';
import emojisTrie from './EmojiTrie';
import FrequentlyUsed from '../../assets/images/history.svg';

/**
* Get the unicode code of an emoji in base 16.
Expand Down Expand Up @@ -82,17 +83,17 @@ function containsOnlyEmojis(message) {
}

/**
* Get the header indices based on the max emojis per row
* Get the header emojis with their code, icon and index
* @param {Object[]} emojis
* @returns {Number[]}
* @returns {Object[]}
*/
function getHeaderIndices(emojis) {
function getHeaderEmojis(emojis) {
const headerIndices = [];
_.each(emojis, (emoji, index) => {
if (!emoji.header) {
return;
}
headerIndices.push(index);
headerIndices.push({code: emoji.code, index, icon: emoji.icon});
});
return headerIndices;
}
Expand Down Expand Up @@ -149,6 +150,7 @@ function mergeEmojisWithFrequentlyUsedEmojis(emojis, frequentlyUsedEmojis = [])
let allEmojis = [{
header: true,
code: 'frequentlyUsed',
icon: FrequentlyUsed,
}];

allEmojis = allEmojis.concat(frequentlyUsedEmojis, emojis);
Expand Down Expand Up @@ -246,7 +248,7 @@ function suggestEmojis(text, limit = 5) {
}

export {
getHeaderIndices,
getHeaderEmojis,
mergeEmojisWithFrequentlyUsedEmojis,
addToFrequentlyUsedEmojis,
containsOnlyEmojis,
Expand Down
3 changes: 1 addition & 2 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1534,9 +1534,8 @@ const styles = {
categoryShortcutButton: {
flex: 1,
borderRadius: 8,
paddingTop: 2,
paddingBottom: 2,
height: CONST.EMOJI_PICKER_ITEM_HEIGHT,
alignItems: 'center',
justifyContent: 'center',
},

Expand Down