diff --git a/package-lock.json b/package-lock.json index b632f29..6a18e4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7067,8 +7067,8 @@ } }, "lbry-redux": { - "version": "github:lbryio/lbry-redux#eb8fff0e5afd43be00822b3f476eea9e0eb7b075", - "from": "github:lbryio/lbry-redux#eb8fff0e5afd43be00822b3f476eea9e0eb7b075", + "version": "github:lbryio/lbry-redux#e8f29f1c47b136669df265babb109d2488a37db0", + "from": "github:lbryio/lbry-redux#e8f29f1c47b136669df265babb109d2488a37db0", "requires": { "proxy-polyfill": "0.1.6", "reselect": "^3.0.0", diff --git a/package.json b/package.json index 7120962..b0ed585 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "base-64": "^0.1.0", "@expo/vector-icons": "^8.1.0", "gfycat-style-urls": "^1.0.3", - "lbry-redux": "lbryio/lbry-redux#eb8fff0e5afd43be00822b3f476eea9e0eb7b075", + "lbry-redux": "lbryio/lbry-redux#e8f29f1c47b136669df265babb109d2488a37db0", "lbryinc": "lbryio/lbryinc#053ca52f4f7f9bf8eb62a3581b183671a475ad1c", "lodash": ">=4.17.11", "merge": ">=1.2.1", diff --git a/src/component/fileItemMedia/view.js b/src/component/fileItemMedia/view.js index 346e878..371cef7 100644 --- a/src/component/fileItemMedia/view.js +++ b/src/component/fileItemMedia/view.js @@ -61,6 +61,7 @@ class FileItemMedia extends React.PureComponent { let style = this.props.style; const { duration, isResolvingUri, thumbnail, title, resizeMode } = this.props; const atStyle = this.state.autoThumbStyle; + const hasDuration = !!duration; if (this.isThumbnailValid(thumbnail) && !this.state.imageLoadFailed) { if (style == null) { @@ -75,7 +76,7 @@ class FileItemMedia extends React.PureComponent { resizeMode={this.getFastImageResizeMode(resizeMode)} style={fileItemMediaStyle.image} /> - {duration && ( + {duration > 0 && ( )} - {duration && ( + {duration > 0 && ( - * - - ) : null} */ return ( {amountText} @@ -81,11 +73,17 @@ class CreditAmount extends React.PureComponent { class FilePrice extends React.PureComponent { componentWillMount() { - this.fetchCost(this.props); + const { cost } = this.props; + if (isNaN(parseFloat(cost))) { + this.fetchCost(this.props); + } } componentWillReceiveProps(nextProps) { - this.fetchCost(nextProps); + const { cost } = this.props; + if (isNaN(parseFloat(cost))) { + this.fetchCost(nextProps); + } } fetchCost(props) { @@ -97,11 +95,11 @@ class FilePrice extends React.PureComponent { } render() { - const { costInfo, look = 'indicator', showFullPrice = false, style, iconStyle, textStyle } = this.props; + const { cost, costInfo, look = 'indicator', showFullPrice = false, style, iconStyle, textStyle } = this.props; const isEstimate = costInfo ? !costInfo.includesData : null; - const amount = costInfo ? parseFloat(costInfo.cost) : 0; - if (!costInfo || isNaN(amount) || amount === 0) { + const amount = cost ? parseFloat(cost) : costInfo ? parseFloat(costInfo.cost) : 0; + if (isNaN(amount) || amount === 0) { return null; } diff --git a/src/component/fileResultItem/index.js b/src/component/fileResultItem/index.js new file mode 100644 index 0000000..f1bff41 --- /dev/null +++ b/src/component/fileResultItem/index.js @@ -0,0 +1,40 @@ +import { connect } from 'react-redux'; +import { + doResolveUri, + makeSelectClaimForUri, + makeSelectMetadataForUri, + makeSelectFileInfoForUri, + makeSelectIsUriResolving, + makeSelectClaimIsNsfw, + makeSelectShortUrlForUri, + makeSelectTitleForUri, + makeSelectThumbnailForUri, +} from 'lbry-redux'; +import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc'; +import { selectShowNsfw } from 'redux/selectors/settings'; +import FileResultItem from './view'; + +const select = (state, props) => ({ + blackListedOutpoints: selectBlackListedOutpoints(state), + claim: makeSelectClaimForUri(props.uri)(state), + fileInfo: makeSelectFileInfoForUri(props.uri)(state), + filteredOutpoints: selectFilteredOutpoints(state), + isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state), + metadata: makeSelectMetadataForUri(props.uri)(state), + nsfw: makeSelectClaimIsNsfw(props.uri)(state), + isResolvingUri: makeSelectIsUriResolving(props.uri)(state), + obscureNsfw: !selectShowNsfw(state), + rewardedContentClaimIds: selectRewardContentClaimIds(state), + shortUrl: makeSelectShortUrlForUri(props.uri)(state), + title: makeSelectTitleForUri(props.uri)(state), + thumbnail: makeSelectThumbnailForUri(props.uri)(state), +}); + +const perform = dispatch => ({ + resolveUri: uri => dispatch(doResolveUri(uri)), +}); + +export default connect( + select, + perform, +)(FileResultItem); diff --git a/src/component/fileResultItem/view.js b/src/component/fileResultItem/view.js new file mode 100644 index 0000000..0c0b10e --- /dev/null +++ b/src/component/fileResultItem/view.js @@ -0,0 +1,146 @@ +import React from 'react'; +import { normalizeURI, parseURI } from 'lbry-redux'; +import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native'; +import { navigateToUri, formatBytes } from 'utils/helper'; +import Colors from 'styles/colors'; +import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api +import DateTime from 'component/dateTime'; +import FileItemMedia from 'component/fileItemMedia'; +import FilePrice from 'component/filePrice'; +import Icon from 'react-native-vector-icons/FontAwesome5'; +import Link from 'component/link'; +import NsfwOverlay from 'component/nsfwOverlay'; +import ProgressBar from 'component/progressBar'; +import fileListStyle from 'styles/fileList'; + +class FileResultItem extends React.PureComponent { + getStorageForFileInfo = fileInfo => { + if (!fileInfo.completed) { + const written = formatBytes(fileInfo.written_bytes); + const total = formatBytes(fileInfo.total_bytes); + return `(${written} / ${total})`; + } + + return formatBytes(fileInfo.written_bytes); + }; + + formatTitle = title => { + if (!title) { + return title; + } + + return title.length > 80 ? title.substring(0, 77).trim() + '...' : title; + }; + + getDownloadProgress = fileInfo => { + return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100); + }; + + onPressHandler = () => { + const { autoplay, navigation, result } = this.props; + const { claimId, name } = result; + const url = normalizeURI(`${name}#${claimId}`); + navigateToUri(navigation, url, { autoplay }, false, url); + }; + + render() { + const { featuredResult, fileInfo, navigation, obscureNsfw, result, rewardedContentClaimIds, style } = this.props; + const { + channel, + channel_claim_id: channelClaimId, + claimId, + duration, + fee, + name, + nsfw, + release_time: releaseTime, + thumbnail_url: thumbnailUrl, + title, + } = result; + + const obscure = obscureNsfw && nsfw; + const url = normalizeURI(`${name}#${claimId}`); + const hasChannel = !!channel; + const channelUrl = channel ? normalizeURI(`${channel}#${channelClaimId}`) : null; + const isRewardContent = rewardedContentClaimIds.includes(claimId); + + return ( + + + + {fileInfo && fileInfo.completed && fileInfo.download_path && ( + + )} + + + {(title || name) && ( + + + {this.formatTitle(title) || this.formatTitle(name)} + + {isRewardContent && } + + )} + + {hasChannel && ( + { + navigateToUri(navigation, normalizeURI(channelUrl), null, false, channelUrl); + }} + /> + )} + + + {fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && ( + {this.getStorageForFileInfo(fileInfo)} + )} + + + + {fileInfo && fileInfo.download_path && ( + + {!fileInfo.completed && ( + + )} + + )} + + + {obscure && navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />} + + ); + } +} + +export default FileResultItem; diff --git a/src/component/relatedContent/index.js b/src/component/relatedContent/index.js index 055a36f..5492125 100644 --- a/src/component/relatedContent/index.js +++ b/src/component/relatedContent/index.js @@ -1,24 +1,27 @@ import { connect } from 'react-redux'; import { doResolveUris, - doSearch, + doResolvedSearch, makeSelectClaimForUri, - makeSelectRecommendedContentForUri, + makeSelectResolvedRecommendedContentForUri, selectResolvingUris, selectIsSearching, } from 'lbry-redux'; import RelatedContent from './view'; +const RESULT_SIZE = 16; + const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), isSearching: selectIsSearching(state), - recommendedContent: makeSelectRecommendedContentForUri(props.uri)(state), + recommendedContent: makeSelectResolvedRecommendedContentForUri(props.uri, RESULT_SIZE)(state), resolvingUris: selectResolvingUris(state), }); const perform = dispatch => ({ resolveUris: uris => dispatch(doResolveUris(uris)), - searchRecommended: (query, claimId) => dispatch(doSearch(query, 20, undefined, true, { related_to: claimId }, false)), + searchRecommended: (query, claimId) => + dispatch(doResolvedSearch(query, RESULT_SIZE, undefined, true, { related_to: claimId }, false)), }); export default connect( diff --git a/src/component/relatedContent/view.js b/src/component/relatedContent/view.js index 52993c1..e0830f4 100644 --- a/src/component/relatedContent/view.js +++ b/src/component/relatedContent/view.js @@ -4,14 +4,11 @@ import { normalizeURI } from 'lbry-redux'; import { navigateToUri } from 'utils/helper'; import Colors from 'styles/colors'; import FileListItem from 'component/fileListItem'; +import FileResultItem from 'component/fileResultItem'; import fileListStyle from 'styles/fileList'; import relatedContentStyle from 'styles/relatedContent'; export default class RelatedContent extends React.PureComponent { - state = { - resolveStarted: false, - }; - componentDidMount() { const { title, claimId, searchRecommended } = this.props; if (title && claimId) { @@ -21,32 +18,7 @@ export default class RelatedContent extends React.PureComponent { shouldComponentUpdate(nextProps, nextState) { const { isSearching, recommendedContent } = nextProps; - return isSearching || (!isSearching && this.allContentResolved()); - } - - allContentResolved() { - const { recommendedContent, resolvingUris } = this.props; - if (recommendedContent) { - let allResolved = true; - recommendedContent.forEach(uri => { - allResolved = allResolved && !resolvingUris.includes(uri); - }); - return allResolved; - } - - return false; - } - - componentDidUpdate() { - const { isSearching, resolveUris, recommendedContent } = this.props; - if (!isSearching && !this.state.resolveStarted) { - this.setState({ resolveStarted: true }, () => { - if (recommendedContent && recommendedContent.length > 0) { - // batch resolve the uris - resolveUris(recommendedContent); - } - }); - } + return isSearching || (!isSearching && recommendedContent && recommendedContent.length > 0); } render() { @@ -57,18 +29,15 @@ export default class RelatedContent extends React.PureComponent { {__('Related Content')} {isSearching && } {recommendedContent && - recommendedContent - .filter(recommendedUri => recommendedUri !== normalizeURI(fullUri)) - .map(recommendedUri => ( - - ))} + recommendedContent.map(result => ( + + ))} ); } diff --git a/src/page/file/view.js b/src/page/file/view.js index c752e49..5beb32d 100644 --- a/src/page/file/view.js +++ b/src/page/file/view.js @@ -148,7 +148,6 @@ class FilePage extends React.PureComponent { navigation, contentType, notify, - recommendedContent: prevRecommendedContent, drawerStack: prevDrawerStack, } = this.props; const { uri } = navigation.state.params; @@ -160,7 +159,6 @@ class FilePage extends React.PureComponent { purchaseUriErrorMessage, streamingUrl, drawerStack, - recommendedContent, resolveUris, } = nextProps; @@ -214,13 +212,6 @@ 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); - } } shouldComponentUpdate(nextProps, nextState) { @@ -760,8 +751,6 @@ class FilePage extends React.PureComponent { position, purchaseUri, pushDrawerStack, - isSearchingRecommendContent, - recommendedContent, thumbnail, title, viewCount, diff --git a/src/page/search/index.js b/src/page/search/index.js index c66c512..339a0e4 100644 --- a/src/page/search/index.js +++ b/src/page/search/index.js @@ -2,8 +2,9 @@ import { connect } from 'react-redux'; import { doClaimSearch, doResolveUris, - doSearch, + doResolvedSearch, doUpdateSearchQuery, + makeSelectResolvedSearchResults, makeSelectSearchUris, selectClaimSearchByQuery, selectIsSearching, @@ -25,10 +26,11 @@ const select = state => ({ query: selectSearchValue(state), resolvingUris: selectResolvingUris(state), uris: makeSelectSearchUris(makeSelectQueryWithOptions(null, numSearchResults)(state))(state), + results: makeSelectResolvedSearchResults(makeSelectQueryWithOptions(null, numSearchResults)(state))(state), }); const perform = dispatch => ({ - search: query => dispatch(doSearch(query, numSearchResults, null, false, {}, false)), + search: query => dispatch(doResolvedSearch(query, numSearchResults, null, false, {})), claimSearch: options => dispatch(doClaimSearch(options)), updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)), pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SEARCH)), diff --git a/src/page/search/view.js b/src/page/search/view.js index 7bfcfbf..3982202 100644 --- a/src/page/search/view.js +++ b/src/page/search/view.js @@ -15,6 +15,7 @@ import Colors from 'styles/colors'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import PageHeader from 'component/pageHeader'; import FileListItem from 'component/fileListItem'; +import FileResultItem from 'component/fileResultItem'; import FloatingWalletBalance from 'component/floatingWalletBalance'; import UriBar from 'component/uriBar'; import searchStyle from 'styles/search'; @@ -104,20 +105,20 @@ class SearchPage extends React.PureComponent { return false; } - shouldComponentUpdate(nextProps) { + /* shouldComponentUpdate(nextProps) { const { isSearching, uris } = this.props; return (isSearching && (!uris || uris.length === 0)) || (!isSearching && this.allContentResolved(this.props)); - } + } */ componentDidUpdate() { - const { isSearching, resolveUris, uris } = this.props; + /* const { isSearching, resolveUris, uris } = this.props; if (!isSearching && !this.state.resultsResolved) { this.setState({ resultsResolved: true }, () => { if (uris && uris.length > 0) { resolveUris(uris); } }); - } + } */ } getSearchQuery() { @@ -191,7 +192,7 @@ class SearchPage extends React.PureComponent { }; render() { - const { isSearching, navigation, query, uris } = this.props; + const { isSearching, navigation, query, results } = this.props; return ( @@ -209,15 +210,15 @@ class SearchPage extends React.PureComponent { style={searchStyle.scrollContainer} contentContainerStyle={searchStyle.scrollPadding} keyboardShouldPersistTaps={'handled'} - data={uris} - keyExtractor={(item, index) => item} + data={results} + keyExtractor={(item, index) => item.claimId} initialNumToRender={8} maxToRenderPerBatch={20} removeClippedSubviews ListEmptyComponent={!isSearching ? this.listEmptyComponent() : null} ListHeaderComponent={this.state.currentUri ? this.listHeaderComponent(this.state.showTagResult) : null} renderItem={({ item }) => ( - + )} /> )}