-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Static emoji autosuggestion #14686
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
Static emoji autosuggestion #14686
Changes from all commits
5b1f23f
4054f29
d247dcc
bd49ca0
d6fc1a9
a80e5ff
4cb3733
0702b6e
3011816
6ad9af4
95ebe2d
74a3a68
18e20de
72dc2c3
25f287b
6f71301
0f8771b
683bf67
b8b3a0d
26713f3
6a074f1
f998dd1
5c8d245
180c6df
65604cb
a10e829
b7fc7e5
8a5e8fa
2f1befd
ca9f306
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import React from 'react'; | ||
import {View, Pressable} from 'react-native'; | ||
import PropTypes from 'prop-types'; | ||
import _ from 'underscore'; | ||
|
||
// We take FlatList from this package to properly handle the scrolling of EmojiSuggestions in chats since one scroll is nested inside another | ||
import {FlatList} from 'react-native-gesture-handler'; | ||
import styles from '../styles/styles'; | ||
import * as StyleUtils from '../styles/StyleUtils'; | ||
import * as EmojiUtils from '../libs/EmojiUtils'; | ||
import Text from './Text'; | ||
import CONST from '../CONST'; | ||
import getStyledTextArray from '../libs/GetStyledTextArray'; | ||
|
||
const propTypes = { | ||
/** The index of the highlighted emoji */ | ||
highlightedEmojiIndex: PropTypes.number, | ||
|
||
/** Array of suggested emoji */ | ||
emojis: PropTypes.arrayOf(PropTypes.shape({ | ||
/** The emoji code */ | ||
code: PropTypes.string, | ||
|
||
/** The name of the emoji */ | ||
name: PropTypes.string, | ||
})).isRequired, | ||
|
||
/** Fired when the user selects an emoji */ | ||
onSelect: PropTypes.func.isRequired, | ||
|
||
/** Emoji prefix that follows the colon */ | ||
prefix: PropTypes.string.isRequired, | ||
|
||
/** Show that we can use large emoji picker. | ||
* Depending on available space and whether the input is expanded, we can have a small or large emoji suggester. | ||
* When this value is false, the suggester will have a height of 2.5 items. When this value is true, the height can be up to 5 items. */ | ||
isEmojiPickerLarge: PropTypes.bool.isRequired, | ||
|
||
/** Show that we should include ReportRecipientLocalTime view height */ | ||
shouldIncludeReportRecipientLocalTimeHeight: PropTypes.bool.isRequired, | ||
|
||
/** Stores user's preferred skin tone */ | ||
preferredSkinToneIndex: PropTypes.number.isRequired, | ||
}; | ||
|
||
const defaultProps = { | ||
highlightedEmojiIndex: 0, | ||
}; | ||
|
||
/** | ||
* @param {Number} numRows | ||
* @param {Boolean} isEmojiPickerLarge | ||
* @returns {Number} | ||
*/ | ||
const measureHeightOfEmojiRows = (numRows, isEmojiPickerLarge) => { | ||
if (isEmojiPickerLarge) { | ||
return numRows * CONST.EMOJI_SUGGESTER.ITEM_HEIGHT; | ||
} | ||
if (numRows > 2) { | ||
// on small screens, we display a scrollable window with a height of 2.5 items, indicating that there are more items available beyond what is currently visible | ||
return CONST.EMOJI_SUGGESTER.SMALL_CONTAINER_HEIGHT_FACTOR * CONST.EMOJI_SUGGESTER.ITEM_HEIGHT; | ||
} | ||
return numRows * CONST.EMOJI_SUGGESTER.ITEM_HEIGHT; | ||
}; | ||
|
||
/** | ||
* Create unique keys for each emoji item | ||
* @param {Object} item | ||
* @param {Number} index | ||
* @returns {String} | ||
*/ | ||
const keyExtractor = (item, index) => `${item.name}+${index}}`; | ||
|
||
const EmojiSuggestions = (props) => { | ||
/** | ||
* Render a suggestion menu item component. | ||
* @param {Object} params.item | ||
* @param {Number} params.index | ||
* @returns {JSX.Element} | ||
*/ | ||
const renderSuggestionMenuItem = ({item, index}) => { | ||
const styledTextArray = getStyledTextArray(item.name, props.prefix); | ||
|
||
return ( | ||
<Pressable | ||
style={({hovered}) => StyleUtils.getEmojiSuggestionItemStyle( | ||
props.highlightedEmojiIndex, | ||
CONST.EMOJI_SUGGESTER.ITEM_HEIGHT, | ||
hovered, | ||
index, | ||
)} | ||
onMouseDown={e => e.preventDefault()} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi! I'm coming from #16485
Causing the bug - #16485 The mWeb Android behavior is well demonstrated here. |
||
onPress={() => props.onSelect(index)} | ||
> | ||
<View style={styles.emojiSuggestionContainer}> | ||
<Text style={styles.emojiSuggestionsEmoji}>{EmojiUtils.getEmojiCodeWithSkinColor(item, props.preferredSkinToneIndex)}</Text> | ||
<Text style={styles.emojiSuggestionsText}> | ||
: | ||
{_.map(styledTextArray, ({text, isColored}, i) => ( | ||
<Text key={`${text}+${i}`} style={StyleUtils.getColoredBackgroundStyle(isColored)}> | ||
{text} | ||
</Text> | ||
))} | ||
: | ||
</Text> | ||
</View> | ||
</Pressable> | ||
); | ||
}; | ||
|
||
const rowHeight = measureHeightOfEmojiRows( | ||
props.emojis.length, | ||
props.isEmojiPickerLarge, | ||
); | ||
|
||
return ( | ||
<View | ||
style={[ | ||
styles.emojiSuggestionsContainer, | ||
StyleUtils.getEmojiSuggestionContainerStyle( | ||
rowHeight, | ||
props.shouldIncludeReportRecipientLocalTimeHeight, | ||
), | ||
]} | ||
> | ||
<FlatList | ||
keyboardShouldPersistTaps="handled" | ||
data={props.emojis} | ||
renderItem={renderSuggestionMenuItem} | ||
keyExtractor={keyExtractor} | ||
style={{height: rowHeight}} | ||
/> | ||
</View> | ||
); | ||
}; | ||
|
||
EmojiSuggestions.propTypes = propTypes; | ||
EmojiSuggestions.defaultProps = defaultProps; | ||
EmojiSuggestions.displayName = 'EmojiSuggestions'; | ||
|
||
export default EmojiSuggestions; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* Render a suggestion menu item component. | ||
* @param {String} name | ||
* @param {String} prefix | ||
* @returns {Array} | ||
*/ | ||
const getStyledTextArray = (name, prefix) => { | ||
const texts = []; | ||
const prefixLocation = name.search(prefix); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another issue when you type this emoji const prefixLocation = name.search(Str.escapeForRegExp(prefix)); Screen.Recording.2023-03-11.at.12.29.12.AM.mov |
||
|
||
if (prefixLocation === 0 && prefix.length === name.length) { | ||
texts.push({text: prefix, isColored: true}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. text should be |
||
} else if (prefixLocation === 0 && prefix.length !== name.length) { | ||
texts.push( | ||
{text: name.slice(0, prefix.length), isColored: true}, | ||
{text: name.slice(prefix.length), isColored: false}, | ||
); | ||
} else if (prefixLocation > 0 && prefix.length !== name.length) { | ||
texts.push( | ||
{text: name.slice(0, prefixLocation), isColored: false}, | ||
{ | ||
text: name.slice(prefixLocation, prefixLocation + prefix.length), | ||
isColored: true, | ||
}, | ||
{ | ||
text: name.slice(prefixLocation + prefix.length), | ||
isColored: false, | ||
}, | ||
); | ||
} else { | ||
texts.push({text: name, isColored: false}); | ||
} | ||
return texts; | ||
}; | ||
|
||
export default getStyledTextArray; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This caused a regression #19289. In our world SPACE is still
and not
1
😅.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoooops 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😄