Add memes & meme-editing views
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { Keyboard, StyleSheet } from 'react-native';
|
||||
import { FAB } from 'react-native-paper';
|
||||
import { ParamListBase, useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { pick } from 'react-native-document-picker';
|
||||
import { pickSingle } from 'react-native-document-picker';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import { ROUTE } from '../types';
|
||||
import { allowedMimeTypes, noOp } from '../utilities';
|
||||
@@ -65,9 +65,9 @@ const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
|
||||
onStateChange={({ open }) => setState(open)}
|
||||
onPress={async () => {
|
||||
if (!state) return;
|
||||
const res = await pick({ type: allowedMimeTypes }).catch(noOp);
|
||||
if (!res) return;
|
||||
navigate(ROUTE.ADD_MEME, { uri: res });
|
||||
const file = await pickSingle({ type: allowedMimeTypes }).catch(noOp);
|
||||
if (!file) return;
|
||||
navigate(ROUTE.ADD_MEME, { file });
|
||||
}}
|
||||
style={
|
||||
orientation === ORIENTATION.PORTRAIT
|
||||
|
51
src/components/memes/memeCard.tsx
Normal file
51
src/components/memes/memeCard.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigation, NavigationProp } from '@react-navigation/native';
|
||||
import { Meme } from '../../database';
|
||||
import { ROUTE, RootStackParamList } from '../../types';
|
||||
import { Card } from 'react-native-paper';
|
||||
import { Image, StyleSheet } from 'react-native';
|
||||
import { useDimensions } from '../../contexts';
|
||||
|
||||
const memeCardStyles = StyleSheet.create({
|
||||
card: {
|
||||
margin: 5,
|
||||
},
|
||||
});
|
||||
|
||||
const MemeCard = ({ meme }: { meme: Meme }) => {
|
||||
const { navigate } = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const { dimensions } = useDimensions();
|
||||
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
|
||||
Image.getSize(meme.uri, (width, height) => {
|
||||
const paddedWidth = (dimensions.width * 0.92) / 2 - 10;
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight((paddedWidth / width) * height);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{imageWidth && imageHeight && (
|
||||
<Card
|
||||
onPress={() =>
|
||||
navigate(ROUTE.EDIT_MEME, { id: meme.id.toHexString() })
|
||||
}
|
||||
style={memeCardStyles.card}>
|
||||
<Card.Cover
|
||||
source={{ uri: meme.uri }}
|
||||
style={{ width: imageWidth, height: imageHeight }}
|
||||
/>
|
||||
<Card.Title
|
||||
title={meme.title}
|
||||
titleVariant="titleSmall"
|
||||
titleNumberOfLines={3}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemeCard;
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { HelperText, TextInput } from 'react-native-paper';
|
||||
import { Image } from 'react-native';
|
||||
import { useDimensions } from '../../contexts';
|
||||
@@ -46,13 +46,11 @@ const MemeEditor = ({
|
||||
const [imageWidth, setImageWidth] = useState<number>();
|
||||
const [imageHeight, setImageHeight] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
Image.getSize(imageUri, (width, height) => {
|
||||
const paddedWidth = dimensions.width * 0.92;
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight((paddedWidth / width) * height);
|
||||
});
|
||||
}, [dimensions.width, imageUri]);
|
||||
Image.getSize(imageUri, (width, height) => {
|
||||
const paddedWidth = dimensions.width * 0.92;
|
||||
setImageWidth(paddedWidth);
|
||||
setImageHeight((paddedWidth / width) * height);
|
||||
});
|
||||
|
||||
if (!imageWidth || !imageHeight) return <LoadingView />;
|
||||
|
||||
|
@@ -59,7 +59,7 @@ const MemeTagSearchModal = ({
|
||||
let collection = collectionIn;
|
||||
|
||||
if (search) {
|
||||
collection = collection.filtered('name CONTAINS[c] $0', search);
|
||||
collection = collection.filtered('name CONTAINS[c] $0', tagName.parsed);
|
||||
}
|
||||
|
||||
collection = collection.sorted(
|
||||
@@ -88,7 +88,6 @@ const MemeTagSearchModal = ({
|
||||
if (!tag) return;
|
||||
memeTags.set(tag.id.toHexString(), tag);
|
||||
setMemeTags(new Map(memeTags));
|
||||
setSearch(tag.name);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -22,8 +22,9 @@ const TagEditor = ({
|
||||
const lastValidTagColor = useRef(tagColor.parsed);
|
||||
|
||||
const handleTagColorChange = (color: string) => {
|
||||
setTagColor(validateColor(color));
|
||||
if (tagColor.valid) lastValidTagColor.current = tagColor.parsed;
|
||||
const result = validateColor(color);
|
||||
setTagColor(result);
|
||||
if (result.valid) lastValidTagColor.current = result.parsed;
|
||||
};
|
||||
|
||||
return (
|
||||
|
@@ -21,13 +21,12 @@ const memeTypePlural = {
|
||||
class Meme extends Object<Meme> {
|
||||
id!: BSON.UUID;
|
||||
type!: MEME_TYPE;
|
||||
uri!: string[];
|
||||
hash!: string[];
|
||||
uri!: string;
|
||||
size!: number;
|
||||
title!: string;
|
||||
description?: string;
|
||||
description!: string;
|
||||
isFavorite!: boolean;
|
||||
tags!: Tag[] | Set<Tag>;
|
||||
tags!: Tag[] | Realm.Set<Tag>;
|
||||
tagsLength!: number;
|
||||
dateCreated!: Date;
|
||||
dateModified!: Date;
|
||||
@@ -40,11 +39,10 @@ class Meme extends Object<Meme> {
|
||||
properties: {
|
||||
id: { type: 'uuid', default: () => new BSON.UUID() },
|
||||
type: { type: 'string', indexed: true },
|
||||
uri: 'string[]',
|
||||
hash: 'string[]',
|
||||
uri: 'string',
|
||||
size: 'int',
|
||||
title: 'string',
|
||||
description: 'string?',
|
||||
description: { type: 'string', default: '' },
|
||||
isFavorite: { type: 'bool', indexed: true, default: false },
|
||||
tags: { type: 'set', objectType: 'Tag', default: [] },
|
||||
tagsLength: { type: 'int', default: 0 },
|
||||
|
@@ -7,7 +7,7 @@ class Tag extends Object<Tag> {
|
||||
id!: BSON.UUID;
|
||||
name!: string;
|
||||
color!: string;
|
||||
memes!: Meme[] | Set<Meme>;
|
||||
memes!: Meme[] | Realm.Set<Meme>;
|
||||
memesLength!: number;
|
||||
dateCreated!: Date;
|
||||
dateModified!: Date;
|
||||
|
@@ -23,7 +23,7 @@ import { MemeEditor } from '../components';
|
||||
const AddMeme = ({
|
||||
route,
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.ADD_MEME>) => {
|
||||
const navigation = useNavigation();
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const realm = useRealm();
|
||||
@@ -32,9 +32,7 @@ const AddMeme = ({
|
||||
(state: RootState) => state.settings.storageUri,
|
||||
)!;
|
||||
|
||||
const uri = route.params.uri[0].uri;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const memeType = getMemeType(route.params.uri[0].type!);
|
||||
const { file } = route.params;
|
||||
|
||||
const [memeTitle, setMemeTitle] = useState(validateMemeTitle('New Meme'));
|
||||
const [memeDescription, setMemeDescription] = useState(
|
||||
@@ -49,31 +47,27 @@ const AddMeme = ({
|
||||
setIsSaving(true);
|
||||
|
||||
const uuid = new BSON.UUID();
|
||||
const savedUri: string[] = [];
|
||||
const hash: string[] = [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const fileExtension = extension(memeType!);
|
||||
if (!fileExtension) navigation.goBack();
|
||||
const mimeType = file.type!;
|
||||
const memeType = getMemeType(mimeType);
|
||||
|
||||
savedUri.push(
|
||||
AndroidScoped.appendPath(
|
||||
storageUri,
|
||||
`${uuid.toHexString()}.${fileExtension as string}`,
|
||||
),
|
||||
const fileExtension = extension(mimeType);
|
||||
if (!fileExtension) goBack();
|
||||
|
||||
const uri = AndroidScoped.appendPath(
|
||||
storageUri,
|
||||
`${uuid.toHexString()}.${fileExtension as string}`,
|
||||
);
|
||||
|
||||
await FileSystem.cp(uri, savedUri[0]);
|
||||
const { size } = await FileSystem.stat(savedUri[0]);
|
||||
hash.push(await FileSystem.hash(savedUri[0], 'MD5'));
|
||||
await FileSystem.cp(file.uri, uri);
|
||||
const { size } = await FileSystem.stat(uri);
|
||||
|
||||
realm.write(() => {
|
||||
const meme: Meme | undefined = realm.create<Meme>(Meme.schema.name, {
|
||||
id: uuid,
|
||||
type: memeType,
|
||||
uri: savedUri,
|
||||
uri,
|
||||
size,
|
||||
hash,
|
||||
title: memeTitle.parsed,
|
||||
description: memeDescription.parsed,
|
||||
isFavorite: memeIsFavorite,
|
||||
@@ -81,22 +75,21 @@ const AddMeme = ({
|
||||
tagsLength: memeTags.size,
|
||||
});
|
||||
|
||||
meme.tags.forEach(tag => {
|
||||
memeTags.forEach(tag => {
|
||||
tag.dateModified = new Date();
|
||||
const memes = tag.memes as Set<Meme>;
|
||||
const memes = tag.memes as Realm.Set<Meme>;
|
||||
memes.add(meme);
|
||||
tag.memesLength = memes.size;
|
||||
});
|
||||
});
|
||||
|
||||
setIsSaving(false);
|
||||
navigation.goBack();
|
||||
goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||
<Appbar.BackAction onPress={() => goBack()} />
|
||||
<Appbar.Content title={'Add Meme'} />
|
||||
<Appbar.Action
|
||||
icon={memeIsFavorite ? 'heart' : 'heart-outline'}
|
||||
@@ -115,7 +108,7 @@ const AddMeme = ({
|
||||
]}>
|
||||
<View style={[styles.flex, styles.justifyStart]}>
|
||||
<MemeEditor
|
||||
imageUri={uri}
|
||||
imageUri={file.uri}
|
||||
memeTitle={memeTitle}
|
||||
setMemeTitle={setMemeTitle}
|
||||
memeDescription={memeDescription}
|
||||
|
@@ -14,7 +14,7 @@ import { Tag } from '../database';
|
||||
import { TagEditor } from '../components';
|
||||
|
||||
const AddTag = () => {
|
||||
const navigation = useNavigation();
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const realm = useRealm();
|
||||
@@ -32,13 +32,13 @@ const AddTag = () => {
|
||||
});
|
||||
});
|
||||
|
||||
navigation.goBack();
|
||||
goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||
<Appbar.BackAction onPress={() => goBack()} />
|
||||
<Appbar.Content title={'Add Tag'} />
|
||||
</Appbar.Header>
|
||||
<ScrollView
|
||||
|
@@ -1,20 +1,112 @@
|
||||
import React from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
import { Appbar, useTheme } from 'react-native-paper';
|
||||
import React, { useState } from 'react';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { Appbar, Button, useTheme } from 'react-native-paper';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { useObject, useRealm } from '@realm/react';
|
||||
import { FileSystem } from 'react-native-file-access';
|
||||
import { BSON } from 'realm';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import styles from '../styles';
|
||||
import { RootStackParamList, ROUTE } from '../types';
|
||||
import { Tag, Meme } from '../database';
|
||||
import { validateMemeTitle, validateMemeDescription } from '../utilities';
|
||||
import { MemeEditor } from '../components';
|
||||
|
||||
const EditMeme = () => {
|
||||
const navigation = useNavigation();
|
||||
const EditMeme = ({
|
||||
route,
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.EDIT_MEME>) => {
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const realm = useRealm();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const meme = useObject<Meme>(
|
||||
Meme.schema.name,
|
||||
BSON.UUID.createFromHexString(route.params.id),
|
||||
)!;
|
||||
|
||||
const [memeTitle, setMemeTitle] = useState(validateMemeTitle(meme.title));
|
||||
const [memeDescription, setMemeDescription] = useState(
|
||||
validateMemeDescription(meme.description),
|
||||
);
|
||||
const [memeIsFavorite, setMemeIsFavorite] = useState(meme.isFavorite);
|
||||
const [memeTags, setMemeTags] = useState(
|
||||
new Map<string, Tag>(
|
||||
(meme.tags as Realm.Set<Tag>).map(tag => [tag.id.toHexString(), tag]),
|
||||
),
|
||||
);
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const handleSave = () => {
|
||||
setIsSaving(true);
|
||||
|
||||
realm.write(() => {
|
||||
meme.tags.forEach(tag => {
|
||||
if (!memeTags.has(tag.id.toHexString())) {
|
||||
const memes = tag.memes as Realm.Set<Meme>;
|
||||
memes.delete(meme);
|
||||
tag.memesLength = memes.size;
|
||||
tag.dateModified = new Date();
|
||||
}
|
||||
});
|
||||
|
||||
memeTags.forEach(tag => {
|
||||
if (!(meme.tags as Realm.Set<Tag>).has(tag)) {
|
||||
const memes = tag.memes as Realm.Set<Meme>;
|
||||
memes.add(meme);
|
||||
tag.memesLength = memes.size;
|
||||
tag.dateModified = new Date();
|
||||
}
|
||||
});
|
||||
|
||||
meme.title = memeTitle.parsed;
|
||||
meme.description = memeDescription.parsed;
|
||||
meme.tags = [...memeTags.values()];
|
||||
meme.tagsLength = memeTags.size;
|
||||
meme.dateModified = new Date();
|
||||
});
|
||||
|
||||
goBack();
|
||||
};
|
||||
|
||||
const handleFavorite = () => {
|
||||
realm.write(() => {
|
||||
meme.isFavorite = !memeIsFavorite;
|
||||
});
|
||||
setMemeIsFavorite(!memeIsFavorite);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
setIsSaving(true);
|
||||
await FileSystem.unlink(meme.uri);
|
||||
|
||||
realm.write(() => {
|
||||
for (const tag of meme.tags) {
|
||||
tag.dateModified = new Date();
|
||||
const memes = tag.memes as Realm.Set<Meme>;
|
||||
memes.delete(meme);
|
||||
tag.memesLength = memes.size;
|
||||
}
|
||||
|
||||
realm.delete(meme);
|
||||
});
|
||||
|
||||
goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||
<Appbar.BackAction onPress={() => goBack()} />
|
||||
<Appbar.Content title={'Edit Meme'} />
|
||||
<Appbar.Action
|
||||
icon={memeIsFavorite ? 'heart' : 'heart-outline'}
|
||||
onPress={handleFavorite}
|
||||
/>
|
||||
<Appbar.Action icon="delete" onPress={handleDelete} />
|
||||
</Appbar.Header>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
@@ -25,7 +117,29 @@ const EditMeme = () => {
|
||||
styles.flexGrow,
|
||||
styles.flexColumnSpaceBetween,
|
||||
{ backgroundColor: colors.background },
|
||||
]}></ScrollView>
|
||||
]}>
|
||||
<View style={[styles.flex, styles.justifyStart]}>
|
||||
<MemeEditor
|
||||
imageUri={meme.uri}
|
||||
memeTitle={memeTitle}
|
||||
setMemeTitle={setMemeTitle}
|
||||
memeDescription={memeDescription}
|
||||
setMemeDescription={setMemeDescription}
|
||||
memeTags={memeTags}
|
||||
setMemeTags={setMemeTags}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.flex, styles.justifyEnd]}>
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="floppy"
|
||||
onPress={handleSave}
|
||||
disabled={!memeTitle.valid || !memeDescription.valid || isSaving}
|
||||
loading={isSaving}>
|
||||
Save
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ import { Appbar, Button, useTheme } from 'react-native-paper';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { BSON } from 'realm';
|
||||
import { useRealm } from '@realm/react';
|
||||
import { useObject, useRealm } from '@realm/react';
|
||||
import { TagEditor } from '../components';
|
||||
import styles from '../styles';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
@@ -15,14 +15,14 @@ import { validateColor, validateTagName } from '../utilities';
|
||||
const EditTag = ({
|
||||
route,
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.EDIT_TAG>) => {
|
||||
const navigation = useNavigation();
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const realm = useRealm();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const tag = realm.objectForPrimaryKey(
|
||||
Tag,
|
||||
const tag = useObject<Tag>(
|
||||
Tag.schema.name,
|
||||
BSON.UUID.createFromHexString(route.params.id),
|
||||
)!;
|
||||
|
||||
@@ -36,14 +36,14 @@ const EditTag = ({
|
||||
tag.dateModified = new Date();
|
||||
});
|
||||
|
||||
navigation.goBack();
|
||||
goBack();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
realm.write(() => {
|
||||
for (const meme of tag.memes) {
|
||||
meme.dateModified = new Date();
|
||||
const tags = meme.tags as Set<Tag>;
|
||||
const tags = meme.tags as Realm.Set<Tag>;
|
||||
tags.delete(tag);
|
||||
meme.tagsLength = tags.size;
|
||||
}
|
||||
@@ -51,13 +51,13 @@ const EditTag = ({
|
||||
realm.delete(tag);
|
||||
});
|
||||
|
||||
navigation.goBack();
|
||||
goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
<Appbar.BackAction onPress={() => navigation.goBack()} />
|
||||
<Appbar.BackAction onPress={() => goBack()} />
|
||||
<Appbar.Content title={'Edit Tag'} />
|
||||
<Appbar.Action icon="delete" onPress={handleDelete} />
|
||||
</Appbar.Header>
|
||||
|
@@ -9,29 +9,34 @@ import {
|
||||
import { useQuery } from '@realm/react';
|
||||
import { useTheme, HelperText } from 'react-native-paper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { FlashList, MasonryFlashList } from '@shopify/flash-list';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import styles from '../styles';
|
||||
import { SORT_DIRECTION, memesSortQuery } from '../types';
|
||||
import { RootState, setNavVisible } from '../state';
|
||||
import { Meme, Tag } from '../database';
|
||||
import { useDimensions } from '../contexts';
|
||||
import { Meme } from '../database';
|
||||
import { ORIENTATION, useDimensions } from '../contexts';
|
||||
import { HideableHeader, MemesHeader } from '../components';
|
||||
import MemeCard from '../components/memes/memeCard';
|
||||
|
||||
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 Memes = () => {
|
||||
const { colors } = useTheme();
|
||||
const { orientation } = useDimensions();
|
||||
const { dimensions, orientation } = useDimensions();
|
||||
const sort = useSelector((state: RootState) => state.memes.sort);
|
||||
const sortDirection = useSelector(
|
||||
(state: RootState) => state.memes.sortDirection,
|
||||
);
|
||||
const view = useSelector((state: RootState) => state.memes.view);
|
||||
const favoritesOnly = useSelector(
|
||||
(state: RootState) => state.memes.favoritesOnly,
|
||||
);
|
||||
@@ -80,10 +85,11 @@ const Memes = () => {
|
||||
setScrollOffset(currentOffset);
|
||||
};
|
||||
|
||||
const flashListRef = useRef<FlashList<Tag>>(null);
|
||||
const flashListRef = useRef<FlashList<Meme>>(null);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
dispatch(setNavVisible(true));
|
||||
const handleBackPress = () => {
|
||||
if (scrollOffset > 0) {
|
||||
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||
@@ -96,7 +102,7 @@ const Memes = () => {
|
||||
|
||||
return () =>
|
||||
BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
|
||||
}, [flashListRef, scrollOffset]),
|
||||
}, [dispatch, scrollOffset]),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -115,6 +121,33 @@ 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}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@@ -80,6 +80,7 @@ const Tags = () => {
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
dispatch(setNavVisible(true));
|
||||
const handleBackPress = () => {
|
||||
if (scrollOffset > 0) {
|
||||
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||
@@ -92,7 +93,7 @@ const Tags = () => {
|
||||
|
||||
return () =>
|
||||
BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
|
||||
}, [flashListRef, scrollOffset]),
|
||||
}, [dispatch, scrollOffset]),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -111,31 +112,33 @@ const Tags = () => {
|
||||
}}
|
||||
/>
|
||||
</HideableHeader>
|
||||
{flashListPadding > 0 && (
|
||||
<FlashList
|
||||
ref={flashListRef}
|
||||
data={tags}
|
||||
estimatedItemSize={52}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={({ item: tag }) => <TagRow tag={tag} />}
|
||||
contentContainerStyle={{
|
||||
paddingTop:
|
||||
flashListPadding +
|
||||
dimensions.height *
|
||||
(orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04),
|
||||
...tagsStyles.flashList,
|
||||
}}
|
||||
ItemSeparatorComponent={() => <Divider />}
|
||||
ListEmptyComponent={() => (
|
||||
<HelperText
|
||||
type={'info'}
|
||||
style={[tagsStyles.helperText, styles.centerText]}>
|
||||
No tags found
|
||||
</HelperText>
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
)}
|
||||
<FlashList
|
||||
ref={flashListRef}
|
||||
data={tags}
|
||||
estimatedItemSize={50}
|
||||
estimatedListSize={{
|
||||
height: dimensions.height,
|
||||
width: dimensions.width * 0.92,
|
||||
}}
|
||||
showsVerticalScrollIndicator={false}
|
||||
renderItem={({ item: tag }) => <TagRow tag={tag} />}
|
||||
contentContainerStyle={{
|
||||
paddingTop:
|
||||
flashListPadding +
|
||||
dimensions.height *
|
||||
(orientation === ORIENTATION.PORTRAIT ? 0.02 : 0.04),
|
||||
...tagsStyles.flashList,
|
||||
}}
|
||||
ItemSeparatorComponent={() => <Divider />}
|
||||
ListEmptyComponent={() => (
|
||||
<HelperText
|
||||
type={'info'}
|
||||
style={[tagsStyles.helperText, styles.centerText]}>
|
||||
No tags found
|
||||
</HelperText>
|
||||
)}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
6
src/types/dimensions.ts
Normal file
6
src/types/dimensions.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
interface Dimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export { type Dimensions };
|
@@ -1,3 +1,4 @@
|
||||
export { type Dimensions } from './dimensions';
|
||||
export { ROUTE, type RootStackParamList } from './route';
|
||||
export {
|
||||
MEME_SORT,
|
||||
|
@@ -12,7 +12,7 @@ enum ROUTE {
|
||||
}
|
||||
|
||||
interface AddMemeRouteParamsFromFiles {
|
||||
uri: DocumentPickerResponse[];
|
||||
file: DocumentPickerResponse;
|
||||
}
|
||||
|
||||
interface EditMemeRouteParams {
|
||||
|
Reference in New Issue
Block a user