diff --git a/src/component/drawerContent/index.js b/src/component/drawerContent/index.js index b2e48fb..e4a5531 100644 --- a/src/component/drawerContent/index.js +++ b/src/component/drawerContent/index.js @@ -1,12 +1,14 @@ import { connect } from 'react-redux'; -import { doToast, 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), }); diff --git a/src/component/drawerContent/view.js b/src/component/drawerContent/view.js index 5b26c22..cf224c2 100644 --- a/src/component/drawerContent/view.js +++ b/src/component/drawerContent/view.js @@ -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': [ @@ -42,6 +44,18 @@ const routesRequiringSdkReady = [ ]; 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 = () => { const { channels = [] } = this.props; if (channels) { @@ -82,7 +96,7 @@ class DrawerContent extends React.PureComponent { }; 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; @@ -189,6 +203,15 @@ class DrawerContent extends React.PureComponent { {__(item.label)} + {item.label === 'Wallet' && this.state.usdExchangeRate > 0 && ( + ({formatUsd(parseFloat(balance) * parseFloat(this.state.usdExchangeRate))}) + )} + {item.label === 'Rewards' && this.state.usdExchangeRate > 0 && ( + + {' '} + ({formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(this.state.usdExchangeRate))}) + + )} ); diff --git a/src/component/floatingWalletBalance/index.js b/src/component/floatingWalletBalance/index.js index cd88305..29aa00a 100644 --- a/src/component/floatingWalletBalance/index.js +++ b/src/component/floatingWalletBalance/index.js @@ -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); diff --git a/src/component/rewardCard/view.js b/src/component/rewardCard/view.js index 87eceaa..eab3b70 100644 --- a/src/component/rewardCard/view.js +++ b/src/component/rewardCard/view.js @@ -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 { 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 { }; 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 { )} + {reward.reward_range && reward.reward_range.indexOf('-') > -1 && ( + {__('up to')} + )} {this.getDisplayAmount()} LBC + {usdExchangeRate > 0 && ( + + ≈{formatUsd(parseFloat(this.getDisplayAmount()) * parseFloat(usdExchangeRate))} + + )} ); diff --git a/src/component/rewardEnrolment/view.js b/src/component/rewardEnrolment/view.js index a010ca9..ff5a9f6 100644 --- a/src/component/rewardEnrolment/view.js +++ b/src/component/rewardEnrolment/view.js @@ -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 ( @@ -43,9 +44,11 @@ class RewardEnrolment extends React.Component { - {__('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'} . diff --git a/src/component/suggestedSubscriptionsGrid/view.js b/src/component/suggestedSubscriptionsGrid/view.js index a046392..d0d1e08 100644 --- a/src/component/suggestedSubscriptionsGrid/view.js +++ b/src/component/suggestedSubscriptionsGrid/view.js @@ -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={ diff --git a/src/component/walletBalance/index.js b/src/component/walletBalance/index.js index 6e136f1..9f69e65 100644 --- a/src/component/walletBalance/index.js +++ b/src/component/walletBalance/index.js @@ -6,7 +6,4 @@ const select = state => ({ balance: selectBalance(state), }); -export default connect( - select, - null -)(WalletBalance); +export default connect(select, null)(WalletBalance); diff --git a/src/component/walletBalance/view.js b/src/component/walletBalance/view.js index 3ef9bf0..f620e9a 100644 --- a/src/component/walletBalance/view.js +++ b/src/component/walletBalance/view.js @@ -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 { + 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 { {(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'} + + {this.state.usdExchangeRate > 0 && ( + + ≈{formatUsd(isNaN(balance) ? 0 : parseFloat(balance) * parseFloat(this.state.usdExchangeRate))} + + )} + ); } diff --git a/src/component/walletBalanceExtra/view.js b/src/component/walletBalanceExtra/view.js index 564ef90..067cc43 100644 --- a/src/component/walletBalanceExtra/view.js +++ b/src/component/walletBalanceExtra/view.js @@ -1,13 +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, @@ -16,25 +17,37 @@ type Props = { }; class WalletBalanceExtra extends React.PureComponent { + state = { + usdExchangeRate: 0, + }; + + componentDidMount() { + Lbryio.getExchangeRates().then(rates => { + if (!isNaN(rates.LBC_USD)) { + this.setState({ usdExchangeRate: rates.LBC_USD }); + } + }); + } + render() { - const { claimsBalance, deviceWalletSynced, supportsBalance, tipsBalance } = this.props; + const { claimsBalance, deviceWalletSynced, navigation, supportsBalance, tipsBalance } = this.props; return ( - - - {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.')} + + + You can convert your credits to USD and withdraw the converted amount using an exchange.{' '} + + . @@ -47,7 +60,18 @@ class WalletBalanceExtra extends React.PureComponent { {formatCredits(parseFloat(tipsBalance), 2)} LBC + + ≈{formatUsd(parseFloat(tipsBalance) * parseFloat(this.state.usdExchangeRate))} + {__('in tips')} + + { + navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH }); + }} + text={__('Earn more tips by uploading cool videos')} + /> @@ -66,6 +90,23 @@ class WalletBalanceExtra extends React.PureComponent { + + + + {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.')} + + + ); } diff --git a/src/page/rewards/view.js b/src/page/rewards/view.js index 7bad9ac..4edec91 100644 --- a/src/page/rewards/view.js +++ b/src/page/rewards/view.js @@ -1,5 +1,5 @@ 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 @@ -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} /> ))} @@ -211,7 +219,9 @@ class RewardsPage extends React.PureComponent { return ( - {(!this.state.isEmailVerified || !this.state.isRewardApproved) && } + {(!this.state.isEmailVerified || !this.state.isRewardApproved) && ( + + )} {this.state.isEmailVerified && this.state.isRewardApproved && ( ({ 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), }); diff --git a/src/page/subscriptions/view.js b/src/page/subscriptions/view.js index f087914..4a48f01 100644 --- a/src/page/subscriptions/view.js +++ b/src/page/subscriptions/view.js @@ -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'; @@ -31,17 +31,21 @@ 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, + usdExchangeRate: 0, userEmailVerified: false, - orderBy: ['release_time'], - filteredChannels: [], - currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting subscriptions by new }; didFocusListener; @@ -58,15 +62,25 @@ class SubscriptionsPage extends React.PureComponent { } onComponentFocused = () => { - const { currentRoute, doFetchMySubscriptions, pushDrawerStack, setPlayerVisible, user } = this.props; + const { currentRoute, doFetchMySubscriptions, pushDrawerStack, sdkReady, setPlayerVisible, user } = this.props; if (currentRoute === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) { pushDrawerStack(); } setPlayerVisible(); NativeModules.Firebase.setCurrentScreen('Subscriptions'); - this.setState({ userEmailVerified: user && user.has_verified_email }); + 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(); }; @@ -75,7 +89,7 @@ class SubscriptionsPage extends React.PureComponent { } componentWillReceiveProps(nextProps) { - const { currentRoute, user } = nextProps; + const { currentRoute, user, sdkReady } = nextProps; const { currentRoute: prevRoute, doFetchMySubscriptions } = this.props; if (Constants.DRAWER_ROUTE_SUBSCRIPTIONS === currentRoute && currentRoute !== prevRoute) { this.onComponentFocused(); @@ -88,9 +102,43 @@ class SubscriptionsPage extends React.PureComponent { }); } + 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 }); }; diff --git a/src/page/wallet/view.js b/src/page/wallet/view.js index 4996426..ed6e9a6 100644 --- a/src/page/wallet/view.js +++ b/src/page/wallet/view.js @@ -94,7 +94,7 @@ class WalletPage extends React.PureComponent { > {!rewardsNotInterested && (!balance || balance === 0) && } - + diff --git a/src/styles/reward.js b/src/styles/reward.js index f4b1178..aa5d603 100644 --- a/src/styles/reward.js +++ b/src/styles/reward.js @@ -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; diff --git a/src/styles/subscriptions.js b/src/styles/subscriptions.js index 2a3a47d..5c03cc2 100644 --- a/src/styles/subscriptions.js +++ b/src/styles/subscriptions.js @@ -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, }, diff --git a/src/styles/tag.js b/src/styles/tag.js index e028410..15bee52 100644 --- a/src/styles/tag.js +++ b/src/styles/tag.js @@ -23,7 +23,7 @@ const tagStyle = StyleSheet.create({ }, text: { fontFamily: 'Inter-Regular', - fontSize: 14, + fontSize: 12, marginRight: 8, }, tagResultsList: { diff --git a/src/styles/wallet.js b/src/styles/wallet.js index 63bc0c7..6b037df 100644 --- a/src/styles/wallet.js +++ b/src/styles/wallet.js @@ -137,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: { @@ -402,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', @@ -424,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; diff --git a/src/utils/helper.js b/src/utils/helper.js index 756826c..66d2e2a 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -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); +}