mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 23:08:07 +01:00
REF: Components isolated from BlueComponents
This commit is contained in:
parent
812867755d
commit
47e02aa84c
13 changed files with 750 additions and 681 deletions
|
@ -1,5 +1,5 @@
|
|||
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
|
||||
import React, { Component, useState, useMemo, useCallback, useContext, useEffect, forwardRef } from 'react';
|
||||
import React, { Component, useContext, forwardRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements';
|
||||
import {
|
||||
|
@ -11,7 +11,6 @@ import {
|
|||
InputAccessoryView,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Linking,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
PlatformColor,
|
||||
|
@ -21,27 +20,18 @@ import {
|
|||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
InteractionManager,
|
||||
I18nManager,
|
||||
} from 'react-native';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { LightningCustodianWallet, MultisigHDWallet } from './class';
|
||||
import { BitcoinUnit } from './models/bitcoinUnits';
|
||||
import * as NavigationService from './NavigationService';
|
||||
import WalletGradient from './class/wallet-gradient';
|
||||
import { BlurView } from '@react-native-community/blur';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
|
||||
import Biometric from './class/biometrics';
|
||||
import { encodeUR } from './blue_modules/ur';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useNavigation, useTheme } from '@react-navigation/native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import { BlueCurrentTheme } from './components/themes';
|
||||
import loc, { formatBalance, formatStringAddTwoWhiteSpaces, formatBalanceWithoutSuffix, transactionTimeToReadable } from './loc';
|
||||
import Lnurl from './class/lnurl';
|
||||
import loc, { formatStringAddTwoWhiteSpaces } from './loc';
|
||||
import { BlueStorageContext } from './blue_modules/storage-context';
|
||||
import ToolTipMenu from './components/TooltipMenu';
|
||||
|
||||
const { height, width } = Dimensions.get('window');
|
||||
const aspectRatio = height / width;
|
||||
|
@ -259,264 +249,6 @@ export const LightningButton = props => {
|
|||
);
|
||||
};
|
||||
|
||||
export class BlueWalletNavigationHeader extends Component {
|
||||
static propTypes = {
|
||||
wallet: PropTypes.shape().isRequired,
|
||||
onWalletUnitChange: PropTypes.func,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props) {
|
||||
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange };
|
||||
}
|
||||
|
||||
static contextType = BlueStorageContext;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
wallet: props.wallet,
|
||||
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
|
||||
allowOnchainAddress: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleCopyPress = _item => {
|
||||
Clipboard.setString(formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
|
||||
};
|
||||
|
||||
componentDidUpdate(prevState) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (prevState.wallet.getID() !== this.state.wallet.getID() && this.state.wallet.type === LightningCustodianWallet.type) {
|
||||
this.verifyIfWalletAllowsOnchainAddress();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
verifyIfWalletAllowsOnchainAddress = () => {
|
||||
if (this.state.wallet.type === LightningCustodianWallet.type) {
|
||||
this.state.wallet
|
||||
.allowOnchainAddress()
|
||||
.then(value => this.setState({ allowOnchainAddress: value }))
|
||||
.catch(e => {
|
||||
console.log('This Lndhub wallet does not have an onchain address API.');
|
||||
this.setState({ allowOnchainAddress: false });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.verifyIfWalletAllowsOnchainAddress();
|
||||
}
|
||||
|
||||
handleBalanceVisibility = async _item => {
|
||||
const wallet = this.state.wallet;
|
||||
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled && wallet.hideBalance) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
return this.props.navigation.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
wallet.hideBalance = !wallet.hideBalance;
|
||||
this.setState({ wallet });
|
||||
await this.context.saveToDisk();
|
||||
};
|
||||
|
||||
changeWalletBalanceUnit = () => {
|
||||
let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit();
|
||||
const wallet = this.state.wallet;
|
||||
if (walletPreviousPreferredUnit === BitcoinUnit.BTC) {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.SATS;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.BTC;
|
||||
} else if (walletPreviousPreferredUnit === BitcoinUnit.SATS) {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.LOCAL_CURRENCY;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.SATS;
|
||||
} else if (walletPreviousPreferredUnit === BitcoinUnit.LOCAL_CURRENCY) {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.BTC;
|
||||
} else {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.BTC;
|
||||
}
|
||||
|
||||
this.setState({ wallet, walletPreviousPreferredUnit: walletPreviousPreferredUnit }, () => {
|
||||
this.props.onWalletUnitChange(wallet);
|
||||
});
|
||||
};
|
||||
|
||||
manageFundsPressed = () => {
|
||||
this.props.onManageFundsPressed();
|
||||
};
|
||||
|
||||
onPress = id => {
|
||||
if (id === 'walletBalanceVisibility') {
|
||||
this.handleBalanceVisibility();
|
||||
} else if (id === 'copyToClipboard') {
|
||||
this.handleCopyPress();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const balance =
|
||||
!this.state.wallet.hideBalance &&
|
||||
formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString();
|
||||
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
|
||||
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
|
||||
{...WalletGradient.linearGradientProps(this.state.wallet.type)}
|
||||
>
|
||||
<Image
|
||||
source={(() => {
|
||||
switch (this.state.wallet.type) {
|
||||
case LightningCustodianWallet.type:
|
||||
return I18nManager.isRTL ? require('./img/lnd-shape-rtl.png') : require('./img/lnd-shape.png');
|
||||
case MultisigHDWallet.type:
|
||||
return I18nManager.isRTL ? require('./img/vault-shape-rtl.png') : require('./img/vault-shape.png');
|
||||
default:
|
||||
return I18nManager.isRTL ? require('./img/btc-shape-rtl.png') : require('./img/btc-shape.png');
|
||||
}
|
||||
})()}
|
||||
style={{
|
||||
width: 99,
|
||||
height: 94,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
testID="WalletLabel"
|
||||
numberOfLines={1}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 19,
|
||||
color: '#fff',
|
||||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||
}}
|
||||
>
|
||||
{this.state.wallet.getLabel()}
|
||||
</Text>
|
||||
<ToolTipMenu
|
||||
title={loc.wallets.balance}
|
||||
onPress={this.onPress}
|
||||
actions={
|
||||
this.state.wallet.hideBalance
|
||||
? [
|
||||
{
|
||||
id: 'walletBalanceVisibility',
|
||||
text: loc.transactions.details_balance_show,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'eye',
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: 'walletBalanceVisibility',
|
||||
text: loc.transactions.details_balance_hide,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'eye.slash',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'copyToClipboard',
|
||||
text: loc.transactions.details_copy,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<TouchableOpacity accessibilityRole="button" style={styles.balance} onPress={this.changeWalletBalanceUnit}>
|
||||
{this.state.wallet.hideBalance ? (
|
||||
<BluePrivateBalance />
|
||||
) : (
|
||||
<Text
|
||||
testID="WalletBalance"
|
||||
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
color: '#fff',
|
||||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||
}}
|
||||
>
|
||||
{balance}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</ToolTipMenu>
|
||||
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
|
||||
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
|
||||
<View
|
||||
style={{
|
||||
marginTop: 14,
|
||||
marginBottom: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: 9,
|
||||
minHeight: 39,
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: 12,
|
||||
height: 39,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
color: '#FFFFFF',
|
||||
}}
|
||||
>
|
||||
{loc.lnd.title}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{this.state.wallet.type === MultisigHDWallet.type && (
|
||||
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
|
||||
<View
|
||||
style={{
|
||||
marginTop: 14,
|
||||
marginBottom: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: 9,
|
||||
minHeight: 39,
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: 12,
|
||||
height: 39,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
color: '#FFFFFF',
|
||||
}}
|
||||
>
|
||||
{loc.multisig.manage_keys}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</LinearGradient>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: remove this comment once this file gets properly converted to typescript.
|
||||
*
|
||||
|
@ -1254,334 +986,6 @@ export const BlueReceiveButtonIcon = props => {
|
|||
);
|
||||
};
|
||||
|
||||
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
|
||||
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
|
||||
const { colors } = useTheme();
|
||||
const { navigate } = useNavigation();
|
||||
const { txMetadata, wallets, preferredFiatCurrency, language } = useContext(BlueStorageContext);
|
||||
const containerStyle = useMemo(
|
||||
() => ({
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: colors.lightBorder,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 16,
|
||||
paddingRight: 0,
|
||||
}),
|
||||
[colors.lightBorder],
|
||||
);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (item.confirmations === 0) {
|
||||
return loc.transactions.pending;
|
||||
} else {
|
||||
return transactionTimeToReadable(item.received);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.confirmations, item.received, language]);
|
||||
const txMemo = txMetadata[item.hash]?.memo ?? '';
|
||||
const subtitle = useMemo(() => {
|
||||
let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
|
||||
if (sub !== '') sub += ' ';
|
||||
sub += txMemo;
|
||||
if (item.memo) sub += item.memo;
|
||||
return sub || null;
|
||||
}, [txMemo, item.confirmations, item.memo]);
|
||||
|
||||
const rowTitle = useMemo(() => {
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = '0';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
} else {
|
||||
return loc.lnd.expired;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, itemPriceUnit, preferredFiatCurrency]);
|
||||
|
||||
const rowTitleStyle = useMemo(() => {
|
||||
let color = colors.successColor;
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
color = colors.successColor;
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
color = colors.successColor;
|
||||
} else {
|
||||
color = '#9AA0AA';
|
||||
}
|
||||
}
|
||||
} else if (item.value / 100000000 < 0) {
|
||||
color = colors.foregroundColor;
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
textAlign: 'right',
|
||||
width: 96,
|
||||
};
|
||||
}, [item, colors.foregroundColor, colors.successColor]);
|
||||
|
||||
const avatar = useMemo(() => {
|
||||
// is it lightning refill tx?
|
||||
if (item.category === 'receive' && item.confirmations < 3) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type && item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (!item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.confirmations) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
} else if (item.value < 0) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}, [item]);
|
||||
|
||||
useEffect(() => {
|
||||
setSubtitleNumberOfLines(1);
|
||||
}, [subtitle]);
|
||||
|
||||
const onPress = useCallback(async () => {
|
||||
if (item.hash) {
|
||||
navigate('TransactionStatus', { hash: item.hash });
|
||||
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
|
||||
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
|
||||
if (lightningWallet.length === 1) {
|
||||
try {
|
||||
// is it a successful lnurl-pay?
|
||||
const LN = new Lnurl(false, AsyncStorage);
|
||||
let paymentHash = item.payment_hash;
|
||||
if (typeof paymentHash === 'object') {
|
||||
paymentHash = Buffer.from(paymentHash.data).toString('hex');
|
||||
}
|
||||
const loaded = await LN.loadSuccessfulPayment(paymentHash);
|
||||
if (loaded) {
|
||||
NavigationService.navigate('ScanLndInvoiceRoot', {
|
||||
screen: 'LnurlPaySuccess',
|
||||
params: {
|
||||
paymentHash,
|
||||
justPaid: false,
|
||||
fromWalletID: lightningWallet[0].getID(),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
navigate('LNDViewInvoice', {
|
||||
invoice: item,
|
||||
walletID: lightningWallet[0].getID(),
|
||||
isModal: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, wallets]);
|
||||
|
||||
const handleOnExpandNote = useCallback(() => {
|
||||
setSubtitleNumberOfLines(0);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subtitle]);
|
||||
|
||||
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
|
||||
|
||||
const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]);
|
||||
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
|
||||
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle), [subtitle]);
|
||||
const handleOnViewOnBlockExplorer = useCallback(() => {
|
||||
const url = `https://mempool.space/tx/${item.hash}`;
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (supported) {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
});
|
||||
}, [item.hash]);
|
||||
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
|
||||
Clipboard.setString(`https://mempool.space/tx/${item.hash}`);
|
||||
}, [item.hash]);
|
||||
|
||||
const onToolTipPress = useCallback(id => {
|
||||
if (id === 'copyAmount') {
|
||||
handleOnCopyAmountTap();
|
||||
} else if (id === 'copyNote') {
|
||||
handleOnCopyNote();
|
||||
} else if (id === 'open_in_blockExplorer') {
|
||||
handleOnViewOnBlockExplorer();
|
||||
} else if (id === 'expandNote') {
|
||||
handleOnExpandNote();
|
||||
} else if (id === 'copy_blockExplorer') {
|
||||
handleCopyOpenInBlockExplorerPress();
|
||||
} else if (id === 'copyTX_ID') {
|
||||
handleOnCopyTransactionID();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const actions = [];
|
||||
if (rowTitle !== loc.lnd.expired) {
|
||||
actions.push({
|
||||
id: 'copyAmount',
|
||||
text: `${loc.transactions.details_copy} ${loc.send.create_amount}`,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (subtitle) {
|
||||
actions.push({
|
||||
id: 'copyNote',
|
||||
text: `${loc.transactions.details_copy} ${loc.transactions.note}`,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (item.hash) {
|
||||
actions.push(
|
||||
{
|
||||
id: 'copyTX_ID',
|
||||
text: `${loc.transactions.details_copy} ${loc.transactions.txid}`,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'copy_blockExplorer',
|
||||
text: `${loc.transactions.details_copy} ${loc.transactions.block_explorer_link}`,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'link',
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
|
||||
|
||||
const toolTipSubMenu = useMemo(() => {
|
||||
const submenu = {
|
||||
menuOptions: ['displayInline'], // <- set the `menuOptions` property
|
||||
menuItems: [],
|
||||
menuTitle: '',
|
||||
};
|
||||
if (item.hash) {
|
||||
submenu.menuItems.push({
|
||||
actionKey: 'open_in_blockExplorer',
|
||||
actionTitle: loc.transactions.details_show_in_block_explorer,
|
||||
});
|
||||
}
|
||||
if (subtitle && subtitleNumberOfLines === 1) {
|
||||
submenu.menuItems.push({
|
||||
actionKey: 'expandNote',
|
||||
actionTitle: loc.transactions.expand_note,
|
||||
});
|
||||
}
|
||||
return submenu;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={{ marginHorizontal: 4 }}>
|
||||
<ToolTipMenu actions={toolTipActions} submenu={toolTipSubMenu} onPress={onToolTipPress}>
|
||||
<BlueListItem
|
||||
leftAvatar={avatar}
|
||||
title={title}
|
||||
subtitleNumberOfLines={subtitleNumberOfLines}
|
||||
subtitle={subtitle}
|
||||
subtitleProps={subtitleProps}
|
||||
onPress={onPress}
|
||||
chevron={false}
|
||||
Component={TouchableOpacity}
|
||||
rightTitle={rowTitle}
|
||||
rightTitleStyle={rowTitleStyle}
|
||||
containerStyle={containerStyle}
|
||||
/>
|
||||
</ToolTipMenu>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export class BlueReplaceFeeSuggestions extends Component {
|
||||
static propTypes = {
|
||||
onFeeSelected: PropTypes.func.isRequired,
|
||||
|
|
377
components/TransactionListItem.js
Normal file
377
components/TransactionListItem.js
Normal file
|
@ -0,0 +1,377 @@
|
|||
/* eslint react/prop-types: "off" */
|
||||
import React, { useState, useMemo, useCallback, useContext, useEffect } from 'react';
|
||||
import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
import * as NavigationService from '../NavigationService';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useNavigation, useTheme } from '@react-navigation/native';
|
||||
import loc, { formatBalanceWithoutSuffix, transactionTimeToReadable } from '../loc';
|
||||
import Lnurl from '../class/lnurl';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import {
|
||||
BlueListItem,
|
||||
BlueTransactionExpiredIcon,
|
||||
BlueTransactionIncomingIcon,
|
||||
BlueTransactionOffchainIcon,
|
||||
BlueTransactionOffchainIncomingIcon,
|
||||
BlueTransactionOnchainIcon,
|
||||
BlueTransactionOutgoingIcon,
|
||||
BlueTransactionPendingIcon,
|
||||
} from '../BlueComponents';
|
||||
|
||||
export const TransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC }) => {
|
||||
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
|
||||
const { colors } = useTheme();
|
||||
const { navigate } = useNavigation();
|
||||
const { txMetadata, wallets, preferredFiatCurrency, language } = useContext(BlueStorageContext);
|
||||
const containerStyle = useMemo(
|
||||
() => ({
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: colors.lightBorder,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 16,
|
||||
paddingRight: 0,
|
||||
}),
|
||||
[colors.lightBorder],
|
||||
);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (item.confirmations === 0) {
|
||||
return loc.transactions.pending;
|
||||
} else {
|
||||
return transactionTimeToReadable(item.received);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.confirmations, item.received, language]);
|
||||
const txMemo = txMetadata[item.hash]?.memo ?? '';
|
||||
const subtitle = useMemo(() => {
|
||||
let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
|
||||
if (sub !== '') sub += ' ';
|
||||
sub += txMemo;
|
||||
if (item.memo) sub += item.memo;
|
||||
return sub || null;
|
||||
}, [txMemo, item.confirmations, item.memo]);
|
||||
|
||||
const rowTitle = useMemo(() => {
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = '0';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
} else {
|
||||
return loc.lnd.expired;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, itemPriceUnit, preferredFiatCurrency]);
|
||||
|
||||
const rowTitleStyle = useMemo(() => {
|
||||
let color = colors.successColor;
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
color = colors.successColor;
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
color = colors.successColor;
|
||||
} else {
|
||||
color = '#9AA0AA';
|
||||
}
|
||||
}
|
||||
} else if (item.value / 100000000 < 0) {
|
||||
color = colors.foregroundColor;
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
textAlign: 'right',
|
||||
width: 96,
|
||||
};
|
||||
}, [item, colors.foregroundColor, colors.successColor]);
|
||||
|
||||
const avatar = useMemo(() => {
|
||||
// is it lightning refill tx?
|
||||
if (item.category === 'receive' && item.confirmations < 3) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type && item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (!item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.confirmations) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
} else if (item.value < 0) {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.iconWidth}>
|
||||
<BlueTransactionIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}, [item]);
|
||||
|
||||
useEffect(() => {
|
||||
setSubtitleNumberOfLines(1);
|
||||
}, [subtitle]);
|
||||
|
||||
const onPress = useCallback(async () => {
|
||||
if (item.hash) {
|
||||
navigate('TransactionStatus', { hash: item.hash });
|
||||
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
|
||||
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
|
||||
if (lightningWallet.length === 1) {
|
||||
try {
|
||||
// is it a successful lnurl-pay?
|
||||
const LN = new Lnurl(false, AsyncStorage);
|
||||
let paymentHash = item.payment_hash;
|
||||
if (typeof paymentHash === 'object') {
|
||||
paymentHash = Buffer.from(paymentHash.data).toString('hex');
|
||||
}
|
||||
const loaded = await LN.loadSuccessfulPayment(paymentHash);
|
||||
if (loaded) {
|
||||
NavigationService.navigate('ScanLndInvoiceRoot', {
|
||||
screen: 'LnurlPaySuccess',
|
||||
params: {
|
||||
paymentHash,
|
||||
justPaid: false,
|
||||
fromWalletID: lightningWallet[0].getID(),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
navigate('LNDViewInvoice', {
|
||||
invoice: item,
|
||||
walletID: lightningWallet[0].getID(),
|
||||
isModal: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item, wallets]);
|
||||
|
||||
const handleOnExpandNote = useCallback(() => {
|
||||
setSubtitleNumberOfLines(0);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [subtitle]);
|
||||
|
||||
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
|
||||
|
||||
const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]);
|
||||
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
|
||||
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle), [subtitle]);
|
||||
const handleOnViewOnBlockExplorer = useCallback(() => {
|
||||
const url = `https://mempool.space/tx/${item.hash}`;
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (supported) {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
});
|
||||
}, [item.hash]);
|
||||
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
|
||||
Clipboard.setString(`https://mempool.space/tx/${item.hash}`);
|
||||
}, [item.hash]);
|
||||
|
||||
const onToolTipPress = useCallback(id => {
|
||||
if (id === TransactionListItem.actionKeys.CopyAmount) {
|
||||
handleOnCopyAmountTap();
|
||||
} else if (id === TransactionListItem.actionKeys.CopyNote) {
|
||||
handleOnCopyNote();
|
||||
} else if (id === TransactionListItem.actionKeys.OpenInBlockExplorer) {
|
||||
handleOnViewOnBlockExplorer();
|
||||
} else if (id === TransactionListItem.actionKeys.ExpandNote) {
|
||||
handleOnExpandNote();
|
||||
} else if (id === TransactionListItem.actionKeys.CopyBlockExplorerLink) {
|
||||
handleCopyOpenInBlockExplorerPress();
|
||||
} else if (id === TransactionListItem.actionKeys.CopyTXID) {
|
||||
handleOnCopyTransactionID();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const actions = [];
|
||||
if (rowTitle !== loc.lnd.expired) {
|
||||
actions.push({
|
||||
id: TransactionListItem.actionKeys.CopyAmount,
|
||||
text: `${loc.transactions.details_copy} ${loc.send.create_amount}`,
|
||||
icon: TransactionListItem.actionIcons.Clipboard,
|
||||
});
|
||||
}
|
||||
|
||||
if (subtitle) {
|
||||
actions.push({
|
||||
id: TransactionListItem.actionKeys.CopyNote,
|
||||
text: `${loc.transactions.details_copy} ${loc.transactions.note}`,
|
||||
icon: TransactionListItem.actionIcons.Clipboard,
|
||||
});
|
||||
}
|
||||
if (item.hash) {
|
||||
actions.push(
|
||||
{
|
||||
id: TransactionListItem.actionKeys.CopyTXID,
|
||||
text: `${loc.transactions.details_copy} ${loc.transactions.txid}`,
|
||||
icon: TransactionListItem.actionIcons.Clipboard,
|
||||
},
|
||||
{
|
||||
id: TransactionListItem.actionKeys.CopyBlockExplorerLink,
|
||||
text: `${loc.transactions.details_copy} ${loc.transactions.block_explorer_link}`,
|
||||
icon: TransactionListItem.actionIcons.Link,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
|
||||
|
||||
const toolTipSubMenu = useMemo(() => {
|
||||
const submenu = {
|
||||
menuOptions: ['displayInline'], // <- set the `menuOptions` property
|
||||
menuItems: [],
|
||||
menuTitle: '',
|
||||
};
|
||||
if (item.hash) {
|
||||
submenu.menuItems.push({
|
||||
actionKey: TransactionListItem.actionKeys.OpenInBlockExplorer,
|
||||
actionTitle: loc.transactions.details_show_in_block_explorer,
|
||||
icon: TransactionListItem.actionIcons.Link,
|
||||
});
|
||||
}
|
||||
if (subtitle && subtitleNumberOfLines === 1) {
|
||||
submenu.menuItems.push({
|
||||
actionKey: TransactionListItem.actionKeys.ExpandNote,
|
||||
actionTitle: loc.transactions.expand_note,
|
||||
icon: TransactionListItem.actionIcons.Note,
|
||||
});
|
||||
}
|
||||
return submenu;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ToolTipMenu actions={toolTipActions} submenu={toolTipSubMenu} onPress={onToolTipPress}>
|
||||
<BlueListItem
|
||||
leftAvatar={avatar}
|
||||
title={title}
|
||||
subtitleNumberOfLines={subtitleNumberOfLines}
|
||||
subtitle={subtitle}
|
||||
subtitleProps={subtitleProps}
|
||||
onPress={onPress}
|
||||
chevron={false}
|
||||
Component={TouchableOpacity}
|
||||
rightTitle={rowTitle}
|
||||
rightTitleStyle={rowTitleStyle}
|
||||
containerStyle={containerStyle}
|
||||
/>
|
||||
</ToolTipMenu>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
TransactionListItem.actionKeys = {
|
||||
CopyTXID: 'copyTX_ID',
|
||||
CopyBlockExplorerLink: 'copy_blockExplorer',
|
||||
ExpandNote: 'expandNote',
|
||||
OpenInBlockExplorer: 'open_in_blockExplorer',
|
||||
CopyAmount: 'copyAmount',
|
||||
CopyNote: 'copyNote',
|
||||
};
|
||||
|
||||
TransactionListItem.actionIcons = {
|
||||
Eye: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'eye',
|
||||
},
|
||||
EyeSlash: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'eye.slash',
|
||||
},
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
Link: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'link',
|
||||
},
|
||||
Note: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'note.text',
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
iconWidth: { width: 25 },
|
||||
container: { marginHorizontal: 4 },
|
||||
});
|
267
components/TransactionsNavigationHeader.js
Normal file
267
components/TransactionsNavigationHeader.js
Normal file
|
@ -0,0 +1,267 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Image, Text, TouchableOpacity, View, InteractionManager, I18nManager, StyleSheet } from 'react-native';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { LightningCustodianWallet, MultisigHDWallet } from '../class';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
import WalletGradient from '../class/wallet-gradient';
|
||||
|
||||
import Biometric from '../class/biometrics';
|
||||
import loc, { formatBalance } from '../loc';
|
||||
import { BlueStorageContext } from '../blue_modules/storage-context';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import { BluePrivateBalance } from '../BlueComponents';
|
||||
|
||||
export default class TransactionsNavigationHeader extends Component {
|
||||
static propTypes = {
|
||||
wallet: PropTypes.shape().isRequired,
|
||||
onWalletUnitChange: PropTypes.func,
|
||||
navigation: PropTypes.shape(),
|
||||
onManageFundsPressed: PropTypes.func,
|
||||
};
|
||||
|
||||
static actionKeys = {
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
WalletBalanceVisibility: 'walletBalanceVisibility',
|
||||
};
|
||||
|
||||
static actionIcons = {
|
||||
Eye: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'eye',
|
||||
},
|
||||
EyeSlash: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'eye.slash',
|
||||
},
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props) {
|
||||
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange };
|
||||
}
|
||||
|
||||
static contextType = BlueStorageContext;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
wallet: props.wallet,
|
||||
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
|
||||
allowOnchainAddress: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleCopyPress = _item => {
|
||||
Clipboard.setString(formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
|
||||
};
|
||||
|
||||
componentDidUpdate(prevState) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (prevState.wallet.getID() !== this.state.wallet.getID() && this.state.wallet.type === LightningCustodianWallet.type) {
|
||||
this.verifyIfWalletAllowsOnchainAddress();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
verifyIfWalletAllowsOnchainAddress = () => {
|
||||
if (this.state.wallet.type === LightningCustodianWallet.type) {
|
||||
this.state.wallet
|
||||
.allowOnchainAddress()
|
||||
.then(value => this.setState({ allowOnchainAddress: value }))
|
||||
.catch(e => {
|
||||
console.log('This Lndhub wallet does not have an onchain address API.');
|
||||
this.setState({ allowOnchainAddress: false });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.verifyIfWalletAllowsOnchainAddress();
|
||||
}
|
||||
|
||||
handleBalanceVisibility = async _item => {
|
||||
const wallet = this.state.wallet;
|
||||
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled && wallet.hideBalance) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
return this.props.navigation.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
wallet.hideBalance = !wallet.hideBalance;
|
||||
this.setState({ wallet });
|
||||
await this.context.saveToDisk();
|
||||
};
|
||||
|
||||
changeWalletBalanceUnit = () => {
|
||||
let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit();
|
||||
const wallet = this.state.wallet;
|
||||
if (walletPreviousPreferredUnit === BitcoinUnit.BTC) {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.SATS;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.BTC;
|
||||
} else if (walletPreviousPreferredUnit === BitcoinUnit.SATS) {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.LOCAL_CURRENCY;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.SATS;
|
||||
} else if (walletPreviousPreferredUnit === BitcoinUnit.LOCAL_CURRENCY) {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.BTC;
|
||||
} else {
|
||||
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
|
||||
walletPreviousPreferredUnit = BitcoinUnit.BTC;
|
||||
}
|
||||
|
||||
this.setState({ wallet, walletPreviousPreferredUnit: walletPreviousPreferredUnit }, () => {
|
||||
this.props.onWalletUnitChange(wallet);
|
||||
});
|
||||
};
|
||||
|
||||
manageFundsPressed = () => {
|
||||
this.props.onManageFundsPressed();
|
||||
};
|
||||
|
||||
onPress = id => {
|
||||
if (id === TransactionsNavigationHeader.actionKeys.WalletBalanceVisibility) {
|
||||
this.handleBalanceVisibility();
|
||||
} else if (id === TransactionsNavigationHeader.actionKeys.CopyToClipboard) {
|
||||
this.handleCopyPress();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const balance =
|
||||
!this.state.wallet.hideBalance &&
|
||||
formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString();
|
||||
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
|
||||
style={styles.lineaderGradient}
|
||||
{...WalletGradient.linearGradientProps(this.state.wallet.type)}
|
||||
>
|
||||
<Image
|
||||
source={(() => {
|
||||
switch (this.state.wallet.type) {
|
||||
case LightningCustodianWallet.type:
|
||||
return I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
|
||||
case MultisigHDWallet.type:
|
||||
return I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png');
|
||||
default:
|
||||
return I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png');
|
||||
}
|
||||
})()}
|
||||
style={styles.chainIcon}
|
||||
/>
|
||||
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel}>
|
||||
{this.state.wallet.getLabel()}
|
||||
</Text>
|
||||
<ToolTipMenu
|
||||
title={loc.wallets.balance}
|
||||
onPress={this.onPress}
|
||||
actions={
|
||||
this.state.wallet.hideBalance
|
||||
? [
|
||||
{
|
||||
id: TransactionsNavigationHeader.actionKeys.WalletBalanceVisibility,
|
||||
text: loc.transactions.details_balance_show,
|
||||
icon: TransactionsNavigationHeader.actionIcons.Eye,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: TransactionsNavigationHeader.actionKeys.WalletBalanceVisibility,
|
||||
text: loc.transactions.details_balance_hide,
|
||||
icon: TransactionsNavigationHeader.actionIcons.EyeSlash,
|
||||
},
|
||||
{
|
||||
id: TransactionsNavigationHeader.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: TransactionsNavigationHeader.actionIcons.Clipboard,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<TouchableOpacity accessibilityRole="button" style={styles.balance} onPress={this.changeWalletBalanceUnit}>
|
||||
{this.state.wallet.hideBalance ? (
|
||||
<BluePrivateBalance />
|
||||
) : (
|
||||
<Text
|
||||
testID="WalletBalance"
|
||||
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
|
||||
numberOfLines={1}
|
||||
adjustsFontSizeToFit
|
||||
style={styles.walletBalance}
|
||||
>
|
||||
{balance}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</ToolTipMenu>
|
||||
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
|
||||
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
|
||||
<View style={styles.manageFundsButton}>
|
||||
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{this.state.wallet.type === MultisigHDWallet.type && (
|
||||
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
|
||||
<View style={styles.manageFundsButton}>
|
||||
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</LinearGradient>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
lineaderGradient: {
|
||||
padding: 15,
|
||||
minHeight: 140,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
chainIcon: {
|
||||
width: 99,
|
||||
height: 94,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
walletLabel: {
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 19,
|
||||
color: '#fff',
|
||||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||
},
|
||||
walletBalance: {
|
||||
backgroundColor: 'transparent',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 36,
|
||||
color: '#fff',
|
||||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||
},
|
||||
manageFundsButton: {
|
||||
marginTop: 14,
|
||||
marginBottom: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: 9,
|
||||
minHeight: 39,
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: 12,
|
||||
height: 39,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
manageFundsButtonText: {
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
});
|
|
@ -66,11 +66,11 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }) =>
|
|||
};
|
||||
|
||||
const onToolTipPress = id => {
|
||||
if (id === 'copyToClipboard') {
|
||||
if (id === AddressItem.actionKeys.CopyToClipboard) {
|
||||
handleCopyPress();
|
||||
} else if (id === 'share') {
|
||||
} else if (id === AddressItem.actionKeys.Share) {
|
||||
handleSharePress();
|
||||
} else if (id === 'signVerify') {
|
||||
} else if (id === AddressItem.actionKeys.SignVerify) {
|
||||
navigateToSignVerify();
|
||||
}
|
||||
};
|
||||
|
@ -78,31 +78,22 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }) =>
|
|||
const getAvailableActions = () => {
|
||||
const actions = [
|
||||
{
|
||||
id: 'copyToClipboard',
|
||||
id: AddressItem.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
icon: AddressItem.actionIcons.Clipboard,
|
||||
},
|
||||
{
|
||||
id: 'share',
|
||||
id: AddressItem.actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
icon: AddressItem.actionIcons.Share,
|
||||
},
|
||||
];
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: 'signVerify',
|
||||
id: AddressItem.actionKeys.SignVerify,
|
||||
text: loc.addresses.sign_title,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'signature',
|
||||
},
|
||||
icon: AddressItem.actionIcons.Signature,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -136,6 +127,27 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }) =>
|
|||
return render();
|
||||
};
|
||||
|
||||
AddressItem.actionKeys = {
|
||||
Share: 'share',
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
SignVerify: 'signVerify',
|
||||
};
|
||||
|
||||
AddressItem.actionIcons = {
|
||||
Signature: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'signature',
|
||||
},
|
||||
Share: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
address: {
|
||||
fontWeight: 'bold',
|
||||
|
|
|
@ -65,10 +65,8 @@
|
|||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "8"
|
||||
notificationPayloadFile = "BlueWalletWatch Extension/PushNotificationPayload.apns">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/BlueWallet">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
|
||||
|
@ -76,7 +74,7 @@
|
|||
BlueprintName = "BlueWalletWatch"
|
||||
ReferencedContainer = "container:BlueWallet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -86,10 +84,8 @@
|
|||
debugDocumentVersioning = "YES"
|
||||
launchAutomaticallySubstyle = "8"
|
||||
notificationPayloadFile = "BlueWalletWatch Extension/PushNotificationPayload.apns">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/BlueWallet">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
|
||||
|
@ -97,16 +93,7 @@
|
|||
BlueprintName = "BlueWalletWatch"
|
||||
ReferencedContainer = "container:BlueWallet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
|
||||
BuildableName = "BlueWalletWatch.app"
|
||||
BlueprintName = "BlueWalletWatch"
|
||||
ReferencedContainer = "container:BlueWallet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
|
|
|
@ -64,10 +64,8 @@
|
|||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
notificationPayloadFile = "BlueWalletWatch Extension/PushNotificationPayload.apns">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/BlueWallet">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
|
||||
|
@ -75,7 +73,7 @@
|
|||
BlueprintName = "BlueWalletWatch"
|
||||
ReferencedContainer = "container:BlueWallet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
@ -83,10 +81,8 @@
|
|||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/BlueWallet">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
|
||||
|
@ -94,16 +90,7 @@
|
|||
BlueprintName = "BlueWalletWatch"
|
||||
ReferencedContainer = "container:BlueWallet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B40D4E2F225841EC00428FCC"
|
||||
BuildableName = "BlueWalletWatch.app"
|
||||
BlueprintName = "BlueWalletWatch"
|
||||
ReferencedContainer = "container:BlueWallet.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -17798,7 +17798,7 @@
|
|||
},
|
||||
"react-native-blue-crypto": {
|
||||
"version": "git+https://github.com/BlueWallet/react-native-blue-crypto.git#841cd9e5abdc468888c76682c95a944ec34c1be7",
|
||||
"from": "git+https://github.com/BlueWallet/react-native-blue-crypto.git"
|
||||
"from": "git+https://github.com/BlueWallet/react-native-blue-crypto.git#841cd9e5abdc468888c76682c95a944ec34c1be7"
|
||||
},
|
||||
"react-native-camera": {
|
||||
"version": "4.0.1",
|
||||
|
|
|
@ -257,12 +257,9 @@ const LNDViewInvoice = () => {
|
|||
<ToolTipMenu
|
||||
actions={[
|
||||
{
|
||||
id: 'shareQRCode',
|
||||
id: LNDViewInvoice.actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
icon: LNDViewInvoice.actionIcons.Share,
|
||||
},
|
||||
]}
|
||||
onPress={handleShareQRCode}
|
||||
|
@ -312,6 +309,17 @@ const LNDViewInvoice = () => {
|
|||
);
|
||||
};
|
||||
|
||||
LNDViewInvoice.actionKeys = {
|
||||
Share: 'share',
|
||||
};
|
||||
|
||||
LNDViewInvoice.actionIcons = {
|
||||
Share: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
|
|
|
@ -162,12 +162,9 @@ const ReceiveDetails = () => {
|
|||
<ToolTipMenu
|
||||
actions={[
|
||||
{
|
||||
id: 'shareQRCode',
|
||||
id: ReceiveDetails.actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
icon: ReceiveDetails.actionIcons.Share,
|
||||
},
|
||||
]}
|
||||
onPress={handleShareQRCode}
|
||||
|
@ -383,6 +380,17 @@ const ReceiveDetails = () => {
|
|||
);
|
||||
};
|
||||
|
||||
ReceiveDetails.actionKeys = {
|
||||
Share: 'share',
|
||||
};
|
||||
|
||||
ReceiveDetails.actionIcons = {
|
||||
Share: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
};
|
||||
|
||||
ReceiveDetails.navigationOptions = navigationStyle(
|
||||
{
|
||||
closeButton: true,
|
||||
|
|
|
@ -227,12 +227,9 @@ const TransactionsDetails = () => {
|
|||
<ToolTipMenu
|
||||
actions={[
|
||||
{
|
||||
id: 'copyToClipboard',
|
||||
id: TransactionsDetails.actionKeys.Clipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
icon: TransactionsDetails.actionIcons.Clipboard,
|
||||
},
|
||||
]}
|
||||
onPress={handleCopyPress}
|
||||
|
@ -251,6 +248,17 @@ const TransactionsDetails = () => {
|
|||
);
|
||||
};
|
||||
|
||||
TransactionsDetails.actionKeys = {
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
};
|
||||
|
||||
TransactionsDetails.actionIcons = {
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'arrow.right.doc.on.clipboard',
|
||||
},
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scroll: {
|
||||
flex: 1,
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
useColorScheme,
|
||||
I18nManager,
|
||||
} from 'react-native';
|
||||
import { BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents';
|
||||
import { BlueHeaderDefaultMain } from '../../BlueComponents';
|
||||
import WalletsCarousel from '../../components/WalletsCarousel';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
|
||||
|
@ -28,6 +28,7 @@ import { BlueStorageContext } from '../../blue_modules/storage-context';
|
|||
import { isDesktop, isMacCatalina, isTablet } from '../../blue_modules/environment';
|
||||
import BlueClipboard from '../../blue_modules/clipboard';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
import { TransactionListItem } from '../../components/TransactionListItem';
|
||||
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
const scanqrHelper = require('../../helpers/scan-qr');
|
||||
|
@ -213,7 +214,7 @@ const WalletsList = () => {
|
|||
const renderTransactionListsRow = data => {
|
||||
return (
|
||||
<View style={styles.transaction}>
|
||||
<BlueTransactionListItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} />
|
||||
<TransactionListItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ import { launchImageLibrary } from 'react-native-image-picker';
|
|||
import { Icon } from 'react-native-elements';
|
||||
import { useRoute, useNavigation, useTheme, useFocusEffect } from '@react-navigation/native';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import { BlueTransactionListItem, BlueWalletNavigationHeader, BlueAlertWalletExportReminder, BlueListItem } from '../../BlueComponents';
|
||||
import { BlueAlertWalletExportReminder, BlueListItem } from '../../BlueComponents';
|
||||
import WalletGradient from '../../class/wallet-gradient';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
import { LightningCustodianWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
|
||||
|
@ -37,6 +37,8 @@ import BuyBitcoin from './buyBitcoin';
|
|||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
import { isDesktop, isMacCatalina } from '../../blue_modules/environment';
|
||||
import BlueClipboard from '../../blue_modules/clipboard';
|
||||
import TransactionsNavigationHeader from '../../components/TransactionsNavigationHeader';
|
||||
import { TransactionListItem } from '../../components/TransactionListItem';
|
||||
|
||||
const fs = require('../../blue_modules/fs');
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
|
@ -449,7 +451,7 @@ const WalletTransactions = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const renderItem = item => <BlueTransactionListItem item={item.item} itemPriceUnit={itemPriceUnit} timeElapsed={timeElapsed} />;
|
||||
const renderItem = item => <TransactionListItem item={item.item} itemPriceUnit={itemPriceUnit} timeElapsed={timeElapsed} />;
|
||||
|
||||
const onBarCodeRead = ret => {
|
||||
if (!isLoading) {
|
||||
|
@ -611,7 +613,7 @@ const WalletTransactions = () => {
|
|||
url={`https://blockpath.com/search/addr?q=${wallet.getXpub()}`}
|
||||
/>
|
||||
)}
|
||||
<BlueWalletNavigationHeader
|
||||
<TransactionsNavigationHeader
|
||||
wallet={wallet}
|
||||
onWalletUnitChange={passedWallet =>
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
|
|
|
@ -87,12 +87,9 @@ const WalletXpub = () => {
|
|||
<ToolTipMenu
|
||||
actions={[
|
||||
{
|
||||
id: 'shareQRCode',
|
||||
id: WalletXpub.actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
icon: WalletXpub.actionIcons.Share,
|
||||
},
|
||||
]}
|
||||
onPress={handleShareQRCode}
|
||||
|
@ -118,6 +115,17 @@ const WalletXpub = () => {
|
|||
);
|
||||
};
|
||||
|
||||
WalletXpub.actionKeys = {
|
||||
Share: 'share',
|
||||
};
|
||||
|
||||
WalletXpub.actionIcons = {
|
||||
Share: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
};
|
||||
|
||||
WalletXpub.navigationOptions = navigationStyle(
|
||||
{
|
||||
closeButton: true,
|
||||
|
|
Loading…
Add table
Reference in a new issue