diff --git a/src/component/AppNavigator.js b/src/component/AppNavigator.js index a28da3c..9c83ca0 100644 --- a/src/component/AppNavigator.js +++ b/src/component/AppNavigator.js @@ -51,6 +51,7 @@ import { selectHashChanged, selectUser, } from 'lbryinc'; +import { doStartDownload, doUpdateDownload, doCompleteDownload } from 'redux/actions/file'; import { makeSelectClientSetting, selectFullscreenMode } from 'redux/selectors/settings'; import { decode as atob } from 'base-64'; import { dispatchNavigateBack, dispatchNavigateToUri, transformUrl } from 'utils/helper'; @@ -311,6 +312,10 @@ class AppWithNavigationState extends React.Component { this.emailVerifyCheckInterval = setInterval(() => this.checkEmailVerification(), 5000); Linking.addEventListener('url', this._handleUrl); + DeviceEventEmitter.addListener('onDownloadStarted', this.handleDownloadStarted); + DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated); + DeviceEventEmitter.addListener('onDownloadCompleted', this.handleDownloadCompleted); + // call /sync/get with interval this.syncGetInterval = setInterval(() => { this.setState({ syncHashChanged: false }); // reset local state @@ -345,7 +350,29 @@ class AppWithNavigationState extends React.Component { ); }; + handleDownloadStarted = evt => { + const { dispatch } = this.props; + const { uri, outpoint, fileInfo } = evt; + dispatch(doStartDownload(uri, outpoint, fileInfo)); + }; + + handleDownloadUpdated = evt => { + const { dispatch } = this.props; + const { uri, outpoint, fileInfo, progress } = evt; + dispatch(doUpdateDownload(uri, outpoint, fileInfo, progress)); + }; + + handleDownloadCompleted = evt => { + const { dispatch } = this.props; + const { uri, outpoint, fileInfo } = evt; + dispatch(doCompleteDownload(uri, outpoint, fileInfo)); + }; + componentWillUnmount() { + DeviceEventEmitter.removeListener('onDownloadStarted', this.handleDownloadStarted); + DeviceEventEmitter.removeListener('onDownloadUpdated', this.handleDownloadUpdated); + DeviceEventEmitter.removeListener('onDownloadCompleted', this.handleDownloadCompleted); + AppState.removeEventListener('change', this._handleAppStateChange); BackHandler.removeEventListener('hardwareBackPress'); Linking.removeEventListener('url', this._handleUrl); diff --git a/src/component/fileDownloadButton/view.js b/src/component/fileDownloadButton/view.js index d2f7ae8..cbb77b3 100644 --- a/src/component/fileDownloadButton/view.js +++ b/src/component/fileDownloadButton/view.js @@ -11,11 +11,6 @@ class FileDownloadButton extends React.PureComponent { } } - componentWillReceiveProps(nextProps) { - // this.checkAvailability(nextProps.uri); - // this.restartDownload(nextProps); - } - restartDownload(props) { const { downloading, fileInfo, uri, restartDownload } = props; @@ -49,9 +44,6 @@ class FileDownloadButton extends React.PureComponent { onButtonLayout, } = this.props; - console.log('uri=' + uri); - console.log(fileInfo); - if ((fileInfo && !fileInfo.stopped) || loading || downloading) { const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0, label = fileInfo ? __('%progress%% complete', { progress: progress.toFixed(0) }) : __('Connecting...'); diff --git a/src/component/fileListItem/view.js b/src/component/fileListItem/view.js index 7cb4c7c..a5f9664 100644 --- a/src/component/fileListItem/view.js +++ b/src/component/fileListItem/view.js @@ -158,7 +158,13 @@ class FileListItem extends React.PureComponent { )} {fileInfo && fileInfo.completed && fileInfo.download_path && ( - + )} {featuredResult && ( diff --git a/src/component/relatedContent/view.js b/src/component/relatedContent/view.js index 4e0630d..8f626f8 100644 --- a/src/component/relatedContent/view.js +++ b/src/component/relatedContent/view.js @@ -8,18 +8,11 @@ import fileListStyle from 'styles/fileList'; import relatedContentStyle from 'styles/relatedContent'; export default class RelatedContent extends React.PureComponent { - state = { - urlsResolved: false, - }; - - componentDidUpdate(prevProps) { + componentDidMount() { const { resolveUris, recommendedContent } = this.props; - - if (recommendedContent && recommendedContent.length > 0 && !this.state.urisResolved) { - this.setState({ urisResolved: true }, () => { - // batch resolve the uris - resolveUris(recommendedContent); - }); + if (recommendedContent && recommendedContent.length > 0) { + // batch resolve the uris + resolveUris(recommendedContent); } } diff --git a/src/index.js b/src/index.js index 74b01f0..c3cf079 100644 --- a/src/index.js +++ b/src/index.js @@ -57,8 +57,7 @@ window.__ = __; const globalExceptionHandler = (error, isFatal) => { if (error && NativeModules.Firebase) { - console.log(error); - NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error)); + NativeModules.Firebase.logException(!!isFatal, error.message ? error.message : 'No message', JSON.stringify(error)); } }; setJSExceptionHandler(globalExceptionHandler, true); diff --git a/src/page/file/index.js b/src/page/file/index.js index 263b305..36dd480 100644 --- a/src/page/file/index.js +++ b/src/page/file/index.js @@ -8,6 +8,7 @@ import { doPurchaseUri, doDeletePurchasedUri, doResolveUri, + doResolveUris, doSearch, doSendTip, doToast, @@ -28,6 +29,7 @@ import { selectPurchasedUris, selectFailedPurchaseUris, selectPurchaseUriErrorMessage, + selectResolvingUris, selectIsSearching, } from 'lbry-redux'; import { @@ -39,13 +41,7 @@ import { selectRewardContentClaimIds, selectBlackListedOutpoints, } from 'lbryinc'; -import { - doStartDownload, - doUpdateDownload, - doCompleteDownload, - doDeleteFile, - doStopDownloadingFile, -} from 'redux/actions/file'; +import { doDeleteFile, doStopDownloadingFile } from 'redux/actions/file'; import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; import { doToggleFullscreenMode } from 'redux/actions/settings'; import { selectDrawerStack } from 'redux/selectors/drawer'; @@ -77,6 +73,7 @@ const select = (state, props) => { thumbnail: makeSelectThumbnailForUri(contentUri)(state), title: makeSelectTitleForUri(contentUri)(state), recommendedContent: makeSelectRecommendedContentForUri(contentUri)(state), + resolvingUris: selectResolvingUris(state), isSearchingRecommendContent: selectIsSearching(state), viewCount: makeSelectViewCountForUri(contentUri)(state), }; @@ -100,14 +97,12 @@ const perform = dispatch => ({ purchaseUri: (uri, costInfo, saveFile) => dispatch(doPurchaseUri(uri, costInfo, saveFile)), deletePurchasedUri: uri => dispatch(doDeletePurchasedUri(uri)), resolveUri: uri => dispatch(doResolveUri(uri)), + resolveUris: uris => dispatch(doResolveUris(uris)), searchRecommended: query => dispatch(doSearch(query, 20, undefined, true)), sendTip: (amount, claimId, isSupport, successCallback, errorCallback) => dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)), setPlayerVisible: () => dispatch(doSetPlayerVisible(true)), stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)), - startDownload: (uri, outpoint, fileInfo) => dispatch(doStartDownload(uri, outpoint, fileInfo)), - updateDownload: (uri, outpoint, fileInfo, progress) => dispatch(doUpdateDownload(uri, outpoint, fileInfo, progress)), - completeDownload: (uri, outpoint, fileInfo) => dispatch(doCompleteDownload(uri, outpoint, fileInfo)), toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)), }); diff --git a/src/page/file/view.js b/src/page/file/view.js index 77ef5fb..0cc1156 100644 --- a/src/page/file/view.js +++ b/src/page/file/view.js @@ -72,6 +72,7 @@ class FilePage extends React.PureComponent { fileViewLogged: false, fullscreenMode: false, fileGetStarted: false, + hasCheckedAllResolved: false, imageUrls: null, isLandscape: false, mediaLoaded: false, @@ -102,17 +103,14 @@ class FilePage extends React.PureComponent { onComponentFocused = () => { StatusBar.setHidden(false); NativeModules.Firebase.setCurrentScreen('File').then(result => { - DeviceEventEmitter.addListener('onDownloadStarted', this.handleDownloadStarted); - DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated); - DeviceEventEmitter.addListener('onDownloadCompleted', this.handleDownloadCompleted); DeviceEventEmitter.addListener('onStoragePermissionGranted', this.handleStoragePermissionGranted); DeviceEventEmitter.addListener('onStoragePermissionRefused', this.handleStoragePermissionRefused); - const { fetchMyClaims, fileInfo, isResolvingUri, resolveUri, navigation } = this.props; + const { claim, fetchMyClaims, fileInfo, isResolvingUri, resolveUri, navigation } = this.props; const { uri, uriVars } = navigation.state.params; this.setState({ uri, uriVars }); - if (!isResolvingUri) resolveUri(uri); + if (!isResolvingUri && !claim) resolveUri(uri); this.fetchFileInfo(this.props); this.fetchCostInfo(this.props); @@ -149,6 +147,7 @@ class FilePage extends React.PureComponent { navigation, contentType, notify, + recommendedContent: prevRecommendedContent, drawerStack: prevDrawerStack, } = this.props; const { uri } = navigation.state.params; @@ -160,6 +159,8 @@ class FilePage extends React.PureComponent { purchaseUriErrorMessage, streamingUrl, drawerStack, + recommendedContent, + resolveUris, } = nextProps; if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) { @@ -175,10 +176,7 @@ class FilePage extends React.PureComponent { const mediaType = Lbry.getMediaType(contentType); const isPlayable = mediaType === 'video' || mediaType === 'audio'; - if ( - (this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) && - NativeModules.UtilityModule - ) { + if (this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) { const { permanent_url: permanentUrl } = claim; if (purchasedUris.includes(uri) || purchasedUris.includes(permanentUrl)) { const { nout, txid } = claim; @@ -215,9 +213,23 @@ class FilePage extends React.PureComponent { if (claim && !this.state.viewCountFetched) { this.setState({ viewCountFetched: true }, () => fetchViewCount(claim.claim_id)); } + + if ( + (!prevRecommendedContent && recommendedContent) || + (recommendedContent && prevRecommendedContent && recommendedContent.length !== prevRecommendedContent.length) + ) { + resolveUris(recommendedContent); + } } - componentDidUpdate(prevProps) { + shouldComponentUpdate(nextProps, nextState) { + return ( + Object.keys(this.difference(nextProps, this.props)).length > 0 || + Object.keys(this.difference(nextState, this.state)).length > 0 + ); + } + + componentDidUpdate(prevProps, prevState) { const { claim, contentType, @@ -227,7 +239,6 @@ class FilePage extends React.PureComponent { resolveUri, navigation, purchaseUri, - searchRecommended, title, } = this.props; const { uri } = this.state; @@ -235,10 +246,6 @@ class FilePage extends React.PureComponent { resolveUri(uri); } - /* if (title && !this.state.didSearchRecommended) { - this.setState({ didSearchRecommended: true }, () => searchRecommended(title)); - } */ - // 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; @@ -390,31 +397,10 @@ class FilePage extends React.PureComponent { } window.player = null; - DeviceEventEmitter.removeListener('onDownloadStarted', this.handleDownloadStarted); - DeviceEventEmitter.removeListener('onDownloadUpdated', this.handleDownloadUpdated); - DeviceEventEmitter.removeListener('onDownloadCompleted', this.handleDownloadCompleted); DeviceEventEmitter.removeListener('onStoragePermissionGranted', this.handleStoragePermissionGranted); DeviceEventEmitter.removeListener('onStoragePermissionRefused', this.handleStoragePermissionRefused); } - 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); - }; - handleStoragePermissionGranted = () => { // permission was allowed. proceed to download const { notify } = this.props; @@ -538,6 +524,7 @@ class FilePage extends React.PureComponent { }; onPlaybackStarted = () => { + const { searchRecommended, title } = this.props; let timeToStartMillis, timeToStart; if (this.startTime) { timeToStartMillis = Date.now() - this.startTime; @@ -555,6 +542,11 @@ class FilePage extends React.PureComponent { payload['time_to_start_ms'] = timeToStartMillis; } NativeModules.Firebase.track('play', payload); + + // only fetch recommended content after playback has started + if (title) { + searchRecommended(title); + } }; onPlaybackFinished = () => { @@ -748,7 +740,7 @@ class FilePage extends React.PureComponent { let innerContent = null; if ((isResolvingUri && !claim) || !claim) { return ( - + {isResolvingUri && ( @@ -786,442 +778,433 @@ class FilePage extends React.PureComponent { ); } - if (claim) { - if (isChannel) { - return ; - } - - 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; - } + 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 ( - - - - {__( - 'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.' - )} - - - - - - ); - } - - 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 (fileInfo && !this.state.autoDownloadStarted && this.state.uriVars && this.state.uriVars.download === 'true') { - this.setState({ autoDownloadStarted: true }, () => { - if (!isPlayable) { - this.checkStoragePermissionForDownload(); - } else { - purchaseUri(uri, costInfo, !isPlayable); - } - NativeModules.UtilityModule.checkDownloads(); - }); - } - - 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)); - } - - return ( - - {!this.state.fullscreenMode && } - {this.state.showWebView && isWebViewable && ( - - )} - - {this.state.showImageViewer && ( - null} - /> - )} - - {!this.state.showWebView && ( - - - {(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && ( - - )} - {!unsupported && - (!this.state.downloadButtonShown || this.state.downloadPressed) && - !this.state.mediaLoaded && ( - - )} - - {unsupported && fileInfo && completed && ( - - - - {__('Unsupported Content')} - - Sorry, we are unable to display this content in the app. You can find the file named{' '} - {fileInfo.file_name} in your - downloads folder. - - - - )} - - {((isPlayable && !completed && !canLoadMedia) || - canOpen || - (!completed && !this.state.streamingMode)) && ( - this.openFile(localFileUri, mediaType)} - isPlayable={isPlayable} - isViewable={isViewable} - onFileActionPress={this.onFileDownloadButtonPressed} - onButtonLayout={() => this.setState({ downloadButtonShown: true })} - /> - )} - {!fileInfo && ( - - )} - - - - - - {(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && ( - { - 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 && ( - - )} - {(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && ( - { - 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} - /> - )} - - { - this.scrollView = ref; - }} - > - this.setState({ showDescription: !this.state.showDescription })} - > - - - - {title} - - {isRewardContent && } - - - - - - {viewCount === 1 && __('%view% view', { view: viewCount })} - {viewCount > 1 && __('%view% views', { view: viewCount })} - - - - - - - - {__('Share')} - - - this.setState({ showTipView: true })} - > - - {__('Tip')} - - - {!canEdit && !isPlayable && ( - - {!fileInfo || - (fileInfo.written_bytes <= 0 && !completed && ( - - - {__('Download')} - - ))} - - {!completed && - fileInfo && - !fileInfo.stopped && - fileInfo.written_bytes > 0 && - fileInfo.written_bytes < fileInfo.total_bytes && - !this.state.stopDownloadConfirmed && ( - - - {__('Stop')} - - )} - - {completed && fileInfo && fileInfo.written_bytes >= fileInfo.total_bytes && ( - - - {__('Open')} - - )} - - )} - - {!canEdit && ( - Linking.openURL(`https://lbry.com/dmca/${claim.claim_id}`)} - > - - {__('Report')} - - )} - - {canEdit && ( - - - {__('Edit')} - - )} - - {(completed || canEdit) && ( - - - {__('Delete')} - - )} - - - - - {channelName && ( - { - navigateToUri( - navigation, - normalizeURI(shortChannelUri || fullChannelUri), - null, - false, - fullChannelUri - ); - }} - /> - )} - {!channelName && ( - - {__('Anonymous')} - - )} - - - - {false && ((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && ( -