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

Add Emoji Picker #1991

Merged
merged 95 commits into from
Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from 87 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
89c1fe9
Add emoji list
stitesExpensify Feb 23, 2021
b8f3230
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Mar 18, 2021
3aef3db
Add emoji picker components and link them to reportActionCompose
stitesExpensify Mar 19, 2021
e63876e
Update emoji list to use unicode escaped strings
stitesExpensify Mar 19, 2021
5841cb5
Update to a flatlist and add method to add emoji to the textInput
stitesExpensify Mar 19, 2021
7c757d6
Add emoji icon
stitesExpensify Mar 22, 2021
96b2e43
Update emoji list to have headers instead of individual arrays per ca…
stitesExpensify Mar 22, 2021
18e4c1e
Add emoji icon to expensicons list
stitesExpensify Mar 22, 2021
cd72afd
Use emoji button instead of send
stitesExpensify Mar 22, 2021
35dd8ae
Add styles for emoji picker button
stitesExpensify Mar 22, 2021
abf77ce
Update structure to match SectionList requirements
stitesExpensify Mar 22, 2021
b75744e
Re-format back to having all emojis in a single list
stitesExpensify Mar 22, 2021
c17f51a
Add basic search
stitesExpensify Mar 22, 2021
4297646
Add blank items to make the headers the only thing on their row
stitesExpensify Mar 22, 2021
0d4862d
Update indicies to be static and add code to account for blank items
stitesExpensify Mar 22, 2021
885f570
Differentiate between pre-label blanks and post-label inline blanks
stitesExpensify Mar 23, 2021
0f8454f
Pad headers
stitesExpensify Mar 23, 2021
d16fb6f
Remove header bar distinction
stitesExpensify Mar 23, 2021
f33c9a3
Update styles to have a 100% header width
stitesExpensify Mar 23, 2021
fb9fc8d
Add toLowerCase to make sure that we find search terms properly
stitesExpensify Mar 23, 2021
857274f
Remove garbage emojis
stitesExpensify Mar 23, 2021
08daae6
Remove testing changes
stitesExpensify Mar 23, 2021
cde5d7a
Update styles
stitesExpensify Mar 23, 2021
9905e4f
Style
stitesExpensify Mar 23, 2021
1c7d547
Focus on open
stitesExpensify Mar 24, 2021
205c9b3
Space emojis evenly
stitesExpensify Mar 24, 2021
b078bff
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Mar 24, 2021
33b210f
Emoji styles
shawnborton Mar 24, 2021
570fb7d
Merge branch 'stites-emojipicker2' of github.com:Expensify/Expensify.…
shawnborton Mar 24, 2021
9f4856e
Remove color swatches from emojis
stitesExpensify Mar 24, 2021
7b1b7ff
More style clean up
shawnborton Mar 25, 2021
b213d60
Merge branch 'stites-emojipicker2' of github.com:Expensify/Expensify.…
shawnborton Mar 25, 2021
719e495
Fix sticky headers on search
stitesExpensify Mar 25, 2021
e11297d
Use textInput or comment to add emoji to textInput
stitesExpensify Mar 25, 2021
61a16f2
Remove unused import
stitesExpensify Mar 26, 2021
f56e00a
re-add react
stitesExpensify Mar 29, 2021
09c9a65
Remove bottom padding on small screens
stitesExpensify Mar 29, 2021
e58e847
Make emoji picker button and emojis in picker hoverable
stitesExpensify Mar 29, 2021
20f971f
Alphabetize expensicons
stitesExpensify Mar 30, 2021
7eab869
Comments for props and state vars
stitesExpensify Mar 30, 2021
5906af7
Use underscore each instead of forEach
stitesExpensify Mar 30, 2021
b199f89
Remove brackets around single styles
stitesExpensify Mar 30, 2021
9ed44c8
Null initialize ref
stitesExpensify Mar 30, 2021
803db87
Bind renderItem instead of using an arrow function to increase perfor…
stitesExpensify Mar 30, 2021
48399a8
undefined instead of null
stitesExpensify Mar 30, 2021
074af23
Rename vars to specify they are related to the emoji picker
stitesExpensify Mar 30, 2021
47826b9
Remove useless style
stitesExpensify Mar 30, 2021
39b69c1
Remove unnecessary styles
stitesExpensify Mar 30, 2021
35cacd0
Remove unnecessary isVisible prop
stitesExpensify Mar 30, 2021
fb2c270
Refactor PopoverWithMeasuredContent to just measure its children rath…
stitesExpensify Mar 30, 2021
550fc21
Return early and use find to make sure we only put each emoji in once
stitesExpensify Mar 30, 2021
8aa1555
add method docs
stitesExpensify Mar 30, 2021
17a74b3
rename onPress function prop
stitesExpensify Mar 30, 2021
0d51fbc
Update props
stitesExpensify Mar 30, 2021
6810b75
Add hover style to styles instead of inline
stitesExpensify Mar 30, 2021
382a878
Change from 'blank' to 'SPACER' and make it a constant
stitesExpensify Mar 30, 2021
8b9c0b1
Make the numColumns a constant in the constructor with comments about…
stitesExpensify Mar 30, 2021
9f64d4c
DRY and add more comments/warnings
stitesExpensify Mar 30, 2021
184785e
Use nativeEvent directly since it will always exist
stitesExpensify Mar 30, 2021
0055378
move emojiPopoverAnchorPosition into state
stitesExpensify Mar 30, 2021
b1b6f53
Add isVisible prop to make sure we only focus when the modal is visible
stitesExpensify Mar 30, 2021
26e8503
Let parent modal decide when the menu is visible
stitesExpensify Mar 31, 2021
7907a97
Update to have static width and height, use a normal popover
stitesExpensify Mar 31, 2021
26fa00d
Fix styles
stitesExpensify Mar 31, 2021
5e6b602
Reduce debounce
stitesExpensify Mar 31, 2021
c88377f
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Apr 1, 2021
4e25b8f
Remove auto-focus
stitesExpensify Apr 1, 2021
388d424
Remove unused prop
stitesExpensify Apr 1, 2021
9557dd7
Remove unused prop
stitesExpensify Apr 1, 2021
23a5f08
Use this.comment as source of truth to prevent weird bugs
stitesExpensify Apr 2, 2021
7a0fc9a
Blur main compose box before showing picker to prevent having to doub…
stitesExpensify Apr 2, 2021
e127abf
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Apr 5, 2021
012a486
Remove refactor of PopoverWithMeasuredContent since I'm not touching …
stitesExpensify Apr 5, 2021
f2c0bc1
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Apr 9, 2021
e752bc9
Styles
stitesExpensify Apr 9, 2021
4a9a842
Make a native version of the emoji picker without a search bar
stitesExpensify Apr 9, 2021
f3c0c40
fix scuffed merge
stitesExpensify Apr 12, 2021
ee8e493
fix comment
stitesExpensify Apr 12, 2021
29546d1
remove unnecessary param since it's default
stitesExpensify Apr 12, 2021
d508a07
remove unnecessary parens
stitesExpensify Apr 12, 2021
2c7e196
Add pressed state to button styles
stitesExpensify Apr 12, 2021
9b75d6d
Make comment more accurate
stitesExpensify Apr 12, 2021
ca841e4
Remove unused code from native picker
stitesExpensify Apr 12, 2021
cbe39fc
Explain animation timing hack
stitesExpensify Apr 13, 2021
a5f795d
Add const for picker size
stitesExpensify Apr 13, 2021
f05b5fb
move comment
stitesExpensify Apr 13, 2021
ad7ac23
Change filter method for better readability
stitesExpensify Apr 13, 2021
22be396
Use style constants
stitesExpensify Apr 14, 2021
6e3c787
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Apr 14, 2021
95c783b
lint style
stitesExpensify Apr 14, 2021
fc9d37f
Merge branch 'master' of github.com:Expensify/Expensify.cash into sti…
stitesExpensify Apr 14, 2021
0b5f628
More scuffed merging
stitesExpensify Apr 15, 2021
4899e43
Method docs
stitesExpensify Apr 15, 2021
3a3856c
make reportActionItems pop out
stitesExpensify Apr 15, 2021
7d97899
Merge branch 'main' of github.com:Expensify/Expensify.cash into stite…
stitesExpensify Apr 15, 2021
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
12,146 changes: 12,146 additions & 0 deletions assets/emojis.js

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions assets/images/emoji.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,20 @@ const CONST = {

// at least 8 characters, 1 capital letter, 1 lowercase number, 1 number
PASSWORD_COMPLEXITY_REGEX_STRING: '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8,}$',

EMOJI_SPACER: 'SPACER',

LOGIN_TYPE: {
PHONE: 'phone',
EMAIL: 'email',
},

KEYBOARD_TYPE: {
NUMERIC: 'numeric',
PHONE_PAD: 'phone-pad',
},

EMOJI_PICKER_SIZE: 360,
};

export default CONST;
24 changes: 13 additions & 11 deletions src/components/Icon/Expensicons.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,40 @@ import Gear from '../../../assets/images/gear.svg';
import Wallet from '../../../assets/images/wallet.svg';
import Lock from '../../../assets/images/lock.svg';
import ArrowRight from '../../../assets/images/arrow-right.svg';
import Emoji from '../../../assets/images/emoji.svg';
import Upload from '../../../assets/images/upload.svg';
import Camera from '../../../assets/images/camera.svg';
import Gallery from '../../../assets/images/gallery.svg';

export {
ArrowRight,
BackArrow,
DownArrow,
Camera,
ChatBubble,
Checkmark,
Clipboard,
Close,
Download,
Emoji,
Gallery,
Gear,
LinkCopy,
Lock,
MagnifyingGlass,
Mail,
MoneyCircle,
Paperclip,
Pencil,
Phone,
Pin,
PinCircle,
Plus,
Profile,
Receipt,
Send,
Trashcan,
Upload,
Users,
Checkmark,
Receipt,
MoneyCircle,
Download,
Profile,
Gear,
Wallet,
Lock,
ArrowRight,
Upload,
Camera,
Gallery,
};
3 changes: 3 additions & 0 deletions src/components/Modal/BaseModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ class BaseModal extends PureComponent {
animationOut={this.props.animationOut || animationOut}
useNativeDriver={this.props.useNativeDriver}
statusBarTranslucent
hideModalContentWhileAnimating={this.props.hideModalContentWhileAnimating}
animationInTiming={this.props.animationInTiming}
animationOutTiming={this.props.animationOutTiming}
>
<SafeAreaInsetsContext.Consumer>
{(insets) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/PopoverWithMeasuredContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class PopoverWithMeasuredContent extends Component {
but we can't measure its dimensions without first rendering it.
*/
<View style={styles.invisible} onLayout={this.measurePopover}>
{this.props.measureContent()}
{this.props.children}
</View>
);
}
Expand Down
128 changes: 128 additions & 0 deletions src/pages/home/report/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, {Component} from 'react';
import {View, FlatList, Text} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import CONST from '../../../../CONST';
import styles from '../../../../styles/styles';
import themeColors from '../../../../styles/themes/default';
import emojis from '../../../../../assets/emojis';
import EmojiPickerMenuItem from '../EmojiPickerMenuItem';
import TextInputFocusable from '../../../../components/TextInputFocusable';

const propTypes = {
// Function to add the selected emoji to the main compose text input
onEmojiSelected: PropTypes.func.isRequired,
};

class EmojiPickerMenu extends Component {
constructor(props) {
super(props);

// Ref for the emoji search input
this.searchInput = undefined;

// This is the number of columns in each row of the picker.
// Because of how flatList implements these rows, each row is an index rather than each element
// For this reason to make headers work, we need to have the header be the only rendered element in its row
// If this number is changed, emojis.js will need to be updated to have the proper number of spacer elements
// around each header.
this.numColumns = 8;

// This is the indices of each category of emojis
// 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
// If more emojis are ever added to emojis.js this will need to be updated or things will break
this.unfilteredHeaderIndices = [0, 34, 60, 88, 99, 121, 148];

this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300);
this.renderItem = this.renderItem.bind(this);

this.state = {
filteredEmojis: emojis,
headerIndices: this.unfilteredHeaderIndices,
};
}

/**
* Filter the entire list of emojis to only emojis that have the search term in their keywords
*
* @param {String} searchTerm
*/
filterEmojis(searchTerm) {
const normalizedSearchTerm = searchTerm.toLowerCase();
if (normalizedSearchTerm === '') {
// There are no headers when searching, so we need to re-make them sticky when there is no search term
this.setState({filteredEmojis: emojis, headerIndices: this.unfilteredHeaderIndices});
return;
}

const newFilteredEmojiList = _.filter(emojis, emoji => (
!emoji.header
&& emoji.code !== CONST.EMOJI_SPACER
&& _.find(emoji.keywords, keyword => keyword.includes(normalizedSearchTerm))
));

// Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky
this.setState({filteredEmojis: newFilteredEmojiList, headerIndices: []});
}

/**
* Given an emoji item object, render a component based on its type.
* Items with the code "SPACER" return nothing and are used to fill rows up to 8
* so that the sticky headers function properly
*
* @param {Object} item
* @returns {*}
*/
renderItem({item}) {
if (item.code === CONST.EMOJI_SPACER) {
return null;
}

if (item.header) {
return (
<Text style={styles.emojiHeaderStyle}>
{item.code}
</Text>
);
}

return (
<EmojiPickerMenuItem
onPress={this.props.onEmojiSelected}
emoji={item.code}
/>
);
}

render() {
return (
<View style={styles.emojiPickerContainer}>
<View style={[styles.pt4, styles.ph4, styles.pb1]}>
<TextInputFocusable
textAlignVertical="top"
placeholder="Search"
placeholderTextColor={themeColors.textSupporting}
onChangeText={this.filterEmojis}
style={styles.textInput}
defaultValue=""
ref={el => this.searchInput = el}
/>
</View>
<FlatList
data={this.state.filteredEmojis}
renderItem={this.renderItem}
keyExtractor={item => `emoji_picker_${item.code}`}
numColumns={this.numColumns}
style={styles.emojiPickerList}
extraData={this.state.filteredEmojis}
stickyHeaderIndices={this.state.headerIndices}
/>
</View>
);
}
}

EmojiPickerMenu.propTypes = propTypes;

export default EmojiPickerMenu;
81 changes: 81 additions & 0 deletions src/pages/home/report/EmojiPickerMenu/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, {Component} from 'react';
import {View, FlatList, Text} from 'react-native';
import PropTypes from 'prop-types';
import CONST from '../../../../CONST';
import styles from '../../../../styles/styles';
import emojis from '../../../../../assets/emojis';
import EmojiPickerMenuItem from '../EmojiPickerMenuItem';

const propTypes = {
// Function to add the selected emoji to the main compose text input
onEmojiSelected: PropTypes.func.isRequired,
};

class EmojiPickerMenu extends Component {
constructor(props) {
super(props);

// This is the number of columns in each row of the picker.
// Because of how flatList implements these rows, each row is an index rather than each element
// For this reason to make headers work, we need to have the header be the only rendered element in its row
// If this number is changed, emojis.js will need to be updated to have the proper number of spacer elements
// around each header.
this.numColumns = 8;

// This is the indices of each category of emojis
// 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
// If this emojis are ever added to emojis.js this will need to be updated or things will break
this.unfilteredHeaderIndices = [0, 34, 60, 88, 99, 121, 148];

this.renderItem = this.renderItem.bind(this);
}

/**
* Given an emoji item object, render a component based on its type.
* Items with the code "SPACER" return nothing and are used to fill rows up to 8
* so that the sticky headers function properly
*
* @param {Object} item
* @returns {*}
*/
renderItem({item}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could also DRY up this renderItem by putting it in a separate file too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chatted with Rory about DRYing these up and I don't think it's necessary given that these files are going to be re-merged soon and then the code won't be duplicated anyway

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does re-merged mean?

if (item.code === CONST.EMOJI_SPACER) {
return null;
}

if (item.header) {
return (
<Text style={styles.emojiHeaderStyle}>
{item.code}
</Text>
);
}

return (
<EmojiPickerMenuItem
onPress={this.props.onEmojiSelected}
emoji={item.code}
/>
);
}

render() {
return (
<View style={styles.emojiPickerContainer}>
<FlatList
data={emojis}
renderItem={this.renderItem}
keyExtractor={item => (`emoji_picker_${item.code}`)}
numColumns={this.numColumns}
style={styles.emojiPickerList}
stickyHeaderIndices={this.unfilteredHeaderIndices}
/>
</View>
);
}
}

EmojiPickerMenu.propTypes = propTypes;

export default EmojiPickerMenu;
30 changes: 30 additions & 0 deletions src/pages/home/report/EmojiPickerMenuItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Pressable, Text} from 'react-native';
import styles, {getButtonBackgroundColorStyle} from '../../../styles/styles';
import getButtonState from '../../../libs/getButtonState';

const propTypes = {
// The unicode that is used to display the emoji
emoji: PropTypes.string.isRequired,

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

const EmojiPickerMenuItem = props => (
<Pressable
onPress={() => props.onPress(props.emoji)}
style={({hovered, pressed}) => ([
styles.emojiItem,
getButtonBackgroundColorStyle(getButtonState(hovered, pressed)),
])}
>
<Text style={styles.emojiText}>{props.emoji}</Text>
</Pressable>
);

EmojiPickerMenuItem.propTypes = propTypes;
EmojiPickerMenuItem.displayName = 'EmojiPickerMenuItem';

export default EmojiPickerMenuItem;
Loading