Fix crash when deleting memes

Again.

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-31 10:03:16 +03:00
parent f635c9d961
commit 880c20661e
9 changed files with 74 additions and 66 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { ComponentProps } from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { useTheme } from 'react-native-paper';
@@ -10,12 +10,17 @@ const loadingViewStyles = StyleSheet.create({
},
});
const LoadingView = () => {
const LoadingView = ({ ...props }: ComponentProps<typeof View>) => {
const { colors } = useTheme();
return (
<View
style={[loadingViewStyles.view, { backgroundColor: colors.background }]}>
{...props}
style={[
props.style,
loadingViewStyles.view,
{ backgroundColor: colors.background },
]}>
<ActivityIndicator size="large" color={colors.primary} />
</View>
);

View File

@@ -27,11 +27,7 @@ const MemeViewItem = ({ meme }: { meme: Meme }) => {
const { dimensions, loading, error } = useImageDimensions({ uri });
if (!error && (loading || !dimensions)) {
return (
<View style={{ width, height }}>
<LoadingView />
</View>
);
return <LoadingView style={{ width, height }} />;
}
return (

View File

@@ -17,7 +17,6 @@ const memeTypePlural = {
[MEME_TYPE.TEXT]: 'Text',
};
// eslint-disable-next-line @typescript-eslint/naming-convention
class Meme extends Object<Meme> {
id!: BSON.UUID;
memeType!: MEME_TYPE;

View File

@@ -2,7 +2,6 @@ import { BSON, Object, ObjectSchema } from 'realm';
import { Meme } from './meme';
import { generateRandomColor } from '../utilities';
// eslint-disable-next-line @typescript-eslint/naming-convention
class Tag extends Object<Tag> {
id!: BSON.UUID;
name!: string;

View File

@@ -56,23 +56,33 @@ const AddMeme = ({
const [memeIsFavorite, setMemeIsFavorite] = useState(false);
const [memeTags, setMemeTags] = useState(new Map<string, Tag>());
useEffect(() => {
const loadMeme = async () => {
const mimeType = await guessMimeType(file.current.uri);
if (!mimeType) {
setMemeError(
new Error('Could not determine MIME type or file is not supported.'),
);
return;
}
setMemeMimeType(mimeType);
};
const resetState = useCallback(async (newIndex = 0) => {
setMemeLoading(true);
void loadMeme();
// eslint-disable-next-line unicorn/no-useless-undefined
setMemeError(undefined);
setIndex(newIndex);
file.current = files.current[newIndex];
setMemeUri(file.current.uri);
setMemeFilename(file.current.filename);
setMemeTitle(validateMemeTitle('New Meme'));
setMemeIsFavorite(false);
setMemeTags(new Map<string, Tag>());
const mimeType = await guessMimeType(file.current.uri);
if (!mimeType) {
setMemeError(
new Error('Could not determine MIME type or file is not supported.'),
);
return;
}
setMemeMimeType(mimeType);
setMemeLoading(false);
}, [file]);
}, []);
useEffect(() => void resetState(), [resetState]);
const saveMeme = useCallback(async () => {
if (!memeMimeType) return;
@@ -126,40 +136,24 @@ const AddMeme = ({
goBack();
}, [goBack, saveMeme]);
const resetState = useCallback(() => {
setMemeUri(file.current.uri);
setMemeFilename(file.current.filename);
setMemeTitle(validateMemeTitle('New Meme'));
setMemeIsFavorite(false);
setMemeTags(new Map<string, Tag>());
}, []);
const handleSaveAndNext = useCallback(async () => {
setIsSaving(true);
await saveMeme();
setIsSaving(false);
setIndex(index + 1);
file.current = files.current[index + 1];
resetState();
await resetState(index + 1);
}, [index, resetState, saveMeme]);
const handleSaveAndAddMore = useCallback(async () => {
setIsSavingAndAddingMore(true);
await saveMeme();
setIsSavingAndAddingMore(false);
setIndex(0);
const response = await pick({
type: allowedMimeTypes,
allowMultiSelection: true,
}).catch(goBack);
if (!response) return;
files.current = documentPickerResponseToAddMemeFile(response);
file.current = files.current[0];
resetState();
await resetState(0);
}, [goBack, resetState, saveMeme]);
return (

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { StyleSheet } from 'react-native';
import { useQuery, useRealm } from '@realm/react';
@@ -8,7 +8,7 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParamList, ROUTE } from '../types';
import { Meme } from '../database';
import { MemeViewItem } from '../components';
import { LoadingView, MemeViewItem } from '../components';
import {
copyMeme,
deleteMeme,
@@ -56,22 +56,30 @@ const MemeView = ({
)!;
const realm = useRealm();
const { ids } = route.params;
const [index, setIndex] = useState(route.params.index);
const [isBlocked, setIsBlocked] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState<string>();
const flashListRef = useRef<FlashList<Meme>>(null);
const [index, setIndex] = useState(route.params.index);
const memes = useQuery<Meme>(Meme.schema.name, collectionIn => {
return collectionIn.filtered(multipleIdQuery(ids));
return collectionIn.filtered(multipleIdQuery(route.params.ids));
});
useEffect(() => {
if (memes.length === 0) navigation.goBack();
if (index >= memes.length) {
setIndex(memes.length - 1);
flashListRef.current?.scrollToIndex({ index: memes.length - 1 });
}
}, [index, memes.length, navigation]);
return (
<>
<Appbar.Header style={memeViewStyles.header}>
<Appbar.BackAction onPress={() => navigation.goBack()} />
<Appbar.Content title={memes[index].title} />
{/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */}
<Appbar.Content title={memes[index]?.title} />
</Appbar.Header>
<FlashList
ref={flashListRef}
@@ -90,12 +98,22 @@ const MemeView = ({
pagingEnabled
horizontal
showsHorizontalScrollIndicator={false}
renderItem={({ item: meme }) => <MemeViewItem meme={meme} />}
renderItem={({ item }) =>
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
item ? (
<MemeViewItem meme={item} />
) : (
<LoadingView style={{ width, height }} />
)
}
scrollEnabled={!isBlocked}
/>
<Appbar style={memeViewStyles.footer}>
<Appbar.Action
icon={memes[index].isFavorite ? 'heart' : 'heart-outline'}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
icon={memes[index]?.isFavorite ? 'heart' : 'heart-outline'}
onPress={() => favoriteMeme(realm, memes[index])}
disabled={isBlocked}
/>
<Appbar.Action
icon="share"
@@ -104,6 +122,7 @@ const MemeView = ({
setSnackbarMessage('Failed to share meme!'),
);
}}
disabled={isBlocked}
/>
<Appbar.Action
icon="content-copy"
@@ -112,25 +131,23 @@ const MemeView = ({
.then(() => setSnackbarMessage('Meme copied!'))
.catch(() => setSnackbarMessage('Failed to copy meme!'));
}}
disabled={isBlocked}
/>
<Appbar.Action
icon="pencil"
onPress={() => {
editMeme(navigation, memes[index]);
}}
disabled={isBlocked}
/>
<Appbar.Action
icon="delete"
onPress={() => {
void deleteMeme(realm, storageUri, memes[index]);
if (index === memes.length - 1) {
setIndex(index - 1);
flashListRef.current?.scrollToIndex({
index: index - 1,
});
}
if (memes.length === 1) navigation.goBack();
onPress={async () => {
setIsBlocked(true);
await deleteMeme(realm, storageUri, memes[index]);
setIsBlocked(false);
}}
disabled={isBlocked}
/>
</Appbar>
<Portal>

View File

@@ -91,8 +91,7 @@ const updateNoMedia = createAsyncThunk(
const validateSettings = createAsyncThunk(
'settings/validateSettings',
// eslint-disable-next-line @typescript-eslint/naming-convention
async (_, { dispatch, getState }) => {
async (ignored, { dispatch, getState }) => {
const state = getState() as RootState;
const { storageUri, noMedia } = state.settings;

View File

@@ -1,8 +1,7 @@
const packageName = 'com.karaolidis.terminallyonline';
const appName = 'Terminally Online';
const fileProvider = 'com.karaolidis.terminallyonline.rnshare.fileprovider';
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noOp = () => {};
export { packageName, appName, fileProvider, noOp };
export { packageName, appName, noOp };

View File

@@ -7,7 +7,7 @@ export {
rgbToRgba,
generateRandomColor,
} from './color';
export { packageName, appName, fileProvider, noOp } from './constants';
export { packageName, appName, noOp } from './constants';
export { multipleIdQuery } from './database';
export { getFlashListItemHeight, getFontAwesome5IconSize } from './dimensions';
export {