diff --git a/package-lock.json b/package-lock.json
index 9421550..1842396 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1462,6 +1462,32 @@
}
}
},
+ "babel-eslint": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz",
+ "integrity": "sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.0.0",
+ "@babel/traverse": "^7.0.0",
+ "@babel/types": "^7.0.0",
+ "eslint-scope": "3.7.1",
+ "eslint-visitor-keys": "^1.0.0"
+ },
+ "dependencies": {
+ "eslint-scope": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
+ "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ }
+ }
+ },
"babel-helper-bindify-decorators": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz",
@@ -2521,6 +2547,12 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I="
},
+ "ci-info": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
+ "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
+ "dev": true
+ },
"class-utils": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@@ -3769,7 +3801,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -4134,7 +4167,8 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4182,6 +4216,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4220,11 +4255,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
},
@@ -4449,6 +4486,25 @@
"toidentifier": "1.0.0"
}
},
+ "husky": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz",
+ "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==",
+ "dev": true,
+ "requires": {
+ "is-ci": "^1.0.10",
+ "normalize-path": "^1.0.0",
+ "strip-indent": "^2.0.0"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz",
+ "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=",
+ "dev": true
+ }
+ }
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -4575,6 +4631,15 @@
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
"dev": true
},
+ "is-ci": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+ "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
+ "dev": true,
+ "requires": {
+ "ci-info": "^1.5.0"
+ }
+ },
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@@ -4954,8 +5019,8 @@
}
},
"lbry-redux": {
- "version": "github:lbryio/lbry-redux#0ff6364a40253387fbe1c4a5b5cd444f616d84e6",
- "from": "github:lbryio/lbry-redux",
+ "version": "github:lbryio/lbry-redux#b2044499c5f43e519384433538c1225d56d3a1f2",
+ "from": "github:lbryio/lbry-redux#multi-claim-search",
"requires": {
"proxy-polyfill": "0.1.6",
"reselect": "^3.0.0",
@@ -8693,6 +8758,12 @@
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
+ "strip-indent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
+ "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
+ "dev": true
+ },
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
diff --git a/package.json b/package.json
index cab4656..11da733 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3",
- "lbry-redux": "lbryio/lbry-redux",
+ "lbry-redux": "lbryio/lbry-redux#multi-claim-search",
"lbryinc": "lbryio/lbryinc",
"lodash": ">=4.17.11",
"merge": ">=1.2.1",
@@ -41,10 +41,11 @@
"redux-persist-transform-compress": "^4.2.0",
"redux-persist-transform-filter": "0.0.18",
"redux-thunk": "^2.3.0",
- "rn-fetch-blob": "^0.10.15"
+ "rn-fetch-blob": "0.10.15"
},
"devDependencies": {
"@babel/core": "^7.5.4",
+ "babel-eslint": "10.0.2",
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.18.0",
@@ -59,6 +60,7 @@
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-standard": "^4.0.0",
"flow-babel-webpack-plugin": "^1.1.1",
+ "husky": "^0.14.3",
"lint-staged": "^7.0.4",
"metro-react-native-babel-preset": "^0.55.0",
"prettier": "^1.11.1"
diff --git a/src/component/AppNavigator.js b/src/component/AppNavigator.js
index 74d3ddc..71cf69a 100644
--- a/src/component/AppNavigator.js
+++ b/src/component/AppNavigator.js
@@ -7,6 +7,7 @@ import FilePage from 'page/file';
import FirstRunScreen from 'page/firstRun';
import PublishPage from 'page/publish';
import RewardsPage from 'page/rewards';
+import TagPage from 'page/tag';
import TrendingPage from 'page/trending';
import SearchPage from 'page/search';
import SettingsPage from 'page/settings';
@@ -40,7 +41,7 @@ import { decode as atob } from 'base-64';
import { dispatchNavigateBack, dispatchNavigateToUri } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
-import Constants from 'constants';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import NavigationButton from 'component/navigationButton';
import discoverStyle from 'styles/discover';
@@ -72,6 +73,12 @@ const discoverStack = createStackNavigator(
header: null,
}),
},
+ Tag: {
+ screen: TagPage,
+ navigationOptions: ({ navigation }) => ({
+ header: null,
+ }),
+ },
Search: {
screen: SearchPage,
navigationOptions: ({ navigation }) => ({
@@ -87,9 +94,9 @@ const discoverStack = createStackNavigator(
discoverStack.navigationOptions = ({ navigation }) => {
let drawerLockMode = 'unlocked';
- /*if (navigation.state.index > 0) {
+ /* if (navigation.state.index > 0) {
drawerLockMode = 'locked-closed';
- }*/
+ } */
return {
drawerLockMode,
@@ -139,7 +146,7 @@ const drawer = createDrawerNavigator(
screen: SubscriptionsPage,
navigationOptions: {
title: 'Subscriptions',
- drawerIcon: ({ tintColor }) => ,
+ drawerIcon: ({ tintColor }) => ,
},
},
WalletStack: {
@@ -279,8 +286,8 @@ class AppWithNavigationState extends React.Component {
checkEmailVerification = () => {
const { dispatch } = this.props;
AsyncStorage.getItem(Constants.KEY_EMAIL_VERIFY_PENDING).then(pending => {
- this.setState({ verifyPending: 'true' === pending });
- if ('true' === pending) {
+ this.setState({ verifyPending: pending === Constants.TRUE_STRING });
+ if (pending === Constants.TRUE_STRING) {
dispatch(doUserCheckEmailVerified());
}
});
@@ -322,7 +329,7 @@ class AppWithNavigationState extends React.Component {
currentDisplayType = 'toast';
}
- if ('toast' === currentDisplayType) {
+ if (currentDisplayType === 'toast') {
ToastAndroid.show(message, ToastAndroid.LONG);
}
@@ -331,7 +338,7 @@ class AppWithNavigationState extends React.Component {
if (user && !emailVerifyPending && !this.state.emailVerifyDone && (emailToVerify || emailVerifyErrorMessage)) {
AsyncStorage.getItem(Constants.KEY_SHOULD_VERIFY_EMAIL).then(shouldVerify => {
- if ('true' === shouldVerify) {
+ if (shouldVerify === 'true') {
this.setState({ emailVerifyDone: true });
const message = emailVerifyErrorMessage
? String(emailVerifyErrorMessage)
diff --git a/src/component/categoryList/index.js b/src/component/categoryList/index.js
index 928bcf6..2d87b90 100644
--- a/src/component/categoryList/index.js
+++ b/src/component/categoryList/index.js
@@ -1,7 +1,4 @@
import { connect } from 'react-redux';
import CategoryList from './view';
-export default connect(
- null,
- null
-)(CategoryList);
+export default connect()(CategoryList);
diff --git a/src/component/channelIconItem/index.js b/src/component/channelIconItem/index.js
new file mode 100644
index 0000000..0096792
--- /dev/null
+++ b/src/component/channelIconItem/index.js
@@ -0,0 +1,18 @@
+import { connect } from 'react-redux';
+import { doResolveUri, makeSelectClaimForUri, makeSelectThumbnailForUri, makeSelectIsUriResolving } from 'lbry-redux';
+import ChannelIconItem from './view';
+
+const select = (state, props) => ({
+ thumbnail: makeSelectThumbnailForUri(props.uri)(state),
+ claim: makeSelectClaimForUri(props.uri)(state),
+ isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
+});
+
+const perform = dispatch => ({
+ resolveUri: uri => dispatch(doResolveUri(uri)),
+});
+
+export default connect(
+ select,
+ perform
+)(ChannelIconItem);
diff --git a/src/component/channelIconItem/view.js b/src/component/channelIconItem/view.js
new file mode 100644
index 0000000..73eba59
--- /dev/null
+++ b/src/component/channelIconItem/view.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { ActivityIndicator, Image, Text, TouchableOpacity, View } from 'react-native';
+import Colors from 'styles/colors';
+import channelIconStyle from 'styles/channelIcon';
+
+export default class ChannelIconItem extends React.PureComponent {
+ componentDidMount() {
+ const { claim, isPlaceholder, uri, resolveUri } = this.props;
+ if (!claim && !isPlaceholder) {
+ resolveUri(uri);
+ }
+ }
+
+ render() {
+ const { claim, isPlaceholder, isResolvingUri, onPress, thumbnail, title } = this.props;
+
+ return (
+
+ {isResolvingUri && (
+
+
+
+ )}
+
+ {isPlaceholder && (
+
+ ALL
+
+ )}
+ {!isPlaceholder && (
+
+ )}
+
+ {!isPlaceholder && (
+
+ {title || (claim ? claim.name : '')}
+
+ )}
+
+ );
+ }
+}
diff --git a/src/component/claimList/index.js b/src/component/claimList/index.js
new file mode 100644
index 0000000..2ab44d1
--- /dev/null
+++ b/src/component/claimList/index.js
@@ -0,0 +1,40 @@
+import { connect } from 'react-redux';
+import {
+ MATURE_TAGS,
+ doClaimSearch,
+ doClaimSearchByTags,
+ makeSelectClaimSearchUrisForTags,
+ makeSelectFetchingClaimSearchForTags,
+ selectFetchingClaimSearch,
+ selectLastClaimSearchUris,
+} from 'lbry-redux';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import ClaimList from './view';
+
+const select = (state, props) => {
+ return {
+ loading: makeSelectFetchingClaimSearchForTags(props.tags)(state),
+ uris: makeSelectClaimSearchUrisForTags(props.tags)(state),
+ // for subscriptions
+ claimSearchLoading: selectFetchingClaimSearch(state),
+ claimSearchUris: selectLastClaimSearchUris(state),
+ };
+};
+
+const perform = dispatch => ({
+ claimSearch: options => dispatch(doClaimSearch(Constants.DEFAULT_PAGE_SIZE, options)),
+ searchByTags: (tags, orderBy = Constants.DEFAULT_ORDER_BY, page = 1) =>
+ dispatch(
+ doClaimSearchByTags(tags, Constants.DEFAULT_PAGE_SIZE, {
+ no_totals: true,
+ order_by: orderBy,
+ page,
+ not_tags: MATURE_TAGS,
+ })
+ ),
+});
+
+export default connect(
+ select,
+ perform
+)(ClaimList);
diff --git a/src/component/claimList/view.js b/src/component/claimList/view.js
new file mode 100644
index 0000000..54f6246
--- /dev/null
+++ b/src/component/claimList/view.js
@@ -0,0 +1,210 @@
+import React from 'react';
+import NavigationActions from 'react-navigation';
+import { ActivityIndicator, FlatList, Text, View } from 'react-native';
+import { MATURE_TAGS, normalizeURI } from 'lbry-redux';
+import _ from 'lodash';
+import FileItem from 'component/fileItem';
+import FileListItem from 'component/fileListItem';
+import Colors from 'styles/colors';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import claimListStyle from 'styles/claimList';
+import discoverStyle from 'styles/discover';
+
+const horizontalLimit = 10;
+const softLimit = 500;
+
+class ClaimList extends React.PureComponent {
+ scrollView = null;
+
+ state = {
+ currentPage: 1, // initial page load is page 1
+ subscriptionsView: false, // whether or not this claim list is for subscriptions
+ trendingForAllView: false,
+ };
+
+ componentDidMount() {
+ const {
+ channelIds,
+ trendingForAll,
+ claimSearch,
+ orderBy = Constants.DEFAULT_ORDER_BY,
+ searchByTags,
+ tags,
+ } = this.props;
+ if (channelIds || trendingForAll) {
+ const options = {
+ order_by: orderBy,
+ no_totals: true,
+ not_tags: MATURE_TAGS,
+ page: this.state.currentPage,
+ };
+ if (channelIds) {
+ this.setState({ subscriptionsView: true });
+ options.channel_ids = channelIds;
+ } else if (trendingForAll) {
+ this.setState({ trendingForAllView: true });
+ }
+
+ claimSearch(options);
+ } else if (tags && tags.length > 0) {
+ searchByTags(tags, orderBy, this.state.currentPgae);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const {
+ claimSearch,
+ orderBy: prevOrderBy,
+ searchByTags,
+ tags: prevTags,
+ channelIds: prevChannelIds,
+ trendingForAll: prevTrendingForAll,
+ } = this.props;
+ const { orderBy, tags, channelIds, trendingForAll } = nextProps;
+ if (
+ !_.isEqual(orderBy, prevOrderBy) ||
+ !_.isEqual(tags, prevTags) ||
+ !_.isEqual(channelIds, prevChannelIds) ||
+ trendingForAll !== prevTrendingForAll
+ ) {
+ // reset to page 1 because the order, tags or channelIds changed
+ this.setState({ currentPage: 1 }, () => {
+ if (this.scrollView) {
+ this.scrollView.scrollToOffset({ animated: true, offset: 0 });
+ }
+ if (trendingForAll || (prevChannelIds && channelIds)) {
+ const options = {
+ order_by: orderBy,
+ no_totals: true,
+ not_tags: MATURE_TAGS,
+ page: this.state.currentPage,
+ };
+ if (channelIds) {
+ this.setState({ subscriptionsView: true });
+ options.channel_ids = channelIds;
+ }
+ if (trendingForAll) {
+ this.setState({ trendingForAllView: true });
+ }
+
+ claimSearch(options);
+ } else if (tags && tags.length > 0) {
+ this.setState({ subscriptionsView: false, trendingForAllView: false });
+ searchByTags(tags, orderBy, this.state.currentPage);
+ }
+ });
+ }
+ }
+
+ handleVerticalEndReached = () => {
+ // fetch more content
+ const { channelIds, claimSearch, claimSearchUris, orderBy, searchByTags, tags, uris } = this.props;
+ const { subscriptionsView, trendingForAllView } = this.state;
+ if ((claimSearchUris && claimSearchUris.length >= softLimit) || (uris && uris.length >= softLimit)) {
+ // don't fetch more than the specified limit to be displayed
+ return;
+ }
+
+ this.setState({ currentPage: this.state.currentPage + 1 }, () => {
+ if (subscriptionsView || trendingForAllView) {
+ const options = {
+ order_by: orderBy,
+ no_totals: true,
+ not_tags: MATURE_TAGS,
+ page: this.state.currentPage,
+ };
+ if (subscriptionsView) {
+ options.channel_ids = channelIds;
+ }
+
+ claimSearch(options);
+ } else {
+ searchByTags(tags, orderBy, this.state.currentPage);
+ }
+ });
+ };
+
+ render() {
+ const {
+ ListHeaderComponent,
+ loading,
+ claimSearchLoading,
+ claimSearchUris,
+ navigation,
+ orientation = Constants.ORIENTATION_VERTICAL,
+ style,
+ uris,
+ } = this.props;
+ const { subscriptionsView, trendingForAllView } = this.state;
+
+ if (Constants.ORIENTATION_VERTICAL === orientation) {
+ const data = subscriptionsView || trendingForAllView ? claimSearchUris : uris;
+ return (
+
+ {
+ this.scrollView = ref;
+ }}
+ ListHeaderComponent={ListHeaderComponent}
+ style={claimListStyle.verticalScrollContainer}
+ contentContainerStyle={claimListStyle.verticalScrollPadding}
+ initialNumToRender={8}
+ maxToRenderPerBatch={24}
+ removeClippedSubviews
+ renderItem={({ item }) => (
+
+ )}
+ data={data}
+ keyExtractor={(item, index) => item}
+ onEndReached={this.handleVerticalEndReached}
+ onEndReachedThreshold={0.9}
+ />
+ {(((subscriptionsView || trendingForAllView) && claimSearchLoading) || loading) && (
+
+
+
+ )}
+
+ );
+ }
+
+ if (Constants.ORIENTATION_HORIZONTAL === orientation) {
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ (
+
+ )}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ data={uris ? uris.slice(0, horizontalLimit) : []}
+ keyExtractor={(item, index) => item}
+ />
+ );
+ }
+
+ return null;
+ }
+}
+
+export default ClaimList;
diff --git a/src/component/fileItem/view.js b/src/component/fileItem/view.js
index 0e44874..8cc0237 100644
--- a/src/component/fileItem/view.js
+++ b/src/component/fileItem/view.js
@@ -13,10 +13,6 @@ import NsfwOverlay from 'component/nsfwOverlay';
import discoverStyle from 'styles/discover';
class FileItem extends React.PureComponent {
- constructor(props) {
- super(props);
- }
-
componentWillMount() {
this.resolve(this.props);
}
@@ -64,8 +60,7 @@ class FileItem extends React.PureComponent {
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
const signingChannel = claim ? claim.signing_channel : null;
const channelName = signingChannel ? signingChannel.name : null;
- const channelClaimId =
- claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
+ const channelClaimId = signingChannel ? signingChannel.claim_id : null;
const fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
const height = claim ? claim.height : null;
@@ -87,13 +82,7 @@ class FileItem extends React.PureComponent {
/>
{!compactView && fileInfo && fileInfo.completed && fileInfo.download_path && (
-
+
)}
{!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path) && (
diff --git a/src/component/fileListItem/view.js b/src/component/fileListItem/view.js
index baaeda8..fa24157 100644
--- a/src/component/fileListItem/view.js
+++ b/src/component/fileListItem/view.js
@@ -41,6 +41,11 @@ class FileListItem extends React.PureComponent {
}
}
+ defaultOnPress = () => {
+ const { navigation, uri } = this.props;
+ navigateToUri(navigation, uri);
+ };
+
render() {
const {
claim,
@@ -60,13 +65,13 @@ class FileListItem extends React.PureComponent {
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isResolving = !fileInfo && isResolvingUri;
- let name, channel, height, channelClaimId, fullChannelUri;
+ let name, channel, height, channelClaimId, fullChannelUri, signingChannel;
if (claim) {
name = claim.name;
signingChannel = claim.signing_channel;
channel = signingChannel ? signingChannel.name : null;
height = claim.height;
- channelClaimId = claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
+ channelClaimId = signingChannel ? signingChannel.claim_id : null;
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
}
@@ -76,7 +81,7 @@ class FileListItem extends React.PureComponent {
return (
-
+
{fileInfo && fileInfo.completed && fileInfo.download_path && (
-
+
)}
{featuredResult && (
diff --git a/src/component/mediaPlayer/view.js b/src/component/mediaPlayer/view.js
index 0fc7ac4..64ed1cc 100644
--- a/src/component/mediaPlayer/view.js
+++ b/src/component/mediaPlayer/view.js
@@ -428,7 +428,7 @@ class MediaPlayer extends React.PureComponent {
bufferForPlaybackMs: 5000,
bufferForPlaybackAfterRebufferMs: 5000,
}}
- ref={(ref: Video) => {
+ ref={ref => {
this.video = ref;
}}
resizeMode={this.state.resizeMode}
@@ -445,7 +445,7 @@ class MediaPlayer extends React.PureComponent {
minLoadRetryCount={999}
/>
- {this.state.firstPlay && thumbnail && thumbnail.trim().length > 0 && (
+ {this.state.firstPlay && thumbnail && (
0) {
+ this.setState({ selectedItem: items[0] });
+ return;
+ }
+
+ this.setState({ selectedItem });
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { selectedItem: prevSelectedItem } = this.props;
+ const { selectedItem } = nextProps;
+ if (selectedItem && selectedItem.name !== prevSelectedItem.name) {
+ this.setState({ selectedItem });
+ }
+ }
+
+ render() {
+ const { items, onItemSelected, title, onOverlayPress } = this.props;
+ const { selectedItem } = this.state;
+
+ return (
+
+
+ {title}
+
+
+ {items.length &&
+ items.map(item => (
+ onItemSelected(item)}
+ >
+
+ {item.label}
+ {selectedItem && selectedItem.name === item.name && (
+
+ )}
+
+ ))}
+
+
+
+ );
+ }
+}
diff --git a/src/component/modalTagSelector/index.js b/src/component/modalTagSelector/index.js
new file mode 100644
index 0000000..1bf7cc7
--- /dev/null
+++ b/src/component/modalTagSelector/index.js
@@ -0,0 +1,27 @@
+import { connect } from 'react-redux';
+import {
+ selectUnfollowedTags,
+ selectFollowedTags,
+ doReplaceTags,
+ doToggleTagFollow,
+ doAddTag,
+ doDeleteTag,
+ doToast,
+} from 'lbry-redux';
+import ModalTagSelector from './view';
+
+const select = state => ({
+ unfollowedTags: selectUnfollowedTags(state),
+ followedTags: selectFollowedTags(state),
+});
+
+export default connect(
+ select,
+ {
+ doToggleTagFollow,
+ doAddTag,
+ doDeleteTag,
+ doReplaceTags,
+ doToast,
+ }
+)(ModalTagSelector);
diff --git a/src/component/modalTagSelector/view.js b/src/component/modalTagSelector/view.js
new file mode 100644
index 0000000..a37c90b
--- /dev/null
+++ b/src/component/modalTagSelector/view.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import { Text, TouchableOpacity, View } from 'react-native';
+import { DEFAULT_FOLLOWED_TAGS } from 'lbry-redux';
+import Button from 'component/button';
+import Colors from 'styles/colors';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import Icon from 'react-native-vector-icons/FontAwesome5';
+import Tag from 'component/tag';
+import TagSearch from 'component/tagSearch';
+import modalTagSelectorStyle from 'styles/modalTagSelector';
+import __ from 'utils/helper';
+
+export default class ModalTagSelector extends React.PureComponent {
+ handleAddTag = tag => {
+ if (!tag) {
+ return;
+ }
+
+ const { followedTags, doToast } = this.props;
+ if (followedTags.map(followedTag => followedTag.name).includes(tag.toLowerCase())) {
+ doToast({ message: __(`You already added the "${tag}" tag.`) });
+ return;
+ }
+
+ this.props.doToggleTagFollow(tag);
+ };
+
+ handleRemoveTag = tag => {
+ if (!tag) {
+ return;
+ }
+
+ this.props.doToggleTagFollow(tag);
+ };
+
+ render() {
+ const { followedTags, onOverlayPress, onDonePress } = this.props;
+ const tags = followedTags ? followedTags.map(tag => tag.name) : DEFAULT_FOLLOWED_TAGS;
+
+ return (
+
+
+
+ Customize your tags
+
+
+ {tags &&
+ tags.map(tag => (
+
+ ))}
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/component/subscribedChannelList/index.js b/src/component/subscribedChannelList/index.js
new file mode 100644
index 0000000..8d97885
--- /dev/null
+++ b/src/component/subscribedChannelList/index.js
@@ -0,0 +1,4 @@
+import { connect } from 'react-redux';
+import SubscribedChannelList from './view';
+
+export default connect()(SubscribedChannelList);
diff --git a/src/component/subscribedChannelList/view.js b/src/component/subscribedChannelList/view.js
new file mode 100644
index 0000000..2e405da
--- /dev/null
+++ b/src/component/subscribedChannelList/view.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Text, FlatList, View } from 'react-native';
+import { normalizeURI } from 'lbry-redux';
+import ChannelIconItem from 'component/channelIconItem';
+import Colors from 'styles/colors';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
+import subscriptionsStyle from 'styles/subscriptions';
+
+export default class SubscribedChannelList extends React.PureComponent {
+ render() {
+ const { subscribedChannels, onChannelSelected } = this.props;
+
+ return (
+
+ (
+ onChannelSelected(item)}
+ />
+ )}
+ data={subscribedChannels}
+ keyExtractor={(item, index) => item}
+ />
+
+ );
+ }
+}
diff --git a/src/component/suggestedSubscriptionItem/index.js b/src/component/suggestedSubscriptionItem/index.js
index 39b6a1c..4a52bb8 100644
--- a/src/component/suggestedSubscriptionItem/index.js
+++ b/src/component/suggestedSubscriptionItem/index.js
@@ -1,22 +1,22 @@
import { connect } from 'react-redux';
import {
- makeSelectFetchingChannelClaims,
- makeSelectClaimsInChannelForPage,
- doFetchClaimsByChannel,
- doResolveUris,
+ doResolveUri,
+ makeSelectClaimForUri,
+ makeSelectThumbnailForUri,
+ makeSelectTitleForUri,
+ makeSelectIsUriResolving,
} 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),
+ thumbnail: makeSelectThumbnailForUri(props.uri)(state),
+ title: makeSelectTitleForUri(props.uri)(state),
+ claim: makeSelectClaimForUri(props.uri)(state),
+ isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
});
const perform = dispatch => ({
- fetchChannel: channel => dispatch(doFetchClaimsByChannel(channel)),
- resolveUris: uris => dispatch(doResolveUris(uris, true)),
+ resolveUri: uri => dispatch(doResolveUri(uri)),
});
export default connect(
diff --git a/src/component/suggestedSubscriptionItem/view.js b/src/component/suggestedSubscriptionItem/view.js
index 46669ed..286566e 100644
--- a/src/component/suggestedSubscriptionItem/view.js
+++ b/src/component/suggestedSubscriptionItem/view.js
@@ -1,76 +1,70 @@
import React from 'react';
import { buildURI, normalizeURI } from 'lbry-redux';
-import { ActivityIndicator, FlatList, Text, View } from 'react-native';
+import { ActivityIndicator, FlatList, Image, Text, View } from 'react-native';
import Colors from 'styles/colors';
import discoverStyle from 'styles/discover';
import FileItem from 'component/fileItem';
+import SubscribeButton from 'component/subscribeButton';
import subscriptionsStyle from 'styles/subscriptions';
+import Tag from 'component/tag';
class SuggestedSubscriptionItem extends React.PureComponent {
componentDidMount() {
- const { fetching, categoryLink, fetchChannel, resolveUris, claims } = this.props;
- if (!fetching && categoryLink && (!claims || claims.length)) {
- fetchChannel(categoryLink);
+ const { claim, uri, resolveUri } = this.props;
+ if (!claim) {
+ resolveUri(uri);
}
}
- 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;
+ const { claim, isResolvingUri, navigation, thumbnail, title, uri } = this.props;
+ let tags;
+ if (claim && claim.value) {
+ tags = claim.value.tags;
+ }
- if (!claims || !claims.length) {
+ if (isResolvingUri) {
return (
-
+
);
}
- if (claims && claims.length > 0) {
- return (
-
-
+
+
- {claims.length > 1 && (
- (
-
- )}
- data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
- keyExtractor={(item, index) => item}
- />
- )}
- );
- }
- return null;
+
+
+ {title && (
+
+ {title}
+
+ )}
+
+ {claim && claim.name}
+
+ {tags && (
+
+ {tags &&
+ tags
+ .slice(0, 3)
+ .map(tag => )}
+
+ )}
+
+
+
+
+
+ );
}
}
diff --git a/src/component/suggestedSubscriptions/index.js b/src/component/suggestedSubscriptions/index.js
index e55edfc..0c6524c 100644
--- a/src/component/suggestedSubscriptions/index.js
+++ b/src/component/suggestedSubscriptions/index.js
@@ -1,13 +1,20 @@
import { connect } from 'react-redux';
+import { doClaimSearch, selectFetchingClaimSearch, selectLastClaimSearchUris, selectFollowedTags } from 'lbry-redux';
import { selectSuggestedChannels, selectIsFetchingSuggested } from 'lbryinc';
import SuggestedSubscriptions from './view';
const select = state => ({
+ followedTags: selectFollowedTags(state),
suggested: selectSuggestedChannels(state),
- loading: selectIsFetchingSuggested(state),
+ loading: selectIsFetchingSuggested(state) || selectFetchingClaimSearch(state),
+ claimSearchUris: selectLastClaimSearchUris(state),
+});
+
+const perform = dispatch => ({
+ claimSearch: options => dispatch(doClaimSearch(10, options)),
});
export default connect(
select,
- null
+ perform
)(SuggestedSubscriptions);
diff --git a/src/component/suggestedSubscriptions/view.js b/src/component/suggestedSubscriptions/view.js
index a20f4e7..39f9bc5 100644
--- a/src/component/suggestedSubscriptions/view.js
+++ b/src/component/suggestedSubscriptions/view.js
@@ -1,53 +1,67 @@
import React from 'react';
-import { ActivityIndicator, SectionList, Text, View } from 'react-native';
+import { ActivityIndicator, FlatList, SectionList, Text, View } from 'react-native';
import { normalizeURI } from 'lbry-redux';
-import { navigateToUri } from 'utils/helper';
+import __, { navigateToUri } from 'utils/helper';
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';
+import _ from 'lodash';
class SuggestedSubscriptions extends React.PureComponent {
+ componentDidMount() {
+ const { claimSearch, followedTags } = this.props;
+ const options = {
+ any_tags: _.shuffle(followedTags.map(tag => tag.name)).slice(0, 2),
+ page: 1,
+ no_totals: true,
+ claim_type: 'channel',
+ };
+ claimSearch(options);
+ }
+
+ buildSections = () => {
+ const { suggested, claimSearchUris } = this.props;
+ const suggestedUris = suggested ? suggested.map(suggested => suggested.uri) : [];
+ return [
+ {
+ title: __('You might like'),
+ data: suggestedUris,
+ },
+ {
+ title: __('Tags you follow'),
+ data: claimSearchUris ? claimSearchUris.filter(uri => !suggestedUris.includes(uri)) : [],
+ },
+ ];
+ };
+
render() {
const { suggested, loading, navigation } = this.props;
if (loading) {
return (
-
+
);
}
- return suggested ? (
+ return (
(
-
+
)}
- renderSectionHeader={({ section: { title } }) => {
- const titleParts = title.split(';');
- const channelName = titleParts[0];
- const channelUri = normalizeURI(titleParts[1]);
- return (
-
- {
- navigateToUri(navigation, normalizeURI(channelUri));
- }}
- />
-
-
- );
- }}
- sections={suggested.map(({ uri, label }) => ({ title: label + ';' + uri, data: [uri] }))}
+ renderSectionHeader={({ section: { title } }) => (
+ {title}
+ )}
+ sections={this.buildSections()}
keyExtractor={(item, index) => item}
/>
- ) : null;
+ );
}
}
diff --git a/src/component/tag/view.js b/src/component/tag/view.js
index e6b47bc..8f56bfd 100644
--- a/src/component/tag/view.js
+++ b/src/component/tag/view.js
@@ -2,6 +2,7 @@ import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import tagStyle from 'styles/tag';
import Colors from 'styles/colors';
+import Constants from 'constants';
import Icon from 'react-native-vector-icons/FontAwesome5';
export default class Tag extends React.PureComponent {
@@ -22,6 +23,7 @@ export default class Tag extends React.PureComponent {
if (navigation) {
// navigate to tag page
+ navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TAG, key: `tagPage`, params: { tag: name } });
}
};
diff --git a/src/component/tagSearch/view.js b/src/component/tagSearch/view.js
index 5756230..ce4c365 100644
--- a/src/component/tagSearch/view.js
+++ b/src/component/tagSearch/view.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { Text, TextInput, TouchableOpacity, View } from 'react-native';
+import { KeyboardAvoidingView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import Tag from 'component/tag';
import tagStyle from 'styles/tag';
import Colors from 'styles/colors';
@@ -47,9 +47,17 @@ export default class TagSearch extends React.PureComponent {
const suggestedTagsSet = new Set(unfollowedTags.map(tag => tag.name));
const suggestedTags = Array.from(suggestedTagsSet).filter(tagNotSelected);
if (tag && tag.trim().length > 0) {
- results.push(tag.toLowerCase());
+ const lcTag = tag.toLowerCase();
+ if (!results.includes(lcTag)) {
+ results.push(lcTag);
+ }
const doesTagMatch = name => name.toLowerCase().includes(tag.toLowerCase());
- results = results.concat(suggestedTags.filter(doesTagMatch).slice(0, 5));
+ results = results.concat(
+ suggestedTags
+ .filter(doesTagMatch)
+ .filter(suggested => lcTag !== suggested.toLowerCase())
+ .slice(0, 5)
+ );
} else {
results = results.concat(suggestedTags.slice(0, 5));
}
@@ -70,11 +78,13 @@ export default class TagSearch extends React.PureComponent {
numberOfLines={1}
onChangeText={this.handleTagChange}
/>
-
- {this.state.tagResults.map(tag => (
- this.onAddTagPress(name)} />
- ))}
-
+
+
+ {this.state.tagResults.map(tag => (
+ this.onAddTagPress(name)} />
+ ))}
+
+
);
}
diff --git a/src/component/uriBar/view.js b/src/component/uriBar/view.js
index 5ca0ea2..59f25ce 100644
--- a/src/component/uriBar/view.js
+++ b/src/component/uriBar/view.js
@@ -136,19 +136,19 @@ class UriBar extends React.PureComponent {
}
render() {
- const { navigation, suggestions, query, value } = this.props;
+ const { navigation, suggestions, query, value, belowOverlay } = this.props;
if (this.state.currentValue === null) {
this.setState({ currentValue: value });
}
- let style = [uriBarStyle.overlay];
+ let style = [uriBarStyle.overlay, belowOverlay ? null : uriBarStyle.overlayElevated];
// TODO: Add optional setting to enable URI / search bar suggestions
/*if (this.state.focused) { style.push(uriBarStyle.inFocus); }*/
return (
-
+
{
if (error && NativeModules.Firebase) {
- console.log(error);
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
}
};
@@ -76,9 +75,9 @@ function enableBatching(reducer) {
};
}
-/*const router = AppNavigator.router;
+/* const router = AppNavigator.router;
const navAction = router.getActionForPathAndParams('FirstRun');
-const initialNavState = router.getStateForAction(navAction);*/
+const initialNavState = router.getStateForAction(navAction); */
const reducers = combineReducers({
auth: authReducer,
@@ -122,10 +121,11 @@ const contentFilter = createFilter('content', ['positions']);
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions']);
const settingsFilter = createFilter('settings', ['clientSettings']);
+const tagsFilter = createFilter('tags', ['followedTags']);
const walletFilter = createFilter('wallet', ['receiveAddress']);
const persistOptions = {
- whitelist: ['auth', 'claims', 'content', 'subscriptions', 'settings', 'wallet'],
+ whitelist: ['auth', 'claims', 'content', 'subscriptions', 'settings', 'tags', 'wallet'],
// Order is important. Needs to be compressed last or other transforms can't
// read the data
transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
diff --git a/src/page/discover/index.js b/src/page/discover/index.js
index 8598e2e..728c702 100644
--- a/src/page/discover/index.js
+++ b/src/page/discover/index.js
@@ -1,5 +1,12 @@
import { connect } from 'react-redux';
-import { doFileList, selectBalance, selectFileInfosDownloaded } from 'lbry-redux';
+import {
+ doClaimSearch,
+ doFileList,
+ selectBalance,
+ selectFileInfosDownloaded,
+ selectLastClaimSearchUris,
+ selectFollowedTags,
+} from 'lbry-redux';
import {
doFetchFeaturedUris,
doFetchRewardedContent,
@@ -11,9 +18,10 @@ import {
selectSubscriptionClaims,
selectUnreadSubscriptions,
} from 'lbryinc';
+
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
-import Constants from 'constants';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DiscoverPage from './view';
const select = state => ({
@@ -23,12 +31,15 @@ const select = state => ({
featuredUris: selectFeaturedUris(state),
fetchingFeaturedUris: selectFetchingFeaturedUris(state),
fileInfos: selectFileInfosDownloaded(state),
+ followedTags: selectFollowedTags(state),
ratingReminderDisabled: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED)(state),
ratingReminderLastShown: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN)(state),
unreadSubscriptions: selectUnreadSubscriptions(state),
+ uris: selectLastClaimSearchUris(state),
});
const perform = dispatch => ({
+ doClaimSearch,
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchSubscriptions: () => dispatch(doFetchMySubscriptions()),
diff --git a/src/page/discover/view.js b/src/page/discover/view.js
index aae5764..b5fe0a3 100644
--- a/src/page/discover/view.js
+++ b/src/page/discover/view.js
@@ -1,17 +1,41 @@
import React from 'react';
import NavigationActions from 'react-navigation';
-import { Alert, ActivityIndicator, Linking, NativeModules, SectionList, Text, View } from 'react-native';
-import { Lbry, normalizeURI, parseURI } from 'lbry-redux';
+import {
+ Alert,
+ ActivityIndicator,
+ Linking,
+ NativeModules,
+ SectionList,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import { DEFAULT_FOLLOWED_TAGS, Lbry, normalizeURI, parseURI } from 'lbry-redux';
+import __, { formatTagTitle } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import CategoryList from 'component/categoryList';
-import Constants from 'constants';
+import ClaimList from 'component/claimList';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Colors from 'styles/colors';
import discoverStyle from 'styles/discover';
import FloatingWalletBalance from 'component/floatingWalletBalance';
+import Icon from 'react-native-vector-icons/FontAwesome5';
+import Link from 'component/link';
+import ModalTagSelector from 'component/modalTagSelector';
+import ModalPicker from 'component/modalPicker';
import UriBar from 'component/uriBar';
+import _ from 'lodash';
class DiscoverPage extends React.PureComponent {
+ state = {
+ tagCollection: [],
+ showModalTagSelector: false,
+ showSortPicker: false,
+ orderBy: Constants.DEFAULT_ORDER_BY,
+ currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[0],
+ };
+
componentDidMount() {
// Track the total time taken if this is the first launch
AsyncStorage.getItem('firstLaunchTime').then(startTime => {
@@ -19,7 +43,7 @@ class DiscoverPage extends React.PureComponent {
// We don't need this value anymore once we've retrieved it
AsyncStorage.removeItem('firstLaunchTime');
- // We know this is the first app launch because firstLaunchTime is set and it's a valid number
+ // We know this is the first app launch because firstLaunchTime is set and it"s a valid number
const start = parseInt(startTime, 10);
const now = moment().unix();
const delta = now - start;
@@ -36,9 +60,9 @@ class DiscoverPage extends React.PureComponent {
}
});
- const { fetchFeaturedUris, fetchRewardedContent, fetchSubscriptions, fileList } = this.props;
+ const { fetchRewardedContent, fetchSubscriptions, fileList, followedTags } = this.props;
- fetchFeaturedUris();
+ this.buildTagCollection(followedTags);
fetchRewardedContent();
fetchSubscriptions();
fileList();
@@ -46,6 +70,25 @@ class DiscoverPage extends React.PureComponent {
this.showRatingReminder();
}
+ handleSortByItemSelected = item => {
+ let orderBy = [];
+ switch (item.name) {
+ case Constants.SORT_BY_HOT:
+ orderBy = Constants.DEFAULT_ORDER_BY;
+ break;
+
+ 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 });
+ };
+
subscriptionForUri = (uri, channelName) => {
const { allSubscriptions } = this.props;
const { claimId, claimName } = parseURI(uri);
@@ -63,6 +106,14 @@ class DiscoverPage extends React.PureComponent {
return null;
};
+ componentWillReceiveProps(nextProps) {
+ const { followedTags: prevFollowedTags } = this.props;
+ const { followedTags } = nextProps;
+ if (!_.isEqual(followedTags, prevFollowedTags)) {
+ this.buildTagCollection(followedTags);
+ }
+ }
+
componentDidUpdate(prevProps, prevState) {
const { unreadSubscriptions, enabledChannelNotifications } = this.props;
@@ -112,7 +163,7 @@ class DiscoverPage extends React.PureComponent {
const { ratingReminderDisabled, ratingReminderLastShown, setClientSetting } = this.props;
const now = moment().unix();
- if ('true' !== ratingReminderDisabled && ratingReminderLastShown) {
+ if (ratingReminderDisabled !== 'true' && ratingReminderLastShown) {
const lastShownParts = ratingReminderLastShown.split('|');
if (lastShownParts.length === 2) {
const lastShownTime = parseInt(lastShownParts[0], 10);
@@ -154,43 +205,104 @@ class DiscoverPage extends React.PureComponent {
setClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN, settingString);
};
- trimClaimIdFromCategory(category) {
- return category.split('#')[0];
- }
+ buildSections = () => {
+ return this.state.tagCollection.map(tags => ({
+ title: tags.length === 1 ? tags[0] : 'Trending',
+ data: [tags],
+ }));
+ };
+
+ buildTagCollection = followedTags => {
+ const tags = followedTags.map(tag => tag.name);
+
+ // each of the followed tags
+ const tagCollection = tags.map(tag => [tag]);
+ // everything
+ tagCollection.unshift(tags);
+
+ this.setState({ tagCollection });
+ };
+
+ handleTagPress = name => {
+ const { navigation } = this.props;
+ if (name.toLowerCase() !== 'trending') {
+ navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TAG, key: `tagPage`, params: { tag: name } });
+ } else {
+ // navigate to the trending page
+ navigation.navigate({ routeName: Constants.FULL_ROUTE_NAME_TRENDING });
+ }
+ };
render() {
- const { featuredUris, fetchingFeaturedUris, navigation } = this.props;
- const hasContent = typeof featuredUris === 'object' && Object.keys(featuredUris).length,
- failedToLoad = !fetchingFeaturedUris && !hasContent;
+ const { navigation } = this.props;
+ const { currentSortByItem, orderBy, showModalTagSelector, showSortPicker } = this.state;
return (
-
- {!hasContent && fetchingFeaturedUris && (
-
-
- Fetching content...
-
- )}
- {!!hasContent && (
- (
-
- )}
- renderSectionHeader={({ section: { title } }) => {title}}
- sections={Object.keys(featuredUris).map(category => ({
- title: this.trimClaimIdFromCategory(category),
- data: [category],
- }))}
- keyExtractor={(item, index) => item}
+
+
+ Explore
+
+ this.setState({ showModalTagSelector: true })}
+ />
+ this.setState({ showSortPicker: true })}
+ >
+ {currentSortByItem.label.split(' ')[0]}
+
+
+
+
+ }
+ style={discoverStyle.scrollContainer}
+ contentContainerStyle={discoverStyle.scrollPadding}
+ initialNumToRender={4}
+ maxToRenderPerBatch={4}
+ removeClippedSubviews
+ renderItem={({ item, index, section }) => (
+ 1 ? Constants.DEFAULT_ORDER_BY : orderBy}
+ tags={item}
+ navigation={navigation}
+ orientation={Constants.ORIENTATION_HORIZONTAL}
+ />
+ )}
+ renderSectionHeader={({ section: { title } }) => (
+
+ this.handleTagPress(title)}>
+ {formatTagTitle(title)}
+
+ this.handleTagPress(title)}>
+
+
+
+ )}
+ sections={this.buildSections()}
+ keyExtractor={(item, index) => item}
+ />
+ {!showModalTagSelector && !showSortPicker && }
+ {showModalTagSelector && (
+ this.setState({ showModalTagSelector: false })}
+ onDonePress={() => this.setState({ showModalTagSelector: false })}
+ />
+ )}
+ {showSortPicker && (
+ this.setState({ showSortPicker: false })}
+ onItemSelected={this.handleSortByItemSelected}
+ selectedItem={currentSortByItem}
+ items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
/>
)}
-
);
}
diff --git a/src/page/file/view.js b/src/page/file/view.js
index f184bbb..41faa94 100644
--- a/src/page/file/view.js
+++ b/src/page/file/view.js
@@ -25,7 +25,7 @@ import Button from 'component/button';
import Tag from 'component/tag';
import ChannelPage from 'page/channel';
import Colors from 'styles/colors';
-import Constants from 'constants';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DateTime from 'component/dateTime';
import FileDownloadButton from 'component/fileDownloadButton';
import FileItemMedia from 'component/fileItemMedia';
@@ -379,23 +379,23 @@ class FilePage extends React.PureComponent {
tokens.length === 0
? ''
: tokens.map((token, j) => {
- let hasSpace = j !== tokens.length - 1;
- let space = hasSpace ? ' ' : '';
+ let hasSpace = j !== tokens.length - 1;
+ let space = hasSpace ? ' ' : '';
- if (token.match(/^(lbry|https?):\/\//g)) {
- return (
-
- );
- } else {
- return token + space;
- }
- });
+ if (token.match(/^(lbry|https?):\/\//g)) {
+ return (
+
+ );
+ } else {
+ return token + space;
+ }
+ });
lineContent.push('\n');
return {lineContent};
@@ -503,7 +503,10 @@ class FilePage extends React.PureComponent {
};
renderTags = tags => {
- return tags.map((tag, i) => );
+ const { navigation } = this.props;
+ return tags.map((tag, i) => (
+
+ ));
};
onFileDownloadButtonPlayed = () => {
@@ -637,15 +640,15 @@ class FilePage extends React.PureComponent {
this.state.isLandscape
? filePageStyle.containedPlayerLandscape
: this.state.fullscreenMode
- ? filePageStyle.fullscreenPlayer
- : filePageStyle.containedPlayer,
+ ? filePageStyle.fullscreenPlayer
+ : filePageStyle.containedPlayer,
];
const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground];
const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground];
// at least 2MB (or the full download) before media can be loaded
const canLoadMedia =
this.state.streamingMode ||
- (fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes)); // 2MB = 1024*1024*2
+ (fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes === fileInfo.total_bytes)); // 2MB = 1024*1024*2
const isViewable = mediaType === 'image' || mediaType === 'text';
const isWebViewable = mediaType === 'text';
const canOpen = isViewable && completed;
@@ -679,7 +682,7 @@ class FilePage extends React.PureComponent {
fileInfo &&
!this.state.autoDownloadStarted &&
this.state.uriVars &&
- 'true' === this.state.uriVars.download
+ this.state.uriVars.download === 'true'
) {
this.setState({ autoDownloadStarted: true }, () => {
purchaseUri(uri, costInfo, !isPlayable);
@@ -727,17 +730,17 @@ class FilePage extends React.PureComponent {
canOpen ||
(!completed && !this.state.streamingMode)) &&
!this.state.downloadPressed && (
- this.setState({ downloadPressed: true })}
- onButtonLayout={() => this.setState({ downloadButtonShown: true })}
- />
- )}
+ this.setState({ downloadPressed: true })}
+ onButtonLayout={() => this.setState({ downloadButtonShown: true })}
+ />
+ )}
{!fileInfo && (
- )}
+
+ )}
)}
@@ -833,7 +836,7 @@ class FilePage extends React.PureComponent {
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
>
-
+
{title}
@@ -846,7 +849,7 @@ class FilePage extends React.PureComponent {
{false &&
((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
-
- )}
+
+ )}
diff --git a/src/page/subscriptions/view.js b/src/page/subscriptions/view.js
index f80cab0..d22414f 100644
--- a/src/page/subscriptions/view.js
+++ b/src/page/subscriptions/view.js
@@ -1,24 +1,41 @@
import React from 'react';
import NavigationActions from 'react-navigation';
-import { ActivityIndicator, FlatList, NativeModules, SectionList, ScrollView, Text, View } from 'react-native';
+import {
+ ActivityIndicator,
+ FlatList,
+ NativeModules,
+ SectionList,
+ ScrollView,
+ Text,
+ TouchableOpacity,
+ View,
+} from 'react-native';
import { buildURI, parseURI } from 'lbry-redux';
-import { uriFromFileInfo } from 'utils/helper';
+import __, { uriFromFileInfo } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import Button from 'component/button';
+import ClaimList from 'component/claimList';
import Colors from 'styles/colors';
-import Constants from 'constants';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import fileListStyle from 'styles/fileList';
import subscriptionsStyle from 'styles/subscriptions';
import FloatingWalletBalance from 'component/floatingWalletBalance';
import FileItem from 'component/fileItem';
+import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
+import ModalPicker from 'component/modalPicker';
+import SubscribedChannelList from 'component/subscribedChannelList';
import SuggestedSubscriptions from 'component/suggestedSubscriptions';
import UriBar from 'component/uriBar';
class SubscriptionsPage extends React.PureComponent {
state = {
showingSuggestedSubs: false,
+ showSortPicker: false,
+ orderBy: ['release_time'],
+ filteredChannels: [],
+ currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting subscriptions by new
};
didFocusListener;
@@ -48,7 +65,6 @@ class SubscriptionsPage extends React.PureComponent {
setPlayerVisible();
doFetchMySubscriptions();
doFetchRecommendedSubscriptions();
- doSetViewMode(subscriptionsViewMode ? subscriptionsViewMode : Constants.SUBSCRIPTIONS_VIEW_ALL);
};
componentDidMount() {
@@ -63,10 +79,38 @@ class SubscriptionsPage extends React.PureComponent {
}
}
- changeViewMode = viewMode => {
- const { setClientSetting, doSetViewMode } = this.props;
- setClientSetting(Constants.SETTING_SUBSCRIPTIONS_VIEW_MODE, viewMode);
- doSetViewMode(viewMode);
+ handleSortByItemSelected = item => {
+ let orderBy = [];
+ switch (item.name) {
+ case Constants.SORT_BY_HOT:
+ orderBy = Constants.DEFAULT_ORDER_BY;
+ break;
+
+ 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 });
+ };
+
+ handleChannelSelected = channelUri => {
+ const { subscribedChannels } = this.props;
+ this.setState({
+ filteredChannels:
+ channelUri === Constants.ALL_PLACEHOLDER
+ ? []
+ : subscribedChannels.filter(channel => channel.uri === channelUri),
+ });
+ };
+
+ prependSubscribedChannelsWithAll = subscribedChannels => {
+ const channelUris = subscribedChannels.map(channel => channel.uri);
+ return [Constants.ALL_PLACEHOLDER].concat(channelUris);
};
render() {
@@ -84,6 +128,7 @@ class SubscriptionsPage extends React.PureComponent {
unreadSubscriptions,
navigation,
} = this.props;
+ const { currentSortByItem, filteredChannels } = this.state;
const numberOfSubscriptions = subscribedChannels ? subscribedChannels.length : 0;
const hasSubscriptions = numberOfSubscriptions > 0;
@@ -92,91 +137,46 @@ class SubscriptionsPage extends React.PureComponent {
this.setState({ showingSuggestedSubs: true });
}
+ const channelIds =
+ filteredChannels.length > 0
+ ? filteredChannels.map(channel => {
+ const { claimId } = parseURI(channel.uri);
+ return claimId;
+ })
+ : subscribedChannels &&
+ subscribedChannels.map(channel => {
+ const { claimId } = parseURI(channel.uri);
+ return claimId;
+ });
+
return (
-
-
- {!this.state.showingSuggestedSubs && hasSubscriptions && !loading && (
-
- this.changeViewMode(Constants.SUBSCRIPTIONS_VIEW_ALL)}
- />
- this.changeViewMode(Constants.SUBSCRIPTIONS_VIEW_LATEST_FIRST)}
- />
-
- )}
-
+
+
+ Channels you follow
+ {!this.state.showingSuggestedSubs && hasSubscriptions && (
+ this.setState({ showSortPicker: true })}
+ >
+ {currentSortByItem.label.split(' ')[0]}
+
+
+ )}
+
{!this.state.showingSuggestedSubs && hasSubscriptions && !loading && (
- {viewMode === Constants.SUBSCRIPTIONS_VIEW_ALL && (
- (
-
- )}
- data={allSubscriptions.sort((a, b) => {
- return b.height - a.height;
- })}
- keyExtractor={(item, index) => uriFromFileInfo(item)}
- />
- )}
-
- {viewMode === Constants.SUBSCRIPTIONS_VIEW_LATEST_FIRST && (
-
- {unreadSubscriptions.length ? (
-
- {unreadSubscriptions.map(({ channel, uris }) => {
- const { claimName } = parseURI(channel);
- return uris.map(uri => (
-
- ));
- })}
-
- ) : (
-
-
- All caught up! You might like the channels below.
-
-
-
- )}
-
- )}
+
+
)}
@@ -189,14 +189,13 @@ class SubscriptionsPage extends React.PureComponent {
{this.state.showingSuggestedSubs && (
{!hasSubscriptions && (
-
- You are not subscribed to any channels at the moment. Here are some channels that we think you might
- enjoy.
-
+
+ You are not subscribed to any channels at the moment.
+
)}
{hasSubscriptions && (
-
+
You are currently subscribed to {numberOfSubscriptions} channel{numberOfSubscriptions > 1 ? 's' : ''}.
@@ -209,13 +208,25 @@ class SubscriptionsPage extends React.PureComponent {
)}
{loadingSuggested && (
-
+
+
+ \\
+
)}
{!loadingSuggested && }
)}
-
+ {!this.state.showSortPicker && }
+ {this.state.showSortPicker && (
+ this.setState({ showSortPicker: false })}
+ onItemSelected={this.handleSortByItemSelected}
+ selectedItem={this.state.currentSortByItem}
+ items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
+ />
+ )}
);
}
diff --git a/src/page/tag/index.js b/src/page/tag/index.js
new file mode 100644
index 0000000..5a76f94
--- /dev/null
+++ b/src/page/tag/index.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
+import { selectCurrentRoute } from 'redux/selectors/drawer';
+import Constants from 'constants';
+import TagPage from './view';
+
+const select = state => ({
+ currentRoute: selectCurrentRoute(state),
+});
+
+const perform = dispatch => ({
+ pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_TAG)),
+ setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
+});
+
+export default connect(
+ select,
+ perform
+)(TagPage);
diff --git a/src/page/tag/view.js b/src/page/tag/view.js
new file mode 100644
index 0000000..8b685a6
--- /dev/null
+++ b/src/page/tag/view.js
@@ -0,0 +1,115 @@
+import React from 'react';
+import { ActivityIndicator, NativeModules, FlatList, Text, TouchableOpacity, View } from 'react-native';
+import { DEFAULT_FOLLOWED_TAGS, normalizeURI } from 'lbry-redux';
+import { formatTagTitle } 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 ModalPicker from 'component/modalPicker';
+import UriBar from 'component/uriBar';
+
+class TagPage extends React.PureComponent {
+ state = {
+ tag: null,
+ showSortPicker: false,
+ orderBy: Constants.DEFAULT_ORDER_BY,
+ currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[0],
+ };
+
+ didFocusListener;
+
+ componentWillMount() {
+ const { navigation } = this.props;
+ this.didFocusListener = navigation.addListener('didFocus', this.onComponentFocused);
+ }
+
+ componentWillUnmount() {
+ if (this.didFocusListener) {
+ this.didFocusListener.remove();
+ }
+ }
+
+ onComponentFocused = () => {
+ const { pushDrawerStack, setPlayerVisible, navigation } = this.props;
+ this.setState({ tag: navigation.state.params.tag });
+ pushDrawerStack();
+ setPlayerVisible();
+ };
+
+ componentDidMount() {
+ this.onComponentFocused();
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const { currentRoute, navigation } = nextProps;
+ const { currentRoute: prevRoute } = this.props;
+ if (Constants.DRAWER_ROUTE_TAG === currentRoute && currentRoute !== prevRoute) {
+ this.onComponentFocused();
+ }
+ }
+
+ handleSortByItemSelected = item => {
+ let orderBy = [];
+ switch (item.name) {
+ case Constants.SORT_BY_HOT:
+ orderBy = Constants.DEFAULT_ORDER_BY;
+ break;
+
+ 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 });
+ };
+
+ render() {
+ const { navigation } = this.props;
+ const { tag, currentSortByItem } = this.state;
+
+ return (
+
+
+
+ {formatTagTitle(tag)}
+ this.setState({ showSortPicker: true })}>
+ {currentSortByItem.label.split(' ')[0]}
+
+
+
+ }
+ style={discoverStyle.tagPageClaimList}
+ orderBy={this.state.orderBy}
+ tags={[tag]}
+ navigation={navigation}
+ orientation={Constants.ORIENTATION_VERTICAL}
+ />
+ {!this.state.showSortPicker && }
+ {this.state.showSortPicker && (
+ this.setState({ showSortPicker: false })}
+ onItemSelected={this.handleSortByItemSelected}
+ selectedItem={this.state.currentSortByItem}
+ items={Constants.CLAIM_SEARCH_SORT_BY_ITEMS}
+ />
+ )}
+
+ );
+ }
+}
+
+export default TagPage;
diff --git a/src/page/trending/index.js b/src/page/trending/index.js
index b543a22..86df237 100644
--- a/src/page/trending/index.js
+++ b/src/page/trending/index.js
@@ -1,18 +1,16 @@
import { connect } from 'react-redux';
-import { doFetchTrendingUris, selectTrendingUris, selectFetchingTrendingUris } from 'lbryinc';
+import { selectFollowedTags } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectCurrentRoute } from 'redux/selectors/drawer';
-import Constants from 'constants';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import TrendingPage from './view';
const select = state => ({
currentRoute: selectCurrentRoute(state),
- trendingUris: selectTrendingUris(state),
- fetchingTrendingUris: selectFetchingTrendingUris(state),
+ followedTags: selectFollowedTags(state),
});
const perform = dispatch => ({
- fetchTrendingUris: () => dispatch(doFetchTrendingUris()),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_TRENDING)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
diff --git a/src/page/trending/view.js b/src/page/trending/view.js
index c25fcf4..3891eb0 100644
--- a/src/page/trending/view.js
+++ b/src/page/trending/view.js
@@ -1,17 +1,32 @@
import React from 'react';
-import { ActivityIndicator, NativeModules, FlatList, Text, View } from 'react-native';
-import { normalizeURI } from 'lbry-redux';
+import { ActivityIndicator, NativeModules, FlatList, Text, TouchableOpacity, View } from 'react-native';
+import { DEFAULT_FOLLOWED_TAGS, normalizeURI } from 'lbry-redux';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
+import ClaimList from 'component/claimList';
import FileItem from 'component/fileItem';
-import discoverStyle from 'styles/discover';
-import fileListStyle from 'styles/fileList';
+import Link from 'component/link';
+import ModalPicker from 'component/modalPicker';
+import ModalTagSelector from 'component/modalTagSelector';
import Colors from 'styles/colors';
-import Constants from 'constants';
+import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import FloatingWalletBalance from 'component/floatingWalletBalance';
+import Icon from 'react-native-vector-icons/FontAwesome5';
import UriBar from 'component/uriBar';
+import discoverStyle from 'styles/discover';
+
+const TRENDING_FOR_ITEMS = [
+ { icon: 'globe-americas', name: 'everyone', label: 'Everyone' },
+ { icon: 'hashtag', name: 'tags', label: 'Tags you follow' },
+];
class TrendingPage extends React.PureComponent {
+ state = {
+ showModalTagSelector: false,
+ showTrendingForPicker: false,
+ currentTrendingForItem: TRENDING_FOR_ITEMS[0],
+ };
+
didFocusListener;
componentWillMount() {
@@ -26,10 +41,9 @@ class TrendingPage extends React.PureComponent {
}
onComponentFocused = () => {
- const { fetchTrendingUris, pushDrawerStack, setPlayerVisible } = this.props;
+ const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack();
setPlayerVisible();
- fetchTrendingUris();
};
componentDidMount() {
@@ -44,39 +58,62 @@ class TrendingPage extends React.PureComponent {
}
}
+ handleTrendingForItemSelected = item => {
+ this.setState({ currentTrendingForItem: item, showTrendingForPicker: false });
+ };
+
render() {
- const { trendingUris, fetchingTrendingUris, navigation } = this.props;
- const hasContent = typeof trendingUris === 'object' && trendingUris.length,
- failedToLoad = !fetchingTrendingUris && !hasContent;
+ const { followedTags, navigation } = this.props;
+ const { currentTrendingForItem, showModalTagSelector, showTrendingForPicker } = this.state;
return (
- {!hasContent && fetchingTrendingUris && (
-
-
- Fetching content...
-
- )}
- {hasContent && (
- (
-
- )}
- data={trendingUris.map(uri => uri.url)}
- keyExtractor={(item, index) => item}
+
+ Trending
+
+ {TRENDING_FOR_ITEMS[1].name === currentTrendingForItem.name && (
+ this.setState({ showModalTagSelector: true })}
+ />
+ )}
+ this.setState({ showTrendingForPicker: true })}
+ >
+ {currentTrendingForItem.label.split(' ')[0]}
+
+
+
+
+ }
+ style={discoverStyle.verticalClaimList}
+ orderBy={Constants.DEFAULT_ORDER_BY}
+ trendingForAll={TRENDING_FOR_ITEMS[0].name === currentTrendingForItem.name}
+ tags={followedTags.map(tag => tag.name)}
+ navigation={navigation}
+ orientation={Constants.ORIENTATION_VERTICAL}
+ />
+ {!showModalTagSelector && }
+ {showModalTagSelector && (
+ this.setState({ showModalTagSelector: false })}
+ onDonePress={() => this.setState({ showModalTagSelector: false })}
+ />
+ )}
+ {showTrendingForPicker && (
+ this.setState({ showTrendingForPicker: false })}
+ onItemSelected={this.handleTrendingForItemSelected}
+ selectedItem={currentTrendingForItem}
+ items={TRENDING_FOR_ITEMS}
/>
)}
-
);
}
diff --git a/src/styles/channelIcon.js b/src/styles/channelIcon.js
new file mode 100644
index 0000000..34856c0
--- /dev/null
+++ b/src/styles/channelIcon.js
@@ -0,0 +1,48 @@
+import { StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const channelIconStyle = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: 16,
+ alignSelf: 'flex-start',
+ },
+ placeholderText: {
+ fontFamily: 'Inter-UI-SemiBold',
+ fontSize: 14,
+ },
+ thumbnailContainer: {
+ width: 80,
+ height: 80,
+ borderRadius: 160,
+ overflow: 'hidden',
+ },
+ borderedThumbnailContainer: {
+ borderWidth: 1,
+ borderColor: Colors.LighterGrey,
+ },
+ thumbnail: {
+ width: '100%',
+ height: '100%',
+ },
+ centered: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ title: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 12,
+ width: 80,
+ marginTop: 4,
+ textAlign: 'center',
+ },
+});
+
+export default channelIconStyle;
diff --git a/src/styles/claimList.js b/src/styles/claimList.js
new file mode 100644
index 0000000..5ab9514
--- /dev/null
+++ b/src/styles/claimList.js
@@ -0,0 +1,34 @@
+import { StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const claimListStyle = StyleSheet.create({
+ horizontalScrollContainer: {
+ marginBottom: 12,
+ },
+ horizontalScrollPadding: {
+ paddingLeft: 16,
+ },
+ verticalScrollContainer: {
+ flex: 1,
+ },
+ verticalScrollPadding: {
+ paddingBottom: 16,
+ },
+ verticalListItem: {
+ flex: 1,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginLeft: 8,
+ marginRight: 8,
+ marginTop: 4,
+ marginBottom: 4,
+ },
+ verticalLoading: {
+ width: '100%',
+ height: 48,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+});
+
+export default claimListStyle;
diff --git a/src/styles/discover.js b/src/styles/discover.js
index 49e80c9..d0ba91b 100644
--- a/src/styles/discover.js
+++ b/src/styles/discover.js
@@ -27,8 +27,28 @@ const discoverStyle = StyleSheet.create({
},
scrollContainer: {
flex: 1,
- paddingTop: 12,
- marginTop: 60,
+ },
+ titleRow: {
+ flexDirection: 'row',
+ marginTop: 76,
+ marginBottom: 8,
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ marginLeft: 16,
+ marginRight: 16,
+ },
+ rightTitleRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ pageTitle: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 24,
+ },
+ customizeLink: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 14,
+ marginRight: 48,
},
trendingContainer: {
flex: 1,
@@ -47,12 +67,18 @@ const discoverStyle = StyleSheet.create({
textAlign: 'center',
marginLeft: 10,
},
+ categoryTitleRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ marginLeft: 16,
+ marginRight: 16,
+ marginTop: 6,
+ marginBottom: 6,
+ },
categoryName: {
fontFamily: 'Inter-UI-SemiBold',
fontSize: 18,
- marginLeft: 24,
- marginTop: 12,
- marginBottom: 6,
color: Colors.Black,
},
fileItem: {
@@ -163,12 +189,49 @@ const discoverStyle = StyleSheet.create({
scrollPadding: {
paddingBottom: 24,
},
+ listLoading: {
+ flex: 1,
+ height: 64,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
horizontalScrollContainer: {
marginBottom: 12,
},
horizontalScrollPadding: {
paddingLeft: 20,
},
+ verticalClaimList: {
+ flex: 1,
+ },
+ tagPageTitle: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 24,
+ },
+ tagPageClaimList: {
+ flex: 1,
+ },
+ tagTitleRow: {
+ marginTop: 76,
+ marginLeft: 16,
+ marginRight: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ tagSortBy: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginRight: 4,
+ },
+ tagSortText: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 14,
+ marginRight: 4,
+ },
+ tagSortIcon: {
+ marginTop: -6,
+ },
});
export default discoverStyle;
diff --git a/src/styles/modalPicker.js b/src/styles/modalPicker.js
new file mode 100644
index 0000000..b412cba
--- /dev/null
+++ b/src/styles/modalPicker.js
@@ -0,0 +1,65 @@
+import { StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const modalPickerStyle = StyleSheet.create({
+ overlay: {
+ backgroundColor: '#00000055',
+ flex: 1,
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ zIndex: 300,
+ },
+ overlayTouchArea: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ },
+ container: {
+ position: 'absolute',
+ left: 8,
+ right: 8,
+ bottom: 8,
+ borderRadius: 8,
+ backgroundColor: Colors.White,
+ padding: 12,
+ },
+ title: {
+ fontFamily: 'Inter-UI-SemiBold',
+ fontSize: 12,
+ marginTop: 4,
+ textTransform: 'uppercase',
+ },
+ listItem: {
+ paddingTop: 10,
+ paddingBottom: 10,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ divider: {
+ marginTop: 12,
+ marginBottom: 8,
+ borderBottomColor: Colors.LighterGrey,
+ borderBottomWidth: 1,
+ width: '100%',
+ },
+ itemIcon: {
+ marginLeft: 8,
+ marginRight: 12,
+ },
+ itemLabel: {
+ alignSelf: 'flex-start',
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 16,
+ },
+ itemSelected: {
+ position: 'absolute',
+ right: 8,
+ },
+});
+
+export default modalPickerStyle;
diff --git a/src/styles/modalTagSelector.js b/src/styles/modalTagSelector.js
new file mode 100644
index 0000000..a341c95
--- /dev/null
+++ b/src/styles/modalTagSelector.js
@@ -0,0 +1,59 @@
+import { StyleSheet } from 'react-native';
+import Colors from './colors';
+
+const modalTagSelectorStyle = StyleSheet.create({
+ overlay: {
+ backgroundColor: '#00000099',
+ flex: 1,
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ zIndex: 300,
+ alignItems: 'center',
+ },
+ overlayTouchArea: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ },
+ container: {
+ flex: 1,
+ borderRadius: 8,
+ backgroundColor: Colors.White,
+ padding: 16,
+ position: 'absolute',
+ left: 8,
+ right: 8,
+ bottom: 8,
+ },
+ tag: {
+ marginRight: 4,
+ marginBottom: 4,
+ },
+ tagList: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ titleRow: {
+ marginBottom: 12,
+ },
+ title: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 24,
+ },
+ buttons: {
+ marginTop: 16,
+ },
+ doneButton: {
+ alignSelf: 'flex-start',
+ backgroundColor: Colors.LbryGreen,
+ paddingLeft: 16,
+ paddingRight: 16,
+ },
+});
+
+export default modalTagSelectorStyle;
diff --git a/src/styles/subscriptions.js b/src/styles/subscriptions.js
index bf24acb..8648421 100644
--- a/src/styles/subscriptions.js
+++ b/src/styles/subscriptions.js
@@ -12,15 +12,16 @@ const subscriptionsStyle = StyleSheet.create({
},
suggestedSubsContainer: {
flex: 1,
- marginTop: 60,
+ },
+ suggestedScrollPadding: {
+ paddingTop: 8,
},
button: {
alignSelf: 'flex-start',
backgroundColor: Colors.LbryGreen,
paddingLeft: 16,
paddingRight: 16,
- marginLeft: 16,
- marginBottom: 16,
+ marginBottom: 8,
},
busyContainer: {
flex: 1,
@@ -38,7 +39,15 @@ const subscriptionsStyle = StyleSheet.create({
infoText: {
fontFamily: 'Inter-UI-Regular',
fontSize: 16,
- margin: 16,
+ marginTop: 8,
+ marginBottom: 8,
+ },
+ infoArea: {
+ marginLeft: 16,
+ marginRight: 16,
+ paddingBottom: 4,
+ borderBottomWidth: 1,
+ borderBottomColor: Colors.LighterGrey,
},
suggestedContainer: {
flex: 1,
@@ -91,6 +100,14 @@ const subscriptionsStyle = StyleSheet.create({
marginTop: 8,
fontSize: 18,
},
+ channelList: {
+ marginLeft: 16,
+ marginRight: 16,
+ marginTop: 8,
+ paddingBottom: 8,
+ borderBottomColor: Colors.LighterGrey,
+ borderBottomWidth: 1,
+ },
channelTitle: {
fontFamily: 'Inter-UI-SemiBold',
fontSize: 20,
@@ -99,11 +116,6 @@ const subscriptionsStyle = StyleSheet.create({
marginBottom: 16,
color: Colors.LbryGreen,
},
- titleRow: {
- flex: 1,
- flexDirection: 'row',
- justifyContent: 'space-between',
- },
subscribeButton: {
alignSelf: 'flex-start',
marginRight: 24,
@@ -129,6 +141,99 @@ const subscriptionsStyle = StyleSheet.create({
activeMode: {
fontFamily: 'Inter-UI-SemiBold',
},
+ claimList: {
+ flex: 1,
+ },
+ pageTitle: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 24,
+ },
+ titleRow: {
+ marginTop: 76,
+ marginLeft: 16,
+ marginRight: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ tagSortBy: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginRight: 4,
+ },
+ tagSortText: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 14,
+ marginRight: 4,
+ },
+ tagSortIcon: {
+ marginTop: -6,
+ },
+ centered: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ suggestedItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 16,
+ marginLeft: 16,
+ marginRight: 16,
+ },
+ suggestedItemThumbnailContainer: {
+ width: 70,
+ height: 70,
+ borderRadius: 140,
+ overflow: 'hidden',
+ },
+ suggestedItemThumbnail: {
+ width: '100%',
+ height: '100%',
+ },
+ suggestedItemDetails: {
+ marginLeft: 16,
+ flexDirection: 'row',
+ },
+ suggestedItemSubscribe: {
+ backgroundColor: Colors.White,
+ position: 'absolute',
+ right: 0,
+ top: 0,
+ },
+ suggestedItemTitle: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 16,
+ marginBottom: 4,
+ },
+ suggestedItemName: {
+ fontFamily: 'Inter-UI-SemiBold',
+ fontSize: 14,
+ marginBottom: 4,
+ color: Colors.LbryGreen,
+ },
+ suggestedItemTagList: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ suggestedSubTitle: {
+ fontFamily: 'Inter-UI-Regular',
+ fontSize: 20,
+ marginLeft: 16,
+ marginRight: 16,
+ marginBottom: 12,
+ },
+ suggestedSectionSeparator: {
+ marginBottom: 16,
+ },
+ tag: {
+ marginRight: 4,
+ marginBottom: 4,
+ },
});
export default subscriptionsStyle;
diff --git a/src/styles/uriBar.js b/src/styles/uriBar.js
index 887f6e5..ea5722e 100644
--- a/src/styles/uriBar.js
+++ b/src/styles/uriBar.js
@@ -16,6 +16,8 @@ const uriBarStyle = StyleSheet.create({
shadowOffset: {
height: StyleSheet.hairlineWidth,
},
+ },
+ containerElevated: {
elevation: 4,
},
uriText: {
@@ -34,6 +36,8 @@ const uriBarStyle = StyleSheet.create({
top: 0,
width: '100%',
zIndex: 200,
+ },
+ overlayElevated: {
elevation: 16,
},
inFocus: {
diff --git a/src/styles/wallet.js b/src/styles/wallet.js
index c8c61df..3079076 100644
--- a/src/styles/wallet.js
+++ b/src/styles/wallet.js
@@ -227,7 +227,7 @@ const walletStyle = StyleSheet.create({
marginTop: 16,
paddingBottom: 14,
borderBottomWidth: 1,
- borderBottomColor: Colors.PageBackground
+ borderBottomColor: Colors.PageBackground,
},
syncDriverLink: {
color: Colors.LbryGreen,
diff --git a/src/utils/helper.js b/src/utils/helper.js
index 527b332..ed41011 100644
--- a/src/utils/helper.js
+++ b/src/utils/helper.js
@@ -1,8 +1,7 @@
import { NavigationActions, StackActions } from 'react-navigation';
import { buildURI, isURIValid } from 'lbry-redux';
import { doPopDrawerStack, doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
-import { DrawerRoutes } from 'constants';
-import Constants from 'constants';
+import Constants, { DrawerRoutes } from 'constants'; // eslint-disable-line node/no-deprecated-api
function getRouteForSpecialUri(uri) {
let targetRoute;
@@ -35,7 +34,8 @@ export function dispatchNavigateToUri(dispatch, nav, uri, isNavigatingBack) {
return;
}
- let uriVars = {};
+ let uriVars = {},
+ uriVarsStr;
if (uri.indexOf('?') > -1) {
uriVarsStr = uri.substring(uri.indexOf('?') + 1);
uri = uri.substring(0, uri.indexOf('?'));
@@ -49,10 +49,10 @@ export function dispatchNavigateToUri(dispatch, nav, uri, isNavigatingBack) {
dispatch(doSetPlayerVisible(true));
}
- if (nav && nav.routes && nav.routes.length > 0 && 'Main' === nav.routes[0].routeName) {
+ if (nav && nav.routes && nav.routes.length > 0 && nav.routes[0].routeName === 'Main') {
const mainRoute = nav.routes[0];
const discoverRoute = mainRoute.routes[0];
- if (discoverRoute.index > 0 && 'File' === discoverRoute.routes[discoverRoute.index].routeName) {
+ if (discoverRoute.index > 0 && discoverRoute.routes[discoverRoute.index].routeName === 'File') {
const fileRoute = discoverRoute.routes[discoverRoute.index];
// Currently on a file page, so we can ignore (if the URI is the same) or replace (different URIs)
if (uri !== fileRoute.params.uri) {
@@ -119,7 +119,8 @@ export function navigateToUri(navigation, uri, additionalParams, isNavigatingBac
return;
}
- let uriVars = {};
+ let uriVars = {},
+ uriVarsStr;
if (uri.indexOf('?') > -1) {
uriVarsStr = uri.substring(uri.indexOf('?') + 1);
uri = uri.substring(0, uri.indexOf('?'));
@@ -128,7 +129,7 @@ export function navigateToUri(navigation, uri, additionalParams, isNavigatingBac
const { store } = window;
const params = Object.assign({ uri, uriVars }, additionalParams);
- if ('File' === navigation.state.routeName) {
+ if (navigation.state.routeName === 'File') {
const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
navigation.dispatch(stackAction);
if (store && store.dispatch && !isNavigatingBack) {
@@ -182,3 +183,15 @@ export function uriFromFileInfo(fileInfo) {
uriParams.claimId = claimId;
return buildURI(uriParams);
}
+
+export function formatTagTitle(title) {
+ if (!title) {
+ return null;
+ }
+ return title.charAt(0).toUpperCase() + title.substring(1);
+}
+
+// i18n placeholder until we find a good react-native i18n module
+export function __(str) {
+ return str;
+}