1123 lines
40 KiB
JavaScript
1123 lines
40 KiB
JavaScript
import React from 'react';
|
|
import { Lbry, normalizeURI, parseURI } from 'lbry-redux';
|
|
import { Lbryio } from 'lbryinc';
|
|
import {
|
|
ActivityIndicator,
|
|
Alert,
|
|
DeviceEventEmitter,
|
|
Dimensions,
|
|
Image,
|
|
Linking,
|
|
NativeModules,
|
|
ScrollView,
|
|
StatusBar,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
TouchableWithoutFeedback,
|
|
View,
|
|
WebView,
|
|
} from 'react-native';
|
|
import { NavigationEvents } from 'react-navigation';
|
|
import { navigateBack, navigateToUri, formatLbryUrlForWeb } from 'utils/helper';
|
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
|
import ImageViewer from 'react-native-image-zoom-viewer';
|
|
import Button from 'component/button';
|
|
import EmptyStateView from 'component/emptyStateView';
|
|
import Tag from 'component/tag';
|
|
import ChannelPage from 'page/channel';
|
|
import Colors from 'styles/colors';
|
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
|
import DateTime from 'component/dateTime';
|
|
import FileDownloadButton from 'component/fileDownloadButton';
|
|
import FileItemMedia from 'component/fileItemMedia';
|
|
import FilePrice from 'component/filePrice';
|
|
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
|
import Link from 'component/link';
|
|
import MediaPlayer from 'component/mediaPlayer';
|
|
import RelatedContent from 'component/relatedContent';
|
|
import SubscribeButton from 'component/subscribeButton';
|
|
import SubscribeNotificationButton from 'component/subscribeNotificationButton';
|
|
import UriBar from 'component/uriBar';
|
|
import Video from 'react-native-video';
|
|
import FileRewardsDriver from 'component/fileRewardsDriver';
|
|
import filePageStyle from 'styles/filePage';
|
|
import uriBarStyle from 'styles/uriBar';
|
|
|
|
class FilePage extends React.PureComponent {
|
|
static navigationOptions = {
|
|
title: '',
|
|
};
|
|
|
|
tipAmountInput = null;
|
|
|
|
playerBackground = null;
|
|
|
|
scrollView = null;
|
|
|
|
startTime = null;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
autoPlayMedia: false,
|
|
autoDownloadStarted: false,
|
|
downloadButtonShown: false,
|
|
downloadPressed: false,
|
|
fileViewLogged: false,
|
|
fullscreenMode: false,
|
|
fileGetStarted: false,
|
|
imageUrls: null,
|
|
isLandscape: false,
|
|
mediaLoaded: false,
|
|
pageSuspended: false,
|
|
relatedContentY: 0,
|
|
sendTipStarted: false,
|
|
showDescription: false,
|
|
showImageViewer: false,
|
|
showWebView: false,
|
|
showTipView: false,
|
|
playerBgHeight: 0,
|
|
playerHeight: 0,
|
|
tipAmount: null,
|
|
uri: null,
|
|
uriVars: null,
|
|
stopDownloadConfirmed: false,
|
|
streamingMode: false,
|
|
};
|
|
}
|
|
|
|
didFocusListener;
|
|
|
|
componentWillMount() {
|
|
const { navigation } = this.props;
|
|
// this.didFocusListener = navigation.addListener('didFocus', this.onComponentFocused);
|
|
}
|
|
|
|
onComponentFocused = () => {
|
|
StatusBar.setHidden(false);
|
|
NativeModules.Firebase.setCurrentScreen('File');
|
|
|
|
DeviceEventEmitter.addListener('onDownloadStarted', this.handleDownloadStarted);
|
|
DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated);
|
|
DeviceEventEmitter.addListener('onDownloadCompleted', this.handleDownloadCompleted);
|
|
|
|
const { fetchMyClaims, fileInfo, isResolvingUri, resolveUri, navigation } = this.props;
|
|
const { uri, uriVars } = navigation.state.params;
|
|
this.setState({ uri, uriVars });
|
|
|
|
if (!isResolvingUri) resolveUri(uri);
|
|
|
|
this.fetchFileInfo(this.props);
|
|
this.fetchCostInfo(this.props);
|
|
fetchMyClaims();
|
|
|
|
if (NativeModules.Firebase) {
|
|
NativeModules.Firebase.track('open_file_page', { uri: uri });
|
|
}
|
|
if (NativeModules.UtilityModule) {
|
|
NativeModules.UtilityModule.keepAwakeOn();
|
|
}
|
|
};
|
|
|
|
componentDidMount() {
|
|
this.onComponentFocused();
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
const {
|
|
claim,
|
|
currentRoute,
|
|
failedPurchaseUris: prevFailedPurchaseUris,
|
|
purchasedUris: prevPurchasedUris,
|
|
navigation,
|
|
contentType,
|
|
notify,
|
|
} = this.props;
|
|
const { uri } = navigation.state.params;
|
|
const {
|
|
currentRoute: prevRoute,
|
|
failedPurchaseUris,
|
|
fileInfo,
|
|
purchasedUris,
|
|
purchaseUriErrorMessage,
|
|
streamingUrl,
|
|
} = nextProps;
|
|
|
|
if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) {
|
|
this.onComponentFocused();
|
|
}
|
|
|
|
if (failedPurchaseUris.includes(uri) && !purchasedUris.includes(uri)) {
|
|
if (purchaseUriErrorMessage && purchaseUriErrorMessage.trim().length > 0) {
|
|
notify({ message: purchaseUriErrorMessage, isError: true });
|
|
}
|
|
this.setState({ downloadPressed: false, fileViewLogged: false, mediaLoaded: false });
|
|
}
|
|
|
|
const mediaType = Lbry.getMediaType(contentType);
|
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
|
if (
|
|
(this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) &&
|
|
NativeModules.UtilityModule
|
|
) {
|
|
if (purchasedUris.includes(uri)) {
|
|
const { nout, txid } = claim;
|
|
const outpoint = `${txid}:${nout}`;
|
|
NativeModules.UtilityModule.queueDownload(outpoint);
|
|
// If the media is playable, file/view will be done in onPlaybackStarted
|
|
if (!isPlayable && !this.state.fileViewLogged) {
|
|
this.logFileView(uri, claim);
|
|
}
|
|
this.setState({ fileGetStarted: false });
|
|
}
|
|
NativeModules.UtilityModule.checkDownloads();
|
|
}
|
|
|
|
if (!this.state.streamingMode && isPlayable) {
|
|
if (streamingUrl) {
|
|
this.setState({ streamingMode: true, currentStreamUrl: streamingUrl });
|
|
} else if (fileInfo && fileInfo.streaming_url) {
|
|
this.setState({ streamingMode: true, currentStreamUrl: fileInfo.streaming_url });
|
|
}
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
const { claim, contentType, fileInfo, isResolvingUri, resolveUri, navigation } = this.props;
|
|
const { uri } = this.state;
|
|
if (!isResolvingUri && claim === undefined && uri) {
|
|
resolveUri(uri);
|
|
}
|
|
|
|
// Returned to the page. If mediaLoaded, and currentMediaInfo is different, update
|
|
if (this.state.mediaLoaded && window.currentMediaInfo && window.currentMediaInfo.uri !== this.state.uri) {
|
|
const { metadata } = this.props;
|
|
window.currentMediaInfo = {
|
|
channel: claim ? claim.channel_name : null,
|
|
title: metadata ? metadata.title : claim.name,
|
|
uri: this.state.uri,
|
|
};
|
|
}
|
|
}
|
|
|
|
fetchFileInfo(props) {
|
|
if (props.fileInfo === undefined) {
|
|
props.fetchFileInfo(props.navigation.state.params.uri);
|
|
}
|
|
}
|
|
|
|
fetchCostInfo(props) {
|
|
if (props.costInfo === undefined) {
|
|
props.fetchCostInfo(props.navigation.state.params.uri);
|
|
}
|
|
}
|
|
|
|
handleFullscreenToggle = isFullscreen => {
|
|
const { toggleFullscreenMode } = this.props;
|
|
this.setState({ fullscreenMode: isFullscreen });
|
|
toggleFullscreenMode(isFullscreen);
|
|
|
|
StatusBar.setHidden(isFullscreen);
|
|
|
|
if (isFullscreen) {
|
|
// fullscreen, so change orientation to landscape mode
|
|
NativeModules.ScreenOrientation.lockOrientationLandscape();
|
|
|
|
// hide the navigation bar (on devices that have the soft navigation bar)
|
|
NativeModules.UtilityModule.hideNavigationBar();
|
|
} else {
|
|
// Switch back to portrait mode when the media is not fullscreen
|
|
NativeModules.ScreenOrientation.lockOrientationPortrait();
|
|
|
|
// hide the navigation bar (on devices that have the soft navigation bar)
|
|
NativeModules.UtilityModule.showNavigationBar();
|
|
}
|
|
};
|
|
|
|
onEditPressed = () => {
|
|
const { claim, navigation } = this.props;
|
|
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH, params: { editMode: true, claimToEdit: claim } });
|
|
};
|
|
|
|
onDeletePressed = () => {
|
|
const { abandonClaim, claim, deleteFile, deletePurchasedUri, myClaimUris, fileInfo, navigation } = this.props;
|
|
|
|
Alert.alert(
|
|
'Delete file',
|
|
'Are you sure you want to remove this file from your device?',
|
|
[
|
|
{ text: 'No' },
|
|
{
|
|
text: 'Yes',
|
|
onPress: () => {
|
|
const { uri } = navigation.state.params;
|
|
|
|
deleteFile(`${claim.txid}:${claim.nout}`, true);
|
|
deletePurchasedUri(uri);
|
|
|
|
NativeModules.UtilityModule.deleteDownload(uri);
|
|
this.setState({
|
|
downloadPressed: false,
|
|
fileViewLogged: false,
|
|
mediaLoaded: false,
|
|
stopDownloadConfirmed: false,
|
|
});
|
|
|
|
if (claim) {
|
|
const fullUri = normalizeURI(`${claim.name}#${claim.claim_id}`);
|
|
const ownedClaim = myClaimUris.includes(fullUri);
|
|
if (ownedClaim) {
|
|
const { txid, nout } = claim;
|
|
abandonClaim(txid, nout);
|
|
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISHES });
|
|
}
|
|
}
|
|
},
|
|
},
|
|
],
|
|
{ cancelable: true }
|
|
);
|
|
};
|
|
|
|
onStopDownloadPressed = () => {
|
|
const { deletePurchasedUri, fileInfo, navigation, notify, stopDownload } = this.props;
|
|
|
|
Alert.alert(
|
|
'Stop download',
|
|
'Are you sure you want to stop downloading this file?',
|
|
[
|
|
{ text: 'No' },
|
|
{
|
|
text: 'Yes',
|
|
onPress: () => {
|
|
const { uri } = navigation.state.params;
|
|
stopDownload(uri, fileInfo);
|
|
deletePurchasedUri(uri);
|
|
if (NativeModules.UtilityModule) {
|
|
NativeModules.UtilityModule.deleteDownload(uri);
|
|
}
|
|
this.setState({
|
|
downloadPressed: false,
|
|
fileViewLogged: false,
|
|
mediaLoaded: false,
|
|
stopDownloadConfirmed: true,
|
|
});
|
|
|
|
// there can be a bit of lag between the user pressing Yes and the UI being updated
|
|
// after the file_set_status and file_delete operations, so let the user know
|
|
notify({
|
|
message: 'The download will stop momentarily. You do not need to wait to discover something else.',
|
|
});
|
|
},
|
|
},
|
|
],
|
|
{ cancelable: true }
|
|
);
|
|
};
|
|
|
|
componentWillUnmount() {
|
|
StatusBar.setHidden(false);
|
|
if (NativeModules.ScreenOrientation) {
|
|
NativeModules.ScreenOrientation.unlockOrientation();
|
|
}
|
|
if (NativeModules.UtilityModule) {
|
|
const utility = NativeModules.UtilityModule;
|
|
utility.keepAwakeOff();
|
|
utility.showNavigationBar();
|
|
}
|
|
if (this.didFocusListener) {
|
|
this.didFocusListener.remove();
|
|
}
|
|
if (window.currentMediaInfo) {
|
|
window.currentMediaInfo = null;
|
|
}
|
|
window.player = null;
|
|
|
|
DeviceEventEmitter.removeListener('onDownloadStarted', this.handleDownloadStarted);
|
|
DeviceEventEmitter.removeListener('onDownloadUpdated', this.handleDownloadUpdated);
|
|
DeviceEventEmitter.removeListener('onDownloadCompleted', this.handleDownloadCompleted);
|
|
}
|
|
|
|
handleDownloadStarted = evt => {
|
|
const { startDownload } = this.props;
|
|
const { uri, outpoint, fileInfo } = evt;
|
|
startDownload(uri, outpoint, fileInfo);
|
|
};
|
|
|
|
handleDownloadUpdated = evt => {
|
|
const { updateDownload } = this.props;
|
|
const { uri, outpoint, fileInfo, progress } = evt;
|
|
updateDownload(uri, outpoint, fileInfo, progress);
|
|
};
|
|
|
|
handleDownloadCompleted = evt => {
|
|
const { completeDownload } = this.props;
|
|
const { uri, outpoint, fileInfo } = evt;
|
|
completeDownload(uri, outpoint, fileInfo);
|
|
};
|
|
|
|
localUriForFileInfo = fileInfo => {
|
|
if (!fileInfo) {
|
|
return null;
|
|
}
|
|
return 'file:///' + fileInfo.download_path;
|
|
};
|
|
|
|
playerUriForFileInfo = fileInfo => {
|
|
const { streamingUrl } = this.props;
|
|
if (fileInfo && fileInfo.download_path) {
|
|
return this.getEncodedDownloadPath(fileInfo);
|
|
}
|
|
if (streamingUrl) {
|
|
return streamingUrl;
|
|
}
|
|
if (this.state.currentStreamUrl) {
|
|
return this.state.currentStreamUrl;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
getEncodedDownloadPath = fileInfo => {
|
|
if (this.state.encodedFilePath) {
|
|
return this.state.encodedFilePath;
|
|
}
|
|
|
|
const { file_name: fileName } = fileInfo;
|
|
const encodedFileName = encodeURIComponent(fileName).replace(/!/g, '%21');
|
|
const encodedFilePath = fileInfo.download_path.replace(fileName, encodedFileName);
|
|
return encodedFilePath;
|
|
};
|
|
|
|
linkify = text => {
|
|
let linkifiedContent = [];
|
|
let lines = text.split(/\n/g);
|
|
linkifiedContent = lines.map((line, i) => {
|
|
let tokens = line.split(/\s/g);
|
|
let lineContent =
|
|
tokens.length === 0
|
|
? ''
|
|
: tokens.map((token, j) => {
|
|
let hasSpace = j !== tokens.length - 1;
|
|
let space = hasSpace ? ' ' : '';
|
|
|
|
if (token.match(/^(lbry|https?):\/\//g)) {
|
|
return (
|
|
<Link
|
|
key={j}
|
|
style={filePageStyle.link}
|
|
href={token}
|
|
text={token}
|
|
effectOnTap={filePageStyle.linkTapped}
|
|
/>
|
|
);
|
|
} else {
|
|
return token + space;
|
|
}
|
|
});
|
|
|
|
lineContent.push('\n');
|
|
return <Text key={i}>{lineContent}</Text>;
|
|
});
|
|
|
|
return linkifiedContent;
|
|
};
|
|
|
|
checkOrientation = () => {
|
|
if (this.state.fullscreenMode) {
|
|
return;
|
|
}
|
|
|
|
const screenDimension = Dimensions.get('window');
|
|
const screenWidth = screenDimension.width;
|
|
const screenHeight = screenDimension.height;
|
|
const isLandscape = screenWidth > screenHeight;
|
|
this.setState({ isLandscape });
|
|
|
|
if (!this.playerBackground) {
|
|
return;
|
|
}
|
|
|
|
if (isLandscape) {
|
|
this.playerBackground.setNativeProps({
|
|
height: screenHeight - StyleSheet.flatten(uriBarStyle.uriContainer).height,
|
|
});
|
|
} else if (this.state.playerBgHeight > 0) {
|
|
this.playerBackground.setNativeProps({ height: this.state.playerBgHeight });
|
|
}
|
|
};
|
|
|
|
onMediaLoaded = (channelName, title, uri) => {
|
|
this.setState({ mediaLoaded: true });
|
|
window.currentMediaInfo = { channel: channelName, title, uri };
|
|
};
|
|
|
|
onPlaybackStarted = () => {
|
|
let timeToStartMillis, timeToStart;
|
|
if (this.startTime) {
|
|
timeToStartMillis = Date.now() - this.startTime;
|
|
timeToStart = Math.ceil(timeToStartMillis / 1000);
|
|
this.startTime = null;
|
|
}
|
|
|
|
const { claim, navigation } = this.props;
|
|
const { uri } = navigation.state.params;
|
|
this.logFileView(uri, claim, timeToStartMillis);
|
|
|
|
let payload = { uri: uri };
|
|
if (!isNaN(timeToStart)) {
|
|
payload['time_to_start_seconds'] = timeToStart;
|
|
payload['time_to_start_ms'] = timeToStartMillis;
|
|
}
|
|
NativeModules.Firebase.track('play', payload);
|
|
};
|
|
|
|
onPlaybackFinished = () => {
|
|
if (this.scrollView && this.state.relatedContentY) {
|
|
this.scrollView.scrollTo({ x: 0, y: this.state.relatedContentY, animated: true });
|
|
}
|
|
};
|
|
|
|
setRelatedContentPosition = evt => {
|
|
if (!this.state.relatedContentY) {
|
|
this.setState({ relatedContentY: evt.nativeEvent.layout.y });
|
|
}
|
|
};
|
|
|
|
logFileView = (uri, claim, timeToStart) => {
|
|
if (!claim) {
|
|
return;
|
|
}
|
|
|
|
const { claimEligibleRewards } = this.props;
|
|
const { nout, claim_id: claimId, txid } = claim;
|
|
const outpoint = `${txid}:${nout}`;
|
|
const params = {
|
|
uri,
|
|
outpoint,
|
|
claim_id: claimId,
|
|
};
|
|
if (!isNaN(timeToStart)) {
|
|
params.time_to_start = timeToStart;
|
|
}
|
|
|
|
Lbryio.call('file', 'view', params)
|
|
.then(() => claimEligibleRewards())
|
|
.catch(() => {});
|
|
this.setState({ fileViewLogged: true });
|
|
};
|
|
|
|
handleSharePress = () => {
|
|
const { claim, notify } = this.props;
|
|
if (claim) {
|
|
const { canonical_url: canonicalUrl, short_url: shortUrl, permanent_url: permanentUrl } = claim;
|
|
const url = Constants.SHARE_BASE_URL + formatLbryUrlForWeb(canonicalUrl || shortUrl || permanentUrl);
|
|
NativeModules.UtilityModule.shareUrl(url);
|
|
}
|
|
};
|
|
|
|
handleSendTip = () => {
|
|
const { claim, balance, navigation, notify, sendTip } = this.props;
|
|
const { uri } = navigation.state.params;
|
|
const { tipAmount } = this.state;
|
|
|
|
if (tipAmount > balance) {
|
|
notify({
|
|
message: 'Insufficient credits',
|
|
});
|
|
return;
|
|
}
|
|
|
|
const suffix = 'credit' + (parseInt(tipAmount, 10) === 1 ? '' : 's');
|
|
Alert.alert(
|
|
'Send tip',
|
|
`Are you sure you want to tip ${tipAmount} ${suffix}?`,
|
|
[
|
|
{ text: 'No' },
|
|
{
|
|
text: 'Yes',
|
|
onPress: () => {
|
|
this.setState({ sendTipStarted: true }, () =>
|
|
sendTip(tipAmount, claim.claim_id, false, () => {
|
|
this.setState({ tipAmount: null, showTipView: false, sendTipStarted: false });
|
|
})
|
|
);
|
|
},
|
|
},
|
|
],
|
|
{ cancelable: true }
|
|
);
|
|
};
|
|
|
|
renderTags = tags => {
|
|
const { navigation } = this.props;
|
|
return tags.map((tag, i) => (
|
|
<Tag style={filePageStyle.tagItem} key={`${tag}-${i}`} name={tag} navigation={navigation} />
|
|
));
|
|
};
|
|
|
|
onFileDownloadButtonPlayed = () => {
|
|
const { setPlayerVisible } = this.props;
|
|
this.startTime = Date.now();
|
|
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
|
|
setPlayerVisible();
|
|
};
|
|
|
|
onBackButtonPressed = () => {
|
|
const { navigation, drawerStack, popDrawerStack } = this.props;
|
|
navigateBack(navigation, drawerStack, popDrawerStack);
|
|
};
|
|
|
|
onSaveFilePressed = () => {
|
|
const { costInfo, fileGet, fileInfo, navigation, purchasedUris, purchaseUri } = this.props;
|
|
const { uri } = navigation.state.params;
|
|
|
|
if (fileInfo || purchasedUris.includes(uri)) {
|
|
// file already in library or URI already purchased, use fileGet directly
|
|
this.setState({ fileGetStarted: true }, () => fileGet(uri, true));
|
|
} else {
|
|
this.setState(
|
|
{
|
|
downloadPressed: true,
|
|
autoPlayMedia: false,
|
|
stopDownloadConfirmed: false,
|
|
},
|
|
() => purchaseUri(uri, costInfo, true)
|
|
);
|
|
}
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
balance,
|
|
claim,
|
|
channels,
|
|
channelUri,
|
|
costInfo,
|
|
fileInfo,
|
|
metadata,
|
|
contentType,
|
|
tab,
|
|
rewardedContentClaimIds,
|
|
isResolvingUri,
|
|
blackListedOutpoints,
|
|
myClaimUris,
|
|
navigation,
|
|
position,
|
|
purchaseUri,
|
|
thumbnail,
|
|
title,
|
|
} = this.props;
|
|
const { uri, autoplay } = navigation.state.params;
|
|
|
|
const { isChannel } = parseURI(uri);
|
|
const myChannelUris = channels ? channels.map(channel => channel.permanent_url) : [];
|
|
const ownedClaim = myClaimUris.includes(uri) || myChannelUris.includes(uri);
|
|
|
|
let innerContent = null;
|
|
if ((isResolvingUri && !claim) || !claim) {
|
|
return (
|
|
<View style={filePageStyle.container}>
|
|
<UriBar value={uri} navigation={navigation} />
|
|
{isResolvingUri && (
|
|
<View style={filePageStyle.busyContainer}>
|
|
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
|
|
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
|
|
</View>
|
|
)}
|
|
{claim === null && !isResolvingUri && (
|
|
<View style={filePageStyle.container}>
|
|
{ownedClaim && (
|
|
<EmptyStateView
|
|
message={
|
|
isChannel
|
|
? 'It looks like you just created this channel. It will appear in a few minutes.'
|
|
: 'It looks you just published this content. It will appear in a few minutes.'
|
|
}
|
|
/>
|
|
)}
|
|
{!ownedClaim && (
|
|
<EmptyStateView
|
|
message={"There's nothing at this location."}
|
|
buttonText={'Publish something here'}
|
|
onButtonPress={() =>
|
|
navigation.navigate({
|
|
routeName: Constants.DRAWER_ROUTE_PUBLISH,
|
|
params: { vanityUrl: uri.trim() },
|
|
})
|
|
}
|
|
/>
|
|
)}
|
|
</View>
|
|
)}
|
|
<FloatingWalletBalance navigation={navigation} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (claim) {
|
|
if (isChannel) {
|
|
return <ChannelPage uri={uri} navigation={navigation} />;
|
|
}
|
|
|
|
let isClaimBlackListed = false;
|
|
|
|
if (blackListedOutpoints) {
|
|
for (let i = 0; i < blackListedOutpoints.length; i += 1) {
|
|
const outpoint = blackListedOutpoints[i];
|
|
if (outpoint.txid === claim.txid && outpoint.nout === claim.nout) {
|
|
isClaimBlackListed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isClaimBlackListed) {
|
|
return (
|
|
<View style={filePageStyle.pageContainer}>
|
|
<View style={filePageStyle.dmcaContainer}>
|
|
<Text style={filePageStyle.dmcaText}>
|
|
In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked
|
|
access to this content from our applications.
|
|
</Text>
|
|
<Link style={filePageStyle.dmcaLink} href="https://lbry.com/faq/dmca" text="Read More" />
|
|
</View>
|
|
<UriBar value={uri} navigation={navigation} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
let tags = [];
|
|
if (claim && claim.value && claim.value.tags) {
|
|
tags = claim.value.tags;
|
|
}
|
|
|
|
const completed = fileInfo && fileInfo.completed;
|
|
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
|
const description = metadata.description ? metadata.description : null;
|
|
const mediaType = Lbry.getMediaType(contentType);
|
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
|
const { height, signing_channel: signingChannel, value } = claim;
|
|
const channelName = signingChannel && signingChannel.name;
|
|
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
|
|
const canSendTip = this.state.tipAmount > 0;
|
|
const fullUri = `${claim.name}#${claim.claim_id}`;
|
|
const canEdit = myClaimUris.includes(normalizeURI(fullUri));
|
|
const showActions =
|
|
(canEdit || (fileInfo && fileInfo.download_path)) &&
|
|
!this.state.fullscreenMode &&
|
|
!this.state.showImageViewer &&
|
|
!this.state.showWebView;
|
|
const showFileActions =
|
|
canEdit ||
|
|
(fileInfo &&
|
|
fileInfo.download_path &&
|
|
(completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes)));
|
|
const fullChannelUri =
|
|
channelClaimId && channelClaimId.trim().length > 0
|
|
? normalizeURI(`${channelName}#${channelClaimId}`)
|
|
: normalizeURI(channelName);
|
|
const shortChannelUri = signingChannel ? signingChannel.short_url : null;
|
|
|
|
const playerStyle = [
|
|
filePageStyle.player,
|
|
this.state.isLandscape
|
|
? filePageStyle.containedPlayerLandscape
|
|
: this.state.fullscreenMode
|
|
? filePageStyle.fullscreenPlayer
|
|
: filePageStyle.containedPlayer,
|
|
];
|
|
const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground];
|
|
const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground];
|
|
// at least 2MB (or the full download) before media can be loaded
|
|
const canLoadMedia =
|
|
this.state.streamingMode ||
|
|
(fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes === fileInfo.total_bytes)); // 2MB = 1024*1024*2
|
|
const duration = claim && claim.value && claim.value.video ? claim.value.video.duration : null;
|
|
const isViewable = mediaType === 'image' || mediaType === 'text';
|
|
const isWebViewable = mediaType === 'text';
|
|
const canOpen = isViewable && completed;
|
|
const localFileUri = this.localUriForFileInfo(fileInfo);
|
|
const unsupported = !isPlayable && !canOpen;
|
|
|
|
const openFile = () => {
|
|
if (mediaType === 'image') {
|
|
// use image viewer
|
|
if (!this.state.showImageViewer) {
|
|
this.setState({
|
|
imageUrls: [
|
|
{
|
|
url: localFileUri,
|
|
},
|
|
],
|
|
showImageViewer: true,
|
|
});
|
|
}
|
|
}
|
|
if (isWebViewable) {
|
|
// show webview
|
|
if (!this.state.showWebView) {
|
|
this.setState({
|
|
showWebView: true,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
if (fileInfo && !this.state.autoDownloadStarted && this.state.uriVars && this.state.uriVars.download === 'true') {
|
|
this.setState({ autoDownloadStarted: true }, () => {
|
|
purchaseUri(uri, costInfo, !isPlayable);
|
|
if (NativeModules.UtilityModule) {
|
|
NativeModules.UtilityModule.checkDownloads();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (this.state.downloadPressed && canOpen) {
|
|
// automatically open a web viewable or image file after the download button is pressed
|
|
openFile();
|
|
}
|
|
|
|
return (
|
|
<View style={filePageStyle.pageContainer}>
|
|
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
|
|
{this.state.showWebView && isWebViewable && (
|
|
<WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
|
|
)}
|
|
|
|
{this.state.showImageViewer && (
|
|
<ImageViewer
|
|
style={StyleSheet.flatten(filePageStyle.viewer)}
|
|
imageUrls={this.state.imageUrls}
|
|
renderIndicator={() => null}
|
|
/>
|
|
)}
|
|
|
|
{!this.state.showWebView && (
|
|
<View
|
|
style={
|
|
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
|
|
}
|
|
onLayout={this.checkOrientation}
|
|
>
|
|
<View style={filePageStyle.mediaContainer}>
|
|
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
|
|
<FileItemMedia
|
|
duration={duration}
|
|
style={filePageStyle.thumbnail}
|
|
title={title}
|
|
thumbnail={thumbnail}
|
|
/>
|
|
)}
|
|
{!unsupported &&
|
|
(!this.state.downloadButtonShown || this.state.downloadPressed) &&
|
|
!this.state.mediaLoaded && (
|
|
<ActivityIndicator size="large" color={Colors.NextLbryGreen} style={filePageStyle.loading} />
|
|
)}
|
|
|
|
{unsupported && fileInfo && completed && (
|
|
<View style={filePageStyle.unsupportedContent}>
|
|
<Image
|
|
style={filePageStyle.unsupportedContentImage}
|
|
resizeMode={'stretch'}
|
|
source={require('../../assets/gerbil-happy.png')}
|
|
/>
|
|
<View style={filePageStyle.unspportedContentTextContainer}>
|
|
<Text style={filePageStyle.unsupportedContentTitle}>Unsupported Content</Text>
|
|
<Text style={filePageStyle.unsupportedContentText}>
|
|
Sorry, we are unable to display this content in the app. You can find the file named{' '}
|
|
<Text style={filePageStyle.unsupportedContentFilename}>{fileInfo.file_name}</Text> in your
|
|
downloads folder.
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{((isPlayable && !completed && !canLoadMedia) ||
|
|
canOpen ||
|
|
(!completed && !this.state.streamingMode)) &&
|
|
!this.state.downloadPressed && (
|
|
<FileDownloadButton
|
|
uri={claim && claim.permanent_url ? claim.permanent_url : uri}
|
|
style={filePageStyle.downloadButton}
|
|
openFile={openFile}
|
|
isPlayable={isPlayable}
|
|
isViewable={isViewable}
|
|
onPlay={this.onFileDownloadButtonPlayed}
|
|
onView={() => this.setState({ downloadPressed: true })}
|
|
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
|
|
/>
|
|
)}
|
|
{!fileInfo && (
|
|
<FilePrice
|
|
uri={uri}
|
|
style={filePageStyle.filePriceContainer}
|
|
textStyle={filePageStyle.filePriceText}
|
|
/>
|
|
)}
|
|
|
|
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
|
|
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
|
|
</TouchableOpacity>
|
|
</View>
|
|
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
|
|
<View
|
|
style={playerBgStyle}
|
|
ref={ref => {
|
|
this.playerBackground = ref;
|
|
}}
|
|
onLayout={evt => {
|
|
if (!this.state.playerBgHeight) {
|
|
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && this.state.fullscreenMode && (
|
|
<View style={fsPlayerBgStyle} />
|
|
)}
|
|
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
|
|
<MediaPlayer
|
|
claim={claim}
|
|
assignPlayer={ref => {
|
|
this.player = ref;
|
|
}}
|
|
uri={uri}
|
|
source={this.playerUriForFileInfo(fileInfo)}
|
|
style={playerStyle}
|
|
autoPlay={autoplay || this.state.autoPlayMedia}
|
|
onFullscreenToggled={this.handleFullscreenToggle}
|
|
onLayout={evt => {
|
|
if (!this.state.playerHeight) {
|
|
this.setState({ playerHeight: evt.nativeEvent.layout.height });
|
|
}
|
|
}}
|
|
onMediaLoaded={() => this.onMediaLoaded(channelName, title, uri)}
|
|
onBackButtonPressed={this.onBackButtonPressed}
|
|
onPlaybackStarted={this.onPlaybackStarted}
|
|
onPlaybackFinished={this.onPlaybackFinished}
|
|
thumbnail={thumbnail}
|
|
position={position}
|
|
/>
|
|
)}
|
|
|
|
{showActions && showFileActions && (
|
|
<View style={filePageStyle.actions}>
|
|
{showFileActions && (
|
|
<View style={filePageStyle.fileActions}>
|
|
{canEdit && (
|
|
<Button
|
|
style={[filePageStyle.actionButton, filePageStyle.editButton]}
|
|
theme={'light'}
|
|
icon={'edit'}
|
|
text={'Edit'}
|
|
onPress={this.onEditPressed}
|
|
/>
|
|
)}
|
|
|
|
{(completed || canEdit) && (
|
|
<Button
|
|
style={filePageStyle.actionButton}
|
|
theme={'light'}
|
|
icon={'trash-alt'}
|
|
text={'Delete'}
|
|
onPress={this.onDeletePressed}
|
|
/>
|
|
)}
|
|
{!completed &&
|
|
fileInfo &&
|
|
!fileInfo.stopped &&
|
|
fileInfo.written_bytes < fileInfo.total_bytes &&
|
|
!this.state.stopDownloadConfirmed && (
|
|
<Button
|
|
style={filePageStyle.actionButton}
|
|
icon={'stop'}
|
|
theme={'light'}
|
|
text={'Stop Download'}
|
|
onPress={this.onStopDownloadPressed}
|
|
/>
|
|
)}
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
<ScrollView
|
|
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
|
|
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}
|
|
keyboardShouldPersistTaps={'handled'}
|
|
ref={ref => {
|
|
this.scrollView = ref;
|
|
}}
|
|
>
|
|
<TouchableWithoutFeedback
|
|
style={filePageStyle.titleTouch}
|
|
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
|
|
>
|
|
<View style={filePageStyle.titleRow}>
|
|
<Text style={filePageStyle.title} selectable>
|
|
{title}
|
|
</Text>
|
|
{isRewardContent && <Icon name="award" style={filePageStyle.rewardIcon} size={16} />}
|
|
<View style={filePageStyle.descriptionToggle}>
|
|
<Icon name={this.state.showDescription ? 'caret-up' : 'caret-down'} size={24} />
|
|
</View>
|
|
</View>
|
|
</TouchableWithoutFeedback>
|
|
|
|
<View style={filePageStyle.largeButtonsRow}>
|
|
<TouchableOpacity style={filePageStyle.largeButton} onPress={this.handleSharePress}>
|
|
<Icon name={'share-alt'} size={20} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>Share</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={filePageStyle.largeButton}
|
|
onPress={() => this.setState({ showTipView: true })}
|
|
>
|
|
<Icon name={'gift'} size={20} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>Tip</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={filePageStyle.largeButton}
|
|
onPress={() => Linking.openURL(`https://lbry.com/dmca/${claim.claim_id}`)}
|
|
>
|
|
<Icon name={'flag'} size={20} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>Report</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={filePageStyle.channelRow}>
|
|
<View style={filePageStyle.publishInfo}>
|
|
{channelName && (
|
|
<Link
|
|
style={filePageStyle.channelName}
|
|
selectable
|
|
text={channelName}
|
|
numberOfLines={1}
|
|
ellipsizeMode={'tail'}
|
|
onPress={() => {
|
|
navigateToUri(
|
|
navigation,
|
|
normalizeURI(shortChannelUri || fullChannelUri),
|
|
null,
|
|
false,
|
|
fullChannelUri
|
|
);
|
|
}}
|
|
/>
|
|
)}
|
|
{!channelName && (
|
|
<Text style={filePageStyle.anonChannelName} selectable ellipsizeMode={'tail'}>
|
|
Anonymous
|
|
</Text>
|
|
)}
|
|
<DateTime
|
|
style={filePageStyle.publishDate}
|
|
textStyle={filePageStyle.publishDateText}
|
|
uri={uri}
|
|
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
|
|
show={DateTime.SHOW_DATE}
|
|
/>
|
|
</View>
|
|
<View style={filePageStyle.subscriptionRow}>
|
|
{false && ((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
|
|
<Button
|
|
style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
|
|
theme={'light'}
|
|
icon={'download'}
|
|
onPress={this.onSaveFilePressed}
|
|
/>
|
|
)}
|
|
{channelName && (
|
|
<SubscribeButton
|
|
style={filePageStyle.actionButton}
|
|
uri={fullChannelUri}
|
|
name={channelName}
|
|
hideText={false}
|
|
/>
|
|
)}
|
|
{channelName && (
|
|
<SubscribeNotificationButton
|
|
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
|
|
uri={fullChannelUri}
|
|
name={channelName}
|
|
/>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
{this.state.showTipView && <View style={filePageStyle.divider} />}
|
|
{this.state.showTipView && (
|
|
<View style={filePageStyle.tipCard}>
|
|
<View style={filePageStyle.row}>
|
|
<View style={filePageStyle.amountRow}>
|
|
<TextInput
|
|
editable={!this.state.sendTipStarted}
|
|
ref={ref => (this.tipAmountInput = ref)}
|
|
onChangeText={value => this.setState({ tipAmount: value })}
|
|
underlineColorAndroid={Colors.NextLbryGreen}
|
|
keyboardType={'numeric'}
|
|
placeholder={'0'}
|
|
value={this.state.tipAmount}
|
|
selectTextOnFocus
|
|
style={[filePageStyle.input, filePageStyle.tipAmountInput]}
|
|
/>
|
|
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
|
|
</View>
|
|
{this.state.sendTipStarted && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
|
|
<Link
|
|
style={[filePageStyle.link, filePageStyle.cancelTipLink]}
|
|
text={'Cancel'}
|
|
onPress={() => this.setState({ showTipView: false })}
|
|
/>
|
|
<Button
|
|
text={'Send a tip'}
|
|
style={[filePageStyle.button, filePageStyle.sendButton]}
|
|
disabled={!canSendTip || this.state.sendTipStarted}
|
|
onPress={this.handleSendTip}
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{this.state.showDescription && description && description.length > 0 && (
|
|
<View style={filePageStyle.divider} />
|
|
)}
|
|
{this.state.showDescription && description && (
|
|
<View>
|
|
<Text style={filePageStyle.description} selectable>
|
|
{this.linkify(description)}
|
|
</Text>
|
|
{tags && tags.length > 0 && (
|
|
<View style={filePageStyle.tagContainer}>
|
|
<Text style={filePageStyle.tagTitle}>Tags</Text>
|
|
<View style={filePageStyle.tagList}>{this.renderTags(tags)}</View>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{costInfo && parseFloat(costInfo.cost) > balance && !fileInfo && (
|
|
<FileRewardsDriver navigation={navigation} />
|
|
)}
|
|
|
|
<View onLayout={this.setRelatedContentPosition} />
|
|
<RelatedContent navigation={navigation} uri={uri} fullUri={fullUri} />
|
|
</ScrollView>
|
|
</View>
|
|
)}
|
|
{!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView && (
|
|
<FloatingWalletBalance navigation={navigation} />
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export default FilePage;
|