Compare commits

...

8 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
41 changed files with 805 additions and 122 deletions

@ -1 +1 @@
Subproject commit 56c375f344911bffe64c0564dd37d2bb8b7761f1 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.", "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", "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" "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"
} }

View file

@ -16,7 +16,7 @@
"@expo/vector-icons": "^8.1.0", "@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3", "gfycat-style-urls": "^1.0.3",
"lbry-redux": "lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017", "lbry-redux": "lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017",
"lbryinc": "lbryio/lbryinc#021ac75d9aa2db488cfff8e9be320402f038f955", "lbryinc": "lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270",
"lodash": ">=4.17.11", "lodash": ">=4.17.11",
"merge": ">=1.2.1", "merge": ">=1.2.1",
"moment": "^2.22.1", "moment": "^2.22.1",

View file

@ -54,7 +54,9 @@ import {
} from 'lbry-redux'; } from 'lbry-redux';
import { import {
Lbryio, Lbryio,
rewards as REWARD_TYPES,
doBlackListedOutpointsSubscribe, doBlackListedOutpointsSubscribe,
doClaimRewardType,
doFilteredOutpointsSubscribe, doFilteredOutpointsSubscribe,
doGetSync, doGetSync,
doUserCheckEmailVerified, doUserCheckEmailVerified,
@ -372,6 +374,28 @@ 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 = () => { handleSdkReady = () => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(doSetSdkReady()); dispatch(doSetSdkReady());
@ -398,6 +422,8 @@ class AppWithNavigationState extends React.Component {
} }
}); });
}); });
this.checkNewAndroidReward();
}; };
handleAccountUnlockFailed() { handleAccountUnlockFailed() {

View file

@ -1,12 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doToast, selectMyChannelClaims } from 'lbry-redux'; import { doToast, selectBalance, selectMyChannelClaims } from 'lbry-redux';
import { selectUser } from 'lbryinc'; import { selectUnclaimedRewardValue, selectUser } from 'lbryinc';
import { selectSdkReady } from 'redux/selectors/settings'; import { selectSdkReady } from 'redux/selectors/settings';
import DrawerContent from './view'; import DrawerContent from './view';
const select = state => ({ const select = state => ({
balance: selectBalance(state),
channels: selectMyChannelClaims(state), channels: selectMyChannelClaims(state),
sdkReady: selectSdkReady(state), sdkReady: selectSdkReady(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
user: selectUser(state), user: selectUser(state),
}); });

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 Icon from 'react-native-vector-icons/FontAwesome5';
import channelIconStyle from 'styles/channelIcon'; import channelIconStyle from 'styles/channelIcon';
import discoverStyle from 'styles/discover'; import discoverStyle from 'styles/discover';
import { Lbryio } from 'lbryinc';
import { formatUsd } from 'utils/helper';
const groupedMenuItems = { const groupedMenuItems = {
'Find content': [ 'Find content': [
@ -42,6 +44,18 @@ const routesRequiringSdkReady = [
]; ];
class DrawerContent extends React.PureComponent { class DrawerContent extends React.PureComponent {
state = {
usdExchangeRate: 0,
};
componentDidMount() {
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
}
getAvatarImageUrl = () => { getAvatarImageUrl = () => {
const { channels = [] } = this.props; const { channels = [] } = this.props;
if (channels) { if (channels) {
@ -82,7 +96,7 @@ class DrawerContent extends React.PureComponent {
}; };
render() { render() {
const { activeTintColor, navigation, user, onItemPress } = this.props; const { activeTintColor, balance, navigation, unclaimedRewardAmount, user, onItemPress } = this.props;
const { state } = navigation; const { state } = navigation;
const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null; const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
@ -189,6 +203,15 @@ class DrawerContent extends React.PureComponent {
</View> </View>
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}> <Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
{__(item.label)} {__(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> </Text>
</TouchableOpacity> </TouchableOpacity>
); );

View file

@ -70,7 +70,9 @@ class FileItem extends React.PureComponent {
const outpointsToHide = !blackListedOutpoints const outpointsToHide = !blackListedOutpoints
? filteredOutpoints ? filteredOutpoints
: blackListedOutpoints.concat(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) { if (shouldHide) {
// don't display blacklisted or filtered outpoints on the Your tags page // don't display blacklisted or filtered outpoints on the Your tags page

View file

@ -99,6 +99,7 @@ class FileListItem extends React.PureComponent {
isRewardContent, isRewardContent,
channelClaimId, channelClaimId,
fullChannelUri, fullChannelUri,
repostUrl,
repostChannel, repostChannel,
repostChannelUrl, repostChannelUrl,
shortChannelUri, shortChannelUri,
@ -114,12 +115,13 @@ class FileListItem extends React.PureComponent {
channelClaimId = signingChannel ? signingChannel.claim_id : null; channelClaimId = signingChannel ? signingChannel.claim_id : null;
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel; fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
shortChannelUri = signingChannel ? signingChannel.short_url : null; shortChannelUri = signingChannel ? signingChannel.short_url : null;
repostUrl = claim.repost_url;
repostChannelUrl = claim.repost_channel_url; repostChannelUrl = claim.repost_channel_url;
if (repostChannelUrl) { if (repostUrl) {
const { claimName: repostChannelName } = parseURI(repostChannelUrl); const { claimName: repostName, channelName } = parseURI(repostUrl);
repostChannel = repostChannelName; repostChannel = channelName;
} }
isRepost = !!repostChannelUrl; isRepost = !!repostUrl;
if (blackListedOutpoints || filteredOutpoints) { if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = !blackListedOutpoints const outpointsToHide = !blackListedOutpoints
@ -148,8 +150,17 @@ class FileListItem extends React.PureComponent {
<Icon name={'retweet'} size={14} style={fileListStyle.repostIcon} /> <Icon name={'retweet'} size={14} style={fileListStyle.repostIcon} />
<Text style={fileListStyle.repostChannelName}> <Text style={fileListStyle.repostChannelName}>
<Link <Link
text={repostChannel} text={`@${repostChannel}`}
onPress={() => navigateToUri(navigation, normalizeURI(repostChannelUrl), null, false, null, false)} onPress={() =>
navigateToUri(
navigation,
normalizeURI(repostChannelUrl || `@${repostChannel}`),
null,
false,
null,
false,
)
}
/>{' '} />{' '}
reposted reposted
</Text> </Text>

View file

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectBalance } from 'lbry-redux'; import { selectBalance } from 'lbry-redux';
import { selectUnclaimedRewardValue } from 'lbryinc'; import { selectUnclaimedRewardValue } from 'lbryinc';
import Constants from 'constants'; import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import FloatingWalletBalance from './view'; import FloatingWalletBalance from './view';
const select = state => ({ const select = state => ({
@ -11,7 +11,4 @@ const select = state => ({
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state), rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
}); });
export default connect( export default connect(select, null)(FloatingWalletBalance);
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 { connect } from 'react-redux';
import { doSendTip, doToast, selectBalance } from 'lbry-redux'; import { doSendTip, doToast, selectBalance } from 'lbry-redux';
import { selectSdkReady } from 'redux/selectors/settings';
import ModalTipView from './view'; import ModalTipView from './view';
const select = state => ({ const select = state => ({
balance: selectBalance(state), balance: selectBalance(state),
sdkReady: selectSdkReady(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -12,7 +14,4 @@ const perform = dispatch => ({
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)), dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
}); });
export default connect( export default connect(select, perform)(ModalTipView);
select,
perform
)(ModalTipView);

View file

@ -5,7 +5,6 @@ import modalStyle from 'styles/modal';
import modalTipStyle from 'styles/modalTip'; import modalTipStyle from 'styles/modalTip';
import Button from 'component/button'; import Button from 'component/button';
import Colors from 'styles/colors'; 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 Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link'; import Link from 'component/link';
@ -19,12 +18,22 @@ export default class ModalTipView extends React.PureComponent {
}; };
handleSendTip = () => { handleSendTip = () => {
const { claim, balance, notify, onSendTipFailed, onSendTipSuccessful, sendTip } = this.props; const { claim, balance, notify, onSendTipFailed, onSendTipSuccessful, sdkReady, sendTip } = this.props;
const { tipAmount } = this.state; 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) { if (tipAmount > balance) {
notify({ notify({
message: 'Insufficient credits', message: 'Insufficient credits',
isError: true,
}); });
return; return;
} }
@ -56,13 +65,13 @@ export default class ModalTipView extends React.PureComponent {
() => { () => {
// error // error
if (onSendTipFailed) onSendTipFailed(); if (onSendTipFailed) onSendTipFailed();
} },
) ),
); );
}, },
}, },
], ],
{ cancelable: true } { cancelable: true },
); );
}; };
@ -115,7 +124,7 @@ export default class ModalTipView extends React.PureComponent {
<Text style={modalTipStyle.infoText}> <Text style={modalTipStyle.infoText}>
{__( {__(
'This will appear as a tip for %content%, which will boost its ability to be discovered while active.', 'This will appear as a tip for %content%, which will boost its ability to be discovered while active.',
{ content: contentName } { content: contentName },
)}{' '} )}{' '}
<Link <Link
style={modalTipStyle.learnMoreLink} style={modalTipStyle.learnMoreLink}

View file

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

View file

@ -7,6 +7,7 @@ import Link from 'component/link';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
import rewardStyle from 'styles/reward'; import rewardStyle from 'styles/reward';
import { formatUsd } from '../../utils/helper';
class RewardEnrolment extends React.Component { class RewardEnrolment extends React.Component {
componentDidMount() { componentDidMount() {
@ -29,7 +30,7 @@ class RewardEnrolment extends React.Component {
}; };
render() { render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props; const { unclaimedRewardAmount, usdExchangeRate } = this.props;
return ( return (
<View style={rewardStyle.enrollContainer}> <View style={rewardStyle.enrollContainer}>
@ -43,9 +44,11 @@ class RewardEnrolment extends React.Component {
<View style={rewardStyle.onboarding}> <View style={rewardStyle.onboarding}>
<Text style={rewardStyle.enrollDescText}> <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'} {'\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'} {'\n\n'}
<Link style={rewardStyle.learnMoreLink} text={__('Learn more')} onPress={this.onLearnMorePressed} />. <Link style={rewardStyle.learnMoreLink} text={__('Learn more')} onPress={this.onLearnMorePressed} />.
</Text> </Text>

View file

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

View file

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

View file

@ -1,9 +1,9 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Image, Text, View } from 'react-native'; import { Image, Text, View } from 'react-native';
import { Lbry, formatCredits } from 'lbry-redux'; import { formatCredits } from 'lbry-redux';
import Address from 'component/address'; import { Lbryio } from 'lbryinc';
import Button from 'component/button'; import { formatUsd } from 'utils/helper';
import walletStyle from 'styles/wallet'; import walletStyle from 'styles/wallet';
type Props = { type Props = {
@ -11,6 +11,18 @@ type Props = {
}; };
class WalletBalance extends React.PureComponent<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() { render() {
const { balance } = this.props; const { balance } = this.props;
return ( return (
@ -21,6 +33,13 @@ class WalletBalance extends React.PureComponent<Props> {
<Text style={walletStyle.balance}> <Text style={walletStyle.balance}>
{(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'} {(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'}
</Text> </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>
); );
} }

View file

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

View file

@ -1,12 +1,14 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Image, Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { Lbry, formatCredits } from 'lbry-redux'; import { formatCredits } from 'lbry-redux';
import Address from 'component/address'; import { Lbryio } from 'lbryinc';
import Button from 'component/button'; import { formatUsd } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import walletStyle from 'styles/wallet'; import walletStyle from 'styles/wallet';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
type Props = { type Props = {
claimsBalance: number, claimsBalance: number,
@ -15,37 +17,96 @@ type Props = {
}; };
class WalletBalanceExtra extends React.PureComponent<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() { render() {
const { claimsBalance, supportsBalance, tipsBalance } = this.props; const { claimsBalance, deviceWalletSynced, navigation, supportsBalance, tipsBalance } = this.props;
return ( return (
<View style={walletStyle.balanceExtraCard}> <View style={walletStyle.balanceExtra}>
<View style={walletStyle.walletExtraRow}> <View style={walletStyle.usdInfoCard}>
<View style={walletStyle.walletExtraCol}> <Text style={walletStyle.usdInfoText}>
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'gift'} size={16} /> You can convert your credits to USD and withdraw the converted amount using an exchange.{' '}
<Text style={walletStyle.walletExtraCaption}>{__('You also have')}</Text> <Link
<View style={walletStyle.balanceRow}> style={walletStyle.usdConvertFaqLink}
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(tipsBalance), 2)}</Text> href={'https://lbry.com/faq/exchanges'}
<Text style={walletStyle.walletExtraCurrency}>LBC</Text> text={__('Learn more')}
</View> />
<Text style={walletStyle.text}>{__('in tips')}</Text> .
</View> </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}> <View style={walletStyle.balanceExtraCard}>
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'lock'} size={16} /> <View style={walletStyle.walletExtraRow}>
<Text style={walletStyle.walletExtraCaption}>{__('You staked')}</Text> <View style={walletStyle.walletExtraCol}>
<View style={walletStyle.balanceRow}> <Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'gift'} size={16} />
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(claimsBalance), 2)}</Text> <Text style={walletStyle.walletExtraCaption}>{__('You also have')}</Text>
<Text style={walletStyle.walletExtraCurrency}>LBC</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> </View>
<Text style={walletStyle.text}>{__('in your publishes')}</Text>
<View style={[walletStyle.balanceRow, walletStyle.walletExtraTopMargin]}> <View style={walletStyle.walletExtraCol}>
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(supportsBalance), 2)}</Text> <Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'lock'} size={16} />
<Text style={walletStyle.walletExtraCurrency}>LBC</Text> <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> </View>
<Text style={walletStyle.text}>{__('in your supports')}</Text>
</View> </View>
</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>
); );
} }

View file

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

View file

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

View file

@ -47,7 +47,7 @@ export default class ChannelCreator extends React.PureComponent {
channelNameUserEdited: false, channelNameUserEdited: false,
newChannelTitle: '', newChannelTitle: '',
newChannelName: '', newChannelName: '',
newChannelBid: 0.1, newChannelBid: 0.01,
addingChannel: false, addingChannel: false,
creatingChannel: false, creatingChannel: false,
editChannelUrl: null, editChannelUrl: null,
@ -280,7 +280,7 @@ export default class ChannelCreator extends React.PureComponent {
handleCreateCancel = () => { handleCreateCancel = () => {
const { clearChannelFormState } = this.props; const { clearChannelFormState } = this.props;
clearChannelFormState(); // explicitly clear state on cancel? 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) => { handlePickerValueChange = (itemValue, itemIndex) => {
@ -589,7 +589,7 @@ export default class ChannelCreator extends React.PureComponent {
channelNameUserEdited: false, channelNameUserEdited: false,
newChannelTitle: '', newChannelTitle: '',
newChannelName: '', newChannelName: '',
newChannelBid: 0.1, newChannelBid: 0.01,
addingChannel: false, addingChannel: false,
creatingChannel: false, creatingChannel: false,
newChannelNameError: '', newChannelNameError: '',

View file

@ -37,6 +37,7 @@ import FilePrice from 'component/filePrice';
import FloatingWalletBalance from 'component/floatingWalletBalance'; import FloatingWalletBalance from 'component/floatingWalletBalance';
import Link from 'component/link'; import Link from 'component/link';
import MediaPlayer from 'component/mediaPlayer'; import MediaPlayer from 'component/mediaPlayer';
import ModalRepostView from 'component/modalRepostView';
import ModalTipView from 'component/modalTipView'; import ModalTipView from 'component/modalTipView';
import ProgressCircle from 'react-native-progress-circle'; import ProgressCircle from 'react-native-progress-circle';
import RelatedContent from 'component/relatedContent'; import RelatedContent from 'component/relatedContent';
@ -94,6 +95,7 @@ class FilePage extends React.PureComponent {
showImageViewer: false, showImageViewer: false,
showWebView: false, showWebView: false,
showTipView: false, showTipView: false,
showRepostView: false,
playbackStarted: false, playbackStarted: false,
playerBgHeight: 0, playerBgHeight: 0,
playerHeight: 0, playerHeight: 0,
@ -265,6 +267,7 @@ class FilePage extends React.PureComponent {
'playerBgHeighht', 'playerBgHeighht',
'playerHeight', 'playerHeight',
'relatedY', 'relatedY',
'showRepostView',
'showTipView', 'showTipView',
'showImageViewer', 'showImageViewer',
'showWebView', 'showWebView',
@ -817,7 +820,16 @@ class FilePage extends React.PureComponent {
}; };
onDownloadPressed = () => { 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; const fileSize = claim && claim.value && claim.value.source ? claim.value.source.size : 0;
Alert.alert( Alert.alert(
__('Download file'), __('Download file'),
@ -1017,7 +1029,6 @@ class FilePage extends React.PureComponent {
myClaimUris, myClaimUris,
navigation, navigation,
position, position,
purchaseUri,
pushDrawerStack, pushDrawerStack,
setPlayerVisible, setPlayerVisible,
thumbnail, thumbnail,
@ -1127,7 +1138,8 @@ class FilePage extends React.PureComponent {
const channelName = signingChannel && signingChannel.name; const channelName = signingChannel && signingChannel.name;
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id; const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const fullUri = `${claim.name}#${claim.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 = const showActions =
(canEdit || (fileInfo && fileInfo.download_path)) && (canEdit || (fileInfo && fileInfo.download_path)) &&
!this.state.fullscreenMode && !this.state.fullscreenMode &&
@ -1348,6 +1360,14 @@ class FilePage extends React.PureComponent {
<Text style={filePageStyle.largeButtonText}>{__('Share')}</Text> <Text style={filePageStyle.largeButtonText}>{__('Share')}</Text>
</TouchableOpacity> </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 <TouchableOpacity
style={filePageStyle.largeButton} style={filePageStyle.largeButton}
onPress={() => this.setState({ showTipView: true })} onPress={() => this.setState({ showTipView: true })}
@ -1522,7 +1542,17 @@ class FilePage extends React.PureComponent {
onSendTipSuccessful={() => this.setState({ showTipView: false })} 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.fullscreenMode &&
!this.state.showRepostView &&
!this.state.showTipView && !this.state.showTipView &&
!this.state.showImageViewer && !this.state.showImageViewer &&
!this.state.showWebView && <FloatingWalletBalance navigation={navigation} />} !this.state.showWebView && <FloatingWalletBalance navigation={navigation} />}

View file

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

View file

@ -121,7 +121,7 @@ class PublishPage extends React.PureComponent {
// input data // input data
hasEditedContentAddress: false, hasEditedContentAddress: false,
bid: 0.1, bid: 0.01,
description: null, description: null,
title: null, title: null,
language: 'en', language: 'en',

View file

@ -194,7 +194,7 @@ class PublishesPage extends React.PureComponent {
}); });
} else { } else {
// TODO: when shortUrl is available for my claims, navigate to that URL instead // TODO: when shortUrl is available for my claims, navigate to that URL instead
navigateToUri(navigation, item); navigateToUri(navigation, claim.permanent_url);
} }
} }
}} }}

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbryio } from 'lbryinc';
import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native'; import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
@ -17,13 +17,14 @@ const FILTER_CLAIMED = 'claimed';
class RewardsPage extends React.PureComponent { class RewardsPage extends React.PureComponent {
state = { state = {
currentFilter: FILTER_AVAILABLE,
firstRewardClaimed: false,
isEmailVerified: false, isEmailVerified: false,
isIdentityVerified: false, isIdentityVerified: false,
isRewardApproved: false, isRewardApproved: false,
verifyRequestStarted: false,
revealVerification: true, revealVerification: true,
firstRewardClaimed: false, usdExchangeRate: 0,
currentFilter: FILTER_AVAILABLE, verifyRequestStarted: false,
}; };
scrollView = null; scrollView = null;
@ -48,6 +49,12 @@ class RewardsPage extends React.PureComponent {
setPlayerVisible(); setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Rewards'); NativeModules.Firebase.setCurrentScreen('Rewards');
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
fetchRewards(); fetchRewards();
this.setState({ this.setState({
@ -158,6 +165,7 @@ class RewardsPage extends React.PureComponent {
canClaim={!isNotEligible} canClaim={!isNotEligible}
reward={reward} reward={reward}
reward_type={reward.reward_type} reward_type={reward.reward_type}
usdExchangeRate={this.state.usdExchangeRate}
/> />
))} ))}
<CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} /> <CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} />
@ -211,7 +219,9 @@ class RewardsPage extends React.PureComponent {
return ( return (
<View style={rewardStyle.container}> <View style={rewardStyle.container}>
<UriBar navigation={navigation} /> <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 && ( {this.state.isEmailVerified && this.state.isRewardApproved && (
<ScrollView <ScrollView

View file

@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import { SETTINGS, doUpdateBlockHeight, doPopulateSharedUserState, doToast } from 'lbry-redux'; import { SETTINGS, doUpdateBlockHeight, doPopulateSharedUserState, doToast } from 'lbry-redux';
import { import {
doAuthenticate, doAuthenticate,
doClaimRewardType,
doInstallNewWithParams, doInstallNewWithParams,
doFetchMySubscriptions, doFetchMySubscriptions,
doFetchRewardedContent, doFetchRewardedContent,

View file

@ -159,6 +159,7 @@ class SplashScreen extends React.PureComponent {
this.getUserSettings(); this.getUserSettings();
}); });
}); });
this.navigateToMain(); this.navigateToMain();
return; return;
} }
@ -322,6 +323,7 @@ class SplashScreen extends React.PureComponent {
componentDidMount() { componentDidMount() {
NativeModules.Firebase.track('app_launch', null); NativeModules.Firebase.track('app_launch', null);
NativeModules.Firebase.setCurrentScreen('Splash'); NativeModules.Firebase.setCurrentScreen('Splash');
NativeModules.UtilityModule.checkSdkReady();
const { navigation } = this.props; const { navigation } = this.props;
const { resetUrl } = navigation.state.params; const { resetUrl } = navigation.state.params;

View file

@ -12,6 +12,8 @@ import {
selectViewMode, selectViewMode,
selectFirstRunCompleted, selectFirstRunCompleted,
selectShowSuggestedSubs, selectShowSuggestedSubs,
selectUnclaimedRewardValue,
selectUser,
} from 'lbryinc'; } from 'lbryinc';
import { doToast, selectFetchingClaimSearch } from 'lbry-redux'; import { doToast, selectFetchingClaimSearch } from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
@ -32,9 +34,12 @@ const select = state => ({
unreadSubscriptions: selectUnreadSubscriptions(state), unreadSubscriptions: selectUnreadSubscriptions(state),
viewMode: selectViewMode(state), viewMode: selectViewMode(state),
firstRunCompleted: selectFirstRunCompleted(state), firstRunCompleted: selectFirstRunCompleted(state),
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
showSuggestedSubs: selectShowSuggestedSubs(state), showSuggestedSubs: selectShowSuggestedSubs(state),
timeItem: selectTimeItem(state), timeItem: selectTimeItem(state),
sdkReady: selectSdkReady(state), sdkReady: selectSdkReady(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
user: selectUser(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -11,7 +11,7 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
import { buildURI, parseURI } from 'lbry-redux'; 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 AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment'; import moment from 'moment';
import Button from 'component/button'; import Button from 'component/button';
@ -31,16 +31,21 @@ import SuggestedSubscriptions from 'component/suggestedSubscriptions';
import SuggestedSubscriptionsGrid from 'component/suggestedSubscriptionsGrid'; import SuggestedSubscriptionsGrid from 'component/suggestedSubscriptionsGrid';
import UriBar from 'component/uriBar'; import UriBar from 'component/uriBar';
import SdkLoadingStatus from 'component/sdkLoadingStatus'; import SdkLoadingStatus from 'component/sdkLoadingStatus';
import Snackbar from 'react-native-snackbar';
import { Lbryio } from 'lbryinc';
class SubscriptionsPage extends React.PureComponent { class SubscriptionsPage extends React.PureComponent {
state = { 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, showingSuggestedSubs: false,
showSortPicker: false, showSortPicker: false,
showTimePicker: false, showTimePicker: false,
showModalSuggestedSubs: false, showModalSuggestedSubs: false,
orderBy: ['release_time'], usdExchangeRate: 0,
filteredChannels: [], userEmailVerified: false,
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting subscriptions by new
}; };
didFocusListener; didFocusListener;
@ -57,15 +62,7 @@ class SubscriptionsPage extends React.PureComponent {
} }
onComponentFocused = () => { onComponentFocused = () => {
const { const { currentRoute, doFetchMySubscriptions, pushDrawerStack, sdkReady, setPlayerVisible, user } = this.props;
currentRoute,
doFetchMySubscriptions,
doFetchRecommendedSubscriptions,
doSetViewMode,
pushDrawerStack,
setPlayerVisible,
subscriptionsViewMode,
} = this.props;
if (currentRoute === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) { if (currentRoute === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) {
pushDrawerStack(); pushDrawerStack();
@ -73,6 +70,17 @@ class SubscriptionsPage extends React.PureComponent {
setPlayerVisible(); setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Subscriptions'); 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(); doFetchMySubscriptions();
}; };
@ -81,15 +89,56 @@ class SubscriptionsPage extends React.PureComponent {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { currentRoute } = nextProps; const { currentRoute, user, sdkReady } = nextProps;
const { currentRoute: prevRoute } = this.props; const { currentRoute: prevRoute, doFetchMySubscriptions } = this.props;
if (Constants.DRAWER_ROUTE_SUBSCRIPTIONS === currentRoute && currentRoute !== prevRoute) { if (Constants.DRAWER_ROUTE_SUBSCRIPTIONS === currentRoute && currentRoute !== prevRoute) {
this.onComponentFocused(); 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(); 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 => { handleSortByItemSelected = item => {
this.setState({ currentSortByItem: item, orderBy: getOrderBy(item), showSortPicker: false }); this.setState({ currentSortByItem: item, orderBy: getOrderBy(item), showSortPicker: false });
}; };

View file

@ -94,7 +94,7 @@ class WalletPage extends React.PureComponent {
> >
{!rewardsNotInterested && (!balance || balance === 0) && <WalletRewardsDriver navigation={navigation} />} {!rewardsNotInterested && (!balance || balance === 0) && <WalletRewardsDriver navigation={navigation} />}
<WalletBalance /> <WalletBalance />
<WalletBalanceExtra /> <WalletBalanceExtra navigation={navigation} />
<WalletAddress /> <WalletAddress />
<WalletSend /> <WalletSend />
<TransactionListRecent navigation={navigation} /> <TransactionListRecent navigation={navigation} />

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

View file

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

View file

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

View file

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

View file

@ -103,10 +103,12 @@ const walletStyle = StyleSheet.create({
marginLeft: 16, marginLeft: 16,
marginRight: 16, marginRight: 16,
}, },
balanceExtraCard: { balanceExtra: {
backgroundColor: Colors.White,
marginLeft: 16, marginLeft: 16,
marginRight: 16, marginRight: 16,
},
balanceExtraCard: {
backgroundColor: Colors.White,
padding: 16, padding: 16,
}, },
balanceBackground: { balanceBackground: {
@ -135,6 +137,13 @@ const walletStyle = StyleSheet.create({
fontFamily: 'Inter-Bold', fontFamily: 'Inter-Bold',
fontSize: 36, fontSize: 36,
marginLeft: 16, marginLeft: 16,
},
usdBalance: {
color: Colors.White,
fontFamily: 'Inter-Regular',
fontSize: 20,
marginLeft: 16,
marginTop: 2,
marginBottom: 16, marginBottom: 16,
}, },
balanceFocus: { balanceFocus: {
@ -252,6 +261,22 @@ const walletStyle = StyleSheet.create({
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: Colors.PageBackground, 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: { syncDriverLink: {
color: Colors.LbryGreen, color: Colors.LbryGreen,
fontFamily: 'Inter-Regular', fontFamily: 'Inter-Regular',
@ -384,6 +409,11 @@ const walletStyle = StyleSheet.create({
fontFamily: 'Inter-SemiBold', fontFamily: 'Inter-SemiBold',
fontSize: 28, fontSize: 28,
}, },
usdWalletExtraBalance: {
fontFamily: 'Inter-Regular',
fontSize: 16,
color: Colors.DescriptionGrey,
},
balanceRow: { balanceRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -406,6 +436,34 @@ const walletStyle = StyleSheet.create({
left: 0, left: 0,
top: 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; export default walletStyle;

View file

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

View file

@ -4599,9 +4599,9 @@ lbry-redux@lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017:
reselect "^3.0.0" reselect "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
lbryinc@lbryio/lbryinc#021ac75d9aa2db488cfff8e9be320402f038f955: lbryinc@lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/021ac75d9aa2db488cfff8e9be320402f038f955" resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/667024ebb7cb207609273174ca422cee47469270"
dependencies: dependencies:
reselect "^3.0.0" reselect "^3.0.0"