Fix crash when deleting memes
Again. Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
@@ -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 (
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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 };
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user