FIX: Improve broadcasting experience #66 (#107)

ADD: New transaction Confirm UI
REF: Improve broadcasting experience #66
FIX: Satoshis to local currency in Send screen
FIX: Fixed Wallet export QR code width
FIX: Invalid BIP21 URI at value #108
FIX: #109  TypeError: undefined is not an object (evaluating 'this.state.tx.inputs.length')
This commit is contained in:
Marcos Rodriguez Vélez 2018-10-27 11:13:09 -04:00 committed by Igor Korsakov
parent a43b4b99a1
commit 6bc435fd48
16 changed files with 472 additions and 92 deletions

View file

@ -21,6 +21,8 @@ import receiveDetails from './screen/receive/details';
import sendDetails from './screen/send/details';
import sendScanQrAddress from './screen/send/scanQrAddress';
import sendCreate from './screen/send/create';
import Confirm from './screen/send/confirm';
import Success from './screen/send/success';
import ManageFunds from './screen/lnd/manageFunds';
import ScanLndInvoice from './screen/lnd/scanLndInvoice';
@ -55,10 +57,22 @@ const WalletsStackNavigator = createStackNavigator({
},
});
const SuccessTransactionStackNavigation = createStackNavigator(
{
Success: {
screen: Success,
},
},
{ mode: 'modal', headerMode: 'none' },
);
const CreateTransactionStackNavigator = createStackNavigator({
SendDetails: {
screen: sendDetails,
},
Confirm: {
screen: Confirm,
},
CreateTransaction: {
screen: sendCreate,
navigationOptions: {
@ -109,6 +123,13 @@ const Tabs = createStackNavigator(
TransactionDetails: {
screen: details,
},
Success: {
screen: SuccessTransactionStackNavigation,
navigationOptions: {
header: null,
gesturesEnabled: false,
},
},
RBF: {
screen: rbf,
},

View file

@ -89,7 +89,7 @@ module.exports = {
conf: 'conf',
},
details: {
title: 'transaction details',
title: 'Transaction details',
from: 'Input',
to: 'Output',
copy: 'Copy',
@ -103,6 +103,7 @@ module.exports = {
fee_field_is_not_valid: 'Fee field is not valid',
address_field_is_not_valid: 'Address field is not valid',
total_exceeds_balance: 'The sending amount exceeds the available balance.',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
address: 'address',
amount_placeholder: 'amount to send (in BTC)',
fee_placeholder: 'plus transaction fee (in BTC)',
@ -113,7 +114,15 @@ module.exports = {
create: 'Create',
remaining_balance: 'Remaining balance',
},
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
create: {
details: 'Details',
title: 'create transaction',
error: 'Error creating transaction. Invalid address or send amount?',
go_back: 'Go Back',

View file

@ -97,6 +97,13 @@ module.exports = {
},
send: {
header: 'enviar',
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
details: {
title: 'Crear Transaccion',
amount_field_is_not_valid: 'La cantidad no es válida',
@ -106,6 +113,7 @@ module.exports = {
amount_placeholder: 'cantidad para enviar (in BTC)',
fee_placeholder: 'más tasa de transaccion (in BTC)',
note_placeholder: 'comentario (para ti mismo)',
create_tx_error: 'Se ha producido un error al crear la transacción. Por favor, asegúrese de que la dirección es válida.',
cancel: 'Cancelar',
scan: 'Escaniar',
address: 'Direccion',
@ -116,6 +124,7 @@ module.exports = {
},
create: {
title: 'Crear una Transaccion',
details: 'Detalles',
error: 'Error al crear una transacción. ¿Dirección o cantidad estan invalidas?',
go_back: 'Regresar',
this_is_hex: 'Este es representacion hex de transacción, firmado y listo para ser transmitido a la red. ¿Continuar?',

View file

@ -79,6 +79,8 @@ strings.formatBalance = (balance, unit) => {
return b.multipliedBy(1000000).toString() + ' ' + BitcoinUnit.BITS;
} else if (unit === BitcoinUnit.SATOSHIS) {
return (b.times(conversion).toString() + ' ' + BitcoinUnit.SATOSHIS).replace(/\./g, '');
} else if (unit === BitcoinUnit.SATS) {
return (b.times(conversion).toString() + ' ' + BitcoinUnit.SATS).replace(/\./g, '');
}
}
return balance + ' ' + BitcoinUnit.BTC;

View file

@ -98,6 +98,13 @@ module.exports = {
},
send: {
header: 'Enviar',
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
details: {
title: 'Criar Transacção',
amount_field_is_not_valid: 'Campo de quantia não é válido',
@ -106,6 +113,7 @@ module.exports = {
receiver_placeholder: 'endereço de envio aqui',
amount_placeholder: 'quantia a enviar (em BTC)',
fee_placeholder: 'mais a taxa de transacção (em BTC)',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
note_placeholder: 'Nota pessoal',
cancel: 'Cancelar',
scan: 'Scanear',
@ -117,6 +125,7 @@ module.exports = {
},
create: {
title: 'Criar Transacção',
details: 'Details',
error: 'Erro ao criar transação. Endereço inválido ou quantia?',
go_back: 'Voltar',
this_is_hex: 'Este é o hex da transação, assinado e pronto para ser difundido para a network. Continuar?',

View file

@ -97,6 +97,13 @@ module.exports = {
},
send: {
header: 'Enviar',
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
details: {
title: 'Criar Transacção',
amount_field_is_not_valid: 'Campo de quantia não é válido',
@ -105,6 +112,7 @@ module.exports = {
receiver_placeholder: 'endereço de envio aqui',
amount_placeholder: 'quantia a enviar (em BTC)',
fee_placeholder: 'mais a taxa de transacção (em BTC)',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
note_placeholder: 'Nota pessoal',
total_exceeds_balance: 'Total amount exceeds available balance.',
cancel: 'Cancelar',
@ -116,6 +124,7 @@ module.exports = {
},
create: {
title: 'Criar Transacção',
details: 'Details',
error: 'Erro ao criar transacção. Endereço inválido ou quantia?',
go_back: 'Voltar',
this_is_hex: 'Este é o hex da transacção, assinado e pronto para ser difundido para a network. Continuar?',

View file

@ -96,6 +96,13 @@ module.exports = {
},
send: {
header: 'Отправить',
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
details: {
title: 'Создать Транзакцию',
amount_field_is_not_valid: 'Поле не валидно',
@ -105,6 +112,7 @@ module.exports = {
amount_placeholder: 'сколько отправить (в BTC)',
fee_placeholder: 'плюс комиссия за перевод (в BTC)',
note_placeholder: 'примечание платежа',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
cancel: 'Отмена',
scan: 'Скан QR',
create: 'Создать',
@ -115,6 +123,7 @@ module.exports = {
},
create: {
title: 'Создать Транзакцию',
details: 'Details',
error: 'Ошибка при создании транзакции. Неправильный адрес назначения или недостаточно средств?',
go_back: 'Назад',
this_is_hex: 'Это данные транзакции. Транзакция подписана и готова быть транслирована в сеть. Продолжить?',

View file

@ -104,6 +104,7 @@ module.exports = {
receiver_placeholder: 'Адреса одержувача',
amount_placeholder: 'скільки відправити (в BTC)',
fee_placeholder: 'плюс комісія за переказ (в BTC)',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
note_placeholder: 'примітка платежу',
cancel: 'Відміна',
scan: 'Скан QR',
@ -113,8 +114,16 @@ module.exports = {
create: 'Створити',
remaining_balance: 'Залишок балансу',
},
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
create: {
title: 'Створити Транзакцію',
details: 'Details',
error: 'Помилка при створенні транзакції. Невiрна адреса призначення або недостатньо коштiв?',
go_back: 'Назад',
this_is_hex: 'Це дані транзакції. Транзакція підписана і готова бути трансльована в мережу. Продовжити?',

View file

@ -3,4 +3,5 @@ export const BitcoinUnit = Object.freeze({
MBTC: 'mBTC',
BITS: 'bits',
SATOSHIS: 'satoshis',
SATS: 'sats',
});

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "BlueWallet",
"version": "3.0.0",
"version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

173
screen/send/confirm.js Normal file
View file

@ -0,0 +1,173 @@
/* global alert */
import React, { Component } from 'react';
import { ActivityIndicator, TouchableOpacity, StyleSheet, View } from 'react-native';
import { Text } from 'react-native-elements';
import { BlueButton, SafeBlueArea, BlueCard, BlueSpacing40, BlueHeaderDefaultSub } from '../../BlueComponents';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import PropTypes from 'prop-types';
let loc = require('../../loc');
let EV = require('../../events');
export default class Confirm extends Component {
static navigationOptions = {
headerStyle: {
backgroundColor: '#FFFFFF',
borderBottomWidth: 0,
},
headerTintColor: '#0c2550',
};
constructor(props) {
super(props);
console.log('send/create constructor');
this.state = {
isLoading: false,
amount: props.navigation.getParam('amount'),
fee: props.navigation.getParam('fee'),
address: props.navigation.getParam('address'),
memo: props.navigation.getParam('memo'),
size: Math.round(props.navigation.getParam('tx').length / 2),
tx: props.navigation.getParam('tx'),
satoshiPerByte: props.navigation.getParam('satoshiPerByte'),
fromWallet: props.navigation.getParam('fromWallet'),
};
}
async componentDidMount() {
console.log('send/create - componentDidMount');
console.log('address = ', this.state.address);
}
broadcast() {
this.setState({ isLoading: true }, async () => {
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
console.log('broadcast result = ', result);
if (typeof result === 'string') {
result = JSON.parse(result);
}
this.setState({ isLoading: false });
if (result && result.error) {
alert(JSON.stringify(result.error));
} else {
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
this.props.navigation.navigate('Success', {
fee: Number(this.state.fee),
amount: this.state.amount,
address: this.state.address,
dismissModal: () => this.props.navigation.dismiss(),
});
}
});
}
render() {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 19 }}>
<BlueHeaderDefaultSub leftText={loc.send.confirm.header.toLowerCase()} rightComponent={null} />
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
<Text
style={{
color: '#0f5cc0',
fontSize: 36,
fontWeight: '600',
}}
>
{this.state.amount}
</Text>
<Text
style={{
color: '#0f5cc0',
fontSize: 16,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '600',
alignSelf: 'flex-end',
}}
>
{' ' + BitcoinUnit.BTC}
</Text>
</View>
<Text
style={{
color: '#37c0a1',
fontSize: 14,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '500',
alignSelf: 'center',
}}
>
{loc.send.create.fee}: {loc.formatBalance(this.state.fee, BitcoinUnit.SATS)}
</Text>
</BlueCard>
<BlueCard>
<Text style={styles.transactionDetailsTitle}>{loc.send.create.to}</Text>
<Text style={styles.transactionDetailsSubtitle}>{this.state.address}</Text>
<BlueSpacing40 />
{this.state.isLoading ? (
<ActivityIndicator />
) : (
<BlueButton
onPress={() => this.broadcast()}
title={loc.send.confirm.sendNow}
style={{ maxWidth: 263, paddingHorizontal: 56 }}
/>
)}
<TouchableOpacity
style={{ marginVertical: 24 }}
onPress={() =>
this.props.navigation.navigate('CreateTransaction', {
amount: this.state.amount,
fee: this.state.fee,
address: this.state.address,
memo: this.state.memo,
tx: this.state.tx,
satoshiPerByte: this.state.satoshiPerByte,
})
}
>
<Text style={{ color: '#0c2550', fontSize: 15, fontWeight: '500', alignSelf: 'center' }}>{loc.transactions.details.title}</Text>
</TouchableOpacity>
</BlueCard>
</SafeBlueArea>
);
}
}
const styles = StyleSheet.create({
transactionDetailsTitle: {
color: '#0c2550',
fontWeight: '500',
fontSize: 17,
marginBottom: 2,
},
transactionDetailsSubtitle: {
color: '#9aa0aa',
fontWeight: '500',
fontSize: 15,
marginBottom: 20,
},
});
Confirm.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
getParam: PropTypes.function,
navigate: PropTypes.function,
dismiss: PropTypes.function,
state: PropTypes.shape({
params: PropTypes.shape({
amount: PropTypes.string,
fee: PropTypes.number,
address: PropTypes.string,
memo: PropTypes.string,
fromWallet: PropTypes.shape({
fromAddress: PropTypes.string,
fromSecret: PropTypes.string,
}),
}),
}),
}),
};

View file

@ -1,13 +1,9 @@
/* global alert */
import React, { Component } from 'react';
import { TextInput, ActivityIndicator, TouchableOpacity, Clipboard, StyleSheet, ScrollView } from 'react-native';
import { TextInput, TouchableOpacity, Clipboard, StyleSheet, ScrollView } from 'react-native';
import { Text } from 'react-native-elements';
import { BlueButton, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
import { BlueHeaderDefaultSub, SafeBlueArea, BlueCard, BlueText } from '../../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
// let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let EV = require('../../events');
export default class SendCreate extends Component {
constructor(props) {
@ -16,14 +12,13 @@ export default class SendCreate extends Component {
this.state = {
isLoading: false,
amount: props.navigation.state.params.amount,
fee: props.navigation.state.params.fee,
address: props.navigation.state.params.address,
memo: props.navigation.state.params.memo,
amount: props.navigation.getParam('amount'),
fee: props.navigation.getParam('fee'),
address: props.navigation.getParam('address'),
memo: props.navigation.getParam('memo'),
size: Math.round(props.navigation.getParam('tx').length / 2),
tx: props.navigation.getParam('tx'),
satoshiPerByte: props.navigation.getParam('satoshiPerByte'),
fromWallet: props.navigation.getParam('fromWallet'),
};
}
@ -32,27 +27,10 @@ export default class SendCreate extends Component {
console.log('address = ', this.state.address);
}
broadcast() {
this.setState({ isLoading: true }, async () => {
let result = await this.state.fromWallet.broadcastTx(this.state.tx);
console.log('broadcast result = ', result);
if (typeof result === 'string') {
result = JSON.parse(result);
}
this.setState({ isLoading: false });
if (result && result.error) {
alert(JSON.stringify(result.error));
} else {
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
alert('Transaction has been successfully broadcasted. Your transaction ID is: ' + JSON.stringify(result.result));
this.props.navigation.navigate('Wallets');
}
});
}
render() {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 19 }}>
<BlueHeaderDefaultSub leftText={loc.send.create.details.toLowerCase()} rightComponent={null} />
<ScrollView>
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
<BlueText style={{ color: '#0c2550', fontWeight: '500' }}>{loc.send.create.this_is_hex}</BlueText>
@ -79,11 +57,6 @@ export default class SendCreate extends Component {
<TouchableOpacity style={{ marginVertical: 24 }} onPress={() => Clipboard.setString(this.state.tx)}>
<Text style={{ color: '#0c2550', fontSize: 15, fontWeight: '500', alignSelf: 'center' }}>Copy and broadcast later</Text>
</TouchableOpacity>
{this.state.isLoading ? (
<ActivityIndicator />
) : (
<BlueButton onPress={() => this.broadcast()} title={loc.send.details.send} style={{ maxWidth: 263, paddingHorizontal: 56 }} />
)}
</BlueCard>
<BlueCard>
<Text style={styles.transactionDetailsTitle}>{loc.send.create.to}</Text>
@ -130,16 +103,13 @@ SendCreate.propTypes = {
goBack: PropTypes.function,
getParam: PropTypes.function,
navigate: PropTypes.function,
dismiss: PropTypes.function,
state: PropTypes.shape({
params: PropTypes.shape({
amount: PropTypes.string,
fee: PropTypes.number,
address: PropTypes.string,
memo: PropTypes.string,
fromWallet: PropTypes.shape({
fromAddress: PropTypes.string,
fromSecret: PropTypes.string,
}),
}),
}),
}),

View file

@ -25,6 +25,7 @@ let BigNumber = require('bignumber.js');
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let bitcoin = require('bitcoinjs-lib');
let currency = require('../../currency');
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;
@ -46,7 +47,7 @@ export default class SendDetails extends Component {
let fromAddress;
if (props.navigation.state.params) fromAddress = props.navigation.state.params.fromAddress;
let fromSecret;
if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret;
if (props.navigation.state.params) fromSecret = props.navigation.state.params.fromSecret;
let fromWallet = {};
let startTime2 = Date.now();
@ -204,7 +205,14 @@ export default class SendDetails extends Component {
}
let startTime = Date.now();
tx = this.state.fromWallet.createTx(utxo, this.state.amount, fee, this.state.address, this.state.memo);
try {
tx = this.state.fromWallet.createTx(utxo, this.state.amount, fee, this.state.address, this.state.memo);
} catch (error) {
console.log(error);
alert(loc.send.details.create_tx_error);
this.setState({ isLoading: false });
return;
}
let endTime = Date.now();
console.log('create tx ', (endTime - startTime) / 1000, 'sec');
@ -238,15 +246,15 @@ export default class SendDetails extends Component {
await BlueApp.saveToDisk();
} catch (err) {
console.log(err);
alert(err);
alert(loc.send.details.create_tx_error);
this.setState({ isLoading: false });
return;
}
this.setState({ isLoading: false }, () =>
this.props.navigation.navigate('CreateTransaction', {
this.props.navigation.navigate('Confirm', {
amount: this.state.amount,
fee: fee.toFixed(8),
fee: Number(fee.toFixed(8)),
address: this.state.address,
memo: this.state.memo,
fromWallet: this.state.fromWallet,
@ -376,6 +384,11 @@ export default class SendDetails extends Component {
{' ' + BitcoinUnit.BTC}
</Text>
</View>
<View style={{ alignItems: 'center', marginBottom: 22, marginTop: 4 }}>
<Text style={{ fontSize: 18, color: '#d4d4d4', fontWeight: '600' }}>
{currency.satoshiToLocalCurrency(loc.formatBalanceWithoutSuffix(this.state.amount || 0, BitcoinUnit.SATOSHIS))}
</Text>
</View>
<View
style={{
flexDirection: 'row',
@ -538,7 +551,8 @@ SendDetails.propTypes = {
params: PropTypes.shape({
address: PropTypes.string,
fromAddress: PropTypes.string,
fromSecret: PropTypes.string,
satoshiPerByte: PropTypes.string,
fromSecret: PropTypes.fromSecret,
memo: PropTypes.string,
}),
}),

109
screen/send/success.js Normal file
View file

@ -0,0 +1,109 @@
import React, { Component } from 'react';
import { Haptic } from 'expo';
import { View } from 'react-native';
import { Text, Icon } from 'react-native-elements';
import { BlueButton, SafeBlueArea, BlueCard } from '../../BlueComponents';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import PropTypes from 'prop-types';
let loc = require('../../loc');
export default class Success extends Component {
constructor(props) {
super(props);
console.log('send/create constructor');
this.state = {
amount: props.navigation.getParam('amount'),
address: props.navigation.getParam('address'),
fee: props.navigation.getParam('fee'),
};
}
async componentDidMount() {
console.log('send/create - componentDidMount');
console.log('address = ', this.state.address);
Haptic.notification(Haptic.NotificationTypes.Success);
}
render() {
return (
<SafeBlueArea style={{ flex: 1, paddingTop: 19 }}>
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 76, paddingBottom: 16 }}>
<Text
style={{
color: '#0f5cc0',
fontSize: 36,
fontWeight: '600',
}}
>
{this.state.amount}
</Text>
<Text
style={{
color: '#0f5cc0',
fontSize: 16,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '600',
alignSelf: 'flex-end',
}}
>
{' ' + BitcoinUnit.BTC}
</Text>
</View>
<Text
style={{
color: '#37c0a1',
fontSize: 14,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '500',
alignSelf: 'center',
}}
>
{loc.send.create.fee}: {loc.formatBalance(this.state.fee, BitcoinUnit.SATS)}
</Text>
</BlueCard>
<View
style={{
backgroundColor: '#ccddf9',
width: 120,
height: 120,
borderRadius: 60,
alignSelf: 'center',
justifyContent: 'center',
marginTop: 43,
marginBottom: 53,
}}
>
<Icon name="check" size={50} type="font-awesome" color="#0f5cc0" />
</View>
<BlueCard>
<BlueButton
onPress={() => {
this.props.navigation.getParam('dismissModal')();
}}
title={loc.send.success.done}
style={{ maxWidth: 263, paddingHorizontal: 56 }}
/>
</BlueCard>
</SafeBlueArea>
);
}
}
Success.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
getParam: PropTypes.function,
navigate: PropTypes.function,
state: PropTypes.shape({
params: PropTypes.shape({
amount: PropTypes.string,
fee: PropTypes.number,
address: PropTypes.string,
}),
}),
}),
};

View file

@ -39,7 +39,7 @@ function formatTime(time) {
export default class TransactionsDetails extends Component {
static navigationOptions = {
header: ({ navigation }) => {
return <BlueHeaderDefaultSub leftText={loc.transactions.details.title} onClose={() => navigation.goBack(null)} />;
return <BlueHeaderDefaultSub leftText={loc.transactions.details.title.toLowerCase()} onClose={() => navigation.goBack(null)} />;
},
};
@ -78,7 +78,7 @@ export default class TransactionsDetails extends Component {
}
render() {
if (this.state.isLoading) {
if (this.state.isLoading || !this.state.hasOwnProperty('tx')) {
return <BlueLoading />;
}
@ -99,51 +99,82 @@ export default class TransactionsDetails extends Component {
}
})()}
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{loc.transactions.details.from}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.from.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.from.filter(onlyUnique).join(', ')}</BlueText>
{this.state.hasOwnProperty('from') && (
<React.Fragment>
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{loc.transactions.details.from}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.from.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.from.filter(onlyUnique).join(', ')}</BlueText>
</React.Fragment>
)}
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{loc.transactions.details.to}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.to.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>
{arrDiff(this.state.from, this.state.to.filter(onlyUnique)).join(', ')}
</BlueText>
{this.state.hasOwnProperty('to') && (
<React.Fragment>
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{loc.transactions.details.to}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.to.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>
{arrDiff(this.state.from, this.state.to.filter(onlyUnique)).join(', ')}
</BlueText>
</React.Fragment>
)}
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
<BlueText style={{ fontSize: 16, fontWeight: '500' }}>Txid</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.tx.hash} />
</View>
<TouchableOpacity
onPress={() => {
const url = `https://live.blockcypher.com/btc/tx/${this.state.tx.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}}
>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.hash}</BlueText>
</TouchableOpacity>
{this.state.tx.hasOwnProperty('hash') && (
<React.Fragment>
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
<BlueText style={{ fontSize: 16, fontWeight: '500' }}>Txid</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.tx.hash} />
</View>
<TouchableOpacity
onPress={() => {
const url = `https://live.blockcypher.com/btc/tx/${this.state.tx.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}}
>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.hash}</BlueText>
</TouchableOpacity>
</React.Fragment>
)}
{this.state.tx.hasOwnProperty('received') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Received</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{formatTime(this.state.tx.received)}</BlueText>
</React.Fragment>
)}
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Received</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{formatTime(this.state.tx.received)}</BlueText>
{this.state.tx.hasOwnProperty('block_height') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Block Height</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{formatTime(this.state.tx.block_height)}</BlueText>
</React.Fragment>
)}
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Block Height</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{formatTime(this.state.tx.block_height)}</BlueText>
{this.state.tx.hasOwnProperty('confirmations') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Confirmations</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.confirmations}</BlueText>
</React.Fragment>
)}
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Confirmations</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.confirmations}</BlueText>
{this.state.tx.hasOwnProperty('inputs') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Inputs</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.inputs.length}</BlueText>
</React.Fragment>
)}
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Inputs</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.inputs.length}</BlueText>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Outputs</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.outputs.length}</BlueText>
{this.state.tx.hasOwnProperty('outputs') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Outputs</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.outputs.length}</BlueText>
</React.Fragment>
)}
</BlueCard>
{(() => {

View file

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Dimensions, ActivityIndicator, View } from 'react-native';
import QRCode from 'react-native-qrcode';
import { BlueSpacing, BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents';
import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
@ -48,6 +48,13 @@ export default class WalletExport extends Component {
});
}
determineSize = () => {
if (width > 312) {
return width - 48;
}
return 312;
};
render() {
if (this.state.isLoading) {
return (
@ -72,8 +79,6 @@ export default class WalletExport extends Component {
{(() => {
if (isIpad) {
return <BlueSpacing40 />;
} else {
return <BlueSpacing />;
}
})()}
<BlueCard style={{ alignItems: 'center', flex: 1 }}>
@ -88,11 +93,11 @@ export default class WalletExport extends Component {
})()}
<QRCode
value={this.state.wallet.getSecret()}
size={312}
size={this.determineSize()}
bgColor={BlueApp.settings.foregroundColor}
fgColor={BlueApp.settings.brandingColor}
/>
<BlueText>{this.state.wallet.getSecret()}</BlueText>
<BlueText style={{ marginVertical: 8 }}>{this.state.wallet.getSecret()}</BlueText>
</BlueCard>
</SafeBlueArea>
);