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",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"devtools": "react-devtools",
"format": "prettier 'src/**/*.{js,json}' --write",
"precommit": "lint-staged"
},
@ -11,7 +12,7 @@
"base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3",
"lbry-redux": "lbryio/lbry-redux#multi-claim-search",
"lbry-redux": "lbryio/lbry-redux#67a654f60630710cae72419448a73a18d076d18a",
"lbryinc": "lbryio/lbryinc",
"lodash": ">=4.17.11",
"merge": ">=1.2.1",
@ -63,6 +64,7 @@
"husky": "^0.14.3",
"lint-staged": "^7.0.4",
"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 moment from 'moment';
const horizontalLimit = 10;
const horizontalLimit = 7;
const softLimit = 500;
class ClaimList extends React.PureComponent {
@ -199,8 +199,8 @@ class ClaimList extends React.PureComponent {
ListHeaderComponent={ListHeaderComponent}
style={claimListStyle.verticalScrollContainer}
contentContainerStyle={claimListStyle.verticalScrollPadding}
initialNumToRender={8}
maxToRenderPerBatch={24}
initialNumToRender={10}
maxToRenderPerBatch={20}
removeClippedSubviews
renderItem={({ item }) => (
<FileListItem key={item} uri={item} style={claimListStyle.verticalListItem} navigation={navigation} />
@ -208,7 +208,7 @@ class ClaimList extends React.PureComponent {
data={data}
keyExtractor={(item, index) => item}
onEndReached={this.handleVerticalEndReached}
onEndReachedThreshold={0.9}
onEndReachedThreshold={0.2}
/>
{(((subscriptionsView || trendingForAllView) && claimSearchLoading) || loading) && (
<View style={claimListStyle.verticalLoading}>

View file

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

View file

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

View file

@ -48,8 +48,10 @@ class FileListItem extends React.PureComponent {
render() {
const {
blackListedOutpoints,
claim,
fileInfo,
filteredOutpoints,
metadata,
featuredResult,
isResolvingUri,
@ -66,7 +68,7 @@ class FileListItem extends React.PureComponent {
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isResolving = !fileInfo && isResolvingUri;
let name, channel, height, channelClaimId, fullChannelUri, signingChannel;
let name, channel, height, channelClaimId, fullChannelUri, shouldHide, signingChannel;
if (claim) {
name = claim.name;
signingChannel = claim.signing_channel;
@ -74,9 +76,14 @@ class FileListItem extends React.PureComponent {
height = claim.height;
channelClaimId = signingChannel ? signingChannel.claim_id : null;
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;
}

View file

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

View file

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

View file

@ -6,7 +6,7 @@ import FileListItem from 'component/fileListItem';
import fileListStyle from 'styles/fileList';
import relatedContentStyle from 'styles/relatedContent';
export default class RelatedContent extends React.PureComponent<Props> {
export default class RelatedContent extends React.PureComponent {
constructor() {
super();
@ -17,7 +17,7 @@ export default class RelatedContent extends React.PureComponent<Props> {
this.getRecommendedContent();
}
componentDidUpdate(prevProps: Props) {
componentDidUpdate(prevProps) {
const { claim, uri } = this.props;
if (uri !== prevProps.uri) {
@ -38,7 +38,7 @@ export default class RelatedContent extends React.PureComponent<Props> {
}
}
didSearch: ?boolean;
didSearch;
render() {
const { recommendedContent, isSearching, navigation } = this.props;
@ -60,7 +60,9 @@ export default class RelatedContent extends React.PureComponent<Props> {
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 file

@ -86,7 +86,7 @@ class RewardCard extends React.PureComponent<Props> {
)}
</TouchableOpacity>
)}
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
{isPending && <ActivityIndicator size="small" color={Colors.NextLbryGreen} />}
</View>
<View style={rewardStyle.midCol}>
<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 subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const subscriptionLabel = isSubscribed ? null : __('Subscribe');
const subscriptionLabel = isSubscribed ? null : __('Follow');
const { claimName } = parseURI(uri);
return (
@ -28,7 +28,7 @@ class SubscribeButton extends React.PureComponent {
theme={'light'}
icon={isSubscribed ? 'heart-broken' : 'heart'}
iconColor={iconColor}
solid={isSubscribed ? false : true}
solid={!isSubscribed}
text={hideText ? null : subscriptionLabel}
onPress={() => {
subscriptionHandler({

View file

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

View file

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

View file

@ -1,20 +1,21 @@
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { formatTagName } from 'utils/helper';
import tagStyle from 'styles/tag';
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';
export default class Tag extends React.PureComponent {
onPressDefault = () => {
const { name, navigation, type, onAddPress, onRemovePress } = this.props;
if ('add' === type) {
if (type === 'add') {
if (onAddPress) {
onAddPress(name);
}
return;
}
if ('remove' === type) {
if (type === 'remove') {
if (onRemovePress) {
onRemovePress(name);
}
@ -48,7 +49,7 @@ export default class Tag extends React.PureComponent {
return (
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
<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} />}
</View>
</TouchableOpacity>

View file

@ -103,7 +103,7 @@ const Constants = {
TIME_ALL,
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: 'chart-line', name: SORT_BY_TOP, label: 'Top content' },
],

View file

@ -18,6 +18,7 @@ import {
authReducer,
blacklistReducer,
costInfoReducer,
filteredReducer,
homepageReducer,
rewardsReducer,
subscriptionsReducer,
@ -32,7 +33,6 @@ import AppWithNavigationState, {
} from 'component/AppNavigator';
import { REHYDRATE, PURGE, persistCombineReducers, persistStore } from 'redux-persist';
import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4';
import AsyncStorage from '@react-native-community/async-storage';
import FilesystemStorage from 'redux-persist-filesystem-storage';
import createCompressor from 'redux-persist-transform-compress';
import createFilter from 'redux-persist-transform-filter';
@ -43,7 +43,6 @@ import thunk from 'redux-thunk';
const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) {
console.log(error);
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
}
};
@ -80,7 +79,7 @@ function enableBatching(reducer) {
const compressor = createCompressor();
const authFilter = createFilter('auth', ['authToken']);
const contentFilter = createFilter('content', ['positions']);
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
const saveClaimsFilter = createFilter('claims', ['claimsByUri']);
const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions']);
const settingsFilter = createFilter('settings', ['clientSettings']);
const tagsFilter = createFilter('tags', ['followedTags']);
@ -109,6 +108,7 @@ const reducers = persistCombineReducers(persistOptions, {
drawer: drawerReducer,
file: fileReducer,
fileInfo: fileInfoReducer,
filtered: filteredReducer,
homepage: homepageReducer,
nav: navigatorReducer,
notifications: notificationsReducer,
@ -136,11 +136,29 @@ const store = createStore(
);
window.store = store;
persistStore(store, persistOptions, err => {
const persistor = persistStore(store, persistOptions, err => {
if (err) {
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
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 { navigateBack } from 'utils/helper';
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 Link from 'component/link';
import FileList from 'component/fileList';
@ -53,7 +53,7 @@ class ChannelPage extends React.PureComponent {
if (fetching) {
contentList = (
<View style={channelPageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={channelPageStyle.infoText}>Fetching content...</Text>
</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 (
<View style={channelPageStyle.aboutTab}>
{!website_url && !email && !description && (
{!websiteUrl && !email && !description && (
<View style={channelPageStyle.busyContainer}>
<Text style={channelPageStyle.infoText}>Nothing here yet. Please check back later.</Text>
</View>
)}
{(website_url || email || description) && (
{(websiteUrl || email || description) && (
<ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}>
{website_url && website_url.trim().length > 0 && (
{websiteUrl && websiteUrl.trim().length > 0 && (
<View style={channelPageStyle.aboutItem}>
<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 file

@ -207,7 +207,7 @@ class DiscoverPage extends React.PureComponent {
buildSections = () => {
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],
}));
};
@ -216,7 +216,9 @@ class DiscoverPage extends React.PureComponent {
const tags = followedTags.map(tag => tag.name);
// each of the followed tags
const tagCollection = tags.map(tag => [tag]);
const tagCollection = _.shuffle(tags)
.slice(0, 6)
.map(tag => [tag]);
// everything
tagCollection.unshift(tags);
@ -268,7 +270,8 @@ class DiscoverPage extends React.PureComponent {
renderItem={({ item, index, section }) => (
<ClaimList
key={item.sort().join(',')}
orderBy={item.length > 1 ? Constants.DEFAULT_ORDER_BY : orderBy}
orderBy={orderBy}
time={Constants.TIME_WEEK}
tags={item}
morePlaceholder
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 { navigateToUri, uriFromFileInfo } from 'utils/helper';
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 FileListItem from 'component/fileListItem';
import FloatingWalletBalance from 'component/floatingWalletBalance';
@ -65,7 +65,7 @@ class DownloadsPage extends React.PureComponent {
)}
{fetching && !hasDownloads && (
<View style={downloadsStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={downloadsStyle.loading} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} style={downloadsStyle.loading} />
</View>
)}
{hasDownloads && (

View file

@ -563,11 +563,11 @@ class FilePage extends React.PureComponent {
let innerContent = null;
if ((isResolvingUri && !claim) || !claim) {
innerContent = (
return (
<View style={filePageStyle.container}>
{isResolvingUri && (
<View style={filePageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
</View>
)}
@ -579,9 +579,13 @@ class FilePage extends React.PureComponent {
<UriBar value={uri} navigation={navigation} />
</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;
if (blackListedOutpoints) {
@ -595,7 +599,7 @@ class FilePage extends React.PureComponent {
}
if (isClaimBlackListed) {
innerContent = (
return (
<View style={filePageStyle.pageContainer}>
<View style={filePageStyle.dmcaContainer}>
<Text style={filePageStyle.dmcaText}>
@ -607,358 +611,362 @@ class FilePage extends React.PureComponent {
<UriBar value={uri} navigation={navigation} />
</View>
);
} else {
let tags = [];
if (claim && claim.value && claim.value.tags) {
tags = claim.value.tags;
}
}
const completed = fileInfo && fileInfo.completed;
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
const description = metadata.description ? metadata.description : null;
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;
let tags = [];
if (claim && claim.value && claim.value.tags) {
tags = claim.value.tags;
}
const playerStyle = [
filePageStyle.player,
this.state.isLandscape
? filePageStyle.containedPlayerLandscape
: this.state.fullscreenMode
? 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
const isViewable = mediaType === 'image' || mediaType === 'text';
const isWebViewable = mediaType === 'text';
const canOpen = isViewable && completed;
const localFileUri = this.localUriForFileInfo(fileInfo);
const completed = fileInfo && fileInfo.completed;
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
const description = metadata.description ? metadata.description : null;
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 openFile = () => {
if (mediaType === 'image') {
// use image viewer
if (!this.state.showImageViewer) {
this.setState({
imageUrls: [
{
url: localFileUri,
},
],
showImageViewer: true,
});
}
const playerStyle = [
filePageStyle.player,
this.state.isLandscape
? filePageStyle.containedPlayerLandscape
: this.state.fullscreenMode
? 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
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 (!this.state.showWebView) {
this.setState({
showWebView: true,
});
}
}
if (isWebViewable) {
// show webview
if (!this.state.showWebView) {
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) {
// automatically open a web viewable or image file after the download button is pressed
openFile();
}
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();
}
});
}
innerContent = (
<View style={filePageStyle.pageContainer}>
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
{this.state.showWebView && isWebViewable && (
<WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
)}
if (this.state.downloadPressed && canOpen) {
// automatically open a web viewable or image file after the download button is pressed
openFile();
}
{this.state.showImageViewer && (
<ImageViewer
style={StyleSheet.flatten(filePageStyle.viewer)}
imageUrls={this.state.imageUrls}
renderIndicator={() => null}
/>
)}
return (
<View style={filePageStyle.pageContainer}>
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
{this.state.showWebView && isWebViewable && (
<WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
)}
{!this.state.showWebView && (
<View
style={
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
}
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}
/>
)}
{this.state.showImageViewer && (
<ImageViewer
style={StyleSheet.flatten(filePageStyle.viewer)}
imageUrls={this.state.imageUrls}
renderIndicator={() => null}
/>
)}
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
</TouchableOpacity>
</View>
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<View
style={playerBgStyle}
ref={ref => {
this.playerBackground = ref;
}}
onLayout={evt => {
if (!this.state.playerBgHeight) {
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
}
}}
/>
{!this.state.showWebView && (
<View
style={
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
}
onLayout={this.checkOrientation}
>
<View style={filePageStyle.mediaContainer}>
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />
)}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) &&
this.state.fullscreenMode && <View style={fsPlayerBgStyle} />}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<MediaPlayer
claim={claim}
assignPlayer={ref => {
this.player = ref;
}}
{(!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded && (
<ActivityIndicator size="large" color={Colors.NextLbryGreen} style={filePageStyle.loading} />
)}
{((isPlayable && !completed && !canLoadMedia) ||
canOpen ||
(!completed && !this.state.streamingMode)) &&
!this.state.downloadPressed && (
<FileDownloadButton
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}
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}
/>
)}
{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}
/>
)}
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
</TouchableOpacity>
</View>
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<View
style={playerBgStyle}
ref={ref => {
this.playerBackground = ref;
}}
onLayout={evt => {
if (!this.state.playerBgHeight) {
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
}
}}
/>
)}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && this.state.fullscreenMode && (
<View style={fsPlayerBgStyle} />
)}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<MediaPlayer
claim={claim}
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>
)}
<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} />}
{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>
)}
{costInfo && parseFloat(costInfo.cost) > balance && <FileRewardsDriver navigation={navigation} />}
{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>
)}
{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>
);
}
<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>
{(!videos || !thumbnailPath || !galleryThumbnailsChecked) && (
<View style={publishStyle.loadingView}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
</View>
)}
{thumbnailPath && (!videos || videos.length === 0) && (

View file

@ -2,7 +2,7 @@ import React from 'react';
import { Lbry } from 'lbry-redux';
import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
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 CustomRewardCard from 'component/customRewardCard';
import PageHeader from 'component/pageHeader';
@ -87,7 +87,7 @@ class RewardsPage extends React.PureComponent {
// claim new_user and new_mobile rewards
for (let i = 0; i < rewards.length; i++) {
const { reward_type: type } = rewards[i];
if ('new_user' === type || 'new_mobile' === type) {
if (type === 'new_user' || type === 'new_mobile') {
claimReward(rewards[i]);
}
}
@ -127,7 +127,7 @@ class RewardsPage extends React.PureComponent {
if (fetching) {
return (
<View style={rewardStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
<Text style={rewardStyle.infoText}>Fetching rewards...</Text>
</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 { navigateToUri } from 'utils/helper';
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 FileListItem from 'component/fileListItem';
import FloatingWalletBalance from 'component/floatingWalletBalance';
@ -91,7 +91,7 @@ class SearchPage extends React.PureComponent {
<UriBar value={query} navigation={navigation} onSearchSubmitted={this.handleSearchSubmitted} />
{isSearching && (
<View style={searchStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} style={searchStyle.loading} />
</View>
)}
@ -105,7 +105,7 @@ class SearchPage extends React.PureComponent {
<FileListItem
key={this.state.currentUri}
uri={this.state.currentUri}
featuredResult={true}
featuredResult
style={searchStyle.featuredResultItem}
navigation={navigation}
onPress={() => navigateToUri(navigation, this.state.currentUri)}
@ -113,14 +113,14 @@ class SearchPage extends React.PureComponent {
)}
{uris && uris.length
? uris.map(uri => (
<FileListItem
key={uri}
uri={uri}
style={searchStyle.resultItem}
navigation={navigation}
onPress={() => navigateToUri(navigation, uri)}
/>
))
<FileListItem
key={uri}
uri={uri}
style={searchStyle.resultItem}
navigation={navigation}
onPress={() => navigateToUri(navigation, uri)}
/>
))
: null}
{(!uris || uris.length === 0) && (
<View style={searchStyle.noResults}>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -95,6 +95,7 @@ class TrendingPage extends React.PureComponent {
orderBy={Constants.DEFAULT_ORDER_BY}
trendingForAll={TRENDING_FOR_ITEMS[0].name === currentTrendingForItem.name}
tags={followedTags.map(tag => tag.name)}
time={null}
navigation={navigation}
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';
export function doSetClientSetting(key, value) {
return {
type: ACTIONS.CLIENT_SETTING_CHANGED,
data: {
key,
value,
},
return dispatch => {
dispatch({
type: ACTIONS.CLIENT_SETTING_CHANGED,
data: {
key,
value,
},
});
if (window.persistor) {
window.persistor.flush();
}
};
}

View file

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

View file

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

View file

@ -3,6 +3,8 @@ import { buildURI, isURIValid } from 'lbry-redux';
import { doPopDrawerStack, doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import Constants, { DrawerRoutes } from 'constants'; // eslint-disable-line node/no-deprecated-api
const tagNameLength = 10;
function getRouteForSpecialUri(uri) {
let targetRoute;
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);
}
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
export function __(str) {
return str;