This commit is contained in:
Danish 2022-08-20 20:02:13 +05:30
parent d9fa16977e
commit 83f0edefcc
8 changed files with 67 additions and 63 deletions

View File

@ -35,12 +35,11 @@ const EmojiCategory = ({ baseUrl, onEmojiSelected, emojis, width, tabsCount }: I
const numColumns = Math.trunc(width ? width / emojiSize : tabsCount); const numColumns = Math.trunc(width ? width / emojiSize : tabsCount);
const { colors } = useTheme(); const { colors } = useTheme();
const renderItem = (emoji: IEmoji) => ( const renderItem = (emoji: IEmoji | string) => (
<Pressable <Pressable
// @ts-ignore key={typeof emoji === 'string' ? emoji : emoji.content}
key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(emoji)} onPress={() => onEmojiSelected(emoji)}
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`} testID={`reaction-picker-${typeof emoji === 'string' ? emoji : emoji.content}`}
android_ripple={{ color: colors.bannerBackground, borderless: true, radius: emojiSize / 2 }} android_ripple={{ color: colors.bannerBackground, borderless: true, radius: emojiSize / 2 }}
style={({ pressed }: { pressed: boolean }) => ({ style={({ pressed }: { pressed: boolean }) => ({
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent' backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'

View File

@ -2813,3 +2813,5 @@ export const emojis = [
'flag_tc', 'flag_tc',
'flag_mf' 'flag_mf'
]; ];
export const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];

View File

@ -2,6 +2,7 @@ import React, { useMemo, useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view'; import ScrollableTabView from 'react-native-scrollable-tab-view';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { shallowEqual } from 'react-redux';
import TabBar from './TabBar'; import TabBar from './TabBar';
import EmojiCategory from './EmojiCategory'; import EmojiCategory from './EmojiCategory';
@ -10,7 +11,6 @@ import styles from './styles';
import categories from './categories'; import categories from './categories';
import database from '../../lib/database'; import database from '../../lib/database';
import { emojisByCategory } from './emojis'; import { emojisByCategory } from './emojis';
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import log from '../../lib/methods/helpers/log'; import log from '../../lib/methods/helpers/log';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
@ -31,20 +31,21 @@ const EmojiPicker = ({
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(); const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
const baseUrl = useAppSelector(state => state.server?.server); const baseUrl = useAppSelector(state => state.server?.server);
const allCustomEmojis: ICustomEmojis = useAppSelector(state => state.customEmojis); const allCustomEmojis: ICustomEmojis = useAppSelector(state => state.customEmojis, shallowEqual);
const customEmojis = useMemo( const customEmojis = useMemo(
() => () =>
Object.keys(allCustomEmojis) Object.keys(allCustomEmojis)
.filter(item => item === allCustomEmojis[item].name) .filter(item => item === allCustomEmojis[item].name)
.map(item => ({ .map(item => ({
content: allCustomEmojis[item].name, content: allCustomEmojis[item].name,
name: allCustomEmojis[item].name,
extension: allCustomEmojis[item].extension, extension: allCustomEmojis[item].extension,
isCustom: true isCustom: true
})), })),
[allCustomEmojis] [allCustomEmojis]
); );
const _addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => { const addFrequentlyUsed = async (emoji: IEmoji) => {
const db = database.active; const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis'); const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord: TFrequentlyUsedEmojiModel; let freqEmojiRecord: TFrequentlyUsedEmojiModel;
@ -69,21 +70,22 @@ const EmojiPicker = ({
}); });
} }
}); });
}); };
const handleEmojiSelect = (emoji: IEmoji) => { const handleEmojiSelect = (emoji: IEmoji | string) => {
try { try {
if (emoji.isCustom) { if (typeof emoji === 'string') {
_addFrequentlyUsed({ addFrequentlyUsed({ content: emoji, name: emoji, isCustom: false });
const shortname = `:${emoji}:`;
onItemClicked(EventTypes.EMOJI_PRESSED, shortnameToUnicode(shortname), shortname);
} else {
addFrequentlyUsed({
content: emoji.content, content: emoji.content,
name: emoji.content,
extension: emoji.extension, extension: emoji.extension,
isCustom: true isCustom: true
}); });
onItemClicked(EventTypes.EMOJI_PRESSED, `:${emoji.content}:`); onItemClicked(EventTypes.EMOJI_PRESSED, `:${emoji.content}:`);
} else {
_addFrequentlyUsed({ content: emoji, isCustom: false });
const shortname = `:${emoji}:`;
onItemClicked(EventTypes.EMOJI_PRESSED, shortnameToUnicode(shortname), shortname);
} }
} catch (e) { } catch (e) {
log(e); log(e);
@ -107,8 +109,8 @@ const EmojiPicker = ({
} }
return ( return (
<EmojiCategory <EmojiCategory
emojis={emojis as IEmoji[]} emojis={emojis}
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)}
style={styles.categoryContainer} style={styles.categoryContainer}
width={width} width={width}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -128,8 +130,8 @@ const EmojiPicker = ({
<View onLayout={onLayout} style={{ flex: 1 }}> <View onLayout={onLayout} style={{ flex: 1 }}>
{searching ? ( {searching ? (
<EmojiCategory <EmojiCategory
emojis={searchedEmojis as IEmoji[]} emojis={searchedEmojis}
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)}
style={styles.categoryContainer} style={styles.categoryContainer}
width={width} width={width}
baseUrl={baseUrl} baseUrl={baseUrl}

View File

@ -11,6 +11,7 @@ import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions'; import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions';
import Touch from '../Touch'; import Touch from '../Touch';
import { DEFAULT_EMOJIS } from '../EmojiPicker/emojis';
type TItem = TFrequentlyUsedEmojiModel | string; type TItem = TFrequentlyUsedEmojiModel | string;
@ -69,8 +70,6 @@ const keyExtractor = (item: TItem) => {
return (emojiModel.id ? emojiModel.content : item) as string; return (emojiModel.id ? emojiModel.content : item) as string;
}; };
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => { const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
const emojiModel = item as TFrequentlyUsedEmojiModel; const emojiModel = item as TFrequentlyUsedEmojiModel;
const emoji = (emojiModel.id ? emojiModel.content : item) as string; const emoji = (emojiModel.id ? emojiModel.content : item) as string;

View File

@ -6,22 +6,19 @@ import { Provider } from 'react-redux';
import store from '../../lib/store'; import store from '../../lib/store';
import EmojiPicker from '../EmojiPicker'; import EmojiPicker from '../EmojiPicker';
import styles from './styles'; import styles from './styles';
import {useTheme} from '../../theme' import { useTheme } from '../../theme';
import { EventTypes } from '../EmojiPicker/interfaces'; import { EventTypes } from '../EmojiPicker/interfaces';
const EmojiKeyboard = () => { const EmojiKeyboard = () => {
const { colors } = useTheme();
const onItemClicked = (eventType: EventTypes, emoji: string | undefined) => { const onItemClicked = (eventType: EventTypes, emoji: string | undefined) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji }); KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji });
}; };
const {colors} = useTheme()
return ( return (
<Provider store={store}> <Provider store={store}>
<View <View style={[styles.emojiKeyboardContainer, { borderTopColor: colors.borderColor }]} testID='messagebox-keyboard-emoji'>
style={[styles.emojiKeyboardContainer, { borderTopColor: colors.borderColor }]}
testID='messagebox-keyboard-emoji'
>
<EmojiPicker onItemClicked={onItemClicked} isEmojiKeyboard={true} /> <EmojiPicker onItemClicked={onItemClicked} isEmojiKeyboard={true} />
</View> </View>
</Provider> </Provider>

View File

@ -10,40 +10,52 @@ import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import CustomEmoji from '../EmojiPicker/CustomEmoji'; import CustomEmoji from '../EmojiPicker/CustomEmoji';
import styles from './styles'; import styles from './styles';
import useFrequentlyUsedEmoji from '../EmojiPicker/frequentlyUsedEmojis'; import useFrequentlyUsedEmoji from '../EmojiPicker/frequentlyUsedEmojis';
import { DEFAULT_EMOJIS } from '../EmojiPicker/emojis';
const BUTTON_HIT_SLOP = { top: 15, right: 15, bottom: 15, left: 15 }; const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 };
const EMOJI_SIZE = 30; const EMOJI_SIZE = 30;
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
interface IEmojiSearchBarProps { interface IEmojiSearchBarProps {
openEmoji: () => void; openEmoji: () => void;
onChangeText: (value: string) => void; onChangeText: (value: string) => void;
emojis: IEmoji[]; emojis: (IEmoji | string)[];
onEmojiSelected: (emoji: IEmoji) => void; onEmojiSelected: (emoji: IEmoji | string) => void;
baseUrl: string; baseUrl: string;
} }
interface ICustomEmoji { interface IListItem {
name: string; emoji: IEmoji | string;
extension: string; onEmojiSelected: (emoji: IEmoji | string) => void;
baseUrl: string;
} }
const Emoji = React.memo(({ emoji, baseUrl }: { emoji: IEmoji; baseUrl: string }) => { const Emoji = ({ emoji, baseUrl }: { emoji: IEmoji | string; baseUrl: string }): React.ReactElement => {
const { colors } = useTheme(); const { colors } = useTheme();
if (emoji.isCustom || emoji.name) { if (typeof emoji === 'string') {
return <CustomEmoji style={{ height: EMOJI_SIZE, width: EMOJI_SIZE, margin: 4 }} emoji={emoji} baseUrl={baseUrl} />;
}
return ( return (
<Text style={[styles.searchedEmoji, { fontSize: EMOJI_SIZE, color: colors.backdropColor }]}> <Text style={[styles.searchedEmoji, { fontSize: EMOJI_SIZE, color: colors.backdropColor }]}>
{shortnameToUnicode(`:${emoji}:`)} {shortnameToUnicode(`:${emoji}:`)}
</Text> </Text>
); );
}); }
return <CustomEmoji style={{ height: EMOJI_SIZE, width: EMOJI_SIZE, margin: 4 }} emoji={emoji} baseUrl={baseUrl} />;
};
const ListItem = ({ emoji, onEmojiSelected, baseUrl }: IListItem): React.ReactElement => {
const key = typeof emoji === 'string' ? emoji : emoji?.name || emoji?.content;
return (
<View style={[styles.emojiContainer]} key={key}>
<Pressable onPress={() => onEmojiSelected(emoji)}>
<Emoji emoji={emoji} baseUrl={baseUrl} />
</Pressable>
</View>
);
};
const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>( const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
({ openEmoji, onChangeText, emojis, onEmojiSelected, baseUrl }, ref) => { ({ openEmoji, onChangeText, emojis, onEmojiSelected, baseUrl }, ref) => {
const { colors } = useTheme(); const { colors } = useTheme();
const [searchText, setSearchText] = useState<string>(''); const [searchText, setSearchText] = useState<string>('');
const [frequentlyUsedEmojis, setFrequentlyUsed] = useState<(string | ICustomEmoji)[]>(); const [frequentlyUsedEmojis, setFrequentlyUsed] = useState<(string | IEmoji)[]>();
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(); const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
useEffect(() => { useEffect(() => {
@ -63,20 +75,12 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
onChangeText(text); onChangeText(text);
}; };
const ListItem = React.memo(({ emoji }: { emoji: IEmoji }) => (
<View style={[styles.emojiContainer]}>
<Pressable onPress={() => onEmojiSelected(emoji)}>
<Emoji emoji={emoji} baseUrl={baseUrl} />
</Pressable>
</View>
));
return ( return (
<View style={{ borderTopWidth: 1, borderTopColor: colors.borderColor, backgroundColor: colors.backgroundColor }}> <View style={{ borderTopWidth: 1, borderTopColor: colors.borderColor, backgroundColor: colors.backgroundColor }}>
<FlatList <FlatList
horizontal horizontal
data={searchText ? emojis : frequentlyUsedEmojis} data={searchText ? emojis : frequentlyUsedEmojis}
renderItem={({ item }) => <ListItem emoji={item as IEmoji} />} renderItem={({ item }) => <ListItem emoji={item} onEmojiSelected={onEmojiSelected} baseUrl={baseUrl} />}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
ListEmptyComponent={() => ( ListEmptyComponent={() => (
<View style={styles.listEmptyComponent}> <View style={styles.listEmptyComponent}>
@ -84,7 +88,7 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
</View> </View>
)} )}
// @ts-ignore // @ts-ignore
keyExtractor={item => item?.name || item} keyExtractor={item => item?.content || item?.name || item}
contentContainerStyle={styles.emojiListContainer} contentContainerStyle={styles.emojiListContainer}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Alert, Keyboard, NativeModules, Text, View, TextInput as RNTextInput, BackHandler } from 'react-native'; import { Alert, Keyboard, NativeModules, Text, View, BackHandler } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker'; import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
@ -166,7 +166,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
private typingTimeout: any; private typingTimeout: any;
private emojiSearchbarRef: React.RefObject<RNTextInput>; private emojiSearchbarRef: any;
static defaultProps = { static defaultProps = {
message: { message: {
@ -198,7 +198,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.text = ''; this.text = '';
this.selection = { start: 0, end: 0 }; this.selection = { start: 0, end: 0 };
this.focused = false; this.focused = false;
this.emojiSearchbarRef = React.createRef<RNTextInput>();
const libPickerLabels = { const libPickerLabels = {
cropperChooseText: I18n.t('Choose'), cropperChooseText: I18n.t('Choose'),
@ -630,7 +629,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
case EventTypes.SEARCH_PRESSED: case EventTypes.SEARCH_PRESSED:
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: true }); this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: true });
setTimeout(() => { setTimeout(() => {
this.emojiSearchbarRef.current?.focus(); if (this.emojiSearchbarRef && this.emojiSearchbarRef.focus) {
this.emojiSearchbarRef.focus();
}
}, 400); }, 400);
break; break;
default: default:
@ -1161,8 +1162,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const onEmojiSelected = (emoji: any) => { const onEmojiSelected = (emoji: any) => {
let selectedEmoji; let selectedEmoji;
if (emoji.name) { if (emoji.name || emoji.content) {
selectedEmoji = `:${emoji.name}:`; selectedEmoji = `:${emoji.name || emoji.content}:`;
} else { } else {
selectedEmoji = shortnameToUnicode(`:${emoji}:`); selectedEmoji = shortnameToUnicode(`:${emoji}:`);
} }
@ -1177,7 +1178,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
return showEmojiSearchbar ? ( return showEmojiSearchbar ? (
<EmojiSearchbar <EmojiSearchbar
ref={this.emojiSearchbarRef} ref={ref => (this.emojiSearchbarRef = ref)}
openEmoji={this.openEmoji} openEmoji={this.openEmoji}
onChangeText={onChangeText} onChangeText={onChangeText}
emojis={searchedEmojis} emojis={searchedEmojis}

View File

@ -5,7 +5,7 @@ import { ImageStyle } from 'react-native-fast-image';
export interface IEmoji { export interface IEmoji {
content: string; content: string;
name: string; name: string;
extension: string; extension?: string;
isCustom: boolean; isCustom: boolean;
count?: number; count?: number;
} }
@ -30,8 +30,8 @@ export interface ICustomEmojiModel {
export interface IEmojiCategory { export interface IEmojiCategory {
baseUrl: string; baseUrl: string;
emojis: IEmoji[]; emojis: (IEmoji | string)[];
onEmojiSelected: (emoji: IEmoji) => void; onEmojiSelected: (emoji: IEmoji | string) => void;
width: number | null; width: number | null;
style: StyleProp<ImageStyle>; style: StyleProp<ImageStyle>;
tabLabel: string; tabLabel: string;