Refactoring and style fixes
This commit is contained in:
parent
8390a4c582
commit
0f51a3c5df
|
@ -3,14 +3,13 @@ import { Text, Pressable } from 'react-native';
|
|||
import { FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
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 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;
|
||||
import { useDimensions } from '../../dimensions';
|
||||
|
||||
interface IEmojiProps {
|
||||
emoji: string | IEmoji;
|
||||
|
@ -18,7 +17,7 @@ interface IEmojiProps {
|
|||
baseUrl: string;
|
||||
}
|
||||
|
||||
const Emoji = React.memo(({ emoji, size, baseUrl }: IEmojiProps) => {
|
||||
const Emoji = ({ emoji, size, baseUrl }: IEmojiProps): React.ReactElement => {
|
||||
if (typeof emoji === 'string')
|
||||
return (
|
||||
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
|
||||
|
@ -28,12 +27,14 @@ const Emoji = React.memo(({ emoji, size, baseUrl }: IEmojiProps) => {
|
|||
return (
|
||||
<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 emojiSize = width ? Math.min(width / tabsCount, MAX_EMOJI_SIZE) : MAX_EMOJI_SIZE;
|
||||
const numColumns = Math.trunc(width ? width / emojiSize : tabsCount);
|
||||
const EmojiCategory = ({ baseUrl, onEmojiSelected, emojis, tabsCount }: IEmojiCategory): React.ReactElement | null => {
|
||||
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) => (
|
||||
<Pressable
|
||||
|
@ -57,14 +58,14 @@ const EmojiCategory = ({ baseUrl, onEmojiSelected, emojis, width, tabsCount }: I
|
|||
<FlatList
|
||||
// rerender FlatList in case of width changes
|
||||
key={`emoji-category-${width}`}
|
||||
// @ts-ignore
|
||||
keyExtractor={item => (item?.isCustom && item.content) || item}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.content)}
|
||||
data={emojis}
|
||||
extraData={{ baseUrl, width }}
|
||||
renderItem={({ item }) => renderItem(item)}
|
||||
numColumns={numColumns}
|
||||
initialNumToRender={45}
|
||||
removeClippedSubviews
|
||||
contentContainerStyle={{ marginHorizontal }}
|
||||
{...scrollPersistTaps}
|
||||
keyboardDismissMode={'none'}
|
||||
/>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { IEmoji, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
|
||||
const useFrequentlyUsedEmoji = (): {
|
||||
export const useFrequentlyUsedEmoji = (): {
|
||||
frequentlyUsed: (string | IEmoji)[];
|
||||
loaded: boolean;
|
||||
} => {
|
||||
|
@ -30,4 +31,29 @@ const useFrequentlyUsedEmoji = (): {
|
|||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { shallowEqual } from 'react-redux';
|
||||
|
||||
import TabBar from './TabBar';
|
||||
|
@ -9,15 +8,14 @@ import EmojiCategory from './EmojiCategory';
|
|||
import Footer from './Footer';
|
||||
import styles from './styles';
|
||||
import categories from './categories';
|
||||
import database from '../../lib/database';
|
||||
import { emojisByCategory } from './emojis';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import log from '../../lib/methods/helpers/log';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IEmoji, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
import { IEmoji, ICustomEmojis } from '../../definitions';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { IEmojiPickerProps, EventTypes } from './interfaces';
|
||||
import useFrequentlyUsedEmoji from './frequentlyUsedEmojis';
|
||||
import { useFrequentlyUsedEmoji, addFrequentlyUsed } from './frequentlyUsedEmojis';
|
||||
|
||||
const EmojiPicker = ({
|
||||
onItemClicked,
|
||||
|
@ -26,7 +24,6 @@ const EmojiPicker = ({
|
|||
searching = false,
|
||||
searchedEmojis = []
|
||||
}: IEmojiPickerProps): React.ReactElement | null => {
|
||||
const [width, setWidth] = useState(null);
|
||||
const { colors } = useTheme();
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||
|
||||
|
@ -45,33 +42,6 @@ const EmojiPicker = ({
|
|||
[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) => {
|
||||
try {
|
||||
if (typeof emoji === 'string') {
|
||||
|
@ -81,7 +51,7 @@ const EmojiPicker = ({
|
|||
} else {
|
||||
addFrequentlyUsed({
|
||||
content: emoji.content,
|
||||
name: emoji.content,
|
||||
name: emoji.name,
|
||||
extension: emoji.extension,
|
||||
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) => {
|
||||
let emojis = [];
|
||||
if (i === 0) {
|
||||
|
@ -112,7 +76,6 @@ const EmojiPicker = ({
|
|||
emojis={emojis}
|
||||
onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)}
|
||||
style={styles.categoryContainer}
|
||||
width={width}
|
||||
baseUrl={baseUrl}
|
||||
tabLabel={label}
|
||||
tabsCount={tabsCount}
|
||||
|
@ -127,13 +90,12 @@ const EmojiPicker = ({
|
|||
const tabsCount = frequentlyUsed.length === 0 ? categories.tabs.length - 1 : categories.tabs.length;
|
||||
|
||||
return (
|
||||
<View onLayout={onLayout} style={{ flex: 1 }}>
|
||||
<View style={styles.emojiPickerContainer}>
|
||||
{searching ? (
|
||||
<EmojiCategory
|
||||
emojis={searchedEmojis}
|
||||
onEmojiSelected={(emoji: IEmoji | string) => handleEmojiSelect(emoji)}
|
||||
style={styles.categoryContainer}
|
||||
width={width}
|
||||
baseUrl={baseUrl}
|
||||
tabLabel={'searching'}
|
||||
tabsCount={tabsCount}
|
||||
|
|
|
@ -2,6 +2,9 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const MAX_EMOJI_SIZE = 50;
|
||||
export const MIN_EMOJI_SIZE = 42;
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
|
@ -14,7 +17,8 @@ export default StyleSheet.create({
|
|||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 10
|
||||
paddingVertical: 10,
|
||||
width: 44
|
||||
},
|
||||
tabEmoji: {
|
||||
fontSize: 20,
|
||||
|
@ -66,5 +70,6 @@ export default StyleSheet.create({
|
|||
width: 44,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
},
|
||||
emojiPickerContainer: { flex: 1 }
|
||||
});
|
||||
|
|
|
@ -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 { FormTextInput } from '../TextInput/FormTextInput';
|
||||
|
@ -9,7 +9,7 @@ import { IEmoji } from '../../definitions';
|
|||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||
import styles from './styles';
|
||||
import useFrequentlyUsedEmoji from '../EmojiPicker/frequentlyUsedEmojis';
|
||||
import { useFrequentlyUsedEmoji, addFrequentlyUsed } from '../EmojiPicker/frequentlyUsedEmojis';
|
||||
import { DEFAULT_EMOJIS } from '../EmojiPicker/emojis';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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 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 (
|
||||
<View style={[styles.emojiContainer]} key={key} testID={`searched-emoji-${key}`}>
|
||||
<Pressable onPress={() => onEmojiSelected(emoji)}>
|
||||
<Pressable onPress={onPress}>
|
||||
<Emoji emoji={emoji} baseUrl={baseUrl} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
@ -55,20 +74,14 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
|
|||
({ openEmoji, onChangeText, emojis, onEmojiSelected, baseUrl }, ref) => {
|
||||
const { colors } = useTheme();
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [frequentlyUsedEmojis, setFrequentlyUsed] = useState<(string | IEmoji)[]>();
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||
const { frequentlyUsed } = useFrequentlyUsedEmoji();
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
const frequentlyUsedWithDefaultEmojis = frequentlyUsed
|
||||
.filter(emoji => {
|
||||
if (typeof emoji === 'string') return !DEFAULT_EMOJIS.includes(emoji);
|
||||
return !DEFAULT_EMOJIS.includes(emoji.name);
|
||||
})
|
||||
.concat(DEFAULT_EMOJIS);
|
||||
setFrequentlyUsed(frequentlyUsedWithDefaultEmojis);
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
const handleTextChange = (text: string) => {
|
||||
setSearchText(text);
|
||||
|
@ -76,10 +89,12 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
|
|||
};
|
||||
|
||||
return (
|
||||
<View style={{ borderTopWidth: 1, borderTopColor: colors.borderColor, backgroundColor: colors.backgroundColor }}>
|
||||
<View
|
||||
style={[styles.emojiSearchViewContainer, { borderTopColor: colors.borderColor, backgroundColor: colors.backgroundColor }]}
|
||||
>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={searchText ? emojis : frequentlyUsedEmojis}
|
||||
data={searchText ? emojis : frequentlyUsedWithDefaultEmojis}
|
||||
renderItem={({ item }) => <ListItem emoji={item} onEmojiSelected={onEmojiSelected} baseUrl={baseUrl} />}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
ListEmptyComponent={() => (
|
||||
|
@ -101,7 +116,7 @@ const EmojiSearchBar = React.forwardRef<TextInput, IEmojiSearchBarProps>(
|
|||
>
|
||||
<CustomIcon name='chevron-left' size={30} color={colors.collapsibleChevron} />
|
||||
</Pressable>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={styles.emojiSearchInput}>
|
||||
<FormTextInput
|
||||
inputRef={ref}
|
||||
autoCapitalize='none'
|
||||
|
|
|
@ -163,6 +163,9 @@ export default StyleSheet.create({
|
|||
},
|
||||
emojiContainer: { justifyContent: 'center', marginHorizontal: 2 },
|
||||
emojiListContainer: { height: 50, paddingHorizontal: 5, marginVertical: 5, flexGrow: 1 },
|
||||
emojiSearchViewContainer: {
|
||||
borderTopWidth: 1
|
||||
},
|
||||
emojiSearchbarContainer: {
|
||||
flexDirection: 'row',
|
||||
height: 50,
|
||||
|
@ -177,5 +180,11 @@ export default StyleSheet.create({
|
|||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
emojiSearchCustomEmoji: {
|
||||
margin: 4
|
||||
},
|
||||
emojiSearchInput: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
|
|
@ -32,7 +32,6 @@ export interface IEmojiCategory {
|
|||
baseUrl: string;
|
||||
emojis: (IEmoji | string)[];
|
||||
onEmojiSelected: (emoji: IEmoji | string) => void;
|
||||
width: number | null;
|
||||
style: StyleProp<ImageStyle>;
|
||||
tabLabel: string;
|
||||
tabsCount: number;
|
||||
|
|
|
@ -69,6 +69,7 @@ export default StyleSheet.create({
|
|||
paddingHorizontal: 15
|
||||
},
|
||||
reactionPickerSearchbar: {
|
||||
paddingHorizontal: 20
|
||||
paddingHorizontal: 20,
|
||||
minHeight: 48
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue