diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js
index 7f6c350e..ba69c482 100644
--- a/app/src/component/AppNavigator.js
+++ b/app/src/component/AppNavigator.js
@@ -1,6 +1,7 @@
import React from 'react';
import AboutPage from '../page/about';
import DiscoverPage from '../page/discover';
+import DownloadsPage from '../page/downloads';
import FilePage from '../page/file';
import FirstRunScreen from '../page/firstRun';
import RewardsPage from '../page/rewards';
@@ -40,8 +41,10 @@ import {
} from 'lbryinc';
import { makeSelectClientSetting } from '../redux/selectors/settings';
import { decode as atob } from 'base-64';
-import NavigationButton from '../component/navigationButton';
+import Colors from '../styles/colors';
import Constants from '../constants';
+import Icon from 'react-native-vector-icons/FontAwesome5';
+import NavigationButton from '../component/navigationButton';
import discoverStyle from '../styles/discover';
import searchStyle from '../styles/search';
import SearchRightHeaderIcon from '../component/searchRightHeaderIcon';
@@ -88,6 +91,26 @@ const trendingStack = StackNavigator({
}
});
+const myLbryStack = StackNavigator({
+ Downloads: {
+ screen: DownloadsPage,
+ navigationOptions: ({ navigation }) => ({
+ title: 'My LBRY',
+ headerLeft: menuNavigationButton(navigation),
+ })
+ }
+});
+
+const rewardsStack = StackNavigator({
+ Rewards: {
+ screen: RewardsPage,
+ navigationOptions: ({ navigation }) => ({
+ title: 'Rewards',
+ headerLeft: menuNavigationButton(navigation),
+ })
+ }
+});
+
const walletStack = StackNavigator({
Wallet: {
screen: WalletPage,
@@ -107,26 +130,36 @@ const walletStack = StackNavigator({
headerMode: 'screen'
});
-const rewardsStack = StackNavigator({
- Rewards: {
- screen: RewardsPage,
- navigationOptions: ({ navigation }) => ({
- title: 'Rewards',
- headerLeft: menuNavigationButton(navigation),
- })
- }
-});
-
const drawer = DrawerNavigator({
- DiscoverStack: { screen: discoverStack },
- TrendingStack: { screen: trendingStack },
- WalletStack: { screen: walletStack },
- Rewards: { screen: rewardsStack },
- Settings: { screen: SettingsPage, navigationOptions: { drawerLockMode: 'locked-closed' } },
- About: { screen: AboutPage, navigationOptions: { drawerLockMode: 'locked-closed' } }
+ DiscoverStack: { screen: discoverStack, navigationOptions: {
+ drawerIcon: ({ tintColor }) =>
+ }},
+ TrendingStack: { screen: trendingStack, navigationOptions: {
+ drawerIcon: ({ tintColor }) =>
+ }},
+ MyLBRYStack: { screen: myLbryStack, navigationOptions: {
+ drawerIcon: ({ tintColor }) =>
+ }},
+ Rewards: { screen: rewardsStack, navigationOptions: {
+ drawerIcon: ({ tintColor }) =>
+ }},
+ WalletStack: { screen: walletStack, navigationOptions: {
+ drawerIcon: ({ tintColor }) =>
+ }},
+ Settings: { screen: SettingsPage, navigationOptions: {
+ drawerLockMode: 'locked-closed',
+ drawerIcon: ({ tintColor }) =>
+ }},
+ About: { screen: AboutPage, navigationOptions: {
+ drawerLockMode: 'locked-closed',
+ drawerIcon: ({ tintColor }) =>
+ }}
}, {
drawerWidth: 300,
- headerMode: 'none'
+ headerMode: 'none',
+ contentOptions: {
+ activeTintColor: Colors.LbryGreen
+ }
});
export const AppNavigator = new StackNavigator({
diff --git a/app/src/component/searchResultItem/index.js b/app/src/component/fileListItem/index.js
similarity index 81%
rename from app/src/component/searchResultItem/index.js
rename to app/src/component/fileListItem/index.js
index e4b7d15d..f88b9118 100644
--- a/app/src/component/searchResultItem/index.js
+++ b/app/src/component/fileListItem/index.js
@@ -7,10 +7,11 @@ import {
makeSelectIsUriResolving,
} from 'lbry-redux';
import { selectShowNsfw } from '../../redux/selectors/settings';
-import SearchResultItem from './view';
+import FileListItem from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
+ fileInfo: makeSelectFileInfoForUri(props.uri)(state),
isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
@@ -21,4 +22,4 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri))
});
-export default connect(select, perform)(SearchResultItem);
+export default connect(select, perform)(FileListItem);
diff --git a/app/src/component/fileListItem/view.js b/app/src/component/fileListItem/view.js
new file mode 100644
index 00000000..2d3c4ac4
--- /dev/null
+++ b/app/src/component/fileListItem/view.js
@@ -0,0 +1,113 @@
+import React from 'react';
+import { normalizeURI, parseURI } from 'lbry-redux';
+import {
+ ActivityIndicator,
+ Platform,
+ ProgressBarAndroid,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import Colors from '../../styles/colors';
+import FileItemMedia from '../fileItemMedia';
+import Link from '../../component/link';
+import NsfwOverlay from '../../component/nsfwOverlay';
+import fileListStyle from '../../styles/fileList';
+
+class FileListItem extends React.PureComponent {
+ getStorageForFileInfo = (fileInfo) => {
+ if (!fileInfo.completed) {
+ const written = this.formatBytes(fileInfo.written_bytes);
+ const total = this.formatBytes(fileInfo.total_bytes);
+ return `(${written} / ${total})`;
+ }
+
+ return this.formatBytes(fileInfo.written_bytes);
+ }
+
+ formatBytes = (bytes) => {
+ if (bytes < 1048576) { // < 1MB
+ const value = (bytes / 1024.0).toFixed(2);
+ return `${value} KB`;
+ }
+
+ if (bytes < 1073741824) { // < 1GB
+ const value = (bytes / (1024.0 * 1024.0)).toFixed(2);
+ return `${value} MB`;
+ }
+
+ const value = (bytes / (1024.0 * 1024.0 * 1024.0)).toFixed(2);
+ return `${value} GB`;
+ }
+
+ getDownloadProgress = (fileInfo) => {
+ return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
+ }
+
+ render() {
+ const {
+ claim,
+ fileInfo,
+ metadata,
+ isResolvingUri,
+ isDownloaded,
+ style,
+ onPress,
+ navigation
+ } = this.props;
+
+ const uri = normalizeURI(this.props.uri);
+ const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
+ const isResolving = !fileInfo && isResolvingUri;
+ const title = fileInfo ? fileInfo.metadata.title : metadata && metadata.title ? metadata.title : parseURI(uri).contentName;
+
+ let name;
+ let channel;
+ if (claim) {
+ name = claim.name;
+ channel = claim.channel_name;
+ }
+
+ return (
+
+
+
+
+ {isResolving && (
+
+ {uri}
+
+
+
+ )}
+
+ {!isResolving && {title || name}}
+ {!isResolving && channel &&
+ {
+ const channelUri = normalizeURI(channel);
+ navigation.navigate({ routeName: 'File', key: channelUri, params: { uri: channelUri }});
+ }} />}
+
+ {fileInfo &&
+
+ {this.getStorageForFileInfo(fileInfo)}
+ {!fileInfo.completed &&
+
+
+
+ }
+
+ }
+
+
+ {obscureNsfw && navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
+
+ );
+ }
+}
+
+export default FileListItem;
diff --git a/app/src/component/searchResultItem/view.js b/app/src/component/searchResultItem/view.js
deleted file mode 100644
index 414e5286..00000000
--- a/app/src/component/searchResultItem/view.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React from 'react';
-import { normalizeURI, parseURI } from 'lbry-redux';
-import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
-import Colors from '../../styles/colors';
-import FileItemMedia from '../fileItemMedia';
-import Link from '../../component/link';
-import NsfwOverlay from '../../component/nsfwOverlay';
-import searchStyle from '../../styles/search';
-
-class SearchResultItem extends React.PureComponent {
- render() {
- const {
- claim,
- metadata,
- isResolvingUri,
- showUri,
- isDownloaded,
- style,
- onPress,
- navigation
- } = this.props;
-
- const uri = normalizeURI(this.props.uri);
- const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
- const title = metadata && metadata.title ? metadata.title : parseURI(uri).contentName;
-
- let name;
- let channel;
- if (claim) {
- name = claim.name;
- channel = claim.channel_name;
- }
-
- return (
-
-
-
-
- {isResolvingUri && (
-
- {uri}
-
-
-
- )}
- {!isResolvingUri && {title || name}}
- {!isResolvingUri && channel &&
- {
- const channelUri = normalizeURI(channel);
- navigation.navigate({ routeName: 'File', key: channelUri, params: { uri: channelUri }});
- }} />}
-
-
- {obscureNsfw && navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
-
- );
- }
-}
-
-export default SearchResultItem;
diff --git a/app/src/page/downloads/index.js b/app/src/page/downloads/index.js
new file mode 100644
index 00000000..11ffe215
--- /dev/null
+++ b/app/src/page/downloads/index.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux';
+import {
+ doFileList,
+ selectFileInfosDownloaded,
+ selectMyClaimsWithoutChannels,
+ selectIsFetchingFileList,
+} from 'lbry-redux';
+import DownloadsPage from './view';
+
+const select = (state) => ({
+ fileInfos: selectFileInfosDownloaded(state),
+ fetching: selectIsFetchingFileList(state),
+ claims: selectMyClaimsWithoutChannels(state),
+});
+
+const perform = dispatch => ({
+ fileList: () => dispatch(doFileList()),
+});
+
+export default connect(select, perform)(DownloadsPage);
diff --git a/app/src/page/downloads/view.js b/app/src/page/downloads/view.js
new file mode 100644
index 00000000..eaede864
--- /dev/null
+++ b/app/src/page/downloads/view.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { Lbry, buildURI } from 'lbry-redux';
+import {
+ ActivityIndicator,
+ Button,
+ FlatList,
+ Text,
+ TextInput,
+ View,
+ ScrollView
+} from 'react-native';
+import Colors from '../../styles/colors';
+import PageHeader from '../../component/pageHeader';
+import FileListItem from '../../component/fileListItem';
+import FloatingWalletBalance from '../../component/floatingWalletBalance';
+import UriBar from '../../component/uriBar';
+import downloadsStyle from '../../styles/downloads';
+import fileListStyle from '../../styles/fileList';
+
+class DownloadsPage extends React.PureComponent {
+ static navigationOptions = {
+ title: 'My LBRY'
+ };
+
+ componentDidMount() {
+ this.props.fileList();
+ }
+
+ uriFromFileInfo(fileInfo) {
+ const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = fileInfo;
+ const uriParams = {};
+ uriParams.contentName = claimName || claimNameDownloaded;
+ uriParams.claimId = claimId;
+ return buildURI(uriParams);
+ }
+
+ render() {
+ const { fetching, fileInfos, navigation } = this.props;
+ const hasDownloads = fileInfos && Object.values(fileInfos).length > 0;
+
+ return (
+
+ {!fetching && !hasDownloads && You have not downloaded anything from LBRY yet.}
+ {fetching && !hasDownloads && }
+ {hasDownloads &&
+ (
+ navigation.navigate({
+ routeName: 'File',
+ key: 'filePage',
+ params: { uri: 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.metadata.title === b.metadata.title) return 0;
+ return (a.metadata.title < b.metadata.title) ? -1 : 1;
+ })}
+ keyExtractor={(item, index) => item.outpoint}
+ />}
+
+
+
+ );
+ }
+}
+
+export default DownloadsPage;
diff --git a/app/src/page/file/view.js b/app/src/page/file/view.js
index d7bfb79c..8e96b5d5 100644
--- a/app/src/page/file/view.js
+++ b/app/src/page/file/view.js
@@ -64,6 +64,7 @@ class FilePage extends React.PureComponent {
const { isResolvingUri, resolveUri, navigation } = this.props;
const { uri } = navigation.state.params;
+
if (!isResolvingUri) resolveUri(uri);
this.fetchFileInfo(this.props);
@@ -295,7 +296,7 @@ class FilePage extends React.PureComponent {
blackListedOutpoints,
navigation
} = this.props;
- const { uri } = navigation.state.params;
+ const { uri, autoplay } = navigation.state.params;
let innerContent = null;
if ((isResolvingUri && !claim) || !claim) {
@@ -402,7 +403,7 @@ class FilePage extends React.PureComponent {
ref={(ref) => { this.player = ref; }}
uri={uri}
style={playerStyle}
- autoPlay={this.state.autoPlayMedia}
+ autoPlay={autoplay || this.state.autoPlayMedia}
onFullscreenToggled={this.handleFullscreenToggle}
onLayout={(evt) => {
if (!this.state.playerHeight) {
diff --git a/app/src/page/search/view.js b/app/src/page/search/view.js
index 8864db02..5a788cca 100644
--- a/app/src/page/search/view.js
+++ b/app/src/page/search/view.js
@@ -10,7 +10,7 @@ import {
} from 'react-native';
import Colors from '../../styles/colors';
import PageHeader from '../../component/pageHeader';
-import SearchResultItem from '../../component/searchResultItem';
+import FileListItem from '../../component/fileListItem';
import FloatingWalletBalance from '../../component/floatingWalletBalance';
import UriBar from '../../component/uriBar';
import searchStyle from '../../styles/search';
@@ -38,15 +38,15 @@ class SearchPage extends React.PureComponent {
No results to display.}
{!isSearching && uris && uris.length ? (
- uris.map(uri => navigation.navigate({
- routeName: 'File',
- key: 'filePage',
- params: { uri }})
- }/>)
+ uris.map(uri => navigation.navigate({
+ routeName: 'File',
+ key: 'filePage',
+ params: { uri }})
+ }/>)
) : null }
{isSearching && }
diff --git a/app/src/styles/downloads.js b/app/src/styles/downloads.js
new file mode 100644
index 00000000..ec04a9d1
--- /dev/null
+++ b/app/src/styles/downloads.js
@@ -0,0 +1,34 @@
+import { StyleSheet } from 'react-native';
+
+const downloadsStyle = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+ itemList: {
+ flex: 1,
+ },
+ scrollContainer: {
+ flex: 1,
+ width: '100%',
+ height: '100%',
+ paddingLeft: 16,
+ paddingRight: 16,
+ marginBottom: 60
+ },
+ scrollPadding: {
+ paddingBottom: 16
+ },
+ noDownloadsText: {
+ textAlign: 'center',
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 14,
+ position: 'absolute'
+ },
+ loading: {
+ position: 'absolute'
+ }
+});
+
+export default downloadsStyle;
diff --git a/app/src/styles/fileList.js b/app/src/styles/fileList.js
new file mode 100644
index 00000000..dfaec77a
--- /dev/null
+++ b/app/src/styles/fileList.js
@@ -0,0 +1,67 @@
+import { Dimensions, StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const screenDimension = Dimensions.get('window');
+const screenWidth = screenDimension.width;
+const screenHeight = screenDimension.height;
+const thumbnailHeight = 100;
+const thumbnailWidth = (screenHeight / screenWidth) * thumbnailHeight;
+
+const fileListStyle = StyleSheet.create({
+ item: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginTop: 16
+ },
+ detailsContainer: {
+ flex: 1
+ },
+ thumbnail: {
+ width: thumbnailWidth,
+ height: thumbnailHeight,
+ marginRight: 16,
+ justifyContent: 'center'
+ },
+ title: {
+ fontFamily: 'Metropolis-SemiBold',
+ fontSize: 16
+ },
+ uri: {
+ fontFamily: 'Metropolis-SemiBold',
+ fontSize: 14,
+ marginBottom: 8
+ },
+ publisher: {
+ fontFamily: 'Metropolis-SemiBold',
+ fontSize: 14,
+ marginTop: 3,
+ color: Colors.LbryGreen
+ },
+ loading: {
+ position: 'absolute'
+ },
+ downloadInfo: {
+ marginTop: 8
+ },
+ downloadStorage: {
+ fontFamily: 'Metropolis-Regular',
+ fontSize: 14,
+ color: Colors.ChannelGrey
+ },
+ progress: {
+ marginTop: 4,
+ height: 3,
+ flex: 1,
+ flexDirection: 'row'
+ },
+ progressCompleted: {
+ backgroundColor: Colors.LbryGreen
+ },
+ progressRemaining: {
+ backgroundColor: Colors.LbryGreen,
+ opacity: 0.2
+ }
+});
+
+export default fileListStyle;
diff --git a/app/src/styles/search.js b/app/src/styles/search.js
index f7357d3d..7d6c9711 100644
--- a/app/src/styles/search.js
+++ b/app/src/styles/search.js
@@ -1,11 +1,4 @@
-import { Dimensions, StyleSheet } from 'react-native';
-import Colors from './colors';
-
-const screenDimension = Dimensions.get('window');
-const screenWidth = screenDimension.width;
-const screenHeight = screenDimension.height;
-const thumbnailHeight = 100;
-const thumbnailWidth = (screenHeight / screenWidth) * thumbnailHeight;
+import { StyleSheet } from 'react-native';
const searchStyle = StyleSheet.create({
container: {
@@ -30,36 +23,12 @@ const searchStyle = StyleSheet.create({
justifyContent: 'space-between',
marginTop: 16
},
- thumbnail: {
- width: thumbnailWidth,
- height: thumbnailHeight,
- marginRight: 16,
- justifyContent: 'center'
- },
- detailsContainer: {
- flex: 1
- },
searchInput: {
width: '100%',
height: '100%',
fontFamily: 'Metropolis-Regular',
fontSize: 16
},
- title: {
- fontFamily: 'Metropolis-SemiBold',
- fontSize: 16
- },
- uri: {
- fontFamily: 'Metropolis-SemiBold',
- fontSize: 14,
- marginBottom: 8
- },
- publisher: {
- fontFamily: 'Metropolis-SemiBold',
- fontSize: 14,
- marginTop: 3,
- color: Colors.LbryGreen
- },
noResultsText: {
textAlign: 'center',
fontFamily: 'Metropolis-Regular',
diff --git a/src/main/java/io/lbry/browser/reactmodules/DownloadManagerModule.java b/src/main/java/io/lbry/browser/reactmodules/DownloadManagerModule.java
index c493ec13..7795115b 100644
--- a/src/main/java/io/lbry/browser/reactmodules/DownloadManagerModule.java
+++ b/src/main/java/io/lbry/browser/reactmodules/DownloadManagerModule.java
@@ -157,7 +157,8 @@ public class DownloadManagerModule extends ReactContextBaseJavaModule {
builder.setContentIntent(getLaunchPendingIntent(id))
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
.setGroup(GROUP_DOWNLOADS)
- .setProgress(MAX_PROGRESS, new Double(progress).intValue(), false);
+ .setProgress(MAX_PROGRESS, new Double(progress).intValue(), false)
+ .setSmallIcon(android.R.drawable.stat_sys_download);
notificationManager.notify(notificationId, builder.build());
if (progress == MAX_PROGRESS) {