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;