1450 lines
50 KiB
JavaScript
1450 lines
50 KiB
JavaScript
import React from 'react';
|
|
import { Lbry, formatCredits, normalizeURI, parseURI } from 'lbry-redux';
|
|
import { Lbryio } from 'lbryinc';
|
|
import {
|
|
ActivityIndicator,
|
|
Alert,
|
|
DeviceEventEmitter,
|
|
Dimensions,
|
|
Image,
|
|
Linking,
|
|
NativeModules,
|
|
Platform,
|
|
ScrollView,
|
|
StatusBar,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
TouchableOpacity,
|
|
TouchableWithoutFeedback,
|
|
View,
|
|
} from 'react-native';
|
|
import { WebView } from 'react-native-webview';
|
|
import { NavigationEvents } from 'react-navigation';
|
|
import { navigateBack, navigateToUri, formatBytes, 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 ModalTipView from 'component/modalTipView';
|
|
import ProgressCircle from 'react-native-progress-circle';
|
|
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';
|
|
import RNFS from 'react-native-fs';
|
|
import showdown from 'showdown';
|
|
import _ from 'lodash';
|
|
|
|
class FilePage extends React.PureComponent {
|
|
static navigationOptions = {
|
|
title: '',
|
|
};
|
|
|
|
playerBackground = null;
|
|
|
|
scrollView = null;
|
|
|
|
startTime = null;
|
|
|
|
webView = null;
|
|
|
|
converter = null;
|
|
|
|
linkHandlerScript = `(function () {
|
|
window.onclick = function(evt) { evt.preventDefault(); window.ReactNativeWebView.postMessage(evt.target.href); evt.stopPropagation(); }
|
|
}());`;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
attemptAutoGet: false,
|
|
autoOpened: false,
|
|
autoDownloadStarted: false,
|
|
autoPlayMedia: false,
|
|
creditsInputFocused: false,
|
|
downloadButtonShown: false,
|
|
downloadPressed: false,
|
|
didSearchRecommended: false,
|
|
fileViewLogged: false,
|
|
fullscreenMode: false,
|
|
fileGetStarted: false,
|
|
hasCheckedAllResolved: false,
|
|
imageUrls: null,
|
|
isLandscape: false,
|
|
mediaLoaded: false,
|
|
pageSuspended: false,
|
|
relatedContentY: 0,
|
|
sendTipStarted: false,
|
|
showDescription: false,
|
|
showImageViewer: false,
|
|
showWebView: false,
|
|
showTipView: false,
|
|
playbackStarted: false,
|
|
playerBgHeight: 0,
|
|
playerHeight: 0,
|
|
uri: null,
|
|
uriVars: null,
|
|
showRecommended: false,
|
|
stopDownloadConfirmed: false,
|
|
streamingMode: false,
|
|
viewCountFetched: false,
|
|
};
|
|
}
|
|
|
|
didFocusListener;
|
|
|
|
componentWillMount() {
|
|
const { navigation } = this.props;
|
|
// this.didFocusListener = navigation.addListener('didFocus', this.onComponentFocused);
|
|
}
|
|
|
|
onComponentFocused = () => {
|
|
StatusBar.setHidden(false);
|
|
NativeModules.Firebase.setCurrentScreen('File').then(result => {
|
|
const { setPlayerVisible } = this.props;
|
|
|
|
DeviceEventEmitter.addListener('onDownloadAborted', this.handleDownloadAborted);
|
|
DeviceEventEmitter.addListener('onStoragePermissionGranted', this.handleStoragePermissionGranted);
|
|
DeviceEventEmitter.addListener('onStoragePermissionRefused', this.handleStoragePermissionRefused);
|
|
|
|
const { claim, fetchMyClaims, fileInfo, isResolvingUri, resolveUri, navigation } = this.props;
|
|
const { uri, uriVars } = navigation.state.params;
|
|
this.setState({ uri, uriVars });
|
|
|
|
setPlayerVisible(true, uri);
|
|
if (!isResolvingUri && !claim) resolveUri(uri);
|
|
|
|
this.fetchFileInfo(uri, this.props);
|
|
this.fetchCostInfo(uri, this.props);
|
|
|
|
fetchMyClaims();
|
|
|
|
NativeModules.Firebase.track('open_file_page', { uri: uri });
|
|
NativeModules.UtilityModule.keepAwakeOn();
|
|
});
|
|
};
|
|
|
|
componentDidMount() {
|
|
this.onComponentFocused();
|
|
}
|
|
|
|
difference = (object, base) => {
|
|
function changes(object, base) {
|
|
return _.transform(object, function(result, value, key) {
|
|
if (!_.isEqual(value, base[key])) {
|
|
result[key] = _.isObject(value) && _.isObject(base[key]) ? changes(value, base[key]) : value;
|
|
}
|
|
});
|
|
}
|
|
return changes(object, base);
|
|
};
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
const {
|
|
claim,
|
|
currentRoute,
|
|
failedPurchaseUris: prevFailedPurchaseUris,
|
|
fetchViewCount,
|
|
purchasedUris: prevPurchasedUris,
|
|
purchaseUriErrorMessage: prevPurchaseUriErrorMessage,
|
|
navigation,
|
|
contentType,
|
|
notify,
|
|
drawerStack: prevDrawerStack,
|
|
} = this.props;
|
|
const {
|
|
currentRoute: prevRoute,
|
|
failedPurchaseUris,
|
|
fileInfo,
|
|
purchasedUris,
|
|
purchaseUriErrorMessage,
|
|
streamingUrl,
|
|
drawerStack,
|
|
resolveUris,
|
|
} = nextProps;
|
|
const uri = this.getPurchaseUrl();
|
|
|
|
if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) {
|
|
this.onComponentFocused();
|
|
}
|
|
|
|
if (failedPurchaseUris.includes(uri) && prevPurchaseUriErrorMessage !== purchaseUriErrorMessage) {
|
|
if (purchaseUriErrorMessage && purchaseUriErrorMessage.trim().length > 0) {
|
|
notify({ message: purchaseUriErrorMessage, isError: true });
|
|
}
|
|
this.setState({ downloadPressed: false, fileViewLogged: false, mediaLoaded: false, showRecommended: true });
|
|
}
|
|
|
|
const mediaType = Lbry.getMediaType(contentType);
|
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
|
if (this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) {
|
|
const { permanent_url: permanentUrl, nout, txid } = claim;
|
|
const outpoint = `${txid}:${nout}`;
|
|
if (this.state.fileGetStarted) {
|
|
NativeModules.UtilityModule.queueDownload(outpoint);
|
|
this.setState({ fileGetStarted: false });
|
|
}
|
|
|
|
if (purchasedUris.includes(uri) || purchasedUris.includes(permanentUrl)) {
|
|
// If the media is playable, file/view will be done in onPlaybackStarted
|
|
if (!isPlayable && !this.state.fileViewLogged) {
|
|
this.logFileView(uri, claim);
|
|
}
|
|
}
|
|
NativeModules.UtilityModule.checkDownloads();
|
|
}
|
|
|
|
if ((!fileInfo || (fileInfo && !fileInfo.completed)) && !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 });
|
|
}
|
|
}
|
|
|
|
if (
|
|
prevDrawerStack[prevDrawerStack.length - 1].route === Constants.DRAWER_ROUTE_FILE_VIEW &&
|
|
prevDrawerStack.length !== drawerStack.length
|
|
) {
|
|
this.setState({
|
|
downloadPressed: false,
|
|
showImageViewer: false,
|
|
showWebView: false,
|
|
});
|
|
}
|
|
|
|
if (claim && !this.state.viewCountFetched) {
|
|
this.setState({ viewCountFetched: true }, () => fetchViewCount(claim.claim_id));
|
|
}
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
|
const { fileInfo: prevFileInfo } = this.props;
|
|
const { fileInfo } = nextProps;
|
|
|
|
const propKeyTriggers = ['balance', 'viewCount', 'isResolvingUri'];
|
|
const stateKeyTriggers = [
|
|
'downloadPressed',
|
|
'fullscreenMode',
|
|
'mediaLoaded',
|
|
'playerBgHeighht',
|
|
'playerHeight',
|
|
'relatedY',
|
|
'showTipView',
|
|
'showImageViewer',
|
|
'showWebView',
|
|
'showDescription',
|
|
'showRecommended',
|
|
'uri',
|
|
];
|
|
for (let i = 0; i < propKeyTriggers.length; i++) {
|
|
const key = propKeyTriggers[i];
|
|
if (this.props[key] !== nextProps[key]) {
|
|
return true;
|
|
}
|
|
}
|
|
for (let i = 0; i < stateKeyTriggers.length; i++) {
|
|
const key = stateKeyTriggers[i];
|
|
if (this.state[key] !== nextState[key]) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!prevFileInfo && fileInfo) {
|
|
return true;
|
|
}
|
|
if (prevFileInfo && fileInfo && Object.keys(this.difference(fileInfo, prevFileInfo)).length > 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
const { claim, contentType, costInfo, fileInfo, isResolvingUri, resolveUri, navigation, title } = 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,
|
|
};
|
|
}
|
|
|
|
// attempt to retrieve images and html/text automatically once the claim is loaded, and it's free
|
|
const mediaType = Lbry.getMediaType(contentType);
|
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
|
const isViewable = mediaType === 'image' || mediaType === 'text';
|
|
if (claim && costInfo && costInfo.cost === 0 && !this.state.autoGetAttempted && isViewable) {
|
|
this.setState({ autoGetAttempted: true }, () => this.checkStoragePermissionForDownload());
|
|
}
|
|
|
|
if (((costInfo && costInfo.cost > 0) || !isPlayable) && (!fileInfo && !isViewable) && !this.state.showRecommended) {
|
|
this.setState({ showRecommended: true });
|
|
}
|
|
|
|
if (
|
|
!fileInfo &&
|
|
!this.state.autoDownloadStarted &&
|
|
claim &&
|
|
costInfo &&
|
|
costInfo.cost === 0 &&
|
|
(isPlayable || (this.state.uriVars && this.state.uriVars.download === 'true'))
|
|
) {
|
|
this.setState({ autoDownloadStarted: true }, () => {
|
|
if (!isPlayable) {
|
|
this.checkStoragePermissionForDownload();
|
|
} else {
|
|
this.confirmPurchaseUri(claim.permanent_url, costInfo, !isPlayable);
|
|
}
|
|
NativeModules.UtilityModule.checkDownloads();
|
|
});
|
|
}
|
|
}
|
|
|
|
fetchFileInfo(uri, props) {
|
|
if (props.fileInfo === undefined) {
|
|
props.fetchFileInfo(uri);
|
|
}
|
|
}
|
|
|
|
fetchCostInfo(uri, props) {
|
|
if (props.costInfo === undefined) {
|
|
props.fetchCostInfo(uri);
|
|
}
|
|
}
|
|
|
|
handleFullscreenToggle = isFullscreen => {
|
|
const { toggleFullscreenMode } = this.props;
|
|
toggleFullscreenMode(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();
|
|
|
|
// show the navigation bar (on devices that have the soft navigation bar)
|
|
NativeModules.UtilityModule.showNavigationBar();
|
|
}
|
|
|
|
this.setState({ fullscreenMode: isFullscreen });
|
|
StatusBar.setHidden(isFullscreen);
|
|
};
|
|
|
|
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;
|
|
const purchaseUrl = this.getPurchaseUrl();
|
|
|
|
deleteFile(`${claim.txid}:${claim.nout}`, true);
|
|
deletePurchasedUri(uri);
|
|
|
|
NativeModules.UtilityModule.deleteDownload(normalizeURI(purchaseUrl));
|
|
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 = this.getPurchaseUrl();
|
|
stopDownload(uri, fileInfo);
|
|
deletePurchasedUri(uri);
|
|
NativeModules.UtilityModule.deleteDownload(normalizeURI(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;
|
|
}
|
|
DeviceEventEmitter.removeListener('onDownloadAborted', this.handleDownloadAborted);
|
|
DeviceEventEmitter.removeListener('onStoragePermissionGranted', this.handleStoragePermissionGranted);
|
|
DeviceEventEmitter.removeListener('onStoragePermissionRefused', this.handleStoragePermissionRefused);
|
|
}
|
|
|
|
handleDownloadAborted = evt => {
|
|
const { deletePurchasedUri, fileInfo, stopDownload } = this.props;
|
|
const { uri, outpoint } = evt;
|
|
const purchaseUrl = normalizeURI(this.getPurchaseUrl());
|
|
if (purchaseUrl === uri) {
|
|
stopDownload(uri, fileInfo);
|
|
deletePurchasedUri(uri);
|
|
NativeModules.UtilityModule.deleteDownload(normalizeURI(uri));
|
|
|
|
this.setState({
|
|
downloadPressed: false,
|
|
fileViewLogged: false,
|
|
mediaLoaded: false,
|
|
stopDownloadConfirmed: true,
|
|
});
|
|
}
|
|
};
|
|
|
|
handleStoragePermissionGranted = () => {
|
|
// permission was allowed. proceed to download
|
|
const { notify } = this.props;
|
|
|
|
// update the configured download folder and then download
|
|
NativeModules.UtilityModule.getDownloadDirectory().then(downloadDirectory => {
|
|
Lbry.settings_set({
|
|
key: 'download_dir',
|
|
value: downloadDirectory,
|
|
})
|
|
.then(() => this.performDownload())
|
|
.catch(() => {
|
|
notify({ message: __('The file could not be downloaded to the default download directory.'), isError: true });
|
|
});
|
|
});
|
|
};
|
|
|
|
handleStoragePermissionRefused = () => {
|
|
const { notify } = this.props;
|
|
this.setState({ downloadPressed: false });
|
|
notify({
|
|
message: __('The file could not be downloaded because the permission to write to storage was not granted.'),
|
|
isError: true,
|
|
});
|
|
};
|
|
|
|
localUriForFileInfo = fileInfo => {
|
|
if (!fileInfo) {
|
|
return null;
|
|
}
|
|
return 'file://' + fileInfo.download_path;
|
|
};
|
|
|
|
playerUriForFileInfo = fileInfo => {
|
|
const { streamingUrl } = this.props;
|
|
if (!this.state.streamingMode && fileInfo && fileInfo.download_path && fileInfo.completed) {
|
|
// take streamingMode in the state into account because if the download completes while
|
|
// the media is already streaming, it will restart from the beginning
|
|
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);
|
|
|
|
// only fetch recommended content after playback has started
|
|
this.setState({ playbackStarted: true, showRecommended: true });
|
|
};
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
renderTags = tags => {
|
|
const { navigation } = this.props;
|
|
return tags.map((tag, i) => (
|
|
<Tag style={filePageStyle.tagItem} key={`${tag}-${i}`} name={tag} navigation={navigation} />
|
|
));
|
|
};
|
|
|
|
confirmPurchaseUri = (uri, costInfo, download) => {
|
|
const { notify, purchaseUri, title } = this.props;
|
|
if (!costInfo) {
|
|
notify({ message: __('This content cannot be viewed at this time. Please try again in a bit.'), isError: true });
|
|
this.setState({ downloadPressed: false });
|
|
this.fetchCostInfo(uri, this.props);
|
|
return;
|
|
}
|
|
|
|
const { cost } = costInfo;
|
|
if (costInfo.cost > 0) {
|
|
Alert.alert(
|
|
__('Confirm Purchase'),
|
|
__(
|
|
cost === 1
|
|
? 'This will purchase "%title%" for %amount% credit'
|
|
: 'This will purchase "%title%" for %amount% credits',
|
|
{ title, amount: cost },
|
|
),
|
|
[
|
|
{
|
|
text: __('OK'),
|
|
onPress: () => purchaseUri(uri, costInfo, download),
|
|
},
|
|
{ text: __('Cancel') },
|
|
],
|
|
);
|
|
} else {
|
|
// Free content. Just call purchaseUri directly.
|
|
purchaseUri(uri, costInfo, download);
|
|
}
|
|
};
|
|
|
|
onFileDownloadButtonPressed = () => {
|
|
const { claim, costInfo, contentType, purchaseUri, setPlayerVisible } = this.props;
|
|
const mediaType = Lbry.getMediaType(contentType);
|
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
|
const isViewable = mediaType === 'image' || mediaType === 'text';
|
|
|
|
const purchaseUrl = this.getPurchaseUrl();
|
|
NativeModules.Firebase.track('purchase_uri', { uri: purchaseUrl });
|
|
|
|
if (!isPlayable) {
|
|
this.onDownloadPressed();
|
|
} else {
|
|
this.confirmPurchaseUri(purchaseUrl, costInfo, !isPlayable);
|
|
}
|
|
|
|
if (isPlayable) {
|
|
this.startTime = Date.now();
|
|
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
|
|
}
|
|
if (isViewable) {
|
|
this.setState({ downloadPressed: true });
|
|
}
|
|
};
|
|
|
|
getPurchaseUrl = () => {
|
|
const { claim, navigation } = this.props;
|
|
const permanentUrl = claim ? claim.permanent_url : null;
|
|
|
|
let purchaseUrl;
|
|
if (navigation.state.params) {
|
|
const { uri, fullUri } = navigation.state.params;
|
|
purchaseUrl = fullUri || permanentUrl || uri;
|
|
}
|
|
if (!purchaseUrl && permanentUrl) {
|
|
purchaseUrl = permanentUrl;
|
|
}
|
|
|
|
return purchaseUrl;
|
|
};
|
|
|
|
onDownloadPressed = () => {
|
|
const { claim, title } = this.props;
|
|
const fileSize = claim && claim.value && claim.value.source ? claim.value.source.size : 0;
|
|
Alert.alert(
|
|
__('Download file'),
|
|
fileSize > 0
|
|
? __('Save "%title%" (%size%) to your device', { title, size: formatBytes(fileSize, 0) })
|
|
: __('Save "%title%" to your device', { title }),
|
|
[
|
|
{ text: __('No') },
|
|
{
|
|
text: __('Yes'),
|
|
onPress: () => {
|
|
this.checkStoragePermissionForDownload();
|
|
},
|
|
},
|
|
],
|
|
{ cancelable: true },
|
|
);
|
|
};
|
|
|
|
checkStoragePermissionForDownload = () => {
|
|
// check if we the permission to write to external storage has been granted
|
|
NativeModules.UtilityModule.canReadWriteStorage().then(canReadWrite => {
|
|
if (!canReadWrite) {
|
|
// request permission
|
|
NativeModules.UtilityModule.requestStoragePermission();
|
|
} else {
|
|
this.performDownload();
|
|
}
|
|
});
|
|
};
|
|
|
|
performDownload = () => {
|
|
const { claim, costInfo, fileGet, fileInfo, purchasedUris } = this.props;
|
|
this.setState(
|
|
{
|
|
downloadPressed: true,
|
|
autoPlayMedia: false,
|
|
stopDownloadConfirmed: false,
|
|
},
|
|
() => {
|
|
const url = this.getPurchaseUrl();
|
|
if (fileInfo || purchasedUris.includes(url)) {
|
|
// file already in library or URI already purchased, use fileGet directly
|
|
this.setState({ fileGetStarted: true }, () => fileGet(url, true));
|
|
} else {
|
|
this.confirmPurchaseUri(url, costInfo, true);
|
|
}
|
|
NativeModules.UtilityModule.checkDownloads();
|
|
},
|
|
);
|
|
};
|
|
|
|
onBackButtonPressed = () => {
|
|
const { navigation, drawerStack, popDrawerStack, setPlayerVisible } = this.props;
|
|
navigateBack(navigation, drawerStack, popDrawerStack, setPlayerVisible);
|
|
};
|
|
|
|
onOpenFilePressed = () => {
|
|
const { contentType, fileInfo, notify } = this.props;
|
|
const localFileUri = this.localUriForFileInfo(fileInfo);
|
|
const mediaType = Lbry.getMediaType(contentType);
|
|
const isViewable = mediaType === 'image' || mediaType === 'text';
|
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
|
if (isViewable) {
|
|
this.openFile(localFileUri, mediaType, contentType);
|
|
} else if (isPlayable) {
|
|
notify({ message: __('Please press the Play button.') });
|
|
} else {
|
|
notify({ message: __('This file cannot be displayed in the LBRY app.') });
|
|
}
|
|
};
|
|
|
|
openFile = (localFileUri, mediaType, contentType) => {
|
|
const { pushDrawerStack } = this.props;
|
|
const isWebViewable = mediaType === 'text';
|
|
|
|
if (mediaType === 'image') {
|
|
// use image viewer
|
|
if (!this.state.showImageViewer) {
|
|
this.setState(
|
|
{
|
|
imageUrls: [
|
|
{
|
|
url: localFileUri,
|
|
},
|
|
],
|
|
showImageViewer: true,
|
|
showRecommended: true,
|
|
},
|
|
() => pushDrawerStack(Constants.DRAWER_ROUTE_FILE_VIEW),
|
|
);
|
|
}
|
|
}
|
|
if (isWebViewable) {
|
|
// show webview
|
|
if (!this.state.showWebView) {
|
|
this.setState(
|
|
{
|
|
showWebView: true,
|
|
showRecommended: true,
|
|
},
|
|
() => {
|
|
pushDrawerStack(Constants.DRAWER_ROUTE_FILE_VIEW);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
handleWebViewLoad = () => {
|
|
const { contentType, fileInfo } = this.props;
|
|
const localFileUri = this.localUriForFileInfo(fileInfo);
|
|
if (this.webView && ['text/markdown', 'text/md'].includes(contentType)) {
|
|
RNFS.readFile(localFileUri, 'utf8').then(markdown => {
|
|
if (this.webView) {
|
|
if (!this.converter) {
|
|
this.converter = new showdown.Converter();
|
|
}
|
|
const html = this.converter.makeHtml(markdown);
|
|
this.webView.injectJavaScript(
|
|
'document.getElementById("content").innerHTML = \'' +
|
|
html.replace(/\n/g, '').replace(/'/g, "\\'") +
|
|
"'; true;",
|
|
);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
handleWebViewMessage = evt => {
|
|
const href = evt.nativeEvent.data;
|
|
if (href && href.startsWith('http')) {
|
|
Linking.openURL(href);
|
|
}
|
|
};
|
|
|
|
buildWebViewSource = () => {
|
|
const { contentType, fileInfo } = this.props;
|
|
const localFileUri = this.localUriForFileInfo(fileInfo);
|
|
|
|
if (['text/markdown', 'text/md'].includes(contentType)) {
|
|
let fontdecl = '';
|
|
if (Platform.OS === 'android') {
|
|
fontdecl = `
|
|
@font-face {
|
|
font-family: 'Inter';
|
|
src: url('file:///android_asset/fonts/Inter-Regular.otf');
|
|
font-weight: normal;
|
|
}
|
|
@font-face {
|
|
font-family: 'Inter;
|
|
src: url('file:///android_asset/fonts/Inter-Bold.otf');
|
|
font-weight: bold;
|
|
}
|
|
`;
|
|
}
|
|
|
|
const html = `
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<meta name="viewport" content="width=device-width, user-scalable=no"/>
|
|
<style type="text/css">
|
|
${fontdecl}
|
|
body { font-family: 'Inter', sans-serif; margin: 16px }
|
|
img { width: 100%; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="content"></div>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
return { html };
|
|
}
|
|
|
|
return { uri: localFileUri };
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
balance,
|
|
claim,
|
|
channels,
|
|
channelUri,
|
|
costInfo,
|
|
fileInfo,
|
|
metadata,
|
|
contentType,
|
|
tab,
|
|
rewardedContentClaimIds,
|
|
isPlayerVisible,
|
|
isResolvingUri,
|
|
blackListedOutpoints,
|
|
myClaimUris,
|
|
navigation,
|
|
position,
|
|
purchaseUri,
|
|
pushDrawerStack,
|
|
setPlayerVisible,
|
|
thumbnail,
|
|
title,
|
|
viewCount,
|
|
} = 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.pageContainer}>
|
|
<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>
|
|
);
|
|
}
|
|
|
|
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) {
|
|
innerContent = (
|
|
<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>
|
|
);
|
|
}
|
|
|
|
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 isWebViewable = mediaType === 'text';
|
|
const { height, signing_channel: signingChannel, value } = claim;
|
|
const channelName = signingChannel && signingChannel.name;
|
|
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
|
|
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 canOpen = isViewable && completed;
|
|
const localFileUri = this.localUriForFileInfo(fileInfo);
|
|
const unsupported = !isPlayable && !canOpen;
|
|
|
|
if (this.state.downloadPressed && canOpen && !this.state.autoOpened) {
|
|
// automatically open a web viewable or image file after the download button is pressed
|
|
this.setState({ autoOpened: true }, () => this.openFile(localFileUri, mediaType, contentType));
|
|
}
|
|
|
|
if (isChannel) {
|
|
return <ChannelPage uri={uri} navigation={navigation} />;
|
|
}
|
|
|
|
return (
|
|
<View style={filePageStyle.pageContainer}>
|
|
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
|
|
{innerContent}
|
|
{this.state.showWebView && isWebViewable && (
|
|
<WebView
|
|
ref={ref => {
|
|
this.webView = ref;
|
|
}}
|
|
allowFileAccess
|
|
javaScriptEnabled
|
|
originWhiteList={['*']}
|
|
source={this.buildWebViewSource()}
|
|
style={filePageStyle.viewer}
|
|
onLoad={this.handleWebViewLoad}
|
|
injectedJavaScript={this.linkHandlerScript}
|
|
onMessage={this.handleWebViewMessage}
|
|
/>
|
|
)}
|
|
{this.state.showImageViewer && (
|
|
<ImageViewer
|
|
style={StyleSheet.flatten(filePageStyle.viewer)}
|
|
imageUrls={this.state.imageUrls}
|
|
renderIndicator={() => null}
|
|
/>
|
|
)}
|
|
{!innerContent && !this.state.showWebView && (
|
|
<View
|
|
style={
|
|
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
|
|
}
|
|
onLayout={this.checkOrientation}
|
|
>
|
|
<TouchableOpacity
|
|
activeOpacity={0.5}
|
|
style={filePageStyle.mediaContainer}
|
|
onPress={this.onFileDownloadButtonPressed}
|
|
>
|
|
{(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)) && (
|
|
<FileDownloadButton
|
|
uri={claim && claim.permanent_url ? claim.permanent_url : uri}
|
|
style={filePageStyle.downloadButton}
|
|
openFile={() => this.openFile(localFileUri, mediaType, contentType)}
|
|
isPlayable={isPlayable}
|
|
isViewable={isViewable}
|
|
onFileActionPress={this.onFileDownloadButtonPressed}
|
|
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
|
|
/>
|
|
)}
|
|
{!fileInfo && (
|
|
<FilePrice
|
|
uri={claim && claim.permanent_url ? claim.permanent_url : uri}
|
|
style={filePageStyle.filePriceContainer}
|
|
textStyle={filePageStyle.filePriceText}
|
|
iconStyle={filePageStyle.filePriceIcon}
|
|
/>
|
|
)}
|
|
|
|
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
|
|
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
|
|
</TouchableOpacity>
|
|
</TouchableOpacity>
|
|
{!innerContent && (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 });
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
{!innerContent &&
|
|
(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) &&
|
|
this.state.fullscreenMode && <View style={fsPlayerBgStyle} />}
|
|
{isPlayerVisible &&
|
|
!innerContent &&
|
|
(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
|
|
<MediaPlayer
|
|
claim={claim}
|
|
assignPlayer={ref => {
|
|
this.player = ref;
|
|
}}
|
|
uri={uri}
|
|
source={this.playerUriForFileInfo(fileInfo)}
|
|
style={playerStyle}
|
|
autoPlay
|
|
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}
|
|
/>
|
|
)}
|
|
|
|
{!innerContent && (
|
|
<ScrollView
|
|
style={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.titleArea}>
|
|
<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>
|
|
<Text style={filePageStyle.viewCount}>
|
|
{viewCount === 1 && __('%view% view', { view: viewCount })}
|
|
{viewCount > 1 && __('%view% views', { view: viewCount })}
|
|
</Text>
|
|
</View>
|
|
</TouchableWithoutFeedback>
|
|
|
|
<View style={filePageStyle.largeButtonsRow}>
|
|
<TouchableOpacity style={filePageStyle.largeButton} onPress={this.handleSharePress}>
|
|
<Icon name={'share-alt'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Share')}</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={filePageStyle.largeButton}
|
|
onPress={() => this.setState({ showTipView: true })}
|
|
>
|
|
<Icon name={'gift'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Tip')}</Text>
|
|
</TouchableOpacity>
|
|
|
|
{!canEdit && (
|
|
<View style={filePageStyle.sharedLargeButton}>
|
|
{!this.state.downloadPressed &&
|
|
(!fileInfo || !fileInfo.download_path || (fileInfo.written_bytes <= 0 && !completed)) && (
|
|
<TouchableOpacity style={filePageStyle.innerLargeButton} onPress={this.onDownloadPressed}>
|
|
<Icon name={'download'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Download')}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
{this.state.downloadPressed && (!fileInfo || fileInfo.written_bytes === 0) && (
|
|
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
|
|
)}
|
|
|
|
{!completed &&
|
|
fileInfo &&
|
|
fileInfo.written_bytes > 0 &&
|
|
fileInfo.written_bytes < fileInfo.total_bytes &&
|
|
!this.state.stopDownloadConfirmed && (
|
|
<TouchableOpacity style={filePageStyle.innerLargeButton} onPress={this.onStopDownloadPressed}>
|
|
<ProgressCircle
|
|
percent={(fileInfo.written_bytes / fileInfo.total_bytes) * 100}
|
|
radius={9}
|
|
borderWidth={2}
|
|
shadowColor={Colors.ActionGrey}
|
|
color={Colors.NextLbryGreen}
|
|
>
|
|
<Icon name={'stop'} size={6} style={filePageStyle.largeButtonIcon} />
|
|
</ProgressCircle>
|
|
<Text style={filePageStyle.largeButtonText}>{__('Stop')}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
{completed && fileInfo && (
|
|
<TouchableOpacity style={filePageStyle.innerLargeButton} onPress={this.onOpenFilePressed}>
|
|
<Icon name={'folder-open'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Open')}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{!canEdit && (
|
|
<TouchableOpacity
|
|
style={filePageStyle.largeButton}
|
|
onPress={() => Linking.openURL(`https://lbry.com/dmca/${claim.claim_id}`)}
|
|
>
|
|
<Icon name={'flag'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Report')}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
{canEdit && (
|
|
<TouchableOpacity style={filePageStyle.largeButton} onPress={this.onEditPressed}>
|
|
<Icon name={'edit'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Edit')}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
|
|
{(completed || canEdit) && (
|
|
<TouchableOpacity style={filePageStyle.largeButton} onPress={this.onDeletePressed}>
|
|
<Icon name={'trash-alt'} size={16} style={filePageStyle.largeButtonIcon} />
|
|
<Text style={filePageStyle.largeButtonText}>{__('Delete')}</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,
|
|
setPlayerVisible,
|
|
);
|
|
}}
|
|
/>
|
|
)}
|
|
{!channelName && (
|
|
<Text style={filePageStyle.anonChannelName} selectable ellipsizeMode={'tail'}>
|
|
{__('Anonymous')}
|
|
</Text>
|
|
)}
|
|
<DateTime
|
|
style={filePageStyle.publishDate}
|
|
textStyle={filePageStyle.publishDateText}
|
|
uri={fullUri}
|
|
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
|
|
show={DateTime.SHOW_DATE}
|
|
/>
|
|
</View>
|
|
<View style={filePageStyle.subscriptionRow}>
|
|
{channelName && (
|
|
<SubscribeButton
|
|
style={filePageStyle.actionButton}
|
|
uri={fullChannelUri}
|
|
name={channelName}
|
|
hideText={false}
|
|
/>
|
|
)}
|
|
{false && channelName && (
|
|
<SubscribeNotificationButton
|
|
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
|
|
uri={fullChannelUri}
|
|
name={channelName}
|
|
/>
|
|
)}
|
|
</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} />
|
|
|
|
{this.state.showRecommended && (
|
|
<RelatedContent
|
|
navigation={navigation}
|
|
claimId={claim.claim_id}
|
|
title={title}
|
|
uri={fullUri}
|
|
fullUri={fullUri}
|
|
/>
|
|
)}
|
|
</ScrollView>
|
|
)}
|
|
</View>
|
|
)}
|
|
{this.state.showTipView && (
|
|
<ModalTipView
|
|
claim={claim}
|
|
channelName={channelName}
|
|
contentName={title}
|
|
onCancelPress={() => this.setState({ showTipView: false })}
|
|
onOverlayPress={() => this.setState({ showTipView: false })}
|
|
onSendTipSuccessful={() => this.setState({ showTipView: false })}
|
|
/>
|
|
)}
|
|
{!this.state.fullscreenMode &&
|
|
!this.state.showTipView &&
|
|
!this.state.showImageViewer &&
|
|
!this.state.showWebView && <FloatingWalletBalance navigation={navigation} />}
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default FilePage;
|