useMemo performance optamizations and other requested changes

This commit is contained in:
Danish Ahmed Mirza 2022-07-14 19:16:29 +05:30 committed by Danish
parent 4c130c0b0b
commit e014777c9e
6 changed files with 124 additions and 97 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text, TouchableOpacity } from 'react-native'; import { Text, Pressable } from 'react-native';
import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { FlatList as GHFlatList } from 'react-native-gesture-handler'; import { FlatList as GHFlatList } from 'react-native-gesture-handler';
@ -8,10 +8,18 @@ import styles from './styles';
import CustomEmoji from './CustomEmoji'; import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji'; import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
import { useTheme } from '../../theme';
import { isIOS } from '../../lib/methods/helpers';
const MAX_EMOJI_SIZE = 50; const MAX_EMOJI_SIZE = 50;
const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => { interface IEmojiProps {
emoji: IEmoji;
size: number;
baseUrl: string;
}
const Emoji = React.memo(({ emoji, size, baseUrl }: IEmojiProps) => {
if (emoji?.isCustom || emoji?.name) { if (emoji?.isCustom || emoji?.name) {
return ( return (
<CustomEmoji <CustomEmoji
@ -26,25 +34,34 @@ const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
{shortnameToUnicode(`:${emoji}:`)} {shortnameToUnicode(`:${emoji}:`)}
</Text> </Text>
); );
}; });
const EmojiCategory = React.memo( const EmojiCategory = ({
({ baseUrl, onEmojiSelected, emojis, width, tabsCount, isBottomSheet, ...props }: IEmojiCategory) => { baseUrl,
onEmojiSelected,
emojis,
width,
tabsCount,
isBottomSheet
}: IEmojiCategory): React.ReactElement | null => {
const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE; const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE;
const numColumns = Math.trunc(width ? width / emojiSize : tabsCount); const numColumns = Math.trunc(width ? width / emojiSize : tabsCount);
const { colors } = useTheme();
const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList; const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList;
const renderItem = (emoji: IEmoji) => ( const renderItem = (emoji: IEmoji) => (
<TouchableOpacity <Pressable
activeOpacity={0.7}
// @ts-ignore // @ts-ignore
key={emoji && emoji.isCustom ? emoji.content : emoji} 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-${emoji && emoji.isCustom ? emoji.content : emoji}`}
android_ripple={{ color: colors.bannerBackground, borderless: true, radius: emojiSize / 2 }}
style={({ pressed }: { pressed: boolean }) => ({
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
})}
> >
{renderEmoji(emoji, emojiSize, baseUrl)} <Emoji emoji={emoji} size={emojiSize} baseUrl={baseUrl} />
</TouchableOpacity> </Pressable>
); );
if (!width) { if (!width) {
@ -58,7 +75,7 @@ const EmojiCategory = React.memo(
// @ts-ignore // @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item} keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis} data={emojis}
extraData={{ baseUrl, onEmojiSelected, width, ...props }} extraData={{ baseUrl, width }}
renderItem={({ item }) => renderItem(item)} renderItem={({ item }) => renderItem(item)}
numColumns={numColumns} numColumns={numColumns}
initialNumToRender={45} initialNumToRender={45}
@ -67,7 +84,6 @@ const EmojiCategory = React.memo(
keyboardDismissMode={'none'} keyboardDismissMode={'none'}
/> />
); );
} };
);
export default EmojiCategory; export default EmojiCategory;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { View, TouchableOpacity } from 'react-native'; import { View, Pressable } from 'react-native';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
@ -14,12 +14,12 @@ const Footer = React.memo(({ onSearchPressed, onBackspacePressed }: IFooterProps
return ( return (
<View style={[styles.footerContainer, { backgroundColor: colors.bannerBackground }]}> <View style={[styles.footerContainer, { backgroundColor: colors.bannerBackground }]}>
<BorderlessButton activeOpacity={0.7} onPress={onSearchPressed} style={styles.footerButtonsContainer}> <BorderlessButton activeOpacity={0.7} onPress={onSearchPressed} style={styles.footerButtonsContainer}>
<CustomIcon color={colors.auxiliaryTintColor} size={25} name='search' /> <CustomIcon color={colors.auxiliaryTintColor} size={24} name='search' />
</BorderlessButton> </BorderlessButton>
<TouchableOpacity activeOpacity={0.7} onPress={onBackspacePressed} hitSlop={BUTTON_HIT_SLOP}> <Pressable onPress={onBackspacePressed} hitSlop={BUTTON_HIT_SLOP} style={({ pressed }) => [{ opacity: pressed ? 0.7 : 1 }]}>
<CustomIcon color={colors.auxiliaryTintColor} size={25} name='backspace' /> <CustomIcon color={colors.auxiliaryTintColor} size={25} name='backspace' />
</TouchableOpacity> </Pressable>
</View> </View>
); );
}); });

View File

@ -1,28 +1,34 @@
import React from 'react'; import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native'; import { Text, Pressable, View } from 'react-native';
import styles from './styles'; import styles from './styles';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { ITabBarProps } from './interfaces'; import { ITabBarProps } from './interfaces';
import { isIOS } from '../../lib/methods/helpers';
const TabBar = React.memo(({ activeTab, tabs, goToPage, tabEmojiStyle }: ITabBarProps) => { const TabBar = ({ activeTab, tabs, goToPage, tabEmojiStyle }: ITabBarProps): React.ReactElement => {
const { colors } = useTheme(); const { colors } = useTheme();
return ( return (
<View style={styles.tabsContainer}> <View style={styles.tabsContainer}>
{tabs?.map((tab, i) => ( {tabs?.map((tab, i) => (
<TouchableOpacity <Pressable
activeOpacity={0.7}
key={tab} key={tab}
onPress={() => goToPage?.(i)} onPress={() => goToPage?.(i)}
style={styles.tab} testID={`reaction-picker-${tab}`}
testID={`reaction-picker-${tab}`}> android_ripple={{ color: colors.bannerBackground }}
style={({ pressed }: { pressed: boolean }) => [
styles.tab,
{
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
}
]}>
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text> <Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
<View style={activeTab === i ? [styles.activeTabLine, { backgroundColor: colors.tintColor }] : styles.tabLine} /> <View style={activeTab === i ? [styles.activeTabLine, { backgroundColor: colors.tintColor }] : styles.tabLine} />
</TouchableOpacity> </Pressable>
))} ))}
</View> </View>
); );
}); };
export default TabBar; export default TabBar;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, 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 orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
@ -19,30 +19,47 @@ import { IEmoji, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definiti
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
import { IEmojiPickerProps, EventTypes } from './interfaces'; import { IEmojiPickerProps, EventTypes } from './interfaces';
const useFrequentlyUsedEmoji = () => {
const [frequentlyUsed, setFrequentlyUsed] = useState<IEmoji[]>([]);
const [loaded, setLoaded] = useState(false);
const getFrequentlyUsedEmojis = async () => {
const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
const frequentlyUsedEmojis = frequentlyUsedOrdered.map(item => {
if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
}
return shortnameToUnicode(`${item.content}`);
}) as IEmoji[];
setFrequentlyUsed(frequentlyUsedEmojis);
setLoaded(true);
};
useEffect(() => {
getFrequentlyUsedEmojis();
}, []);
return { frequentlyUsed, loaded };
};
const EmojiPicker = React.memo( const EmojiPicker = React.memo(
({ onItemClicked, tabEmojiStyle, isEmojiKeyboard = false, searching = false, searchedEmojis = [] }: IEmojiPickerProps) => { ({ onItemClicked, tabEmojiStyle, isEmojiKeyboard = false, searching = false, searchedEmojis = [] }: IEmojiPickerProps) => {
const [frequentlyUsed, setFrequentlyUsed] = useState<IEmoji[]>([]);
const [show, setShow] = useState(false);
const [width, setWidth] = useState(null); const [width, setWidth] = useState(null);
const { colors } = useTheme(); const { colors } = useTheme();
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);
const customEmojis = Object.keys(allCustomEmojis) const customEmojis = useMemo(
() =>
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,
extension: allCustomEmojis[item].extension, extension: allCustomEmojis[item].extension,
isCustom: true isCustom: true
})); })),
[allCustomEmojis]
useEffect(() => { );
const init = async () => {
await updateFrequentlyUsed();
setShow(true);
};
init();
}, []);
const handleEmojiSelect = (emoji: IEmoji) => { const handleEmojiSelect = (emoji: IEmoji) => {
try { try {
@ -91,19 +108,6 @@ const EmojiPicker = React.memo(
}); });
}); });
const updateFrequentlyUsed = async () => {
const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
const frequentlyUsedEmojis = frequentlyUsedOrdered.map(item => {
if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
}
return shortnameToUnicode(`${item.content}`);
}) as IEmoji[];
setFrequentlyUsed(frequentlyUsedEmojis);
};
const onLayout = ({ const onLayout = ({
nativeEvent: { nativeEvent: {
layout: { width } layout: { width }
@ -133,7 +137,7 @@ const EmojiPicker = React.memo(
); );
}; };
if (!show) { if (!loaded) {
return null; return null;
} }

View File

@ -56,15 +56,15 @@ export default StyleSheet.create({
margin: 8 margin: 8
}, },
footerContainer: { footerContainer: {
height: 45, height: 44,
paddingHorizontal: 12, paddingHorizontal: 12,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center' alignItems: 'center'
}, },
footerButtonsContainer: { footerButtonsContainer: {
height: 30, height: 44,
width: 30, width: 44,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center' alignItems: 'center'
} }

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, TextInput, FlatList } from 'react-native'; import { View, Text, Pressable, TextInput, FlatList } from 'react-native';
import { orderBy } from 'lodash'; import { orderBy } from 'lodash';
import { FormTextInput } from '../TextInput/FormTextInput'; import { FormTextInput } from '../TextInput/FormTextInput';
@ -63,9 +63,7 @@ const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>(
const emojiSize = 30; const emojiSize = 30;
return ( return (
<View style={[styles.emojiContainer]}> <View style={[styles.emojiContainer]}>
<TouchableOpacity activeOpacity={0.7} onPress={() => onEmojiSelected(emoji)}> <Pressable onPress={() => onEmojiSelected(emoji)}>{renderEmoji(emoji, emojiSize, baseUrl)}</Pressable>
{renderEmoji(emoji, emojiSize, baseUrl)}
</TouchableOpacity>
</View> </View>
); );
}; };
@ -87,9 +85,12 @@ const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>(
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
<View style={styles.emojiSearchbarContainer}> <View style={styles.emojiSearchbarContainer}>
<TouchableOpacity style={styles.openEmojiKeyboard} activeOpacity={0.7} onPress={openEmoji} hitSlop={BUTTON_HIT_SLOP}> <Pressable
style={({ pressed }: { pressed: boolean }) => [styles.openEmojiKeyboard, { opacity: pressed ? 0.7 : 1 }]}
onPress={openEmoji}
hitSlop={BUTTON_HIT_SLOP}>
<CustomIcon name='chevron-left' size={30} color={colors.collapsibleChevron} /> <CustomIcon name='chevron-left' size={30} color={colors.collapsibleChevron} />
</TouchableOpacity> </Pressable>
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<FormTextInput <FormTextInput
inputRef={ref} inputRef={ref}