Add delete download support

This commit is contained in:
Bill Thornton 2021-11-24 00:37:34 -05:00
parent bd3cd7f1df
commit c57ae44a5a
6 changed files with 138 additions and 18 deletions

View File

@ -11,13 +11,19 @@ import { Button, ListItem } from 'react-native-elements';
import { getIconName } from '../utils/Icons';
const DownloadListItem = ({ item, index, onShare }) => (
const DownloadListItem = ({ item, index, onSelect, onShare, isEditMode = false, isSelected = false }) => (
<ListItem
topDivider={index === 0}
bottomDivider
>
{isEditMode &&
<ListItem.CheckBox
onPress={() => onSelect(item)}
checked={isSelected}
/>
}
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
<ListItem.Title>{item.title || item.fileName || item.itemId}</ListItem.Title>
</ListItem.Content>
{item.isComplete ?
<Button
@ -26,6 +32,7 @@ const DownloadListItem = ({ item, index, onShare }) => (
name: getIconName('share-outline'),
type: 'ionicon'
}}
disabled={isEditMode}
onPress={() => onShare(item)}
/> : <ActivityIndicator />
}
@ -35,7 +42,10 @@ const DownloadListItem = ({ item, index, onShare }) => (
DownloadListItem.propTypes = {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
onShare: PropTypes.func.isRequired
onSelect: PropTypes.func.isRequired,
onShare: PropTypes.func.isRequired,
isEditMode: PropTypes.bool,
isSelected: PropTypes.bool
};
export default DownloadListItem;

View File

@ -92,7 +92,6 @@ true;
case 'downloadFile':
console.log('Download item', data);
const url = new URL(data.item.url); // eslint-disable-line no-case-declarations
// console.log('url', url.searchParams.get('api_key'));
rootStore.downloadStore.add({
...data.item,
apiKey: url.searchParams.get('api_key')

View File

@ -2,6 +2,8 @@
"common": {
"beta": "BETA",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"ok": "OK",
"unknown": "Unknown"
},

View File

@ -76,7 +76,8 @@ const TabNavigator = observer(() => {
name={Screens.DownloadsTab}
component={DownloadScreen}
options={{
title: t('headings.downloads')
title: t('headings.downloads'),
headerShown: true
}}
/>
)}

View File

@ -4,12 +4,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { useNavigation } from '@react-navigation/native';
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import { observer } from 'mobx-react-lite';
import React, { useContext, useEffect, useState } from 'react';
import { FlatList, StyleSheet } from 'react-native';
import { ThemeContext } from 'react-native-elements';
import { useTranslation } from 'react-i18next';
import { Alert, FlatList, StyleSheet } from 'react-native';
import { Button, ThemeContext } from 'react-native-elements';
import { SafeAreaView } from 'react-native-safe-area-context';
import DownloadListItem from '../components/DownloadListItem';
@ -19,42 +21,116 @@ const getDownloadDir = download => `${FileSystem.documentDirectory}${download.se
const getDownloadUri = download => getDownloadDir(download) + encodeURI(download.filename);
async function ensureDirExists(dir) {
console.log('ensure directory', dir);
const info = await FileSystem.getInfoAsync(dir);
console.log('directory info', info);
if (!info.exists) {
await FileSystem.makeDirectoryAsync(dir, { intermediates: true });
}
}
const DownloadScreen = observer(() => {
const navigation = useNavigation();
const { rootStore } = useStores();
const { t } = useTranslation();
const { theme } = useContext(ThemeContext);
const [ isEditMode, setIsEditMode ] = useState(false);
const [ resumables, setResumables ] = useState([]);
const [ selectedItems, setSelectedItems ] = useState([]);
async function deleteItem(item) {
// TODO: Add user messaging on errors
try {
await FileSystem.deleteAsync(getDownloadDir(item));
rootStore.downloadStore.remove(rootStore.downloadStore.downloads.indexOf(item));
} catch (e) {
console.error('Failed to delete download', e);
}
}
function exitEditMode() {
setIsEditMode(false);
setSelectedItems([]);
}
function onDeleteItems(items) {
Alert.alert(
'Delete Downloads',
'These items will be permanently deleted from this device.',
[
{
text: t('common.cancel'),
onPress: exitEditMode
},
{
text: `Delete ${items.length} Downloads`,
onPress: async () => {
await Promise.all(items.map(deleteItem));
exitEditMode();
},
style: 'destructive'
}
]
);
}
React.useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
isEditMode ?
<Button
title={t('common.cancel')}
type='clear'
onPress={exitEditMode}
style={styles.leftButton}
/> :
null
),
headerRight: () => (
isEditMode ?
<Button
title={t('common.delete')}
type='clear'
style={styles.rightButton}
disabled={selectedItems.length < 1}
onPress={() => {
onDeleteItems(selectedItems);
}}
/> :
<Button
title={t('common.edit')}
type='clear'
onPress={() => {
setIsEditMode(true);
}}
style={styles.rightButton}
/>
)
});
}, [ navigation, isEditMode, selectedItems ]);
async function downloadFile(download) {
console.log('download file', download);
await ensureDirExists(getDownloadDir(download));
const url = download.url;
const uri = getDownloadUri(download);
console.log('url', url, uri);
const resumable = FileSystem.createDownloadResumable(
url,
uri,
{},
// TODO: Show download progress in ui
console.log
);
setResumables([ ...resumables, resumable ]);
try {
download.isDownloading = true;
rootStore.downloadStore.update(download, { isDownloading: true });
await resumable.downloadAsync();
download.isDownloading = false;
download.isComplete = true;
rootStore.downloadStore.update(download, {
isDownloading: false,
isComplete: true
});
} catch (e) {
console.error('Download failed', e);
download.isDownloading = false;
rootStore.downloadStore.update(download, { isDownloading: false });
}
}
@ -73,11 +149,20 @@ const DownloadScreen = observer(() => {
edges={[ 'right', 'left' ]}
>
<FlatList
data={rootStore.downloadStore.downloads}
data={[ ...rootStore.downloadStore.downloads ]}
renderItem={({ item, index }) => (
<DownloadListItem
item={item}
index={index}
isEditMode={isEditMode}
isSelected={selectedItems.includes(item)}
onSelect={() => {
if (selectedItems.includes(item)) {
setSelectedItems(selectedItems.filter(selected => selected !== item));
} else {
setSelectedItems([ ...selectedItems, item ]);
}
}}
onShare={async () => {
Sharing.shareAsync(
await FileSystem.getContentUriAsync(getDownloadUri(item))
@ -98,6 +183,12 @@ const styles = StyleSheet.create({
},
listContainer: {
marginTop: 1
},
leftButton: {
marginLeft: 8
},
rightButton: {
marginRight: 8
}
});

View File

@ -12,12 +12,28 @@ export default class DownloadStore {
add(download) {
// Do not allow duplicate downloads
if (!this.downloads.find(search => search.serverId === download.serverId && search.itemId === download.itemId)) {
this.downloads.push(download);
this.downloads = [ ...this.downloads, download ];
}
}
remove(index) {
this.downloads.splice(index, 1);
this.downloads = [
...this.downloads.slice(0, index),
...this.downloads.slice(index + 1)
];
}
update(original, changes = {}) {
const index = this.downloads.findIndex(search => search.serverId === original.serverId && search.itemId === original.itemId);
if (index > -1) {
const updated = {
...this.downloads[index],
...changes
};
this.downloads[index] = updated;
} else {
console.warn('trying to update download missing from store', original);
}
}
reset() {
@ -29,5 +45,6 @@ decorate(DownloadStore, {
downloads: [ observable ],
add: action,
remove: action,
update: action,
reset: action
});