Add variable storage locations & batch adding
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -9,10 +9,7 @@ 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 {
|
||||
DocumentPickerResponse,
|
||||
pickSingle,
|
||||
} from 'react-native-document-picker';
|
||||
import { DocumentPickerResponse, pick } from 'react-native-document-picker';
|
||||
import { ROUTE, RootStackParamList } from '../../types';
|
||||
import { Meme, Tag } from '../../database';
|
||||
import { RootState } from '../../state';
|
||||
@@ -36,19 +33,24 @@ const AddMeme = ({
|
||||
(state: RootState) => state.settings.storageUri,
|
||||
)!;
|
||||
|
||||
const file = useRef(route.params.file);
|
||||
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 [memeUriError, setMemeUriError] = useState<Error>();
|
||||
const [memeFilename, setMemeFilename] = useState(
|
||||
file.current.name ?? undefined,
|
||||
);
|
||||
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 [isSavingAndAddingAnother, setIsSavingAndAddingAnother] =
|
||||
useState(false);
|
||||
const [isSavingAndAddingMore, setIsSavingAndAddingMore] = useState(false);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
const saveMeme = useCallback(async () => {
|
||||
const uuid = new BSON.UUID();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mimeType = file.current.type!;
|
||||
@@ -58,10 +60,10 @@ const AddMeme = ({
|
||||
const fileExtension = extension(mimeType) as string;
|
||||
if (!fileExtension) goBack();
|
||||
|
||||
const uri = AndroidScoped.appendPath(
|
||||
storageUri,
|
||||
`${uuid.toHexString()}-${Math.round(Date.now() / 1000)}.${fileExtension}`,
|
||||
);
|
||||
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);
|
||||
@@ -70,7 +72,7 @@ const AddMeme = ({
|
||||
const meme: Meme | undefined = realm.create<Meme>(Meme.schema.name, {
|
||||
id: uuid,
|
||||
type: memeType,
|
||||
uri,
|
||||
filename,
|
||||
mimeType,
|
||||
size,
|
||||
title: memeTitle.parsed,
|
||||
@@ -87,6 +89,47 @@ const AddMeme = ({
|
||||
});
|
||||
}, [goBack, memeIsFavorite, memeTags, memeTitle.parsed, realm, storageUri]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
setIsSaving(true);
|
||||
await saveMeme();
|
||||
setIsSaving(false);
|
||||
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.name ?? undefined);
|
||||
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);
|
||||
files.current = (await pick({
|
||||
type: allowedMimeTypes,
|
||||
allowMultiSelection: true,
|
||||
}).catch(goBack)) as DocumentPickerResponse[];
|
||||
file.current = files.current[0];
|
||||
|
||||
setMemeUri(file.current.uri);
|
||||
setMemeFilename(file.current.name ?? undefined);
|
||||
setMemeTitle(validateMemeTitle('New Meme'));
|
||||
setMemeIsFavorite(false);
|
||||
setMemeTags(new Map<string, Tag>());
|
||||
}, [goBack, saveMeme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
@@ -98,7 +141,7 @@ const AddMeme = ({
|
||||
/>
|
||||
</Appbar.Header>
|
||||
<Banner
|
||||
visible={!!memeUriError}
|
||||
visible={!!memeError}
|
||||
actions={[
|
||||
{
|
||||
label: 'Cancel',
|
||||
@@ -119,8 +162,9 @@ const AddMeme = ({
|
||||
<View style={editorStyles.editorView}>
|
||||
<MemeEditor
|
||||
memeUri={memeUri}
|
||||
memeUriError={memeUriError}
|
||||
setMemeUriError={setMemeUriError}
|
||||
memeFilename={memeFilename}
|
||||
memeError={memeError}
|
||||
setMemeError={setMemeError}
|
||||
memeTitle={memeTitle}
|
||||
setMemeTitle={setMemeTitle}
|
||||
memeTags={memeTags}
|
||||
@@ -131,46 +175,31 @@ const AddMeme = ({
|
||||
<Button
|
||||
mode="contained-tonal"
|
||||
icon="plus"
|
||||
onPress={async () => {
|
||||
setIsSavingAndAddingAnother(true);
|
||||
await handleSave();
|
||||
setIsSavingAndAddingAnother(false);
|
||||
file.current = (await pickSingle({
|
||||
type: allowedMimeTypes,
|
||||
}).catch(goBack)) as DocumentPickerResponse;
|
||||
setMemeUri(file.current.uri);
|
||||
setMemeTitle(validateMemeTitle('New Meme'));
|
||||
setMemeIsFavorite(false);
|
||||
setMemeTags(new Map<string, Tag>());
|
||||
}}
|
||||
onPress={handleSaveAndAddMore}
|
||||
disabled={
|
||||
!memeTitle.valid ||
|
||||
isSaving ||
|
||||
isSavingAndAddingAnother ||
|
||||
!!memeUriError
|
||||
isSavingAndAddingMore ||
|
||||
!!memeError ||
|
||||
!isLastFile
|
||||
}
|
||||
loading={isSavingAndAddingAnother}
|
||||
loading={isSavingAndAddingMore}
|
||||
style={editorStyles.saveAndAddButton}>
|
||||
Save & Add
|
||||
Save & Add More
|
||||
</Button>
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="floppy"
|
||||
onPress={async () => {
|
||||
setIsSaving(true);
|
||||
await handleSave();
|
||||
setIsSaving(false);
|
||||
goBack();
|
||||
}}
|
||||
onPress={isLastFile ? handleSave : handleSaveAndNext}
|
||||
disabled={
|
||||
!memeTitle.valid ||
|
||||
isSaving ||
|
||||
isSavingAndAddingAnother ||
|
||||
!!memeUriError
|
||||
isSavingAndAddingMore ||
|
||||
!!memeError
|
||||
}
|
||||
loading={isSaving}
|
||||
style={editorStyles.saveButton}>
|
||||
Save
|
||||
{isLastFile ? 'Save' : 'Save & Next'}
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@@ -26,10 +26,9 @@ const AddTag = () => {
|
||||
|
||||
// Although saving tags is instantaneous, we still want to show a loading
|
||||
// indicator to prevent the user from spamming the save button.
|
||||
const [isSavingAndAddingAnother, setIsSavingAndAddingAnother] =
|
||||
useState(false);
|
||||
const [isSavingAndAddingMore, setIsSavingAndAddingMore] = useState(false);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const saveTag = useCallback(() => {
|
||||
realm.write(() => {
|
||||
realm.create(Tag.schema.name, {
|
||||
name: tagName.parsed,
|
||||
@@ -38,6 +37,19 @@ const AddTag = () => {
|
||||
});
|
||||
}, [realm, tagColor.parsed, tagName.parsed]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
saveTag();
|
||||
goBack();
|
||||
}, [goBack, saveTag]);
|
||||
|
||||
const handleSaveAndAddMore = useCallback(() => {
|
||||
setIsSavingAndAddingMore(true);
|
||||
saveTag();
|
||||
setTimeout(() => setIsSavingAndAddingMore(false), 250);
|
||||
setTagName(validateTagName('newTag'));
|
||||
setTagColor(validateColor(generateRandomColor()));
|
||||
}, [saveTag]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar.Header>
|
||||
@@ -64,26 +76,17 @@ const AddTag = () => {
|
||||
<Button
|
||||
mode="contained-tonal"
|
||||
icon="plus"
|
||||
onPress={() => {
|
||||
setIsSavingAndAddingAnother(true);
|
||||
handleSave();
|
||||
setTimeout(() => setIsSavingAndAddingAnother(false), 250);
|
||||
setTagName(validateTagName('newTag'));
|
||||
setTagColor(validateColor(generateRandomColor()));
|
||||
}}
|
||||
disabled={!tagName.valid || isSavingAndAddingAnother}
|
||||
loading={isSavingAndAddingAnother}
|
||||
onPress={handleSaveAndAddMore}
|
||||
disabled={!tagName.valid || isSavingAndAddingMore}
|
||||
loading={isSavingAndAddingMore}
|
||||
style={editorStyles.saveAndAddButton}>
|
||||
Save & Add
|
||||
</Button>
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="floppy"
|
||||
onPress={() => {
|
||||
handleSave();
|
||||
goBack();
|
||||
}}
|
||||
disabled={!tagName.valid || isSavingAndAddingAnother}
|
||||
onPress={handleSave}
|
||||
disabled={!tagName.valid || isSavingAndAddingMore}
|
||||
style={editorStyles.saveButton}>
|
||||
Save
|
||||
</Button>
|
||||
|
@@ -9,6 +9,8 @@ import { BSON } from 'realm';
|
||||
import { RootStackParamList, ROUTE } from '../../types';
|
||||
import { pickSingle } from 'react-native-document-picker';
|
||||
import { AndroidScoped, FileSystem } from 'react-native-file-access';
|
||||
import { extension } from 'react-native-mime-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Tag, Meme } from '../../database';
|
||||
import {
|
||||
StringValidationResult,
|
||||
@@ -21,8 +23,6 @@ import {
|
||||
} from '../../utilities';
|
||||
import { MemeEditor } from '../../components';
|
||||
import editorStyles from './editorStyles';
|
||||
import { extension } from 'react-native-mime-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../../state';
|
||||
|
||||
const EditMeme = ({
|
||||
@@ -42,10 +42,11 @@ const EditMeme = ({
|
||||
Meme.schema.name,
|
||||
BSON.UUID.createFromHexString(route.params.id),
|
||||
)!;
|
||||
const uri = AndroidScoped.appendPath(storageUri, meme.filename);
|
||||
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
|
||||
const [memeUriError, setMemeUriError] = useState<Error>();
|
||||
const [memeError, setMemeError] = useState<Error>();
|
||||
const [memeTitle, setMemeTitle] = useState(validateMemeTitle(meme.title));
|
||||
const [memeTags, setMemeTags] = useState(
|
||||
new Map<string, Tag>(meme.tags.map(tag => [tag.id.toHexString(), tag])),
|
||||
@@ -64,6 +65,8 @@ const EditMeme = ({
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
setIsSaving(true);
|
||||
|
||||
realm.write(() => {
|
||||
meme.tags.forEach(tag => {
|
||||
if (!memeTags.has(tag.id.toHexString())) {
|
||||
@@ -87,7 +90,17 @@ const EditMeme = ({
|
||||
meme.tagsLength = memeTags.size;
|
||||
meme.dateModified = new Date();
|
||||
});
|
||||
}, [meme, memeTags, memeTitle.parsed, realm]);
|
||||
|
||||
setIsSaving(false);
|
||||
goBack();
|
||||
}, [goBack, meme, memeTags, memeTitle.parsed, realm]);
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
setIsSaving(true);
|
||||
await deleteMeme(realm, storageUri, meme);
|
||||
setIsSaving(false);
|
||||
goBack();
|
||||
}, [goBack, meme, realm, storageUri]);
|
||||
|
||||
const handleFixUri = useCallback(async () => {
|
||||
const file = await pickSingle({ type: allowedMimeTypes }).catch(noOp);
|
||||
@@ -101,16 +114,16 @@ const EditMeme = ({
|
||||
const fileExtension = extension(mimeType) as string;
|
||||
if (!fileExtension) return;
|
||||
|
||||
const uri = AndroidScoped.appendPath(
|
||||
storageUri,
|
||||
`${meme.id.toHexString()}-${Date.now() / 1000}.${fileExtension}`,
|
||||
);
|
||||
const filename = `${meme.id.toHexString()}-${
|
||||
Date.now() / 1000
|
||||
}.${fileExtension}`;
|
||||
const newUri = AndroidScoped.appendPath(storageUri, filename);
|
||||
|
||||
await FileSystem.cp(file.uri, uri);
|
||||
const { size } = await FileSystem.stat(uri);
|
||||
await FileSystem.cp(file.uri, newUri);
|
||||
const { size } = await FileSystem.stat(newUri);
|
||||
|
||||
realm.write(() => {
|
||||
meme.uri = uri;
|
||||
meme.filename = filename;
|
||||
meme.type = memeType;
|
||||
meme.mimeType = mimeType;
|
||||
meme.size = size;
|
||||
@@ -126,18 +139,10 @@ const EditMeme = ({
|
||||
icon={meme.isFavorite ? 'heart' : 'heart-outline'}
|
||||
onPress={() => favoriteMeme(realm, meme)}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="delete"
|
||||
onPress={async () => {
|
||||
setIsSaving(true);
|
||||
await deleteMeme(realm, meme);
|
||||
setIsSaving(false);
|
||||
goBack();
|
||||
}}
|
||||
/>
|
||||
<Appbar.Action icon="delete" onPress={handleDelete} />
|
||||
</Appbar.Header>
|
||||
<Banner
|
||||
visible={!!memeUriError}
|
||||
visible={!!memeError}
|
||||
actions={[
|
||||
{
|
||||
label: 'Fix URI',
|
||||
@@ -145,12 +150,7 @@ const EditMeme = ({
|
||||
},
|
||||
{
|
||||
label: 'Delete Meme',
|
||||
onPress: async () => {
|
||||
setIsSaving(true);
|
||||
await deleteMeme(realm, meme);
|
||||
setIsSaving(false);
|
||||
goBack();
|
||||
},
|
||||
onPress: handleDelete,
|
||||
},
|
||||
]}>
|
||||
The URI for this meme appears to be broken. This may have been caused by
|
||||
@@ -166,9 +166,10 @@ const EditMeme = ({
|
||||
]}>
|
||||
<View style={editorStyles.editorView}>
|
||||
<MemeEditor
|
||||
memeUri={meme.uri}
|
||||
memeUriError={memeUriError}
|
||||
setMemeUriError={setMemeUriError}
|
||||
memeUri={uri}
|
||||
memeFilename={meme.filename}
|
||||
memeError={memeError}
|
||||
setMemeError={setMemeError}
|
||||
memeTitle={memeTitle}
|
||||
setMemeTitle={handleMemeTitleChange}
|
||||
memeTags={memeTags}
|
||||
@@ -179,14 +180,9 @@ const EditMeme = ({
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="floppy"
|
||||
onPress={() => {
|
||||
setIsSaving(true);
|
||||
handleSave();
|
||||
setIsSaving(false);
|
||||
goBack();
|
||||
}}
|
||||
onPress={handleSave}
|
||||
disabled={
|
||||
!memeTitle.valid || !hasChanges || isSaving || !!memeUriError
|
||||
!memeTitle.valid || !hasChanges || isSaving || !!memeError
|
||||
}
|
||||
loading={isSaving}
|
||||
style={editorStyles.soloSaveButton}>
|
||||
|
@@ -51,7 +51,9 @@ const EditTag = ({
|
||||
tag.color = tagColor.parsed;
|
||||
tag.dateModified = new Date();
|
||||
});
|
||||
}, [realm, tag, tagColor.parsed, tagName.parsed]);
|
||||
|
||||
goBack();
|
||||
}, [goBack, realm, tag, tagColor.parsed, tagName.parsed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -87,10 +89,7 @@ const EditTag = ({
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="floppy"
|
||||
onPress={() => {
|
||||
handleSave();
|
||||
goBack();
|
||||
}}
|
||||
onPress={handleSave}
|
||||
disabled={!tagName.valid || !tagColor.valid || !hasChanges}
|
||||
style={editorStyles.soloSaveButton}>
|
||||
Save
|
||||
|
@@ -17,6 +17,8 @@ import {
|
||||
multipleIdQuery,
|
||||
shareMeme,
|
||||
} from '../utilities';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../state';
|
||||
|
||||
const memeViewStyles = StyleSheet.create({
|
||||
// eslint-disable-next-line react-native/no-color-literals
|
||||
@@ -48,6 +50,10 @@ const MemeView = ({
|
||||
}: NativeStackScreenProps<RootStackParamList, ROUTE.MEME_VIEW>) => {
|
||||
const { height, width } = useSafeAreaFrame();
|
||||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const storageUri = useSelector(
|
||||
(state: RootState) => state.settings.storageUri,
|
||||
)!;
|
||||
const realm = useRealm();
|
||||
|
||||
const { ids } = route.params;
|
||||
@@ -97,7 +103,7 @@ const MemeView = ({
|
||||
<Appbar.Action
|
||||
icon="share"
|
||||
onPress={() => {
|
||||
shareMeme(memes[index]).catch(() => {
|
||||
shareMeme(realm, storageUri, memes[index]).catch(() => {
|
||||
setSnackbarMessage('Failed to share meme!');
|
||||
setSnackbarVisible(true);
|
||||
});
|
||||
@@ -106,7 +112,7 @@ const MemeView = ({
|
||||
<Appbar.Action
|
||||
icon="content-copy"
|
||||
onPress={async () => {
|
||||
await copyMeme(memes[index])
|
||||
await copyMeme(realm, storageUri, memes[index])
|
||||
.then(() => {
|
||||
setSnackbarMessage('Meme copied!');
|
||||
setSnackbarVisible(true);
|
||||
@@ -132,7 +138,7 @@ const MemeView = ({
|
||||
index: index - 1,
|
||||
});
|
||||
}
|
||||
void deleteMeme(realm, memes[index]);
|
||||
void deleteMeme(realm, storageUri, memes[index]);
|
||||
if (memes.length === 1) navigation.goBack();
|
||||
}}
|
||||
/>
|
||||
|
@@ -10,7 +10,6 @@ import {
|
||||
Text,
|
||||
useTheme,
|
||||
} from 'react-native-paper';
|
||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import type {} from 'redux-thunk/extend-redux';
|
||||
import {
|
||||
@@ -18,8 +17,11 @@ import {
|
||||
setGridColumns,
|
||||
setMasonryColumns,
|
||||
setNoMedia,
|
||||
setStorageUri,
|
||||
} from '../state';
|
||||
import StorageLocationChangeDialog from '../components/storageLocationChangeDialog';
|
||||
import { useRealm } from '@realm/react';
|
||||
import { FileSystem, FileStat } from 'react-native-file-access';
|
||||
import { Meme } from '../database';
|
||||
|
||||
const settingsStyles = StyleSheet.create({
|
||||
scrollView: {
|
||||
@@ -51,18 +53,38 @@ const Settings = () => {
|
||||
const gridColumns = useSelector(
|
||||
(state: RootState) => state.settings.gridColumns,
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const storageUri = useSelector(
|
||||
(state: RootState) => state.settings.storageUri,
|
||||
)!;
|
||||
const dispatch = useDispatch();
|
||||
const realm = useRealm();
|
||||
|
||||
const [isOptimizingDatabase, setIsOptimizingDatabase] = useState(false);
|
||||
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
||||
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||
|
||||
const optimizeDatabase = () => {
|
||||
setIsOptimizingDatabase(true);
|
||||
// TODO: clean up missing / extra files
|
||||
setSnackbarMessage('Database optimized!');
|
||||
const [
|
||||
storageLocationChangeDialogVisible,
|
||||
setStorageLocationChangeDialogVisible,
|
||||
] = useState(false);
|
||||
|
||||
const refreshMemeMetadata = async () => {
|
||||
const stat = await FileSystem.statDir(storageUri);
|
||||
|
||||
const statMap = new Map<string, FileStat>();
|
||||
stat.forEach(s => statMap.set(s.filename, s));
|
||||
|
||||
const memes = realm.objects<Meme>(Meme.schema.name);
|
||||
|
||||
realm.write(() => {
|
||||
memes.forEach(meme => {
|
||||
const fileStat = statMap.get(meme.filename);
|
||||
meme.size = fileStat?.size ?? 0;
|
||||
});
|
||||
});
|
||||
|
||||
setSnackbarMessage('Meme metadata refreshed.');
|
||||
setSnackbarVisible(true);
|
||||
setIsOptimizingDatabase(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -111,15 +133,18 @@ const Settings = () => {
|
||||
/>
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<List.Subheader>Media Storage</List.Subheader>
|
||||
<List.Subheader>Storage</List.Subheader>
|
||||
<Button
|
||||
mode="elevated"
|
||||
style={settingsStyles.marginBottom}
|
||||
onPress={async () => {
|
||||
const { uri } = await openDocumentTree(true);
|
||||
void dispatch(setStorageUri(uri));
|
||||
}}>
|
||||
Change External Storage Path
|
||||
onPress={() => setStorageLocationChangeDialogVisible(true)}>
|
||||
Change Storage Location
|
||||
</Button>
|
||||
<Button
|
||||
mode="elevated"
|
||||
style={settingsStyles.marginBottom}
|
||||
onPress={refreshMemeMetadata}>
|
||||
Refresh Meme Metadata
|
||||
</Button>
|
||||
<View style={settingsStyles.hideMediaSwitch}>
|
||||
<Text>Hide media from gallery</Text>
|
||||
@@ -131,17 +156,16 @@ const Settings = () => {
|
||||
/>
|
||||
</View>
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<List.Subheader>Database</List.Subheader>
|
||||
<Button
|
||||
mode="elevated"
|
||||
loading={isOptimizingDatabase}
|
||||
onPress={optimizeDatabase}>
|
||||
Optimize Database Now
|
||||
</Button>
|
||||
</List.Section>
|
||||
</ScrollView>
|
||||
<Portal>
|
||||
<Portal>
|
||||
<StorageLocationChangeDialog
|
||||
visible={storageLocationChangeDialogVisible}
|
||||
setVisible={setStorageLocationChangeDialogVisible}
|
||||
setSnackbarVisible={setSnackbarVisible}
|
||||
setSnackbarMessage={setSnackbarMessage}
|
||||
/>
|
||||
</Portal>
|
||||
<Snackbar
|
||||
visible={snackbarVisible}
|
||||
onDismiss={() => setSnackbarVisible(false)}
|
||||
|
Reference in New Issue
Block a user