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 { Text, TouchableOpacity } from 'react-native';
import { Text, Pressable } from 'react-native';
import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { FlatList as GHFlatList } from 'react-native-gesture-handler';
@ -8,10 +8,18 @@ import styles from './styles';
import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
import { useTheme } from '../../theme';
import { isIOS } from '../../lib/methods/helpers';
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) {
return (
<CustomEmoji
@ -26,48 +34,56 @@ const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
{shortnameToUnicode(`:${emoji}:`)}
</Text>
);
});
const EmojiCategory = ({
baseUrl,
onEmojiSelected,
emojis,
width,
tabsCount,
isBottomSheet
}: IEmojiCategory): React.ReactElement | null => {
const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE;
const numColumns = Math.trunc(width ? width / emojiSize : tabsCount);
const { colors } = useTheme();
const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList;
const renderItem = (emoji: IEmoji) => (
<Pressable
// @ts-ignore
key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(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'
})}
>
<Emoji emoji={emoji} size={emojiSize} baseUrl={baseUrl} />
</Pressable>
);
if (!width) {
return null;
}
return (
<FlatList
// rerender FlatList in case of width changes
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis}
extraData={{ baseUrl, width }}
renderItem={({ item }) => renderItem(item)}
numColumns={numColumns}
initialNumToRender={45}
removeClippedSubviews
{...scrollPersistTaps}
keyboardDismissMode={'none'}
/>
);
};
const EmojiCategory = React.memo(
({ baseUrl, onEmojiSelected, emojis, width, tabsCount, isBottomSheet, ...props }: IEmojiCategory) => {
const emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE;
const numColumns = Math.trunc(width ? width / emojiSize : tabsCount);
const FlatList = isBottomSheet ? BottomSheetFlatList : GHFlatList;
const renderItem = (emoji: IEmoji) => (
<TouchableOpacity
activeOpacity={0.7}
// @ts-ignore
key={emoji && emoji.isCustom ? emoji.content : emoji}
onPress={() => onEmojiSelected(emoji)}
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}
>
{renderEmoji(emoji, emojiSize, baseUrl)}
</TouchableOpacity>
);
if (!width) {
return null;
}
return (
<FlatList
// rerender FlatList in case of width changes
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis}
extraData={{ baseUrl, onEmojiSelected, width, ...props }}
renderItem={({ item }) => renderItem(item)}
numColumns={numColumns}
initialNumToRender={45}
removeClippedSubviews
{...scrollPersistTaps}
keyboardDismissMode={'none'}
/>
);
}
);
export default EmojiCategory;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View, TouchableOpacity } from 'react-native';
import { View, Pressable } from 'react-native';
import { BorderlessButton } from 'react-native-gesture-handler';
import { useTheme } from '../../theme';
@ -14,12 +14,12 @@ const Footer = React.memo(({ onSearchPressed, onBackspacePressed }: IFooterProps
return (
<View style={[styles.footerContainer, { backgroundColor: colors.bannerBackground }]}>
<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>
<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' />
</TouchableOpacity>
</Pressable>
</View>
);
});

View File

@ -1,28 +1,34 @@
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { Text, Pressable, View } from 'react-native';
import styles from './styles';
import { useTheme } from '../../theme';
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();
return (
<View style={styles.tabsContainer}>
{tabs?.map((tab, i) => (
<TouchableOpacity
activeOpacity={0.7}
<Pressable
key={tab}
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>
<View style={activeTab === i ? [styles.activeTabLine, { backgroundColor: colors.tintColor }] : styles.tabLine} />
</TouchableOpacity>
</Pressable>
))}
</View>
);
});
};
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 ScrollableTabView from 'react-native-scrollable-tab-view';
import orderBy from 'lodash/orderBy';
@ -19,30 +19,47 @@ import { IEmoji, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definiti
import { useAppSelector } from '../../lib/hooks';
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(
({ onItemClicked, tabEmojiStyle, isEmojiKeyboard = false, searching = false, searchedEmojis = [] }: IEmojiPickerProps) => {
const [frequentlyUsed, setFrequentlyUsed] = useState<IEmoji[]>([]);
const [show, setShow] = useState(false);
const [width, setWidth] = useState(null);
const { colors } = useTheme();
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
const baseUrl = useAppSelector(state => state.server?.server);
const allCustomEmojis: ICustomEmojis = useAppSelector(state => state.customEmojis);
const customEmojis = Object.keys(allCustomEmojis)
.filter(item => item === allCustomEmojis[item].name)
.map(item => ({
content: allCustomEmojis[item].name,
extension: allCustomEmojis[item].extension,
isCustom: true
}));
useEffect(() => {
const init = async () => {
await updateFrequentlyUsed();
setShow(true);
};
init();
}, []);
const customEmojis = useMemo(
() =>
Object.keys(allCustomEmojis)
.filter(item => item === allCustomEmojis[item].name)
.map(item => ({
content: allCustomEmojis[item].name,
extension: allCustomEmojis[item].extension,
isCustom: true
})),
[allCustomEmojis]
);
const handleEmojiSelect = (emoji: IEmoji) => {
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 = ({
nativeEvent: {
layout: { width }
@ -133,7 +137,7 @@ const EmojiPicker = React.memo(
);
};
if (!show) {
if (!loaded) {
return null;
}

View File

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

View File

@ -1,5 +1,5 @@
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 { FormTextInput } from '../TextInput/FormTextInput';
@ -63,9 +63,7 @@ const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>(
const emojiSize = 30;
return (
<View style={[styles.emojiContainer]}>
<TouchableOpacity activeOpacity={0.7} onPress={() => onEmojiSelected(emoji)}>
{renderEmoji(emoji, emojiSize, baseUrl)}
</TouchableOpacity>
<Pressable onPress={() => onEmojiSelected(emoji)}>{renderEmoji(emoji, emojiSize, baseUrl)}</Pressable>
</View>
);
};
@ -87,9 +85,12 @@ const EmojiSearchbar = React.forwardRef<TextInput, IEmojiSearchbarProps>(
keyboardShouldPersistTaps='always'
/>
<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} />
</TouchableOpacity>
</Pressable>
<View style={{ flex: 1 }}>
<FormTextInput
inputRef={ref}