implement direct URI navigation (#134)
This commit is contained in:
parent
077af85181
commit
4fbf90654e
14 changed files with 287 additions and 83 deletions
|
@ -1,5 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doFetchCostInfoForUri,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectDownloadingForUri,
|
||||
makeSelectLoadingForUri,
|
||||
|
@ -17,7 +18,8 @@ const select = (state, props) => ({
|
|||
|
||||
const perform = dispatch => ({
|
||||
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
|
||||
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint))
|
||||
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FileDownloadButton);
|
|
@ -3,6 +3,13 @@ import { NativeModules, Text, View, TouchableOpacity } from 'react-native';
|
|||
import fileDownloadButtonStyle from '../../styles/fileDownloadButton';
|
||||
|
||||
class FileDownloadButton extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
const { costInfo, fetchCostInfo, uri } = this.props;
|
||||
if (costInfo === undefined) {
|
||||
fetchCostInfo(uri);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
//this.checkAvailability(nextProps.uri);
|
||||
this.restartDownload(nextProps);
|
||||
|
@ -54,7 +61,7 @@ class FileDownloadButton extends React.PureComponent {
|
|||
if (!costInfo) {
|
||||
return (
|
||||
<View style={[style, fileDownloadButtonStyle.container]}>
|
||||
<Text>Fetching cost info...</Text>
|
||||
<Text style={fileDownloadButtonStyle.text}>Fetching cost info...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
12
app/src/component/uriBar/index.js
Normal file
12
app/src/component/uriBar/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import UriBar from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UriBar);
|
44
app/src/component/uriBar/view.js
Normal file
44
app/src/component/uriBar/view.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { normalizeURI } from 'lbry-redux';
|
||||
import { TextInput, View } from 'react-native';
|
||||
import uriBarStyle from '../../styles/uriBar';
|
||||
|
||||
class UriBar extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
uri: null,
|
||||
currentValue: null
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, navigation } = this.props;
|
||||
if (!this.state.currentValue) {
|
||||
this.setState({ currentValue: value });
|
||||
}
|
||||
|
||||
// TODO: Search and URI suggestions overlay
|
||||
return (
|
||||
<View style={uriBarStyle.uriContainer}>
|
||||
<TextInput style={uriBarStyle.uriText}
|
||||
placeholder={'Enter a LBRY URI or some text'}
|
||||
underlineColorAndroid={'transparent'}
|
||||
numberOfLines={1}
|
||||
value={this.state.currentValue}
|
||||
returnKeyType={'go'}
|
||||
onChangeText={(text) => this.setState({uri: text, currentValue: text})}
|
||||
onSubmitEditing={() => {
|
||||
if (this.state.uri) {
|
||||
let uri = this.state.uri;
|
||||
uri = uri.replace(/ /g, '-');
|
||||
navigation.navigate('File', { uri: normalizeURI(uri) });
|
||||
}
|
||||
}}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UriBar;
|
19
app/src/page/channel/index.js
Normal file
19
app/src/page/channel/index.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimsInChannelForCurrentPage,
|
||||
makeSelectFetchingChannelClaims,
|
||||
} from 'lbry-redux';
|
||||
import ChannelPage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimsInChannel: makeSelectClaimsInChannelForCurrentPage(props.uri)(state),
|
||||
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ChannelPage);
|
24
app/src/page/channel/view.js
Normal file
24
app/src/page/channel/view.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { ScrollView, Text, View } from 'react-native';
|
||||
import UriBar from '../../component/uriBar';
|
||||
import channelPageStyle from '../../styles/channelPage';
|
||||
|
||||
class ChannelPage extends React.PureComponent {
|
||||
render() {
|
||||
const { claim, navigation, uri } = this.props;
|
||||
const { name } = claim;
|
||||
|
||||
return (
|
||||
<View style={channelPageStyle.container}>
|
||||
<Text style={channelPageStyle.title}>{name}</Text>
|
||||
<ScrollView style={channelPageStyle.content}>
|
||||
|
||||
</ScrollView>
|
||||
<UriBar value={uri} navigation={navigation} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ChannelPage;
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import NavigationActions from 'react-navigation';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AsyncStorage,
|
||||
NativeModules,
|
||||
SectionList,
|
||||
ScrollView,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
@ -12,6 +12,8 @@ import { normalizeURI } from 'lbry-redux';
|
|||
import moment from 'moment';
|
||||
import FileItem from '../../component/fileItem';
|
||||
import discoverStyle from '../../styles/discover';
|
||||
import Colors from '../../styles/colors';
|
||||
import UriBar from '../../component/uriBar';
|
||||
import Feather from 'react-native-vector-icons/Feather';
|
||||
|
||||
class DiscoverPage extends React.PureComponent {
|
||||
|
@ -48,7 +50,12 @@ class DiscoverPage extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<View style={discoverStyle.container}>
|
||||
{!hasContent && fetchingFeaturedUris && <Text style={discoverStyle.title}>Fetching content...</Text>}
|
||||
{!hasContent && fetchingFeaturedUris && (
|
||||
<View style={discoverStyle.busyContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||
<Text style={discoverStyle.title}>Fetching content...</Text>
|
||||
</View>
|
||||
)}
|
||||
{hasContent &&
|
||||
<SectionList style={discoverStyle.scrollContainer}
|
||||
renderItem={ ({item, index, section}) => (
|
||||
|
@ -66,6 +73,7 @@ class DiscoverPage extends React.PureComponent {
|
|||
keyExtractor={(item, index) => item}
|
||||
/>
|
||||
}
|
||||
<UriBar navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doFetchFileInfo,
|
||||
makeSelectFileInfoForUri,
|
||||
doResolveUri,
|
||||
doFetchCostInfoForUri,
|
||||
makeSelectIsUriResolving,
|
||||
makeSelectCostInfoForUri,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectMetadataForUri,
|
||||
selectRewardContentClaimIds,
|
||||
makeSelectCostInfoForUri
|
||||
selectBlackListedOutpoints,
|
||||
} from 'lbry-redux';
|
||||
import { doDeleteFile, doStopDownloadingFile } from '../../redux/actions/file';
|
||||
import FilePage from './view';
|
||||
|
@ -15,7 +18,9 @@ import FilePage from './view';
|
|||
const select = (state, props) => {
|
||||
const selectProps = { uri: props.navigation.state.params.uri };
|
||||
return {
|
||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||
claim: makeSelectClaimForUri(selectProps.uri)(state),
|
||||
isResolvingUri: makeSelectIsUriResolving(selectProps.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(selectProps.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(selectProps.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(selectProps.uri)(state),
|
||||
|
@ -29,6 +34,7 @@ const select = (state, props) => {
|
|||
const perform = dispatch => ({
|
||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
|
||||
deleteFile: (fileInfo, deleteFromDevice, abandonClaim) => {
|
||||
dispatch(doDeleteFile(fileInfo, deleteFromDevice, abandonClaim));
|
||||
|
|
|
@ -13,9 +13,11 @@ import {
|
|||
NativeModules
|
||||
} from 'react-native';
|
||||
import Colors from '../../styles/colors';
|
||||
import ChannelPage from '../channel';
|
||||
import FileItemMedia from '../../component/fileItemMedia';
|
||||
import FileDownloadButton from '../../component/fileDownloadButton';
|
||||
import MediaPlayer from '../../component/mediaPlayer';
|
||||
import UriBar from '../../component/uriBar';
|
||||
import Video from 'react-native-video';
|
||||
import filePageStyle from '../../styles/filePage';
|
||||
|
||||
|
@ -34,15 +36,27 @@ class FilePage extends React.PureComponent {
|
|||
|
||||
componentDidMount() {
|
||||
StatusBar.setHidden(false);
|
||||
|
||||
const { isResolvingUri, resolveUri, navigation } = this.props;
|
||||
const { uri } = navigation.state.params;
|
||||
if (!isResolvingUri) resolveUri(uri);
|
||||
|
||||
this.fetchFileInfo(this.props);
|
||||
this.fetchCostInfo(this.props);
|
||||
|
||||
if (NativeModules.Mixpanel) {
|
||||
NativeModules.Mixpanel.track('Open File Page', { Uri: this.props.navigation.state.params.uri });
|
||||
NativeModules.Mixpanel.track('Open File Page', { Uri: uri });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchFileInfo(nextProps);
|
||||
componentDidUpdate() {
|
||||
this.fetchFileInfo(this.props);
|
||||
const { isResolvingUri, resolveUri, claim, navigation } = this.props;
|
||||
const { uri } = navigation.state.params;
|
||||
|
||||
if (!isResolvingUri && claim === undefined && uri) {
|
||||
resolveUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
fetchFileInfo(props) {
|
||||
|
@ -113,17 +127,34 @@ class FilePage extends React.PureComponent {
|
|||
contentType,
|
||||
tab,
|
||||
rewardedContentClaimIds,
|
||||
isResolvingUri,
|
||||
blackListedOutpoints,
|
||||
navigation
|
||||
} = this.props;
|
||||
const { uri } = navigation.state.params;
|
||||
|
||||
if (!claim || !metadata) {
|
||||
return (
|
||||
let innerContent = null;
|
||||
if ((isResolvingUri && !claim) || !claim) {
|
||||
innerContent = (
|
||||
<View style={filePageStyle.container}>
|
||||
<Text style={filePageStyle.emptyClaimText}>Empty claim or metadata info.</Text>
|
||||
{isResolvingUri &&
|
||||
<View style={filePageStyle.busyContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
|
||||
</View>}
|
||||
{ claim === null && !isResolvingUri &&
|
||||
<View style={filePageStyle.container}>
|
||||
<Text style={filePageStyle.emptyClaimText}>There's nothing at this location.</Text>
|
||||
</View>
|
||||
}
|
||||
<UriBar value={uri} navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
} else if (claim && claim.name.length && claim.name[0] === '@') {
|
||||
innerContent = (
|
||||
<ChannelPage uri={uri} navigation={navigation} />
|
||||
);
|
||||
} else if (claim) {
|
||||
const completed = fileInfo && fileInfo.completed;
|
||||
const title = metadata.title;
|
||||
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
||||
|
@ -144,17 +175,17 @@ class FilePage extends React.PureComponent {
|
|||
const canLoadMedia = fileInfo &&
|
||||
(fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes); // 2MB = 1024*1024*2
|
||||
|
||||
return (
|
||||
innerContent = (
|
||||
<View style={filePageStyle.pageContainer}>
|
||||
<View style={filePageStyle.mediaContainer}>
|
||||
{(!fileInfo || (isPlayable && !canLoadMedia)) &&
|
||||
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />}
|
||||
{isPlayable && !this.state.mediaLoaded && <ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />}
|
||||
{!completed && !canLoadMedia && <FileDownloadButton uri={navigation.state.params.uri} style={filePageStyle.downloadButton} />}
|
||||
{!completed && !canLoadMedia && <FileDownloadButton uri={uri} style={filePageStyle.downloadButton} />}
|
||||
</View>
|
||||
{canLoadMedia && <View style={playerBgStyle} />}
|
||||
{canLoadMedia && <MediaPlayer fileInfo={fileInfo}
|
||||
uri={navigation.state.params.uri}
|
||||
uri={uri}
|
||||
style={playerStyle}
|
||||
onFullscreenToggled={this.handleFullscreenToggle}
|
||||
onMediaLoaded={() => { this.setState({ mediaLoaded: true }); }}/>}
|
||||
|
@ -171,15 +202,13 @@ class FilePage extends React.PureComponent {
|
|||
{channelName && <Text style={filePageStyle.channelName} selectable={true}>{channelName}</Text>}
|
||||
{description && <Text style={filePageStyle.description} selectable={true}>{description}</Text>}
|
||||
</ScrollView>
|
||||
<View style={filePageStyle.uriContainer}>
|
||||
<TextInput style={filePageStyle.uriText}
|
||||
underlineColorAndroid={'transparent'}
|
||||
numberOfLines={1}
|
||||
value={navigation.state.params.uri} />
|
||||
</View>
|
||||
<UriBar value={uri} navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return innerContent;
|
||||
}
|
||||
}
|
||||
|
||||
export default FilePage;
|
||||
|
|
20
app/src/styles/channelPage.js
Normal file
20
app/src/styles/channelPage.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import Colors from './colors';
|
||||
|
||||
const channelPageStyle = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
content: {
|
||||
flex: 1
|
||||
},
|
||||
title: {
|
||||
color: Colors.LbryGreen,
|
||||
fontFamily: 'Metropolis-SemiBold',
|
||||
fontSize: 30,
|
||||
margin: 16
|
||||
}
|
||||
});
|
||||
|
||||
export default channelPageStyle;
|
|
@ -2,17 +2,22 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
const discoverStyle = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
flex: 1
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1
|
||||
},
|
||||
busyContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
marginLeft: 10
|
||||
},
|
||||
categoryName: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
|
|
|
@ -2,7 +2,7 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
const fileDownloadButtonStyle = StyleSheet.create({
|
||||
container: {
|
||||
width: 120,
|
||||
width: 160,
|
||||
height: 36,
|
||||
borderRadius: 18,
|
||||
justifyContent: 'center',
|
||||
|
|
|
@ -116,19 +116,17 @@ const filePageStyle = StyleSheet.create({
|
|||
position: 'absolute',
|
||||
top: '40%'
|
||||
},
|
||||
uriContainer: {
|
||||
padding: 8,
|
||||
backgroundColor: Colors.VeryLightGrey,
|
||||
alignSelf: 'flex-end'
|
||||
busyContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
uriText: {
|
||||
backgroundColor: Colors.White,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.LightGrey,
|
||||
padding: 8,
|
||||
borderRadius: 4,
|
||||
infoText: {
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 16
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
marginLeft: 10
|
||||
}
|
||||
});
|
||||
|
||||
|
|
30
app/src/styles/uriBar.js
Normal file
30
app/src/styles/uriBar.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import Colors from './colors';
|
||||
|
||||
const uriBarStyle = StyleSheet.create({
|
||||
uriContainer: {
|
||||
backgroundColor: Colors.White,
|
||||
padding: 8,
|
||||
alignSelf: 'flex-end',
|
||||
width: '100%',
|
||||
shadowColor: Colors.Black,
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: StyleSheet.hairlineWidth,
|
||||
shadowOffset: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
},
|
||||
elevation: 4
|
||||
},
|
||||
uriText: {
|
||||
backgroundColor: Colors.White,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.LightGrey,
|
||||
padding: 8,
|
||||
borderRadius: 4,
|
||||
fontFamily: 'Metropolis-Regular',
|
||||
fontSize: 16,
|
||||
width: '100%'
|
||||
}
|
||||
});
|
||||
|
||||
export default uriBarStyle;
|
Loading…
Reference in a new issue