diff --git a/android/app/build.gradle b/android/app/build.gradle index 3ecfc63..c74c06e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -285,6 +285,8 @@ dependencies { } else { implementation jscFlavor } + + implementation project(':react-native-fs') } if (isNewArchitectureEnabled()) { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 045c4e7..ce3243e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ package="com.browses3"> + + + android:theme="@style/AppTheme" + android:requestLegacyExternalStorage="true"> { ToastAndroid.show('Successfully saved settings', ToastAndroid.SHORT); }) .catch(e => { - console.log(e); ToastAndroid.show('Failed to save settings', ToastAndroid.SHORT); throw e; }); diff --git a/src/screens/Buckets.js b/src/screens/Buckets.js index 10ed579..3db9a20 100644 --- a/src/screens/Buckets.js +++ b/src/screens/Buckets.js @@ -1,11 +1,12 @@ import React, {useState, useEffect} from 'react'; -import {ScrollView, ToastAndroid} from 'react-native'; +import {ScrollView, ToastAndroid, Text} from 'react-native'; import useS3 from '../hooks/useS3'; import Bucket from '../components/Bucket'; const Buckets = () => { const s3 = useS3(); const [buckets, setBuckets] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { if (s3 && !buckets.length) { @@ -16,10 +17,15 @@ const Buckets = () => { } setBuckets(data.Buckets); + setLoading(false); }); } }, [s3]); + if (loading) { + return Loading...; + } + return ( {buckets.map(bucket => ( diff --git a/src/screens/DirectoryDetails.js b/src/screens/DirectoryDetails.js index df29f75..50fa522 100644 --- a/src/screens/DirectoryDetails.js +++ b/src/screens/DirectoryDetails.js @@ -1,5 +1,5 @@ import React, {useState, useEffect} from 'react'; -import {ScrollView, ToastAndroid} from 'react-native'; +import {ScrollView, ToastAndroid, Text} from 'react-native'; import {useRoute} from '@react-navigation/native'; import useS3 from '../hooks/useS3'; import Directory from '../components/Directory'; @@ -9,6 +9,7 @@ const DirectoryDetails = () => { const route = useRoute(); const s3 = useS3(); const [contents, setContents] = useState({dirs: [], files: []}); + const [loading, setLoading] = useState(true); useEffect(() => { if (s3 && !contents.length) { @@ -29,11 +30,16 @@ const DirectoryDetails = () => { } setContents({dirs: data.CommonPrefixes, files: data.Contents}); + setLoading(false); }, ); } }, [s3]); + if (loading) { + return Loading...; + } + return ( {contents.dirs.map(dir => ( diff --git a/src/screens/FileDetails.js b/src/screens/FileDetails.js index bc55382..831e568 100644 --- a/src/screens/FileDetails.js +++ b/src/screens/FileDetails.js @@ -1,14 +1,16 @@ -import React, {useState, useEffect} from 'react'; +import React, {useState, useEffect, useCallback} from 'react'; import { StyleSheet, ScrollView, Text, + View, Button, ToastAndroid, PermissionsAndroid, } from 'react-native'; import {useRoute} from '@react-navigation/native'; import prettyBytes from 'pretty-bytes'; +import RNFS from 'react-native-fs'; import useS3 from '../hooks/useS3'; const FileDetails = () => { @@ -18,10 +20,10 @@ const FileDetails = () => { useEffect(() => { if (s3 && !data) { - s3.getObject( + s3.headObject( { Bucket: route.params.bucket, - Key: route.params.file + Key: route.params.file, }, (err, data) => { if (err) { @@ -35,6 +37,66 @@ const FileDetails = () => { } }, [s3]); + const download = useCallback(() => { + PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, + ) + .then(granted => { + if (granted != PermissionsAndroid.RESULTS.GRANTED) { + throw new Error('No permission'); + } + }) + .then(() => + PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, + ), + ) + .then(granted => { + if (granted != PermissionsAndroid.RESULTS.GRANTED) { + throw new Error('No permission'); + } + }) + .then(() => { + ToastAndroid.show('Downloading...', ToastAndroid.LONG); + + s3.getObject( + { + Bucket: route.params.bucket, + Key: route.params.file, + }, + (err, data) => { + if (err) { + ToastAndroid.show('Failed to download file', ToastAndroid.SHORT); + return; + } + + const dir = `${RNFS.DownloadDirectoryPath}/BrowseS3/${route.params.bucket}`; + const file = `${dir}/${route.params.file.replace(/\//g, '_')}`; + const fileContents = data.Body.toString('base64'); + + RNFS.mkdir(dir) + .then(() => RNFS.writeFile(file, fileContents, 'base64')) + .then(() => RNFS.scanFile(file)) + .then(() => { + ToastAndroid.show('Download completed', ToastAndroid.SHORT); + }) + .catch(e => { + ToastAndroid.show( + 'Failed to download file', + ToastAndroid.SHORT, + ); + }); + }, + ); + }) + .catch(() => { + ToastAndroid.show( + 'You need to grant the write permission to download files', + ToastAndroid.SHORT, + ); + }); + }, [s3, route]); + if (!data) { return Loading...; } @@ -48,6 +110,9 @@ const FileDetails = () => { Size: {prettyBytes(data.ContentLength)} Content type: {data.ContentType} + +