Compare commits

...

10 commits

Author SHA1 Message Date
Akinwale Ariwodola
aa5dd878c0 fix displayed wallet custody message when signed in 2020-03-31 21:17:21 +01:00
Akinwale Ariwodola
47c6fb5889 update app strings 2020-03-26 15:46:33 +01:00
Akinwale Ariwodola
89a9571ce1
Display USD (#135)
* show usd values for lbc on wallet and rewards pages
* link to publish page
* update phrasing
* don't show rewards nag if user is not interested
2020-03-26 15:13:56 +01:00
Akinwale Ariwodola
689e30a5f0
display lbry.tv sync custody message (#134)
* display lbry.tv sync custody message
* rephrase text
2020-03-24 16:17:33 +01:00
Akinwale Ariwodola
33b4806c84 add values to liteFile state 2020-03-23 17:22:29 +01:00
Akinwale Ariwodola
7e1794ce29 fix publishes link. change minimum bid to 0.01. 2020-03-23 09:42:32 +01:00
Akinwale Ariwodola
4c4d561f30 new_android reward 2020-03-20 17:00:18 +01:00
Akinwale Ariwodola
a5953dcef0
Repost creation (#133)
* create reposts
* display reposts properly on publishes page
* show/hide advanced link
* fix edit mode on owned publishes
2020-03-20 14:21:18 +01:00
Akinwale Ariwodola
d33ed55bdf add gitlab CI for multi-project pipeline 2020-03-20 08:36:04 +01:00
Akinwale Ariwodola
12d7fbd472
lbry.tv hybrid mode (#132)
* use lbry.tv for resolve and claim_search
* gracefully handle dynamic sdk ready state
* update pinned lbry-redux commit
* use empty state view when sdk is not ready
* traditional mode if dht is enabled
2020-03-20 08:25:39 +01:00
73 changed files with 1282 additions and 12597 deletions

8
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,8 @@
stages:
- build
build:
variables:
REACT_NATIVE_BRANCH: $CI_COMMIT_REF_NAME
stage: build
trigger: lbry/lbry-android

@ -1 +1 @@
Subproject commit bfd3c711abc4f693dbb68b06fdfce67036b1e4a3
Subproject commit ff30e7f6a4358fd997a9e6d9f75bfe6959eafcb6

View file

@ -304,5 +304,23 @@
"This content cannot be viewed at this time. Please try again in a bit.": "This content cannot be viewed at this time. Please try again in a bit.",
"Download file": "Download file",
"Save %title% (%size%) to your device": "Save %title% (%size%) to your device",
"Save \"%title%\" (%size%) to your device": "Save \"%title%\" (%size%) to your device"
"Save \"%title%\" (%size%) to your device": "Save \"%title%\" (%size%) to your device",
"Find Channels to follow": "Find Channels to follow",
"LBRY works better if you follow at least 5 creators you like. Sign in to show creators you follow if you already have an account.": "LBRY works better if you follow at least 5 creators you like. Sign in to show creators you follow if you already have an account.",
"%remaining% more...": "%remaining% more...",
"Did you know that you can earn free credits worth up to %amount%?": "Did you know that you can earn free credits worth up to %amount%?",
"SHOW ME": "SHOW ME",
"Convert credits to USD on Bittrex": "Convert credits to USD on Bittrex",
"You also have": "You also have",
"in tips": "in tips",
"Earn more tips by uploading cool videos": "Earn more tips by uploading cool videos",
"You staked": "You staked",
"in your publishes": "in your publishes",
"in your supports": "in your supports",
"Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet.": "Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet.",
"A backup of your wallet is synced with lbry.tv": "A backup of your wallet is synced with lbry.tv",
"What does this mean?": "What does this mean?",
"LBRY credits allow you to publish or purchase content.": "LBRY credits allow you to publish or purchase content.",
"You can obtain free credits worth %amount% after you provide an email address.": "You can obtain free credits worth %amount% after you provide an email address.",
"up to": "up to"
}

12178
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -15,8 +15,8 @@
"base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3",
"lbry-redux": "lbryio/lbry-redux#84e697079968364fe526020086c8a44f4d2ef391",
"lbryinc": "lbryio/lbryinc#28afb9b06c3d142bad8347939c043a21b6cb1ae1",
"lbry-redux": "lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017",
"lbryinc": "lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270",
"lodash": ">=4.17.11",
"merge": ">=1.2.1",
"moment": "^2.22.1",

View file

@ -42,8 +42,10 @@ import {
} from 'react-native';
import { selectDrawerStack } from 'redux/selectors/drawer';
import {
Lbry,
ACTIONS,
SETTINGS,
doBalanceSubscribe,
doDismissToast,
doPopulateSharedUserState,
doPreferenceGet,
@ -52,6 +54,10 @@ import {
} from 'lbry-redux';
import {
Lbryio,
rewards as REWARD_TYPES,
doBlackListedOutpointsSubscribe,
doClaimRewardType,
doFilteredOutpointsSubscribe,
doGetSync,
doUserCheckEmailVerified,
doUserEmailVerify,
@ -75,6 +81,7 @@ import discoverStyle from 'styles/discover';
import searchStyle from 'styles/search';
import SearchRightHeaderIcon from 'component/searchRightHeaderIcon';
import Snackbar from 'react-native-snackbar';
import { doSetSdkReady } from 'redux/actions/settings';
const SYNC_GET_INTERVAL = 1000 * 60 * 5; // every 5 minutes
@ -327,6 +334,7 @@ class AppWithNavigationState extends React.Component {
this.emailVerifyCheckInterval = setInterval(() => this.checkEmailVerification(), 5000);
Linking.addEventListener('url', this._handleUrl);
DeviceEventEmitter.addListener('onSdkReady', this.handleSdkReady);
DeviceEventEmitter.addListener('onDownloadAborted', this.handleDownloadAborted);
DeviceEventEmitter.addListener('onDownloadStarted', this.handleDownloadStarted);
DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated);
@ -366,6 +374,70 @@ class AppWithNavigationState extends React.Component {
);
};
checkNewAndroidReward = () => {
const { dispatch, doToast } = this.props;
const claimRewardCallback = err => {
if (err) {
// an error occurred, do not display anything
return;
}
// reward successfully claimed
NativeModules.UtilityModule.setNativeBooleanSetting(Constants.SETTING_NEW_ANDROID_REWARD_CLAIMED, true);
};
NativeModules.UtilityModule.getNativeBooleanSetting(Constants.SETTING_NEW_ANDROID_REWARD_CLAIMED, false).then(
rewardClaimed => {
if (!rewardClaimed) {
dispatch(
doClaimRewardType(REWARD_TYPES.TYPE_NEW_ANDROID, { notifyError: false, callback: claimRewardCallback }),
);
}
},
);
};
handleSdkReady = () => {
const { dispatch } = this.props;
dispatch(doSetSdkReady());
dispatch(doBalanceSubscribe());
dispatch(doBlackListedOutpointsSubscribe());
dispatch(doFilteredOutpointsSubscribe());
Lbry.wallet_status().then(secureWalletStatus => {
// For now, automatically unlock the wallet if a password is set so that downloads work
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password => {
if ((secureWalletStatus.is_encrypted && !secureWalletStatus.is_locked) || secureWalletStatus.is_locked) {
this.setState({
message: __('Unlocking account'),
details: __('Decrypting wallet'),
});
// unlock the wallet and then finish the splash screen
Lbry.wallet_unlock({ password: password || '' }).then(unlocked => {
if (unlocked) {
} else {
// this.handleAccountUnlockFailed();
}
});
}
});
});
this.checkNewAndroidReward();
};
handleAccountUnlockFailed() {
const { dispatch } = this.props;
dispatch(
doToast({
message: __(
'Your wallet failed to unlock, which means you may not be able to play any videos or access your funds. Restart the app to fix this.',
),
isError: true,
}),
);
}
handleDownloadStarted = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo } = evt;
@ -401,6 +473,7 @@ class AppWithNavigationState extends React.Component {
};
componentWillUnmount() {
DeviceEventEmitter.removeListener('onSdkReady', this.handleSdkReady);
DeviceEventEmitter.removeListener('onDownloadAborted', this.handleDownloadAborted);
DeviceEventEmitter.removeListener('onDownloadStarted', this.handleDownloadStarted);
DeviceEventEmitter.removeListener('onDownloadUpdated', this.handleDownloadUpdated);

View file

@ -19,7 +19,4 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
});
export default connect(
select,
perform
)(ChannelIconItem);
export default connect(select, perform)(ChannelIconItem);

View file

@ -23,7 +23,4 @@ const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(
select,
perform,
)(ClaimList);
export default connect(select, perform)(ClaimList);

View file

@ -36,7 +36,4 @@ const perform = dispatch => ({
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(
select,
perform,
)(ClaimResultItem);
export default connect(select, perform)(ClaimResultItem);

View file

@ -1,18 +1,19 @@
import { connect } from 'react-redux';
import { doFetchChannelListMine, selectMyChannelClaims } from 'lbry-redux';
import { selectUser } from 'lbryinc';
import { doToast, selectBalance, selectMyChannelClaims } from 'lbry-redux';
import { selectUnclaimedRewardValue, selectUser } from 'lbryinc';
import { selectSdkReady } from 'redux/selectors/settings';
import DrawerContent from './view';
const select = state => ({
balance: selectBalance(state),
channels: selectMyChannelClaims(state),
sdkReady: selectSdkReady(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
user: selectUser(state),
});
const perform = dispatch => ({
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
notify: data => dispatch(doToast(data)),
});
export default connect(
select,
perform,
)(DrawerContent);
export default connect(select, perform)(DrawerContent);

View file

@ -6,6 +6,8 @@ import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import channelIconStyle from 'styles/channelIcon';
import discoverStyle from 'styles/discover';
import { Lbryio } from 'lbryinc';
import { formatUsd } from 'utils/helper';
const groupedMenuItems = {
'Find content': [
@ -31,11 +33,27 @@ const groupedMenuItems = {
};
const groupNames = Object.keys(groupedMenuItems);
const routesRequiringSdkReady = [
Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
Constants.DRAWER_ROUTE_MY_LBRY,
Constants.DRAWER_ROUTE_PUBLISHES,
Constants.DRAWER_ROUTE_PUBLISH,
Constants.DRAWER_ROUTE_WALLET,
Constants.DRAWER_ROUTE_REWARDS,
Constants.DRAWER_ROUTE_INVITES,
];
class DrawerContent extends React.PureComponent {
state = {
usdExchangeRate: 0,
};
componentDidMount() {
const { fetchChannelListMine } = this.props;
fetchChannelListMine();
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
}
getAvatarImageUrl = () => {
@ -62,8 +80,23 @@ class DrawerContent extends React.PureComponent {
});
};
handleItemPress = routeName => {
const { navigation, notify, sdkReady } = this.props;
if (!sdkReady && routesRequiringSdkReady.includes(routeName)) {
if (navigation.closeDrawer) {
navigation.closeDrawer();
}
notify({
message: __('The background service is still initializing. Please try again when initialization is complete.'),
});
return;
}
navigation.navigate({ routeName: routeName });
};
render() {
const { activeTintColor, navigation, user, onItemPress } = this.props;
const { activeTintColor, balance, navigation, unclaimedRewardAmount, user, onItemPress } = this.props;
const { state } = navigation;
const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
@ -157,7 +190,7 @@ class DrawerContent extends React.PureComponent {
focused ? discoverStyle.menuItemTouchAreaFocused : null,
]}
key={item.label}
onPress={() => navigation.navigate({ routeName: item.route })}
onPress={() => this.handleItemPress(item.route)}
delayPressIn={0}
>
<View style={discoverStyle.menuItemIcon}>
@ -170,6 +203,15 @@ class DrawerContent extends React.PureComponent {
</View>
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
{__(item.label)}
{item.label === 'Wallet' && this.state.usdExchangeRate > 0 && (
<Text> ({formatUsd(parseFloat(balance) * parseFloat(this.state.usdExchangeRate))})</Text>
)}
{item.label === 'Rewards' && this.state.usdExchangeRate > 0 && (
<Text>
{' '}
({formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(this.state.usdExchangeRate))})
</Text>
)}
</Text>
</TouchableOpacity>
);

View file

@ -35,7 +35,4 @@ const perform = dispatch => ({
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(
select,
perform,
)(FileItem);
export default connect(select, perform)(FileItem);

View file

@ -70,7 +70,9 @@ class FileItem extends React.PureComponent {
const outpointsToHide = !blackListedOutpoints
? filteredOutpoints
: blackListedOutpoints.concat(filteredOutpoints);
shouldHide = outpointsToHide.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
shouldHide = outpointsToHide.some(
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
);
}
if (shouldHide) {
// don't display blacklisted or filtered outpoints on the Your tags page

View file

@ -36,7 +36,4 @@ const perform = dispatch => ({
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(
select,
perform,
)(FileListItem);
export default connect(select, perform)(FileListItem);

View file

@ -99,6 +99,7 @@ class FileListItem extends React.PureComponent {
isRewardContent,
channelClaimId,
fullChannelUri,
repostUrl,
repostChannel,
repostChannelUrl,
shortChannelUri,
@ -114,18 +115,21 @@ class FileListItem extends React.PureComponent {
channelClaimId = signingChannel ? signingChannel.claim_id : null;
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
shortChannelUri = signingChannel ? signingChannel.short_url : null;
repostUrl = claim.repost_url;
repostChannelUrl = claim.repost_channel_url;
if (repostChannelUrl) {
const { claimName: repostChannelName } = parseURI(repostChannelUrl);
repostChannel = repostChannelName;
if (repostUrl) {
const { claimName: repostName, channelName } = parseURI(repostUrl);
repostChannel = channelName;
}
isRepost = !!repostChannelUrl;
isRepost = !!repostUrl;
if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = !blackListedOutpoints
? filteredOutpoints
: blackListedOutpoints.concat(filteredOutpoints);
shouldHide = outpointsToHide.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
shouldHide = outpointsToHide.some(
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
);
}
// TODO: hide channels on tag pages?
@ -143,10 +147,23 @@ class FileListItem extends React.PureComponent {
<View>
{isRepost && (
<View style={fileListStyle.repostInfo}>
<Icon name={"retweet"} size={14} style={fileListStyle.repostIcon} />
<Icon name={'retweet'} size={14} style={fileListStyle.repostIcon} />
<Text style={fileListStyle.repostChannelName}>
<Link text={repostChannel}
onPress={() => navigateToUri(navigation, normalizeURI(repostChannelUrl), null, false, null, false)} /> reposted</Text>
<Link
text={`@${repostChannel}`}
onPress={() =>
navigateToUri(
navigation,
normalizeURI(repostChannelUrl || `@${repostChannel}`),
null,
false,
null,
false,
)
}
/>{' '}
reposted
</Text>
</View>
)}

View file

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectBalance } from 'lbry-redux';
import { selectUnclaimedRewardValue } from 'lbryinc';
import Constants from 'constants';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import FloatingWalletBalance from './view';
const select = state => ({
@ -11,7 +11,4 @@ const select = state => ({
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
});
export default connect(
select,
null
)(FloatingWalletBalance);
export default connect(select, null)(FloatingWalletBalance);

View file

@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import {
doClearRepostError,
doFetchChannelListMine,
doRepost,
doToast,
selectBalance,
selectMyChannelClaims,
selectRepostError,
selectRepostLoading,
} from 'lbry-redux';
import ModalRepostView from './view';
const select = state => ({
balance: selectBalance(state),
channels: selectMyChannelClaims(state),
reposting: selectRepostLoading(state),
error: selectRepostError(state),
});
const perform = dispatch => ({
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
notify: data => dispatch(doToast(data)),
repost: options => dispatch(doRepost(options)),
clearError: () => dispatch(doClearRepostError()),
});
export default connect(select, perform)(ModalRepostView);

View file

@ -0,0 +1,204 @@
import React from 'react';
import { ActivityIndicator, Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { formatCredits, creditsToString } from 'lbry-redux';
import modalStyle from 'styles/modal';
import modalRepostStyle from 'styles/modalRepost';
import ChannelSelector from 'component/channelSelector';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import { logPublish } from 'utils/helper';
export default class ModalRepostView extends React.PureComponent {
depositAmountInput;
state = {
channelName: null,
creditsInputFocused: false,
depositAmount: '0.01',
repostName: null,
repostStarted: false,
showAdvanced: false,
};
componentDidMount() {
const { claim, fetchChannelListMine } = this.props;
const { name } = claim;
fetchChannelListMine();
this.setState({ repostName: name });
this.checkChannelSelection(this.props);
}
componentWillReceiveProps(nextProps) {
this.checkChannelSelection(nextProps);
const { notify } = this.props;
const { reposting, error } = nextProps;
if (this.state.repostStarted && !reposting && error) {
this.setState({ repostStarted: false });
notify({ message: error, isError: true });
}
}
checkChannelSelection = props => {
const { channels = [] } = props;
if (!this.state.channelName && channels && channels.length > 0) {
const firstChannel = channels[0];
this.setState({ channelName: firstChannel.name });
}
};
handleChannelChange = channelName => {
const { channels = [] } = this.props;
if (channels && channels.length > 0) {
const filtered = channels.filter(c => c.name === channelName);
if (filtered.length > 0) {
const channel = filtered[0];
this.setState({ channelName });
}
}
};
handleRepost = () => {
const { claim, balance, notify, repost, onRepostSuccessful, channels = [], clearError } = this.props;
const { depositAmount, repostName, channelName } = this.state;
if (parseInt(depositAmount, 10) > balance) {
notify({
message: 'Insufficient credits',
isError: true,
});
return;
}
clearError();
const channel = channels.filter(ch => ch.name === channelName)[0];
this.setState({ repostStarted: true }, () => {
repost({
name: repostName,
bid: creditsToString(depositAmount),
channel_id: channel.claim_id,
claim_id: claim.claim_id,
}).then(repostClaim => {
logPublish(repostClaim);
this.setState({ repostStarted: false });
notify({ message: __('The content was successfully reposted!') });
if (onRepostSuccessful) onRepostSuccessful();
});
});
};
render() {
const { balance, channels, reposting, title, onCancelPress, onOverlayPress } = this.props;
const canRepost = !!this.state.channelName && !!this.state.repostName;
const channelsLoaded = channels && channels.length > 0;
const processing = this.state.repostStarted || reposting || !channelsLoaded;
return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={modalStyle.container} activeOpacity={1}>
<View
style={modalRepostStyle.container}
onLayout={() => {
if (this.tipAmountInput) {
this.tipAmountInput.focus();
}
}}
>
<Text style={modalRepostStyle.title} numberOfLines={1}>
{__('Repost %title%', { title })}
</Text>
<Text style={modalRepostStyle.infoText}>
{__('Repost your favorite content to help more people discover them!')}
</Text>
<Text style={modalRepostStyle.label}>{__('Channel to post on')}</Text>
<ChannelSelector
showAnonymous={false}
channelName={this.state.channelName}
onChannelChange={this.handleChannelChange}
/>
{this.state.showAdvanced && (
<View>
<Text style={modalRepostStyle.label}>{__('Name')}</Text>
<View style={modalRepostStyle.nameRow}>
<TextInput
editable={false}
value={this.state.channelName ? `lbry://${this.state.channelName}/` : ''}
style={modalRepostStyle.input}
/>
<TextInput
editable={canRepost}
value={this.state.repostName}
underlineColorAndroid={Colors.NextLbryGreen}
selectTextOnFocus
onChangeText={value => this.setState({ repostName: value })}
style={modalRepostStyle.input}
/>
</View>
<Text style={modalRepostStyle.label}>{__('Deposit')}</Text>
<View style={modalRepostStyle.row}>
<View style={modalRepostStyle.amountRow}>
<TextInput
editable={!this.state.repostStarted}
ref={ref => (this.depositAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
underlineColorAndroid={Colors.NextLbryGreen}
keyboardType={'numeric'}
onFocus={() => this.setState({ creditsInputFocused: true })}
onBlur={() => this.setState({ creditsInputFocused: false })}
placeholder={'0'}
value={this.state.depositAmount}
selectTextOnFocus
style={modalRepostStyle.depositAmountInput}
/>
<Text style={modalRepostStyle.currency}>LBC</Text>
<View style={modalRepostStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={modalRepostStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
</View>
</View>
)}
<View style={modalRepostStyle.buttonRow}>
{!processing && (
<Link
style={modalRepostStyle.cancelLink}
text={__('Cancel')}
onPress={() => {
if (onCancelPress) onCancelPress();
}}
/>
)}
{processing && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
<View style={modalRepostStyle.rightButtonRow}>
<Link
style={modalRepostStyle.advancedLink}
text={this.state.showAdvanced ? __('Hide advanced') : __('Show advanced')}
onPress={() => {
this.setState({ showAdvanced: !this.state.showAdvanced });
}}
/>
<Button
text={__('Repost')}
style={modalRepostStyle.button}
disabled={!canRepost || this.state.repostStarted || reposting}
onPress={this.handleRepost}
/>
</View>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}
}

View file

@ -1,9 +1,11 @@
import { connect } from 'react-redux';
import { doSendTip, doToast, selectBalance } from 'lbry-redux';
import { selectSdkReady } from 'redux/selectors/settings';
import ModalTipView from './view';
const select = state => ({
balance: selectBalance(state),
sdkReady: selectSdkReady(state),
});
const perform = dispatch => ({
@ -12,7 +14,4 @@ const perform = dispatch => ({
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
});
export default connect(
select,
perform
)(ModalTipView);
export default connect(select, perform)(ModalTipView);

View file

@ -5,7 +5,6 @@ import modalStyle from 'styles/modal';
import modalTipStyle from 'styles/modalTip';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
@ -19,12 +18,22 @@ export default class ModalTipView extends React.PureComponent {
};
handleSendTip = () => {
const { claim, balance, notify, onSendTipFailed, onSendTipSuccessful, sendTip } = this.props;
const { claim, balance, notify, onSendTipFailed, onSendTipSuccessful, sdkReady, sendTip } = this.props;
const { tipAmount } = this.state;
if (!sdkReady) {
notify({
message: __(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
),
});
return;
}
if (tipAmount > balance) {
notify({
message: 'Insufficient credits',
isError: true,
});
return;
}
@ -56,13 +65,13 @@ export default class ModalTipView extends React.PureComponent {
() => {
// error
if (onSendTipFailed) onSendTipFailed();
}
)
},
),
);
},
},
],
{ cancelable: true }
{ cancelable: true },
);
};
@ -115,7 +124,7 @@ export default class ModalTipView extends React.PureComponent {
<Text style={modalTipStyle.infoText}>
{__(
'This will appear as a tip for %content%, which will boost its ability to be discovered while active.',
{ content: contentName }
{ content: contentName },
)}{' '}
<Link
style={modalTipStyle.learnMoreLink}

View file

@ -1,10 +1,11 @@
// @flow
import React from 'react';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import Colors from '../../styles/colors';
import { formatUsd } from 'utils/helper';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from '../link';
import rewardStyle from '../../styles/reward';
import Link from 'component/link';
import rewardStyle from 'styles/reward';
type Props = {
canClaim: boolean,
@ -61,7 +62,7 @@ class RewardCard extends React.PureComponent<Props> {
if (reward) {
const claimed = !!reward.transaction_id;
if (!claimed && reward.reward_range && reward.reward_range.includes('-')) {
return reward.reward_range.split('-')[0] + '+'; // ex: 5+
return reward.reward_range.split('-')[1];
} else if (reward.reward_amount > 0) {
return reward.reward_amount;
}
@ -72,7 +73,7 @@ class RewardCard extends React.PureComponent<Props> {
};
render() {
const { canClaim, isPending, onClaimPress, reward } = this.props;
const { canClaim, isPending, onClaimPress, reward, usdExchangeRate } = this.props;
const claimed = !!reward.transaction_id;
return (
@ -117,8 +118,16 @@ class RewardCard extends React.PureComponent<Props> {
)}
</View>
<View style={rewardStyle.rightCol}>
{reward.reward_range && reward.reward_range.indexOf('-') > -1 && (
<Text style={rewardStyle.rightColHeader}>{__('up to')}</Text>
)}
<Text style={rewardStyle.rewardAmount}>{this.getDisplayAmount()}</Text>
<Text style={rewardStyle.rewardCurrency}>LBC</Text>
{usdExchangeRate > 0 && (
<Text style={rewardStyle.rewardUsd}>
&asymp;{formatUsd(parseFloat(this.getDisplayAmount()) * parseFloat(usdExchangeRate))}
</Text>
)}
</View>
</TouchableOpacity>
);

View file

@ -7,6 +7,7 @@ import Link from 'component/link';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import rewardStyle from 'styles/reward';
import { formatUsd } from '../../utils/helper';
class RewardEnrolment extends React.Component {
componentDidMount() {
@ -29,7 +30,7 @@ class RewardEnrolment extends React.Component {
};
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
const { unclaimedRewardAmount, usdExchangeRate } = this.props;
return (
<View style={rewardStyle.enrollContainer}>
@ -43,9 +44,11 @@ class RewardEnrolment extends React.Component {
<View style={rewardStyle.onboarding}>
<Text style={rewardStyle.enrollDescText}>
{__('LBRY credits allow you to purchase content, publish content, and influence the network.')}
{__('LBRY credits allow you to publish or purchase content.')}
{'\n\n'}
{__('You get credits for free for providing an email address and taking other basic actions.')}
{__('You can obtain free credits worth %amount% after you provide an email address.', {
amount: formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(usdExchangeRate)),
})}
{'\n\n'}
<Link style={rewardStyle.learnMoreLink} text={__('Learn more')} onPress={this.onLearnMorePressed} />.
</Text>

View file

@ -0,0 +1,4 @@
import { connect } from 'react-redux';
import SdkLoadingStatus from './view';
export default connect()(SdkLoadingStatus);

View file

@ -0,0 +1,15 @@
import { ActivityIndicator, Text, View } from 'react-native';
import React from 'react';
import discoverStyle from 'styles/discover';
import Colors from 'styles/colors';
export default class SdkLoadingStatus extends React.PureComponent {
render() {
return (
<View style={discoverStyle.sdkLoading}>
<ActivityIndicator color={Colors.White} size={'small'} />
<Text style={discoverStyle.sdkLoadingText}>{__('The LBRY background service is initializing...')}</Text>
</View>
);
}
}

View file

@ -23,7 +23,4 @@ const perform = dispatch => ({
unsubscribe: subscription => doChannelUnsubscribe(subscription),
});
export default connect(
select,
perform,
)(SuggestedSubscriptionItem);
export default connect(select, perform)(SuggestedSubscriptionItem);

View file

@ -16,7 +16,4 @@ const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(
select,
perform
)(SuggestedSubscriptions);
export default connect(select, perform)(SuggestedSubscriptions);

View file

@ -24,7 +24,4 @@ const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(
select,
perform,
)(SuggestedSubscriptionsGrid);
export default connect(select, perform)(SuggestedSubscriptionsGrid);

View file

@ -58,7 +58,7 @@ class SuggestedSubscriptionsGrid extends React.PureComponent {
const uris = claimSearchByQuery[claimSearchKey];
if (
lastPageReached[claimSearchKey] ||
((uris.length > 0 && uris.length < suggestedPageSize) || uris.length >= softLimit)
(uris.length > 0 && uris.length < suggestedPageSize) || uris.length >= softLimit
) {
return;
}
@ -81,7 +81,7 @@ class SuggestedSubscriptionsGrid extends React.PureComponent {
}
render() {
const { claimSearchByQuery, suggested, inModal, navigation } = this.props;
const { claimSearchByQuery, inModal, navigation } = this.props;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
const claimSearchUris = claimSearchByQuery[claimSearchKey];
@ -92,7 +92,7 @@ class SuggestedSubscriptionsGrid extends React.PureComponent {
maxToRenderPerBatch={48}
removeClippedSubviews
itemDimension={120}
spacing={2}
spacing={1}
items={claimSearchUris}
style={inModal ? subscriptionsStyle.modalScrollContainer : subscriptionsStyle.scrollContainer}
contentContainerStyle={

View file

@ -6,7 +6,4 @@ const select = state => ({
balance: selectBalance(state),
});
export default connect(
select,
null
)(WalletBalance);
export default connect(select, null)(WalletBalance);

View file

@ -1,9 +1,9 @@
// @flow
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Lbry, formatCredits } from 'lbry-redux';
import Address from 'component/address';
import Button from 'component/button';
import { formatCredits } from 'lbry-redux';
import { Lbryio } from 'lbryinc';
import { formatUsd } from 'utils/helper';
import walletStyle from 'styles/wallet';
type Props = {
@ -11,6 +11,18 @@ type Props = {
};
class WalletBalance extends React.PureComponent<Props> {
state = {
usdExchangeRate: 0,
};
componentDidMount() {
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
}
render() {
const { balance } = this.props;
return (
@ -21,6 +33,13 @@ class WalletBalance extends React.PureComponent<Props> {
<Text style={walletStyle.balance}>
{(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'}
</Text>
<Text style={walletStyle.usdBalance}>
{this.state.usdExchangeRate > 0 && (
<Text>
&asymp;{formatUsd(isNaN(balance) ? 0 : parseFloat(balance) * parseFloat(this.state.usdExchangeRate))}
</Text>
)}
</Text>
</View>
);
}

View file

@ -1,14 +1,14 @@
import { connect } from 'react-redux';
import { selectClaimsBalance, selectSupportsBalance, selectTipsBalance } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import WalletBalanceExtra from './view';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
const select = state => ({
claimsBalance: selectClaimsBalance(state) || 0,
deviceWalletSynced: makeSelectClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED)(state),
supportsBalance: selectSupportsBalance(state) || 0,
tipsBalance: selectTipsBalance(state) || 0,
});
export default connect(
select,
null,
)(WalletBalanceExtra);
export default connect(select, null)(WalletBalanceExtra);

View file

@ -1,12 +1,14 @@
// @flow
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Lbry, formatCredits } from 'lbry-redux';
import Address from 'component/address';
import Button from 'component/button';
import { Text, View } from 'react-native';
import { formatCredits } from 'lbry-redux';
import { Lbryio } from 'lbryinc';
import { formatUsd } from 'utils/helper';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import walletStyle from 'styles/wallet';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
type Props = {
claimsBalance: number,
@ -15,37 +17,96 @@ type Props = {
};
class WalletBalanceExtra extends React.PureComponent<Props> {
state = {
usdExchangeRate: 0,
};
componentDidMount() {
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
}
render() {
const { claimsBalance, supportsBalance, tipsBalance } = this.props;
const { claimsBalance, deviceWalletSynced, navigation, supportsBalance, tipsBalance } = this.props;
return (
<View style={walletStyle.balanceExtraCard}>
<View style={walletStyle.walletExtraRow}>
<View style={walletStyle.walletExtraCol}>
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'gift'} size={16} />
<Text style={walletStyle.walletExtraCaption}>{__('You also have')}</Text>
<View style={walletStyle.balanceRow}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(tipsBalance), 2)}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
</View>
<Text style={walletStyle.text}>{__('in tips')}</Text>
</View>
<View style={walletStyle.balanceExtra}>
<View style={walletStyle.usdInfoCard}>
<Text style={walletStyle.usdInfoText}>
You can convert your credits to USD and withdraw the converted amount using an exchange.{' '}
<Link
style={walletStyle.usdConvertFaqLink}
href={'https://lbry.com/faq/exchanges'}
text={__('Learn more')}
/>
.
</Text>
<Link
style={walletStyle.usdConvertLink}
href={'https://bittrex.com/Account/Register?referralCode=4M1-P30-BON'}
text={__('Convert credits to USD on Bittrex')}
/>
</View>
<View style={walletStyle.walletExtraCol}>
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'lock'} size={16} />
<Text style={walletStyle.walletExtraCaption}>{__('You staked')}</Text>
<View style={walletStyle.balanceRow}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(claimsBalance), 2)}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
<View style={walletStyle.balanceExtraCard}>
<View style={walletStyle.walletExtraRow}>
<View style={walletStyle.walletExtraCol}>
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'gift'} size={16} />
<Text style={walletStyle.walletExtraCaption}>{__('You also have')}</Text>
<View style={walletStyle.balanceRow}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(tipsBalance), 2)}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
</View>
<Text style={walletStyle.usdWalletExtraBalance}>
&asymp;{formatUsd(parseFloat(tipsBalance) * parseFloat(this.state.usdExchangeRate))}
</Text>
<Text style={walletStyle.text}>{__('in tips')}</Text>
<Link
style={walletStyle.earnTipsLink}
onPress={() => {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH });
}}
text={__('Earn more tips by uploading cool videos')}
/>
</View>
<Text style={walletStyle.text}>{__('in your publishes')}</Text>
<View style={[walletStyle.balanceRow, walletStyle.walletExtraTopMargin]}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(supportsBalance), 2)}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
<View style={walletStyle.walletExtraCol}>
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'lock'} size={16} />
<Text style={walletStyle.walletExtraCaption}>{__('You staked')}</Text>
<View style={walletStyle.balanceRow}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(claimsBalance), 2)}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
</View>
<Text style={walletStyle.text}>{__('in your publishes')}</Text>
<View style={[walletStyle.balanceRow, walletStyle.walletExtraTopMargin]}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(supportsBalance), 2)}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
</View>
<Text style={walletStyle.text}>{__('in your supports')}</Text>
</View>
<Text style={walletStyle.text}>{__('in your supports')}</Text>
</View>
</View>
<View style={walletStyle.syncDriverCustody}>
<Text style={walletStyle.syncInfoText}>
{deviceWalletSynced
? __('A backup of your wallet is synced with lbry.tv')
: __('Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet.')}
</Text>
<Link
text={__('What does this mean?')}
href={
deviceWalletSynced
? 'https://lbry.com/faq/account-sync'
: 'https://lbry.com/faq/how-to-backup-wallet#android'
}
style={walletStyle.syncInfoLink}
/>
</View>
</View>
);
}

View file

@ -27,7 +27,7 @@ class WalletSyncDriver extends React.PureComponent<Props> {
},
},
],
{ cancelable: true }
{ cancelable: true },
);
}
};

View file

@ -45,6 +45,9 @@ const Constants = {
SETTING_REWARDS_NOT_INTERESTED: 'rewardsNotInterested',
SETTING_DEVICE_WALLET_SYNCED: 'deviceWalletSynced',
SETTING_DHT_ENABLED: 'dhtEnabled',
SETTING_NEW_ANDROID_REWARD_CLAIMED: 'newAndroidRewardClaimed',
ACTION_SDK_READY: 'SDK_READY',
ACTION_DELETE_COMPLETED_BLOBS: 'DELETE_COMPLETED_BLOBS',
ACTION_FIRST_RUN_PAGE_CHANGED: 'FIRST_RUN_PAGE_CHANGED',
@ -147,7 +150,7 @@ const Constants = {
TRUE_STRING: 'true',
MINIMUM_TRANSACTION_BALANCE: 0.1,
MINIMUM_TRANSACTION_BALANCE: 0.01,
SHARE_BASE_URL: 'https://open.lbry.com',
};

View file

@ -55,6 +55,10 @@ import settingsReducer from 'redux/reducers/settings';
import thunk from 'redux-thunk';
window.__ = __;
if (!NativeModules.UtilityModule.dhtEnabled) {
Lbry.alternateConnectionString = 'https://api.lbry.tv/api/v1/proxy';
Lbry.methodsUsingAlternateConnectionString = ['claim_search', 'resolve'];
}
const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) {

View file

@ -22,6 +22,7 @@ import {
import { doUpdateChannelFormState, doClearChannelFormState } from 'redux/actions/form';
import { selectDrawerStack } from 'redux/selectors/drawer';
import { selectChannelFormState, selectHasChannelFormState } from 'redux/selectors/form';
import { selectSdkReady } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import ChannelCreator from './view';
@ -33,6 +34,7 @@ const select = state => ({
fetchingChannels: selectFetchingMyChannels(state),
balance: selectBalance(state),
hasFormState: selectHasChannelFormState(state),
sdkReady: selectSdkReady(state),
updatingChannel: selectUpdatingChannel(state),
updateChannelError: selectUpdateChannelError(state),
});
@ -52,7 +54,4 @@ const perform = dispatch => ({
setExplicitNavigateBack: flag => dispatch(doSetExplicitNavigateBack(flag)),
});
export default connect(
select,
perform,
)(ChannelCreator);
export default connect(select, perform)(ChannelCreator);

View file

@ -47,7 +47,7 @@ export default class ChannelCreator extends React.PureComponent {
channelNameUserEdited: false,
newChannelTitle: '',
newChannelName: '',
newChannelBid: 0.1,
newChannelBid: 0.01,
addingChannel: false,
creatingChannel: false,
editChannelUrl: null,
@ -176,9 +176,7 @@ export default class ChannelCreator extends React.PureComponent {
NativeModules.Firebase.setCurrentScreen('Channels').then(result => {
pushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR, navigation.state.params ? navigation.state.params : null);
setPlayerVisible();
if (!fetchingChannels) {
fetchChannelListMine();
}
fetchChannelListMine();
DeviceEventEmitter.addListener('onDocumentPickerFilePicked', this.onFilePicked);
DeviceEventEmitter.addListener('onDocumentPickerCanceled', this.onPickerCanceled);
@ -282,7 +280,7 @@ export default class ChannelCreator extends React.PureComponent {
handleCreateCancel = () => {
const { clearChannelFormState } = this.props;
clearChannelFormState(); // explicitly clear state on cancel?
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.1 });
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.01 });
};
handlePickerValueChange = (itemValue, itemIndex) => {
@ -591,7 +589,7 @@ export default class ChannelCreator extends React.PureComponent {
channelNameUserEdited: false,
newChannelTitle: '',
newChannelName: '',
newChannelBid: 0.1,
newChannelBid: 0.01,
addingChannel: false,
creatingChannel: false,
newChannelNameError: '',
@ -802,7 +800,15 @@ export default class ChannelCreator extends React.PureComponent {
};
render() {
const { abandoningClaimIds, balance, fetchingChannels, updatingChannel, channels = [], navigation } = this.props;
const {
abandoningClaimIds,
balance,
fetchingChannels,
sdkReady,
updatingChannel,
channels = [],
navigation,
} = this.props;
const {
autoStyle,
autoStyles,
@ -826,6 +832,19 @@ export default class ChannelCreator extends React.PureComponent {
const hasChannels = channels && channels.length > 0;
if (!sdkReady) {
return (
<View style={channelCreatorStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
return (
<View style={channelCreatorStyle.container}>
<UriBar

View file

@ -13,7 +13,7 @@ import {
} from 'lbryinc';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetClientSetting, doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { makeSelectClientSetting, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectSdkReady, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DiscoverPage from './view';
@ -27,6 +27,7 @@ const select = state => ({
followedTags: selectFollowedTags(state),
ratingReminderDisabled: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED)(state),
ratingReminderLastShown: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN)(state),
sdkReady: selectSdkReady(state),
sortByItem: selectSortByItem(state),
timeItem: selectTimeItem(state),
unreadSubscriptions: selectUnreadSubscriptions(state),
@ -46,7 +47,4 @@ const perform = dispatch => ({
setTimeItem: item => dispatch(doSetTimeItem(item)),
});
export default connect(
select,
perform,
)(DiscoverPage);
export default connect(select, perform)(DiscoverPage);

View file

@ -23,6 +23,7 @@ import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import ModalTagSelector from 'component/modalTagSelector';
import ModalPicker from 'component/modalPicker';
import SdkLoadingStatus from 'component/sdkLoadingStatus';
import UriBar from 'component/uriBar';
import _ from 'lodash';
@ -268,8 +269,8 @@ class DiscoverPage extends React.PureComponent {
);
render() {
const { currentRoute, navigation, sortByItem, timeItem } = this.props;
const { orderBy, showModalTagSelector, showSortPicker, showTimePicker } = this.state;
const { currentRoute, navigation, sdkReady, sortByItem, timeItem } = this.props;
const { showModalTagSelector, showSortPicker, showTimePicker } = this.state;
return (
<View style={discoverStyle.container}>
@ -289,7 +290,7 @@ class DiscoverPage extends React.PureComponent {
keyExtractor={(item, index) => item}
/>
)}
{!showModalTagSelector && !showSortPicker && !showTimePicker && (
{sdkReady && !showModalTagSelector && !showSortPicker && !showTimePicker && (
<FloatingWalletBalance navigation={navigation} />
)}
{showModalTagSelector && (
@ -316,6 +317,8 @@ class DiscoverPage extends React.PureComponent {
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}
{!sdkReady && <SdkLoadingStatus />}
</View>
);
}

View file

@ -11,15 +11,17 @@ import {
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doDeleteFile } from 'redux/actions/file';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import { selectSdkReady } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DownloadsPage from './view';
const select = state => ({
claims: selectMyClaimsWithoutChannels(state),
currentRoute: selectCurrentRoute(state),
fileInfos: selectFileInfosDownloaded(state),
downloadedUris: selectDownloadedUris(state),
fileInfos: selectFileInfosDownloaded(state),
fetching: selectIsFetchingFileList(state) || selectIsFetchingClaimListMine(state),
sdkReady: selectSdkReady(state),
});
const perform = dispatch => ({
@ -32,7 +34,4 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(
select,
perform
)(DownloadsPage);
export default connect(select, perform)(DownloadsPage);

View file

@ -144,11 +144,24 @@ class DownloadsPage extends React.PureComponent {
};
render() {
const { fetching, claims, downloadedUris, fileInfos, navigation } = this.props;
const { downloadedUris, fetching, navigation, sdkReady } = this.props;
const { selectionMode, selectedUris } = this.state;
const filteredUris = this.getFilteredUris();
const hasDownloads = filteredUris && filteredUris.length > 0;
if (!sdkReady) {
return (
<View style={downloadsStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
return (
<View style={downloadsStyle.container}>
<UriBar

View file

@ -41,6 +41,7 @@ import { doDeleteFile, doStopDownloadingFile } from 'redux/actions/file';
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doToggleFullscreenMode } from 'redux/actions/settings';
import { selectDrawerStack, makeSelectPlayerVisible } from 'redux/selectors/drawer';
import { selectSdkReady } from 'redux/selectors/settings';
import FilePage from './view';
const select = (state, props) => {
@ -66,6 +67,7 @@ const select = (state, props) => {
failedPurchaseUris: selectFailedPurchaseUris(state),
myClaimUris: selectMyClaimUrisWithoutChannels(state),
purchaseUriErrorMessage: selectPurchaseUriErrorMessage(state),
sdkReady: selectSdkReady(state),
streamingUrl: makeSelectStreamingUrlForUri(contentUri)(state),
thumbnail: makeSelectThumbnailForUri(contentUri)(state),
title: makeSelectTitleForUri(contentUri)(state),
@ -99,7 +101,4 @@ const perform = dispatch => ({
toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)),
});
export default connect(
select,
perform,
)(FilePage);
export default connect(select, perform)(FilePage);

View file

@ -37,13 +37,13 @@ import FilePrice from 'component/filePrice';
import FloatingWalletBalance from 'component/floatingWalletBalance';
import Link from 'component/link';
import MediaPlayer from 'component/mediaPlayer';
import ModalRepostView from 'component/modalRepostView';
import ModalTipView from 'component/modalTipView';
import ProgressCircle from 'react-native-progress-circle';
import RelatedContent from 'component/relatedContent';
import SubscribeButton from 'component/subscribeButton';
import SubscribeNotificationButton from 'component/subscribeNotificationButton';
import UriBar from 'component/uriBar';
import Video from 'react-native-video';
import FileRewardsDriver from 'component/fileRewardsDriver';
import filePageStyle from 'styles/filePage';
import uriBarStyle from 'styles/uriBar';
@ -73,7 +73,7 @@ class FilePage extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
attemptAutoGet: false,
autoGetAttempted: false,
autoOpened: false,
autoDownloadStarted: false,
autoPlayMedia: false,
@ -95,6 +95,7 @@ class FilePage extends React.PureComponent {
showImageViewer: false,
showWebView: false,
showTipView: false,
showRepostView: false,
playbackStarted: false,
playerBgHeight: 0,
playerHeight: 0,
@ -266,6 +267,7 @@ class FilePage extends React.PureComponent {
'playerBgHeighht',
'playerHeight',
'relatedY',
'showRepostView',
'showTipView',
'showImageViewer',
'showWebView',
@ -297,7 +299,17 @@ class FilePage extends React.PureComponent {
}
componentDidUpdate(prevProps, prevState) {
const { claim, contentType, costInfo, fileInfo, isResolvingUri, resolveUri, navigation, title } = this.props;
const {
claim,
contentType,
costInfo,
fileInfo,
isResolvingUri,
resolveUri,
sdkReady,
navigation,
title,
} = this.props;
const { uri } = this.state;
if (!isResolvingUri && claim === undefined && uri) {
resolveUri(uri);
@ -323,10 +335,12 @@ class FilePage extends React.PureComponent {
const isPlayable = mediaType === 'video' || mediaType === 'audio';
const isViewable = mediaType === 'image' || mediaType === 'text';
if (claim && costInfo && costInfo.cost === 0 && !this.state.autoGetAttempted && isViewable) {
this.setState({ autoGetAttempted: true }, () => this.checkStoragePermissionForDownload());
this.setState({ autoGetAttempted: true }, () => {
this.checkStoragePermissionForDownload();
});
}
if (((costInfo && costInfo.cost > 0) || !isPlayable) && (!fileInfo && !isViewable) && !this.state.showRecommended) {
if (((costInfo && costInfo.cost > 0) || !isPlayable) && !fileInfo && !isViewable && !this.state.showRecommended) {
this.setState({ showRecommended: true });
}
@ -706,7 +720,7 @@ class FilePage extends React.PureComponent {
};
confirmPurchaseUri = (uri, costInfo, download) => {
const { notify, purchaseUri, title } = this.props;
const { notify, purchaseUri, sdkReady, title } = this.props;
if (!costInfo) {
notify({ message: __('This content cannot be viewed at this time. Please try again in a bit.'), isError: true });
this.setState({ downloadPressed: false });
@ -715,6 +729,11 @@ class FilePage extends React.PureComponent {
}
const { cost } = costInfo;
if (!NativeModules.UtilityModule.dhtEnabled && !sdkReady && parseFloat(cost) === 0) {
this.attemptLbryTvPlayback();
return;
}
if (costInfo.cost > 0) {
Alert.alert(
__('Confirm Purchase'),
@ -742,22 +761,40 @@ class FilePage extends React.PureComponent {
}
};
getStreamUrlForClaim = claim => {
const { name, claim_id: claimId } = claim;
return `https://player.lbry.tv/content/claims/${name}/${claimId}/stream`;
};
attemptLbryTvPlayback = () => {
const { claim } = this.props;
if (claim) {
this.setState({ streamingMode: true, currentStreamUrl: this.getStreamUrlForClaim(claim) });
}
};
onFileDownloadButtonPressed = () => {
this.startTime = Date.now();
const { claim, costInfo, contentType, setPlayerVisible } = this.props;
const { claim, costInfo, contentType, notify, sdkReady, setPlayerVisible } = this.props;
const mediaType = Lbry.getMediaType(contentType);
const isPlayable = mediaType === 'video' || mediaType === 'audio';
const isViewable = mediaType === 'image' || mediaType === 'text';
const purchaseUrl = this.getPurchaseUrl();
NativeModules.Firebase.track('purchase_uri', { uri: purchaseUrl });
if (!isPlayable) {
if (!sdkReady) {
notify({
message: __('The LBRY background service is still initializing. Please wait a few moments and try again.'),
});
return;
}
this.onDownloadPressed();
} else {
this.confirmPurchaseUri(purchaseUrl, costInfo, !isPlayable);
}
NativeModules.Firebase.track('purchase_uri', { uri: purchaseUrl });
if (isPlayable) {
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
}
@ -783,7 +820,16 @@ class FilePage extends React.PureComponent {
};
onDownloadPressed = () => {
const { claim, title } = this.props;
const { claim, notify, sdkReady, title } = this.props;
if (!sdkReady) {
notify({
message: __(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
),
});
return;
}
const fileSize = claim && claim.value && claim.value.source ? claim.value.source.size : 0;
Alert.alert(
__('Download file'),
@ -983,7 +1029,6 @@ class FilePage extends React.PureComponent {
myClaimUris,
navigation,
position,
purchaseUri,
pushDrawerStack,
setPlayerVisible,
thumbnail,
@ -1093,7 +1138,8 @@ class FilePage extends React.PureComponent {
const channelName = signingChannel && signingChannel.name;
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const fullUri = `${claim.name}#${claim.claim_id}`;
const canEdit = myClaimUris.includes(normalizeURI(fullUri));
const canEdit =
myClaimUris.includes(normalizeURI(fullUri)) || myClaimUris.includes(normalizeURI(claim.canonical_url));
const showActions =
(canEdit || (fileInfo && fileInfo.download_path)) &&
!this.state.fullscreenMode &&
@ -1177,7 +1223,7 @@ class FilePage extends React.PureComponent {
style={filePageStyle.mediaContainer}
onPress={this.onFileDownloadButtonPressed}
>
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
{(canOpen || !fileInfo || (isPlayable && !canLoadMedia) || (!canOpen && fileInfo)) && (
<FileItemMedia
duration={duration}
style={filePageStyle.thumbnail}
@ -1314,6 +1360,14 @@ class FilePage extends React.PureComponent {
<Text style={filePageStyle.largeButtonText}>{__('Share')}</Text>
</TouchableOpacity>
<TouchableOpacity
style={filePageStyle.largeButton}
onPress={() => this.setState({ showRepostView: true })}
>
<Icon name={'retweet'} size={16} style={filePageStyle.largeButtonIcon} />
<Text style={filePageStyle.largeButtonText}>{__('Repost')}</Text>
</TouchableOpacity>
<TouchableOpacity
style={filePageStyle.largeButton}
onPress={() => this.setState({ showTipView: true })}
@ -1488,7 +1542,17 @@ class FilePage extends React.PureComponent {
onSendTipSuccessful={() => this.setState({ showTipView: false })}
/>
)}
{this.state.showRepostView && (
<ModalRepostView
claim={claim}
title={title}
onCancelPress={() => this.setState({ showRepostView: false })}
onOverlayPress={() => this.setState({ showRepostView: false })}
onRepostSuccessful={() => this.setState({ showRepostView: false })}
/>
)}
{!this.state.fullscreenMode &&
!this.state.showRepostView &&
!this.state.showTipView &&
!this.state.showImageViewer &&
!this.state.showWebView && <FloatingWalletBalance navigation={navigation} />}

View file

@ -12,23 +12,22 @@ import {
doFetchInviteStatus,
doUserInviteNew,
} from 'lbryinc';
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doUpdateChannelFormState, doClearChannelFormState } from 'redux/actions/form';
import { selectDrawerStack } from 'redux/selectors/drawer';
import { selectChannelFormState, selectHasChannelFormState } from 'redux/selectors/form';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectSdkReady } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import InvitesPage from './view';
const select = state => ({
channels: selectMyChannelClaims(state),
errorMessage: selectUserInviteNewErrorMessage(state),
fetchingChannels: selectFetchingMyChannels(state),
fetchingInvitees: selectUserInviteStatusIsPending(state),
errorMessage: selectUserInviteNewErrorMessage(state),
invitesRemaining: selectUserInvitesRemaining(state),
referralCode: selectUserInviteReferralCode(state),
isPending: selectUserInviteNewIsPending(state),
invitees: selectUserInvitees(state),
invitesRemaining: selectUserInvitesRemaining(state),
isPending: selectUserInviteNewIsPending(state),
referralCode: selectUserInviteReferralCode(state),
referralReward: selectReferralReward(state),
sdkReady: selectSdkReady(state),
});
const perform = dispatch => ({
@ -40,7 +39,4 @@ const perform = dispatch => ({
notify: data => dispatch(doToast(data)),
});
export default connect(
select,
perform,
)(InvitesPage);
export default connect(select, perform)(InvitesPage);

View file

@ -11,14 +11,10 @@ import {
View,
} from 'react-native';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
import PageHeader from 'component/pageHeader';
import RewardCard from 'component/rewardCard';
import RewardEnrolment from 'component/rewardEnrolment';
import EmptyStateView from 'component/emptyStateView';
import UriBar from 'component/uriBar';
import invitesStyle from 'styles/invites';
import { fetchReferralCode, logPublish } from 'utils/helper';
@ -134,10 +130,23 @@ class InvitesPage extends React.PureComponent {
};
render() {
const { fetchingInvitees, user, navigation, notify, isPending, invitees } = this.props;
const { email, inviteLink } = this.state;
const { fetchingInvitees, invitees, isPending, navigation, sdkReady } = this.props;
const { email } = this.state;
const hasInvitees = invitees && invitees.length > 0;
if (!sdkReady) {
return (
<View style={invitesStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
return (
<View style={invitesStyle.container}>
<UriBar navigation={navigation} />

View file

@ -38,15 +38,19 @@ class LiteFilePage extends React.PureComponent {
player = null;
startTime = null;
state = {
channelName: null,
channelUrl: null,
title: null,
fileViewLogged: false,
fullscreenMode: false,
playbackStarted: false,
playerHeight: null,
isLandscape: false,
sdkReady: false, // TODO: progressively enable features (e.g. tip) when sdk is ready
showRecommended: false,
title: null,
viewCount: 0,
};
@ -127,6 +131,10 @@ class LiteFilePage extends React.PureComponent {
);
};
componentDidMount() {
this.startTime = Date.now();
}
componentDidUpdate() {
const { navigation } = this.props;
const { uri } = navigation.state.params;

View file

@ -13,6 +13,7 @@ import { selectDrawerStack } from 'redux/selectors/drawer';
import { doUpdatePublishFormState, doClearPublishFormState, doPendingPublishSuccess } from 'redux/actions/form';
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectPublishFormState, selectHasPublishFormState } from 'redux/selectors/form';
import { selectSdkReady } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import PublishPage from './view';
@ -23,6 +24,7 @@ const select = state => ({
myClaims: selectMyClaims(state),
publishFormState: selectPublishFormState(state),
publishFormValues: selectPublishFormValues(state),
sdkReady: selectSdkReady(state),
});
const perform = dispatch => ({
@ -39,7 +41,4 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(
select,
perform,
)(PublishPage);
export default connect(select, perform)(PublishPage);

View file

@ -37,6 +37,7 @@ import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import EmptyStateView from 'component/emptyStateView';
import FastImage from 'react-native-fast-image';
import FloatingWalletBalance from 'component/floatingWalletBalance';
import Icon from 'react-native-vector-icons/FontAwesome5';
@ -120,7 +121,7 @@ class PublishPage extends React.PureComponent {
// input data
hasEditedContentAddress: false,
bid: 0.1,
bid: 0.01,
description: null,
title: null,
language: 'en',
@ -974,7 +975,7 @@ class PublishPage extends React.PureComponent {
};
render() {
const { balance, navigation, notify, publishFormValues } = this.props;
const { balance, navigation, notify, sdkReady } = this.props;
const {
allThumbnailsChecked,
canUseCamera,
@ -987,6 +988,19 @@ class PublishPage extends React.PureComponent {
videos,
} = this.state;
if (!sdkReady) {
return (
<View style={publishStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
let content;
if (Constants.PHASE_SELECTOR === currentPhase) {
content = (

View file

@ -9,6 +9,7 @@ import {
selectIsFetchingClaimListMine,
} from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectSdkReady } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import PublishesPage from './view';
@ -16,6 +17,7 @@ const select = state => ({
uris: selectMyClaimUrisWithoutChannels(state),
fetching: selectIsFetchingClaimListMine(state),
pendingClaims: selectPendingClaims(state),
sdkReady: selectSdkReady(state),
});
const perform = dispatch => ({
@ -27,7 +29,4 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(
select,
perform
)(PublishesPage);
export default connect(select, perform)(PublishesPage);

View file

@ -114,14 +114,27 @@ class PublishesPage extends React.PureComponent {
},
},
],
{ cancelable: true }
{ cancelable: true },
);
};
render() {
const { fetching, navigation, uris } = this.props;
const { fetching, navigation, sdkReady, uris } = this.props;
const { selectionMode, selectedUris } = this.state;
if (!sdkReady) {
return (
<View style={publishStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
return (
<View style={publishStyle.container}>
<UriBar
@ -181,7 +194,7 @@ class PublishesPage extends React.PureComponent {
});
} else {
// TODO: when shortUrl is available for my claims, navigate to that URL instead
navigateToUri(navigation, item);
navigateToUri(navigation, claim.permanent_url);
}
}
}}

View file

@ -12,7 +12,8 @@ import {
import { doToast } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants';
import { selectSdkReady } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import RewardsPage from './view';
const select = state => ({
@ -22,6 +23,7 @@ const select = state => ({
emailVerifyPending: selectEmailVerifyIsPending(state),
fetching: selectFetchingRewards(state),
rewards: selectUnclaimedRewards(state),
sdkReady: selectSdkReady(state),
user: selectUser(state),
});
@ -33,7 +35,4 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(
select,
perform
)(RewardsPage);
export default connect(select, perform)(RewardsPage);

View file

@ -1,11 +1,11 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import { Lbryio } from 'lbryinc';
import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
import Colors from 'styles/colors';
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';
import EmptyStateView from 'component/emptyStateView';
import RewardCard from 'component/rewardCard';
import RewardEnrolment from 'component/rewardEnrolment';
import UriBar from 'component/uriBar';
@ -17,13 +17,14 @@ const FILTER_CLAIMED = 'claimed';
class RewardsPage extends React.PureComponent {
state = {
currentFilter: FILTER_AVAILABLE,
firstRewardClaimed: false,
isEmailVerified: false,
isIdentityVerified: false,
isRewardApproved: false,
verifyRequestStarted: false,
revealVerification: true,
firstRewardClaimed: false,
currentFilter: FILTER_AVAILABLE,
usdExchangeRate: 0,
verifyRequestStarted: false,
};
scrollView = null;
@ -48,6 +49,12 @@ class RewardsPage extends React.PureComponent {
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Rewards');
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
fetchRewards();
this.setState({
@ -158,6 +165,7 @@ class RewardsPage extends React.PureComponent {
canClaim={!isNotEligible}
reward={reward}
reward_type={reward.reward_type}
usdExchangeRate={this.state.usdExchangeRate}
/>
))}
<CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} />
@ -192,13 +200,28 @@ class RewardsPage extends React.PureComponent {
};
render() {
const { user, navigation } = this.props;
const { navigation, sdkReady } = this.props;
const { currentFilter } = this.state;
if (!sdkReady) {
return (
<View style={rewardStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
return (
<View style={rewardStyle.container}>
<UriBar navigation={navigation} />
{(!this.state.isEmailVerified || !this.state.isRewardApproved) && <RewardEnrolment navigation={navigation} />}
{(!this.state.isEmailVerified || !this.state.isRewardApproved) && (
<RewardEnrolment usdExchangeRate={this.state.usdExchangeRate} navigation={navigation} />
)}
{this.state.isEmailVerified && this.state.isRewardApproved && (
<ScrollView

View file

@ -1,6 +1,16 @@
import React from 'react';
import { SETTINGS } from 'lbry-redux';
import { ActivityIndicator, Picker, Platform, Text, View, ScrollView, Switch, NativeModules } from 'react-native';
import {
ActivityIndicator,
Picker,
Platform,
Text,
TextInput,
View,
ScrollView,
Switch,
NativeModules,
} from 'react-native';
import { navigateBack } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
@ -328,7 +338,9 @@ class SettingsPage extends React.PureComponent {
<View style={settingsStyle.switchText}>
<Text style={settingsStyle.label}>{__('Participate in the data network')}</Text>
<Text style={settingsStyle.description}>
{__('Enable DHT (this will take effect upon app and background service restart)')}
{__(
'Enable peer-to-peer functionality (this will take effect upon app and background service restart)',
)}
</Text>
</View>
<View style={settingsStyle.switchContainer}>

View file

@ -1,11 +1,9 @@
import { connect } from 'react-redux';
import { SETTINGS, doBalanceSubscribe, doUpdateBlockHeight, doPopulateSharedUserState, doToast } from 'lbry-redux';
import { SETTINGS, doUpdateBlockHeight, doPopulateSharedUserState, doToast } from 'lbry-redux';
import {
doAuthenticate,
doInstallNew,
doClaimRewardType,
doInstallNewWithParams,
doBlackListedOutpointsSubscribe,
doFilteredOutpointsSubscribe,
doFetchMySubscriptions,
doFetchRewardedContent,
doGetSync,
@ -32,9 +30,6 @@ const perform = dispatch => ({
dispatch(doAuthenticate(appVersion, os, firebaseToken, true, null, callInstall)),
installNewWithParams: (appVersion, installationId, nodeId, lbrynetVersion, os, platform, firebaseToken) =>
dispatch(doInstallNewWithParams(appVersion, installationId, nodeId, lbrynetVersion, os, platform, firebaseToken)),
balanceSubscribe: () => dispatch(doBalanceSubscribe()),
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
filteredOutpointsSubscribe: () => dispatch(doFilteredOutpointsSubscribe()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchSubscriptions: callback => dispatch(doFetchMySubscriptions(callback)),
getSync: (password, callback) => dispatch(doGetSync(password, callback)),

View file

@ -23,6 +23,7 @@ class SplashScreen extends React.PureComponent {
};
state = {
authWithoutSdk: false,
accountUnlockFailed: false,
appVersion: null,
daemonReady: false,
@ -53,7 +54,7 @@ class SplashScreen extends React.PureComponent {
liteModeParams: {
installationId: installIdContent,
nodeId: null,
lbrynetVersion: '0.62.0',
lbrynetVersion: '0.64.0',
platform,
},
},
@ -66,26 +67,26 @@ class SplashScreen extends React.PureComponent {
});
};
updateStatus() {
authenticateWithoutSdk() {
const { authenticate } = this.props;
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
this.setState({ appVersion, shouldAuthenticate: true, authWithoutSdk: true });
NativeModules.Firebase.getMessagingToken()
.then(firebaseToken => {
this.setState({ firebaseToken }, () => authenticate(appVersion, Platform.OS, firebaseToken, false));
})
.catch(() => {
authenticate(appVersion, Platform.OS, null, false);
});
});
}
updateStatus() {
const { liteMode } = this.state;
if (liteMode) {
// authenticate immediately
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
this.setState({ appVersion, shouldAuthenticate: true });
NativeModules.Firebase.getMessagingToken()
.then(firebaseToken => {
this.setState({ firebaseToken }, () => authenticate(appVersion, Platform.OS, firebaseToken, false));
})
.catch(() => {
authenticate(appVersion, Platform.OS, null, false);
});
});
} else {
Lbry.status().then(status => {
this._updateStatusCallback(status);
});
// authenticate immediately
if (!NativeModules.UtilityModule.dhtEnabled) {
this.authenticateWithoutSdk();
}
}
@ -121,40 +122,51 @@ class SplashScreen extends React.PureComponent {
componentWillReceiveProps(nextProps) {
const { getSync, installNewWithParams } = this.props;
const { daemonReady, shouldAuthenticate, liteMode, liteModeParams, appVersion, firebaseToken } = this.state;
const {
daemonReady,
authWithoutSdk,
shouldAuthenticate,
liteMode,
liteModeParams,
appVersion,
firebaseToken,
} = this.state;
const { user } = nextProps;
if (liteMode && user && user.id) {
this.navigateToLiteMode();
} else if (daemonReady && shouldAuthenticate && user && user.id) {
this.setState({ shouldAuthenticate: false }, () => {
// call install new after successful authentication
if (liteMode) {
const { installationId, nodeId, lbrynetVersion, platform } = liteModeParams;
installNewWithParams(
appVersion,
installationId,
nodeId,
lbrynetVersion,
Platform.OS,
platform,
firebaseToken,
);
}
} else if (shouldAuthenticate && user && user.id) {
if (daemonReady || authWithoutSdk) {
this.setState({ shouldAuthenticate: false }, () => {
// call install new after successful authentication
if (authWithoutSdk) {
const { installationId, nodeId, lbrynetVersion, platform } = liteModeParams;
installNewWithParams(
appVersion,
installationId,
nodeId,
lbrynetVersion,
Platform.OS,
platform,
firebaseToken,
);
}
// user is authenticated, navigate to the main view
if (user.has_verified_email) {
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(walletPassword => {
getSync(walletPassword, () => {
this.getUserSettings();
// user is authenticated, navigate to the main view
if (user.has_verified_email) {
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(walletPassword => {
getSync(walletPassword, () => {
this.getUserSettings();
});
});
});
this.navigateToMain();
return;
}
this.navigateToMain();
});
this.navigateToMain();
return;
}
this.navigateToMain();
});
}
}
}
@ -194,22 +206,9 @@ class SplashScreen extends React.PureComponent {
};
finishSplashScreen = () => {
const {
authenticate,
balanceSubscribe,
blacklistedOutpointsSubscribe,
filteredOutpointsSubscribe,
getSync,
updateBlockHeight,
user,
} = this.props;
const { authenticate, getSync, user } = this.props;
// Lbry.resolve({ urls: 'lbry://one' }).then(() => {
// Leave the splash screen
balanceSubscribe();
blacklistedOutpointsSubscribe();
filteredOutpointsSubscribe();
if (user && user.id && user.has_verified_email) {
// user already authenticated
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(walletPassword => {
@ -229,31 +228,14 @@ class SplashScreen extends React.PureComponent {
});
});
}
// });
};
handleAccountUnlockFailed() {
this.setState({ accountUnlockFailed: true });
}
_updateStatusCallback(status) {
const { fetchSubscriptions, getSync, setClientSetting } = this.props;
const startupStatus = status.startup_status;
const walletStatus = status.wallet;
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
const hasStarted = startupStatus.stream_manager && startupStatus.wallet && status.wallet.blocks_behind <= 0;
if (hasStarted) {
// Wait until we are able to resolve a name before declaring
// that we are done.
// TODO: This is a hack, and the logic should live in the daemon
// to give us a better sense of when we are actually started
this.setState({
daemonReady: true,
isLagging: false,
isRunning: true,
});
handleSdkReady = () => {
this.setState({ daemonReady: true }, () => {
Lbry.wallet_status().then(secureWalletStatus => {
// For now, automatically unlock the wallet if a password is set so that downloads work
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password => {
@ -284,9 +266,12 @@ class SplashScreen extends React.PureComponent {
}
});
});
});
};
return;
}
handleSdkStatusResponse = evt => {
const { status } = evt;
const walletStatus = status.wallet;
const headerSyncProgress = walletStatus ? walletStatus.headers_synchronization_progress : null;
if (headerSyncProgress && headerSyncProgress < 100) {
@ -321,23 +306,25 @@ class SplashScreen extends React.PureComponent {
details: __('Initializing LBRY service'),
});
}
setTimeout(() => {
this.updateStatus();
}, 1000);
}
};
componentWillMount() {
DeviceEventEmitter.addListener('onNotificationTargetLaunch', this.onNotificationTargetLaunch);
DeviceEventEmitter.addListener('onSdkReady', this.handleSdkReady);
DeviceEventEmitter.addListener('onSdkStatusResponse', this.handleSdkStatusResponse);
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('onNotificationTargetLaunch', this.onNotificationTargetLaunch);
DeviceEventEmitter.removeListener('onSdkReady', this.handleSdkReady);
DeviceEventEmitter.removeListener('onSdkStatusResponse', this.handleSdkStatusResponse);
}
componentDidMount() {
NativeModules.Firebase.track('app_launch', null);
NativeModules.Firebase.setCurrentScreen('Splash');
NativeModules.UtilityModule.checkSdkReady();
const { navigation } = this.props;
const { resetUrl } = navigation.state.params;
const isResetUrlSet = !!resetUrl;
@ -357,11 +344,7 @@ class SplashScreen extends React.PureComponent {
}
// Only connect after checking initial launch url / notification launch target
if (liteMode) {
this.initLiteMode();
} else {
this.lbryConnect();
}
this.initLiteMode();
});
});
@ -377,19 +360,23 @@ class SplashScreen extends React.PureComponent {
}
lbryConnect = () => {
Lbry.connect()
.then(() => {
this.updateStatus();
})
.catch(e => {
this.setState({
isLagging: true,
message: __('Connection Failure'),
details: __(
'We could not establish a connection to the SDK. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.',
),
if (NativeModules.UtilityModule.dhtEnabled) {
Lbry.connect()
.then(() => {
this.updateStatus();
})
.catch(e => {
this.setState({
isLagging: true,
message: __('Connection Failure'),
details: __(
'We could not establish a connection to the SDK. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.',
),
});
});
});
} else {
this.updateStatus(); // skip lbry.connect for now (unless dht flag is enabled)
}
};
handleContinueAnywayPressed = () => {

View file

@ -12,11 +12,13 @@ import {
selectViewMode,
selectFirstRunCompleted,
selectShowSuggestedSubs,
selectUnclaimedRewardValue,
selectUser,
} from 'lbryinc';
import { doToast, selectFetchingClaimSearch } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetClientSetting, doSetTimeItem } from 'redux/actions/settings';
import { makeSelectClientSetting, selectTimeItem } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectSdkReady, selectTimeItem } from 'redux/selectors/settings';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import SubscriptionsPage from './view';
@ -32,8 +34,12 @@ const select = state => ({
unreadSubscriptions: selectUnreadSubscriptions(state),
viewMode: selectViewMode(state),
firstRunCompleted: selectFirstRunCompleted(state),
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
showSuggestedSubs: selectShowSuggestedSubs(state),
timeItem: selectTimeItem(state),
sdkReady: selectSdkReady(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
user: selectUser(state),
});
const perform = dispatch => ({
@ -48,7 +54,4 @@ const perform = dispatch => ({
setTimeItem: item => dispatch(doSetTimeItem(item)),
});
export default connect(
select,
perform,
)(SubscriptionsPage);
export default connect(select, perform)(SubscriptionsPage);

View file

@ -11,7 +11,7 @@ import {
View,
} from 'react-native';
import { buildURI, parseURI } from 'lbry-redux';
import { getOrderBy } from 'utils/helper';
import { formatUsd, getOrderBy } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import Button from 'component/button';
@ -30,16 +30,22 @@ import SubscribedChannelList from 'component/subscribedChannelList';
import SuggestedSubscriptions from 'component/suggestedSubscriptions';
import SuggestedSubscriptionsGrid from 'component/suggestedSubscriptionsGrid';
import UriBar from 'component/uriBar';
import SdkLoadingStatus from 'component/sdkLoadingStatus';
import Snackbar from 'react-native-snackbar';
import { Lbryio } from 'lbryinc';
class SubscriptionsPage extends React.PureComponent {
state = {
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting subscriptions by new
filteredChannels: [],
orderBy: ['release_time'],
showRewardsNag: true,
showingSuggestedSubs: false,
showSortPicker: false,
showTimePicker: false,
showModalSuggestedSubs: false,
orderBy: ['release_time'],
filteredChannels: [],
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting subscriptions by new
usdExchangeRate: 0,
userEmailVerified: false,
};
didFocusListener;
@ -56,15 +62,7 @@ class SubscriptionsPage extends React.PureComponent {
}
onComponentFocused = () => {
const {
currentRoute,
doFetchMySubscriptions,
doFetchRecommendedSubscriptions,
doSetViewMode,
pushDrawerStack,
setPlayerVisible,
subscriptionsViewMode,
} = this.props;
const { currentRoute, doFetchMySubscriptions, pushDrawerStack, sdkReady, setPlayerVisible, user } = this.props;
if (currentRoute === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) {
pushDrawerStack();
@ -72,6 +70,17 @@ class SubscriptionsPage extends React.PureComponent {
setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Subscriptions');
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD }, () => {
if (sdkReady && parseFloat(this.state.usdExchangeRate) > 0 && user && !user.is_reward_approved) {
this.showRewardsAvailable();
}
});
}
});
this.setState({ userEmailVerified: user && user.has_verified_email });
doFetchMySubscriptions();
};
@ -80,15 +89,56 @@ class SubscriptionsPage extends React.PureComponent {
}
componentWillReceiveProps(nextProps) {
const { currentRoute } = nextProps;
const { currentRoute: prevRoute } = this.props;
const { currentRoute, user, sdkReady } = nextProps;
const { currentRoute: prevRoute, doFetchMySubscriptions } = this.props;
if (Constants.DRAWER_ROUTE_SUBSCRIPTIONS === currentRoute && currentRoute !== prevRoute) {
this.onComponentFocused();
}
if (user && user.has_verified_email && !this.state.userEmailVerified) {
// user just signed in
this.setState({ showingSuggestedSubs: false, userEmailVerified: true }, () => {
doFetchMySubscriptions();
});
}
if (
sdkReady &&
parseFloat(this.state.usdExchangeRate) > 0 &&
this.state.showRewardsNag &&
user &&
!user.is_reward_approved
) {
this.showRewardsAvailable();
}
this.unsubscribeShortChannelUrls();
}
showRewardsAvailable = () => {
const { navigation, unclaimedRewardAmount, rewardsNotInterested } = this.props;
if (rewardsNotInterested) {
this.setState({ showRewardsNag: false });
return;
}
this.setState({ showRewardsNag: false }, () => {
Snackbar.show({
title: __('Did you know that you can earn free credits worth up to %amount%?', {
amount: formatUsd(parseFloat(this.state.usdExchangeRate) * parseFloat(unclaimedRewardAmount)),
}),
duration: Snackbar.LENGTH_LONG,
action: {
title: __('SHOW ME'),
color: Colors.LbryGreen,
onPress: () => {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_REWARDS });
},
},
});
});
};
handleSortByItemSelected = item => {
this.setState({ currentSortByItem: item, orderBy: getOrderBy(item), showSortPicker: false });
};
@ -137,21 +187,7 @@ class SubscriptionsPage extends React.PureComponent {
};
render() {
const {
suggestedChannels,
subscribedChannels,
allSubscriptions,
doCompleteFirstRun,
doShowSuggestedSubs,
loading,
loadingSuggested,
firstRunCompleted,
showSuggestedSubs,
timeItem,
unreadSubscriptions,
navigation,
notify,
} = this.props;
const { subscribedChannels, loading, loadingSuggested, sdkReady, timeItem, navigation, notify } = this.props;
const { currentSortByItem, filteredChannels, showModalSuggestedSubs, showSortPicker, showTimePicker } = this.state;
const numberOfSubscriptions = subscribedChannels ? subscribedChannels.length : 0;
@ -296,6 +332,8 @@ class SubscriptionsPage extends React.PureComponent {
onDonePress={() => this.setState({ showModalSuggestedSubs: false })}
/>
)}
{!sdkReady && <SdkLoadingStatus />}
</View>
);
}

View file

@ -3,11 +3,12 @@ import { selectFollowedTags, doToggleTagFollow } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import { selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import { selectSdkReady, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import TagPage from './view';
const select = state => ({
currentRoute: selectCurrentRoute(state),
sdkReady: selectSdkReady(state),
sortByItem: selectSortByItem(state),
timeItem: selectTimeItem(state),
followedTags: selectFollowedTags(state),
@ -21,7 +22,4 @@ const perform = dispatch => ({
toggleTagFollow: tag => dispatch(doToggleTagFollow(tag)),
});
export default connect(
select,
perform
)(TagPage);
export default connect(select, perform)(TagPage);

View file

@ -14,6 +14,7 @@ import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import FloatingWalletBalance from 'component/floatingWalletBalance';
import Link from 'component/link';
import ModalPicker from 'component/modalPicker';
import SdkLoadingStatus from 'component/sdkLoadingStatus';
import UriBar from 'component/uriBar';
class TagPage extends React.PureComponent {
@ -127,7 +128,7 @@ class TagPage extends React.PureComponent {
};
render() {
const { navigation, sortByItem, timeItem } = this.props;
const { navigation, sdkReady, sortByItem, timeItem } = this.props;
const { tag, showSortPicker, showTimePicker } = this.state;
return (
@ -144,7 +145,7 @@ class TagPage extends React.PureComponent {
orientation={Constants.ORIENTATION_VERTICAL}
/>
)}
{!showSortPicker && !showTimePicker && <FloatingWalletBalance navigation={navigation} />}
{sdkReady && !showSortPicker && !showTimePicker && <FloatingWalletBalance navigation={navigation} />}
{showSortPicker && (
<ModalPicker
title={__('Sort content by')}
@ -163,6 +164,7 @@ class TagPage extends React.PureComponent {
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/>
)}
{!sdkReady && <SdkLoadingStatus />}
</View>
);
}

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectClientSetting, selectSdkReady } from 'redux/selectors/settings';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import { selectBalance } from 'lbry-redux';
@ -15,6 +15,7 @@ const select = state => ({
deviceWalletSynced: makeSelectClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED)(state),
hasSyncedWallet: selectHasSyncedWallet(state),
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
sdkReady: selectSdkReady(state),
understandsRisks: makeSelectClientSetting(Constants.SETTING_ALPHA_UNDERSTANDS_RISKS)(state),
user: selectUser(state),
});
@ -27,7 +28,4 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(
select,
perform
)(WalletPage);
export default connect(select, perform)(WalletPage);

View file

@ -1,5 +1,6 @@
import React from 'react';
import { NativeModules, ScrollView, Text, View } from 'react-native';
import EmptyStateView from 'component/emptyStateView';
import TransactionListRecent from 'component/transactionListRecent';
import WalletAddress from 'component/walletAddress';
import WalletBalance from 'component/walletBalance';
@ -8,8 +9,6 @@ import WalletSend from 'component/walletSend';
import WalletRewardsDriver from 'component/walletRewardsDriver';
import WalletSignIn from 'component/walletSignIn';
import WalletSyncDriver from 'component/walletSyncDriver';
import Button from 'component/button';
import Link from 'component/link';
import UriBar from 'component/uriBar';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import walletStyle from 'styles/wallet';
@ -60,16 +59,20 @@ class WalletPage extends React.PureComponent {
};
render() {
const {
balance,
backupDismissed,
hasSyncedWallet,
rewardsNotInterested,
understandsRisks,
setClientSetting,
navigation,
user,
} = this.props;
const { balance, rewardsNotInterested, understandsRisks, navigation, sdkReady, user } = this.props;
if (!sdkReady) {
return (
<View style={walletStyle.container}>
<UriBar navigation={navigation} />
<EmptyStateView
message={__(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
)}
/>
</View>
);
}
const signedIn = user && user.has_verified_email;
if (!signedIn && !understandsRisks) {
@ -91,7 +94,7 @@ class WalletPage extends React.PureComponent {
>
{!rewardsNotInterested && (!balance || balance === 0) && <WalletRewardsDriver navigation={navigation} />}
<WalletBalance />
<WalletBalanceExtra />
<WalletBalanceExtra navigation={navigation} />
<WalletAddress />
<WalletSend />
<TransactionListRecent navigation={navigation} />

View file

@ -17,6 +17,14 @@ export function doSetClientSetting(key, value) {
};
}
export function doSetSdkReady() {
return dispatch => {
dispatch({
type: Constants.ACTION_SDK_READY,
});
};
}
export function doSetSortByItem(item) {
return dispatch => {
dispatch({

View file

@ -4,6 +4,7 @@ import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
const reducers = {};
const defaultState = {
clientSettings: {},
sdkReady: false,
sortByItemName: Constants.SORT_BY_HOT,
timeItemName: Constants.TIME_WEEK,
fullscreenMode: false,
@ -20,6 +21,11 @@ reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => {
});
};
reducers[Constants.ACTION_SDK_READY] = (state, action) =>
Object.assign({}, state, {
sdkReady: true,
});
reducers[Constants.ACTION_SORT_BY_ITEM_CHANGED] = (state, action) =>
Object.assign({}, state, {
sortByItemName: action.data.name,

View file

@ -4,38 +4,22 @@ import { getSortByItemForName, getTimeItemForName } from 'utils/helper';
const selectState = state => state.settings || {};
export const selectDaemonSettings = createSelector(
selectState,
state => state.daemonSettings
);
export const selectDaemonSettings = createSelector(selectState, state => state.daemonSettings);
export const selectClientSettings = createSelector(
selectState,
state => state.clientSettings || {}
);
export const selectClientSettings = createSelector(selectState, state => state.clientSettings || {});
export const selectSortByItem = createSelector(
selectState,
state => getSortByItemForName(state.sortByItemName)
);
export const selectSortByItem = createSelector(selectState, state => getSortByItemForName(state.sortByItemName));
export const selectTimeItem = createSelector(
selectState,
state => getTimeItemForName(state.timeItemName)
);
export const selectTimeItem = createSelector(selectState, state => getTimeItemForName(state.timeItemName));
export const makeSelectClientSetting = setting =>
createSelector(
selectClientSettings,
settings => (settings ? settings[setting] : undefined)
);
createSelector(selectClientSettings, settings => (settings ? settings[setting] : undefined));
// refactor me
export const selectShowNsfw = makeSelectClientSetting(SETTINGS.SHOW_NSFW);
export const selectKeepDaemonRunning = makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING);
export const selectFullscreenMode = createSelector(
selectState,
state => state.fullscreenMode
);
export const selectFullscreenMode = createSelector(selectState, state => state.fullscreenMode);
export const selectSdkReady = createSelector(selectState, state => state.sdkReady);

View file

@ -398,6 +398,23 @@ const discoverStyle = StyleSheet.create({
fontSize: 14,
textAlign: 'center',
},
sdkLoading: {
position: 'absolute',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
bottom: 0,
left: 0,
right: 0,
padding: 8,
backgroundColor: Colors.LbryGreen,
},
sdkLoadingText: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.White,
marginLeft: 8,
},
});
export default discoverStyle;

93
src/styles/modalRepost.js Normal file
View file

@ -0,0 +1,93 @@
import { StyleSheet } from 'react-native';
import Colors from './colors';
const modalRepostStyle = StyleSheet.create({
container: {
padding: 16,
},
title: {
fontFamily: 'Inter-Regular',
fontSize: 24,
marginBottom: 8,
},
row: {
flexDirection: 'row',
flex: 1,
},
amountRow: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 24,
},
depositAmountInput: {
fontFamily: 'Inter-Regular',
fontSize: 14,
alignSelf: 'flex-start',
textAlign: 'right',
width: 80,
letterSpacing: 1,
},
currency: {
fontFamily: 'Inter-Regular',
fontSize: 12,
marginLeft: 4,
},
buttonRow: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 16,
},
button: {
backgroundColor: Colors.LbryGreen,
},
cancelLink: {
color: Colors.Grey,
},
advancedLink: {
color: Colors.Grey,
marginRight: 16,
},
balance: {
alignItems: 'center',
flexDirection: 'row',
marginLeft: 24,
},
balanceText: {
fontFamily: 'Inter-SemiBold',
fontSize: 14,
marginLeft: 4,
},
info: {
marginTop: 4,
},
infoText: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.DescriptionGrey,
},
learnMoreLink: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.LbryGreen,
},
label: {
fontFamily: 'Inter-Regular',
fontSize: 14,
marginTop: 8,
},
nameRow: {
flexDirection: 'row',
alignItems: 'center',
},
rightButtonRow: {
flexDirection: 'row',
alignItems: 'center',
},
input: {
fontFamily: 'Inter-Regular',
fontSize: 14,
},
});
export default modalRepostStyle;

View file

@ -411,6 +411,7 @@ const publishStyle = StyleSheet.create({
marginTop: 60,
},
publishesScrollPadding: {
paddingTop: 16,
paddingBottom: 16,
},
listItem: {
@ -418,8 +419,9 @@ const publishStyle = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 8,
marginLeft: 8,
marginRight: 8,
marginLeft: 16,
marginRight: 16,
marginBottom: 12,
},
noVideos: {
color: Colors.White,
@ -449,7 +451,7 @@ const publishStyle = StyleSheet.create({
publishesFooterButton: {
alignSelf: 'flex-start',
backgroundColor: Colors.LbryGreen,
marginTop: 16,
marginTop: 8,
},
thumbnailEditOverlay: {
alignItems: 'center',

View file

@ -147,6 +147,10 @@ const rewardStyle = StyleSheet.create({
width: '18%',
alignItems: 'center',
},
rightColHeader: {
fontFamily: 'Inter-Regular',
fontSize: 12,
},
rewardAmount: {
fontFamily: 'Inter-Regular',
fontSize: 26,
@ -154,6 +158,7 @@ const rewardStyle = StyleSheet.create({
},
rewardCurrency: {
fontFamily: 'Inter-Regular',
fontSize: 12,
},
rewardTitle: {
fontFamily: 'Inter-Regular',
@ -322,6 +327,12 @@ const rewardStyle = StyleSheet.create({
activeFilterLink: {
fontFamily: 'Inter-SemiBold',
},
rewardUsd: {
fontFamily: 'Inter-Regular',
fontSize: 12,
color: Colors.DescriptionGrey,
marginTop: 6,
},
});
export default rewardStyle;

View file

@ -192,8 +192,6 @@ const subscriptionsStyle = StyleSheet.create({
suggestedItem: {
alignItems: 'center',
marginBottom: 16,
marginLeft: 16,
marginRight: 16,
height: 140,
},
suggestedItemThumbnailContainer: {
@ -209,8 +207,8 @@ const subscriptionsStyle = StyleSheet.create({
height: '100%',
},
suggestedItemDetails: {
marginLeft: 16,
marginRight: 16,
marginLeft: 8,
marginRight: 8,
alignItems: 'center',
},
suggestedItemSubscribe: {
@ -229,7 +227,7 @@ const subscriptionsStyle = StyleSheet.create({
suggestedItemTitle: {
fontFamily: 'Inter-Regular',
textAlign: 'center',
fontSize: 14,
fontSize: 13,
marginTop: 4,
marginBottom: 2,
},

View file

@ -23,7 +23,7 @@ const tagStyle = StyleSheet.create({
},
text: {
fontFamily: 'Inter-Regular',
fontSize: 14,
fontSize: 12,
marginRight: 8,
},
tagResultsList: {

View file

@ -103,10 +103,12 @@ const walletStyle = StyleSheet.create({
marginLeft: 16,
marginRight: 16,
},
balanceExtraCard: {
backgroundColor: Colors.White,
balanceExtra: {
marginLeft: 16,
marginRight: 16,
},
balanceExtraCard: {
backgroundColor: Colors.White,
padding: 16,
},
balanceBackground: {
@ -135,6 +137,13 @@ const walletStyle = StyleSheet.create({
fontFamily: 'Inter-Bold',
fontSize: 36,
marginLeft: 16,
},
usdBalance: {
color: Colors.White,
fontFamily: 'Inter-Regular',
fontSize: 20,
marginLeft: 16,
marginTop: 2,
marginBottom: 16,
},
balanceFocus: {
@ -252,6 +261,22 @@ const walletStyle = StyleSheet.create({
borderBottomWidth: 1,
borderBottomColor: Colors.PageBackground,
},
syncDriverCustody: {
backgroundColor: Colors.LbryGreen,
padding: 16,
},
syncInfoText: {
color: Colors.White,
fontFamily: 'Inter-Regular',
fontSize: 16,
marginBottom: 8,
},
syncInfoLink: {
color: Colors.White,
fontFamily: 'Inter-Regular',
fontSize: 14,
textDecorationLine: 'underline',
},
syncDriverLink: {
color: Colors.LbryGreen,
fontFamily: 'Inter-Regular',
@ -384,6 +409,11 @@ const walletStyle = StyleSheet.create({
fontFamily: 'Inter-SemiBold',
fontSize: 28,
},
usdWalletExtraBalance: {
fontFamily: 'Inter-Regular',
fontSize: 16,
color: Colors.DescriptionGrey,
},
balanceRow: {
flexDirection: 'row',
alignItems: 'center',
@ -406,6 +436,34 @@ const walletStyle = StyleSheet.create({
left: 0,
top: 0,
},
usdInfoCard: {
backgroundColor: Colors.White,
padding: 16,
borderBottomColor: Colors.VeryLightGrey,
borderBottomWidth: 1,
},
usdInfoText: {
fontFamily: 'Inter-Regular',
fontSize: 14,
marginBottom: 8,
},
usdConvertLink: {
fontFamily: 'Inter-Regular',
fontSize: 16,
color: Colors.LbryGreen,
},
usdConvertFaqLink: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.LbryGreen,
},
earnTipsLink: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.LbryGreen,
marginTop: 12,
marginRight: 16,
},
});
export default walletStyle;

View file

@ -436,3 +436,10 @@ export function fetchReferralCode(successCallback, errorCallback) {
export function decode(value) {
return decodeURIComponent(value).replace(/\+/g, ' ');
}
export function formatUsd(value) {
if (isNaN(parseFloat(value))) {
value = 0;
}
return '$' + parseFloat(value).toFixed(2);
}

View file

@ -4591,17 +4591,17 @@ latest-version@^3.0.0:
dependencies:
package-json "^4.0.0"
lbry-redux@lbryio/lbry-redux#84e697079968364fe526020086c8a44f4d2ef391:
lbry-redux@lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/84e697079968364fe526020086c8a44f4d2ef391"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/69ffd110dbf3633e5847f61f008751edec033017"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"
uuid "^3.3.2"
lbryinc@lbryio/lbryinc#28afb9b06c3d142bad8347939c043a21b6cb1ae1:
lbryinc@lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/28afb9b06c3d142bad8347939c043a21b6cb1ae1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/667024ebb7cb207609273174ca422cee47469270"
dependencies:
reselect "^3.0.0"