additional rewards page changes and new verification flow #542

Merged
akinwale merged 9 commits from rewards-rework into master 2019-05-15 12:00:15 +02:00
29 changed files with 893 additions and 65 deletions

View file

@ -11,6 +11,7 @@ import SettingsPage from 'page/settings';
import SplashScreen from 'page/splash'; import SplashScreen from 'page/splash';
import SubscriptionsPage from 'page/subscriptions'; import SubscriptionsPage from 'page/subscriptions';
import TransactionHistoryPage from 'page/transactionHistory'; import TransactionHistoryPage from 'page/transactionHistory';
import VerificationScreen from 'page/verification';
import WalletPage from 'page/wallet'; import WalletPage from 'page/wallet';
import SearchInput from 'component/searchInput'; import SearchInput from 'component/searchInput';
import { import {
@ -212,10 +213,19 @@ const mainStackNavigator = new createStackNavigator({
}, },
Main: { Main: {
screen: drawer screen: drawer
},
Verification: {
screen: VerificationScreen,
navigationOptions: {
drawerLockMode: 'locked-closed'
}
} }
}, { }, {
headerMode: 'none' headerMode: 'none'
}); });
export const AppNavigator = mainStackNavigator; export const AppNavigator = mainStackNavigator;
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware( export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
state => state.nav, state => state.nav,

View file

@ -1,9 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doFetchBlock, makeSelectBlockDate } from 'lbry-redux'; import { makeSelectDateForUri } from 'lbry-redux';
import DateTime from './view'; import DateTime from './view';
const select = (state, props) => ({ const select = (state, props) => ({
date: !props.date && props.block ? makeSelectBlockDate(props.block)(state) : props.date, date: props.date || makeSelectDateForUri(props.uri)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -90,7 +90,7 @@ class FileItem extends React.PureComponent {
<Link style={discoverStyle.channelName} text={channelName} onPress={() => { <Link style={discoverStyle.channelName} text={channelName} onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri)); navigateToUri(navigation, normalizeURI(fullChannelUri));
}} />} }} />}
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo block={height} /> <DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
</View>} </View>}
</TouchableOpacity> </TouchableOpacity>
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />} {obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}

View file

@ -107,7 +107,7 @@ class FileListItem extends React.PureComponent {
<View style={fileListStyle.info}> <View style={fileListStyle.info}>
{fileInfo && <Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>} {fileInfo && <Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>}
<DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo block={height} /> <DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} />
</View> </View>
{fileInfo && {fileInfo &&

View file

@ -1,11 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectTotalBalance } from 'lbry-redux'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectBalance } from 'lbry-redux';
import { selectUnclaimedRewardValue } from 'lbryinc'; import { selectUnclaimedRewardValue } from 'lbryinc';
import Constants from 'constants';
import FloatingWalletBalance from './view'; import FloatingWalletBalance from './view';
const select = state => ({ const select = state => ({
balance: selectTotalBalance(state), balance: selectBalance(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state), unclaimedRewardAmount: selectUnclaimedRewardValue(state),
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
}); });
export default connect(select, null)(FloatingWalletBalance); export default connect(select, null)(FloatingWalletBalance);

View file

@ -14,11 +14,11 @@ type Props = {
class FloatingWalletBalance extends React.PureComponent<Props> { class FloatingWalletBalance extends React.PureComponent<Props> {
render() { render() {
const { balance, navigation, unclaimedRewardAmount } = this.props; const { balance, navigation, rewardsNotInterested, unclaimedRewardAmount } = this.props;
return ( return (
<View style={[floatingButtonStyle.view, floatingButtonStyle.bottomRight]}> <View style={[floatingButtonStyle.view, floatingButtonStyle.bottomRight]}>
{unclaimedRewardAmount > 0 && {(!rewardsNotInterested && unclaimedRewardAmount > 0) &&
<TouchableOpacity style={floatingButtonStyle.pendingContainer} <TouchableOpacity style={floatingButtonStyle.pendingContainer}
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })} > onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })} >
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} /> <Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />

View file

@ -72,16 +72,7 @@ class RewardCard extends React.PureComponent<Props> {
} }
}}> }}>
<View style={rewardStyle.leftCol}> <View style={rewardStyle.leftCol}>
{!isPending && <TouchableOpacity onPress={() => {
if (!claimed) {
this.onClaimPress();
}
}}>
<Icon name={claimed ? "check-circle" : "circle"}
style={claimed ? rewardStyle.claimed : (canClaim ? rewardStyle.unclaimed : rewardStyle.disabled)}
size={20} />
</TouchableOpacity>}
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
</View> </View>
<View style={rewardStyle.midCol}> <View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text> <Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text>
@ -92,6 +83,16 @@ class RewardCard extends React.PureComponent<Props> {
error={'The transaction URL could not be opened'} />} error={'The transaction URL could not be opened'} />}
</View> </View>
<View style={rewardStyle.rightCol}> <View style={rewardStyle.rightCol}>
{!isPending && <TouchableOpacity onPress={() => {
if (!claimed) {
this.onClaimPress();
}
}}>
<Icon name={claimed ? "check-circle" : "circle"}
style={claimed ? rewardStyle.claimed : (canClaim ? rewardStyle.unclaimed : rewardStyle.disabled)}
size={20} />
</TouchableOpacity>}
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
<Text style={rewardStyle.rewardAmount}>{reward.reward_amount}</Text> <Text style={rewardStyle.rewardAmount}>{reward.reward_amount}</Text>
<Text style={rewardStyle.rewardCurrency}>LBC</Text> <Text style={rewardStyle.rewardCurrency}>LBC</Text>
</View> </View>

View file

@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { doRewardList, selectUnclaimedRewardValue, selectFetchingRewards, selectUser } from 'lbryinc';
import RewardEnrolment from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
user: selectUser(state)
});
const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
});
export default connect(select, perform)(RewardEnrolment);

View file

@ -0,0 +1,53 @@
import React from 'react';
import { NativeModules, Text, TouchableOpacity, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Constants from 'constants';
import Link from 'component/link';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import rewardStyle from 'styles/reward';
class RewardEnrolment extends React.Component {
componentDidMount() {
this.props.fetchRewards();
}
onNotInterestedPressed = () => {
const { navigation, setClientSetting } = this.props;
setClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED, true);
navigation.navigate({ routeName: 'DiscoverStack' });
}
onEnrollPressed = () => {
const { navigation } = this.props;
navigation.navigate({ routeName: 'Verification' })
}
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
return (
<View style={rewardStyle.enrollContainer} onPress>
<View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}>
{unclaimedRewardAmount} unclaimed credits
</Text>
</View>
<View style={rewardStyle.onboarding}>
<Text style={rewardStyle.enrollDescText}>LBRY credits allow you to purchase content, publish content, and influence the network. You can start earning credits by watching videos on LBRY.</Text>
</View>
<View style={rewardStyle.buttonRow}>
<Link style={rewardStyle.notInterestedLink} text={"Not interested"} onPress={this.onNotInterestedPressed} />
<Button style={rewardStyle.enrollButton} theme={"light"} text={"Enroll"} onPress={this.onEnrollPressed} />
</View>
</View>
);
}
}
export default RewardEnrolment;

View file

@ -1,9 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectTotalBalance } from 'lbry-redux'; import { selectBalance } from 'lbry-redux';
import WalletBalance from './view'; import WalletBalance from './view';
const select = state => ({ const select = state => ({
balance: selectTotalBalance(state), balance: selectBalance(state),
}); });
export default connect(select, null)(WalletBalance); export default connect(select, null)(WalletBalance);

View file

@ -0,0 +1,6 @@
import { connect } from 'react-redux';
import WalletRewardsDriver from './view';
const select = state => ({});
export default connect(select, null)(WalletRewardsDriver);

View file

@ -0,0 +1,17 @@
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import walletStyle from 'styles/wallet';
class WalletRewardsDriver extends React.PureComponent<Props> {
render() {
const { navigation } = this.props;
return (
<TouchableOpacity style={walletStyle.rewardDriverCard} onPress={() => navigation.navigate('Rewards')}>
<Text style={walletStyle.rewardDriverText}>Earn credits while using the LBRY app. Tap to get started.</Text>
</TouchableOpacity>
);
}
}
export default WalletRewardsDriver;

View file

@ -4,6 +4,12 @@ const Constants = {
FIRST_RUN_PAGE_WALLET: "wallet", FIRST_RUN_PAGE_WALLET: "wallet",
FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account", FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account",
VERIFY_PAGE_EMAIL: "email-verify",
VERIFY_PAGE_PHONE_NUMBER: "phone-number-verify",
PHASE_COLLECTION: "collection",
PHASE_VERIFICATION: "verification",
CONTENT_TAB: "content", CONTENT_TAB: "content",
ABOUT_TAB: "about", ABOUT_TAB: "about",
@ -16,6 +22,8 @@ const Constants = {
SETTING_SUBSCRIPTIONS_VIEW_MODE: "subscriptionsViewMode", SETTING_SUBSCRIPTIONS_VIEW_MODE: "subscriptionsViewMode",
SETTING_RATING_REMINDER_LAST_SHOWN: "ratingReminderLastShown", SETTING_RATING_REMINDER_LAST_SHOWN: "ratingReminderLastShown",
SETTING_RATING_REMINDER_DISABLED: "ratingReminderDisabled", SETTING_RATING_REMINDER_DISABLED: "ratingReminderDisabled",
SETTING_BACKUP_DISMISSED: "backupDismissed",
SETTING_REWARDS_NOT_INTERESTED: "rewardsNotInterested",
ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS", ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS",
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED", ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",

View file

@ -601,7 +601,7 @@ class FilePage extends React.PureComponent {
<DateTime <DateTime
style={filePageStyle.publishDate} style={filePageStyle.publishDate}
textStyle={filePageStyle.publishDateText} textStyle={filePageStyle.publishDateText}
block={height} uri={uri}
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }} formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
show={DateTime.SHOW_DATE} /> show={DateTime.SHOW_DATE} />
</View> </View>

View file

@ -1,5 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
doClaimRewardType,
doRewardList, doRewardList,
selectEmailVerifyErrorMessage, selectEmailVerifyErrorMessage,
selectEmailVerifyIsPending, selectEmailVerifyIsPending,
@ -23,6 +24,7 @@ const select = state => ({
}); });
const perform = dispatch => ({ const perform = dispatch => ({
claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
fetchRewards: () => dispatch(doRewardList()), fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data)), notify: data => dispatch(doToast(data)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_REWARDS)) pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_REWARDS))

View file

@ -14,6 +14,7 @@ import PhoneNumberRewardSubcard from 'component/phoneNumberRewardSubcard';
import EmailRewardSubcard from 'component/emailRewardSubcard'; import EmailRewardSubcard from 'component/emailRewardSubcard';
import PageHeader from 'component/pageHeader'; import PageHeader from 'component/pageHeader';
import RewardCard from 'component/rewardCard'; import RewardCard from 'component/rewardCard';
import RewardEnrolment from 'component/rewardEnrolment';
import RewardSummary from 'component/rewardSummary'; import RewardSummary from 'component/rewardSummary';
import UriBar from 'component/uriBar'; import UriBar from 'component/uriBar';
import rewardStyle from 'styles/reward'; import rewardStyle from 'styles/reward';
@ -24,7 +25,8 @@ class RewardsPage extends React.PureComponent {
isIdentityVerified: false, isIdentityVerified: false,
isRewardApproved: false, isRewardApproved: false,
verifyRequestStarted: false, verifyRequestStarted: false,
revealVerification: false revealVerification: true,
firstRewardClaimed: false
}; };
scrollView = null; scrollView = null;
@ -43,7 +45,8 @@ class RewardsPage extends React.PureComponent {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { emailVerifyErrorMessage, emailVerifyPending, user } = nextProps; const { emailVerifyErrorMessage, emailVerifyPending, rewards, user } = nextProps;
const { claimReward } = this.props;
if (emailVerifyPending) { if (emailVerifyPending) {
this.setState({ verifyRequestStarted: true }); this.setState({ verifyRequestStarted: true });
} }
@ -63,6 +66,17 @@ class RewardsPage extends React.PureComponent {
isRewardApproved: (user && user.is_reward_approved) isRewardApproved: (user && user.is_reward_approved)
}); });
} }
if (rewards && rewards.length && this.state.isRewardApproved && !this.state.firstRewardClaimed) {
// claim new_user and new_mobile rewards
for (let i = 0; i < rewards.length; i++) {
const { reward_type: type } = rewards[i];
if ('new_user' === type || 'new_mobile' === type) {
claimReward(rewards[i]);
}
}
this.setState({ firstRewardClaimed: true });
}
} }
renderVerification() { renderVerification() {
@ -70,23 +84,12 @@ class RewardsPage extends React.PureComponent {
return null; return null;
} }
if (!this.state.isEmailVerified || !this.state.isIdentityVerified) {
return (
<View style={[rewardStyle.card, rewardStyle.verification]}>
<Text style={rewardStyle.title}>Humans Only</Text>
<Text style={rewardStyle.text}>Rewards are for human beings only. You'll have to prove you're one of us before you can claim any rewards.</Text>
{!this.state.isEmailVerified && <EmailRewardSubcard />}
{this.state.isEmailVerified && !this.state.isIdentityVerified && <PhoneNumberRewardSubcard />}
</View>
);
}
if (this.state.isEmailVerified && this.state.isIdentityVerified && !this.state.isRewardApproved) { if (this.state.isEmailVerified && this.state.isIdentityVerified && !this.state.isRewardApproved) {
return ( return (
<View style={[rewardStyle.card, rewardStyle.verification]}> <View style={[rewardStyle.card, rewardStyle.verification]}>
<Text style={rewardStyle.title}>Manual Reward Verification</Text> <Text style={rewardStyle.title}>Manual Reward Verification</Text>
<Text style={rewardStyle.text}> <Text style={rewardStyle.text}>
You need to be manually verified before you can start claiming rewards. Please request to be verified on the <Link style={rewardStyle.textLink} href="https://discordapp.com/invite/Z3bERWA" text="LBRY Discord server" />. You need to be manually verified before you can start claiming rewards. Please request to be verified on the <Link style={rewardStyle.greenLink} href="https://discordapp.com/invite/Z3bERWA" text="LBRY Discord server" />.
</Text> </Text>
</View> </View>
); );
@ -153,16 +156,18 @@ class RewardsPage extends React.PureComponent {
return ( return (
<View style={rewardStyle.container}> <View style={rewardStyle.container}>
<UriBar navigation={navigation} /> <UriBar navigation={navigation} />
<ScrollView {(!this.state.isEmailVerified || !this.state.isIdentityVerified || !this.state.isRewardApproved) &&
ref={ref => this.scrollView = ref} <RewardEnrolment navigation={navigation} />}
keyboardShouldPersistTaps={'handled'}
style={rewardStyle.scrollContainer} {(this.state.isEmailVerified && this.state.isIdentityVerified && this.state.isRewardApproved) &&
contentContainerStyle={rewardStyle.scrollContentContainer}> <ScrollView
<RewardSummary navigation={navigation} showVerification={this.showVerification} /> ref={ref => this.scrollView = ref}
{this.state.revealVerification && this.renderVerification()} keyboardShouldPersistTaps={'handled'}
{this.renderUnclaimedRewards()} style={rewardStyle.scrollContainer}
{this.renderClaimedRewards()} contentContainerStyle={rewardStyle.scrollContentContainer}>
</ScrollView> {this.renderUnclaimedRewards()}
{this.renderClaimedRewards()}
</ScrollView>}
</View> </View>
); );
} }

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doTotalBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux'; import { doBalanceSubscribe, doUpdateBlockHeight, doToast } from 'lbry-redux';
import { import {
doAuthenticate, doAuthenticate,
doBlackListedOutpointsSubscribe, doBlackListedOutpointsSubscribe,
@ -23,7 +23,7 @@ const select = state => ({
const perform = dispatch => ({ const perform = dispatch => ({
authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)), authenticate: (appVersion, os) => dispatch(doAuthenticate(appVersion, os)),
totalBalanceSubscribe: () => dispatch(doTotalBalanceSubscribe()), balanceSubscribe: () => dispatch(doBalanceSubscribe()),
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()), blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()), checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()), deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),

View file

@ -137,7 +137,7 @@ class SplashScreen extends React.PureComponent {
finishSplashScreen = () => { finishSplashScreen = () => {
const { const {
authenticate, authenticate,
totalBalanceSubscribe, balanceSubscribe,
blacklistedOutpointsSubscribe, blacklistedOutpointsSubscribe,
checkSubscriptionsInit, checkSubscriptionsInit,
updateBlockHeight, updateBlockHeight,
@ -147,7 +147,7 @@ class SplashScreen extends React.PureComponent {
Lbry.resolve({ urls: 'lbry://one' }).then(() => { Lbry.resolve({ urls: 'lbry://one' }).then(() => {
// Leave the splash screen // Leave the splash screen
totalBalanceSubscribe(); balanceSubscribe();
blacklistedOutpointsSubscribe(); blacklistedOutpointsSubscribe();
checkSubscriptionsInit(); checkSubscriptionsInit();
updateBlockHeight(); updateBlockHeight();

View file

@ -0,0 +1,42 @@
import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
import {
doUserEmailNew,
doUserEmailToVerify,
doUserResendVerificationEmail,
doUserPhoneNew,
doUserPhoneVerify,
selectPhoneNewErrorMessage,
selectPhoneNewIsPending,
selectPhoneToVerify,
selectPhoneVerifyIsPending,
selectPhoneVerifyErrorMessage,
selectEmailNewErrorMessage,
selectEmailNewIsPending,
selectEmailToVerify,
selectUser,
} from 'lbryinc';
import Verification from './view';
const select = (state) => ({
emailToVerify: selectEmailToVerify(state),
emailNewErrorMessage: selectEmailNewErrorMessage(state),
emailNewPending: selectEmailNewIsPending(state),
user: selectUser(state),
phoneVerifyErrorMessage: selectPhoneVerifyErrorMessage(state),
phoneVerifyIsPending: selectPhoneVerifyIsPending(state),
phone: selectPhoneToVerify(state),
phoneNewErrorMessage: selectPhoneNewErrorMessage(state),
phoneNewIsPending: selectPhoneNewIsPending(state),
});
const perform = dispatch => ({
addUserEmail: email => dispatch(doUserEmailNew(email)),
addUserPhone: (phone, country_code) => dispatch(doUserPhoneNew(phone, country_code)),
verifyPhone: (verificationCode) => dispatch(doUserPhoneVerify(verificationCode)),
notify: data => dispatch(doToast(data)),
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
});
export default connect(select, perform)(Verification);

View file

@ -0,0 +1,132 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import { ActivityIndicator, View, Text, TextInput } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Link from 'component/link';
import Colors from 'styles/colors';
import Constants from 'constants';
import firstRunStyle from 'styles/firstRun';
import rewardStyle from 'styles/reward';
class EmailVerifyPage extends React.PureComponent {
state = {
email: null,
phase: Constants.PHASE_COLLECTION,
placeholder: 'you@example.com',
verifyStarted: false,
previousEmail: null
}
handleChangeText = (text) => {
this.setState({ email: text });
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
}
componentWillReceiveProps(nextProps) {
const { emailNewErrorMessage, emailNewPending, emailToVerify } = nextProps;
const { notify } = this.props;
if (this.state.verifyStarted && !emailNewPending) {
if (emailNewErrorMessage) {
notify({ message: String(emailNewErrorMessage), isError: true });
this.setState({ verifyStarted: false });
} else {
this.setState({ phase: Constants.PHASE_VERIFICATION });
//notify({ message: 'Please follow the instructions in the email sent to your address to continue.' });
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
}
}
}
onSendVerificationPressed = () => {
const { addUserEmail, emailNewPending, notify, resendVerificationEmail } = this.props;
if (emailNewPending) {
return;
}
const { email } = this.state;
if (!email || email.trim().length === 0 || email.indexOf('@') === -1) {
return notify({
message: 'Please provide a valid email address to continue.',
});
}
if (this.state.previousEmail === this.state.email) {
// resend
resendVerificationEmail(this.state.email);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
this.setState({ verifyStarted: true, phase: Constants.PHASE_VERIFICATION });
return;
}
this.setState({ verifyStarted: true });
addUserEmail(email);
}
onResendPressed = () => {
const { resendVerificationEmail, notify } = this.props;
// resend verification email if there was one previously set (and it wasn't changed)
resendVerificationEmail(this.state.email);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
notify({ message: 'Please follow the instructions in the email sent to your address to continue.' });
}
onEditPressed = () => {
this.setState({ verifyStarted: false, phase: Constants.PHASE_COLLECTION, previousEmail: this.state.email });
}
render() {
const { emailNewPending } = this.props;
return (
<View style={firstRunStyle.container}>
<Text style={rewardStyle.verificationTitle}>{(Constants.PHASE_COLLECTION === this.state.phase) ? 'Email' : 'Verify Email'}</Text>
{(Constants.PHASE_COLLECTION === this.state.phase) &&
<View>
<Text style={firstRunStyle.paragraph}>Please provide an email address.</Text>
<TextInput style={firstRunStyle.emailInput}
placeholder={this.state.placeholder}
underlineColorAndroid="transparent"
value={this.state.email}
onChangeText={text => this.handleChangeText(text)}
onFocus={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: '' });
}
}}
onBlur={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: 'you@example.com' });
}
}}
/>
<View style={rewardStyle.buttonContainer}>
{!this.state.verifyStarted &&
<Button
style={rewardStyle.verificationButton}
theme={"light"}
text={"Send verification email"}
onPress={this.onSendVerificationPressed} />}
{this.state.verifyStarted && emailNewPending &&
<ActivityIndicator size={"small"} color={Colors.White} style={rewardStyle.loading} />}
</View>
</View>}
{(Constants.PHASE_VERIFICATION === this.state.phase) &&
<View>
<Text style={firstRunStyle.paragraph}>An email has been sent to {this.state.email}. Please follow the instructions in the message to verify your email address.</Text>
<View style={rewardStyle.buttonContainer}>
<Button style={rewardStyle.verificationButton} theme={"light"} text={"Resend"} onPress={this.onResendPressed} />
<Link style={rewardStyle.verificationLink} text={"Edit"} onPress={this.onEditPressed} />
</View>
</View>
}
</View>
);
}
}
export default EmailVerifyPage;

View file

@ -0,0 +1,23 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import { ActivityIndicator, View, Text, TextInput } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Link from 'component/link';
import Colors from 'styles/colors';
import Constants from 'constants';
import firstRunStyle from 'styles/firstRun';
import rewardStyle from 'styles/reward';
class ManualVerifyPage extends React.PureComponent {
render() {
return (
<View style={firstRunStyle.container}>
<Text style={rewardStyle.verificationTitle}>Manual Reward Verification</Text>
<Text style={firstRunStyle.paragraph}>You need to be manually verified before you can start claiming rewards. Please request to be verified on the <Link style={rewardStyle.underlinedTextLink} href="https://discordapp.com/invite/Z3bERWA" text="LBRY Discord server" />.</Text>
</View>
);
}
}
export default ManualVerifyPage;

View file

@ -0,0 +1,235 @@
// @flow
import React from 'react';
import {
ActivityIndicator,
DeviceEventEmitter,
NativeModules,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants';
import CountryPicker from 'react-native-country-picker-modal';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import PhoneInput from 'react-native-phone-input';
import firstRunStyle from 'styles/firstRun';
import rewardStyle from 'styles/reward';
class PhoneVerifyPage extends React.PureComponent {
phoneInput = null;
picker = null;
constructor(props) {
super(props);
this.state = {
canReceiveSms: false,
cca2: 'US',
codeVerifyStarted: false,
codeVerifySuccessful: false,
countryCode: null,
newPhoneAdded: false,
number: null,
phoneVerifyFailed: false,
verificationCode: null,
phase: Constants.PHASE_COLLECTION
};
}
componentDidMount() {
const { phone } = this.props;
if (phone && String(phone).trim().length > 0) {
this.setState({ newPhoneAdded: true, phase: Constants.PHASE_VERIFICATION });
}
}
componentDidUpdate(prevProps) {
const {
phoneVerifyIsPending,
phoneVerifyErrorMessage,
notify,
phoneNewErrorMessage,
phoneNewIsPending,
onPhoneVerifySuccessful
} = this.props;
if (!phoneNewIsPending && (phoneNewIsPending !== prevProps.phoneNewIsPending)) {
if (phoneNewErrorMessage) {
notify({ message: String(phoneNewErrorMessage) });
this.setState({ phoneVerifyFailed: true });
} else {
this.setState({ newPhoneAdded: true, phase: Constants.PHASE_VERIFICATION, phoneVerifyFailed: false });
}
}
if (!phoneVerifyIsPending && (phoneVerifyIsPending !== prevProps.phoneVerifyIsPending)) {
if (phoneVerifyErrorMessage) {
notify({ message: String(phoneVerifyErrorMessage) });
this.setState({ codeVerifyStarted: false, phoneVerifyFailed: true });
} else {
notify({ message: 'Your phone number was successfully verified.' });
this.setState({ codeVerifySuccessful: true, phoneVerifyFailed: false });
if (onPhoneVerifySuccessful) {
onPhoneVerifySuccessful();
}
}
}
}
onEditPressed = () => {
this.setState({ newPhoneAdded: false, phase: Constants.PHASE_COLLECTION, phoneVerifyFailed: false });
}
receiveVerificationCode = (evt) => {
if (!this.state.newPhoneAdded || this.state.codeVerifySuccessful) {
return;
}
const { verifyPhone } = this.props;
this.setState({ codeVerifyStarted: true });
verifyPhone(evt.code);
}
onSendTextPressed = () => {
const { addUserPhone, notify } = this.props;
if (!this.phoneInput.isValidNumber()) {
return notify({
message: 'Please provide a valid telephone number.',
});
}
this.setState({ phoneVerifyFailed: false });
const countryCode = this.phoneInput.getCountryCode();
const number = this.phoneInput.getValue().replace('+' + countryCode, '');
this.setState({ countryCode, number });
addUserPhone(number, countryCode);
}
onVerifyPressed = () => {
if (this.state.codeVerifyStarted) {
return;
}
const { verifyPhone } = this.props;
this.setState({ codeVerifyStarted: true, phoneVerifyFailed: false });
verifyPhone(this.state.verificationCode);
}
onPressFlag = () => {
if (this.picker) {
this.picker.openModal();
}
}
selectCountry(country) {
this.phoneInput.selectCountry(country.cca2.toLowerCase());
this.setState({ cca2: country.cca2 });
}
handleChangeText = (text) => {
this.setState({ verificationCode: text });
};
render() {
const {
phoneVerifyIsPending,
phoneVerifyErrorMessage,
phone,
phoneErrorMessage,
phoneNewIsPending
} = this.props;
return (
<View style={firstRunStyle.container}>
<Text style={rewardStyle.verificationTitle}>{this.state.phase === Constants.PHASE_VERIFICATION ? 'Verify ' : '' }Phone Number</Text>
<View style={rewardStyle.phoneVerificationContainer}>
{this.state.phase == Constants.PHASE_COLLECTION &&
<View>
<Text style={[rewardStyle.bottomMarginMedium, firstRunStyle.paragraph]}>Please provide a phone number to prevent fraud.</Text>
<PhoneInput
ref={(ref) => { this.phoneInput = ref; }}
style={StyleSheet.flatten(rewardStyle.phoneInput)}
textProps={{ placeholder: '(phone number)' }}
textStyle={StyleSheet.flatten(rewardStyle.phoneInputText)}
onPressFlag={this.onPressFlag} />
<View style={rewardStyle.buttonContainer}>
{!phoneNewIsPending &&
<Button
style={[rewardStyle.verificationButton, rewardStyle.topMarginMedium]}
theme={"light"}
text={"Send verification text"}
onPress={this.onSendTextPressed} />}
{phoneNewIsPending &&
<ActivityIndicator
style={[rewardStyle.loading, rewardStyle.topMarginMedium]}
size="small"
color={Colors.White} />}
</View>
</View>}
{this.state.phase === Constants.PHASE_VERIFICATION &&
<View>
{!phoneVerifyIsPending && !this.codeVerifyStarted &&
<View>
<Text style={[rewardStyle.bottomMarginSmall, firstRunStyle.paragraph]}>
Please enter the verification code sent to {phone}.
</Text>
<TextInput
style={rewardStyle.verificationCodeInput}
keyboardType="numeric"
placeholder="0000"
underlineColorAndroid="transparent"
value={this.state.verificationCode}
onChangeText={text => this.handleChangeText(text)}
/>
<View style={rewardStyle.buttonContainer}>
<Button
style={[rewardStyle.verificationButton, rewardStyle.topMarginSmall]}
theme={"light"}
text={"Verify"}
onPress={this.onVerifyPressed} />
<Link style={rewardStyle.verificationLink} text={"Edit"} onPress={this.onEditPressed} />
</View>
</View>
}
{phoneVerifyIsPending &&
<View>
<Text style={firstRunStyle.paragraph}>Verifying your phone number...</Text>
<ActivityIndicator
color={Colors.White}
size="small"
style={[rewardStyle.loading, rewardStyle.topMarginMedium, rewardStyle.leftRightMargin]} />
</View>}
</View>
}
{this.state.phoneVerifyFailed &&
<View style={rewardStyle.failureFootnote}>
<Text style={rewardStyle.paragraphText}>
Sorry, we were unable to verify your phone number. Please go to <Link style={rewardStyle.textLink} href="http://chat.lbry.com" text="chat.lbry.com" /> for manual verification if this keeps happening.
</Text>
</View>}
</View>
<CountryPicker
ref={(picker) => { this.picker = picker; }}
cca2={this.state.cca2}
filterable={true}
onChange={value => this.selectCountry(value)}
showCallingCode={true}
translation="eng">
<View />
</CountryPicker>
</View>
);
}
};
export default PhoneVerifyPage;

View file

@ -0,0 +1,140 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
ActivityIndicator,
Linking,
NativeModules,
Text,
TouchableOpacity,
View
} from 'react-native';
import { NavigationActions, StackActions } from 'react-navigation';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
import Constants from 'constants';
import EmailVerifyPage from './internal/email-verify-page';
import ManualVerifyPage from './internal/manual-verify-page';
import PhoneVerifyPage from './internal/phone-verify-page';
import firstRunStyle from 'styles/firstRun';
class VerificationScreen extends React.PureComponent {
state = {
currentPage: null,
emailSubmitted: false,
isFirstRun: false,
launchUrl: null,
showSkip: false,
skipAccountConfirmed: false,
showBottomContainer: true,
walletPassword: null,
isEmailVerified: false,
isIdentityVerified: false,
isRewardApproved: false
};
componentDidMount() {
const { user } = this.props;
this.checkVerificationStatus(user);
}
checkVerificationStatus = (user) => {
const { navigation } = this.props;
this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email),
isIdentityVerified: (user && user.is_identity_verified),
isRewardApproved: (user && user.is_reward_approved)
}, () => {
if (!this.state.isEmailVerified) {
this.setState({ currentPage: 'emailVerify' });
}
if (this.state.isEmailVerified && !this.state.isIdentityVerified) {
this.setState({ currentPage: 'phoneVerify' });
}
if (this.state.isEmailVerified && this.state.isIdentityVerified && !this.state.isRewardApproved) {
this.setState({ currentPage: 'manualVerify' });
}
if (this.state.isEmailVerified && this.state.isIdentityVerified && this.state.isRewardApproved) {
// verification steps already completed
// simply navigate back to the rewards page
navigation.goBack();
}
});
}
componentWillReceiveProps(nextProps) {
const { user } = nextProps;
this.checkVerificationStatus(user);
}
onCloseButtonPressed = () => {
const { navigation } = this.props;
navigation.goBack();
}
render() {
const {
addUserEmail,
emailNewErrorMessage,
emailNewPending,
emailToVerify,
navigation,
notify,
addUserPhone,
phone,
phoneVerifyIsPending,
phoneVerifyErrorMessage,
phoneNewIsPending,
phoneNewErrorMessage,
resendVerificationEmail,
verifyPhone
} = this.props;
let page = null;
switch (this.state.currentPage) {
case 'emailVerify':
page = (
<EmailVerifyPage
addUserEmail={addUserEmail}
emailNewErrorMessage={emailNewErrorMessage}
emailNewPending={emailNewPending}
emailToVerify={emailToVerify}
notify={notify}
resendVerificationEmail={resendVerificationEmail}
/>
);
break;
case 'phoneVerify':
page = (
<PhoneVerifyPage
addUserPhone={addUserPhone}
phone={phone}
phoneVerifyIsPending={phoneVerifyIsPending}
phoneVerifyErrorMessage={phoneVerifyErrorMessage}
phoneNewIsPending={phoneNewIsPending}
phoneNewErrorMessage={phoneNewErrorMessage}
notify={notify}
verifyPhone={verifyPhone}
/>
);
break;
case 'manualVerify':
page = (
<ManualVerifyPage />
);
}
return (
<View style={firstRunStyle.screenContainer}>
{page}
<TouchableOpacity style={firstRunStyle.closeButton} onPress={this.onCloseButtonPressed}>
<Text style={firstRunStyle.closeButtonText}>x</Text>
</TouchableOpacity>
</View>
);
}
}
export default VerificationScreen;

View file

@ -2,13 +2,17 @@ import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doPushDrawerStack } from 'redux/actions/drawer'; import { doPushDrawerStack } from 'redux/actions/drawer';
import { selectBalance } from 'lbry-redux';
import { doGetSync, selectUser } from 'lbryinc'; import { doGetSync, selectUser } from 'lbryinc';
import Constants from 'constants'; import Constants from 'constants';
import WalletPage from './view'; import WalletPage from './view';
const select = state => ({ const select = state => ({
user: selectUser(state), user: selectUser(state),
balance: selectBalance(state),
understandsRisks: makeSelectClientSetting(Constants.SETTING_ALPHA_UNDERSTANDS_RISKS)(state), understandsRisks: makeSelectClientSetting(Constants.SETTING_ALPHA_UNDERSTANDS_RISKS)(state),
backupDismissed: makeSelectClientSetting(Constants.SETTING_BACKUP_DISMISSED)(state),
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { NativeModules, ScrollView, Text, View } from 'react-native'; import { NativeModules, ScrollView, Text, View } from 'react-native';
import TransactionListRecent from 'component/transactionListRecent'; import TransactionListRecent from 'component/transactionListRecent';
import WalletRewardsDriver from 'component/walletRewardsDriver';
import WalletAddress from 'component/walletAddress'; import WalletAddress from 'component/walletAddress';
import WalletBalance from 'component/walletBalance'; import WalletBalance from 'component/walletBalance';
import WalletSend from 'component/walletSend'; import WalletSend from 'component/walletSend';
@ -20,8 +21,20 @@ class WalletPage extends React.PureComponent {
} }
} }
onDismissBackupPressed = () => {
const { setClientSetting } = this.props;
setClientSetting(Constants.SETTING_BACKUP_DISMISSED, true);
}
render() { render() {
const { understandsRisks, setClientSetting, navigation } = this.props; const {
balance,
backupDismissed,
rewardsNotInterested,
understandsRisks,
setClientSetting,
navigation
} = this.props;
if (!understandsRisks) { if (!understandsRisks) {
return ( return (
@ -41,17 +54,20 @@ class WalletPage extends React.PureComponent {
return ( return (
<View style={walletStyle.container}> <View style={walletStyle.container}>
<UriBar navigation={navigation} /> <UriBar navigation={navigation} />
<ScrollView keyboardShouldPersistTaps={'handled'}> <ScrollView style={walletStyle.scrollContainer} keyboardShouldPersistTaps={'handled'}>
{!backupDismissed &&
<View style={walletStyle.warningCard}> <View style={walletStyle.warningCard}>
<Text style={walletStyle.warningText}> <Text style={walletStyle.warningText}>
Please backup your wallet file using the instructions at <Link style={walletStyle.warningText} text="https://lbry.com/faq/how-to-backup-wallet#android" href="https://lbry.com/faq/how-to-backup-wallet#android" />. Please backup your wallet file using the instructions at <Link style={walletStyle.warningText} text="https://lbry.com/faq/how-to-backup-wallet#android" href="https://lbry.com/faq/how-to-backup-wallet#android" />.
</Text> </Text>
</View> <Button text={'Dismiss'} style={walletStyle.button} onPress={this.onDismissBackupPressed} />
</View>}
{(!rewardsNotInterested) && (!balance || balance === 0) && <WalletRewardsDriver navigation={navigation} />}
<WalletBalance /> <WalletBalance />
<WalletAddress /> <WalletAddress />
<WalletSend /> <WalletSend />
<TransactionListRecent navigation={this.props.navigation} /> <TransactionListRecent navigation={navigation} />
</ScrollView> </ScrollView>
</View> </View>
); );

View file

@ -129,6 +129,20 @@ const firstRunStyle = StyleSheet.create({
}, },
titleIcon: { titleIcon: {
marginTop: 8 marginTop: 8
},
closeButton: {
position: 'absolute',
top: 8,
right: 8,
width: 48,
height: 48,
borderRadius: 48,
justifyContent: 'center'
},
closeButtonText: {
alignSelf: 'center',
color: Colors.White,
fontSize: 16
} }
}); });

View file

@ -27,6 +27,24 @@ const rewardStyle = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
flexDirection: 'row' flexDirection: 'row'
}, },
enrollContainer: {
flex: 1,
marginTop: 76,
marginLeft: 16,
marginRight: 16,
marginBottom: 16,
padding: 24,
backgroundColor: Colors.LbryGreen
},
onboarding: {
marginTop: 36
},
enrollDescText: {
fontFamily: 'Inter-UI-Regular',
fontSize: 18,
lineHeight: 28,
color: Colors.White
},
rewardsContainer: { rewardsContainer: {
flex: 1 flex: 1
}, },
@ -101,21 +119,32 @@ const rewardStyle = StyleSheet.create({
bottomMarginLarge: { bottomMarginLarge: {
marginBottom: 24 marginBottom: 24
}, },
leftRightMargin: {
marginLeft: 32,
marginRight: 32
},
link: { link: {
color: Colors.LbryGreen, color: Colors.LbryGreen,
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
fontSize: 14, fontSize: 14,
}, },
textLink: { textLink: {
color: Colors.White
},
underlinedTextLink: {
color: Colors.White,
textDecorationLine: 'underline'
},
greenLink: {
color: Colors.LbryGreen color: Colors.LbryGreen
}, },
leftCol: { leftCol: {
width: '15%', width: '5%',
alignItems: 'center', alignItems: 'center',
paddingLeft: 6 paddingLeft: 6
}, },
midCol: { midCol: {
width: '65%' width: '75%'
}, },
rightCol: { rightCol: {
width: '18%', width: '18%',
@ -178,12 +207,14 @@ const rewardStyle = StyleSheet.create({
paddingRight: 4 paddingRight: 4
}, },
phoneInput: { phoneInput: {
marginLeft: 8 marginLeft: 32,
marginRight: 32
}, },
phoneInputText: { phoneInputText: {
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
fontSize: 16, fontSize: 16,
letterSpacing: 1.3 letterSpacing: 1.3,
color: Colors.White
}, },
verifyingText: { verifyingText: {
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
@ -193,8 +224,11 @@ const rewardStyle = StyleSheet.create({
}, },
verificationCodeInput: { verificationCodeInput: {
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
color: Colors.White,
fontSize: 24, fontSize: 24,
letterSpacing: 12 letterSpacing: 12,
marginLeft: 32,
marginRight: 32
}, },
loading: { loading: {
alignSelf: 'flex-start' alignSelf: 'flex-start'
@ -202,12 +236,24 @@ const rewardStyle = StyleSheet.create({
smsPermissionContainer: { smsPermissionContainer: {
marginBottom: 32 marginBottom: 32
}, },
dismissButton: { buttonRow: {
alignSelf: 'flex-end', width: '100%',
position: 'absolute',
alignItems: 'center',
left: 24,
bottom: 24,
flexDirection: 'row',
justifyContent: 'space-between'
},
notInterestedLink: {
fontSize: 14,
fontFamily: 'Inter-UI-Regular',
color: Colors.White
},
enrollButton: {
backgroundColor: Colors.White, backgroundColor: Colors.White,
paddingLeft: 16, paddingLeft: 16,
paddingRight: 16, paddingRight: 16
marginTop: 8,
}, },
customCodeInput: { customCodeInput: {
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
@ -221,7 +267,41 @@ const rewardStyle = StyleSheet.create({
backgroundColor: Colors.LbryGreen backgroundColor: Colors.LbryGreen
}, },
failureFootnote: { failureFootnote: {
marginTop: 12 marginTop: 32,
marginLeft: 32,
marginRight: 32
},
buttonContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
paddingLeft: 32,
paddingRight: 32,
marginTop: 16
},
verificationTitle: {
fontSize: 32,
color: Colors.White,
fontFamily: 'Inter-UI-Regular',
marginLeft: 32,
marginRight: 32,
marginBottom: 24
},
verificationButton: {
backgroundColor: Colors.White,
paddingLeft: 16,
paddingRight: 16
},
verificationLink: {
color: Colors.White,
fontSize: 14
},
paragraphText: {
fontFamily: 'Inter-UI-Regular',
color: Colors.White,
fontSize: 12,
lineHeight: 16
} }
}); });

View file

@ -5,6 +5,9 @@ const walletStyle = StyleSheet.create({
container: { container: {
backgroundColor: Colors.PageBackground backgroundColor: Colors.PageBackground
}, },
scrollContainer: {
marginTop: 60
},
row: { row: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -40,7 +43,7 @@ const walletStyle = StyleSheet.create({
backgroundColor: Colors.Orange, backgroundColor: Colors.Orange,
padding: 16, padding: 16,
marginLeft: 16, marginLeft: 16,
marginTop: 76, marginTop: 16,
marginRight: 16 marginRight: 16
}, },
transactionsCard: { transactionsCard: {
@ -142,7 +145,8 @@ const walletStyle = StyleSheet.create({
color: Colors.White, color: Colors.White,
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
fontSize: 16, fontSize: 16,
lineHeight: 24 lineHeight: 24,
marginBottom: 8
}, },
understand: { understand: {
marginLeft: 16, marginLeft: 16,
@ -168,6 +172,19 @@ const walletStyle = StyleSheet.create({
}, },
transactionHistoryScroll: { transactionHistoryScroll: {
marginTop: 60 marginTop: 60
},
rewardDriverCard: {
padding: 16,
backgroundColor: Colors.LbryGreen,
marginLeft: 16,
marginTop: 16,
marginRight: 16
},
rewardDriverText: {
color: Colors.White,
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
lineHeight: 16
} }
}); });

View file

@ -55,6 +55,7 @@ public class FirebaseModule extends ReactContextBaseJavaModule {
@ReactMethod @ReactMethod
public void logException(boolean fatal, String message, ReadableMap payload) { public void logException(boolean fatal, String message, ReadableMap payload) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString("message", message);
if (payload != null) { if (payload != null) {
HashMap<String, Object> payloadMap = payload.toHashMap(); HashMap<String, Object> payloadMap = payload.toHashMap();
for (Map.Entry<String, Object> entry : payloadMap.entrySet()) { for (Map.Entry<String, Object> entry : payloadMap.entrySet()) {