Suggested subscriptions (#423)
* suggested subscriptions implementation * some style and loading tweaks * add subscribe buttons and style tweaks
This commit is contained in:
parent
654d50cd1b
commit
7f6874b791
13 changed files with 285 additions and 47 deletions
|
@ -33,6 +33,15 @@ class FileItem extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
navigateToFileUri = () => {
|
||||
const { navigation, uri } = this.props;
|
||||
const normalizedUri = normalizeURI(uri);
|
||||
if (NativeModules.Mixpanel) {
|
||||
NativeModules.Mixpanel.track('Discover Tap', { Uri: normalizeURI });
|
||||
}
|
||||
navigateToUri(navigation, normalizedUri);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
claim,
|
||||
|
@ -42,7 +51,10 @@ class FileItem extends React.PureComponent {
|
|||
rewardedContentClaimIds,
|
||||
style,
|
||||
mediaStyle,
|
||||
navigation
|
||||
navigation,
|
||||
showDetails,
|
||||
compactView,
|
||||
titleBeforeThumbnail
|
||||
} = this.props;
|
||||
|
||||
const uri = normalizeURI(this.props.uri);
|
||||
|
@ -55,24 +67,21 @@ class FileItem extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<View style={style}>
|
||||
<TouchableOpacity style={discoverStyle.container} onPress={() => {
|
||||
if (NativeModules.Mixpanel) {
|
||||
NativeModules.Mixpanel.track('Discover Tap', { Uri: uri });
|
||||
}
|
||||
navigateToUri(navigation, uri);
|
||||
}
|
||||
}>
|
||||
<TouchableOpacity style={discoverStyle.container} onPress={this.navigateToFileUri}>
|
||||
{!compactView && titleBeforeThumbnail && <Text style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>}
|
||||
<FileItemMedia title={title}
|
||||
thumbnail={thumbnail}
|
||||
blurRadius={obscureNsfw ? 15 : 0}
|
||||
resizeMode="cover"
|
||||
isResolvingUri={isResolvingUri}
|
||||
style={mediaStyle} />
|
||||
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
|
||||
<View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
|
||||
|
||||
{!compactView && <FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />}
|
||||
{!compactView && <View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
|
||||
<Text style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>
|
||||
{isRewardContent && <Icon style={discoverStyle.rewardIcon} name="award" size={20} />}
|
||||
</View>
|
||||
</View>}
|
||||
{(!compactView && showDetails) &&
|
||||
<View style={discoverStyle.detailsRow}>
|
||||
{channelName &&
|
||||
<Link style={discoverStyle.channelName} text={channelName} onPress={() => {
|
||||
|
@ -80,7 +89,7 @@ class FileItem extends React.PureComponent {
|
|||
navigateToUri(navigation, channelUri);
|
||||
}} />}
|
||||
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo block={height} />
|
||||
</View>
|
||||
</View>}
|
||||
</TouchableOpacity>
|
||||
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
|
||||
</View>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import * as React from 'react';
|
||||
import { buildURI } from 'lbry-redux';
|
||||
import { FlatList } from 'react-native';
|
||||
import FileItem from '../fileItem';
|
||||
import discoverStyle from '../../styles/discover';
|
||||
import FileItem from 'component/fileItem';
|
||||
import discoverStyle from 'styles/discover';
|
||||
|
||||
// In the future, all Flow types need to be specified in a common source (lbry-redux, perhaps?)
|
||||
type FileInfo = {
|
||||
|
@ -175,7 +175,9 @@ class FileList extends React.PureComponent<Props, State> {
|
|||
renderItem={({item}) => (
|
||||
<FileItem style={discoverStyle.fileItem}
|
||||
uri={item}
|
||||
navigation={navigation} />
|
||||
navigation={navigation}
|
||||
showDetails={true}
|
||||
compactView={false} />
|
||||
)} />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
makeSelectFileInfoForUri,
|
||||
makeSelectIsUriResolving,
|
||||
} from 'lbry-redux';
|
||||
import { selectShowNsfw } from '../../redux/selectors/settings';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import FileListItem from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
25
app/src/component/suggestedSubscriptionItem/index.js
Normal file
25
app/src/component/suggestedSubscriptionItem/index.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectFetchingChannelClaims,
|
||||
makeSelectClaimsInChannelForPage,
|
||||
doFetchClaimsByChannel,
|
||||
doResolveUris,
|
||||
} from 'lbry-redux';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import SuggestedSubscriptionItem from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claims: makeSelectClaimsInChannelForPage(props.categoryLink)(state),
|
||||
fetching: makeSelectFetchingChannelClaims(props.categoryLink)(state),
|
||||
obscureNsfw: !selectShowNsfw(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
fetchChannel: channel => dispatch(doFetchClaimsByChannel(channel)),
|
||||
resolveUris: uris => dispatch(doResolveUris(uris, true)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(SuggestedSubscriptionItem);
|
74
app/src/component/suggestedSubscriptionItem/view.js
Normal file
74
app/src/component/suggestedSubscriptionItem/view.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
import React from 'react';
|
||||
import { buildURI, normalizeURI } from 'lbry-redux';
|
||||
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
|
||||
import Colors from 'styles/colors';
|
||||
import discoverStyle from 'styles/discover';
|
||||
import FileItem from 'component/fileItem';
|
||||
import subscriptionsStyle from 'styles/subscriptions';
|
||||
|
||||
class SuggestedSubscriptionItem extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
const { fetching, categoryLink, fetchChannel, resolveUris, claims } = this.props;
|
||||
if (!fetching && categoryLink && (!claims || claims.length)) {
|
||||
fetchChannel(categoryLink);
|
||||
}
|
||||
}
|
||||
|
||||
uriForClaim = (claim) => {
|
||||
const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = claim;
|
||||
const uriParams = {};
|
||||
|
||||
// This is unfortunate
|
||||
// https://github.com/lbryio/lbry/issues/1159
|
||||
const name = claimName || claimNameDownloaded;
|
||||
uriParams.contentName = name;
|
||||
uriParams.claimId = claimId;
|
||||
const uri = buildURI(uriParams);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { categoryLink, fetching, obscureNsfw, claims, navigation } = this.props;
|
||||
|
||||
if (!claims || !claims.length) {
|
||||
return (
|
||||
<View style={subscriptionsStyle.busyContainer}>
|
||||
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (claims && claims.length > 0) {
|
||||
return (
|
||||
<View style={subscriptionsStyle.suggestedContainer}>
|
||||
<FileItem
|
||||
style={subscriptionsStyle.fileItem}
|
||||
mediaStyle={subscriptionsStyle.fileItemMedia}
|
||||
uri={this.uriForClaim(claims[0])}
|
||||
navigation={navigation} />
|
||||
{(claims.length > 1) &&
|
||||
<FlatList style={subscriptionsStyle.compactItems}
|
||||
horizontal={true}
|
||||
renderItem={ ({item}) => (
|
||||
<FileItem
|
||||
style={subscriptionsStyle.compactFileItem}
|
||||
mediaStyle={subscriptionsStyle.compactFileItemMedia}
|
||||
key={item}
|
||||
uri={normalizeURI(item)}
|
||||
navigation={navigation}
|
||||
compactView={true} />
|
||||
)
|
||||
}
|
||||
data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
|
||||
keyExtractor={(item, index) => item}
|
||||
/>}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default SuggestedSubscriptionItem;
|
13
app/src/component/suggestedSubscriptions/index.js
Normal file
13
app/src/component/suggestedSubscriptions/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectSuggestedChannels, selectIsFetchingSuggested } from 'lbryinc';
|
||||
import SuggestedSubscriptions from './view';
|
||||
|
||||
const select = state => ({
|
||||
suggested: selectSuggestedChannels(state),
|
||||
loading: selectIsFetchingSuggested(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(SuggestedSubscriptions);
|
52
app/src/component/suggestedSubscriptions/view.js
Normal file
52
app/src/component/suggestedSubscriptions/view.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
import { ActivityIndicator, SectionList, Text, View } from 'react-native';
|
||||
import { normalizeURI } from 'lbry-redux';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
|
||||
import Colors from 'styles/colors';
|
||||
import discoverStyle from 'styles/discover';
|
||||
import subscriptionsStyle from 'styles/subscriptions';
|
||||
import Link from 'component/link';
|
||||
|
||||
class SuggestedSubscriptions extends React.PureComponent {
|
||||
render() {
|
||||
const { suggested, loading, navigation } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return suggested ? (
|
||||
<SectionList style={subscriptionsStyle.scrollContainer}
|
||||
renderItem={ ({item, index, section}) => { console.log(item); return (
|
||||
<SuggestedSubscriptionItem
|
||||
key={item}
|
||||
categoryLink={normalizeURI(item)}
|
||||
navigation={navigation} />
|
||||
); }
|
||||
}
|
||||
renderSectionHeader={
|
||||
({section: {title}}) => {
|
||||
const titleParts = title.split(';');
|
||||
const channelName = titleParts[0];
|
||||
const channelUri = normalizeURI(titleParts[1]);
|
||||
return (
|
||||
<View style={subscriptionsStyle.titleRow}>
|
||||
<Link style={subscriptionsStyle.channelTitle} text={channelName} href={channelUri} />
|
||||
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
sections={suggested.map(({ uri, label }) => ({ title: (label + ';' + uri), data: [uri] }))}
|
||||
keyExtractor={(item, index) => item}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export default SuggestedSubscriptions;
|
|
@ -10,12 +10,12 @@ import {
|
|||
} from 'react-native';
|
||||
import { normalizeURI, parseURI } from 'lbry-redux';
|
||||
import moment from 'moment';
|
||||
import Colors from '../../styles/colors';
|
||||
import discoverStyle from '../../styles/discover';
|
||||
import FloatingWalletBalance from '../../component/floatingWalletBalance';
|
||||
import FileItem from '../../component/fileItem';
|
||||
import RewardSummary from '../../component/rewardSummary';
|
||||
import UriBar from '../../component/uriBar';
|
||||
import Colors from 'styles/colors';
|
||||
import discoverStyle from 'styles/discover';
|
||||
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||
import FileItem from 'component/fileItem';
|
||||
import RewardSummary from 'component/rewardSummary';
|
||||
import UriBar from 'component/uriBar';
|
||||
|
||||
class DiscoverPage extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
|
@ -125,7 +125,9 @@ class DiscoverPage extends React.PureComponent {
|
|||
mediaStyle={discoverStyle.fileItemMedia}
|
||||
key={item}
|
||||
uri={normalizeURI(item)}
|
||||
navigation={navigation} />
|
||||
navigation={navigation}
|
||||
compactView={false}
|
||||
showDetails={true} />
|
||||
)
|
||||
}
|
||||
renderSectionHeader={
|
||||
|
|
|
@ -31,11 +31,8 @@ const select = state => ({
|
|||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
doFetchMySubscriptions,
|
||||
doSetViewMode,
|
||||
doFetchRecommendedSubscriptions,
|
||||
doCompleteFirstRun,
|
||||
doShowSuggestedSubs,
|
||||
doFetchMySubscriptions: () => dispatch(doFetchMySubscriptions()),
|
||||
doFetchRecommendedSubscriptions: () => dispatch(doFetchRecommendedSubscriptions()),
|
||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SUBSCRIPTIONS))
|
||||
});
|
||||
|
||||
|
|
|
@ -17,18 +17,20 @@ import discoverStyle from 'styles/discover';
|
|||
import subscriptionsStyle from 'styles/subscriptions';
|
||||
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||
import FileItem from 'component/fileItem';
|
||||
import SuggestedSubscriptions from 'component/suggestedSubscriptions';
|
||||
import UriBar from 'component/uriBar';
|
||||
|
||||
class SubscriptionsPage extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
componentWillMount() {
|
||||
const {
|
||||
doFetchMySubscriptions,
|
||||
doFetchRecommendedSubscriptions,
|
||||
pushDrawerStack,
|
||||
} = this.props;
|
||||
|
||||
pushDrawerStack();
|
||||
doFetchMySubscriptions();
|
||||
//doFetchRecommendedSubscriptions();
|
||||
doFetchRecommendedSubscriptions();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -77,10 +79,12 @@ class SubscriptionsPage extends React.PureComponent {
|
|||
}
|
||||
|
||||
{!hasSubscriptions &&
|
||||
<View style={subscriptionsStyle.busyContainer}>
|
||||
<View style={subscriptionsStyle.container}>
|
||||
<Text style={subscriptionsStyle.infoText}>
|
||||
You are not subscribed to any channels. Feel free to discover new channels that you can subscribe to.
|
||||
You are not subscribed to any channels at the moment. Here are some channels that we think you might enjoy.
|
||||
</Text>
|
||||
{loadingSuggested && <ActivityIndicator size="large" colors={Colors.LbryGreen} style={subscriptionsStyle.loading} />}
|
||||
{!loadingSuggested && <SuggestedSubscriptions navigation={navigation} />}
|
||||
</View>}
|
||||
|
||||
<FloatingWalletBalance navigation={navigation} />
|
||||
|
|
|
@ -10,11 +10,11 @@ import {
|
|||
} from 'react-native';
|
||||
import { normalizeURI } from 'lbry-redux';
|
||||
import moment from 'moment';
|
||||
import FileItem from '../../component/fileItem';
|
||||
import discoverStyle from '../../styles/discover';
|
||||
import Colors from '../../styles/colors';
|
||||
import FloatingWalletBalance from '../../component/floatingWalletBalance';
|
||||
import UriBar from '../../component/uriBar';
|
||||
import FileItem from '/component/fileItem';
|
||||
import discoverStyle from 'styles/discover';
|
||||
import Colors from 'styles/colors';
|
||||
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||
import UriBar from 'component/uriBar';
|
||||
|
||||
class TrendingPage extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
|
@ -44,7 +44,9 @@ class TrendingPage extends React.PureComponent {
|
|||
mediaStyle={discoverStyle.fileItemMedia}
|
||||
key={item}
|
||||
uri={normalizeURI(item)}
|
||||
navigation={navigation} />
|
||||
navigation={navigation}
|
||||
showDetails={true}
|
||||
compactView={false} />
|
||||
)
|
||||
}
|
||||
data={trendingUris.map(uri => uri.url)}
|
||||
|
|
|
@ -2,15 +2,15 @@ import { Dimensions, PixelRatio, StyleSheet } from 'react-native';
|
|||
import Colors from './colors';
|
||||
|
||||
const screenDimension = Dimensions.get('window');
|
||||
const screenWidth = screenDimension.width;
|
||||
const screenHeight = screenDimension.height;
|
||||
export const screenWidth = screenDimension.width;
|
||||
export const screenHeight = screenDimension.height;
|
||||
const screenWidthPixels = PixelRatio.getPixelSizeForLayoutSize(screenWidth);
|
||||
const screenHeightPixels = PixelRatio.getPixelSizeForLayoutSize(screenHeight);
|
||||
// calculate thumbnail width and height based on device's aspect ratio
|
||||
const horizontalMargin = 48; // left and right margins (24 + 24)
|
||||
const verticalMargin = (screenWidthPixels > 720 && screenHeightPixels > 1920) ? 0 : ((screenWidthPixels <= 720) ? 20 : 16);
|
||||
const mediaWidth = screenWidth - horizontalMargin;
|
||||
const mediaHeight = ((screenWidth / screenHeight) * ((screenWidthPixels <= 720) ? screenWidth : mediaWidth)) - verticalMargin;
|
||||
// calculate thumbnail width and height based on device's aspect ratio
|
||||
export const horizontalMargin = 48; // left and right margins (24 + 24)
|
||||
export const verticalMargin = (screenWidthPixels > 720 && screenHeightPixels > 1920) ? 0 : ((screenWidthPixels <= 720) ? 20 : 16);
|
||||
export const mediaWidth = screenWidth - horizontalMargin;
|
||||
export const mediaHeight = ((screenWidth / screenHeight) * ((screenWidthPixels <= 720) ? screenWidth : mediaWidth)) - verticalMargin;
|
||||
|
||||
const discoverStyle = StyleSheet.create({
|
||||
container: {
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import {
|
||||
screenWidth,
|
||||
horizontalMargin,
|
||||
mediaWidth,
|
||||
mediaHeight
|
||||
} from 'styles/discover';
|
||||
import Colors from 'styles/colors';
|
||||
|
||||
const subscriptionsStyle = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -19,14 +26,65 @@ const subscriptionsStyle = StyleSheet.create({
|
|||
paddingTop: 24
|
||||
},
|
||||
infoText: {
|
||||
textAlign: 'center',
|
||||
fontFamily: 'Inter-UI-Regular',
|
||||
fontSize: 16,
|
||||
margin: 16
|
||||
},
|
||||
suggestedContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
fileItem: {
|
||||
marginLeft: 24,
|
||||
marginRight: 24,
|
||||
marginBottom: 24
|
||||
},
|
||||
compactItems: {
|
||||
flex: 1,
|
||||
marginTop: 6,
|
||||
marginLeft: 20,
|
||||
marginRight: 24,
|
||||
marginBottom: 24,
|
||||
height: 80,
|
||||
},
|
||||
compactFileItem: {
|
||||
width: (screenWidth - horizontalMargin - (6 * 3)) / 3,
|
||||
marginLeft: 6,
|
||||
height: '100%'
|
||||
},
|
||||
compactFileItemMedia: {
|
||||
width: (screenWidth - horizontalMargin) / 3,
|
||||
height: '100%'
|
||||
},
|
||||
fileItemMedia: {
|
||||
width: mediaWidth,
|
||||
height: mediaHeight,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
fileItemName: {
|
||||
fontFamily: 'Inter-UI-Bold',
|
||||
marginTop: 8,
|
||||
fontSize: 18
|
||||
},
|
||||
channelTitle: {
|
||||
fontFamily: 'Inter-UI-SemiBold',
|
||||
fontSize: 20,
|
||||
marginLeft: 24,
|
||||
marginTop: 16,
|
||||
marginBottom: 16,
|
||||
color: Colors.LbryGreen
|
||||
},
|
||||
titleRow: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
subscribeButton: {
|
||||
alignSelf: 'flex-start',
|
||||
marginRight: 24,
|
||||
marginTop: 8,
|
||||
backgroundColor: Colors.White,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue