This repository has been archived on 2025-07-31. You can view files and clone it, but cannot push or open issues or pull requests.
Files
terminally-online/src/screens/memes.tsx
Nikolaos Karaolidis b83407f1f4 Add video support
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2023-08-01 14:35:10 +03:00

176 lines
5.0 KiB
TypeScript

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<NativeStackNavigationProp<ParamListBase>>();
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>(
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<NativeScrollEvent>) => {
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<FlashList<Meme>>(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 (
<>
<HideableHeader visible={navVisisble}>
<MemesHeader
search={search}
setSearch={setSearch}
autoFocus={autoFocus}
onLayout={event => {
setFlashListPadding(
event.nativeEvent.layout.height +
height * (orientation === 'portrait' ? 0.02 : 0.04),
);
}}
/>
</HideableHeader>
<View
style={[memesStyles.listView, { backgroundColor: colors.background }]}>
<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;