Add memes views & searching

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-23 20:20:11 +03:00
parent e44ee7de34
commit 04661ca356
28 changed files with 737 additions and 247 deletions

View File

@@ -1,38 +1,79 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { RefObject, useCallback, useRef, useState } from 'react';
import {
BackHandler,
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
View,
} from 'react-native';
import { useQuery } from '@realm/react';
import { useTheme, HelperText } from 'react-native-paper';
import { useTheme } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { FlashList, MasonryFlashList } from '@shopify/flash-list';
import { FlashList } from '@shopify/flash-list';
import { useFocusEffect } from '@react-navigation/native';
import styles from '../styles';
import { SORT_DIRECTION, memesSortQuery } from '../types';
import { SORT_DIRECTION, VIEW, memesSortQuery } from '../types';
import { RootState, setNavVisible } from '../state';
import { Meme } from '../database';
import { ORIENTATION, useDimensions } from '../contexts';
import { HideableHeader, MemesHeader } from '../components';
import MemeCard from '../components/memes/memeCard';
import {
HideableHeader,
MemesHeader,
MemesMasonryView,
MemesGridView,
MemesListView,
} from '../components';
const memesStyles = StyleSheet.create({
helperText: {
marginVertical: 10,
},
flashList: {
paddingBottom: 100,
// Needed to prevent fucky MasonryFlashList, see https://github.com/Shopify/flash-list/issues/876
paddingHorizontal: 0.01,
},
});
const MemesView = ({
memes,
flashListRef,
flashListPadding,
handleScroll,
}: {
memes: Realm.Results<Meme & Realm.Object<Meme>>;
flashListRef: RefObject<FlashList<Meme>>;
flashListPadding: number;
handleScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
}) => {
const view = useSelector((state: RootState) => state.memes.view);
switch (view) {
case VIEW.MASONRY: {
return (
<MemesMasonryView
memes={memes}
flashListRef={flashListRef}
flashListPadding={flashListPadding}
handleScroll={handleScroll}
/>
);
}
case VIEW.GRID: {
return (
<MemesGridView
memes={memes}
flashListRef={flashListRef}
flashListPadding={flashListPadding}
handleScroll={handleScroll}
/>
);
}
case VIEW.LIST: {
return (
<MemesListView
memes={memes}
flashListRef={flashListRef}
flashListPadding={flashListPadding}
handleScroll={handleScroll}
/>
);
}
default: {
return <></>;
}
}
};
const Memes = () => {
const { colors } = useTheme();
const { dimensions, orientation } = useDimensions();
const sort = useSelector((state: RootState) => state.memes.sort);
const sortDirection = useSelector(
(state: RootState) => state.memes.sortDirection,
@@ -54,10 +95,31 @@ const Memes = () => {
collectionIn => {
let collection = collectionIn;
const tokens = search
.match(/"[^"]+"|\S+/gi)
?.map(token => token.replaceAll(/["']/g, ''));
const tags = tokens
?.filter(token => token.startsWith('#'))
.map(tag => tag.slice(1));
const words = tokens?.filter(token => !token.startsWith('#'));
const tagsQuery = tags
?.map((tag, index) => `ANY tags.name CONTAINS[c] $${index}`)
.join(' OR ');
const wordsQuery = words
?.map((word, index) => `title CONTAINS[c] $${index}`)
.join(' OR ');
if (favoritesOnly) collection = collection.filtered('isFavorite == true');
if (filter) collection = collection.filtered('type == $0', filter);
if (search) {
collection = collection.filtered('title CONTAINS[c] $0', search);
if (tags && tagsQuery) {
collection = collection.filtered(tagsQuery, ...tags);
}
if (words && wordsQuery) {
collection = collection.filtered(wordsQuery, ...words);
}
collection = collection.sorted(
@@ -79,7 +141,9 @@ const Memes = () => {
dispatch(setNavVisible(true));
} else {
const diff = currentOffset - scrollOffset;
if (Math.abs(diff) > 50) dispatch(setNavVisible(diff < 0));
if (Math.abs(diff) > 50) {
dispatch(setNavVisible(diff < 0));
}
}
setScrollOffset(currentOffset);
@@ -89,7 +153,6 @@ const Memes = () => {
useFocusEffect(
useCallback(() => {
dispatch(setNavVisible(true));
const handleBackPress = () => {
if (scrollOffset > 0) {
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
@@ -102,7 +165,13 @@ const Memes = () => {
return () =>
BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
}, [dispatch, scrollOffset]),
}, [scrollOffset]),
);
useFocusEffect(
useCallback(() => {
dispatch(setNavVisible(true));
}, [dispatch]),
);
return (
@@ -121,32 +190,11 @@ const Memes = () => {
}}
/>
</HideableHeader>
<MasonryFlashList
ref={flashListRef}
data={memes}
estimatedItemSize={200}
estimatedListSize={{
height: dimensions.height,
width: dimensions.width * 0.92,
}}
numColumns={2}
showsVerticalScrollIndicator={false}
renderItem={({ item: meme }) => <MemeCard meme={meme} />}
contentContainerStyle={{
paddingTop:
flashListPadding +
dimensions.height *
(orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04),
...memesStyles.flashList,
}}
ListEmptyComponent={() => (
<HelperText
type={'info'}
style={[memesStyles.helperText, styles.centerText]}>
No memes found
</HelperText>
)}
onScroll={handleScroll}
<MemesView
memes={memes}
flashListRef={flashListRef}
flashListPadding={flashListPadding}
handleScroll={handleScroll}
/>
</View>
);