Performance (#9)

* performance improvements
* fix time picker for top content on tag page
* redux-persist v5. style tweaks.
This commit is contained in:
Akinwale Ariwodola 2019-08-09 07:41:40 +01:00 committed by GitHub
parent 22d860a885
commit 18d654cc66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1725 additions and 439 deletions

1132
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
"private": "true", "private": "true",
"scripts": { "scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start", "start": "node node_modules/react-native/local-cli/cli.js start",
"devtools": "react-devtools",
"format": "prettier 'src/**/*.{js,json}' --write", "format": "prettier 'src/**/*.{js,json}' --write",
"precommit": "lint-staged" "precommit": "lint-staged"
}, },
@ -11,7 +12,7 @@
"base-64": "^0.1.0", "base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0", "@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3", "gfycat-style-urls": "^1.0.3",
"lbry-redux": "lbryio/lbry-redux#multi-claim-search", "lbry-redux": "lbryio/lbry-redux#67a654f60630710cae72419448a73a18d076d18a",
"lbryinc": "lbryio/lbryinc", "lbryinc": "lbryio/lbryinc",
"lodash": ">=4.17.11", "lodash": ">=4.17.11",
"merge": ">=1.2.1", "merge": ">=1.2.1",
@ -63,6 +64,7 @@
"husky": "^0.14.3", "husky": "^0.14.3",
"lint-staged": "^7.0.4", "lint-staged": "^7.0.4",
"metro-react-native-babel-preset": "^0.55.0", "metro-react-native-babel-preset": "^0.55.0",
"prettier": "^1.11.1" "prettier": "^1.11.1",
"react-devtools": "^3.6.3"
} }
} }

View file

@ -12,7 +12,7 @@ import claimListStyle from 'styles/claimList';
import discoverStyle from 'styles/discover'; import discoverStyle from 'styles/discover';
import moment from 'moment'; import moment from 'moment';
const horizontalLimit = 10; const horizontalLimit = 7;
const softLimit = 500; const softLimit = 500;
class ClaimList extends React.PureComponent { class ClaimList extends React.PureComponent {
@ -199,8 +199,8 @@ class ClaimList extends React.PureComponent {
ListHeaderComponent={ListHeaderComponent} ListHeaderComponent={ListHeaderComponent}
style={claimListStyle.verticalScrollContainer} style={claimListStyle.verticalScrollContainer}
contentContainerStyle={claimListStyle.verticalScrollPadding} contentContainerStyle={claimListStyle.verticalScrollPadding}
initialNumToRender={8} initialNumToRender={10}
maxToRenderPerBatch={24} maxToRenderPerBatch={20}
removeClippedSubviews removeClippedSubviews
renderItem={({ item }) => ( renderItem={({ item }) => (
<FileListItem key={item} uri={item} style={claimListStyle.verticalListItem} navigation={navigation} /> <FileListItem key={item} uri={item} style={claimListStyle.verticalListItem} navigation={navigation} />
@ -208,7 +208,7 @@ class ClaimList extends React.PureComponent {
data={data} data={data}
keyExtractor={(item, index) => item} keyExtractor={(item, index) => item}
onEndReached={this.handleVerticalEndReached} onEndReached={this.handleVerticalEndReached}
onEndReachedThreshold={0.9} onEndReachedThreshold={0.2}
/> />
{(((subscriptionsView || trendingForAllView) && claimSearchLoading) || loading) && ( {(((subscriptionsView || trendingForAllView) && claimSearchLoading) || loading) && (
<View style={claimListStyle.verticalLoading}> <View style={claimListStyle.verticalLoading}>

View file

@ -57,7 +57,7 @@ class CustomRewardCard extends React.PureComponent<Props> {
return ( return (
<View style={[rewardStyle.rewardCard, rewardStyle.row]}> <View style={[rewardStyle.rewardCard, rewardStyle.row]}>
<View style={rewardStyle.leftCol}> <View style={rewardStyle.leftCol}>
{rewardIsPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />} {rewardIsPending && <ActivityIndicator size="small" color={Colors.NextLbryGreen} />}
</View> </View>
<View style={rewardStyle.midCol}> <View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>Custom Code</Text> <Text style={rewardStyle.rewardTitle}>Custom Code</Text>

View file

@ -8,12 +8,15 @@ import {
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
import FileListItem from './view'; import FileListItem from './view';
const select = (state, props) => ({ const select = (state, props) => ({
blackListedOutpoints: selectBlackListedOutpoints(state),
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
filteredOutpoints: selectFilteredOutpoints(state),
isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state), isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state), isResolvingUri: makeSelectIsUriResolving(props.uri)(state),

View file

@ -48,8 +48,10 @@ class FileListItem extends React.PureComponent {
render() { render() {
const { const {
blackListedOutpoints,
claim, claim,
fileInfo, fileInfo,
filteredOutpoints,
metadata, metadata,
featuredResult, featuredResult,
isResolvingUri, isResolvingUri,
@ -66,7 +68,7 @@ class FileListItem extends React.PureComponent {
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isResolving = !fileInfo && isResolvingUri; const isResolving = !fileInfo && isResolvingUri;
let name, channel, height, channelClaimId, fullChannelUri, signingChannel; let name, channel, height, channelClaimId, fullChannelUri, shouldHide, signingChannel;
if (claim) { if (claim) {
name = claim.name; name = claim.name;
signingChannel = claim.signing_channel; signingChannel = claim.signing_channel;
@ -74,9 +76,14 @@ class FileListItem extends React.PureComponent {
height = claim.height; height = claim.height;
channelClaimId = signingChannel ? signingChannel.claim_id : null; channelClaimId = signingChannel ? signingChannel.claim_id : null;
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel; fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = blackListedOutpoints.concat(filteredOutpoints);
shouldHide = outpointsToHide.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
}
} }
if (featuredResult && !isResolvingUri && !claim && !title && !name) { if (shouldHide || (featuredResult && !isResolvingUri && !claim && !title && !name)) {
return null; return null;
} }

View file

@ -468,7 +468,7 @@ class MediaPlayer extends React.PureComponent {
{this.state.buffering && ( {this.state.buffering && (
<View style={mediaPlayerStyle.loadingContainer}> <View style={mediaPlayerStyle.loadingContainer}>
<ActivityIndicator color={Colors.LbryGreen} size="large" /> <ActivityIndicator color={Colors.NextLbryGreen} size="large" />
</View> </View>
)} )}

View file

@ -1,11 +1,11 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
makeSelectClaimForUri, makeSelectClaimForUri,
doSearch,
makeSelectRecommendedContentForUri, makeSelectRecommendedContentForUri,
makeSelectTitleForUri, makeSelectTitleForUri,
selectIsSearching, selectIsSearching,
} from 'lbry-redux'; } from 'lbry-redux';
import { doNativeSearch } from 'redux/actions/performance';
import RelatedContent from './view'; import RelatedContent from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -16,7 +16,7 @@ const select = (state, props) => ({
}); });
const perform = dispatch => ({ const perform = dispatch => ({
search: query => dispatch(doSearch(query, 20, undefined, true)), search: query => dispatch(doNativeSearch(query, 20, undefined, true)),
}); });
export default connect( export default connect(

View file

@ -6,7 +6,7 @@ import FileListItem from 'component/fileListItem';
import fileListStyle from 'styles/fileList'; import fileListStyle from 'styles/fileList';
import relatedContentStyle from 'styles/relatedContent'; import relatedContentStyle from 'styles/relatedContent';
export default class RelatedContent extends React.PureComponent<Props> { export default class RelatedContent extends React.PureComponent {
constructor() { constructor() {
super(); super();
@ -17,7 +17,7 @@ export default class RelatedContent extends React.PureComponent<Props> {
this.getRecommendedContent(); this.getRecommendedContent();
} }
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps) {
const { claim, uri } = this.props; const { claim, uri } = this.props;
if (uri !== prevProps.uri) { if (uri !== prevProps.uri) {
@ -38,7 +38,7 @@ export default class RelatedContent extends React.PureComponent<Props> {
} }
} }
didSearch: ?boolean; didSearch;
render() { render() {
const { recommendedContent, isSearching, navigation } = this.props; const { recommendedContent, isSearching, navigation } = this.props;
@ -60,7 +60,9 @@ export default class RelatedContent extends React.PureComponent<Props> {
onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })} onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })}
/> />
))} ))}
{isSearching && <ActivityIndicator size="small" color={Colors.LbryGreen} style={relatedContentStyle.loading} />} {isSearching && (
<ActivityIndicator size="small" color={Colors.NextLbryGreen} style={relatedContentStyle.loading} />
)}
</View> </View>
); );
} }

View file

@ -86,7 +86,7 @@ class RewardCard extends React.PureComponent<Props> {
)} )}
</TouchableOpacity> </TouchableOpacity>
)} )}
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />} {isPending && <ActivityIndicator size="small" color={Colors.NextLbryGreen} />}
</View> </View>
<View style={rewardStyle.midCol}> <View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text> <Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text>

View file

@ -19,7 +19,7 @@ class SubscribeButton extends React.PureComponent {
const iconColor = isSubscribed ? null : Colors.Red; const iconColor = isSubscribed ? null : Colors.Red;
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe; const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const subscriptionLabel = isSubscribed ? null : __('Subscribe'); const subscriptionLabel = isSubscribed ? null : __('Follow');
const { claimName } = parseURI(uri); const { claimName } = parseURI(uri);
return ( return (
@ -28,7 +28,7 @@ class SubscribeButton extends React.PureComponent {
theme={'light'} theme={'light'}
icon={isSubscribed ? 'heart-broken' : 'heart'} icon={isSubscribed ? 'heart-broken' : 'heart'}
iconColor={iconColor} iconColor={iconColor}
solid={isSubscribed ? false : true} solid={!isSubscribed}
text={hideText ? null : subscriptionLabel} text={hideText ? null : subscriptionLabel}
onPress={() => { onPress={() => {
subscriptionHandler({ subscriptionHandler({

View file

@ -42,24 +42,22 @@ class SuggestedSubscriptionItem extends React.PureComponent {
</View> </View>
<View style={subscriptionsStyle.suggestedItemDetails}> <View style={subscriptionsStyle.suggestedItemDetails}>
<View style={subscriptionsStyle.suggestedItemInfo}> {title && (
{title && ( <Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={1}>
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={1}> {title}
{title}
</Text>
)}
<Text style={subscriptionsStyle.suggestedItemName} numberOfLines={1}>
{claim && claim.name}
</Text> </Text>
{tags && ( )}
<View style={subscriptionsStyle.suggestedItemTagList}> <Text style={subscriptionsStyle.suggestedItemName} numberOfLines={1}>
{tags && {claim && claim.name}
tags </Text>
.slice(0, 3) {tags && (
.map(tag => <Tag style={subscriptionsStyle.tag} key={tag} name={tag} navigation={navigation} />)} <View style={subscriptionsStyle.suggestedItemTagList}>
</View> {tags &&
)} tags
</View> .slice(0, 3)
.map(tag => <Tag style={subscriptionsStyle.tag} key={tag} name={tag} navigation={navigation} />)}
</View>
)}
</View> </View>
<SubscribeButton style={subscriptionsStyle.suggestedItemSubscribe} uri={normalizeURI(uri)} /> <SubscribeButton style={subscriptionsStyle.suggestedItemSubscribe} uri={normalizeURI(uri)} />

View file

@ -43,7 +43,7 @@ class SuggestedSubscriptions extends React.PureComponent {
if (loading) { if (loading) {
return ( return (
<View style={subscriptionsStyle.centered}> <View style={subscriptionsStyle.centered}>
<ActivityIndicator size="large" color={Colors.LbryGreen} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} />
</View> </View>
); );
} }

View file

@ -1,20 +1,21 @@
import React from 'react'; import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native'; import { Text, TouchableOpacity, View } from 'react-native';
import { formatTagName } from 'utils/helper';
import tagStyle from 'styles/tag'; import tagStyle from 'styles/tag';
import Colors from 'styles/colors'; 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 Icon from 'react-native-vector-icons/FontAwesome5';
export default class Tag extends React.PureComponent { export default class Tag extends React.PureComponent {
onPressDefault = () => { onPressDefault = () => {
const { name, navigation, type, onAddPress, onRemovePress } = this.props; const { name, navigation, type, onAddPress, onRemovePress } = this.props;
if ('add' === type) { if (type === 'add') {
if (onAddPress) { if (onAddPress) {
onAddPress(name); onAddPress(name);
} }
return; return;
} }
if ('remove' === type) { if (type === 'remove') {
if (onRemovePress) { if (onRemovePress) {
onRemovePress(name); onRemovePress(name);
} }
@ -48,7 +49,7 @@ export default class Tag extends React.PureComponent {
return ( return (
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}> <TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
<View style={tagStyle.content}> <View style={tagStyle.content}>
<Text style={tagStyle.text}>{name}</Text> <Text style={tagStyle.text}>{formatTagName(name)}</Text>
{type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />} {type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />}
</View> </View>
</TouchableOpacity> </TouchableOpacity>

View file

@ -103,7 +103,7 @@ const Constants = {
TIME_ALL, TIME_ALL,
CLAIM_SEARCH_SORT_BY_ITEMS: [ CLAIM_SEARCH_SORT_BY_ITEMS: [
{ icon: 'fire-alt', name: SORT_BY_HOT, label: 'Hot content' }, { icon: 'fire-alt', name: SORT_BY_HOT, label: 'Trending content' },
{ icon: 'certificate', name: SORT_BY_NEW, label: 'New content' }, { icon: 'certificate', name: SORT_BY_NEW, label: 'New content' },
{ icon: 'chart-line', name: SORT_BY_TOP, label: 'Top content' }, { icon: 'chart-line', name: SORT_BY_TOP, label: 'Top content' },
], ],

View file

@ -18,6 +18,7 @@ import {
authReducer, authReducer,
blacklistReducer, blacklistReducer,
costInfoReducer, costInfoReducer,
filteredReducer,
homepageReducer, homepageReducer,
rewardsReducer, rewardsReducer,
subscriptionsReducer, subscriptionsReducer,
@ -32,7 +33,6 @@ import AppWithNavigationState, {
} from 'component/AppNavigator'; } from 'component/AppNavigator';
import { REHYDRATE, PURGE, persistCombineReducers, persistStore } from 'redux-persist'; import { REHYDRATE, PURGE, persistCombineReducers, persistStore } from 'redux-persist';
import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4'; import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4';
import AsyncStorage from '@react-native-community/async-storage';
import FilesystemStorage from 'redux-persist-filesystem-storage'; import FilesystemStorage from 'redux-persist-filesystem-storage';
import createCompressor from 'redux-persist-transform-compress'; import createCompressor from 'redux-persist-transform-compress';
import createFilter from 'redux-persist-transform-filter'; import createFilter from 'redux-persist-transform-filter';
@ -43,7 +43,6 @@ import thunk from 'redux-thunk';
const globalExceptionHandler = (error, isFatal) => { const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) { if (error && NativeModules.Firebase) {
console.log(error);
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error)); NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
} }
}; };
@ -80,7 +79,7 @@ function enableBatching(reducer) {
const compressor = createCompressor(); const compressor = createCompressor();
const authFilter = createFilter('auth', ['authToken']); const authFilter = createFilter('auth', ['authToken']);
const contentFilter = createFilter('content', ['positions']); const contentFilter = createFilter('content', ['positions']);
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']); const saveClaimsFilter = createFilter('claims', ['claimsByUri']);
const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions']); const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions']);
const settingsFilter = createFilter('settings', ['clientSettings']); const settingsFilter = createFilter('settings', ['clientSettings']);
const tagsFilter = createFilter('tags', ['followedTags']); const tagsFilter = createFilter('tags', ['followedTags']);
@ -109,6 +108,7 @@ const reducers = persistCombineReducers(persistOptions, {
drawer: drawerReducer, drawer: drawerReducer,
file: fileReducer, file: fileReducer,
fileInfo: fileInfoReducer, fileInfo: fileInfoReducer,
filtered: filteredReducer,
homepage: homepageReducer, homepage: homepageReducer,
nav: navigatorReducer, nav: navigatorReducer,
notifications: notificationsReducer, notifications: notificationsReducer,
@ -136,11 +136,29 @@ const store = createStore(
); );
window.store = store; window.store = store;
persistStore(store, persistOptions, err => { const persistor = persistStore(store, persistOptions, err => {
if (err) { if (err) {
console.log('Unable to load saved SETTINGS'); console.log('Unable to load saved SETTINGS');
} }
}); });
window.persistor = persistor;
/*
const persistFilter = {
'auth': ['authToken'],
'claims': ['byId', 'claimsByUri'],
'content': ['positions'],
'subscriptions': ['enabledChannelNotifications', 'subscriptions'],
'settings': ['clientSettings'],
'tags': ['followedTags'],
'wallet': ['receiveAddress']
};
store.subscribe(() => {
const state = (({ auth, claims, content, subscriptions, settings, tags, wallet }) =>
({ auth, claims, content, subscriptions, settings, tags, wallet }))(store.getState());
NativeModules.StatePersistor.update(state, persistFilter);
}); */
// TODO: Find i18n module that is compatible with react-native // TODO: Find i18n module that is compatible with react-native
global.__ = str => str; global.__ = str => str;

View file

@ -4,7 +4,7 @@ import { ActivityIndicator, Dimensions, Image, ScrollView, Text, TouchableOpacit
import { TabView, SceneMap } from 'react-native-tab-view'; import { TabView, SceneMap } from 'react-native-tab-view';
import { navigateBack } from 'utils/helper'; import { navigateBack } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Button from 'component/button'; import Button from 'component/button';
import Link from 'component/link'; import Link from 'component/link';
import FileList from 'component/fileList'; import FileList from 'component/fileList';
@ -53,7 +53,7 @@ class ChannelPage extends React.PureComponent {
if (fetching) { if (fetching) {
contentList = ( contentList = (
<View style={channelPageStyle.busyContainer}> <View style={channelPageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={channelPageStyle.infoText}>Fetching content...</Text> <Text style={channelPageStyle.infoText}>Fetching content...</Text>
</View> </View>
); );
@ -123,21 +123,21 @@ class ChannelPage extends React.PureComponent {
); );
} }
const { cover, description, thumbnail, email, website_url, title } = claim.value; const { cover, description, thumbnail, email, website_url: websiteUrl, title } = claim.value;
return ( return (
<View style={channelPageStyle.aboutTab}> <View style={channelPageStyle.aboutTab}>
{!website_url && !email && !description && ( {!websiteUrl && !email && !description && (
<View style={channelPageStyle.busyContainer}> <View style={channelPageStyle.busyContainer}>
<Text style={channelPageStyle.infoText}>Nothing here yet. Please check back later.</Text> <Text style={channelPageStyle.infoText}>Nothing here yet. Please check back later.</Text>
</View> </View>
)} )}
{(website_url || email || description) && ( {(websiteUrl || email || description) && (
<ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}> <ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}>
{website_url && website_url.trim().length > 0 && ( {websiteUrl && websiteUrl.trim().length > 0 && (
<View style={channelPageStyle.aboutItem}> <View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutTitle}>Website</Text> <Text style={channelPageStyle.aboutTitle}>Website</Text>
<Link style={channelPageStyle.aboutText} text={website_url} href={website_url} /> <Link style={channelPageStyle.aboutText} text={websiteUrl} href={websiteUrl} />
</View> </View>
)} )}

View file

@ -207,7 +207,7 @@ class DiscoverPage extends React.PureComponent {
buildSections = () => { buildSections = () => {
return this.state.tagCollection.map(tags => ({ return this.state.tagCollection.map(tags => ({
title: tags.length === 1 ? tags[0] : 'Trending', title: tags.length === 1 ? tags[0] : 'All tags you follow',
data: [tags], data: [tags],
})); }));
}; };
@ -216,7 +216,9 @@ class DiscoverPage extends React.PureComponent {
const tags = followedTags.map(tag => tag.name); const tags = followedTags.map(tag => tag.name);
// each of the followed tags // each of the followed tags
const tagCollection = tags.map(tag => [tag]); const tagCollection = _.shuffle(tags)
.slice(0, 6)
.map(tag => [tag]);
// everything // everything
tagCollection.unshift(tags); tagCollection.unshift(tags);
@ -268,7 +270,8 @@ class DiscoverPage extends React.PureComponent {
renderItem={({ item, index, section }) => ( renderItem={({ item, index, section }) => (
<ClaimList <ClaimList
key={item.sort().join(',')} key={item.sort().join(',')}
orderBy={item.length > 1 ? Constants.DEFAULT_ORDER_BY : orderBy} orderBy={orderBy}
time={Constants.TIME_WEEK}
tags={item} tags={item}
morePlaceholder morePlaceholder
navigation={navigation} navigation={navigation}

View file

@ -3,7 +3,7 @@ import { Lbry, buildURI } from 'lbry-redux';
import { ActivityIndicator, Button, FlatList, Text, TextInput, View, ScrollView } from 'react-native'; import { ActivityIndicator, Button, FlatList, Text, TextInput, View, ScrollView } from 'react-native';
import { navigateToUri, uriFromFileInfo } from 'utils/helper'; import { navigateToUri, uriFromFileInfo } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import PageHeader from 'component/pageHeader'; import PageHeader from 'component/pageHeader';
import FileListItem from 'component/fileListItem'; import FileListItem from 'component/fileListItem';
import FloatingWalletBalance from 'component/floatingWalletBalance'; import FloatingWalletBalance from 'component/floatingWalletBalance';
@ -65,7 +65,7 @@ class DownloadsPage extends React.PureComponent {
)} )}
{fetching && !hasDownloads && ( {fetching && !hasDownloads && (
<View style={downloadsStyle.busyContainer}> <View style={downloadsStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={downloadsStyle.loading} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} style={downloadsStyle.loading} />
</View> </View>
)} )}
{hasDownloads && ( {hasDownloads && (

View file

@ -563,11 +563,11 @@ class FilePage extends React.PureComponent {
let innerContent = null; let innerContent = null;
if ((isResolvingUri && !claim) || !claim) { if ((isResolvingUri && !claim) || !claim) {
innerContent = ( return (
<View style={filePageStyle.container}> <View style={filePageStyle.container}>
{isResolvingUri && ( {isResolvingUri && (
<View style={filePageStyle.busyContainer}> <View style={filePageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text> <Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
</View> </View>
)} )}
@ -579,9 +579,13 @@ class FilePage extends React.PureComponent {
<UriBar value={uri} navigation={navigation} /> <UriBar value={uri} navigation={navigation} />
</View> </View>
); );
} else if (claim && claim.name.length && claim.name[0] === '@') { }
innerContent = <ChannelPage uri={uri} navigation={navigation} />;
} else if (claim) { if (claim) {
if (claim && claim.name.length && claim.name[0] === '@') {
return <ChannelPage uri={uri} navigation={navigation} />;
}
let isClaimBlackListed = false; let isClaimBlackListed = false;
if (blackListedOutpoints) { if (blackListedOutpoints) {
@ -595,7 +599,7 @@ class FilePage extends React.PureComponent {
} }
if (isClaimBlackListed) { if (isClaimBlackListed) {
innerContent = ( return (
<View style={filePageStyle.pageContainer}> <View style={filePageStyle.pageContainer}>
<View style={filePageStyle.dmcaContainer}> <View style={filePageStyle.dmcaContainer}>
<Text style={filePageStyle.dmcaText}> <Text style={filePageStyle.dmcaText}>
@ -607,358 +611,362 @@ class FilePage extends React.PureComponent {
<UriBar value={uri} navigation={navigation} /> <UriBar value={uri} navigation={navigation} />
</View> </View>
); );
} else { }
let tags = [];
if (claim && claim.value && claim.value.tags) {
tags = claim.value.tags;
}
const completed = fileInfo && fileInfo.completed; let tags = [];
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id); if (claim && claim.value && claim.value.tags) {
const description = metadata.description ? metadata.description : null; tags = claim.value.tags;
const mediaType = Lbry.getMediaType(contentType); }
const isPlayable = mediaType === 'video' || mediaType === 'audio';
const { height, signing_channel: signingChannel, value } = claim;
const channelName = signingChannel && signingChannel.name;
const showActions =
fileInfo &&
fileInfo.download_path &&
!this.state.fullscreenMode &&
!this.state.showImageViewer &&
!this.state.showWebView;
const showFileActions =
fileInfo &&
fileInfo.download_path &&
(completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes));
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const canSendTip = this.state.tipAmount > 0;
const fullChannelUri =
channelClaimId && channelClaimId.trim().length > 0 ? `${channelName}#${channelClaimId}` : channelName;
const playerStyle = [ const completed = fileInfo && fileInfo.completed;
filePageStyle.player, const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
this.state.isLandscape const description = metadata.description ? metadata.description : null;
? filePageStyle.containedPlayerLandscape const mediaType = Lbry.getMediaType(contentType);
: this.state.fullscreenMode const isPlayable = mediaType === 'video' || mediaType === 'audio';
? filePageStyle.fullscreenPlayer const { height, signing_channel: signingChannel, value } = claim;
: filePageStyle.containedPlayer, const channelName = signingChannel && signingChannel.name;
]; const showActions =
const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground]; fileInfo &&
const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground]; fileInfo.download_path &&
// at least 2MB (or the full download) before media can be loaded !this.state.fullscreenMode &&
const canLoadMedia = !this.state.showImageViewer &&
this.state.streamingMode || !this.state.showWebView;
(fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes === fileInfo.total_bytes)); // 2MB = 1024*1024*2 const showFileActions =
const isViewable = mediaType === 'image' || mediaType === 'text'; fileInfo &&
const isWebViewable = mediaType === 'text'; fileInfo.download_path &&
const canOpen = isViewable && completed; (completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes));
const localFileUri = this.localUriForFileInfo(fileInfo); const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const canSendTip = this.state.tipAmount > 0;
const fullChannelUri =
channelClaimId && channelClaimId.trim().length > 0 ? `${channelName}#${channelClaimId}` : channelName;
const openFile = () => { const playerStyle = [
if (mediaType === 'image') { filePageStyle.player,
// use image viewer this.state.isLandscape
if (!this.state.showImageViewer) { ? filePageStyle.containedPlayerLandscape
this.setState({ : this.state.fullscreenMode
imageUrls: [ ? filePageStyle.fullscreenPlayer
{ : filePageStyle.containedPlayer,
url: localFileUri, ];
}, const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground];
], const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground];
showImageViewer: true, // 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
const isViewable = mediaType === 'image' || mediaType === 'text';
const isWebViewable = mediaType === 'text';
const canOpen = isViewable && completed;
const localFileUri = this.localUriForFileInfo(fileInfo);
const openFile = () => {
if (mediaType === 'image') {
// use image viewer
if (!this.state.showImageViewer) {
this.setState({
imageUrls: [
{
url: localFileUri,
},
],
showImageViewer: true,
});
} }
if (isWebViewable) { }
// show webview if (isWebViewable) {
if (!this.state.showWebView) { // show webview
this.setState({ if (!this.state.showWebView) {
showWebView: true, this.setState({
}); showWebView: true,
} });
} }
};
if (
fileInfo &&
!this.state.autoDownloadStarted &&
this.state.uriVars &&
this.state.uriVars.download === 'true'
) {
this.setState({ autoDownloadStarted: true }, () => {
purchaseUri(uri, costInfo, !isPlayable);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.checkDownloads();
}
});
} }
};
if (this.state.downloadPressed && canOpen) { if (fileInfo && !this.state.autoDownloadStarted && this.state.uriVars && this.state.uriVars.download === 'true') {
// automatically open a web viewable or image file after the download button is pressed this.setState({ autoDownloadStarted: true }, () => {
openFile(); purchaseUri(uri, costInfo, !isPlayable);
} if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.checkDownloads();
}
});
}
innerContent = ( if (this.state.downloadPressed && canOpen) {
<View style={filePageStyle.pageContainer}> // automatically open a web viewable or image file after the download button is pressed
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />} openFile();
{this.state.showWebView && isWebViewable && ( }
<WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
)}
{this.state.showImageViewer && ( return (
<ImageViewer <View style={filePageStyle.pageContainer}>
style={StyleSheet.flatten(filePageStyle.viewer)} {!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
imageUrls={this.state.imageUrls} {this.state.showWebView && isWebViewable && (
renderIndicator={() => null} <WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
/> )}
)}
{!this.state.showWebView && ( {this.state.showImageViewer && (
<View <ImageViewer
style={ style={StyleSheet.flatten(filePageStyle.viewer)}
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer imageUrls={this.state.imageUrls}
} renderIndicator={() => null}
onLayout={this.checkOrientation} />
> )}
<View style={filePageStyle.mediaContainer}>
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />
)}
{(!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded && (
<ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />
)}
{((isPlayable && !completed && !canLoadMedia) ||
canOpen ||
(!completed && !this.state.streamingMode)) &&
!this.state.downloadPressed && (
<FileDownloadButton
uri={uri}
style={filePageStyle.downloadButton}
openFile={openFile}
isPlayable={isPlayable}
isViewable={isViewable}
onPlay={this.onFileDownloadButtonPlayed}
onView={() => this.setState({ downloadPressed: true })}
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
/>
)}
{!fileInfo && (
<FilePrice
uri={uri}
style={filePageStyle.filePriceContainer}
textStyle={filePageStyle.filePriceText}
/>
)}
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}> {!this.state.showWebView && (
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} /> <View
</TouchableOpacity> style={
</View> this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && ( }
<View onLayout={this.checkOrientation}
style={playerBgStyle} >
ref={ref => { <View style={filePageStyle.mediaContainer}>
this.playerBackground = ref; {(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
}} <FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />
onLayout={evt => {
if (!this.state.playerBgHeight) {
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
}
}}
/>
)} )}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && {(!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded && (
this.state.fullscreenMode && <View style={fsPlayerBgStyle} />} <ActivityIndicator size="large" color={Colors.NextLbryGreen} style={filePageStyle.loading} />
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && ( )}
<MediaPlayer {((isPlayable && !completed && !canLoadMedia) ||
claim={claim} canOpen ||
assignPlayer={ref => { (!completed && !this.state.streamingMode)) &&
this.player = ref; !this.state.downloadPressed && (
}} <FileDownloadButton
uri={uri} uri={uri}
source={this.playerUriForFileInfo(fileInfo)} style={filePageStyle.downloadButton}
style={playerStyle} openFile={openFile}
autoPlay={autoplay || this.state.autoPlayMedia} isPlayable={isPlayable}
onFullscreenToggled={this.handleFullscreenToggle} isViewable={isViewable}
onLayout={evt => { onPlay={this.onFileDownloadButtonPlayed}
if (!this.state.playerHeight) { onView={() => this.setState({ downloadPressed: true })}
this.setState({ playerHeight: evt.nativeEvent.layout.height }); onButtonLayout={() => this.setState({ downloadButtonShown: true })}
} />
}} )}
onMediaLoaded={() => this.onMediaLoaded(channelName, title, uri)} {!fileInfo && (
onBackButtonPressed={this.onBackButtonPressed} <FilePrice
onPlaybackStarted={this.onPlaybackStarted} uri={uri}
onPlaybackFinished={this.onPlaybackFinished} style={filePageStyle.filePriceContainer}
thumbnail={thumbnail} textStyle={filePageStyle.filePriceText}
position={position}
/> />
)} )}
{showActions && showFileActions && ( <TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
<View style={filePageStyle.actions}> <Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
{showFileActions && ( </TouchableOpacity>
<View style={filePageStyle.fileActions}> </View>
{completed && ( {(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<Button <View
style={filePageStyle.actionButton} style={playerBgStyle}
theme={'light'} ref={ref => {
icon={'trash'} this.playerBackground = ref;
text={'Delete'} }}
onPress={this.onDeletePressed} onLayout={evt => {
/> if (!this.state.playerBgHeight) {
)} this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
{!completed && }
fileInfo && }}
!fileInfo.stopped && />
fileInfo.written_bytes < fileInfo.total_bytes && )}
!this.state.stopDownloadConfirmed && ( {(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && this.state.fullscreenMode && (
<Button <View style={fsPlayerBgStyle} />
style={filePageStyle.actionButton} )}
icon={'stop'} {(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
theme={'light'} <MediaPlayer
text={'Stop Download'} claim={claim}
onPress={this.onStopDownloadPressed} assignPlayer={ref => {
/> this.player = ref;
)} }}
uri={uri}
source={this.playerUriForFileInfo(fileInfo)}
style={playerStyle}
autoPlay={autoplay || this.state.autoPlayMedia}
onFullscreenToggled={this.handleFullscreenToggle}
onLayout={evt => {
if (!this.state.playerHeight) {
this.setState({ playerHeight: evt.nativeEvent.layout.height });
}
}}
onMediaLoaded={() => this.onMediaLoaded(channelName, title, uri)}
onBackButtonPressed={this.onBackButtonPressed}
onPlaybackStarted={this.onPlaybackStarted}
onPlaybackFinished={this.onPlaybackFinished}
thumbnail={thumbnail}
position={position}
/>
)}
{showActions && showFileActions && (
<View style={filePageStyle.actions}>
{showFileActions && (
<View style={filePageStyle.fileActions}>
{completed && (
<Button
style={filePageStyle.actionButton}
theme={'light'}
icon={'trash'}
text={'Delete'}
onPress={this.onDeletePressed}
/>
)}
{!completed &&
fileInfo &&
!fileInfo.stopped &&
fileInfo.written_bytes < fileInfo.total_bytes &&
!this.state.stopDownloadConfirmed && (
<Button
style={filePageStyle.actionButton}
icon={'stop'}
theme={'light'}
text={'Stop Download'}
onPress={this.onStopDownloadPressed}
/>
)}
</View>
)}
</View>
)}
<ScrollView
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}
keyboardShouldPersistTaps={'handled'}
ref={ref => {
this.scrollView = ref;
}}
>
<TouchableWithoutFeedback
style={filePageStyle.titleTouch}
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
>
<View style={filePageStyle.titleRow}>
<Text style={filePageStyle.title} selectable>
{title}
</Text>
<View style={filePageStyle.descriptionToggle}>
<Icon name={this.state.showDescription ? 'caret-up' : 'caret-down'} size={24} />
</View>
</View>
</TouchableWithoutFeedback>
<View style={filePageStyle.channelRow}>
<View style={filePageStyle.publishInfo}>
{channelName && (
<Link
style={filePageStyle.channelName}
selectable
text={channelName}
numberOfLines={1}
ellipsizeMode={'tail'}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}}
/>
)}
{!channelName && (
<Text style={filePageStyle.anonChannelName} selectable ellipsizeMode={'tail'}>
Anonymous
</Text>
)}
<DateTime
style={filePageStyle.publishDate}
textStyle={filePageStyle.publishDateText}
uri={uri}
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
show={DateTime.SHOW_DATE}
/>
</View>
<View style={filePageStyle.subscriptionRow}>
{false && ((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
<Button
style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
theme={'light'}
icon={'download'}
onPress={this.onSaveFilePressed}
/>
)}
<Button
style={[filePageStyle.actionButton, filePageStyle.tipButton]}
theme={'light'}
icon={'gift'}
onPress={() => this.setState({ showTipView: true })}
/>
{channelName && (
<SubscribeButton
style={filePageStyle.actionButton}
uri={fullChannelUri}
name={channelName}
hideText={false}
/>
)}
{channelName && (
<SubscribeNotificationButton
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
uri={fullChannelUri}
name={channelName}
/>
)}
</View>
</View>
{this.state.showTipView && <View style={filePageStyle.divider} />}
{this.state.showTipView && (
<View style={filePageStyle.tipCard}>
<View style={filePageStyle.row}>
<View style={filePageStyle.amountRow}>
<TextInput
ref={ref => (this.tipAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.tipAmount}
style={[filePageStyle.input, filePageStyle.tipAmountInput]}
/>
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
</View>
<Link
style={[filePageStyle.link, filePageStyle.cancelTipLink]}
text={'Cancel'}
onPress={() => this.setState({ showTipView: false })}
/>
<Button
text={'Send a tip'}
style={[filePageStyle.button, filePageStyle.sendButton]}
disabled={!canSendTip}
onPress={this.handleSendTip}
/>
</View>
</View>
)}
{this.state.showDescription && description && description.length > 0 && (
<View style={filePageStyle.divider} />
)}
{this.state.showDescription && description && (
<View>
<Text style={filePageStyle.description} selectable>
{this.linkify(description)}
</Text>
{tags && tags.length > 0 && (
<View style={filePageStyle.tagContainer}>
<Text style={filePageStyle.tagTitle}>Tags</Text>
<View style={filePageStyle.tagList}>{this.renderTags(tags)}</View>
</View> </View>
)} )}
</View> </View>
)} )}
<ScrollView
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}
keyboardShouldPersistTaps={'handled'}
ref={ref => {
this.scrollView = ref;
}}
>
<TouchableWithoutFeedback
style={filePageStyle.titleTouch}
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
>
<View style={filePageStyle.titleRow}>
<Text style={filePageStyle.title} selectable>
{title}
</Text>
<View style={filePageStyle.descriptionToggle}>
<Icon name={this.state.showDescription ? 'caret-up' : 'caret-down'} size={24} />
</View>
</View>
</TouchableWithoutFeedback>
{channelName && (
<View style={filePageStyle.channelRow}>
<View style={filePageStyle.publishInfo}>
<Link
style={filePageStyle.channelName}
selectable
text={channelName}
numberOfLines={1}
ellipsizeMode={'tail'}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}}
/>
<DateTime
style={filePageStyle.publishDate}
textStyle={filePageStyle.publishDateText}
uri={uri}
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
show={DateTime.SHOW_DATE}
/>
</View>
<View style={filePageStyle.subscriptionRow}>
{false &&
((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
<Button
style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
theme={'light'}
icon={'download'}
onPress={this.onSaveFilePressed}
/>
)}
<Button
style={[filePageStyle.actionButton, filePageStyle.tipButton]}
theme={'light'}
icon={'gift'}
onPress={() => this.setState({ showTipView: true })}
/>
<SubscribeButton
style={filePageStyle.actionButton}
uri={fullChannelUri}
name={channelName}
hideText={false}
/>
<SubscribeNotificationButton
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
uri={fullChannelUri}
name={channelName}
/>
</View>
</View>
)}
{this.state.showTipView && <View style={filePageStyle.divider} />} {costInfo && parseFloat(costInfo.cost) > balance && <FileRewardsDriver navigation={navigation} />}
{this.state.showTipView && (
<View style={filePageStyle.tipCard}>
<View style={filePageStyle.row}>
<View style={filePageStyle.amountRow}>
<TextInput
ref={ref => (this.tipAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.tipAmount}
style={[filePageStyle.input, filePageStyle.tipAmountInput]}
/>
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
</View>
<Link
style={[filePageStyle.link, filePageStyle.cancelTipLink]}
text={'Cancel'}
onPress={() => this.setState({ showTipView: false })}
/>
<Button
text={'Send a tip'}
style={[filePageStyle.button, filePageStyle.sendButton]}
disabled={!canSendTip}
onPress={this.handleSendTip}
/>
</View>
</View>
)}
{this.state.showDescription && description && description.length > 0 && ( <View onLayout={this.setRelatedContentPosition} />
<View style={filePageStyle.divider} /> <RelatedContent navigation={navigation} uri={uri} />
)} </ScrollView>
{this.state.showDescription && description && ( </View>
<View> )}
<Text style={filePageStyle.description} selectable> {!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView && (
{this.linkify(description)} <FloatingWalletBalance navigation={navigation} />
</Text> )}
{tags && tags.length > 0 && ( </View>
<View style={filePageStyle.tagContainer}> );
<Text style={filePageStyle.tagTitle}>Tags</Text>
<View style={filePageStyle.tagList}>{this.renderTags(tags)}</View>
</View>
)}
</View>
)}
{costInfo && parseFloat(costInfo.cost) > balance && <FileRewardsDriver navigation={navigation} />}
<View onLayout={this.setRelatedContentPosition} />
<RelatedContent navigation={navigation} uri={uri} />
</ScrollView>
</View>
)}
{!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView && (
<FloatingWalletBalance navigation={navigation} />
)}
</View>
);
}
} }
return innerContent; return null;
} }
} }

View file

@ -550,7 +550,7 @@ class PublishPage extends React.PureComponent {
</View> </View>
{(!videos || !thumbnailPath || !galleryThumbnailsChecked) && ( {(!videos || !thumbnailPath || !galleryThumbnailsChecked) && (
<View style={publishStyle.loadingView}> <View style={publishStyle.loadingView}>
<ActivityIndicator size="large" color={Colors.LbryGreen} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} />
</View> </View>
)} )}
{thumbnailPath && (!videos || videos.length === 0) && ( {thumbnailPath && (!videos || videos.length === 0) && (

View file

@ -2,7 +2,7 @@ import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native'; import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Link from 'component/link'; import Link from 'component/link';
import CustomRewardCard from 'component/customRewardCard'; import CustomRewardCard from 'component/customRewardCard';
import PageHeader from 'component/pageHeader'; import PageHeader from 'component/pageHeader';
@ -87,7 +87,7 @@ class RewardsPage extends React.PureComponent {
// claim new_user and new_mobile rewards // claim new_user and new_mobile rewards
for (let i = 0; i < rewards.length; i++) { for (let i = 0; i < rewards.length; i++) {
const { reward_type: type } = rewards[i]; const { reward_type: type } = rewards[i];
if ('new_user' === type || 'new_mobile' === type) { if (type === 'new_user' || type === 'new_mobile') {
claimReward(rewards[i]); claimReward(rewards[i]);
} }
} }
@ -127,7 +127,7 @@ class RewardsPage extends React.PureComponent {
if (fetching) { if (fetching) {
return ( return (
<View style={rewardStyle.busyContainer}> <View style={rewardStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={rewardStyle.infoText}>Fetching rewards...</Text> <Text style={rewardStyle.infoText}>Fetching rewards...</Text>
</View> </View>
); );

View file

@ -3,7 +3,7 @@ import { Lbry, parseURI, normalizeURI, isURIValid } from 'lbry-redux';
import { ActivityIndicator, Button, Text, TextInput, View, ScrollView } from 'react-native'; import { ActivityIndicator, Button, Text, TextInput, View, ScrollView } from 'react-native';
import { navigateToUri } from 'utils/helper'; import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import PageHeader from 'component/pageHeader'; import PageHeader from 'component/pageHeader';
import FileListItem from 'component/fileListItem'; import FileListItem from 'component/fileListItem';
import FloatingWalletBalance from 'component/floatingWalletBalance'; import FloatingWalletBalance from 'component/floatingWalletBalance';
@ -91,7 +91,7 @@ class SearchPage extends React.PureComponent {
<UriBar value={query} navigation={navigation} onSearchSubmitted={this.handleSearchSubmitted} /> <UriBar value={query} navigation={navigation} onSearchSubmitted={this.handleSearchSubmitted} />
{isSearching && ( {isSearching && (
<View style={searchStyle.busyContainer}> <View style={searchStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} style={searchStyle.loading} />
</View> </View>
)} )}
@ -105,7 +105,7 @@ class SearchPage extends React.PureComponent {
<FileListItem <FileListItem
key={this.state.currentUri} key={this.state.currentUri}
uri={this.state.currentUri} uri={this.state.currentUri}
featuredResult={true} featuredResult
style={searchStyle.featuredResultItem} style={searchStyle.featuredResultItem}
navigation={navigation} navigation={navigation}
onPress={() => navigateToUri(navigation, this.state.currentUri)} onPress={() => navigateToUri(navigation, this.state.currentUri)}
@ -113,14 +113,14 @@ class SearchPage extends React.PureComponent {
)} )}
{uris && uris.length {uris && uris.length
? uris.map(uri => ( ? uris.map(uri => (
<FileListItem <FileListItem
key={uri} key={uri}
uri={uri} uri={uri}
style={searchStyle.resultItem} style={searchStyle.resultItem}
navigation={navigation} navigation={navigation}
onPress={() => navigateToUri(navigation, uri)} onPress={() => navigateToUri(navigation, uri)}
/> />
)) ))
: null} : null}
{(!uris || uris.length === 0) && ( {(!uris || uris.length === 0) && (
<View style={searchStyle.noResults}> <View style={searchStyle.noResults}>

View file

@ -3,6 +3,7 @@ import { doBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
import { import {
doAuthenticate, doAuthenticate,
doBlackListedOutpointsSubscribe, doBlackListedOutpointsSubscribe,
doFilteredOutpointsSubscribe,
doCheckSubscriptionsInit, doCheckSubscriptionsInit,
doFetchMySubscriptions, doFetchMySubscriptions,
doFetchRewardedContent, doFetchRewardedContent,
@ -25,6 +26,7 @@ const perform = dispatch => ({
authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)), authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)),
balanceSubscribe: () => dispatch(doBalanceSubscribe()), balanceSubscribe: () => dispatch(doBalanceSubscribe()),
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()), blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
filteredOutpointsSubscribe: () => dispatch(doFilteredOutpointsSubscribe()),
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()), checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchSubscriptions: callback => dispatch(doFetchMySubscriptions(callback)), fetchSubscriptions: callback => dispatch(doFetchMySubscriptions(callback)),

View file

@ -10,7 +10,7 @@ import Button from 'component/button';
import ProgressBar from 'component/progressBar'; import ProgressBar from 'component/progressBar';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import splashStyle from 'styles/splash'; import splashStyle from 'styles/splash';
const BLOCK_HEIGHT_INTERVAL = 1000 * 60 * 2.5; // every 2.5 minutes const BLOCK_HEIGHT_INTERVAL = 1000 * 60 * 2.5; // every 2.5 minutes
@ -50,7 +50,7 @@ class SplashScreen extends React.PureComponent {
} }
navigateToMain = () => { navigateToMain = () => {
const { navigation } = this.props; const { navigation, notify, verifyUserEmail, verifyUserEmailFailure } = this.props;
const resetAction = StackActions.reset({ const resetAction = StackActions.reset({
index: 0, index: 0,
actions: [NavigationActions.navigate({ routeName: 'Main' })], actions: [NavigationActions.navigate({ routeName: 'Main' })],
@ -114,9 +114,8 @@ class SplashScreen extends React.PureComponent {
balanceSubscribe, balanceSubscribe,
blacklistedOutpointsSubscribe, blacklistedOutpointsSubscribe,
checkSubscriptionsInit, checkSubscriptionsInit,
filteredOutpointsSubscribe,
getSync, getSync,
navigation,
notify,
updateBlockHeight, updateBlockHeight,
user, user,
} = this.props; } = this.props;
@ -125,11 +124,8 @@ class SplashScreen extends React.PureComponent {
// Leave the splash screen // Leave the splash screen
balanceSubscribe(); balanceSubscribe();
blacklistedOutpointsSubscribe(); blacklistedOutpointsSubscribe();
filteredOutpointsSubscribe();
checkSubscriptionsInit(); checkSubscriptionsInit();
updateBlockHeight();
setInterval(() => {
updateBlockHeight();
}, BLOCK_HEIGHT_INTERVAL);
if (user && user.id && user.has_verified_email) { if (user && user.id && user.has_verified_email) {
// user already authenticated // user already authenticated
@ -186,7 +182,6 @@ class SplashScreen extends React.PureComponent {
this.finishSplashScreen(); this.finishSplashScreen();
}) })
.catch(() => this.handleAccountUnlockFailed()); .catch(() => this.handleAccountUnlockFailed());
return;
} else { } else {
this.setState({ this.setState({
message: testingNetwork, message: testingNetwork,
@ -222,7 +217,7 @@ class SplashScreen extends React.PureComponent {
}); });
} else if (walletStatus && walletStatus.blocks_behind > 0) { } else if (walletStatus && walletStatus.blocks_behind > 0) {
const behind = walletStatus.blocks_behind; const behind = walletStatus.blocks_behind;
const behindText = behind + ' block' + (behind == 1 ? '' : 's') + ' behind'; const behindText = behind + ' block' + (behind === 1 ? '' : 's') + ' behind';
this.setState({ this.setState({
message: 'Blockchain Sync', message: 'Blockchain Sync',
details: behindText, details: behindText,
@ -254,7 +249,7 @@ class SplashScreen extends React.PureComponent {
// Start measuring the first launch time from the splash screen // Start measuring the first launch time from the splash screen
// (time to first user interaction - after first run completed) // (time to first user interaction - after first run completed)
AsyncStorage.getItem('hasLaunched').then(value => { AsyncStorage.getItem('hasLaunched').then(value => {
if ('true' !== value) { if (value !== 'true') {
AsyncStorage.setItem('hasLaunched', 'true'); AsyncStorage.setItem('hasLaunched', 'true');
// only set firstLaunchTime since we've determined that this is the first app launch ever // only set firstLaunchTime since we've determined that this is the first app launch ever
AsyncStorage.setItem('firstLaunchTime', String(moment().unix())); AsyncStorage.setItem('firstLaunchTime', String(moment().unix()));

View file

@ -5,8 +5,9 @@ import {
doFetchRecommendedSubscriptions, doFetchRecommendedSubscriptions,
selectSubscriptionClaims, selectSubscriptionClaims,
selectSubscriptions, selectSubscriptions,
selectSubscriptionsBeingFetched,
selectIsFetchingSubscriptions, selectIsFetchingSubscriptions,
selectIsFetchingSuggested,
selectSuggestedChannels,
selectUnreadSubscriptions, selectUnreadSubscriptions,
selectViewMode, selectViewMode,
selectFirstRunCompleted, selectFirstRunCompleted,
@ -16,13 +17,15 @@ import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectCurrentRoute } from 'redux/selectors/drawer'; import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import SubscriptionsPage from './view'; import SubscriptionsPage from './view';
const select = state => ({ const select = state => ({
currentRoute: selectCurrentRoute(state), currentRoute: selectCurrentRoute(state),
loading: selectIsFetchingSubscriptions(state) || Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length), loading: selectIsFetchingSubscriptions(state),
loadingSuggested: selectIsFetchingSuggested(state),
subscribedChannels: selectSubscriptions(state), subscribedChannels: selectSubscriptions(state),
suggestedChannels: selectSuggestedChannels(state),
subscriptionsViewMode: makeSelectClientSetting(Constants.SETTING_SUBSCRIPTIONS_VIEW_MODE)(state), subscriptionsViewMode: makeSelectClientSetting(Constants.SETTING_SUBSCRIPTIONS_VIEW_MODE)(state),
allSubscriptions: selectSubscriptionClaims(state), allSubscriptions: selectSubscriptionClaims(state),
unreadSubscriptions: selectUnreadSubscriptions(state), unreadSubscriptions: selectUnreadSubscriptions(state),

View file

@ -115,11 +115,12 @@ class SubscriptionsPage extends React.PureComponent {
render() { render() {
const { const {
suggestedChannels,
subscribedChannels, subscribedChannels,
allSubscriptions, allSubscriptions,
loading,
viewMode, viewMode,
doSetViewMode, doSetViewMode,
loading,
loadingSuggested, loadingSuggested,
firstRunCompleted, firstRunCompleted,
doCompleteFirstRun, doCompleteFirstRun,
@ -182,7 +183,7 @@ class SubscriptionsPage extends React.PureComponent {
{hasSubscriptions && loading && ( {hasSubscriptions && loading && (
<View style={subscriptionsStyle.busyContainer}> <View style={subscriptionsStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={subscriptionsStyle.loading} /> <ActivityIndicator size="large" color={Colors.NextLbryGreen} style={subscriptionsStyle.loading} />
</View> </View>
)} )}
@ -209,8 +210,7 @@ class SubscriptionsPage extends React.PureComponent {
{loadingSuggested && ( {loadingSuggested && (
<View style={subscriptionsStyle.centered}> <View style={subscriptionsStyle.centered}>
<ActivityIndicator size="large" colors={Colors.LbryGreen} style={subscriptionsStyle.loading} /> <ActivityIndicator size="large" colors={Colors.NextLbryGreen} style={subscriptionsStyle.loading} />
\\
</View> </View>
)} )}
{!loadingSuggested && <SuggestedSubscriptions navigation={navigation} />} {!loadingSuggested && <SuggestedSubscriptions navigation={navigation} />}

View file

@ -78,7 +78,7 @@ class TagPage extends React.PureComponent {
}; };
handleTimeItemSelected = item => { handleTimeItemSelected = item => {
this.setState({ time: item.name }); this.setState({ currentTimeItem: item, time: item.name, showTimePicker: false });
}; };
render() { render() {

View file

@ -95,6 +95,7 @@ class TrendingPage extends React.PureComponent {
orderBy={Constants.DEFAULT_ORDER_BY} orderBy={Constants.DEFAULT_ORDER_BY}
trendingForAll={TRENDING_FOR_ITEMS[0].name === currentTrendingForItem.name} trendingForAll={TRENDING_FOR_ITEMS[0].name === currentTrendingForItem.name}
tags={followedTags.map(tag => tag.name)} tags={followedTags.map(tag => tag.name)}
time={null}
navigation={navigation} navigation={navigation}
orientation={Constants.ORIENTATION_VERTICAL} orientation={Constants.ORIENTATION_VERTICAL}
/> />

View file

@ -0,0 +1,96 @@
// @flow
import { NativeModules } from 'react-native';
import {
ACTIONS,
batchActions,
buildURI,
doResolveUri,
doUpdateSearchQuery,
makeSelectSearchUris,
selectSuggestions,
makeSelectQueryWithOptions,
selectSearchValue,
} from 'lbry-redux';
let CONNECTION_STRING = 'https://lighthouse.lbry.com/';
const handleNativeFetchResponse = str => {
const json = JSON.parse(str);
if (json.error) {
return Promise.reject(new Error(json.error));
}
return Promise.resolve(json);
};
// Use a native asyncTask to call the lighthouse api
export const doNativeSearch = (
rawQuery: string, // pass in a query if you don't want to search for what's in the search bar
size: ?number, // only pass in if you don't want to use the users setting (ex: related content)
from: ?number,
isBackgroundSearch: boolean = false
) => (dispatch: Dispatch, getState: GetState) => {
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
if (!query) {
dispatch({
type: ACTIONS.SEARCH_FAIL,
});
return;
}
const state = getState();
const queryWithOptions = makeSelectQueryWithOptions(query, size, from, isBackgroundSearch)(state);
// If we have already searched for something, we don't need to do anything
const urisForQuery = makeSelectSearchUris(queryWithOptions)(state);
if (urisForQuery && !!urisForQuery.length) {
return;
}
dispatch({
type: ACTIONS.SEARCH_START,
});
// If the user is on the file page with a pre-populated uri and they select
// the search option without typing anything, searchQuery will be empty
// We need to populate it so the input is filled on the search page
// isBackgroundSearch means the search is happening in the background, don't update the search query
if (!state.search.searchQuery && !isBackgroundSearch) {
dispatch(doUpdateSearchQuery(query));
}
const url = `${CONNECTION_STRING}search?${queryWithOptions}`;
NativeModules.Requests.get(url)
.then(handleNativeFetchResponse)
.then((data: Array<{ name: String, claimId: string }>) => {
const uris = [];
const actions = [];
data.forEach(result => {
if (result.name) {
const uri = buildURI({
claimName: result.name,
claimId: result.claimId,
});
actions.push(doResolveUri(uri));
uris.push(uri);
}
});
actions.push({
type: ACTIONS.SEARCH_SUCCESS,
data: {
query: queryWithOptions,
uris,
},
});
dispatch(batchActions(...actions));
})
.catch(e => {
console.log(e);
dispatch({
type: ACTIONS.SEARCH_FAIL,
});
});
};

View file

@ -1,11 +1,17 @@
import { ACTIONS } from 'lbry-redux'; import { ACTIONS } from 'lbry-redux';
export function doSetClientSetting(key, value) { export function doSetClientSetting(key, value) {
return { return dispatch => {
type: ACTIONS.CLIENT_SETTING_CHANGED, dispatch({
data: { type: ACTIONS.CLIENT_SETTING_CHANGED,
key, data: {
value, key,
}, value,
},
});
if (window.persistor) {
window.persistor.flush();
}
}; };
} }

View file

@ -93,6 +93,11 @@ const filePageStyle = StyleSheet.create({
fontSize: 14, fontSize: 14,
color: Colors.LbryGreen, color: Colors.LbryGreen,
}, },
anonChannelName: {
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
color: Colors.DescriptionGrey,
},
publishDateText: { publishDateText: {
fontFamily: 'Inter-UI-SemiBold', fontFamily: 'Inter-UI-SemiBold',
fontSize: 12, fontSize: 12,

View file

@ -197,7 +197,8 @@ const subscriptionsStyle = StyleSheet.create({
}, },
suggestedItemDetails: { suggestedItemDetails: {
marginLeft: 16, marginLeft: 16,
flexDirection: 'row', marginRight: 16,
flex: 0.8,
}, },
suggestedItemSubscribe: { suggestedItemSubscribe: {
backgroundColor: Colors.White, backgroundColor: Colors.White,
@ -209,12 +210,14 @@ const subscriptionsStyle = StyleSheet.create({
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
fontSize: 16, fontSize: 16,
marginBottom: 4, marginBottom: 4,
width: '85%',
}, },
suggestedItemName: { suggestedItemName: {
fontFamily: 'Inter-UI-SemiBold', fontFamily: 'Inter-UI-SemiBold',
fontSize: 14, fontSize: 14,
marginBottom: 4, marginBottom: 4,
color: Colors.LbryGreen, color: Colors.LbryGreen,
width: '95%',
}, },
suggestedItemTagList: { suggestedItemTagList: {
flexDirection: 'row', flexDirection: 'row',

View file

@ -3,6 +3,8 @@ import { buildURI, isURIValid } from 'lbry-redux';
import { doPopDrawerStack, doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; import { doPopDrawerStack, doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import Constants, { DrawerRoutes } from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants, { DrawerRoutes } from 'constants'; // eslint-disable-line node/no-deprecated-api
const tagNameLength = 10;
function getRouteForSpecialUri(uri) { function getRouteForSpecialUri(uri) {
let targetRoute; let targetRoute;
const page = uri.substring(8).trim(); // 'lbry://?'.length == 8 const page = uri.substring(8).trim(); // 'lbry://?'.length == 8
@ -191,6 +193,17 @@ export function formatTagTitle(title) {
return title.charAt(0).toUpperCase() + title.substring(1); return title.charAt(0).toUpperCase() + title.substring(1);
} }
export function formatTagName(name) {
if (!name) {
return null;
}
if (name.length <= tagNameLength) {
return name;
}
return name.substring(0, 7) + '...';
}
// i18n placeholder until we find a good react-native i18n module // i18n placeholder until we find a good react-native i18n module
export function __(str) { export function __(str) {
return str; return str;