diff --git a/android b/android index ff30e7f..cc3055f 160000 --- a/android +++ b/android @@ -1 +1 @@ -Subproject commit ff30e7f6a4358fd997a9e6d9f75bfe6959eafcb6 +Subproject commit cc3055f1c98c4dd5e5bbccdd9d4a39a5a37fdf82 diff --git a/src/component/AppNavigator.js b/src/component/AppNavigator.js index d003376..1d91b2b 100644 --- a/src/component/AppNavigator.js +++ b/src/component/AppNavigator.js @@ -4,6 +4,7 @@ import ChannelCreatorPage from 'page/channelCreator'; import DiscoverPage from 'page/discover'; import DownloadsPage from 'page/downloads'; import DrawerContent from 'component/drawerContent'; +import EditorsChoicePage from 'page/editorsChoice'; import FilePage from 'page/file'; import LiteFilePage from 'page/liteFile'; import FirstRunScreen from 'page/firstRun'; @@ -164,6 +165,13 @@ const drawer = createDrawerNavigator( drawerIcon: ({ tintColor }) => <Icon name="home" size={drawerIconSize} style={{ color: tintColor }} />, }, }, + EditorsChoice: { + screen: EditorsChoicePage, + navigationOptions: { + title: "Editors' Choice", + drawerIcon: ({ tintColor }) => <Icon name="star" size={drawerIconSize} style={{ color: tintColor }} />, + }, + }, Discover: { screen: DiscoverPage, navigationOptions: ({ navigation }) => ({ diff --git a/src/component/claimList/view.js b/src/component/claimList/view.js index e985067..61fb5ca 100644 --- a/src/component/claimList/view.js +++ b/src/component/claimList/view.js @@ -3,6 +3,7 @@ import NavigationActions from 'react-navigation'; import { ActivityIndicator, FlatList, Text, TouchableOpacity, View } from 'react-native'; import { MATURE_TAGS, normalizeURI, createNormalizedClaimSearchKey } from 'lbry-redux'; import _ from 'lodash'; +import EditorsChoiceItem from 'component/editorsChoiceItem'; import FileItem from 'component/fileItem'; import FileListItem from 'component/fileListItem'; import Icon from 'react-native-vector-icons/FontAwesome5'; @@ -72,7 +73,7 @@ class ClaimList extends React.PureComponent { buildClaimSearchOptions() { const { orderBy, channelIds, showNsfwContent, tags, time } = this.props; - const { currentPage, subscriptionsView } = this.state; + const { currentPage } = this.state; const options = { order_by: orderBy, @@ -101,7 +102,7 @@ class ClaimList extends React.PureComponent { return `>${Math.floor( moment() .subtract(1, time) - .unix() + .unix(), )}`; }; @@ -120,7 +121,7 @@ class ClaimList extends React.PureComponent { const uris = claimSearchByQuery[claimSearchKey]; if ( lastPageReached[claimSearchKey] || - ((uris.length > 0 && uris.length < Constants.DEFAULT_PAGE_SIZE) || uris.length >= softLimit) + (uris.length > 0 && uris.length < Constants.DEFAULT_PAGE_SIZE) || uris.length >= softLimit ) { return; } @@ -174,6 +175,12 @@ class ClaimList extends React.PureComponent { ); }; + renderEditorsChoiceItem = ({ item }) => { + const { navigation } = this.props; + + return <EditorsChoiceItem style={discoverStyle.fileItem} key={item} uri={normalizeURI(item)} />; + }; + renderHorizontalItem = ({ item }) => { const { navigation } = this.props; @@ -195,6 +202,7 @@ class ClaimList extends React.PureComponent { render() { const { ListHeaderComponent, + editorsChoice, loading, morePlaceholder, navigation, @@ -226,7 +234,7 @@ class ClaimList extends React.PureComponent { initialNumToRender={10} maxToRenderPerBatch={20} removeClippedSubviews - renderItem={this.renderVerticalItem} + renderItem={editorsChoice ? this.renderEditorsChoiceItem : this.renderVerticalItem} data={uris} keyExtractor={(item, index) => item} onEndReached={this.handleVerticalEndReached} diff --git a/src/component/drawerContent/view.js b/src/component/drawerContent/view.js index cf224c2..941ce47 100644 --- a/src/component/drawerContent/view.js +++ b/src/component/drawerContent/view.js @@ -12,6 +12,7 @@ import { formatUsd } from 'utils/helper'; const groupedMenuItems = { 'Find content': [ { icon: 'heart', solid: true, label: 'Following', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS }, + { icon: 'star', solid: true, label: "Editor's Choice", route: Constants.DRAWER_ROUTE_EDITORS_CHOICE }, { icon: 'hashtag', label: 'Your Tags', route: Constants.DRAWER_ROUTE_DISCOVER }, { icon: 'globe-americas', label: 'All Content', route: Constants.DRAWER_ROUTE_TRENDING }, ], diff --git a/src/component/editorsChoiceItem/index.js b/src/component/editorsChoiceItem/index.js new file mode 100644 index 0000000..495ec7b --- /dev/null +++ b/src/component/editorsChoiceItem/index.js @@ -0,0 +1,22 @@ +import { connect } from 'react-redux'; +import { + doResolveUri, + makeSelectClaimForUri, + makeSelectMetadataForUri, + makeSelectTitleForUri, + makeSelectThumbnailForUri, +} from 'lbry-redux'; +import EditorsChoiceItem from './view'; + +const select = (state, props) => ({ + claim: makeSelectClaimForUri(props.uri)(state), + metadata: makeSelectMetadataForUri(props.uri)(state), + title: makeSelectTitleForUri(props.uri)(state), + thumbnail: makeSelectThumbnailForUri(props.uri)(state), +}); + +const perform = dispatch => ({ + resolveUri: uri => dispatch(doResolveUri(uri)), +}); + +export default connect(select, perform)(EditorsChoiceItem); diff --git a/src/component/editorsChoiceItem/view.js b/src/component/editorsChoiceItem/view.js new file mode 100644 index 0000000..b076cf0 --- /dev/null +++ b/src/component/editorsChoiceItem/view.js @@ -0,0 +1,67 @@ +import React from 'react'; +import { normalizeURI, parseURI } from 'lbry-redux'; +import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native'; +import { navigateToUri } from 'utils/helper'; +import Colors from 'styles/colors'; +import ChannelIconItem from 'component/channelIconItem'; +import channelIconStyle from 'styles/channelIcon'; +import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api +import DateTime from 'component/dateTime'; +import FastImage from 'react-native-fast-image'; +import FileItemMedia from 'component/fileItemMedia'; +import FilePrice from 'component/filePrice'; +import Icon from 'react-native-vector-icons/FontAwesome5'; +import Link from 'component/link'; +import NsfwOverlay from 'component/nsfwOverlay'; +import ProgressBar from 'component/progressBar'; +import editorsChoiceStyle from 'styles/editorsChoice'; + +class EditorsChoiceItem extends React.PureComponent { + componentDidMount() { + const { claim, resolveUri, uri, batchResolve } = this.props; + if (!claim && !batchResolve) { + resolveUri(uri); + } + } + + defaultOnPress = () => { + const { autoplay, claim, navigation, uri } = this.props; + navigateToUri(navigation, uri, { autoplay }, false, claim ? claim.permanent_url : null); + }; + + onPressHandler = () => { + const { claim, onPress } = this.props; + if (onPress) { + onPress(claim); + } else { + this.defaultOnPress(); + } + }; + + render() { + const { metadata, title, thumbnail } = this.props; + + return ( + <TouchableOpacity style={editorsChoiceStyle.item}> + <Text style={editorsChoiceStyle.title} numberOfLines={1}> + {title} + </Text> + <View style={editorsChoiceStyle.itemRow}> + <FastImage + style={editorsChoiceStyle.thumbnail} + resizeMode={FastImage.resizeMode.cover} + source={{ uri: thumbnail }} + /> + + <View style={editorsChoiceStyle.detailsContainer}> + <Text style={editorsChoiceStyle.description} numberOfLines={5}> + {metadata.description ? metadata.description : __('No description available')} + </Text> + </View> + </View> + </TouchableOpacity> + ); + } +} + +export default EditorsChoiceItem; diff --git a/src/constants.js b/src/constants.js index e3ac8df..38b68b9 100644 --- a/src/constants.js +++ b/src/constants.js @@ -81,6 +81,7 @@ const Constants = { PAGE_WALLET: 'wallet', DRAWER_ROUTE_DISCOVER: 'Discover', + DRAWER_ROUTE_EDITORS_CHOICE: 'EditorsChoice', DRAWER_ROUTE_TRENDING: 'Trending', DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions', DRAWER_ROUTE_MY_LBRY: 'Downloads', @@ -159,6 +160,7 @@ export default Constants; export const DrawerRoutes = [ Constants.DRAWER_ROUTE_DISCOVER, + Constants.DRAWER_ROUTE_EDITORS_CHOICE, Constants.DRAWER_ROUTE_TRENDING, Constants.DRAWER_ROUTE_SUBSCRIPTIONS, Constants.DRAWER_ROUTE_MY_LBRY, diff --git a/src/page/editorsChoice/index.js b/src/page/editorsChoice/index.js new file mode 100644 index 0000000..978e098 --- /dev/null +++ b/src/page/editorsChoice/index.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; +import { selectCurrentRoute } from 'redux/selectors/drawer'; +import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api +import EditorsChoicePage from './view'; + +const select = state => ({ + currentRoute: selectCurrentRoute(state), +}); + +const perform = dispatch => ({ + pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_EDITORS_CHOICE)), + setPlayerVisible: () => dispatch(doSetPlayerVisible(false)), +}); + +export default connect(select, perform)(EditorsChoicePage); diff --git a/src/page/editorsChoice/view.js b/src/page/editorsChoice/view.js new file mode 100644 index 0000000..0869638 --- /dev/null +++ b/src/page/editorsChoice/view.js @@ -0,0 +1,90 @@ +import React from 'react'; +import { ActivityIndicator, NativeModules, FlatList, Text, TouchableOpacity, ScrollView, View } from 'react-native'; +import { DEFAULT_FOLLOWED_TAGS, normalizeURI } from 'lbry-redux'; +import { formatTagTitle, getOrderBy } from 'utils/helper'; +import AsyncStorage from '@react-native-community/async-storage'; +import moment from 'moment'; +import ClaimList from 'component/claimList'; +import FileItem from 'component/fileItem'; +import Icon from 'react-native-vector-icons/FontAwesome5'; +import discoverStyle from 'styles/discover'; +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 SdkLoadingStatus from 'component/sdkLoadingStatus'; +import UriBar from 'component/uriBar'; +import editorsChoiceStyle from 'styles/editorsChoice'; + +class EditorsChoicePage extends React.PureComponent { + onComponentFocused = () => { + const { pushDrawerStack, setPlayerVisible } = this.props; + pushDrawerStack(); + setPlayerVisible(); + NativeModules.Firebase.setCurrentScreen('EditorsChoice'); + }; + + componentDidMount() { + this.onComponentFocused(); + } + + componentWillReceiveProps(nextProps) { + const { currentRoute } = nextProps; + const { currentRoute: prevRoute } = this.props; + if (Constants.DRAWER_ROUTE_EDITORS_CHOICE === currentRoute && currentRoute !== prevRoute) { + this.onComponentFocused(); + } + } + + render() { + const { navigation } = this.props; + + return ( + <View style={editorsChoiceStyle.container}> + <UriBar navigation={navigation} /> + + <ScrollView style={editorsChoiceStyle.categories} contentContainerStyle={editorsChoiceStyle.categoriesContent}> + <Text style={editorsChoiceStyle.category}>{__('Short Films')}</Text> + <ClaimList + style={editorsChoiceStyle.claimList} + channelIds={['7056f8267188fc49cd3f7162b4115d9e3c8216f6']} + editorsChoice + navigation={navigation} + orientation={Constants.ORIENTATION_VERTICAL} + /> + + <Text style={editorsChoiceStyle.category}>{__('Feature-Length Films')}</Text> + <ClaimList + style={editorsChoiceStyle.claimList} + channelIds={['7aad6f36f61da95cb02471fae55f736b28e3bca7']} + editorsChoice + navigation={navigation} + orientation={Constants.ORIENTATION_VERTICAL} + /> + + <Text style={editorsChoiceStyle.category}>{__('Documentaries')}</Text> + <ClaimList + style={editorsChoiceStyle.claimList} + channelIds={['d57c606e11462e821d5596430c336b58716193bb']} + editorsChoice + navigation={navigation} + orientation={Constants.ORIENTATION_VERTICAL} + /> + + <Text style={editorsChoiceStyle.category}>{__('Episodic Content')}</Text> + <ClaimList + style={editorsChoiceStyle.claimList} + channelIds={['ea5fc1bd3e1335776fe2641a539a47850606d7db']} + editorsChoice + navigation={navigation} + orientation={Constants.ORIENTATION_VERTICAL} + /> + </ScrollView> + </View> + ); + } +} + +export default EditorsChoicePage; diff --git a/src/styles/editorsChoice.js b/src/styles/editorsChoice.js new file mode 100644 index 0000000..5aa6a83 --- /dev/null +++ b/src/styles/editorsChoice.js @@ -0,0 +1,64 @@ +import { Dimensions, PixelRatio, StyleSheet } from 'react-native'; +import { mediaWidth, mediaHeight } from './discover'; +import Colors from './colors'; + +const screenDimension = Dimensions.get('window'); +const screenWidth = screenDimension.width; +const screenHeight = screenDimension.height; +const screenWidthPixels = PixelRatio.getPixelSizeForLayoutSize(screenWidth); +const screenHeightPixels = PixelRatio.getPixelSizeForLayoutSize(screenHeight); +const verticalAdjust = screenHeightPixels > 1280 && screenHeightPixels <= 1920 ? 6 : 0; +const thumbnailWidth = screenWidthPixels <= 720 ? 144 : 156; +// taller thumbnails +const thumbnailHeight = (screenWidth / screenHeight) * thumbnailWidth - verticalAdjust; + +const editorsChoiceStyle = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Colors.PageBackground, + }, + categories: { + marginTop: 60, + }, + categoriesContent: { + padding: 16, + }, + item: { + flex: 1, + marginTop: 8, + marginBottom: 12, + }, + itemRow: { + alignItems: 'center', + flexDirection: 'row', + }, + category: { + fontFamily: 'Inter-SemiBold', + fontSize: 20, + color: Colors.LbryGreen, + }, + thumbnail: { + width: thumbnailWidth, + height: thumbnailHeight, + marginRight: screenWidthPixels <= 720 ? 10 : 12, + alignItems: 'center', + justifyContent: 'center', + }, + detailsContainer: { + flex: 1, + paddingLeft: 2, + paddingRight: 2, + }, + title: { + fontFamily: 'Inter-SemiBold', + fontSize: 16, + marginBottom: 4, + }, + description: { + fontFamily: 'Inter-Regular', + fontSize: 12, + marginTop: 2, + }, +}); + +export default editorsChoiceStyle;