diff --git a/app/src/component/storageStatsCard/index.js b/app/src/component/storageStatsCard/index.js
new file mode 100644
index 00000000..22a04726
--- /dev/null
+++ b/app/src/component/storageStatsCard/index.js
@@ -0,0 +1,4 @@
+import { connect } from 'react-redux';
+import StorageStatsCard from './view';
+
+export default connect()(StorageStatsCard);
diff --git a/app/src/component/storageStatsCard/view.js b/app/src/component/storageStatsCard/view.js
new file mode 100644
index 00000000..c21722ed
--- /dev/null
+++ b/app/src/component/storageStatsCard/view.js
@@ -0,0 +1,128 @@
+import React from 'react';
+import { normalizeURI, parseURI } from 'lbry-redux';
+import {
+ ActivityIndicator,
+ Platform,
+ Switch,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import { formatBytes } from '../../utils/helper';
+import Colors from '../../styles/colors';
+import storageStatsStyle from '../../styles/storageStats';
+
+class StorageStatsCard extends React.PureComponent {
+ state = {
+ totalBytes: 0,
+ totalAudioBytes: 0,
+ totalAudioPercent: 0,
+ totalImageBytes: 0,
+ totalImagePercent: 0,
+ totalVideoBytes: 0,
+ totalVideoPercent: 0,
+ totalOtherBytes: 0,
+ totalOtherPercent: 0,
+ showStats: false
+ };
+
+ componentDidMount() {
+ // calculate total bytes
+ const { fileInfos } = this.props;
+
+ let totalBytes = 0, totalAudioBytes = 0, totalImageBytes = 0, totalVideoBytes = 0;
+ let totalAudioPercent = 0, totalImagePercent = 0, totalVideoPercent = 0;
+
+ fileInfos.forEach(fileInfo => {
+ if (fileInfo.completed) {
+ const bytes = fileInfo.written_bytes;
+ const type = fileInfo.mime_type;
+ totalBytes += bytes;
+ if (type) {
+ if (type.startsWith('audio/')) totalAudioBytes += bytes;
+ if (type.startsWith('image/')) totalImageBytes += bytes;
+ if (type.startsWith('video/')) totalVideoBytes += bytes;
+ }
+ }
+ });
+
+ totalAudioPercent = ((totalAudioBytes / totalBytes) * 100).toFixed(2);
+ totalImagePercent = ((totalImageBytes / totalBytes) * 100).toFixed(2);
+ totalVideoPercent = ((totalVideoBytes / totalBytes) * 100).toFixed(2);
+
+ this.setState({
+ totalBytes,
+ totalAudioBytes,
+ totalAudioPercent,
+ totalImageBytes,
+ totalImagePercent,
+ totalVideoBytes,
+ totalVideoPercent,
+ totalOtherBytes: totalBytes - (totalAudioBytes + totalImageBytes + totalVideoBytes),
+ totalOtherPercent: (100 - (parseFloat(totalAudioPercent) +
+ parseFloat(totalImagePercent) +
+ parseFloat(totalVideoPercent))).toFixed(2)
+ });
+ }
+
+ render() {
+ return (
+
+
+
+ {formatBytes(this.state.totalBytes, 2)}
+ used
+
+
+ Stats
+ this.setState({ showStats: value })} />
+
+
+ {this.state.showStats &&
+
+
+
+
+
+
+
+
+ {this.state.totalAudioBytes > 0 &&
+
+
+ Audio
+ {formatBytes(this.state.totalAudioBytes, 2)}
+
+ }
+ {this.state.totalImageBytes > 0 &&
+
+
+ Images
+ {formatBytes(this.state.totalImageBytes, 2)}
+
+ }
+ {this.state.totalVideoBytes > 0 &&
+
+
+ Videos
+ {formatBytes(this.state.totalVideoBytes, 2)}
+
+ }
+ {this.state.totalOtherBytes > 0 &&
+
+
+ Other
+ {formatBytes(this.state.totalOtherBytes, 2)}
+
+ }
+
+ }
+
+ )
+ }
+}
+
+export default StorageStatsCard;
diff --git a/app/src/page/downloads/view.js b/app/src/page/downloads/view.js
index f8a924de..96a7b66f 100644
--- a/app/src/page/downloads/view.js
+++ b/app/src/page/downloads/view.js
@@ -14,6 +14,7 @@ import Colors from '../../styles/colors';
import PageHeader from '../../component/pageHeader';
import FileListItem from '../../component/fileListItem';
import FloatingWalletBalance from '../../component/floatingWalletBalance';
+import StorageStatsCard from '../../component/storageStatsCard';
import UriBar from '../../component/uriBar';
import downloadsStyle from '../../styles/downloads';
import fileListStyle from '../../styles/fileList';
@@ -41,29 +42,38 @@ class DownloadsPage extends React.PureComponent {
return (
- {!fetching && !hasDownloads && You have not downloaded anything from LBRY yet.}
- {fetching && !hasDownloads && }
+ {!fetching && !hasDownloads &&
+
+ You have not downloaded anything from LBRY yet.
+ }
+ {fetching && !hasDownloads &&
+
+
+ }
{hasDownloads &&
- (
- navigateToUri(navigation, this.uriFromFileInfo(item), { autoplay: true })} />
- )
- }
- data={fileInfos.sort((a, b) => {
- // TODO: Implement sort based on user selection
- if (!a.completed && b.completed) return -1;
- if (a.completed && !b.completed) return 1;
- if (a.metadata.title === b.metadata.title) return 0;
- return (a.metadata.title < b.metadata.title) ? -1 : 1;
- })}
- keyExtractor={(item, index) => item.outpoint}
- />}
+
+
+ (
+ navigateToUri(navigation, this.uriFromFileInfo(item), { autoplay: true })} />
+ )
+ }
+ data={fileInfos.sort((a, b) => {
+ // TODO: Implement sort based on user selection
+ if (!a.completed && b.completed) return -1;
+ if (a.completed && !b.completed) return 1;
+ if (a.metadata.title === b.metadata.title) return 0;
+ return (a.metadata.title < b.metadata.title) ? -1 : 1;
+ })}
+ keyExtractor={(item, index) => item.outpoint}
+ />
+ }
diff --git a/app/src/page/splash/view.js b/app/src/page/splash/view.js
index 0359eddc..dcd619b0 100644
--- a/app/src/page/splash/view.js
+++ b/app/src/page/splash/view.js
@@ -121,7 +121,7 @@ class SplashScreen extends React.PureComponent {
const { deleteCompleteBlobs } = this.props;
const startupStatus = status.startup_status;
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
- const hasStarted = startupStatus.wallet && status.wallet.blocks_behind <= 0;
+ const hasStarted = startupStatus.file_manager && startupStatus.wallet && status.wallet.blocks_behind <= 0;
if (hasStarted) {
deleteCompleteBlobs();
diff --git a/app/src/styles/colors.js b/app/src/styles/colors.js
index 9b1c0b5f..71b4c4eb 100644
--- a/app/src/styles/colors.js
+++ b/app/src/styles/colors.js
@@ -10,7 +10,11 @@ const Colors = {
Orange: '#ffbb00',
Red: '#ff0000',
VeryLightGrey: '#f1f1f1',
- White: '#ffffff'
+ White: '#ffffff',
+
+ StatsAudio: '#f6a637',
+ StatsImage: '#ff4a7d',
+ StatsOther: '#26bcf7'
};
export default Colors;
diff --git a/app/src/styles/downloads.js b/app/src/styles/downloads.js
index ec04a9d1..5c6bc4fc 100644
--- a/app/src/styles/downloads.js
+++ b/app/src/styles/downloads.js
@@ -2,22 +2,29 @@ import { StyleSheet } from 'react-native';
const downloadsStyle = StyleSheet.create({
container: {
+ flex: 1
+ },
+ busyContainer: {
flex: 1,
justifyContent: 'center',
- alignItems: 'center'
+ alignItems: 'center',
+ flexDirection: 'row'
+ },
+ subContainer: {
+ flex: 1
},
itemList: {
flex: 1,
},
scrollContainer: {
flex: 1,
- width: '100%',
- height: '100%',
paddingLeft: 16,
paddingRight: 16,
+ marginTop: 16,
marginBottom: 60
},
scrollPadding: {
+ marginTop: -16,
paddingBottom: 16
},
noDownloadsText: {
diff --git a/app/src/styles/storageStats.js b/app/src/styles/storageStats.js
new file mode 100644
index 00000000..c6d078f9
--- /dev/null
+++ b/app/src/styles/storageStats.js
@@ -0,0 +1,84 @@
+import { StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const storageStatsStyle = StyleSheet.create({
+ container: {
+ flex: 1
+ },
+ row: {
+ flexDirection: 'row'
+ },
+ card: {
+ backgroundColor: Colors.White,
+ marginTop: 16,
+ marginLeft: 16,
+ marginRight: 16,
+ padding: 16
+ },
+ totalSize: {
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 36
+ },
+ annotation: {
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 14,
+ marginTop: -4
+ },
+ statsText: {
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 14
+ },
+ distributionBar: {
+ flexDirection: 'row',
+ width: '100%',
+ height: 8,
+ marginTop: 16,
+ marginBottom: 16
+ },
+ audioDistribution: {
+ backgroundColor: Colors.StatsAudio
+ },
+ imageDistribution: {
+ backgroundColor: Colors.StatsImage
+ },
+ videoDistribution: {
+ backgroundColor: Colors.LbryGreen
+ },
+ otherDistribution: {
+ backgroundColor: Colors.StatsOther
+ },
+ legendItem: {
+ alignItems: 'center',
+ marginBottom: 8,
+ justifyContent: 'space-between'
+ },
+ legendBox: {
+ width: 16,
+ height: 16,
+ },
+ legendText: {
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 14,
+ flex: 0.3
+ },
+ legendSize: {
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 14,
+ flex: 0.6,
+ textAlign: 'right'
+ },
+ statsToggle: {
+ marginLeft: 8,
+ },
+ summary: {
+ flex: 0.5,
+ alignSelf: 'flex-start'
+ },
+ toggleStatsContainer: {
+ flex: 0.5,
+ alignItems: 'center',
+ justifyContent: 'flex-end'
+ }
+});
+
+export default storageStatsStyle;
diff --git a/app/src/utils/helper.js b/app/src/utils/helper.js
index 1a177894..2807e1a7 100644
--- a/app/src/utils/helper.js
+++ b/app/src/utils/helper.js
@@ -1,40 +1,59 @@
import { NavigationActions, StackActions } from 'react-navigation';
-export function navigateToUri(navigation, uri, additionalParams) {
- if (!navigation) {
- return;
- }
-
- if (uri === navigation.state.key) {
- return;
- }
-
- const params = Object.assign({ uri }, additionalParams);
- if ('File' === navigation.state.routeName) {
+export function dispatchNavigateToUri(dispatch, nav, uri) {
+ const params = { uri };
+ if (nav && nav.routes && nav.routes.length > 0 && 'Main' === nav.routes[0].routeName) {
+ const mainRoute = nav.routes[0];
+ const discoverRoute = mainRoute.routes[0];
+ if (discoverRoute.index > 0 && 'File' === discoverRoute.routes[discoverRoute.index].routeName) {
+ const fileRoute = discoverRoute.routes[discoverRoute.index];
+ // Currently on a file page, so we can ignore (if the URI is the same) or replace (different URIs)
+ if (uri !== fileRoute.params.uri) {
const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
- navigation.dispatch(stackAction);
+ dispatch(stackAction);
return;
+ }
}
+ }
- navigation.navigate({ routeName: 'File', key: uri, params });
+ const navigateAction = NavigationActions.navigate({ routeName: 'File', key: uri, params });
+ dispatch(navigateAction);
}
-export function dispatchNavigateToUri(dispatch, nav, uri) {
- const params = { uri };
- if (nav && nav.routes && nav.routes.length > 0 && 'Main' === nav.routes[0].routeName) {
- const mainRoute = nav.routes[0];
- const discoverRoute = mainRoute.routes[0];
- if (discoverRoute.index > 0 && 'File' === discoverRoute.routes[discoverRoute.index].routeName) {
- const fileRoute = discoverRoute.routes[discoverRoute.index];
- // Currently on a file page, so we can ignore (if the URI is the same) or replace (different URIs)
- if (uri !== fileRoute.params.uri) {
- const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
- dispatch(stackAction);
- return;
- }
- }
- }
+export function formatBytes(bytes, decimalPoints = 0) {
+ if (!bytes) {
+ return '0 KB';
+ }
- const navigateAction = NavigationActions.navigate({ routeName: 'File', key: uri, params });
- dispatch(navigateAction);
-}
\ No newline at end of file
+ if (bytes < 1048576) { // < 1MB
+ const value = (bytes / 1024.0).toFixed(decimalPoints);
+ return `${value} KB`;
+ }
+
+ if (bytes < 1073741824) { // < 1GB
+ const value = (bytes / (1024.0 * 1024.0)).toFixed(decimalPoints);
+ return `${value} MB`;
+ }
+
+ const value = (bytes / (1024.0 * 1024.0 * 1024.0)).toFixed(decimalPoints);
+ return `${value} GB`;
+}
+
+export function navigateToUri(navigation, uri, additionalParams) {
+ if (!navigation) {
+ return;
+ }
+
+ if (uri === navigation.state.key) {
+ return;
+ }
+
+ const params = Object.assign({ uri }, additionalParams);
+ if ('File' === navigation.state.routeName) {
+ const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
+ navigation.dispatch(stackAction);
+ return;
+ }
+
+ navigation.navigate({ routeName: 'File', key: uri, params });
+}
diff --git a/src/main/java/io/lbry/browser/LbrynetService.java b/src/main/java/io/lbry/browser/LbrynetService.java
index eb7fec8c..8384dc11 100644
--- a/src/main/java/io/lbry/browser/LbrynetService.java
+++ b/src/main/java/io/lbry/browser/LbrynetService.java
@@ -37,7 +37,7 @@ import org.renpy.android.ResourceManager;
*/
public class LbrynetService extends PythonService {
- private static final int SERVICE_NOTIFICATION_GROUP_ID = -1;
+ public static final int SERVICE_NOTIFICATION_GROUP_ID = 5;
public static final String ACTION_STOP_SERVICE = "io.lbry.browser.ACTION_STOP_SERVICE";
diff --git a/src/main/java/io/lbry/browser/MainActivity.java b/src/main/java/io/lbry/browser/MainActivity.java
index fa84e2b8..667c34e6 100644
--- a/src/main/java/io/lbry/browser/MainActivity.java
+++ b/src/main/java/io/lbry/browser/MainActivity.java
@@ -83,6 +83,8 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
*/
private boolean serviceRunning;
+ private boolean receivedStopService;
+
protected String getMainComponentName() {
return "LBRYApp";
}
@@ -138,6 +140,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
stopServiceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ MainActivity.this.receivedStopService = true;
MainActivity.this.finish();
}
};
@@ -383,6 +386,9 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
notificationManager.cancel(downloadNotificationIds.get(i));
}
}
+ if (receivedStopService || !isServiceRunning(LbrynetService.class)) {
+ notificationManager.cancelAll();
+ }
super.onDestroy();
if (mReactInstanceManager != null) {