Refactor tag screen to use FlashList

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-15 19:37:15 +03:00
parent bd1dcd6809
commit 622d88cf40
11 changed files with 95 additions and 49 deletions

View File

@@ -24,14 +24,17 @@ const RootScrollView = ({
padded && padded &&
orientation == 'portrait' && [ orientation == 'portrait' && [
styles.paddingHorizontal, styles.paddingHorizontal,
styles.paddingVertical, styles.paddingTop,
], ],
padded && padded &&
orientation == 'landscape' && [ orientation == 'landscape' && [
styles.paddingHorizontal, styles.paddingHorizontal,
styles.smallPaddingVertical, styles.smallPaddingTop,
], ],
centered && [styles.centered, styles.flex], centered && [styles.centered, styles.flex],
styles.fullSize,
centered && [styles.centered, styles.flex],
styles.fullSize,
{ backgroundColor: colors.background }, { backgroundColor: colors.background },
style, style,
]} ]}

View File

@@ -24,14 +24,15 @@ const RootView = ({
padded && padded &&
orientation == 'portrait' && [ orientation == 'portrait' && [
styles.paddingHorizontal, styles.paddingHorizontal,
styles.paddingVertical, styles.paddingTop,
], ],
padded && padded &&
orientation == 'landscape' && [ orientation == 'landscape' && [
styles.paddingHorizontal, styles.paddingHorizontal,
styles.smallPaddingVertical, styles.smallPaddingTop,
], ],
centered && [styles.centered, styles.flex], centered && [styles.centered, styles.flex],
styles.fullSize,
{ backgroundColor: colors.background }, { backgroundColor: colors.background },
style, style,
]}> ]}>

View File

@@ -4,8 +4,8 @@ import { Chip } from 'react-native-paper';
import { Tag } from '../database'; import { Tag } from '../database';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
const TagChip = (properties: { tag: Tag }) => { const TagChip = ({ tag }: { tag: Tag }) => {
const contrastColor = getContrastColor(properties.tag.color); const contrastColor = getContrastColor(tag.color);
return ( return (
<Chip <Chip
@@ -15,11 +15,11 @@ const TagChip = (properties: { tag: Tag }) => {
compact compact
style={[ style={[
{ {
backgroundColor: properties.tag.color, backgroundColor: tag.color,
}, },
]} ]}
textStyle={{ color: contrastColor }}> textStyle={{ color: contrastColor }}>
{'#' + properties.tag.name} {'#' + tag.name}
</Chip> </Chip>
); );
}; };

View File

@@ -15,9 +15,9 @@ const tagPreviewStyles = StyleSheet.create({
}, },
}); });
const TagPreview = (properties: { name: string; color: string }) => { const TagPreview = ({ name, color }: { name: string; color: string }) => {
const { responsive } = useDimensions(); const { responsive } = useDimensions();
const contrastColor = getContrastColor(properties.color); const contrastColor = getContrastColor(color);
return ( return (
<View <View
@@ -36,11 +36,11 @@ const TagPreview = (properties: { name: string; color: string }) => {
style={[ style={[
tagPreviewStyles.chip, tagPreviewStyles.chip,
{ {
backgroundColor: properties.color, backgroundColor: color,
}, },
]} ]}
textStyle={[tagPreviewStyles.text, { color: contrastColor }]}> textStyle={[tagPreviewStyles.text, { color: contrastColor }]}>
{'#' + properties.name} {'#' + name}
</Chip> </Chip>
</View> </View>
); );

View File

@@ -45,7 +45,7 @@ class Meme extends Realm.Object<Meme> {
title: 'string', title: 'string',
description: 'string?', description: 'string?',
isFavorite: { type: 'bool', indexed: true, default: false }, isFavorite: { type: 'bool', indexed: true, default: false },
tags: 'Tag[]', tags: { type: 'list', objectType: 'Tag', default: [] },
tagsLength: { type: 'int', default: 0 }, tagsLength: { type: 'int', default: 0 },
dateCreated: { type: 'date', default: new Date() }, dateCreated: { type: 'date', default: new Date() },
dateModified: { type: 'date', default: new Date() }, dateModified: { type: 'date', default: new Date() },

View File

@@ -16,9 +16,9 @@ class Tag extends Realm.Object<Tag> {
primaryKey: 'id', primaryKey: 'id',
properties: { properties: {
id: 'uuid', id: 'uuid',
name: 'string', name: { type: 'string', indexed: true },
color: 'string', color: 'string',
memes: 'Meme[]', memes: { type: 'list', objectType: 'Meme', default: [] },
memesLength: { type: 'int', default: 0 }, memesLength: { type: 'int', default: 0 },
dateCreated: { type: 'date', default: new Date() }, dateCreated: { type: 'date', default: new Date() },
dateModified: { type: 'date', default: new Date() }, dateModified: { type: 'date', default: new Date() },
@@ -39,5 +39,4 @@ const deleteAllTags = (realm: Realm) => {
}); });
}; };
export { Tag, deleteTag, deleteAllTags }; export { Tag, deleteTag, deleteAllTags };

View File

@@ -50,7 +50,6 @@ const AddTag = () => {
id: new BSON.UUID(), id: new BSON.UUID(),
name: tagName, name: tagName,
color: tagColor, color: tagColor,
memes: [],
}); });
}); });
navigation.goBack(); navigation.goBack();

View File

@@ -1,16 +1,17 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View, Text } from 'react-native';
import { import {
Button, Button,
DataTable,
Divider, Divider,
HelperText, HelperText,
Menu, Menu,
Searchbar, Searchbar,
TouchableRipple,
} from 'react-native-paper'; } from 'react-native-paper';
import { useQuery, useRealm } from '@realm/react'; import { useQuery, useRealm } from '@realm/react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { RootScrollView, TagChip } from '../components'; import { FlashList } from '@shopify/flash-list';
import { RootView, TagChip } from '../components';
import { Tag, deleteTag } from '../database'; import { Tag, deleteTag } from '../database';
import styles from '../styles'; import styles from '../styles';
import { import {
@@ -26,17 +27,48 @@ const tagStyles = StyleSheet.create({
headerButtonView: { headerButtonView: {
height: 50, height: 50,
}, },
tagRow: {
flexWrap: 'wrap',
justifyContent: 'space-between',
flexDirection: 'row',
alignItems: 'center',
padding: 10,
},
tagView: {
flexShrink: 1,
maxWidth: '80%',
},
helperText: { helperText: {
marginVertical: 10, marginVertical: 10,
}, },
tagView: {
justifyContent: 'center',
maxWidth: '75%',
},
}); });
const Tags = () => { const TagRow = ({ tag }: { tag: Tag }) => {
const realm = useRealm(); const realm = useRealm();
return (
<TouchableRipple onPress={() => deleteTag(realm, tag)}>
<View style={tagStyles.tagRow}>
<View style={tagStyles.tagView}>
<TagChip tag={tag} />
</View>
<Text>{tag.memesLength}</Text>
</View>
</TouchableRipple>
);
};
const ListEmpty = () => {
return (
<View style={styles.alignCenter}>
<HelperText type={'info'} style={tagStyles.helperText}>
No tags found
</HelperText>
</View>
);
};
const Tags = () => {
const sort = useSelector((state: RootState) => state.tags.sort); const sort = useSelector((state: RootState) => state.tags.sort);
const sortDirection = useSelector( const sortDirection = useSelector(
(state: RootState) => state.tags.sortDirection, (state: RootState) => state.tags.sortDirection,
@@ -60,16 +92,19 @@ const Tags = () => {
}; };
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const tags = useQuery<Tag>(Tag.schema.name) const tags = useQuery<Tag>(Tag.schema.name)
.filtered(`name CONTAINS[c] "${search}"`) .filtered(`name CONTAINS[c] "${search}"`)
.sorted(tagSortQuery(sort), sortDirection === SORT_DIRECTION.ASCENDING); .sorted(tagSortQuery(sort), sortDirection === SORT_DIRECTION.DESCENDING);
return ( return (
<RootScrollView padded> <RootView padded>
<Searchbar <Searchbar
placeholder="Search Tags" placeholder="Search Tags"
value={search} value={search}
onChangeText={setSearch} onChangeText={(value: string) => {
setSearch(value);
}}
/> />
<View <View
style={[ style={[
@@ -103,26 +138,14 @@ const Tags = () => {
</Menu> </Menu>
</View> </View>
<Divider /> <Divider />
<DataTable> <FlashList
{tags.map(tag => ( data={tags}
<DataTable.Row estimatedItemSize={52}
key={tag.id.toHexString()} renderItem={({ item }) => <TagRow tag={item} />}
onPress={() => deleteTag(realm, tag)}> ItemSeparatorComponent={() => <Divider />}
<View style={tagStyles.tagView}> ListEmptyComponent={() => <ListEmpty />}
<TagChip tag={tag} /> />
</View> </RootView>
<DataTable.Cell numeric>{tag.memesLength}</DataTable.Cell>
</DataTable.Row>
))}
</DataTable>
{tags.length === 0 && (
<View style={styles.alignCenter}>
<HelperText type={'info'} style={tagStyles.helperText}>
No tags found
</HelperText>
</View>
)}
</RootScrollView>
); );
}; };

View File

@@ -10,6 +10,9 @@ const styles = StyleSheet.create({
smallPaddingVertical: { smallPaddingVertical: {
paddingVertical: '2%', paddingVertical: '2%',
}, },
smallPaddingTop: {
paddingTop: '2%',
},
padding: { padding: {
padding: '5%', padding: '5%',
}, },
@@ -19,6 +22,9 @@ const styles = StyleSheet.create({
paddingVertical: { paddingVertical: {
paddingVertical: '5%', paddingVertical: '5%',
}, },
paddingTop: {
paddingTop: '5%',
},
centered: { centered: {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@@ -61,6 +67,16 @@ const styles = StyleSheet.create({
justifyEnd: { justifyEnd: {
justifyContent: 'flex-end', justifyContent: 'flex-end',
}, },
fullWidth: {
width: '100%',
},
fullHeight: {
height: '100%',
},
fullSize: {
width: '100%',
height: '100%',
},
}); });
export default styles; export default styles;

View File

@@ -32,6 +32,7 @@ const homeSortQuery = (sort: HOME_SORT) => {
enum TAG_SORT { enum TAG_SORT {
NAME = 'Name', NAME = 'Name',
COLOR = 'Color',
MEMES_LENGTH = 'Items', MEMES_LENGTH = 'Items',
DATE_CREATED = 'Date Created', DATE_CREATED = 'Date Created',
DATE_MODIFIED = 'Date Modified', DATE_MODIFIED = 'Date Modified',
@@ -43,6 +44,9 @@ const tagSortQuery = (sort: TAG_SORT) => {
case TAG_SORT.NAME: { case TAG_SORT.NAME: {
return 'name'; return 'name';
} }
case TAG_SORT.COLOR: {
return 'color';
}
case TAG_SORT.MEMES_LENGTH: { case TAG_SORT.MEMES_LENGTH: {
return 'memesLength'; return 'memesLength';
} }

View File

@@ -8,7 +8,8 @@ const getSortIcon = (
switch (sort) { switch (sort) {
case HOME_SORT.TITLE: case HOME_SORT.TITLE:
case TAG_SORT.NAME: { case TAG_SORT.NAME:
case TAG_SORT.COLOR: {
sortIcon = 'sort-alphabetical'; sortIcon = 'sort-alphabetical';
break; break;
} }