implement my LBRY downloads page #273
15 changed files with 395 additions and 133 deletions
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AboutPage from '../page/about';
|
import AboutPage from '../page/about';
|
||||||
import DiscoverPage from '../page/discover';
|
import DiscoverPage from '../page/discover';
|
||||||
|
import DownloadsPage from '../page/downloads';
|
||||||
import FilePage from '../page/file';
|
import FilePage from '../page/file';
|
||||||
import FirstRunScreen from '../page/firstRun';
|
import FirstRunScreen from '../page/firstRun';
|
||||||
import RewardsPage from '../page/rewards';
|
import RewardsPage from '../page/rewards';
|
||||||
|
@ -40,8 +41,10 @@ import {
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
||||||
import { decode as atob } from 'base-64';
|
import { decode as atob } from 'base-64';
|
||||||
import NavigationButton from '../component/navigationButton';
|
import Colors from '../styles/colors';
|
||||||
import Constants from '../constants';
|
import Constants from '../constants';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import NavigationButton from '../component/navigationButton';
|
||||||
import discoverStyle from '../styles/discover';
|
import discoverStyle from '../styles/discover';
|
||||||
import searchStyle from '../styles/search';
|
import searchStyle from '../styles/search';
|
||||||
import SearchRightHeaderIcon from '../component/searchRightHeaderIcon';
|
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({
|
const walletStack = StackNavigator({
|
||||||
Wallet: {
|
Wallet: {
|
||||||
screen: WalletPage,
|
screen: WalletPage,
|
||||||
|
@ -107,26 +130,36 @@ const walletStack = StackNavigator({
|
||||||
headerMode: 'screen'
|
headerMode: 'screen'
|
||||||
});
|
});
|
||||||
|
|
||||||
const rewardsStack = StackNavigator({
|
|
||||||
Rewards: {
|
|
||||||
screen: RewardsPage,
|
|
||||||
navigationOptions: ({ navigation }) => ({
|
|
||||||
title: 'Rewards',
|
|
||||||
headerLeft: menuNavigationButton(navigation),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const drawer = DrawerNavigator({
|
const drawer = DrawerNavigator({
|
||||||
DiscoverStack: { screen: discoverStack },
|
DiscoverStack: { screen: discoverStack, navigationOptions: {
|
||||||
TrendingStack: { screen: trendingStack },
|
drawerIcon: ({ tintColor }) => <Icon name="compass" size={20} style={{ color: tintColor }} />
|
||||||
WalletStack: { screen: walletStack },
|
}},
|
||||||
Rewards: { screen: rewardsStack },
|
TrendingStack: { screen: trendingStack, navigationOptions: {
|
||||||
Settings: { screen: SettingsPage, navigationOptions: { drawerLockMode: 'locked-closed' } },
|
drawerIcon: ({ tintColor }) => <Icon name="fire" size={20} style={{ color: tintColor }} />
|
||||||
About: { screen: AboutPage, navigationOptions: { drawerLockMode: 'locked-closed' } }
|
}},
|
||||||
|
MyLBRYStack: { screen: myLbryStack, navigationOptions: {
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="folder" size={20} style={{ color: tintColor }} />
|
||||||
|
}},
|
||||||
|
Rewards: { screen: rewardsStack, navigationOptions: {
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="trophy" size={20} style={{ color: tintColor }} />
|
||||||
|
}},
|
||||||
|
WalletStack: { screen: walletStack, navigationOptions: {
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />
|
||||||
|
}},
|
||||||
|
Settings: { screen: SettingsPage, navigationOptions: {
|
||||||
|
drawerLockMode: 'locked-closed',
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="cog" size={20} style={{ color: tintColor }} />
|
||||||
|
}},
|
||||||
|
About: { screen: AboutPage, navigationOptions: {
|
||||||
|
drawerLockMode: 'locked-closed',
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="info" size={20} style={{ color: tintColor }} />
|
||||||
|
}}
|
||||||
}, {
|
}, {
|
||||||
drawerWidth: 300,
|
drawerWidth: 300,
|
||||||
headerMode: 'none'
|
headerMode: 'none',
|
||||||
|
contentOptions: {
|
||||||
|
activeTintColor: Colors.LbryGreen
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AppNavigator = new StackNavigator({
|
export const AppNavigator = new StackNavigator({
|
||||||
|
|
|
@ -7,10 +7,11 @@ import {
|
||||||
makeSelectIsUriResolving,
|
makeSelectIsUriResolving,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectShowNsfw } from '../../redux/selectors/settings';
|
import { selectShowNsfw } from '../../redux/selectors/settings';
|
||||||
import SearchResultItem from './view';
|
import FileListItem from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state),
|
isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state),
|
||||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||||
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||||
|
@ -21,4 +22,4 @@ const perform = dispatch => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri))
|
resolveUri: uri => dispatch(doResolveUri(uri))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(SearchResultItem);
|
export default connect(select, perform)(FileListItem);
|
113
app/src/component/fileListItem/view.js
Normal file
113
app/src/component/fileListItem/view.js
Normal file
|
@ -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 (
|
||||||
|
<View style={style}>
|
||||||
|
<TouchableOpacity style={style} onPress={onPress}>
|
||||||
|
<FileItemMedia style={fileListStyle.thumbnail}
|
||||||
|
blurRadius={obscureNsfw ? 15 : 0}
|
||||||
|
resizeMode="cover"
|
||||||
|
title={title}
|
||||||
|
thumbnail={metadata ? metadata.thumbnail : null} />
|
||||||
|
<View style={fileListStyle.detailsContainer}>
|
||||||
|
{isResolving && (
|
||||||
|
<View>
|
||||||
|
<Text style={fileListStyle.uri}>{uri}</Text>
|
||||||
|
<View style={fileListStyle.row}>
|
||||||
|
<ActivityIndicator size={"small"} color={Colors.LbryGreen} />
|
||||||
|
</View>
|
||||||
|
</View>)}
|
||||||
|
|
||||||
|
{!isResolving && <Text style={fileListStyle.title}>{title || name}</Text>}
|
||||||
|
{!isResolving && channel &&
|
||||||
|
<Link style={fileListStyle.publisher} text={channel} onPress={() => {
|
||||||
|
const channelUri = normalizeURI(channel);
|
||||||
|
navigation.navigate({ routeName: 'File', key: channelUri, params: { uri: channelUri }});
|
||||||
|
}} />}
|
||||||
|
|
||||||
|
{fileInfo &&
|
||||||
|
<View style={fileListStyle.downloadInfo}>
|
||||||
|
<Text style={fileListStyle.downloadStorage}>{this.getStorageForFileInfo(fileInfo)}</Text>
|
||||||
|
{!fileInfo.completed &&
|
||||||
|
<View style={fileListStyle.progress}>
|
||||||
|
<View style={[fileListStyle.progressCompleted, { flex: this.getDownloadProgress(fileInfo) } ]} />
|
||||||
|
|||||||
|
<View style={[fileListStyle.progressRemaining, { flex: (100 - this.getDownloadProgress(fileInfo)) } ]} />
|
||||||
|
</View>}
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileListItem;
|
|
@ -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 (
|
|
||||||
<View style={style}>
|
|
||||||
<TouchableOpacity style={style} onPress={onPress}>
|
|
||||||
<FileItemMedia style={searchStyle.thumbnail}
|
|
||||||
blurRadius={obscureNsfw ? 15 : 0}
|
|
||||||
resizeMode="cover"
|
|
||||||
title={title}
|
|
||||||
thumbnail={metadata ? metadata.thumbnail : null} />
|
|
||||||
<View style={searchStyle.detailsContainer}>
|
|
||||||
{isResolvingUri && (
|
|
||||||
<View>
|
|
||||||
<Text style={searchStyle.uri}>{uri}</Text>
|
|
||||||
<View style={searchStyle.row}>
|
|
||||||
<ActivityIndicator size={"small"} color={Colors.LbryGreen} />
|
|
||||||
</View>
|
|
||||||
</View>)}
|
|
||||||
{!isResolvingUri && <Text style={searchStyle.title}>{title || name}</Text>}
|
|
||||||
{!isResolvingUri && channel &&
|
|
||||||
<Link style={searchStyle.publisher} text={channel} onPress={() => {
|
|
||||||
const channelUri = normalizeURI(channel);
|
|
||||||
navigation.navigate({ routeName: 'File', key: channelUri, params: { uri: channelUri }});
|
|
||||||
}} />}
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SearchResultItem;
|
|
|
@ -45,12 +45,20 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleItemPress = (item) => {
|
handleItemPress = (item) => {
|
||||||
const { navigation, updateSearchQuery } = this.props;
|
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
|
||||||
const { type, value } = item;
|
const { type, value } = item;
|
||||||
|
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
|
|
||||||
if (SEARCH_TYPES.SEARCH === type) {
|
if (SEARCH_TYPES.SEARCH === type) {
|
||||||
|
this.setState({ currentValue: value });
|
||||||
|
updateSearchQuery(value);
|
||||||
|
|
||||||
|
if (onSearchSubmitted) {
|
||||||
|
onSearchSubmitted(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value }});
|
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value }});
|
||||||
} else {
|
} else {
|
||||||
const uri = normalizeURI(value);
|
const uri = normalizeURI(value);
|
||||||
|
|
20
app/src/page/downloads/index.js
Normal file
20
app/src/page/downloads/index.js
Normal file
|
@ -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);
|
77
app/src/page/downloads/view.js
Normal file
77
app/src/page/downloads/view.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
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 (
|
||||||
|
<View style={downloadsStyle.container}>
|
||||||
|
{!fetching && !hasDownloads && <Text style={downloadsStyle.noDownloadsText}>You have not downloaded anything from LBRY yet.</Text>}
|
||||||
|
{fetching && !hasDownloads && <ActivityIndicator size="large" color={Colors.LbryGreen} style={downloadsStyle.loading} /> }
|
||||||
|
{hasDownloads &&
|
||||||
|
<FlatList
|
||||||
|
style={downloadsStyle.scrollContainer}
|
||||||
|
contentContainerStyle={downloadsStyle.scrollPadding}
|
||||||
|
renderItem={ ({item}) => (
|
||||||
|
<FileListItem
|
||||||
|
style={fileListStyle.item}
|
||||||
|
uri={this.uriFromFileInfo(item)}
|
||||||
|
navigation={navigation}
|
||||||
|
onPress={() => 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.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}
|
||||||
|
/>}
|
||||||
|
<FloatingWalletBalance navigation={navigation} />
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DownloadsPage;
|
|
@ -64,6 +64,7 @@ class FilePage extends React.PureComponent {
|
||||||
|
|
||||||
const { isResolvingUri, resolveUri, navigation } = this.props;
|
const { isResolvingUri, resolveUri, navigation } = this.props;
|
||||||
const { uri } = navigation.state.params;
|
const { uri } = navigation.state.params;
|
||||||
|
|
||||||
if (!isResolvingUri) resolveUri(uri);
|
if (!isResolvingUri) resolveUri(uri);
|
||||||
|
|
||||||
this.fetchFileInfo(this.props);
|
this.fetchFileInfo(this.props);
|
||||||
|
@ -295,7 +296,7 @@ class FilePage extends React.PureComponent {
|
||||||
blackListedOutpoints,
|
blackListedOutpoints,
|
||||||
navigation
|
navigation
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { uri } = navigation.state.params;
|
const { uri, autoplay } = navigation.state.params;
|
||||||
|
|
||||||
let innerContent = null;
|
let innerContent = null;
|
||||||
if ((isResolvingUri && !claim) || !claim) {
|
if ((isResolvingUri && !claim) || !claim) {
|
||||||
|
@ -402,7 +403,7 @@ class FilePage extends React.PureComponent {
|
||||||
ref={(ref) => { this.player = ref; }}
|
ref={(ref) => { this.player = ref; }}
|
||||||
uri={uri}
|
uri={uri}
|
||||||
style={playerStyle}
|
style={playerStyle}
|
||||||
autoPlay={this.state.autoPlayMedia}
|
autoPlay={autoplay || this.state.autoPlayMedia}
|
||||||
onFullscreenToggled={this.handleFullscreenToggle}
|
onFullscreenToggled={this.handleFullscreenToggle}
|
||||||
onLayout={(evt) => {
|
onLayout={(evt) => {
|
||||||
if (!this.state.playerHeight) {
|
if (!this.state.playerHeight) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import Colors from '../../styles/colors';
|
import Colors from '../../styles/colors';
|
||||||
import PageHeader from '../../component/pageHeader';
|
import PageHeader from '../../component/pageHeader';
|
||||||
import SearchResultItem from '../../component/searchResultItem';
|
import FileListItem from '../../component/fileListItem';
|
||||||
import FloatingWalletBalance from '../../component/floatingWalletBalance';
|
import FloatingWalletBalance from '../../component/floatingWalletBalance';
|
||||||
import UriBar from '../../component/uriBar';
|
import UriBar from '../../component/uriBar';
|
||||||
import searchStyle from '../../styles/search';
|
import searchStyle from '../../styles/search';
|
||||||
|
@ -38,15 +38,15 @@ class SearchPage extends React.PureComponent {
|
||||||
<Text style={searchStyle.noResultsText}>No results to display.</Text>}
|
<Text style={searchStyle.noResultsText}>No results to display.</Text>}
|
||||||
<ScrollView style={searchStyle.scrollContainer} contentContainerStyle={searchStyle.scrollPadding}>
|
<ScrollView style={searchStyle.scrollContainer} contentContainerStyle={searchStyle.scrollPadding}>
|
||||||
{!isSearching && uris && uris.length ? (
|
{!isSearching && uris && uris.length ? (
|
||||||
uris.map(uri => <SearchResultItem key={uri}
|
uris.map(uri => <FileListItem key={uri}
|
||||||
uri={uri}
|
uri={uri}
|
||||||
style={searchStyle.resultItem}
|
style={searchStyle.resultItem}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
onPress={() => navigation.navigate({
|
onPress={() => navigation.navigate({
|
||||||
routeName: 'File',
|
routeName: 'File',
|
||||||
key: 'filePage',
|
key: 'filePage',
|
||||||
params: { uri }})
|
params: { uri }})
|
||||||
}/>)
|
}/>)
|
||||||
) : null }
|
) : null }
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
{isSearching && <ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} /> }
|
{isSearching && <ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} /> }
|
||||||
|
|
|
@ -46,7 +46,7 @@ class SettingsPage extends React.PureComponent {
|
||||||
|
|
||||||
<View style={settingsStyle.row}>
|
<View style={settingsStyle.row}>
|
||||||
<View style={settingsStyle.switchText}>
|
<View style={settingsStyle.switchText}>
|
||||||
<Text style={settingsStyle.label}>Keep the daemon background service running when the app is suspended.</Text>
|
<Text style={settingsStyle.label}>Keep the daemon background service running after closing the app</Text>
|
||||||
<Text style={settingsStyle.description}>Enable this option for quicker app launch and to keep the synchronisation with the blockchain up to date.</Text>
|
<Text style={settingsStyle.description}>Enable this option for quicker app launch and to keep the synchronisation with the blockchain up to date.</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={settingsStyle.switchContainer}>
|
<View style={settingsStyle.switchContainer}>
|
||||||
|
|
34
app/src/styles/downloads.js
Normal file
34
app/src/styles/downloads.js
Normal file
|
@ -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;
|
67
app/src/styles/fileList.js
Normal file
67
app/src/styles/fileList.js
Normal file
|
@ -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;
|
|
@ -1,11 +1,4 @@
|
||||||
import { Dimensions, StyleSheet } from 'react-native';
|
import { 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 searchStyle = StyleSheet.create({
|
const searchStyle = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -30,36 +23,12 @@ const searchStyle = StyleSheet.create({
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
marginTop: 16
|
marginTop: 16
|
||||||
},
|
},
|
||||||
thumbnail: {
|
|
||||||
width: thumbnailWidth,
|
|
||||||
height: thumbnailHeight,
|
|
||||||
marginRight: 16,
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
detailsContainer: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
searchInput: {
|
searchInput: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
fontFamily: 'Metropolis-Regular',
|
fontFamily: 'Metropolis-Regular',
|
||||||
fontSize: 16
|
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: {
|
noResultsText: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontFamily: 'Metropolis-Regular',
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
|
|
@ -25,12 +25,14 @@ const settingsStyle = StyleSheet.create({
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: 'Metropolis-Regular'
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
lineHeight: 18
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
color: '#aaaaaa',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontFamily: 'Metropolis-Regular',
|
fontFamily: 'Metropolis-Regular',
|
||||||
color: '#aaaaaa'
|
lineHeight: 18
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,8 @@ public class DownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
builder.setContentIntent(getLaunchPendingIntent(id))
|
builder.setContentIntent(getLaunchPendingIntent(id))
|
||||||
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
|
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
|
||||||
.setGroup(GROUP_DOWNLOADS)
|
.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());
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
|
||||||
if (progress == MAX_PROGRESS) {
|
if (progress == MAX_PROGRESS) {
|
||||||
|
|
Loading…
Reference in a new issue
I noticed this only works because the dynamic elements are regenerated.
Are you getting warnings from not using
key
attributes?