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/editors/addMeme.tsx
2023-07-29 20:55:18 +03:00

214 lines
6.8 KiB
TypeScript

import React, { useCallback, useRef, useState } from 'react';
import { Appbar, Banner, Button, useTheme } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { ScrollView, View } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useRealm } from '@realm/react';
import { BSON } from 'realm';
import { AndroidScoped, FileSystem } from 'react-native-file-access';
import { useSelector } from 'react-redux';
import { extension } from 'react-native-mime-types';
import { useDeviceOrientation } from '@react-native-community/hooks';
import { pick } from 'react-native-document-picker';
import {
documentPickerResponseToAddMemeFile,
ROUTE,
RootStackParamList,
} from '../../types';
import { Meme, Tag } from '../../database';
import { RootState } from '../../state';
import {
allowedMimeTypes,
getMemeType,
validateMemeTitle,
} from '../../utilities';
import { MemeEditor } from '../../components';
import editorStyles from './editorStyles';
const AddMeme = ({
route,
}: NativeStackScreenProps<RootStackParamList, ROUTE.ADD_MEME>) => {
const { goBack } = useNavigation();
const { colors } = useTheme();
const orientation = useDeviceOrientation();
const realm = useRealm();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const storageUri = useSelector(
(state: RootState) => state.settings.storageUri,
)!;
const [index, setIndex] = useState(0);
const files = useRef(route.params.files);
const file = useRef(files.current[index]);
const isLastFile = index === files.current.length - 1;
const [memeUri, setMemeUri] = useState(file.current.uri);
const [memeFilename, setMemeFilename] = useState(file.current.filename);
const [memeError, setMemeError] = useState<Error>();
const [memeTitle, setMemeTitle] = useState(validateMemeTitle('New Meme'));
const [memeIsFavorite, setMemeIsFavorite] = useState(false);
const [memeTags, setMemeTags] = useState(new Map<string, Tag>());
const [isSaving, setIsSaving] = useState(false);
const [isSavingAndAddingMore, setIsSavingAndAddingMore] = useState(false);
const saveMeme = useCallback(async () => {
const uuid = new BSON.UUID();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mimeType = file.current.type!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const memeType = getMemeType(mimeType)!;
const fileExtension = extension(mimeType) as string;
if (!fileExtension) goBack();
const filename = `${uuid.toHexString()}-${Math.round(
Date.now() / 1000,
)}.${fileExtension}`;
const uri = AndroidScoped.appendPath(storageUri, filename);
await FileSystem.cp(file.current.uri, uri);
const { size } = await FileSystem.stat(uri);
realm.write(() => {
const meme: Meme | undefined = realm.create<Meme>(Meme.schema.name, {
id: uuid,
type: memeType,
filename,
mimeType,
size,
title: memeTitle.parsed,
isFavorite: memeIsFavorite,
tags: [...memeTags.values()],
tagsLength: memeTags.size,
});
memeTags.forEach(tag => {
tag.dateModified = new Date();
tag.memes.push(meme);
tag.memesLength = tag.memes.length;
});
});
}, [goBack, memeIsFavorite, memeTags, memeTitle.parsed, realm, storageUri]);
const handleSave = useCallback(async () => {
setIsSaving(true);
await saveMeme();
goBack();
}, [goBack, saveMeme]);
const handleSaveAndNext = useCallback(async () => {
setIsSaving(true);
await saveMeme();
setIsSaving(false);
setIndex(index + 1);
file.current = files.current[index + 1];
setMemeUri(file.current.uri);
setMemeFilename(file.current.filename);
setMemeTitle(validateMemeTitle('New Meme'));
setMemeIsFavorite(false);
setMemeTags(new Map<string, Tag>());
}, [index, 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];
setMemeUri(file.current.uri);
setMemeFilename(file.current.filename);
setMemeTitle(validateMemeTitle('New Meme'));
setMemeIsFavorite(false);
setMemeTags(new Map<string, Tag>());
}, [goBack, saveMeme]);
return (
<>
<Appbar.Header>
<Appbar.BackAction onPress={() => goBack()} />
<Appbar.Content title={'Add Meme'} />
<Appbar.Action
icon={memeIsFavorite ? 'heart' : 'heart-outline'}
onPress={() => setMemeIsFavorite(!memeIsFavorite)}
/>
</Appbar.Header>
<Banner
visible={!!memeError}
actions={[
{
label: 'Cancel',
onPress: goBack,
},
]}>
The selected URI appears to be broken. This may have been caused by the
file being corrupted or unsupported.
</Banner>
<ScrollView
contentContainerStyle={[
editorStyles.scrollView,
orientation === 'portrait'
? editorStyles.scrollViewPortrait
: editorStyles.scrollViewLandscape,
{ backgroundColor: colors.background },
]}>
<View style={editorStyles.editorView}>
<MemeEditor
memeUri={memeUri}
memeFilename={memeFilename}
memeError={memeError}
setMemeError={setMemeError}
memeTitle={memeTitle}
setMemeTitle={setMemeTitle}
memeTags={memeTags}
setMemeTags={setMemeTags}
/>
</View>
<View style={editorStyles.saveButtonView}>
<Button
mode="contained-tonal"
icon="plus"
onPress={handleSaveAndAddMore}
disabled={
!memeTitle.valid ||
isSaving ||
isSavingAndAddingMore ||
!!memeError ||
!isLastFile
}
loading={isSavingAndAddingMore}
style={editorStyles.saveAndAddButton}>
Save & Add More
</Button>
<Button
mode="contained"
icon="floppy"
onPress={isLastFile ? handleSave : handleSaveAndNext}
disabled={
!memeTitle.valid ||
isSaving ||
isSavingAndAddingMore ||
!!memeError
}
loading={isSaving}
style={editorStyles.saveButton}>
{isLastFile ? 'Save' : 'Save & Next'}
</Button>
</View>
</ScrollView>
</>
);
};
export default AddMeme;