Unverified Commit f05d357f authored by Akinwale Ariwodola's avatar Akinwale Ariwodola Committed by GitHub
Browse files

Following rework (#124)

* make following the default page. add suggested grd.
* suggested grid vertical scrolling. semi-infinite scroll
* search and related content fixes
parent 1f649b9d
...@@ -7067,8 +7067,8 @@ ...@@ -7067,8 +7067,8 @@
} }
}, },
"lbry-redux": { "lbry-redux": {
"version": "github:lbryio/lbry-redux#1de1d534c982db913f145a6171f39d7b8ebd61af", "version": "github:lbryio/lbry-redux#5c874e921769093428966fa7ecdf723719cb9067",
"from": "github:lbryio/lbry-redux#1de1d534c982db913f145a6171f39d7b8ebd61af", "from": "github:lbryio/lbry-redux#5c874e921769093428966fa7ecdf723719cb9067",
"requires": { "requires": {
"proxy-polyfill": "0.1.6", "proxy-polyfill": "0.1.6",
"reselect": "^3.0.0", "reselect": "^3.0.0",
...@@ -7076,8 +7076,8 @@ ...@@ -7076,8 +7076,8 @@
} }
}, },
"lbryinc": { "lbryinc": {
"version": "github:lbryio/lbryinc#138a053754ec8e3da8e9bf153d32f527c962f25c", "version": "github:lbryio/lbryinc#0dc8829a319a708f45a855765f70a193ccb72676",
"from": "github:lbryio/lbryinc#138a053754ec8e3da8e9bf153d32f527c962f25c", "from": "github:lbryio/lbryinc#0dc8829a319a708f45a855765f70a193ccb72676",
"requires": { "requires": {
"reselect": "^3.0.0" "reselect": "^3.0.0"
} }
......
...@@ -88,12 +88,13 @@ const menuNavigationButton = navigation => ( ...@@ -88,12 +88,13 @@ const menuNavigationButton = navigation => (
const discoverStack = createStackNavigator( const discoverStack = createStackNavigator(
{ {
Discover: { Subscriptions: {
screen: DiscoverPage, screen: SubscriptionsPage,
navigationOptions: ({ navigation }) => ({ navigationOptions: {
title: 'Explore', title: 'Following',
header: null, header: null,
}), drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={drawerIconSize} style={{ color: tintColor }} />,
},
}, },
File: { File: {
screen: FilePage, screen: FilePage,
...@@ -160,10 +161,17 @@ const drawer = createDrawerNavigator( ...@@ -160,10 +161,17 @@ const drawer = createDrawerNavigator(
DiscoverStack: { DiscoverStack: {
screen: discoverStack, screen: discoverStack,
navigationOptions: { navigationOptions: {
title: 'Explore', title: 'Following',
drawerIcon: ({ tintColor }) => <Icon name="home" size={drawerIconSize} style={{ color: tintColor }} />, drawerIcon: ({ tintColor }) => <Icon name="home" size={drawerIconSize} style={{ color: tintColor }} />,
}, },
}, },
Discover: {
screen: DiscoverPage,
navigationOptions: ({ navigation }) => ({
title: 'Your Tags',
header: null,
}),
},
Trending: { Trending: {
screen: TrendingPage, screen: TrendingPage,
navigationOptions: { navigationOptions: {
...@@ -171,13 +179,6 @@ const drawer = createDrawerNavigator( ...@@ -171,13 +179,6 @@ const drawer = createDrawerNavigator(
drawerIcon: ({ tintColor }) => <Icon name="fire" size={drawerIconSize} style={{ color: tintColor }} />, drawerIcon: ({ tintColor }) => <Icon name="fire" size={drawerIconSize} style={{ color: tintColor }} />,
}, },
}, },
Subscriptions: {
screen: SubscriptionsPage,
navigationOptions: {
title: 'Subscriptions',
drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={drawerIconSize} style={{ color: tintColor }} />,
},
},
WalletStack: { WalletStack: {
screen: walletStack, screen: walletStack,
navigationOptions: { navigationOptions: {
......
...@@ -9,8 +9,8 @@ import discoverStyle from 'styles/discover'; ...@@ -9,8 +9,8 @@ import discoverStyle from 'styles/discover';
const groupedMenuItems = { const groupedMenuItems = {
'Find content': [ 'Find content': [
{ icon: 'hashtag', label: 'Your Tags', route: Constants.DRAWER_ROUTE_DISCOVER },
{ icon: 'heart', solid: true, label: 'Following', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS }, { icon: 'heart', solid: true, label: 'Following', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS },
{ icon: 'hashtag', label: 'Your Tags', route: Constants.DRAWER_ROUTE_DISCOVER },
{ icon: 'globe-americas', label: 'All Content', route: Constants.DRAWER_ROUTE_TRENDING }, { icon: 'globe-americas', label: 'All Content', route: Constants.DRAWER_ROUTE_TRENDING },
], ],
'Your content': [ 'Your content': [
...@@ -145,7 +145,7 @@ class DrawerContent extends React.PureComponent { ...@@ -145,7 +145,7 @@ class DrawerContent extends React.PureComponent {
const focused = const focused =
activeItemKey === item.route || activeItemKey === item.route ||
(activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER && (activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER &&
item.route === Constants.DRAWER_ROUTE_DISCOVER) || item.route === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) ||
(activeItemKey === Constants.FULL_ROUTE_NAME_WALLET && (activeItemKey === Constants.FULL_ROUTE_NAME_WALLET &&
item.route === Constants.DRAWER_ROUTE_WALLET); item.route === Constants.DRAWER_ROUTE_WALLET);
return ( return (
......
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectFetchingClaimSearch } from 'lbry-redux';
import ModalSuggestedSubscriptions from './view'; import ModalSuggestedSubscriptions from './view';
export default connect()(ModalSuggestedSubscriptions); const select = state => ({
loadingSuggested: selectFetchingClaimSearch(state),
});
export default connect(select)(ModalSuggestedSubscriptions);
import React from 'react'; import React from 'react';
import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { ActivityIndicator, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import modalStyle from 'styles/modal'; import modalStyle from 'styles/modal';
import subscriptionsStyle from 'styles/subscriptions'; import subscriptionsStyle from 'styles/subscriptions';
import Button from 'component/button'; import Button from 'component/button';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import SuggestedSubscriptions from 'component/suggestedSubscriptions'; import SuggestedSubscriptionsGrid from 'component/suggestedSubscriptionsGrid';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
export default class ModalSuggestedSubcriptions extends React.PureComponent { export default class ModalSuggestedSubcriptions extends React.PureComponent {
render() { render() {
const { navigation, onDonePress, onOverlayPress } = this.props; const { loadingSuggested, navigation, onDonePress, onOverlayPress } = this.props;
return ( return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}> <TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={[modalStyle.container, subscriptionsStyle.modalContainer]} activeOpacity={1}> <TouchableOpacity style={[modalStyle.container, subscriptionsStyle.modalContainer]} activeOpacity={1}>
<SuggestedSubscriptions inModal navigation={navigation} /> <SuggestedSubscriptionsGrid inModal navigation={navigation} />
<View style={modalStyle.buttons}> <View style={modalStyle.wideButtons}>
<Button style={modalStyle.doneButton} text={__('Done')} onPress={onDonePress} /> <Button style={modalStyle.wideDoneButton} text={__('Done')} onPress={onDonePress} />
{loadingSuggested && (
<ActivityIndicator size="small" color={Colors.White} style={subscriptionsStyle.modalLoading} />
)}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
</TouchableOpacity> </TouchableOpacity>
......
import { connect } from 'react-redux';
import { doChannelSubscribe, doChannelUnsubscribe, selectSubscriptions, makeSelectIsSubscribed } from 'lbryinc';
import { doToast } from 'lbry-redux';
import SubscribeButtonOverlay from './view';
const select = (state, props) => ({
subscriptions: selectSubscriptions(state),
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
});
export default connect(
select,
{
doChannelSubscribe,
doChannelUnsubscribe,
doToast,
},
)(SubscribeButtonOverlay);
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import { NativeModules, Text, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Button from 'component/button';
import Colors from 'styles/colors';
class SubscribeButtonOverlay extends React.PureComponent {
handlePress = () => {
const { claim, isSubscribed, doChannelSubscribe, doChannelUnsubscribe, uri } = this.props;
if (!claim) {
return;
}
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const { name: claimName } = claim;
subscriptionHandler({
channelName: claimName,
uri: normalizeURI(uri),
});
};
render() {
const { uri, isSubscribed, style } = this.props;
let styles = style.length ? style : [style];
return (
<TouchableOpacity style={styles} opacity={0.7} onPress={this.handlePress}>
{isSubscribed && <Icon name={'heart'} size={20} solid color={Colors.Red} />}
{!isSubscribed && <Icon name={'heart'} size={20} color={Colors.Red} />}
</TouchableOpacity>
);
}
}
export default SubscribeButtonOverlay;
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectIsUriResolving, makeSelectIsUriResolving,
} from 'lbry-redux'; } from 'lbry-redux';
import { doChannelSubscribe, doChannelUnsubscribe, makeSelectIsSubscribed } from 'lbryinc';
import SuggestedSubscriptionItem from './view'; import SuggestedSubscriptionItem from './view';
const select = (state, props) => ({ const select = (state, props) => ({
...@@ -13,13 +14,16 @@ const select = (state, props) => ({ ...@@ -13,13 +14,16 @@ const select = (state, props) => ({
title: makeSelectTitleForUri(props.uri)(state), title: makeSelectTitleForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state), isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)), resolveUri: uri => dispatch(doResolveUri(uri)),
subscribe: subscription => doChannelSubscribe(subscription),
unsubscribe: subscription => doChannelUnsubscribe(subscription),
}); });
export default connect( export default connect(
select, select,
perform perform,
)(SuggestedSubscriptionItem); )(SuggestedSubscriptionItem);
import React from 'react'; import React from 'react';
import { buildURI, normalizeURI } from 'lbry-redux'; import { buildURI, normalizeURI } from 'lbry-redux';
import { ActivityIndicator, FlatList, Image, Text, View } from 'react-native'; import { ActivityIndicator, FlatList, Image, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri } from 'utils/helper'; import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem'; import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon'; import channelIconStyle from 'styles/channelIcon';
import discoverStyle from 'styles/discover'; import discoverStyle from 'styles/discover';
import FileItem from 'component/fileItem'; import FileItem from 'component/fileItem';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButtonOverlay from 'component/subscribeButtonOverlay';
import subscriptionsStyle from 'styles/subscriptions'; import subscriptionsStyle from 'styles/subscriptions';
import Link from 'component/link'; import Link from 'component/link';
import Tag from 'component/tag'; import Tag from 'component/tag';
...@@ -31,6 +31,7 @@ class SuggestedSubscriptionItem extends React.PureComponent { ...@@ -31,6 +31,7 @@ class SuggestedSubscriptionItem extends React.PureComponent {
render() { render() {
const { claim, isResolvingUri, navigation, thumbnail, title, uri } = this.props; const { claim, isResolvingUri, navigation, thumbnail, title, uri } = this.props;
let shortUrl, tags; let shortUrl, tags;
if (claim) { if (claim) {
shortUrl = claim.short_url; shortUrl = claim.short_url;
...@@ -49,7 +50,7 @@ class SuggestedSubscriptionItem extends React.PureComponent { ...@@ -49,7 +50,7 @@ class SuggestedSubscriptionItem extends React.PureComponent {
} }
return ( return (
<View style={subscriptionsStyle.suggestedItem}> <TouchableOpacity style={subscriptionsStyle.suggestedItem}>
<View style={[subscriptionsStyle.suggestedItemThumbnailContainer, this.state.autoStyle]}> <View style={[subscriptionsStyle.suggestedItemThumbnailContainer, this.state.autoStyle]}>
{hasThumbnail && ( {hasThumbnail && (
<Image style={subscriptionsStyle.suggestedItemThumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} /> <Image style={subscriptionsStyle.suggestedItemThumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} />
...@@ -62,35 +63,37 @@ class SuggestedSubscriptionItem extends React.PureComponent { ...@@ -62,35 +63,37 @@ class SuggestedSubscriptionItem extends React.PureComponent {
</View> </View>
<View style={subscriptionsStyle.suggestedItemDetails}> <View style={subscriptionsStyle.suggestedItemDetails}>
{title && ( <Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={2}>
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={1}> {title || claim.name}
{title} </Text>
</Text>
)}
{claim && (
<Link
style={subscriptionsStyle.suggestedItemName}
numberOfLines={1}
text={claim.name}
onPress={() => navigateToUri(navigation, normalizeURI(shortUrl || uri), null, false, claim.permanent_url)}
/>
)}
{tags && ( {tags && (
<View style={subscriptionsStyle.suggestedItemTagList}> <View style={subscriptionsStyle.suggestedItemTagList}>
{tags && {tags &&
tags tags
.slice(0, 3) .slice(0, 1)
.map(tag => ( .map(tag => (
<Tag style={subscriptionsStyle.tag} key={tag} name={tag} navigation={navigation} truncate /> <Tag
numberOfLines={1}
onPress={this.handleItemPress}
style={subscriptionsStyle.tag}
key={tag}
name={tag}
navigation={navigation}
truncate
/>
))} ))}
</View> </View>
)} )}
</View> </View>
{claim && ( {claim && (
<SubscribeButton style={subscriptionsStyle.suggestedItemSubscribe} uri={normalizeURI(claim.permanent_url)} /> <SubscribeButtonOverlay
claim={claim}
style={subscriptionsStyle.suggestedItemSubscribeOverlay}
uri={normalizeURI(claim.permanent_url)}
/>
)} )}
</View> </TouchableOpacity>
); );
} }
} }
......
import { connect } from 'react-redux';
import {
doClaimSearch,
selectFetchingClaimSearch,
selectClaimSearchByQuery,
selectClaimSearchByQueryLastPageReached,
selectFollowedTags,
} from 'lbry-redux';
import { selectSubscriptions, selectSuggestedChannels, selectIsFetchingSuggested } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
import SuggestedSubscriptionsGrid from './view';
const select = state => ({
followedTags: selectFollowedTags(state),
subscriptions: selectSubscriptions(state),
suggested: selectSuggestedChannels(state),
loading: selectIsFetchingSuggested(state) || selectFetchingClaimSearch(state),
claimSearchByQuery: selectClaimSearchByQuery(state),
lastPageReached: selectClaimSearchByQueryLastPageReached(state),
showNsfwContent: selectShowNsfw(state),
});
const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(
select,
perform,
)(SuggestedSubscriptionsGrid);
import React from 'react';
import { ActivityIndicator, SectionList, Text, View } from 'react-native';
import { MATURE_TAGS, createNormalizedClaimSearchKey, normalizeURI } from 'lbry-redux';
import { navigateToUri } from 'utils/helper';
import { FlatGrid } from 'react-native-super-grid';
import SubscribeButton from 'component/subscribeButton';
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import discoverStyle from 'styles/discover';
import subscriptionsStyle from 'styles/subscriptions';
import Link from 'component/link';
import _ from 'lodash';
const suggestedPageSize = 24;
const softLimit = 2400;
class SuggestedSubscriptionsGrid extends React.PureComponent {
state = {
currentPage: 1,
options: {},
};
buildClaimSearchOptions() {
const { showNsfwContent, subscriptions } = this.props;
const { currentPage } = this.state;
const options = {
no_totals: true,
page: currentPage,
page_size: suggestedPageSize,
claim_type: 'channel',
order_by: [Constants.ORDER_BY_EFFECTIVE_AMOUNT],
};
if (!showNsfwContent) {
options.not_tags = MATURE_TAGS;
}
/* if (subscriptions && subscriptions.length > 0) {
options.not_channel_ids = subscriptions.map(subscription => subscription.uri.split('#')[1]);
} */
return options;
}
doClaimSearch() {
const { claimSearch } = this.props;
const options = this.buildClaimSearchOptions();
claimSearch(options);
}
handleVerticalEndReached = () => {
// fetch more content
const { claimSearchByQuery, lastPageReached } = this.props;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
const uris = claimSearchByQuery[claimSearchKey];
if (
lastPageReached[claimSearchKey] ||
((uris.length > 0 && uris.length < suggestedPageSize) || uris.length >= softLimit)
) {
return;
}
this.setState({ currentPage: this.state.currentPage + 1 }, () => this.doClaimSearch());
};
componentDidMount() {
const { claimSearch, followedTags, showNsfwContent } = this.props;
this.doClaimSearch();
}
render() {
const { claimSearchByQuery, suggested, inModal, navigation } = this.props;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
const claimSearchUris = claimSearchByQuery[claimSearchKey];
return (
<FlatGrid
initialNumToRender={24}
maxToRenderPerBatch={48}
removeClippedSubviews
itemDimension={120}
spacing={2}
items={claimSearchUris}
style={inModal ? subscriptionsStyle.modalScrollContainer : subscriptionsStyle.scrollContainer}
contentContainerStyle={
inModal ? subscriptionsStyle.modalSuggestedScrollContent : subscriptionsStyle.suggestedScrollContent
}
renderItem={({ item, index }) => (
<SuggestedSubscriptionItem key={item} uri={normalizeURI(item)} navigation={navigation} />
)}
onEndReached={this.handleVerticalEndReached}
onEndReachedThreshold={0.2}
/>
);
}
}
export default SuggestedSubscriptionsGrid;
...@@ -30,7 +30,7 @@ export default class Tag extends React.PureComponent { ...@@ -30,7 +30,7 @@ export default class Tag extends React.PureComponent {
}; };
render() { render() {
const { name, onPress, style, type, truncate } = this.props; const { name, numberOfLines, onPress, style, type, truncate } = this.props;
let styles = []; let styles = [];
if (style) { if (style) {
...@@ -50,7 +50,9 @@ export default class Tag extends React.PureComponent { ...@@ -50,7 +50,9 @@ export default class Tag extends React.PureComponent {
return ( return (
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}> <TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
<View style={tagStyle.content}> <View style={tagStyle.content}>
<Text style={tagStyle.text}>{truncate ? formatTagName(name) : name}</Text> <Text style={tagStyle.text} numberOfLines={numberOfLines}>
{truncate ? formatTagName(name) : name}
</Text>
{type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />} {type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
......