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 { ActivityIndicator, StyleSheet, View } from 'react-native';
import { useTheme } from 'react-native-paper'; 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(); const { colors } = useTheme();
return ( return (
<View <View
style={[loadingViewStyles.view, { backgroundColor: colors.background }]}> {...props}
style={[
props.style,
loadingViewStyles.view,
{ backgroundColor: colors.background },
]}>
<ActivityIndicator size="large" color={colors.primary} /> <ActivityIndicator size="large" color={colors.primary} />
</View> </View>
); );

View File

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

View File

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

View File

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

View File

@@ -56,8 +56,20 @@ const AddMeme = ({
const [memeIsFavorite, setMemeIsFavorite] = useState(false); const [memeIsFavorite, setMemeIsFavorite] = useState(false);
const [memeTags, setMemeTags] = useState(new Map<string, Tag>()); const [memeTags, setMemeTags] = useState(new Map<string, Tag>());
useEffect(() => { const resetState = useCallback(async (newIndex = 0) => {
const loadMeme = async () => { setMemeLoading(true);
// 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); const mimeType = await guessMimeType(file.current.uri);
if (!mimeType) { if (!mimeType) {
setMemeError( setMemeError(
@@ -65,14 +77,12 @@ const AddMeme = ({
); );
return; return;
} }
setMemeMimeType(mimeType); setMemeMimeType(mimeType);
};
setMemeLoading(true);
void loadMeme();
setMemeLoading(false); setMemeLoading(false);
}, [file]); }, []);
useEffect(() => void resetState(), [resetState]);
const saveMeme = useCallback(async () => { const saveMeme = useCallback(async () => {
if (!memeMimeType) return; if (!memeMimeType) return;
@@ -126,40 +136,24 @@ const AddMeme = ({
goBack(); goBack();
}, [goBack, saveMeme]); }, [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 () => { const handleSaveAndNext = useCallback(async () => {
setIsSaving(true); setIsSaving(true);
await saveMeme(); await saveMeme();
setIsSaving(false); setIsSaving(false);
await resetState(index + 1);
setIndex(index + 1);
file.current = files.current[index + 1];
resetState();
}, [index, resetState, saveMeme]); }, [index, resetState, saveMeme]);
const handleSaveAndAddMore = useCallback(async () => { const handleSaveAndAddMore = useCallback(async () => {
setIsSavingAndAddingMore(true); setIsSavingAndAddingMore(true);
await saveMeme(); await saveMeme();
setIsSavingAndAddingMore(false); setIsSavingAndAddingMore(false);
setIndex(0);
const response = await pick({ const response = await pick({
type: allowedMimeTypes, type: allowedMimeTypes,
allowMultiSelection: true, allowMultiSelection: true,
}).catch(goBack); }).catch(goBack);
if (!response) return; if (!response) return;
files.current = documentPickerResponseToAddMemeFile(response); files.current = documentPickerResponseToAddMemeFile(response);
file.current = files.current[0]; await resetState(0);
resetState();
}, [goBack, resetState, saveMeme]); }, [goBack, resetState, saveMeme]);
return ( 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 { NativeStackScreenProps } from '@react-navigation/native-stack';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { useQuery, useRealm } from '@realm/react'; 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 { NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParamList, ROUTE } from '../types'; import { RootStackParamList, ROUTE } from '../types';
import { Meme } from '../database'; import { Meme } from '../database';
import { MemeViewItem } from '../components'; import { LoadingView, MemeViewItem } from '../components';
import { import {
copyMeme, copyMeme,
deleteMeme, deleteMeme,
@@ -56,22 +56,30 @@ const MemeView = ({
)!; )!;
const realm = useRealm(); const realm = useRealm();
const { ids } = route.params; const [isBlocked, setIsBlocked] = useState(false);
const [index, setIndex] = useState(route.params.index);
const [snackbarMessage, setSnackbarMessage] = useState<string>(); const [snackbarMessage, setSnackbarMessage] = useState<string>();
const flashListRef = useRef<FlashList<Meme>>(null); const flashListRef = useRef<FlashList<Meme>>(null);
const [index, setIndex] = useState(route.params.index);
const memes = useQuery<Meme>(Meme.schema.name, collectionIn => { 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 ( return (
<> <>
<Appbar.Header style={memeViewStyles.header}> <Appbar.Header style={memeViewStyles.header}>
<Appbar.BackAction onPress={() => navigation.goBack()} /> <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> </Appbar.Header>
<FlashList <FlashList
ref={flashListRef} ref={flashListRef}
@@ -90,12 +98,22 @@ const MemeView = ({
pagingEnabled pagingEnabled
horizontal horizontal
showsHorizontalScrollIndicator={false} 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 style={memeViewStyles.footer}>
<Appbar.Action <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])} onPress={() => favoriteMeme(realm, memes[index])}
disabled={isBlocked}
/> />
<Appbar.Action <Appbar.Action
icon="share" icon="share"
@@ -104,6 +122,7 @@ const MemeView = ({
setSnackbarMessage('Failed to share meme!'), setSnackbarMessage('Failed to share meme!'),
); );
}} }}
disabled={isBlocked}
/> />
<Appbar.Action <Appbar.Action
icon="content-copy" icon="content-copy"
@@ -112,25 +131,23 @@ const MemeView = ({
.then(() => setSnackbarMessage('Meme copied!')) .then(() => setSnackbarMessage('Meme copied!'))
.catch(() => setSnackbarMessage('Failed to copy meme!')); .catch(() => setSnackbarMessage('Failed to copy meme!'));
}} }}
disabled={isBlocked}
/> />
<Appbar.Action <Appbar.Action
icon="pencil" icon="pencil"
onPress={() => { onPress={() => {
editMeme(navigation, memes[index]); editMeme(navigation, memes[index]);
}} }}
disabled={isBlocked}
/> />
<Appbar.Action <Appbar.Action
icon="delete" icon="delete"
onPress={() => { onPress={async () => {
void deleteMeme(realm, storageUri, memes[index]); setIsBlocked(true);
if (index === memes.length - 1) { await deleteMeme(realm, storageUri, memes[index]);
setIndex(index - 1); setIsBlocked(false);
flashListRef.current?.scrollToIndex({
index: index - 1,
});
}
if (memes.length === 1) navigation.goBack();
}} }}
disabled={isBlocked}
/> />
</Appbar> </Appbar>
<Portal> <Portal>

View File

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

View File

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

View File

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