useMemo performance optamizations and other requested changes
This commit is contained in:
parent
4c130c0b0b
commit
e014777c9e
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue