lbry-react-native/src/page/file/view.js
2019-11-18 06:50:10 +01:00

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;