214 lines
6.8 KiB
TypeScript
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;
|