Refactoring and style fixes

This commit is contained in:
Danish 2022-08-26 02:55:58 +05:30
parent 8390a4c582
commit 0f51a3c5df
8 changed files with 98 additions and 80 deletions

View File

@ -3,14 +3,13 @@ import { Text, Pressable } from 'react-native';
import { FlatList } from 'react-native-gesture-handler'; import { FlatList } from 'react-native-gesture-handler';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import styles from './styles'; import styles, { MIN_EMOJI_SIZE, MAX_EMOJI_SIZE } 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 { useTheme } from '../../theme';
import { isIOS } from '../../lib/methods/helpers'; import { isIOS } from '../../lib/methods/helpers';
import { useDimensions } from '../../dimensions';
const MAX_EMOJI_SIZE = 50;
interface IEmojiProps { interface IEmojiProps {
emoji: string | IEmoji; emoji: string | IEmoji;
@ -18,7 +17,7 @@ interface IEmojiProps {
baseUrl: string; baseUrl: string;
} }
const Emoji = React.memo(({ emoji, size, baseUrl }: IEmojiProps) => { const Emoji = ({ emoji, size, baseUrl }: IEmojiProps): React.ReactElement => {
if (typeof emoji === 'string') if (typeof emoji === 'string')
return ( return (
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}> <Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
@ -28,12 +27,14 @@ const Emoji = React.memo(({ emoji, size, baseUrl }: IEmojiProps) => {
return ( return (
<CustomEmoji style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]} emoji={emoji} baseUrl={baseUrl} /> <CustomEmoji style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]} emoji={emoji} baseUrl={baseUrl} />
); );
}); };
const EmojiCategory = ({ baseUrl, onEmojiSelected, emojis, width, tabsCount }: IEmojiCategory): React.ReactElement | null => { const EmojiCategory = ({ baseUrl, onEmojiSelected, emojis, tabsCount }: 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 { colors } = useTheme();
const { width } = useDimensions();
const emojiSize = Math.min(Math.max(width / tabsCount, MIN_EMOJI_SIZE), MAX_EMOJI_SIZE);
const numColumns = Math.trunc(width / emojiSize);
const marginHorizontal = (width - numColumns * emojiSize) / 2;
const renderItem = (emoji: IEmoji | string) => ( const renderItem = (emoji: IEmoji | string) => (
<Pressable <Pressable
@ -57,14 +58,14 @@ const EmojiCategory = ({ baseUrl, onEmojiSelected, emojis, width, tabsCount }: I
<FlatList <FlatList
// rerender FlatList in case of width changes // rerender FlatList in case of width changes
key={`emoji-category-${width}`} key={`emoji-category-${width}`}
// @ts-ignore keyExtractor={item => (typeof item === 'string' ? item : item.content)}
keyExtractor={item => (item?.isCustom && item.content) || item}
data={emojis} data={emojis}
extraData={{ baseUrl, width }} extraData={{ baseUrl, width }}
renderItem={({ item }) => renderItem(item)} renderItem={({ item }) => renderItem(item)}
numColumns={numColumns} numColumns={numColumns}
initialNumToRender={45} initialNumToRender={45}
removeClippedSubviews removeClippedSubviews
contentContainerStyle={{ marginHorizontal }}
{...scrollPersistTaps} {...scrollPersistTaps}
keyboardDismissMode={'none'} keyboardDismissMode={'none'}
/> />

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../../lib/database'; import database from '../../lib/database';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
import { IEmoji } from '../../definitions'; import { IEmoji, TFrequentlyUsedEmojiModel } from '../../definitions';
const useFrequentlyUsedEmoji = (): { export const useFrequentlyUsedEmoji = (): {
frequentlyUsed: (string | IEmoji)[]; frequentlyUsed: (string | IEmoji)[];
loaded: boolean; loaded: boolean;
} => { } => {
@ -30,4 +31,29 @@ const useFrequentlyUsedEmoji = (): {
return { frequentlyUsed, loaded }; return { frequentlyUsed, loaded };
}; };
export default useFrequentlyUsedEmoji; export const addFrequentlyUsed = async (emoji: IEmoji) => {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
try {
freqEmojiRecord = await freqEmojiCollection.find(emoji.content || emoji.name);
} catch (error) {
// Do nothing
}
await db.write(async () => {
if (freqEmojiRecord) {
await freqEmojiRecord.update(f => {
if (f.count) {
f.count += 1;
}
});
} else {
await freqEmojiCollection.create(f => {
f._raw = sanitizedRaw({ id: emoji.content || emoji.name }, freqEmojiCollection.schema);
Object.assign(f, emoji);
f.count = 1;
});
}
});
};

View File

@ -1,7 +1,6 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo } 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 { shallowEqual } from 'react-redux'; import { shallowEqual } from 'react-redux';
import TabBar from './TabBar'; import TabBar from './TabBar';
@ -9,15 +8,14 @@ import EmojiCategory from './EmojiCategory';
import Footer from './Footer'; import Footer from './Footer';
import styles from './styles'; import styles from './styles';
import categories from './categories'; import categories from './categories';
import database from '../../lib/database';
import { emojisByCategory } from './emojis'; import { emojisByCategory } from './emojis';
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';
import { IEmoji, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions'; import { IEmoji, ICustomEmojis } from '../../definitions';
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
import { IEmojiPickerProps, EventTypes } from './interfaces'; import { IEmojiPickerProps, EventTypes } from './interfaces';
import useFrequentlyUsedEmoji from './frequentlyUsedEmojis'; import { useFrequentlyUsedEmoji, addFrequentlyUsed } from './frequentlyUsedEmojis';
const EmojiPicker = ({ const EmojiPicker = ({
onItemClicked, onItemClicked,
@ -26,7 +24,6 @@ const EmojiPicker = ({
searching = false, searching = false,
searchedEmojis = [] searchedEmojis = []
}: IEmojiPickerProps): React.ReactElement | null => { }: IEmojiPickerProps): React.ReactElement | null => {
const [width, setWidth] = useState(null);
const { colors } = useTheme(); const { colors } = useTheme();
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(); const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
@ -45,33 +42,6 @@ const EmojiPicker = ({
[allCustomEmojis] [allCustomEmojis]
); );
const addFrequentlyUsed = async (emoji: IEmoji) => {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
try {
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
} catch (error) {
// Do nothing
}
await db.write(async () => {
if (freqEmojiRecord) {
await freqEmojiRecord.update(f => {
if (f.count) {
f.count += 1;
}
});
} else {
await freqEmojiCollection.create(f => {
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
Object.assign(f, emoji);
f.count = 1;
});
}
});
};
const handleEmojiSelect = (emoji: IEmoji | string) => { const handleEmojiSelect = (emoji: IEmoji | string) => {
try { try {
if (typeof emoji === 'string') { if (typeof emoji === 'string') {
@ -81,7 +51,7 @@ const EmojiPicker = ({
} else { } else {
addFrequentlyUsed({ addFrequentlyUsed({
content: emoji.content, content: emoji.content,
name: emoji.content, name: emoji.name,
extension: emoji.extension, extension: emoji.extension,
isCustom: true isCustom: true
}); });
@ -92,12 +62,6 @@ const EmojiPicker = ({
} }
}; };
const onLayout = ({
nativeEvent: {
layout: { width }
}
}: any) => setWidth(width);
const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string, tabsCount: number) => { const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string, tabsCount: number) => {
let emojis = []; let emojis = [];
if (i === 0) { if (i === 0) {
@ -112,7 +76,6 @@ const EmojiPicker = ({
emojis={emojis} emojis={emojis}
onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)} onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)}
style={styles.categoryContainer} style={styles.categoryContainer}
width={width}
baseUrl={baseUrl} baseUrl={baseUrl}
tabLabel={label} tabLabel={label}
tabsCount={tabsCount} tabsCount={tabsCount}
@ -127,13 +90,12 @@ const EmojiPicker = ({
const tabsCount = frequentlyUsed.length === 0 ? categories.tabs.length - 1 : categories.tabs.length; const tabsCount = frequentlyUsed.length === 0 ? categories.tabs.length - 1 : categories.tabs.length;
return ( return (
<View onLayout={onLayout} style={{ flex: 1 }}> <View style={styles.emojiPickerContainer}>
{searching ? ( {searching ? (
<EmojiCategory <EmojiCategory
emojis={searchedEmojis} emojis={searchedEmojis}
onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)} onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)}
style={styles.categoryContainer} style={styles.categoryContainer}
width={width}
baseUrl={baseUrl} baseUrl={baseUrl}
tabLabel={'searching'} tabLabel={'searching'}
tabsCount={tabsCount} tabsCount={tabsCount}

View File

@ -2,6 +2,9 @@ import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
export const MAX_EMOJI_SIZE = 50;
export const MIN_EMOJI_SIZE = 42;
export default StyleSheet.create({ export default StyleSheet.create({
container: { container: {
flex: 1 flex: 1
@ -14,7 +17,8 @@ export default StyleSheet.create({
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingVertical: 10 paddingVertical: 10,
width: 44
}, },
tabEmoji: { tabEmoji: {
fontSize: 20, fontSize: 20,
@ -66,5 +70,6 @@ export default StyleSheet.create({
width: 44, width: 44,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center' alignItems: 'center'
} },
emojiPickerContainer: { flex: 1 }
}); });

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { View, Text, Pressable, TextInput, FlatList } from 'react-native'; import { View, Text, Pressable, TextInput, FlatList } from 'react-native';
import { FormTextInput } from '../TextInput/FormTextInput'; import { FormTextInput } from '../TextInput/FormTextInput';
@ -9,7 +9,7 @@ import { IEmoji } from '../../definitions';
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; 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, addFrequentlyUsed } from '../EmojiPicker/frequentlyUsedEmojis';
import { DEFAULT_EMOJIS } from '../EmojiPicker/emojis'; import { DEFAULT_EMOJIS } from '../EmojiPicker/emojis';
const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 }; const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 };
@ -37,14 +37,33 @@ const Emoji = ({ emoji, baseUrl }: { emoji: IEmoji | string; baseUrl: string }):
</Text> </Text>
); );
} }
return <CustomEmoji style={{ height: EMOJI_SIZE, width: EMOJI_SIZE, margin: 4 }} emoji={emoji} baseUrl={baseUrl} />; return (
<CustomEmoji
style={[styles.emojiSearchCustomEmoji, { height: EMOJI_SIZE, width: EMOJI_SIZE }]}
emoji={emoji}
baseUrl={baseUrl}
/>
);
}; };
const ListItem = ({ emoji, onEmojiSelected, baseUrl }: IListItem): React.ReactElement => { const ListItem = ({ emoji, onEmojiSelected, baseUrl }: IListItem): React.ReactElement => {
const key = typeof emoji === 'string' ? emoji : emoji?.name || emoji?.content; const key = typeof emoji === 'string' ? emoji : emoji?.name || emoji?.content;
const onPress = () => {
onEmojiSelected(emoji);
if (typeof emoji === 'string') {
addFrequentlyUsed({ content: emoji, name: emoji, isCustom: false });
} else {
addFrequentlyUsed({
content: emoji?.content || emoji?.name,
name: emoji?.name,
extension: emoji.extension,
isCustom: true
});
}
};
return ( return (
<View style={[styles.emojiContainer]} key={key} testID={`searched-emoji-${key}`}> <View style={[styles.emojiContainer]} key={key} testID={`searched-emoji-${key}`}>
<Pressable onPress={() => onEmojiSelected(emoji)}> <Pressable onPress={onPress}>
<Emoji emoji={emoji} baseUrl={baseUrl} /> <Emoji emoji={emoji} baseUrl={baseUrl} />
</Pressable> </Pressable>
</View> </View>
@ -55,20 +74,14 @@ 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 | IEmoji)[]>(); const { frequentlyUsed } = useFrequentlyUsedEmoji();
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
useEffect(() => {
if (loaded) {
const frequentlyUsedWithDefaultEmojis = frequentlyUsed const frequentlyUsedWithDefaultEmojis = frequentlyUsed
.filter(emoji => { .filter(emoji => {
if (typeof emoji === 'string') return !DEFAULT_EMOJIS.includes(emoji); if (typeof emoji === 'string') return !DEFAULT_EMOJIS.includes(emoji);
return !DEFAULT_EMOJIS.includes(emoji.name); return !DEFAULT_EMOJIS.includes(emoji.name);
}) })
.concat(DEFAULT_EMOJIS); .concat(DEFAULT_EMOJIS);
setFrequentlyUsed(frequentlyUsedWithDefaultEmojis);
}
}, [loaded]);
const handleTextChange = (text: string) => { const handleTextChange = (text: string) => {
setSearchText(text); setSearchText(text);
@ -76,10 +89,12 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
}; };
return ( return (
<View style={{ borderTopWidth: 1, borderTopColor: colors.borderColor, backgroundColor: colors.backgroundColor }}> <View
style={[styles.emojiSearchViewContainer, { borderTopColor: colors.borderColor, backgroundColor: colors.backgroundColor }]}
>
<FlatList <FlatList
horizontal horizontal
data={searchText ? emojis : frequentlyUsedEmojis} data={searchText ? emojis : frequentlyUsedWithDefaultEmojis}
renderItem={({ item }) => <ListItem emoji={item} onEmojiSelected={onEmojiSelected} baseUrl={baseUrl} />} renderItem={({ item }) => <ListItem emoji={item} onEmojiSelected={onEmojiSelected} baseUrl={baseUrl} />}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
ListEmptyComponent={() => ( ListEmptyComponent={() => (
@ -101,7 +116,7 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
> >
<CustomIcon name='chevron-left' size={30} color={colors.collapsibleChevron} /> <CustomIcon name='chevron-left' size={30} color={colors.collapsibleChevron} />
</Pressable> </Pressable>
<View style={{ flex: 1 }}> <View style={styles.emojiSearchInput}>
<FormTextInput <FormTextInput
inputRef={ref} inputRef={ref}
autoCapitalize='none' autoCapitalize='none'

View File

@ -163,6 +163,9 @@ export default StyleSheet.create({
}, },
emojiContainer: { justifyContent: 'center', marginHorizontal: 2 }, emojiContainer: { justifyContent: 'center', marginHorizontal: 2 },
emojiListContainer: { height: 50, paddingHorizontal: 5, marginVertical: 5, flexGrow: 1 }, emojiListContainer: { height: 50, paddingHorizontal: 5, marginVertical: 5, flexGrow: 1 },
emojiSearchViewContainer: {
borderTopWidth: 1
},
emojiSearchbarContainer: { emojiSearchbarContainer: {
flexDirection: 'row', flexDirection: 'row',
height: 50, height: 50,
@ -177,5 +180,11 @@ export default StyleSheet.create({
width: '100%', width: '100%',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
},
emojiSearchCustomEmoji: {
margin: 4
},
emojiSearchInput: {
flex: 1
} }
}); });

View File

@ -32,7 +32,6 @@ export interface IEmojiCategory {
baseUrl: string; baseUrl: string;
emojis: (IEmoji | string)[]; emojis: (IEmoji | string)[];
onEmojiSelected: (emoji: IEmoji | string) => void; onEmojiSelected: (emoji: IEmoji | string) => void;
width: number | null;
style: StyleProp<ImageStyle>; style: StyleProp<ImageStyle>;
tabLabel: string; tabLabel: string;
tabsCount: number; tabsCount: number;

View File

@ -69,6 +69,7 @@ export default StyleSheet.create({
paddingHorizontal: 15 paddingHorizontal: 15
}, },
reactionPickerSearchbar: { reactionPickerSearchbar: {
paddingHorizontal: 20 paddingHorizontal: 20,
minHeight: 48
} }
}); });