display resolved search results #107

Merged
akinwale merged 3 commits from resolved-search into master 2020-01-05 19:08:36 +01:00
11 changed files with 234 additions and 85 deletions

4
package-lock.json generated
View file

@ -7047,8 +7047,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",

View file

@ -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",

View file

@ -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 && (
<VideoDuration
duration={duration}
style={fileItemMediaStyle.duration}
@ -104,7 +105,7 @@ class FileItemMedia extends React.PureComponent {
.toUpperCase()}
</Text>
)}
{duration && (
{duration > 0 && (
<VideoDuration
duration={duration}
style={fileItemMediaStyle.duration}

View file

@ -63,14 +63,6 @@ class CreditAmount extends React.PureComponent {
}
}
/* {this.props.isEstimate ? (
<span
className="credit-amount__estimate"
title={__('This is an estimate and does not include data fees')}
>
*
</span>
) : null} */
return (
<Text style={style} numberOfLines={1}>
{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;
}

View file

@ -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);

View file

@ -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 (
<View style={style}>
<TouchableOpacity style={style} onPress={this.onPressHandler}>
<FileItemMedia
style={fileListStyle.thumbnail}
duration={duration}
resizeMode="cover"
title={title || name || normalizeURI(url).substring(7)}
thumbnail={thumbnailUrl}
/>
{fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon
style={featuredResult ? fileListStyle.featuredDownloadedIcon : fileListStyle.downloadedIcon}
solid
color={Colors.NextLbryGreen}
name={'folder'}
size={16}
/>
)}
<FilePrice
cost={fee ? parseFloat(fee) / 100000000 : 0}
uri={url}
style={fileListStyle.filePriceContainer}
iconStyle={fileListStyle.filePriceIcon}
textStyle={fileListStyle.filePriceText}
/>
<View style={fileListStyle.detailsContainer}>
{(title || name) && (
<View style={fileListStyle.titleContainer}>
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
{this.formatTitle(title) || this.formatTitle(name)}
</Text>
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
</View>
)}
{hasChannel && (
<Link
style={fileListStyle.publisher}
text={channel}
onPress={() => {
navigateToUri(navigation, normalizeURI(channelUrl), null, false, channelUrl);
}}
/>
)}
<View style={fileListStyle.info}>
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text>{this.getStorageForFileInfo(fileInfo)}</Text>
)}
<DateTime
style={fileListStyle.publishInfo}
textStyle={fileListStyle.infoText}
timeAgo
date={releaseTime}
/>
</View>
{fileInfo && fileInfo.download_path && (
<View style={fileListStyle.downloadInfo}>
{!fileInfo.completed && (
<ProgressBar
borderRadius={3}
color={Colors.NextLbryGreen}
height={3}
style={fileListStyle.progress}
progress={this.getDownloadProgress(fileInfo)}
/>
)}
</View>
)}
</View>
</TouchableOpacity>
{obscure && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
</View>
);
}
}
export default FileResultItem;

View file

@ -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(

View file

@ -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 {
<Text style={relatedContentStyle.title}>{__('Related Content')}</Text>
{isSearching && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
{recommendedContent &&
recommendedContent
.filter(recommendedUri => recommendedUri !== normalizeURI(fullUri))
.map(recommendedUri => (
<FileListItem
style={fileListStyle.item}
key={recommendedUri}
uri={recommendedUri}
batchResolve
navigation={navigation}
autoplay
/>
))}
recommendedContent.map(result => (
<FileResultItem
style={fileListStyle.item}
key={result.claimId}
result={result}
navigation={navigation}
autoplay
/>
))}
</View>
);
}

View file

@ -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,

View file

@ -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)),

View file

@ -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 (
<View style={searchStyle.container}>
@ -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 }) => (
<FileListItem key={item} uri={item} style={searchStyle.resultItem} batchResolve navigation={navigation} />
<FileResultItem key={item.claimId} result={item} style={searchStyle.resultItem} navigation={navigation} />
)}
/>
)}