159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
import React, { useCallback, useRef, useState } from 'react';
|
|
import {
|
|
BackHandler,
|
|
NativeScrollEvent,
|
|
NativeSyntheticEvent,
|
|
View,
|
|
} from 'react-native';
|
|
import { useQuery } from '@realm/react';
|
|
import { useTheme } from 'react-native-paper';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import { FlashList } from '@shopify/flash-list';
|
|
import {
|
|
ParamListBase,
|
|
useFocusEffect,
|
|
useNavigation,
|
|
} from '@react-navigation/native';
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import styles from '../styles';
|
|
import { ROUTE, SORT_DIRECTION, memesSortQuery } from '../types';
|
|
import { RootState, setNavVisible } from '../state';
|
|
import { Meme } from '../database';
|
|
import { HideableHeader, MemesHeader, MemesList } from '../components';
|
|
|
|
const Memes = () => {
|
|
const { colors } = useTheme();
|
|
const { navigate } =
|
|
useNavigation<NativeStackNavigationProp<ParamListBase>>();
|
|
const sort = useSelector((state: RootState) => state.memes.sort);
|
|
const sortDirection = useSelector(
|
|
(state: RootState) => state.memes.sortDirection,
|
|
);
|
|
const favoritesOnly = useSelector(
|
|
(state: RootState) => state.memes.favoritesOnly,
|
|
);
|
|
const filter = useSelector((state: RootState) => state.memes.filter);
|
|
const navVisisble = useSelector(
|
|
(state: RootState) => state.navigation.navVisible,
|
|
);
|
|
const dispatch = useDispatch();
|
|
|
|
const [flashListPadding, setFlashListPadding] = useState(0);
|
|
const [search, setSearch] = useState('');
|
|
|
|
const memes = useQuery<Meme>(
|
|
Meme.schema.name,
|
|
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 (tags && tagsQuery) {
|
|
collection = collection.filtered(tagsQuery, ...tags);
|
|
}
|
|
if (words && wordsQuery) {
|
|
collection = collection.filtered(wordsQuery, ...words);
|
|
}
|
|
|
|
collection = collection.sorted(
|
|
memesSortQuery(sort),
|
|
sortDirection === SORT_DIRECTION.DESCENDING,
|
|
);
|
|
|
|
return collection;
|
|
},
|
|
[sort, sortDirection, favoritesOnly, filter, search],
|
|
);
|
|
|
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
|
|
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
const currentOffset = event.nativeEvent.contentOffset.y;
|
|
|
|
if (currentOffset <= 150) {
|
|
dispatch(setNavVisible(true));
|
|
} else {
|
|
const diff = currentOffset - scrollOffset;
|
|
if (Math.abs(diff) > 50) {
|
|
dispatch(setNavVisible(diff < 0));
|
|
}
|
|
}
|
|
|
|
setScrollOffset(currentOffset);
|
|
};
|
|
|
|
const flashListRef = useRef<FlashList<Meme>>(null);
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
const handleBackPress = () => {
|
|
if (scrollOffset <= 0) return false;
|
|
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
|
return true;
|
|
};
|
|
|
|
BackHandler.addEventListener('hardwareBackPress', handleBackPress);
|
|
|
|
return () =>
|
|
BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
|
|
}, [scrollOffset]),
|
|
);
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
dispatch(setNavVisible(true));
|
|
}, [dispatch]),
|
|
);
|
|
|
|
return (
|
|
<View
|
|
style={[
|
|
styles.paddingHorizontal,
|
|
styles.fullSize,
|
|
{ backgroundColor: colors.background },
|
|
]}>
|
|
<HideableHeader visible={navVisisble}>
|
|
<MemesHeader
|
|
search={search}
|
|
setSearch={setSearch}
|
|
onLayout={event => {
|
|
setFlashListPadding(event.nativeEvent.layout.height);
|
|
}}
|
|
/>
|
|
</HideableHeader>
|
|
<MemesList
|
|
memes={memes}
|
|
flashListRef={flashListRef}
|
|
flashListPadding={flashListPadding}
|
|
handleScroll={handleScroll}
|
|
focusMeme={(index: number) => {
|
|
navigate(ROUTE.MEME_VIEW, {
|
|
ids: memes.map(meme => meme.id.toHexString()),
|
|
index,
|
|
});
|
|
}}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default Memes;
|