Wallet implementation (#92)
* Created walletAddress and walletBalance components * Added walletSend and transaction list components. * added transaction history page
This commit is contained in:
parent
351e39826b
commit
3706bc5936
31 changed files with 845 additions and 15 deletions
BIN
app/src/assets/stripe@2x.png
Normal file
BIN
app/src/assets/stripe@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import AboutPage from '../page/about';
|
||||||
import DiscoverPage from '../page/discover';
|
import DiscoverPage from '../page/discover';
|
||||||
import FilePage from '../page/file';
|
import FilePage from '../page/file';
|
||||||
import SearchPage from '../page/search';
|
import SearchPage from '../page/search';
|
||||||
import SettingsPage from '../page/settings';
|
import SettingsPage from '../page/settings';
|
||||||
import AboutPage from '../page/about';
|
|
||||||
import SplashScreen from '../page/splash';
|
import SplashScreen from '../page/splash';
|
||||||
|
import TransactionHistoryPage from '../page/transactionHistory';
|
||||||
|
import WalletPage from '../page/wallet';
|
||||||
import SearchInput from '../component/searchInput';
|
import SearchInput from '../component/searchInput';
|
||||||
import {
|
import {
|
||||||
addNavigationHelpers,
|
addNavigationHelpers,
|
||||||
|
@ -19,9 +21,10 @@ import {
|
||||||
AsyncStorage,
|
AsyncStorage,
|
||||||
BackHandler,
|
BackHandler,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
TextInput
|
TextInput,
|
||||||
|
ToastAndroid
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SETTINGS } from 'lbry-redux';
|
import { SETTINGS, doHideNotification, selectNotification } from 'lbry-redux';
|
||||||
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
import { makeSelectClientSetting } from '../redux/selectors/settings';
|
||||||
import Feather from 'react-native-vector-icons/Feather';
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
import discoverStyle from '../styles/discover';
|
import discoverStyle from '../styles/discover';
|
||||||
|
@ -55,8 +58,29 @@ const discoverStack = StackNavigator({
|
||||||
headerMode: 'screen',
|
headerMode: 'screen',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const walletStack = StackNavigator({
|
||||||
|
Wallet: {
|
||||||
|
screen: WalletPage,
|
||||||
|
navigationOptions: ({ navigation }) => ({
|
||||||
|
title: 'Wallet',
|
||||||
|
headerLeft: <Feather name="menu" size={24} style={discoverStyle.drawerHamburger} onPress={() => navigation.navigate('DrawerOpen')} />,
|
||||||
|
headerRight: <Feather name="search" size={24} style={discoverStyle.rightHeaderIcon} onPress={() => navigation.navigate('Search')} />
|
||||||
|
})
|
||||||
|
},
|
||||||
|
TransactionHistory: {
|
||||||
|
screen: TransactionHistoryPage,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Transaction History',
|
||||||
|
drawerLockMode: 'locked-closed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
headerMode: 'screen'
|
||||||
|
});
|
||||||
|
|
||||||
const drawer = DrawerNavigator({
|
const drawer = DrawerNavigator({
|
||||||
Discover: { screen: discoverStack },
|
Discover: { screen: discoverStack },
|
||||||
|
Wallet: { screen: walletStack },
|
||||||
Settings: { screen: SettingsPage, navigationOptions: { drawerLockMode: 'locked-closed' } },
|
Settings: { screen: SettingsPage, navigationOptions: { drawerLockMode: 'locked-closed' } },
|
||||||
About: { screen: AboutPage, navigationOptions: { drawerLockMode: 'locked-closed' } }
|
About: { screen: AboutPage, navigationOptions: { drawerLockMode: 'locked-closed' } }
|
||||||
}, {
|
}, {
|
||||||
|
@ -79,6 +103,8 @@ export const AppNavigator = new StackNavigator({
|
||||||
});
|
});
|
||||||
|
|
||||||
class AppWithNavigationState extends React.Component {
|
class AppWithNavigationState extends React.Component {
|
||||||
|
static supportedDisplayTypes = ['toast'];
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
AppState.addEventListener('change', this._handleAppStateChange);
|
AppState.addEventListener('change', this._handleAppStateChange);
|
||||||
BackHandler.addEventListener('hardwareBackPress', function() {
|
BackHandler.addEventListener('hardwareBackPress', function() {
|
||||||
|
@ -106,7 +132,33 @@ class AppWithNavigationState extends React.Component {
|
||||||
AppState.removeEventListener('change', this._handleAppStateChange);
|
AppState.removeEventListener('change', this._handleAppStateChange);
|
||||||
BackHandler.removeEventListener('hardwareBackPress');
|
BackHandler.removeEventListener('hardwareBackPress');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps) {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
const { notification } = nextProps;
|
||||||
|
if (notification) {
|
||||||
|
const { displayType, message } = notification;
|
||||||
|
let currentDisplayType;
|
||||||
|
if (displayType.length) {
|
||||||
|
for (let i = 0; i < displayType.length; i++) {
|
||||||
|
const type = displayType[i];
|
||||||
|
if (AppWithNavigationState.supportedDisplayTypes.indexOf(type) > -1) {
|
||||||
|
currentDisplayType = type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (AppWithNavigationState.supportedDisplayTypes.indexOf(displayType) > -1) {
|
||||||
|
currentDisplayType = displayType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('toast' === currentDisplayType) {
|
||||||
|
ToastAndroid.show(message, ToastAndroid.SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(doHideNotification());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_handleAppStateChange = (nextAppState) => {
|
_handleAppStateChange = (nextAppState) => {
|
||||||
// Check if the app was suspended
|
// Check if the app was suspended
|
||||||
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
|
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
|
||||||
|
@ -136,8 +188,9 @@ class AppWithNavigationState extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
nav: state.nav,
|
nav: state.nav,
|
||||||
|
notification: selectNotification(state),
|
||||||
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
|
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
|
||||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state)
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(AppWithNavigationState);
|
export default connect(mapStateToProps)(AppWithNavigationState);
|
||||||
|
|
7
app/src/component/address/index.js
Normal file
7
app/src/component/address/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doNotify } from 'lbry-redux';
|
||||||
|
import Address from './view';
|
||||||
|
|
||||||
|
export default connect(null, {
|
||||||
|
doNotify,
|
||||||
|
})(Address);
|
29
app/src/component/address/view.js
Normal file
29
app/src/component/address/view.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Clipboard, Text, View } from 'react-native';
|
||||||
|
import Button from '../button';
|
||||||
|
import walletStyle from '../../styles/wallet';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
address: string,
|
||||||
|
doNotify: ({ message: string, displayType: Array<string> }) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Address extends React.PureComponent<Props> {
|
||||||
|
render() {
|
||||||
|
const { address, doNotify, style } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[walletStyle.row, style]}>
|
||||||
|
<Text selectable={true} numberOfLines={1} style={walletStyle.address}>{address || ''}</Text>
|
||||||
|
<Button icon={'clipboard'} style={walletStyle.button} onPress={() => {
|
||||||
|
Clipboard.setString(address);
|
||||||
|
doNotify({
|
||||||
|
message: 'Address copied',
|
||||||
|
displayType: ['toast'],
|
||||||
|
});
|
||||||
|
}} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
4
app/src/component/button/index.js
Normal file
4
app/src/component/button/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Button from './view';
|
||||||
|
|
||||||
|
export default connect(null, null)(Button);
|
39
app/src/component/button/view.js
Normal file
39
app/src/component/button/view.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, TouchableOpacity } from 'react-native';
|
||||||
|
import buttonStyle from '../../styles/button';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||||
|
|
||||||
|
export default class Button extends React.PureComponent<Props> {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
disabled,
|
||||||
|
style,
|
||||||
|
text,
|
||||||
|
icon,
|
||||||
|
onPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
let styles = [buttonStyle.button, buttonStyle.row];
|
||||||
|
if (style.length) {
|
||||||
|
styles = styles.concat(style);
|
||||||
|
} else {
|
||||||
|
styles.push(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
styles.push(buttonStyle.disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
const textStyles = [buttonStyle.text];
|
||||||
|
if (icon && icon.trim().length > 0) {
|
||||||
|
textStyles.push(buttonStyle.textWithIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity disabled={disabled} style={styles} onPress={onPress}>
|
||||||
|
{icon && <Icon name={icon} size={18} color='#ffffff' class={buttonStyle.icon} /> }
|
||||||
|
{text && (text.trim().length > 0) && <Text style={textStyles}>{text}</Text>}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
13
app/src/component/transactionList/index.js
Normal file
13
app/src/component/transactionList/index.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
//import { selectClaimedRewardsByTransactionId } from 'redux/selectors/rewards';
|
||||||
|
import { selectAllMyClaimsByOutpoint } from 'lbry-redux';
|
||||||
|
import TransactionList from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
//rewards: selectClaimedRewardsByTransactionId(state),
|
||||||
|
myClaims: selectAllMyClaimsByOutpoint(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(TransactionList);
|
|
@ -0,0 +1,45 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import { formatCredits } from 'lbry-redux';
|
||||||
|
import moment from 'moment';
|
||||||
|
import transactionListStyle from '../../../styles/transactionList';
|
||||||
|
|
||||||
|
class TransactionListItem extends React.PureComponent {
|
||||||
|
capitalize(string: string) {
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { transaction } = this.props;
|
||||||
|
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={transactionListStyle.listItem}>
|
||||||
|
<View style={[transactionListStyle.row, transactionListStyle.topRow]}>
|
||||||
|
<View style={transactionListStyle.col}>
|
||||||
|
<Text style={transactionListStyle.text}>{this.capitalize(type)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={transactionListStyle.col}>
|
||||||
|
<Text style={[transactionListStyle.amount, transactionListStyle.text]}>{formatCredits(amount, 8)}</Text>
|
||||||
|
{ fee !== 0 && (<Text style={[transactionListStyle.amount, transactionListStyle.text]}>fee {formatCredits(fee, 8)}</Text>) }
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={transactionListStyle.row}>
|
||||||
|
<View style={transactionListStyle.col}>
|
||||||
|
<Text style={[transactionListStyle.smallText, transactionListStyle.txid]}>{txid.substring(0, 8)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={transactionListStyle.col}>
|
||||||
|
{date ? (
|
||||||
|
<Text style={transactionListStyle.smallText}>{moment(date).format('MMM D')}</Text>
|
||||||
|
) : (
|
||||||
|
<Text style={transactionListStyle.smallText}>Pending</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransactionListItem;
|
69
app/src/component/transactionList/view.js
Normal file
69
app/src/component/transactionList/view.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import TransactionListItem from './internal/transaction-list-item';
|
||||||
|
import transactionListStyle from '../../styles/transactionList';
|
||||||
|
|
||||||
|
export type Transaction = {
|
||||||
|
amount: number,
|
||||||
|
claim_id: string,
|
||||||
|
claim_name: string,
|
||||||
|
fee: number,
|
||||||
|
nout: number,
|
||||||
|
txid: string,
|
||||||
|
type: string,
|
||||||
|
date: Date,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransactionList extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
filter: 'all',
|
||||||
|
};
|
||||||
|
|
||||||
|
(this: any).handleFilterChanged = this.handleFilterChanged.bind(this);
|
||||||
|
(this: any).filterTransaction = this.filterTransaction.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFilterChanged(event: React.SyntheticInputEvent<*>) {
|
||||||
|
this.setState({
|
||||||
|
filter: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filterTransaction(transaction: Transaction) {
|
||||||
|
const { filter } = this.state;
|
||||||
|
|
||||||
|
return filter === 'all' || filter === transaction.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { emptyMessage, rewards, transactions } = this.props;
|
||||||
|
const { filter } = this.state;
|
||||||
|
const transactionList = transactions.filter(this.filterTransaction);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{!transactionList.length && (
|
||||||
|
<Text style={transactionListStyle.noTransactions}>{emptyMessage || 'No transactions to list.'}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!transactionList.length && (
|
||||||
|
<View>
|
||||||
|
{transactionList.map(t => (
|
||||||
|
<TransactionListItem
|
||||||
|
key={`${t.txid}:${t.nout}`}
|
||||||
|
transaction={t}
|
||||||
|
reward={rewards && rewards[t.txid]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransactionList;
|
20
app/src/component/transactionListRecent/index.js
Normal file
20
app/src/component/transactionListRecent/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doFetchTransactions,
|
||||||
|
selectRecentTransactions,
|
||||||
|
selectHasTransactions,
|
||||||
|
selectIsFetchingTransactions,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import TransactionListRecent from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||||
|
transactions: selectRecentTransactions(state),
|
||||||
|
hasTransactions: selectHasTransactions(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(TransactionListRecent);
|
46
app/src/component/transactionListRecent/view.js
Normal file
46
app/src/component/transactionListRecent/view.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
//import BusyIndicator from 'component/common/busy-indicator';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import Button from '../button';
|
||||||
|
import TransactionList from '../transactionList';
|
||||||
|
import type { Transaction } from '../transactionList/view';
|
||||||
|
import walletStyle from '../../styles/wallet';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
fetchTransactions: () => void,
|
||||||
|
fetchingTransactions: boolean,
|
||||||
|
hasTransactions: boolean,
|
||||||
|
transactions: Array<Transaction>,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TransactionListRecent extends React.PureComponent<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { fetchingTransactions, hasTransactions, transactions, navigation } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={walletStyle.transactionsCard}>
|
||||||
|
<View style={[walletStyle.row, walletStyle.transactionsHeader]}>
|
||||||
|
<Text style={walletStyle.transactionsTitle}>Recent Transactions</Text>
|
||||||
|
<Button style={walletStyle.button} text={'View All'}
|
||||||
|
onPress={() => navigation.navigate('TransactionHistory')} />
|
||||||
|
</View>
|
||||||
|
{fetchingTransactions && (
|
||||||
|
<Text style={walletStyle.infoText}>Fetching transactions...</Text>
|
||||||
|
)}
|
||||||
|
{!fetchingTransactions && (
|
||||||
|
<TransactionList
|
||||||
|
transactions={transactions}
|
||||||
|
emptyMessage={"Looks like you don't have any recent transactions."}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransactionListRecent;
|
20
app/src/component/walletAddress/index.js
Normal file
20
app/src/component/walletAddress/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doCheckAddressIsMine,
|
||||||
|
doGetNewAddress,
|
||||||
|
selectReceiveAddress,
|
||||||
|
selectGettingNewAddress,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import WalletAddress from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
receiveAddress: selectReceiveAddress(state),
|
||||||
|
gettingNewAddress: selectGettingNewAddress(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
checkAddressIsMine: address => dispatch(doCheckAddressIsMine(address)),
|
||||||
|
getNewAddress: () => dispatch(doGetNewAddress()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(WalletAddress);
|
47
app/src/component/walletAddress/view.js
Normal file
47
app/src/component/walletAddress/view.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import Address from '../address';
|
||||||
|
import Button from '../button';
|
||||||
|
import walletStyle from '../../styles/wallet';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
checkAddressIsMine: string => void,
|
||||||
|
receiveAddress: string,
|
||||||
|
getNewAddress: () => void,
|
||||||
|
gettingNewAddress: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class WalletAddress extends React.PureComponent<Props> {
|
||||||
|
componentWillMount() {
|
||||||
|
const { checkAddressIsMine, receiveAddress, getNewAddress } = this.props;
|
||||||
|
if (!receiveAddress) {
|
||||||
|
getNewAddress();
|
||||||
|
} else {
|
||||||
|
checkAddressIsMine(receiveAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { receiveAddress, getNewAddress, gettingNewAddress } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={walletStyle.card}>
|
||||||
|
<Text style={walletStyle.title}>Receive Credits</Text>
|
||||||
|
<Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}>Use this wallet address to receive credits sent by another user (or yourself).</Text>
|
||||||
|
<Address address={receiveAddress} style={walletStyle.bottomMarginSmall} />
|
||||||
|
<Button style={[walletStyle.button, walletStyle.bottomMarginLarge]}
|
||||||
|
icon={'refresh'}
|
||||||
|
text={'Get New Address'}
|
||||||
|
onPress={getNewAddress}
|
||||||
|
disabled={gettingNewAddress}
|
||||||
|
/>
|
||||||
|
<Text style={walletStyle.smallText}>
|
||||||
|
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletAddress;
|
9
app/src/component/walletBalance/index.js
Normal file
9
app/src/component/walletBalance/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectBalance } from 'lbry-redux';
|
||||||
|
import WalletBalance from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, null)(WalletBalance);
|
29
app/src/component/walletBalance/view.js
Normal file
29
app/src/component/walletBalance/view.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Image, Text, View } from 'react-native';
|
||||||
|
import { formatCredits } from 'lbry-redux'
|
||||||
|
import Address from '../address';
|
||||||
|
import Button from '../button';
|
||||||
|
import walletStyle from '../../styles/wallet';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
balance: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
class WalletBalance extends React.PureComponent<Props> {
|
||||||
|
render() {
|
||||||
|
const { balance } = this.props;
|
||||||
|
return (
|
||||||
|
<View style={walletStyle.balanceCard}>
|
||||||
|
<Image style={walletStyle.balanceBackground} resizeMode={'cover'} source={require('../../assets/stripe.png')} />
|
||||||
|
<Text style={walletStyle.balanceTitle}>Balance</Text>
|
||||||
|
<Text style={walletStyle.balanceCaption}>You currently have</Text>
|
||||||
|
<Text style={walletStyle.balance}>
|
||||||
|
{(balance || balance === 0) && (formatCredits(balance, 2) + ' LBC')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletBalance;
|
22
app/src/component/walletSend/index.js
Normal file
22
app/src/component/walletSend/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doNotify,
|
||||||
|
doSendDraftTransaction,
|
||||||
|
selectDraftTransaction,
|
||||||
|
selectDraftTransactionError,
|
||||||
|
selectBalance
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import WalletSend from './view';
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
sendToAddress: (address, amount) => dispatch(doSendDraftTransaction(address, amount)),
|
||||||
|
notify: (data) => dispatch(doNotify(data))
|
||||||
|
});
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
balance: selectBalance(state),
|
||||||
|
draftTransaction: selectDraftTransaction(state),
|
||||||
|
transactionError: selectDraftTransactionError(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(WalletSend);
|
89
app/src/component/walletSend/view.js
Normal file
89
app/src/component/walletSend/view.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { regexAddress } from 'lbry-redux';
|
||||||
|
import { TextInput, Text, View } from 'react-native';
|
||||||
|
import Button from '../button';
|
||||||
|
import walletStyle from '../../styles/wallet';
|
||||||
|
|
||||||
|
type DraftTransaction = {
|
||||||
|
address: string,
|
||||||
|
amount: ?number, // So we can use a placeholder in the input
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sendToAddress: (string, number) => void,
|
||||||
|
balance: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
class WalletSend extends React.PureComponent<Props> {
|
||||||
|
state = {
|
||||||
|
amount: null,
|
||||||
|
address: null
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps) {
|
||||||
|
const { draftTransaction, transactionError } = nextProps;
|
||||||
|
if (transactionError && transactionError.trim().length > 0) {
|
||||||
|
this.setState({ address: draftTransaction.address, amount: draftTransaction.amount });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSend = () => {
|
||||||
|
const { balance, sendToAddress, notify } = this.props;
|
||||||
|
const { address, amount } = this.state;
|
||||||
|
if (address && !regexAddress.test(address)) {
|
||||||
|
notify({
|
||||||
|
message: 'The recipient address is not a valid LBRY address.',
|
||||||
|
displayType: ['toast']
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > balance) {
|
||||||
|
notify({
|
||||||
|
message: 'Insufficient credits',
|
||||||
|
displayType: ['toast']
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount && address) {
|
||||||
|
sendToAddress(address, parseFloat(amount));
|
||||||
|
this.setState({ address: null, amount: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { balance } = this.props;
|
||||||
|
const canSend = this.state.address &&
|
||||||
|
this.state.amount > 0 &&
|
||||||
|
this.state.address.trim().length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={walletStyle.card}>
|
||||||
|
<Text style={walletStyle.title}>Send Credits</Text>
|
||||||
|
<Text style={walletStyle.text}>Amount</Text>
|
||||||
|
<View style={[walletStyle.amountRow, walletStyle.bottomMarginMedium]}>
|
||||||
|
<TextInput onChangeText={value => this.setState({amount: value})}
|
||||||
|
keyboardType={'numeric'}
|
||||||
|
value={this.state.amount}
|
||||||
|
style={[walletStyle.input, walletStyle.amountInput]} />
|
||||||
|
<Text style={[walletStyle.text, walletStyle.currency]}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={walletStyle.text}>Recipient address</Text>
|
||||||
|
<View style={walletStyle.row}>
|
||||||
|
<TextInput onChangeText={value => this.setState({address: value})}
|
||||||
|
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
|
||||||
|
value={this.state.address}
|
||||||
|
style={[walletStyle.input, walletStyle.addressInput, walletStyle.bottomMarginMedium]} />
|
||||||
|
<Button text={'Send'}
|
||||||
|
style={[walletStyle.button, walletStyle.sendButton]}
|
||||||
|
disabled={!canSend}
|
||||||
|
onPress={this.handleSend} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletSend;
|
|
@ -24,6 +24,7 @@ import {
|
||||||
claimsReducer,
|
claimsReducer,
|
||||||
costInfoReducer,
|
costInfoReducer,
|
||||||
fileInfoReducer,
|
fileInfoReducer,
|
||||||
|
notificationsReducer,
|
||||||
searchReducer,
|
searchReducer,
|
||||||
walletReducer
|
walletReducer
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
@ -71,6 +72,7 @@ const reducers = combineReducers({
|
||||||
claims: claimsReducer,
|
claims: claimsReducer,
|
||||||
costInfo: costInfoReducer,
|
costInfo: costInfoReducer,
|
||||||
fileInfo: fileInfoReducer,
|
fileInfo: fileInfoReducer,
|
||||||
|
notifications: notificationsReducer,
|
||||||
search: searchReducer,
|
search: searchReducer,
|
||||||
wallet: walletReducer,
|
wallet: walletReducer,
|
||||||
nav: navigatorReducer,
|
nav: navigatorReducer,
|
||||||
|
@ -96,12 +98,13 @@ const compressor = createCompressor();
|
||||||
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||||
const settingsFilter = createFilter('settings', ['clientSettings']);
|
const settingsFilter = createFilter('settings', ['clientSettings']);
|
||||||
|
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||||
|
|
||||||
const persistOptions = {
|
const persistOptions = {
|
||||||
whitelist: ['claims', 'subscriptions', 'settings'],
|
whitelist: ['claims', 'subscriptions', 'settings', 'wallet'],
|
||||||
// Order is important. Needs to be compressed last or other transforms can't
|
// Order is important. Needs to be compressed last or other transforms can't
|
||||||
// read the data
|
// read the data
|
||||||
transforms: [saveClaimsFilter, subscriptionsFilter, settingsFilter, compressor],
|
transforms: [saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
|
||||||
debounce: 10000,
|
debounce: 10000,
|
||||||
storage: AsyncStorage
|
storage: AsyncStorage
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import discoverStyle from '../../styles/discover';
|
||||||
import Feather from 'react-native-vector-icons/Feather';
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
|
|
||||||
class DiscoverPage extends React.PureComponent {
|
class DiscoverPage extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentDidMount() {
|
||||||
// Track the total time taken if this is the first launch
|
// Track the total time taken if this is the first launch
|
||||||
AsyncStorage.getItem('firstLaunchTime').then(startTime => {
|
AsyncStorage.getItem('firstLaunchTime').then(startTime => {
|
||||||
if (startTime !== null && !isNaN(parseInt(startTime, 10))) {
|
if (startTime !== null && !isNaN(parseInt(startTime, 10))) {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { doBalanceSubscribe } from 'lbry-redux';
|
||||||
import SplashScreen from './view';
|
import SplashScreen from './view';
|
||||||
|
|
||||||
const select = state => ({});
|
const perform = dispatch => ({
|
||||||
const perform = dispatch => ({});
|
balanceSubscribe: () => dispatch(doBalanceSubscribe())
|
||||||
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(SplashScreen);
|
export default connect(null, perform)(SplashScreen);
|
|
@ -40,7 +40,8 @@ class SplashScreen extends React.PureComponent {
|
||||||
|
|
||||||
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
||||||
// Leave the splash screen
|
// Leave the splash screen
|
||||||
const { navigation } = this.props;
|
const { balanceSubscribe, navigation } = this.props;
|
||||||
|
balanceSubscribe();
|
||||||
navigation.navigate('Main');
|
navigation.navigate('Main');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
18
app/src/page/transactionHistory/index.js
Normal file
18
app/src/page/transactionHistory/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doFetchTransactions,
|
||||||
|
selectTransactionItems,
|
||||||
|
selectIsFetchingTransactions,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import TransactionHistoryPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||||
|
transactions: selectTransactionItems(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(TransactionHistoryPage);
|
29
app/src/page/transactionHistory/view.js
Normal file
29
app/src/page/transactionHistory/view.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, ScrollView, Text } from 'react-native';
|
||||||
|
import TransactionList from '../../component/transactionList';
|
||||||
|
import walletStyle from '../../styles/wallet';
|
||||||
|
|
||||||
|
class TransactionHistoryPage extends React.PureComponent {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { fetchingTransactions, transactions } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View style={walletStyle.historyList}>
|
||||||
|
{fetchingTransactions && !transactions.length && (
|
||||||
|
<Text style={walletStyle.infoText}>Loading transactions...</Text>
|
||||||
|
)}
|
||||||
|
{transactions && transactions.length && (
|
||||||
|
<TransactionList transactions={transactions} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransactionHistoryPage;
|
4
app/src/page/wallet/index.js
Normal file
4
app/src/page/wallet/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import WalletPage from './view';
|
||||||
|
|
||||||
|
export default connect(null, null)(WalletPage);
|
21
app/src/page/wallet/view.js
Normal file
21
app/src/page/wallet/view.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ScrollView } from 'react-native';
|
||||||
|
import TransactionListRecent from '../../component/transactionListRecent';
|
||||||
|
import WalletAddress from '../../component/walletAddress';
|
||||||
|
import WalletBalance from '../../component/walletBalance';
|
||||||
|
import WalletSend from '../../component/walletSend';
|
||||||
|
|
||||||
|
class WalletPage extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<WalletBalance />
|
||||||
|
<WalletAddress />
|
||||||
|
<WalletSend />
|
||||||
|
<TransactionListRecent navigation={this.props.navigation} />
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletPage;
|
|
@ -1,9 +1,9 @@
|
||||||
import {
|
import {
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
Lbry,
|
Lbry,
|
||||||
|
selectBalance,
|
||||||
makeSelectCostInfoForUri,
|
makeSelectCostInfoForUri,
|
||||||
makeSelectFileInfoForUri,
|
makeSelectFileInfoForUri,
|
||||||
selectTotalDownloadProgress,
|
|
||||||
selectDownloadingByOutpoint,
|
selectDownloadingByOutpoint,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
|
@ -187,7 +187,7 @@ export function doLoadVideo(uri) {
|
||||||
export function doPurchaseUri(uri, specificCostInfo) {
|
export function doPurchaseUri(uri, specificCostInfo) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const balance = 0;//selectBalance(state);
|
const balance = selectBalance(state);
|
||||||
const fileInfo = makeSelectFileInfoForUri(uri)(state);
|
const fileInfo = makeSelectFileInfoForUri(uri)(state);
|
||||||
const downloadingByOutpoint = selectDownloadingByOutpoint(state);
|
const downloadingByOutpoint = selectDownloadingByOutpoint(state);
|
||||||
const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint];
|
const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint];
|
||||||
|
|
30
app/src/styles/button.js
Normal file
30
app/src/styles/button.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
const buttonStyle = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 8,
|
||||||
|
paddingLeft: 12,
|
||||||
|
paddingRight: 12
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
backgroundColor: '#999999'
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
color: '#ffffff',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: '#ffffff',
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
textWithIcon: {
|
||||||
|
marginLeft: 8
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default buttonStyle;
|
46
app/src/styles/transactionList.js
Normal file
46
app/src/styles/transactionList.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import Colors from './colors';
|
||||||
|
|
||||||
|
const transactionListStyle = StyleSheet.create({
|
||||||
|
listItem: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#eeeeee',
|
||||||
|
paddingLeft: 16,
|
||||||
|
paddingRight: 16,
|
||||||
|
paddingTop: 12,
|
||||||
|
paddingBottom: 12
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
},
|
||||||
|
topRow: {
|
||||||
|
marginBottom: 4
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
alignSelf: 'stretch'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
textAlign: 'right'
|
||||||
|
},
|
||||||
|
txid: {
|
||||||
|
color: Colors.LbryGreen
|
||||||
|
},
|
||||||
|
smallText: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#aaaaaa'
|
||||||
|
},
|
||||||
|
noTransactions: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: 16,
|
||||||
|
color: '#aaaaaa'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default transactionListStyle;
|
133
app/src/styles/wallet.js
Normal file
133
app/src/styles/wallet.js
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import Colors from './colors';
|
||||||
|
|
||||||
|
const walletStyle = StyleSheet.create({
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
amountRow: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
borderColor: '#cccccc',
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
padding: 8,
|
||||||
|
width: '85%'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
backgroundColor: Colors.LbryGreen
|
||||||
|
},
|
||||||
|
historyList: {
|
||||||
|
backgroundColor: '#ffffff'
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
marginTop: 16,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
padding: 16
|
||||||
|
},
|
||||||
|
transactionsCard: {
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
margin: 16
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
|
fontSize: 20,
|
||||||
|
marginBottom: 24
|
||||||
|
},
|
||||||
|
transactionsTitle: {
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
|
fontSize: 20
|
||||||
|
},
|
||||||
|
transactionsHeader: {
|
||||||
|
paddingTop: 12,
|
||||||
|
paddingBottom: 12,
|
||||||
|
paddingLeft: 16,
|
||||||
|
paddingRight: 16,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#eeeeee'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
smallText: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
balanceCard: {
|
||||||
|
marginTop: 16,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16
|
||||||
|
},
|
||||||
|
balanceBackground: {
|
||||||
|
position: 'absolute',
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
balanceTitle: {
|
||||||
|
color: '#ffffff',
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
|
fontSize: 18,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginTop: 16
|
||||||
|
},
|
||||||
|
balanceCaption: {
|
||||||
|
color: '#caedB9',
|
||||||
|
fontFamily: 'Metropolis-Medium',
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginTop: 8,
|
||||||
|
marginBottom: 96
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
color: '#ffffff',
|
||||||
|
fontFamily: 'Metropolis-Bold',
|
||||||
|
fontSize: 36,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginBottom: 16
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
color: '#aaaaaa',
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
padding: 16,
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
fontFamily: 'Metropolis-Regular',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
amountInput: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
addressInput: {
|
||||||
|
width: '80%'
|
||||||
|
},
|
||||||
|
currency: {
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
marginTop: 17
|
||||||
|
},
|
||||||
|
sendButton: {
|
||||||
|
marginTop: 8
|
||||||
|
},
|
||||||
|
bottomMarginSmall: {
|
||||||
|
marginBottom: 8
|
||||||
|
},
|
||||||
|
bottomMarginMedium: {
|
||||||
|
marginBottom: 16
|
||||||
|
},
|
||||||
|
bottomMarginLarge: {
|
||||||
|
marginBottom: 24
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default walletStyle;
|
|
@ -16,7 +16,9 @@ import org.json.JSONException;
|
||||||
|
|
||||||
public class MixpanelModule extends ReactContextBaseJavaModule {
|
public class MixpanelModule extends ReactContextBaseJavaModule {
|
||||||
|
|
||||||
private static final String MIXPANEL_TOKEN = "93b81fb957cb0ddcd3198c10853a6a95";
|
// TODO: Detect dev / debug and release mode and update value accordingly
|
||||||
|
//private static final String MIXPANEL_TOKEN = "93b81fb957cb0ddcd3198c10853a6a95"; // Production
|
||||||
|
private static final String MIXPANEL_TOKEN = "bc1630b8be64c5dfaa4700b3a62969f3"; // Dev Testing
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
|
|
BIN
src/main/res/drawable-xhdpi/src_assets_stripe.png
Normal file
BIN
src/main/res/drawable-xhdpi/src_assets_stripe.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Loading…
Add table
Reference in a new issue