import React, { useCallback, useRef, useState } from 'react'; import { BackHandler, NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View, useWindowDimensions, } 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 { ROUTE, SORT_DIRECTION, memesSortQuery } from '../types'; import { RootState, setNavVisible } from '../state'; import { Meme } from '../database'; import { HideableHeader, MemesHeader, MemesList } from '../components'; import { useDeviceOrientation } from '@react-native-community/hooks'; const memesStyles = StyleSheet.create({ listView: { paddingHorizontal: '4%', width: '100%', height: '100%', }, }); const Memes = () => { const { colors } = useTheme(); const { navigate } = useNavigation>(); const { height } = useWindowDimensions(); const orientation = useDeviceOrientation(); 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 autoFocus = useSelector( (state: RootState) => state.settings.autoFocusMemesSearch, ); const dispatch = useDispatch(); const [flashListPadding, setFlashListPadding] = useState(0); const [search, setSearch] = useState(''); const memes = useQuery( 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('memeType == $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 previousOffset = useRef(0); const handleScroll = (event: NativeSyntheticEvent) => { const currentOffset = event.nativeEvent.contentOffset.y; if (currentOffset <= 150) { dispatch(setNavVisible(true)); } else { const diff = currentOffset - previousOffset.current; if (Math.abs(diff) > 35) { dispatch(setNavVisible(diff < 0)); } } previousOffset.current = currentOffset; }; const flashListRef = useRef>(null); useFocusEffect( useCallback(() => { const handleBackPress = () => { if (previousOffset.current <= 0) return false; flashListRef.current?.scrollToOffset({ offset: 0, animated: true }); return true; }; BackHandler.addEventListener('hardwareBackPress', handleBackPress); return () => BackHandler.removeEventListener('hardwareBackPress', handleBackPress); }, []), ); useFocusEffect( useCallback(() => { dispatch(setNavVisible(true)); }, [dispatch]), ); return ( <> { setFlashListPadding( event.nativeEvent.layout.height + height * (orientation === 'portrait' ? 0.02 : 0.04), ); }} /> { navigate(ROUTE.MEME_VIEW, { ids: memes.map(meme => meme.id.toHexString()), index, }); }} /> ); }; export default Memes;