rework discovery views with new/top/trending sorting #25

Merged
akinwale merged 9 commits from content-views into master 2019-08-20 10:03:34 +02:00
38 changed files with 670 additions and 291 deletions
src
component
AppNavigator.js
button
claimList
drawerContent
constants.jsindex.js
page
about
channel
discover
downloads
file
firstRun
publish
publishes
rewards
search
settings
splash
subscriptions
tag
transactionHistory
trending
verification
wallet
redux
actions
reducers
selectors
styles
utils

View file

@ -141,7 +141,7 @@ const drawer = createDrawerNavigator(
Trending: {
screen: TrendingPage,
navigationOptions: {
title: 'Trending',
title: 'All Content',
drawerIcon: ({ tintColor }) => <Icon name="fire" size={drawerIconSize} style={{ color: tintColor }} />,
},
},

View file

@ -34,14 +34,14 @@ export default class Button extends React.PureComponent {
}
let renderIcon = (
<Icon name={icon} size={18} color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White} />
<Icon name={icon} size={16} color={iconColor || (theme === 'light' ? Colors.DarkGrey : Colors.White)} />
);
if (solid) {
renderIcon = (
<Icon
name={icon}
size={18}
color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White}
size={16}
color={iconColor || (theme === 'light' ? Colors.DarkGrey : Colors.White)}
solid
/>
);

View file

@ -21,17 +21,14 @@ class ClaimList extends React.PureComponent {
state = {
currentPage: 1, // initial page load is page 1
subscriptionsView: false, // whether or not this claim list is for subscriptions
trendingForAllView: false,
lastPageReached: false,
};
componentDidMount() {
const { channelIds, trendingForAll } = this.props;
const { channelIds } = this.props;
if (channelIds) {
this.setState({ subscriptionsView: true });
} else if (trendingForAll) {
this.setState({ trendingForAllView: true });
}
this.doClaimSearch();
@ -44,18 +41,16 @@ class ClaimList extends React.PureComponent {
searchByTags,
tags: prevTags,
channelIds: prevChannelIds,
trendingForAll: prevTrendingForAll,
time: prevTime,
showNsfwContent,
} = prevProps;
const { claimSearchByQuery, orderBy, tags, channelIds, trendingForAll, time } = this.props;
const { claimSearchByQuery, orderBy, tags, channelIds, time } = this.props;
if (
!_.isEqual(orderBy, prevOrderBy) ||
!_.isEqual(tags, prevTags) ||
!_.isEqual(channelIds, prevChannelIds) ||
time !== prevTime ||
trendingForAll !== prevTrendingForAll
time !== prevTime
) {
// reset to page 1 because the order, tags or channelIds changed
this.setState({ currentPage: 1 }, () => {
@ -63,15 +58,12 @@ class ClaimList extends React.PureComponent {
this.scrollView.scrollToOffset({ animated: true, offset: 0 });
}
if (trendingForAll || (prevChannelIds && channelIds)) {
if (prevChannelIds && channelIds) {
if (channelIds) {
this.setState({ subscriptionsView: true });
}
if (trendingForAll) {
this.setState({ trendingForAllView: true });
}
} else if (tags && tags.length > 0) {
this.setState({ subscriptionsView: false, trendingForAllView: false });
this.setState({ subscriptionsView: false });
}
this.doClaimSearch();
@ -80,15 +72,8 @@ class ClaimList extends React.PureComponent {
}
buildClaimSearchOptions() {
const {
orderBy = Constants.DEFAULT_ORDER_BY,
channelIds,
showNsfwContent,
tags,
time,
trendingForAll,
} = this.props;
const { currentPage, subscriptionsView, trendingForAllView } = this.state;
const { orderBy = Constants.DEFAULT_ORDER_BY, channelIds, showNsfwContent, tags, time } = this.props;
const { currentPage, subscriptionsView } = this.state;
const options = {
order_by: orderBy,
@ -99,7 +84,7 @@ class ClaimList extends React.PureComponent {
if (channelIds) {
options.channel_ids = channelIds;
} else if (!trendingForAll && !trendingForAllView && tags && tags.length > 0) {
} else if (tags && tags.length > 0) {
options.any_tags = tags;
}
if (!showNsfwContent) {
@ -156,7 +141,7 @@ class ClaimList extends React.PureComponent {
if (tags.length === 1) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TAG, key: 'tagPage', params: { tag: tags[0] } });
} else {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TRENDING });
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TRENDING, params: { filterForTags: true } });
}
};
@ -176,8 +161,16 @@ class ClaimList extends React.PureComponent {
};
renderVerticalItem = ({ item }) => {
const { navigation } = this.props;
return <FileListItem key={item} uri={item} style={claimListStyle.verticalListItem} navigation={navigation} />;
const { hideChannel, navigation } = this.props;
return (
<FileListItem
key={item}
uri={item}
hideChannel={hideChannel}
style={claimListStyle.verticalListItem}
navigation={navigation}
/>
);
};
renderHorizontalItem = ({ item }) => {
@ -208,7 +201,7 @@ class ClaimList extends React.PureComponent {
style,
claimSearchByQuery,
} = this.props;
const { subscriptionsView, trendingForAllView } = this.state;
const { subscriptionsView } = this.state;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);

View file

@ -1,34 +1,76 @@
import React from 'react';
import { DrawerItems, SafeAreaView } from 'react-navigation';
import { ScrollView } from 'react-native';
import Constants from 'constants';
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import discoverStyle from 'styles/discover';
const groupedMenuItems = {
'Find content': [
{ icon: 'hashtag', label: 'Your tags', route: Constants.FULL_ROUTE_NAME_DISCOVER },
{ icon: 'heart', solid: true, label: 'Subscriptions', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS },
{ icon: 'globe-americas', label: 'All content', route: Constants.DRAWER_ROUTE_TRENDING },
],
'Your content': [
{ icon: 'download', label: 'Library', route: Constants.DRAWER_ROUTE_MY_LBRY },
{ icon: 'cloud-upload-alt', label: 'Publishes', route: Constants.DRAWER_ROUTE_PUBLISHES },
{ icon: 'upload', label: 'New publish', route: Constants.DRAWER_ROUTE_PUBLISH },
],
Wallet: [
{ icon: 'wallet', label: 'Wallet', route: Constants.FULL_ROUTE_NAME_WALLET },
{ icon: 'award', label: 'Rewards', route: Constants.DRAWER_ROUTE_REWARDS },
],
Settings: [
{ icon: 'cog', label: 'Settings', route: Constants.DRAWER_ROUTE_SETTINGS },
{ icon: 'info', label: 'About', route: Constants.DRAWER_ROUTE_ABOUT },
],
};
const groupNames = Object.keys(groupedMenuItems);
class DrawerContent extends React.PureComponent {
render() {
const props = this.props;
const { navigation, onItemPress } = props;
const { activeTintColor, navigation, onItemPress } = this.props;
const { state } = navigation;
const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
return (
<ScrollView>
<ScrollView contentContainerStyle={discoverStyle.menuScrollContent}>
<SafeAreaView style={discoverStyle.drawerContentContainer} forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems
{...props}
onItemPress={route => {
const { routeName } = route.route;
if (Constants.FULL_ROUTE_NAME_DISCOVER === routeName) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_DISCOVER });
return;
}
{groupNames.map(groupName => {
const menuItems = groupedMenuItems[groupName];
if (Constants.FULL_ROUTE_NAME_WALLET === routeName) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_WALLET });
return;
}
onItemPress(route);
}}
/>
return (
<View key={groupName} style={discoverStyle.menuGroup}>
{groupNames[3] !== groupName && (
<Text key={`${groupName}-title`} style={discoverStyle.menuGroupName}>
{groupName}
</Text>
)}
{menuItems.map(item => {
const focused = activeItemKey === item.route;
return (
<TouchableOpacity
accessible
accessibilityLabel={item.label}
style={[discoverStyle.menuItemTouchArea, focused ? discoverStyle.menuItemTouchAreaFocused : null]}
key={item.label}
onPress={() => navigation.navigate({ routeName: item.route })}
delayPressIn={0}
>
<View style={discoverStyle.menuItemIcon}>
<Icon name={item.icon} size={16} solid={item.solid} color={focused ? activeTintColor : null} />
</View>
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
{item.label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
})}
</SafeAreaView>
</ScrollView>
);

View file

@ -54,6 +54,7 @@ const Constants = {
ACTION_REACT_NAVIGATION_REPLACE: 'Navigation/REPLACE',
ACTION_SORT_BY_ITEM_CHANGED: 'SORT_BY_ITEM_CHANGED',
ACTION_TIME_ITEM_CHANGED: 'TIME_ITEM_CHANGED',
ORIENTATION_HORIZONTAL: 'horizontal',
ORIENTATION_VERTICAL: 'vertical',

View file

@ -43,6 +43,7 @@ import thunk from 'redux-thunk';
const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) {
console.log(error);
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
}
};

View file

@ -43,6 +43,7 @@ class AboutPage extends React.PureComponent {
const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('About');
if (NativeModules.VersionInfo) {
NativeModules.VersionInfo.getAppVersion().then(version => {

View file

@ -1,26 +1,23 @@
import { connect } from 'react-redux';
import {
doFetchClaimsByChannel,
makeSelectClaimForUri,
makeSelectClaimsInChannelForCurrentPageState,
makeSelectFetchingChannelClaims,
makeSelectTotalPagesForChannel,
} from 'lbry-redux';
import { doFetchClaimsByChannel, makeSelectClaimForUri } from 'lbry-redux';
import { doPopDrawerStack } from 'redux/actions/drawer';
import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { selectDrawerStack } from 'redux/selectors/drawer';
import { selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import ChannelPage from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
claimsInChannel: makeSelectClaimsInChannelForCurrentPageState(props.uri)(state),
drawerStack: selectDrawerStack(state),
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
totalPages: makeSelectTotalPagesForChannel(props.uri, 10)(state), // Update to use a default PAGE_SIZE constant
sortByItem: selectSortByItem(state),
timeItem: selectTimeItem(state),
});
const perform = dispatch => ({
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
popDrawerStack: () => dispatch(doPopDrawerStack()),
setSortByItem: item => dispatch(doSetSortByItem(item)),
setTimeItem: item => dispatch(doSetTimeItem(item)),
});
export default connect(

View file

@ -1,113 +1,111 @@
// @flow
import React from 'react';
import { ActivityIndicator, Dimensions, Image, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import {
ActivityIndicator,
Dimensions,
Image,
NativeModules,
ScrollView,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';
import { normalizeURI } from 'lbry-redux';
import { navigateBack } from 'utils/helper';
import { navigateBack, getOrderBy } from 'utils/helper';
import ChannelIconItem from 'component/channelIconItem';
import ClaimList from 'component/claimList';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Button from 'component/button';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import FileList from 'component/fileList';
import ModalPicker from 'component/modalPicker';
import PageHeader from 'component/pageHeader';
import SubscribeButton from 'component/subscribeButton';
import SubscribeNotificationButton from 'component/subscribeNotificationButton';
import UriBar from 'component/uriBar';
import channelIconStyle from 'styles/channelIcon';
import channelPageStyle from 'styles/channelPage';
import discoverStyle from 'styles/discover';
class ChannelPage extends React.PureComponent {
state = {
page: 1,
showPageButtons: false,
autoStyle: null,
showSortPicker: false,
showTimePicker: false,
orderBy: Constants.DEFAULT_ORDER_BY,
activeTab: Constants.CONTENT_TAB,
};
componentDidMount() {
const { uri, page, claimsInChannel, fetchClaims, fetchClaimCount } = this.props;
if (!claimsInChannel || !claimsInChannel.length) {
fetchClaims(uri, page || this.state.page);
}
componentWillMount() {
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
}
handlePreviousPage = () => {
const { uri, fetchClaims } = this.props;
if (this.state.page > 1) {
this.setState({ page: this.state.page - 1, showPageButtons: false }, () => {
fetchClaims(uri, this.state.page);
});
}
componentDidMount() {
NativeModules.Firebase.setCurrentScreen('Channel');
}
handleSortByItemSelected = item => {
const { setSortByItem } = this.props;
setSortByItem(item);
this.setState({ orderBy: getOrderBy(item), showSortPicker: false });
};
handleNextPage = () => {
const { uri, fetchClaims, totalPages } = this.props;
if (this.state.page < totalPages) {
this.setState({ page: this.state.page + 1, showPageButtons: false }, () => {
fetchClaims(uri, this.state.page);
});
}
handleTimeItemSelected = item => {
const { setTimeItem } = this.props;
setTimeItem(item);
this.setState({ showTimePicker: false });
};
listHeader = () => {
const { sortByItem, timeItem } = this.props;
return (
<View style={channelPageStyle.listHeader}>
<View style={discoverStyle.pickerRow}>
<View style={discoverStyle.leftPickerRow}>
<TouchableOpacity style={discoverStyle.tagSortBy} onPress={() => this.setState({ showSortPicker: true })}>
<Text style={discoverStyle.tagSortText}>{sortByItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
{Constants.SORT_BY_TOP === sortByItem.name && (
<TouchableOpacity style={discoverStyle.tagTime} onPress={() => this.setState({ showTimePicker: true })}>
<Text style={discoverStyle.tagSortText}>{timeItem.label}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
</View>
</View>
</View>
);
};
renderContent = () => {
const { fetching, claimsInChannel, totalPages, navigation } = this.props;
const { claim, navigation, timeItem } = this.props;
let contentList;
if (fetching) {
contentList = (
<View style={channelPageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={channelPageStyle.infoText}>Fetching content...</Text>
</View>
);
} else {
contentList =
claimsInChannel && claimsInChannel.length ? (
<FileList
sortByHeight
hideFilter
fileInfos={claimsInChannel}
navigation={navigation}
style={channelPageStyle.fileList}
contentContainerStyle={channelPageStyle.fileListContent}
onEndReached={() => this.setState({ showPageButtons: true })}
/>
) : (
<View style={channelPageStyle.busyContainer}>
<Text style={channelPageStyle.infoText}>No content found.</Text>
</View>
);
}
let pageButtons;
if (totalPages > 1 && this.state.showPageButtons) {
pageButtons = (
<View style={channelPageStyle.pageButtons}>
<View>
{this.state.page > 1 && (
<Button
style={channelPageStyle.button}
text={'Previous'}
disabled={!!fetching}
onPress={this.handlePreviousPage}
/>
)}
</View>
{this.state.page < totalPages && (
<Button
style={[channelPageStyle.button, channelPageStyle.nextButton]}
text={'Next'}
disabled={!!fetching}
onPress={this.handleNextPage}
/>
)}
</View>
);
let channelId;
if (claim) {
channelId = claim.claim_id;
}
return (
<View style={channelPageStyle.contentTab}>
{contentList}
{pageButtons}
{channelId && (
<ClaimList
ListHeaderComponent={this.listHeader}
hideChannel
orderBy={this.state.orderBy}
time={timeItem.name}
navigation={navigation}
orientation={Constants.ORIENTATION_VERTICAL}
channelIds={[channelId]}
style={channelPageStyle.claimList}
/>
)}
</View>
);
};
@ -162,10 +160,16 @@ class ChannelPage extends React.PureComponent {
};
render() {
const { fetching, claimsInChannel, claim, navigation, totalPages, uri, drawerStack, popDrawerStack } = this.props;
const { claim, navigation, uri, drawerStack, popDrawerStack, sortByItem, timeItem } = this.props;
const { name, permanent_url: permanentUrl } = claim;
const { autoStyle, showSortPicker, showTimePicker } = this.state;
let thumbnailUrl, coverUrl, title, fullUri;
let thumbnailUrl,
coverUrl,
title,
fullUri,
displayName = null,
substrIndex = 0;
if (claim) {
if (claim.value) {
title = claim.value.title;
@ -177,6 +181,8 @@ class ChannelPage extends React.PureComponent {
}
}
displayName = title || claim.name;
substrIndex = displayName.startsWith('@') ? 1 : 0;
fullUri = normalizeURI(`${claim.name}#${claim.claim_id}`);
}
@ -200,16 +206,15 @@ class ChannelPage extends React.PureComponent {
<Text style={channelPageStyle.channelName}>{title && title.trim().length > 0 ? title : name}</Text>
</View>
<View style={channelPageStyle.avatarImageContainer}>
<Image
style={channelPageStyle.avatarImage}
resizeMode={'cover'}
source={
thumbnailUrl && thumbnailUrl.trim().length > 0
? { uri: thumbnailUrl }
: require('../../assets/default_avatar.jpg')
}
/>
<View style={[channelPageStyle.avatarImageContainer, autoStyle]}>
{thumbnailUrl && (
<Image style={channelPageStyle.avatarImage} resizeMode={'cover'} source={{ uri: thumbnailUrl }} />
)}
{(!thumbnailUrl || thumbnailUrl.trim().length === 0) && (
<Text style={channelIconStyle.autothumbCharacter}>
{displayName.substring(substrIndex, substrIndex + 1).toUpperCase()}
</Text>
)}
</View>
<View style={channelPageStyle.subscribeButtonContainer}>
@ -242,6 +247,25 @@ class ChannelPage extends React.PureComponent {
{Constants.CONTENT_TAB === this.state.activeTab && this.renderContent()}
{Constants.ABOUT_TAB === this.state.activeTab && this.renderAbout()}
</View>
{showSortPicker && (
<ModalPicker
title={__('Sort content by')}
onOverlayPress={() => this.setState({ showSortPicker: false })}
onItemSelected={this.handleSortByItemSelected}
selectedItem={sortByItem}
items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
/>
)}
{showTimePicker && (
<ModalPicker
title={__('Content from')}
onOverlayPress={() => this.setState({ showTimePicker: false })}
onItemSelected={this.handleTimeItemSelected}
selectedItem={timeItem}
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}
</View>
);
}

View file

@ -11,8 +11,8 @@ import {
selectSubscriptionClaims,
selectUnreadSubscriptions,
} from 'lbryinc';
import { doSetClientSetting, doSetSortByItem } from 'redux/actions/settings';
import { makeSelectClientSetting, selectSortByItem } from 'redux/selectors/settings';
import { doSetClientSetting, doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { makeSelectClientSetting, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DiscoverPage from './view';
@ -27,6 +27,7 @@ const select = state => ({
ratingReminderDisabled: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED)(state),
ratingReminderLastShown: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN)(state),
sortByItem: selectSortByItem(state),
timeItem: selectTimeItem(state),
unreadSubscriptions: selectUnreadSubscriptions(state),
});
@ -39,6 +40,7 @@ const perform = dispatch => ({
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setSortByItem: item => dispatch(doSetSortByItem(item)),
setTimeItem: item => dispatch(doSetTimeItem(item)),
});
export default connect(

View file

@ -33,10 +33,13 @@ class DiscoverPage extends React.PureComponent {
remainingTags: [],
showModalTagSelector: false,
showSortPicker: false,
showTimePicker: false,
orderBy: Constants.DEFAULT_ORDER_BY,
};
componentDidMount() {
NativeModules.Firebase.setCurrentScreen('Your tags');
// Track the total time taken if this is the first launch
AsyncStorage.getItem('firstLaunchTime').then(startTime => {
if (startTime !== null && !isNaN(parseInt(startTime, 10))) {
@ -78,6 +81,12 @@ class DiscoverPage extends React.PureComponent {
this.setState({ orderBy, showSortPicker: false });
};
handleTimeItemSelected = item => {
const { setTimeItem } = this.props;
setTimeItem(item);
this.setState({ showTimePicker: false });
};
subscriptionForUri = (uri, channelName) => {
const { allSubscriptions } = this.props;
const { claimId, claimName } = parseURI(uri);
@ -230,26 +239,41 @@ class DiscoverPage extends React.PureComponent {
});
} else {
// navigate to the trending page
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TRENDING });
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TRENDING, params: { filterForTags: true } });
}
};
sectionListHeader = () => (
<View style={discoverStyle.titleRow}>
<Text style={discoverStyle.pageTitle}>Explore</Text>
<View style={discoverStyle.rightTitleRow}>
<Link
style={discoverStyle.customizeLink}
text={'Customize'}
onPress={() => this.setState({ showModalTagSelector: true })}
/>
<TouchableOpacity style={discoverStyle.tagSortBy} onPress={() => this.setState({ showSortPicker: true })}>
<Text style={discoverStyle.tagSortText}>{this.props.sortByItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
sectionListHeader = () => {
const { sortByItem, timeItem } = this.props;
return (
<View style={discoverStyle.listHeader}>
<View style={discoverStyle.titleRow}>
<Text style={discoverStyle.pageTitle}>Your tags</Text>
</View>
<View style={discoverStyle.pickerRow}>
<View style={discoverStyle.leftPickerRow}>
<TouchableOpacity style={discoverStyle.tagSortBy} onPress={() => this.setState({ showSortPicker: true })}>
<Text style={discoverStyle.tagSortText}>{sortByItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
{Constants.SORT_BY_TOP === sortByItem.name && (
<TouchableOpacity style={discoverStyle.tagTime} onPress={() => this.setState({ showTimePicker: true })}>
<Text style={discoverStyle.tagSortText}>{timeItem.label}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
</View>
<Link
style={discoverStyle.customizeLink}
text={'Customize'}
onPress={() => this.setState({ showModalTagSelector: true })}
/>
</View>
</View>
</View>
);
);
};
sectionListFooter = () => {
const { remainingTags } = this.state;
@ -279,7 +303,7 @@ class DiscoverPage extends React.PureComponent {
<ClaimList
key={item.sort().join(',')}
orderBy={this.state.orderBy}
time={Constants.TIME_WEEK}
time={this.props.timeItem.name}
tags={item}
morePlaceholder
navigation={this.props.navigation}
@ -299,8 +323,8 @@ class DiscoverPage extends React.PureComponent {
);
render() {
const { navigation, sortByItem } = this.props;
const { orderBy, showModalTagSelector, showSortPicker } = this.state;
const { navigation, sortByItem, timeItem } = this.props;
const { orderBy, showModalTagSelector, showSortPicker, showTimePicker } = this.state;
return (
<View style={discoverStyle.container}>
@ -318,7 +342,9 @@ class DiscoverPage extends React.PureComponent {
sections={this.buildSections()}
keyExtractor={(item, index) => item}
/>
{!showModalTagSelector && !showSortPicker && <FloatingWalletBalance navigation={navigation} />}
{!showModalTagSelector && !showSortPicker && !showTimePicker && (
<FloatingWalletBalance navigation={navigation} />
)}
{showModalTagSelector && (
<ModalTagSelector
onOverlayPress={() => this.setState({ showModalTagSelector: false })}
@ -334,6 +360,15 @@ class DiscoverPage extends React.PureComponent {
items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
/>
)}
{showTimePicker && (
<ModalPicker
title={__('Content from')}
onOverlayPress={() => this.setState({ showTimePicker: false })}
onItemSelected={this.handleTimeItemSelected}
selectedItem={timeItem}
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}
</View>
);
}

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Lbry, buildURI, normalizeURI } from 'lbry-redux';
import { ActivityIndicator, Button, FlatList, Text, TextInput, View, ScrollView } from 'react-native';
import { ActivityIndicator, Button, FlatList, NativeModules, Text, TextInput, View, ScrollView } from 'react-native';
import { navigateToUri, uriFromFileInfo } from 'utils/helper';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
@ -34,6 +34,8 @@ class DownloadsPage extends React.PureComponent {
const { fetchMyClaims, fileList, pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Library');
fetchMyClaims();
fileList();
};

View file

@ -93,6 +93,7 @@ class FilePage extends React.PureComponent {
onComponentFocused = () => {
StatusBar.setHidden(false);
NativeModules.Firebase.setCurrentScreen('File');
DeviceEventEmitter.addListener('onDownloadStarted', this.handleDownloadStarted);
DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated);

View file

@ -45,6 +45,8 @@ class FirstRunScreen extends React.PureComponent {
});
if (NativeModules.FirstRun) {
NativeModules.Firebase.setCurrentScreen('First Run');
NativeModules.FirstRun.isFirstRun().then(firstRun => {
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
AsyncStorage.removeItem(Constants.KEY_EMAIL_VERIFY_PENDING);

View file

@ -79,6 +79,7 @@ class PublishPage extends React.PureComponent {
canUseCamera: false,
titleFocused: false,
descriptionFocused: false,
loadingVideos: false,
// gallery videos
videos: null,
@ -151,13 +152,20 @@ class PublishPage extends React.PureComponent {
onComponentFocused = () => {
const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Publish');
NativeModules.Gallery.canUseCamera().then(canUseCamera => this.setState({ canUseCamera }));
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => this.setState({ thumbnailPath }));
NativeModules.Gallery.getVideos().then(videos => this.setState({ videos }));
this.setState(
{
loadingVideos: true,
},
() => {
NativeModules.Gallery.getVideos().then(videos => this.setState({ videos, loadingVideos: false }));
}
);
};
getNewUri(name, channel) {
@ -586,7 +594,7 @@ class PublishPage extends React.PureComponent {
render() {
const { balance, navigation, notify, publishFormValues } = this.props;
const { canUseCamera, currentPhase, checkedThumbnails, thumbnailPath, videos } = this.state;
const { canUseCamera, currentPhase, checkedThumbnails, loadingVideos, thumbnailPath, videos } = this.state;
let content;
if (Constants.PHASE_SELECTOR === currentPhase) {
@ -625,13 +633,13 @@ class PublishPage extends React.PureComponent {
</View>
</View>
</View>
{(!videos || !thumbnailPath || checkedThumbnails.length === 0) && (
{(loadingVideos || !thumbnailPath || checkedThumbnails.length === 0) && (
<View style={publishStyle.loadingView}>
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
</View>
)}
{thumbnailPath && (!videos || videos.length === 0) && (
<View style={publishStyle.centered}>
{!loadingVideos && (!videos || videos.length === 0) && (
<View style={publishStyle.relativeCentered}>
<Text style={publishStyle.noVideos}>
We could not find any videos on your device. Take a photo or record a video to get started.
</Text>

View file

@ -1,5 +1,5 @@
import React from 'react';
import { ActivityIndicator, Alert, FlatList, Text, TouchableOpacity, View } from 'react-native';
import { ActivityIndicator, Alert, FlatList, NativeModules, Text, TouchableOpacity, View } from 'react-native';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
@ -35,9 +35,10 @@ class PublishesPage extends React.PureComponent {
onComponentFocused = () => {
const { checkPendingPublishes, fetchMyClaims, pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Publishes');
fetchMyClaims();
checkPendingPublishes();
};

View file

@ -42,6 +42,8 @@ class RewardsPage extends React.PureComponent {
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Rewards');
fetchRewards();
this.setState({

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Lbry, parseURI, normalizeURI, isURIValid } from 'lbry-redux';
import { ActivityIndicator, Button, Text, TextInput, View, ScrollView } from 'react-native';
import { ActivityIndicator, Button, NativeModules, Text, TextInput, View, ScrollView } from 'react-native';
import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
@ -37,6 +37,7 @@ class SearchPage extends React.PureComponent {
const { pushDrawerStack, setPlayerVisible, query, search } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Search');
const searchQuery = query || this.getSearchQuery();
if (searchQuery && searchQuery.trim().length > 0) {

View file

@ -28,6 +28,7 @@ class SettingsPage extends React.PureComponent {
const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Settings');
};
componentDidMount() {

View file

@ -239,6 +239,8 @@ class SplashScreen extends React.PureComponent {
NativeModules.Firebase.track('app_launch', null);
}
NativeModules.Firebase.setCurrentScreen('Splash');
this.props.fetchRewardedContent();
Linking.getInitialURL().then(url => {
if (url) {

View file

@ -14,8 +14,8 @@ import {
selectShowSuggestedSubs,
} from 'lbryinc';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetClientSetting, doSetTimeItem } from 'redux/actions/settings';
import { makeSelectClientSetting, selectTimeItem } from 'redux/selectors/settings';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import SubscriptionsPage from './view';
@ -32,6 +32,7 @@ const select = state => ({
viewMode: selectViewMode(state),
firstRunCompleted: selectFirstRunCompleted(state),
showSuggestedSubs: selectShowSuggestedSubs(state),
timeItem: selectTimeItem(state),
});
const perform = dispatch => ({
@ -41,6 +42,7 @@ const perform = dispatch => ({
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SUBSCRIPTIONS)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
setTimeItem: item => dispatch(doSetTimeItem(item)),
});
export default connect(

View file

@ -11,7 +11,7 @@ import {
View,
} from 'react-native';
import { buildURI, parseURI } from 'lbry-redux';
import { __, uriFromFileInfo } from 'utils/helper';
import { __, getOrderBy } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import Button from 'component/button';
@ -33,6 +33,7 @@ class SubscriptionsPage extends React.PureComponent {
state = {
showingSuggestedSubs: false,
showSortPicker: false,
showTimePicker: false,
orderBy: ['release_time'],
filteredChannels: [],
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting subscriptions by new
@ -63,6 +64,8 @@ class SubscriptionsPage extends React.PureComponent {
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Subscriptions');
doFetchMySubscriptions();
doFetchRecommendedSubscriptions();
};
@ -80,22 +83,13 @@ class SubscriptionsPage extends React.PureComponent {
}
handleSortByItemSelected = item => {
let orderBy = [];
switch (item.name) {
case Constants.SORT_BY_HOT:
orderBy = Constants.DEFAULT_ORDER_BY;
break;
this.setState({ currentSortByItem: item, orderBy: getOrderBy(item), showSortPicker: false });
};
case Constants.SORT_BY_NEW:
orderBy = ['release_time'];
break;
case Constants.SORT_BY_TOP:
orderBy = ['effective_amount'];
break;
}
this.setState({ currentSortByItem: item, orderBy, showSortPicker: false });
handleTimeItemSelected = item => {
const { setTimeItem } = this.props;
setTimeItem(item);
this.setState({ showTimePicker: false });
};
handleChannelSelected = channelUri => {
@ -118,18 +112,17 @@ class SubscriptionsPage extends React.PureComponent {
suggestedChannels,
subscribedChannels,
allSubscriptions,
viewMode,
doSetViewMode,
doCompleteFirstRun,
doShowSuggestedSubs,
loading,
loadingSuggested,
firstRunCompleted,
doCompleteFirstRun,
doShowSuggestedSubs,
showSuggestedSubs,
timeItem,
unreadSubscriptions,
navigation,
} = this.props;
const { currentSortByItem, filteredChannels } = this.state;
const { currentSortByItem, filteredChannels, showSortPicker, showTimePicker } = this.state;
const numberOfSubscriptions = subscribedChannels ? subscribedChannels.length : 0;
const hasSubscriptions = numberOfSubscriptions > 0;
@ -155,7 +148,9 @@ class SubscriptionsPage extends React.PureComponent {
<UriBar navigation={navigation} belowOverlay={this.state.showSortPicker} />
<View style={subscriptionsStyle.titleRow}>
<Text style={subscriptionsStyle.pageTitle}>Channels you follow</Text>
{!this.state.showingSuggestedSubs && hasSubscriptions && (
</View>
{!this.state.showingSuggestedSubs && hasSubscriptions && (
<View style={subscriptionsStyle.pickerRow}>
<TouchableOpacity
style={subscriptionsStyle.tagSortBy}
onPress={() => this.setState({ showSortPicker: true })}
@ -163,8 +158,18 @@ class SubscriptionsPage extends React.PureComponent {
<Text style={subscriptionsStyle.tagSortText}>{currentSortByItem.label.split(' ')[0]}</Text>
<Icon style={subscriptionsStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
</View>
{Constants.SORT_BY_TOP === currentSortByItem.name && (
<TouchableOpacity
style={subscriptionsStyle.tagSortBy}
onPress={() => this.setState({ showTimePicker: true })}
>
<Text style={subscriptionsStyle.tagSortText}>{timeItem.label}</Text>
<Icon style={subscriptionsStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
</View>
)}
{!this.state.showingSuggestedSubs && hasSubscriptions && !loading && (
<View style={subscriptionsStyle.subContainer}>
<SubscribedChannelList
@ -175,6 +180,7 @@ class SubscriptionsPage extends React.PureComponent {
style={subscriptionsStyle.claimList}
channelIds={channelIds}
orderBy={this.state.orderBy}
time={timeItem.name}
navigation={navigation}
orientation={Constants.ORIENTATION_VERTICAL}
/>
@ -217,8 +223,8 @@ class SubscriptionsPage extends React.PureComponent {
</View>
)}
{!this.state.showSortPicker && <FloatingWalletBalance navigation={navigation} />}
{this.state.showSortPicker && (
{!showSortPicker && !showTimePicker && <FloatingWalletBalance navigation={navigation} />}
{showSortPicker && (
<ModalPicker
title={__('Sort content by')}
onOverlayPress={() => this.setState({ showSortPicker: false })}
@ -227,6 +233,15 @@ class SubscriptionsPage extends React.PureComponent {
items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
/>
)}
{showTimePicker && (
<ModalPicker
title={__('Content from')}
onOverlayPress={() => this.setState({ showTimePicker: false })}
onItemSelected={this.handleTimeItemSelected}
selectedItem={timeItem}
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}
</View>
);
}

View file

@ -1,19 +1,24 @@
import { connect } from 'react-redux';
import { selectFollowedTags, doToggleTagFollow } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetSortByItem } from 'redux/actions/settings';
import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import { selectSortByItem } from 'redux/selectors/settings';
import { selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import TagPage from './view';
const select = state => ({
currentRoute: selectCurrentRoute(state),
sortByItem: selectSortByItem(state),
timeItem: selectTimeItem(state),
followedTags: selectFollowedTags(state),
});
const perform = dispatch => ({
pushDrawerStack: (routeName, params) => dispatch(doPushDrawerStack(routeName, params)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
setSortByItem: item => dispatch(doSetSortByItem(item)),
setTimeItem: item => dispatch(doSetTimeItem(item)),
toggleTagFollow: tag => dispatch(doToggleTagFollow(tag)),
});
export default connect(

View file

@ -12,6 +12,7 @@ import fileListStyle from 'styles/fileList';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import FloatingWalletBalance from 'component/floatingWalletBalance';
import Link from 'component/link';
import ModalPicker from 'component/modalPicker';
import UriBar from 'component/uriBar';
@ -21,8 +22,6 @@ class TagPage extends React.PureComponent {
showSortPicker: false,
showTimePicker: false,
orderBy: Constants.DEFAULT_ORDER_BY,
time: Constants.TIME_WEEK,
currentTimeItem: Constants.CLAIM_SEARCH_TIME_ITEMS[1],
};
didFocusListener;
@ -41,9 +40,10 @@ class TagPage extends React.PureComponent {
onComponentFocused = () => {
const { navigation, pushDrawerStack, setPlayerVisible, sortByItem } = this.props;
const { tag } = navigation.state.params;
this.setState({ tag, sortByItem, orderBy: getOrderBy(sortByItem) });
this.setState({ tag, orderBy: getOrderBy(sortByItem) });
pushDrawerStack(Constants.DRAWER_ROUTE_TAG, navigation.state.params);
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Tag');
};
componentDidMount() {
@ -65,42 +65,80 @@ class TagPage extends React.PureComponent {
};
handleTimeItemSelected = item => {
this.setState({ currentTimeItem: item, time: item.name, showTimePicker: false });
const { setTimeItem } = this.props;
setTimeItem(item);
this.setState({ showTimePicker: false });
};
isFollowingTag = tag => {
const { followedTags } = this.props;
return followedTags.map(tag => tag.name).includes(tag);
};
handleFollowTagToggle = () => {
const { toggleTagFollow } = this.props;
const { tag } = this.state;
const isFollowing = this.isFollowingTag(tag);
if (isFollowing) {
// unfollow
NativeModules.Firebase.track('tag_unfollow', { tag });
} else {
// follow
NativeModules.Firebase.track('tag_follow', { tag });
}
toggleTagFollow(tag);
if (window.persistor) {
window.persistor.flush();
}
};
listHeader = () => {
const { sortByItem, timeItem } = this.props;
const { tag } = this.state;
return (
<View style={discoverStyle.listHeader}>
<View style={discoverStyle.titleRow}>
<Text style={discoverStyle.pageTitle}>{formatTagTitle(tag)}</Text>
</View>
<View style={discoverStyle.pickerRow}>
<View style={discoverStyle.leftPickerRow}>
<TouchableOpacity style={discoverStyle.tagSortBy} onPress={() => this.setState({ showSortPicker: true })}>
<Text style={discoverStyle.tagSortText}>{sortByItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
{Constants.SORT_BY_TOP === sortByItem.name && (
<TouchableOpacity style={discoverStyle.tagTime} onPress={() => this.setState({ showTimePicker: true })}>
<Text style={discoverStyle.tagSortText}>{timeItem.label}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
</View>
<Link
style={discoverStyle.customizeLink}
text={this.isFollowingTag(tag) ? 'Unfollow' : 'Follow'}
onPress={this.handleFollowTagToggle}
/>
</View>
</View>
);
};
render() {
const { navigation, sortByItem } = this.props;
const { tag, currentTimeItem, showSortPicker, showTimePicker } = this.state;
const { navigation, sortByItem, timeItem } = this.props;
const { tag, showSortPicker, showTimePicker } = this.state;
return (
<View style={discoverStyle.container}>
<UriBar navigation={navigation} belowOverlay={showSortPicker || showTimePicker} />
{this.state.tag && (
<ClaimList
ListHeaderComponent={
<View style={discoverStyle.tagTitleRow}>
<Text style={discoverStyle.tagPageTitle}>{formatTagTitle(tag)}</Text>
{Constants.SORT_BY_TOP === sortByItem.name && (
<TouchableOpacity
style={discoverStyle.tagTime}
onPress={() => this.setState({ showTimePicker: true })}
>
<Text style={discoverStyle.tagSortText}>{currentTimeItem.label}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
<TouchableOpacity
style={discoverStyle.tagSortBy}
onPress={() => this.setState({ showSortPicker: true })}
>
<Text style={discoverStyle.tagSortText}>{sortByItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
</View>
}
ListHeaderComponent={this.listHeader}
style={discoverStyle.tagPageClaimList}
orderBy={this.state.orderBy}
time={this.state.time}
time={timeItem.name}
tags={[tag]}
navigation={navigation}
orientation={Constants.ORIENTATION_VERTICAL}
@ -112,7 +150,7 @@ class TagPage extends React.PureComponent {
title={__('Sort content by')}
onOverlayPress={() => this.setState({ showSortPicker: false })}
onItemSelected={this.handleSortByItemSelected}
selectedItem={this.state.sortByItem}
selectedItem={sortByItem}
items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
/>
)}
@ -121,7 +159,7 @@ class TagPage extends React.PureComponent {
title={__('Content from')}
onOverlayPress={() => this.setState({ showTimePicker: false })}
onItemSelected={this.handleTimeItemSelected}
selectedItem={this.state.currentTimeItem}
selectedItem={timeItem}
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}

View file

@ -1,5 +1,5 @@
import React from 'react';
import { View, ScrollView, Text } from 'react-native';
import { NativeModules, View, ScrollView, Text } from 'react-native';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import TransactionList from 'component/transactionList';
import UriBar from 'component/uriBar';
@ -23,6 +23,8 @@ class TransactionHistoryPage extends React.PureComponent {
const { fetchTransactions, pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Transaction History');
fetchTransactions();
};

View file

@ -1,18 +1,23 @@
import { connect } from 'react-redux';
import { selectFollowedTags } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import { selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import TrendingPage from './view';
const select = state => ({
currentRoute: selectCurrentRoute(state),
sortByItem: selectSortByItem(state),
timeItem: selectTimeItem(state),
followedTags: selectFollowedTags(state),
});
const perform = dispatch => ({
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_TRENDING)),
pushDrawerStack: (routeName, params) => dispatch(doPushDrawerStack(routeName, params)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
setSortByItem: item => dispatch(doSetSortByItem(item)),
setTimeItem: item => dispatch(doSetTimeItem(item)),
});
export default connect(

View file

@ -1,6 +1,7 @@
import React from 'react';
import { ActivityIndicator, NativeModules, FlatList, Text, TouchableOpacity, View } from 'react-native';
import { DEFAULT_FOLLOWED_TAGS, normalizeURI } from 'lbry-redux';
import { normalizeURI } from 'lbry-redux';
import { getOrderBy } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import ClaimList from 'component/claimList';
@ -23,7 +24,10 @@ const TRENDING_FOR_ITEMS = [
class TrendingPage extends React.PureComponent {
state = {
showModalTagSelector: false,
showSortByPicker: false,
showTimePicker: false,
showTrendingForPicker: false,
orderBy: Constants.DEFAULT_ORDER_BY,
currentTrendingForItem: TRENDING_FOR_ITEMS[0],
};
@ -41,9 +45,12 @@ class TrendingPage extends React.PureComponent {
}
onComponentFocused = () => {
const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
const { pushDrawerStack, setPlayerVisible, navigation } = this.props;
const { filterForTags } = navigation.state.params ? navigation.state.params : { filterForTags: false };
this.setState({ currentTrendingForItem: TRENDING_FOR_ITEMS[filterForTags ? 1 : 0] });
pushDrawerStack(Constants.DRAWER_ROUTE_TRENDING, navigation.state.params);
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('All content');
};
componentDidMount() {
@ -62,44 +69,96 @@ class TrendingPage extends React.PureComponent {
this.setState({ currentTrendingForItem: item, showTrendingForPicker: false });
};
handleSortByItemSelected = item => {
const { setSortByItem } = this.props;
setSortByItem(item);
this.setState({ orderBy: getOrderBy(item), showSortPicker: false });
};
handleTimeItemSelected = item => {
const { setTimeItem } = this.props;
setTimeItem(item);
this.setState({ showTimePicker: false });
};
listHeader = () => {
const { sortByItem, timeItem } = this.props;
const { currentTrendingForItem } = this.state;
const sortByTop = Constants.SORT_BY_TOP === sortByItem.name;
return (
<View style={discoverStyle.listHeader}>
<View style={discoverStyle.titleRow}>
<Text style={discoverStyle.pageTitle}>All content</Text>
</View>
<View style={discoverStyle.pickerRow}>
<View style={discoverStyle.leftPickerRow}>
<TouchableOpacity
style={discoverStyle.allTagSortBy}
onPress={() => this.setState({ showSortPicker: true })}
>
<Text style={discoverStyle.tagSortText}>{sortByItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
<Text style={discoverStyle.pickerLabel}>for</Text>
<TouchableOpacity
style={discoverStyle.allTagSortBy}
onPress={() => this.setState({ showTrendingForPicker: true })}
>
<Text style={discoverStyle.tagSortText}>{currentTrendingForItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
{sortByTop && <Text style={discoverStyle.pickerLabel}>in the</Text>}
{sortByTop && (
<TouchableOpacity style={discoverStyle.tagTime} onPress={() => this.setState({ showTimePicker: true })}>
<Text style={discoverStyle.tagSortText}>{timeItem.label}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
)}
</View>
{TRENDING_FOR_ITEMS[1].name === currentTrendingForItem.name && (
<Link
style={discoverStyle.customizeLink}
text={'Customize'}
onPress={() => this.setState({ showModalTagSelector: true })}
/>
)}
</View>
</View>
);
};
render() {
const { followedTags, navigation } = this.props;
const { currentTrendingForItem, showModalTagSelector, showTrendingForPicker } = this.state;
const { followedTags, navigation, sortByItem, timeItem } = this.props;
const {
currentTrendingForItem,
orderBy,
showModalTagSelector,
showSortPicker,
showTimePicker,
showTrendingForPicker,
} = this.state;
const filteredForTags = TRENDING_FOR_ITEMS[1].name === currentTrendingForItem.name;
return (
<View style={discoverStyle.container}>
<UriBar navigation={navigation} />
<ClaimList
ListHeaderComponent={
<View style={discoverStyle.titleRow}>
<Text style={discoverStyle.pageTitle}>Trending</Text>
<View style={discoverStyle.rightTitleRow}>
{TRENDING_FOR_ITEMS[1].name === currentTrendingForItem.name && (
<Link
style={discoverStyle.customizeLink}
text={'Customize'}
onPress={() => this.setState({ showModalTagSelector: true })}
/>
)}
<TouchableOpacity
style={discoverStyle.tagSortBy}
onPress={() => this.setState({ showTrendingForPicker: true })}
>
<Text style={discoverStyle.tagSortText}>{currentTrendingForItem.label.split(' ')[0]}</Text>
<Icon style={discoverStyle.tagSortIcon} name={'sort-down'} size={14} />
</TouchableOpacity>
</View>
</View>
}
ListHeaderComponent={this.listHeader}
style={discoverStyle.verticalClaimList}
orderBy={Constants.DEFAULT_ORDER_BY}
trendingForAll={TRENDING_FOR_ITEMS[0].name === currentTrendingForItem.name}
tags={followedTags.map(tag => tag.name)}
time={null}
orderBy={orderBy}
tags={filteredForTags ? followedTags.map(tag => tag.name) : null}
time={timeItem.name}
navigation={navigation}
orientation={Constants.ORIENTATION_VERTICAL}
/>
{!showModalTagSelector && <FloatingWalletBalance navigation={navigation} />}
{!showModalTagSelector && !showTrendingForPicker && !showSortPicker && !showTimePicker && (
<FloatingWalletBalance navigation={navigation} />
)}
{showModalTagSelector && (
<ModalTagSelector
onOverlayPress={() => this.setState({ showModalTagSelector: false })}
@ -108,13 +167,31 @@ class TrendingPage extends React.PureComponent {
)}
{showTrendingForPicker && (
<ModalPicker
title={'Trending for'}
title={'Filter for'}
onOverlayPress={() => this.setState({ showTrendingForPicker: false })}
onItemSelected={this.handleTrendingForItemSelected}
selectedItem={currentTrendingForItem}
items={TRENDING_FOR_ITEMS}
/>
)}
{showSortPicker && (
<ModalPicker
title={__('Sort content by')}
onOverlayPress={() => this.setState({ showSortPicker: false })}
onItemSelected={this.handleSortByItemSelected}
selectedItem={sortByItem}
items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
/>
)}
{showTimePicker && (
<ModalPicker
title={__('Content from')}
onOverlayPress={() => this.setState({ showTimePicker: false })}
onItemSelected={this.handleTimeItemSelected}
selectedItem={timeItem}
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}
</View>
);
}

View file

@ -29,6 +29,7 @@ class VerificationScreen extends React.PureComponent {
componentDidMount() {
const { user } = this.props;
this.checkVerificationStatus(user);
NativeModules.Firebase.setCurrentScreen('Verification');
}
setEmailVerificationPhase = value => {

View file

@ -42,6 +42,7 @@ class WalletPage extends React.PureComponent {
const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Wallet');
const { deviceWalletSynced, getSync, user } = this.props;
if (deviceWalletSynced && user && user.has_verified_email) {

View file

@ -27,3 +27,14 @@ export function doSetSortByItem(item) {
});
};
}
export function doSetTimeItem(item) {
return dispatch => {
dispatch({
type: Constants.ACTION_TIME_ITEM_CHANGED,
data: {
name: item.name,
},
});
};
}

View file

@ -5,6 +5,7 @@ const reducers = {};
const defaultState = {
clientSettings: {},
sortByItemName: Constants.SORT_BY_HOT,
timeItemName: Constants.TIME_WEEK,
};
reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => {
@ -23,6 +24,11 @@ reducers[Constants.ACTION_SORT_BY_ITEM_CHANGED] = (state, action) =>
sortByItemName: action.data.name,
});
reducers[Constants.ACTION_TIME_ITEM_CHANGED] = (state, action) =>
Object.assign({}, state, {
timeItemName: action.data.name,
});
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);

View file

@ -1,6 +1,6 @@
import { SETTINGS } from 'lbry-redux';
import { createSelector } from 'reselect';
import { getSortByItemForName } from 'utils/helper';
import { getSortByItemForName, getTimeItemForName } from 'utils/helper';
const selectState = state => state.settings || {};
@ -19,6 +19,11 @@ export const selectSortByItem = createSelector(
state => getSortByItemForName(state.sortByItemName)
);
export const selectTimeItem = createSelector(
selectState,
state => getTimeItemForName(state.timeItemName)
);
export const makeSelectClientSetting = setting =>
createSelector(
selectClientSettings,

View file

@ -33,7 +33,7 @@ const buttonStyle = StyleSheet.create({
fontSize: 14,
},
textWithIcon: {
marginLeft: 8,
marginLeft: 4,
},
});

View file

@ -31,13 +31,11 @@ const channelPageStyle = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
infoText: {
fontFamily: 'Inter-UI-Regular',
fontSize: 20,
textAlign: 'center',
marginLeft: 10,
},
pageButtons: {
width: '100%',
@ -69,7 +67,7 @@ const channelPageStyle = StyleSheet.create({
subscribeButtonContainer: {
position: 'absolute',
flexDirection: 'row',
left: 8,
right: 8,
bottom: -90,
zIndex: 100,
},
@ -148,11 +146,25 @@ const channelPageStyle = StyleSheet.create({
left: 24,
bottom: -40,
zIndex: 100,
alignItems: 'center',
justifyContent: 'center',
},
avatarImage: {
width: '100%',
height: '100%',
},
listHeader: {
marginTop: 16,
marginBottom: 8,
marginLeft: 16,
marginRight: 16,
},
claimList: {
flex: 1,
},
claimListContent: {
paddingTop: 16,
},
});
export default channelPageStyle;

View file

@ -28,16 +28,25 @@ const discoverStyle = StyleSheet.create({
scrollContainer: {
flex: 1,
},
listHeader: {
marginBottom: 8,
marginLeft: 16,
marginRight: 16,
},
titleRow: {
flexDirection: 'row',
marginTop: 76,
marginBottom: 8,
alignItems: 'center',
justifyContent: 'space-between',
marginLeft: 16,
marginRight: 16,
},
rightTitleRow: {
pickerRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 8,
},
leftPickerRow: {
flexDirection: 'row',
alignItems: 'center',
},
@ -48,7 +57,6 @@ const discoverStyle = StyleSheet.create({
customizeLink: {
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
marginRight: 48,
},
trendingContainer: {
flex: 1,
@ -258,7 +266,12 @@ const discoverStyle = StyleSheet.create({
tagSortBy: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 4,
marginRight: 24,
},
allTagSortBy: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 8,
},
tagTime: {
flexDirection: 'row',
@ -272,6 +285,53 @@ const discoverStyle = StyleSheet.create({
tagSortIcon: {
marginTop: -6,
},
pickerLabel: {
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
color: Colors.DescriptionGrey,
marginRight: 6,
},
menuScrollContent: {
paddingTop: 16,
},
menuGroup: {
marginTop: 8,
marginBottom: 8,
},
menuGroupName: {
fontFamily: 'Inter-UI-Regular',
fontSize: 12,
color: Colors.DescriptionGrey,
textTransform: 'uppercase',
marginBottom: 4,
marginLeft: 16,
marginRight: 16,
},
menuItemTouchArea: {
flex: 1,
flexDirection: 'row',
paddingTop: 12,
paddingBottom: 12,
paddingLeft: 16,
paddingRight: 16,
alignItems: 'center',
},
menuItemTouchAreaFocused: {
backgroundColor: Colors.VeryLightGrey,
},
menuItemFocused: {
color: Colors.LbryGreen,
},
menuItemIcon: {
alignItems: 'center',
justifyContent: 'center',
width: 24,
},
menuItem: {
marginLeft: 8,
fontFamily: 'Inter-UI-Regular',
fontSize: 16,
},
});
export default discoverStyle;

View file

@ -341,6 +341,11 @@ const publishStyle = StyleSheet.create({
fontSize: 14,
marginLeft: 8,
},
relativeCentered: {
alignItems: 'center',
justifyContent: 'center',
padding: 16,
},
centered: {
position: 'absolute',
left: 0,

View file

@ -156,10 +156,18 @@ const subscriptionsStyle = StyleSheet.create({
alignItems: 'center',
justifyContent: 'space-between',
},
pickerRow: {
flexDirection: 'row',
alignItems: 'center',
marginLeft: 16,
marginRight: 16,
marginTop: 8,
marginBottom: 8,
},
tagSortBy: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 4,
marginRight: 24,
},
tagSortText: {
fontFamily: 'Inter-UI-Regular',

View file

@ -217,6 +217,16 @@ export function getSortByItemForName(name) {
return null;
}
export function getTimeItemForName(name) {
for (let i = 0; i < Constants.CLAIM_SEARCH_TIME_ITEMS.length; i++) {
if (name === Constants.CLAIM_SEARCH_TIME_ITEMS[i].name) {
return Constants.CLAIM_SEARCH_TIME_ITEMS[i];
}
}
return null;
}
export function getOrderBy(item) {
let orderBy = [];
switch (item.name) {