Add memes views & searching
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
Reference in New Issue
Block a user