diff --git a/.flowconfig b/.flowconfig
index f3eb74de6..9bded78be 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -67,4 +67,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[version]
-^0.85.0
+^0.86.0
diff --git a/.gitignore b/.gitignore
index 5d647565f..04555cfac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,7 @@ buck-out/
# Bundle artifact
*.jsbundle
+
+#BlueWallet
+release-notes.json
+release-notes.txt
\ No newline at end of file
diff --git a/BlueApp.js b/BlueApp.js
index 2a1b249ce..3166d9199 100644
--- a/BlueApp.js
+++ b/BlueApp.js
@@ -7,6 +7,7 @@ let EV = require('./events');
let currency = require('./currency');
let loc = require('./loc');
let A = require('./analytics');
+let BlueElectrum = require('./BlueElectrum'); // eslint-disable-line
/** @type {AppStorage} */
let BlueApp = new AppStorage();
@@ -31,7 +32,7 @@ async function startAndDecrypt(retry) {
let securityAlert = require('./security-alert');
await securityAlert.start();
// now, lets try to fetch balance and txs for first wallet if it is time for it
- let hadToRefresh = false;
+ /* let hadToRefresh = false;
let noErr = true;
try {
let wallets = BlueApp.getWallets();
@@ -57,7 +58,7 @@ async function startAndDecrypt(retry) {
if (hadToRefresh && noErr) {
await BlueApp.saveToDisk(); // caching
- }
+ } */
}
if (!success && password) {
diff --git a/BlueComponents.js b/BlueComponents.js
index ea2140a4f..ca442b368 100644
--- a/BlueComponents.js
+++ b/BlueComponents.js
@@ -4,7 +4,7 @@
import React, { Component } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types';
-import { Icon, Button, FormLabel, FormInput, Text, Header, List, ListItem } from 'react-native-elements';
+import { Icon, FormLabel, FormInput, Text, Header, List, ListItem } from 'react-native-elements';
import {
TouchableOpacity,
TouchableWithoutFeedback,
@@ -15,10 +15,11 @@ import {
StyleSheet,
Dimensions,
Image,
+ Keyboard,
SafeAreaView,
+ InputAccessoryView,
Clipboard,
Platform,
- LayoutAnimation,
TextInput,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
@@ -44,14 +45,19 @@ if (aspectRatio > 1.6) {
export class BlueButton extends Component {
render() {
- const backgroundColor = this.props.disabled ? '#99a0ab' : '#ccddf9';
+ let backgroundColor = '#ccddf9';
+ let fontColor = '#0c2550';
+ if (this.props.hasOwnProperty('disabled') && this.props.disabled === true) {
+ backgroundColor = '#eef0f4';
+ fontColor = '#9aa0aa';
+ }
return (
{this.props.icon && }
- {this.props.title && {this.props.title}}
+ {this.props.title && {this.props.title}}
);
@@ -141,26 +147,18 @@ export class LightningButton extends Component {
export class BlueButtonLink extends Component {
render() {
- // eslint-disable-next-line
- this.props.buttonStyle = this.props.buttonStyle || {};
-
return (
-
+ {...this.props}
+ >
+ {this.props.title}
+
);
}
}
@@ -204,36 +202,32 @@ export class BlueCopyTextToClipboard extends Component {
text: '',
};
- state = { hasTappedText: false };
-
- constructor() {
- super();
- if (Platform.OS === 'android') UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
+ constructor(props) {
+ super(props);
+ if (Platform.OS === 'android') {
+ UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
+ }
+ this.state = { hasTappedText: false, address: props.text };
}
copyToClipboard = () => {
- LayoutAnimation.configureNext(LayoutAnimation.Presets.spring, () => {
+ this.setState({ hasTappedText: true }, () => {
Clipboard.setString(this.props.text);
- setTimeout(() => {
- LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
- this.setState({ hasTappedText: false });
- }, 1000);
+ this.setState({ address: loc.wallets.xpub.copiedToClipboard }, () => {
+ setTimeout(() => {
+ this.setState({ hasTappedText: false, address: this.props.text });
+ }, 1000);
+ });
});
- this.setState({ hasTappedText: true });
};
render() {
return (
-
- {this.props.text}
-
- {this.state.hasTappedText && (
-
- {loc.wallets.xpub.copiedToClipboard}
-
- )}
+
+ {this.state.address}
+
);
@@ -551,6 +545,27 @@ export class BlueList extends Component {
}
}
+export class BlueUseAllFundsButton extends Component {
+ static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
+ static propTypes = {
+ wallet: PropTypes.shape().isRequired,
+ onUseAllPressed: PropTypes.func.isRequired,
+ };
+
+ render() {
+ return (
+
+
+
+ Total: {this.props.wallet.getBalance()} {BitcoinUnit.BTC}
+
+
+
+
+ );
+ }
+}
+
export class BlueLoading extends Component {
render() {
return (
@@ -576,7 +591,7 @@ const stylesBlueIcon = StyleSheet.create({
paddingHorizontal: 14,
paddingTop: 8,
},
- boxIncomming: {
+ boxIncoming: {
position: 'relative',
},
ball: {
@@ -585,14 +600,14 @@ const stylesBlueIcon = StyleSheet.create({
borderRadius: 15,
backgroundColor: '#ccddf9',
},
- ballIncomming: {
+ ballIncoming: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#d2f8d6',
transform: [{ rotate: '-45deg' }],
},
- ballIncommingWithoutRotate: {
+ ballIncomingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
@@ -655,12 +670,12 @@ export class BluePlusIcon extends Component {
}
}
-export class BlueTransactionIncommingIcon extends Component {
+export class BlueTransactionIncomingIcon extends Component {
render() {
return (
-
-
+
+
@@ -673,7 +688,7 @@ export class BlueTransactionPendingIcon extends Component {
render() {
return (
-
+
-
+
@@ -708,8 +723,8 @@ export class BlueTransactionOnchainIcon extends Component {
render() {
return (
-
-
+
+
-
+
@@ -743,8 +758,8 @@ export class BlueTransactionOffchainIncomingIcon extends Component {
render() {
return (
-
-
+
+
@@ -757,7 +772,7 @@ export class BlueTransactionOutgoingIcon extends Component {
render() {
return (
-
+
@@ -983,6 +998,386 @@ export class NewWalletPanel extends Component {
}
}
+export class BlueTransactionListItem extends Component {
+ static propTypes = {
+ item: PropTypes.shape().isRequired,
+ itemPriceUnit: PropTypes.string,
+ };
+
+ static defaultProps = {
+ itemPriceUnit: BitcoinUnit.BTC,
+ };
+
+ txMemo = () => {
+ if (BlueApp.tx_metadata[this.props.item.hash] && BlueApp.tx_metadata[this.props.item.hash]['memo']) {
+ return BlueApp.tx_metadata[this.props.item.hash]['memo'];
+ }
+ return '';
+ };
+
+ rowTitle = () => {
+ const item = this.props.item;
+ 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 loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
+ } else if (invoiceExpiration < now) {
+ if (item.ispaid) {
+ return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
+ } else {
+ return loc.lnd.expired;
+ }
+ }
+ } else {
+ return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
+ }
+ };
+
+ rowTitleStyle = () => {
+ const item = this.props.item;
+ let color = '#37c0a1';
+
+ 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 = '#37c0a1';
+ } else if (invoiceExpiration < now) {
+ if (item.ispaid) {
+ color = '#37c0a1';
+ } else {
+ color = '#FF0000';
+ }
+ }
+ } else if (item.value / 100000000 < 0) {
+ color = BlueApp.settings.foregroundColor;
+ }
+
+ return {
+ fontWeight: '600',
+ fontSize: 16,
+ color: color,
+ };
+ };
+
+ avatar = () => {
+ // is it lightning refill tx?
+ if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
+ return (
+
+
+
+ );
+ }
+
+ if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
+ return (
+
+
+
+ );
+ }
+ if (this.props.item.type === 'paid_invoice') {
+ // is it lightning offchain payment?
+ return (
+
+
+
+ );
+ }
+
+ if (this.props.item.type === 'user_invoice' || this.props.item.type === 'payment_request') {
+ if (!this.props.item.ispaid) {
+ const currentDate = new Date();
+ const now = (currentDate.getTime() / 1000) | 0;
+ const invoiceExpiration = this.props.item.timestamp + this.props.item.expire_time;
+ if (invoiceExpiration < now) {
+ return (
+
+
+
+ );
+ }
+ } else {
+ return (
+
+
+
+ );
+ }
+ }
+
+ if (!this.props.item.confirmations) {
+ return (
+
+
+
+ );
+ } else if (this.props.item.value < 0) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ );
+ }
+ };
+
+ subtitle = () => {
+ return (
+ (this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
+ this.txMemo() +
+ (this.props.item.memo || '')
+ );
+ };
+
+ onPress = () => {
+ if (this.props.item.hash) {
+ NavigationService.navigate('TransactionDetails', { hash: this.props.item.hash });
+ } else if (
+ this.props.item.type === 'user_invoice' ||
+ this.props.item.type === 'payment_request' ||
+ this.props.item.type === 'paid_invoice'
+ ) {
+ const lightningWallet = BlueApp.getWallets().filter(wallet => {
+ if (typeof wallet === 'object') {
+ if (wallet.hasOwnProperty('secret')) {
+ return wallet.getSecret() === this.props.item.fromWallet;
+ }
+ }
+ });
+ if (lightningWallet.length === 1) {
+ NavigationService.navigate('LNDViewInvoice', {
+ invoice: this.props.item,
+ fromWallet: lightningWallet[0],
+ isModal: false,
+ });
+ }
+ }
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export class BlueListTransactionItem extends Component {
+ static propTypes = {
+ item: PropTypes.shape().isRequired,
+ itemPriceUnit: PropTypes.string,
+ };
+
+ static defaultProps = {
+ itemPriceUnit: BitcoinUnit.BTC,
+ };
+
+ txMemo = () => {
+ if (BlueApp.tx_metadata[this.props.item.hash] && BlueApp.tx_metadata[this.props.item.hash]['memo']) {
+ return BlueApp.tx_metadata[this.props.item.hash]['memo'];
+ }
+ return '';
+ };
+
+ rowTitle = () => {
+ const item = this.props.item;
+ 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 loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
+ } else if (invoiceExpiration < now) {
+ if (item.ispaid) {
+ return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
+ } else {
+ return loc.lnd.expired;
+ }
+ }
+ } else {
+ return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
+ }
+ };
+
+ rowTitleStyle = () => {
+ const item = this.props.item;
+ let color = '#37c0a1';
+
+ 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 = '#37c0a1';
+ } else if (invoiceExpiration < now) {
+ if (item.ispaid) {
+ color = '#37c0a1';
+ } else {
+ color = '#FF0000';
+ }
+ }
+ } else if (item.value / 100000000 < 0) {
+ color = BlueApp.settings.foregroundColor;
+ }
+
+ return {
+ fontWeight: '600',
+ fontSize: 16,
+ color: color,
+ };
+ };
+
+ avatar = () => {
+ // is it lightning refill tx?
+ if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
+ return (
+
+
+
+ );
+ }
+
+ if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
+ return (
+
+
+
+ );
+ }
+ if (this.props.item.type === 'paid_invoice') {
+ // is it lightning offchain payment?
+ return (
+
+
+
+ );
+ }
+
+ if (this.props.item.type === 'user_invoice' || this.props.item.type === 'payment_request') {
+ if (!this.props.item.ispaid) {
+ const currentDate = new Date();
+ const now = (currentDate.getTime() / 1000) | 0;
+ const invoiceExpiration = this.props.item.timestamp + this.props.item.expire_time;
+ if (invoiceExpiration < now) {
+ return (
+
+
+
+ );
+ }
+ } else {
+ return (
+
+
+
+ );
+ }
+ }
+
+ if (!this.props.item.confirmations) {
+ return (
+
+
+
+ );
+ } else if (this.props.item.value < 0) {
+ return (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ );
+ }
+ };
+
+ subtitle = () => {
+ return (
+ (this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
+ this.txMemo() +
+ (this.props.item.memo || '')
+ );
+ };
+
+ onPress = () => {
+ if (this.props.item.hash) {
+ NavigationService.navigate('TransactionDetails', { hash: this.props.item.hash });
+ } else if (
+ this.props.item.type === 'user_invoice' ||
+ this.props.item.type === 'payment_request' ||
+ this.props.item.type === 'paid_invoice'
+ ) {
+ const lightningWallet = BlueApp.getWallets().filter(wallet => {
+ if (typeof wallet === 'object') {
+ if (wallet.hasOwnProperty('secret')) {
+ return wallet.getSecret() === this.props.item.fromWallet;
+ }
+ }
+ });
+ NavigationService.navigate('LNDViewInvoice', {
+ invoice: this.props.item,
+ fromWallet: lightningWallet[0],
+ isModal: false,
+ });
+ }
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
+
const sliderWidth = width * 1;
const itemWidth = width * 0.82;
const sliderHeight = 190;
@@ -999,12 +1394,17 @@ export class WalletsCarousel extends Component {
_renderItem({ item, index }) {
let scaleValue = new Animated.Value(1.0);
-
+ let props = { duration: 50 };
+ if (Platform.OS === 'android') {
+ props['useNativeDriver'] = true;
+ }
this.onPressedIn = () => {
- Animated.spring(scaleValue, { toValue: 0.9, duration: 100, useNativeDriver: Platform.OS === 'android' }).start();
+ props.toValue = 0.9;
+ Animated.spring(scaleValue, props).start();
};
this.onPressedOut = () => {
- Animated.spring(scaleValue, { toValue: 1.0, duration: 100, useNativeDriver: Platform.OS === 'android' }).start();
+ props.toValue = 1.0;
+ Animated.spring(scaleValue, props).start();
};
if (!item) {
@@ -1173,6 +1573,7 @@ export class BlueAddressInput extends Component {
value={this.props.address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.props.isLoading}
+ onSubmitEditing={Keyboard.dismiss}
/>
(this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled}
value={amount}
- autoFocus={this.props.pointerEvents !== 'none'}
placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'}
style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
diff --git a/BlueElectrum.js b/BlueElectrum.js
new file mode 100644
index 000000000..ae78508d9
--- /dev/null
+++ b/BlueElectrum.js
@@ -0,0 +1,195 @@
+import { AsyncStorage } from 'react-native';
+const ElectrumClient = require('electrum-client');
+let bitcoin = require('bitcoinjs-lib');
+let reverse = require('buffer-reverse');
+
+const storageKey = 'ELECTRUM_PEERS';
+const defaultPeer = { host: 'electrum.coinucopia.io', tcp: 50001 };
+
+let mainClient = false;
+let mainConnected = false;
+
+async function connectMain() {
+ let usingPeer = await getRandomHardcodedPeer();
+ try {
+ console.log('begin connection:', JSON.stringify(usingPeer));
+ mainClient = new ElectrumClient(usingPeer.tcp, usingPeer.host, 'tcp');
+ await mainClient.connect();
+ const ver = await mainClient.server_version('2.7.11', '1.2');
+ console.log('connected to ', ver);
+ let peers = await mainClient.serverPeers_subscribe();
+ if (peers && peers.length > 0) {
+ mainConnected = true;
+ AsyncStorage.setItem(storageKey, JSON.stringify(peers));
+ }
+ } catch (e) {
+ mainConnected = false;
+ console.log('bad connection:', JSON.stringify(usingPeer));
+ }
+
+ if (!mainConnected) {
+ console.log('retry');
+ setTimeout(connectMain, 5000);
+ }
+}
+
+connectMain();
+
+/**
+ * Returns random hardcoded electrum server guaranteed to work
+ * at the time of writing.
+ *
+ * @returns {Promise<{tcp, host}|*>}
+ */
+async function getRandomHardcodedPeer() {
+ let hardcodedPeers = [
+ { host: 'node.ispol.sk', tcp: '50001' },
+ { host: 'electrum.vom-stausee.de', tcp: '50001' },
+ { host: 'orannis.com', tcp: '50001' },
+ { host: '139.162.14.142', tcp: '50001' },
+ { host: 'daedalus.bauerj.eu', tcp: '50001' },
+ { host: 'electrum.eff.ro', tcp: '50001' },
+ { host: 'electrum.anduck.net', tcp: '50001' },
+ { host: 'mooo.not.fyi', tcp: '50011' },
+ { host: 'electrum.coinucopia.io', tcp: '50001' },
+ ];
+ return hardcodedPeers[(hardcodedPeers.length * Math.random()) | 0];
+}
+
+/**
+ * Returns random electrum server out of list of servers
+ * previous electrum server told us. Nearly half of them is
+ * usually offline.
+ * Not used for now.
+ *
+ * @returns {Promise<{tcp: number, host: string}>}
+ */
+// eslint-disable-next-line
+async function getRandomDynamicPeer() {
+ try {
+ let peers = JSON.parse(await AsyncStorage.getItem(storageKey));
+ peers = peers.sort(() => Math.random() - 0.5); // shuffle
+ for (let peer of peers) {
+ let ret = {};
+ ret.host = peer[1];
+ for (let item of peer[2]) {
+ if (item.startsWith('t')) {
+ ret.tcp = item.replace('t', '');
+ }
+ }
+ if (ret.host && ret.tcp) return ret;
+ }
+
+ return defaultPeer; // failed to find random client, using default
+ } catch (_) {
+ return defaultPeer; // smth went wrong, using default
+ }
+}
+
+/**
+ *
+ * @param address {String}
+ * @returns {Promise
{this.renderCreateButton()}
@@ -116,7 +117,7 @@ export default class LNDCreateInvoice extends Component {
LNDCreateInvoice.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
+ goBack: PropTypes.func,
navigate: PropTypes.func,
getParam: PropTypes.func,
}),
diff --git a/screen/lnd/lndViewAdditionalInvoiceInformation.js b/screen/lnd/lndViewAdditionalInvoiceInformation.js
index 1bb8741ab..698b1b1e7 100644
--- a/screen/lnd/lndViewAdditionalInvoiceInformation.js
+++ b/screen/lnd/lndViewAdditionalInvoiceInformation.js
@@ -82,8 +82,8 @@ export default class LNDViewAdditionalInvoiceInformation extends Component {
LNDViewAdditionalInvoiceInformation.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- getParam: PropTypes.function,
- dismiss: PropTypes.function,
+ goBack: PropTypes.func,
+ getParam: PropTypes.func,
+ dismiss: PropTypes.func,
}),
};
diff --git a/screen/lnd/lndViewInvoice.js b/screen/lnd/lndViewInvoice.js
index 1440fb36d..68f0f1d83 100644
--- a/screen/lnd/lndViewInvoice.js
+++ b/screen/lnd/lndViewInvoice.js
@@ -236,9 +236,9 @@ export default class LNDViewInvoice extends Component {
LNDViewInvoice.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- navigate: PropTypes.function,
- getParam: PropTypes.function,
- dismiss: PropTypes.function,
+ goBack: PropTypes.func,
+ navigate: PropTypes.func,
+ getParam: PropTypes.func,
+ dismiss: PropTypes.func,
}),
};
diff --git a/screen/lnd/manageFunds.js b/screen/lnd/manageFunds.js
index bf4116063..0488cb170 100644
--- a/screen/lnd/manageFunds.js
+++ b/screen/lnd/manageFunds.js
@@ -83,10 +83,10 @@ export default class ManageFunds extends Component {
ManageFunds.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- dismiss: PropTypes.function,
- navigate: PropTypes.function,
- getParam: PropTypes.function,
+ goBack: PropTypes.func,
+ dismiss: PropTypes.func,
+ navigate: PropTypes.func,
+ getParam: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
fromSecret: PropTypes.string,
diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js
index 61584f6f0..0df2c3ff2 100644
--- a/screen/lnd/scanLndInvoice.js
+++ b/screen/lnd/scanLndInvoice.js
@@ -72,12 +72,6 @@ export default class ScanLndInvoice extends React.Component {
processInvoice = data => {
this.setState({ isLoading: true }, async () => {
- if (this.ignoreRead) return;
- this.ignoreRead = true;
- setTimeout(() => {
- this.ignoreRead = false;
- }, 6000);
-
if (!this.state.fromWallet) {
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
return this.props.navigation.goBack();
@@ -149,23 +143,20 @@ export default class ScanLndInvoice extends React.Component {
return alert(loc.lnd.sameWalletAsInvoiceError);
}
- let start = +new Date();
- let end;
try {
await fromWallet.payInvoice(this.state.invoice, this.state.decoded.num_satoshis);
- end = +new Date();
} catch (Err) {
console.log(Err.message);
this.setState({ isLoading: false });
- this.props.navigation.goBack();
- return alert('Error');
+ return alert(Err.message);
}
- console.log('payInvoice took', (end - start) / 1000, 'sec');
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
-
- alert('Success');
- this.props.navigation.goBack();
+ this.props.navigation.navigate('Success', {
+ amount: this.state.decoded.num_satoshis,
+ amountUnit: BitcoinUnit.SATS,
+ invoiceDescription: this.state.decoded.description,
+ });
},
);
}
@@ -237,24 +228,26 @@ export default class ScanLndInvoice extends React.Component {
)}
- {this.state.isLoading ? (
-
-
-
- ) : (
- {
- this.pay();
- }}
- disabled={this.shouldDisablePayButton()}
- />
- )}
+
+ {this.state.isLoading ? (
+
+
+
+ ) : (
+ {
+ this.pay();
+ }}
+ disabled={this.shouldDisablePayButton()}
+ />
+ )}
+
@@ -264,10 +257,10 @@ export default class ScanLndInvoice extends React.Component {
ScanLndInvoice.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- navigate: PropTypes.function,
- getParam: PropTypes.function,
- dismiss: PropTypes.function,
+ goBack: PropTypes.func,
+ navigate: PropTypes.func,
+ getParam: PropTypes.func,
+ dismiss: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
uri: PropTypes.string,
diff --git a/screen/receive/details.js b/screen/receive/details.js
index 6a3e61b24..0a05b5e7c 100644
--- a/screen/receive/details.js
+++ b/screen/receive/details.js
@@ -79,7 +79,6 @@ export default class ReceiveDetails extends Component {
}
render() {
- console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret);
if (this.state.isLoading) {
return ;
}
@@ -128,8 +127,8 @@ export default class ReceiveDetails extends Component {
ReceiveDetails.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- navigate: PropTypes.function,
+ goBack: PropTypes.func,
+ navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
address: PropTypes.string,
diff --git a/screen/selftest.js b/screen/selftest.js
index e0363d60f..05c89b342 100644
--- a/screen/selftest.js
+++ b/screen/selftest.js
@@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet } from '../class';
let BigNumber = require('bignumber.js');
let encryption = require('../encryption');
+let bitcoin = require('bitcoinjs-lib');
+let BlueElectrum = require('../BlueElectrum');
export default class Selftest extends Component {
static navigationOptions = () => ({
@@ -42,6 +44,20 @@ export default class Selftest extends Component {
//
+ if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
+ let addr4elect = '3GCvDBAktgQQtsbN6x5DYiQCMmgZ9Yk8BK';
+ let electrumBalance = await BlueElectrum.getBalanceByAddress(addr4elect);
+ if (electrumBalance.confirmed !== 51432)
+ throw new Error('BlueElectrum getBalanceByAddress failure, got ' + JSON.stringify(electrumBalance));
+
+ let electrumTxs = await BlueElectrum.getTransactionsByAddress(addr4elect);
+ if (electrumTxs.length !== 1) throw new Error('BlueElectrum getTransactionsByAddress failure, got ' + JSON.stringify(electrumTxs));
+ } else {
+ console.warn('skipping RN-specific test');
+ }
+
+ //
+
let l = new LegacyWallet();
l.setSecret('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct');
if (l.getAddress() !== '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31') {
@@ -158,7 +174,6 @@ export default class Selftest extends Component {
];
let tx = l.createTx(utxo, 0.001, 0.0001, '1QHf8Gp3wfmFiSdEX4FtrssCGR68diN1cj');
- let bitcoin = require('bitcoinjs-lib');
let txDecoded = bitcoin.Transaction.fromHex(tx);
let txid = txDecoded.getId();
diff --git a/screen/send/confirm.js b/screen/send/confirm.js
index 9d47d582b..cb1e13780 100644
--- a/screen/send/confirm.js
+++ b/screen/send/confirm.js
@@ -105,15 +105,7 @@ export default class Confirm extends Component {
{loc.send.create.to}
{this.state.address}
- {this.state.isLoading ? (
-
- ) : (
- this.broadcast()}
- title={loc.send.confirm.sendNow}
- style={{ maxWidth: 263, paddingHorizontal: 56 }}
- />
- )}
+ {this.state.isLoading ? : this.broadcast()} title={loc.send.confirm.sendNow} />}
@@ -154,10 +146,10 @@ const styles = StyleSheet.create({
Confirm.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- getParam: PropTypes.function,
- navigate: PropTypes.function,
- dismiss: PropTypes.function,
+ goBack: PropTypes.func,
+ getParam: PropTypes.func,
+ navigate: PropTypes.func,
+ dismiss: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
amount: PropTypes.string,
diff --git a/screen/send/create.js b/screen/send/create.js
index f262d2429..4492bf911 100644
--- a/screen/send/create.js
+++ b/screen/send/create.js
@@ -108,10 +108,10 @@ const styles = StyleSheet.create({
SendCreate.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- getParam: PropTypes.function,
- navigate: PropTypes.function,
- dismiss: PropTypes.function,
+ goBack: PropTypes.func,
+ getParam: PropTypes.func,
+ navigate: PropTypes.func,
+ dismiss: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
amount: PropTypes.string,
diff --git a/screen/send/details.js b/screen/send/details.js
index 3058152b6..45b9e47b6 100644
--- a/screen/send/details.js
+++ b/screen/send/details.js
@@ -11,6 +11,7 @@ import {
StyleSheet,
Platform,
Slider,
+ AsyncStorage,
Text,
} from 'react-native';
import { Icon } from 'react-native-elements';
@@ -71,13 +72,14 @@ export default class SendDetails extends Component {
fromAddress,
fromWallet,
fromSecret,
- isLoading: true,
+ isLoading: false,
address,
memo,
fee: 1,
networkTransactionFees: new NetworkTransactionFee(1, 1, 1),
feeSliderValue: 1,
bip70TransactionExpiration: null,
+ renderWalletSelectionButtonHidden: false,
};
}
@@ -91,7 +93,7 @@ export default class SendDetails extends Component {
const dataWithoutSchema = data.replace('bitcoin:', '');
if (btcAddressRx.test(dataWithoutSchema) || dataWithoutSchema.indexOf('bc1') === 0) {
this.setState({
- address: data,
+ address: dataWithoutSchema,
bip70TransactionExpiration: null,
isLoading: false,
});
@@ -126,20 +128,27 @@ export default class SendDetails extends Component {
};
async componentDidMount() {
- let recommendedFees = await NetworkTransactionFees.recommendedFees().catch(response => {
- this.setState({
- fee: response.halfHourFee,
- networkTransactionFees: response,
- feeSliderValue: response.halfHourFee,
- isLoading: false,
- });
- });
- if (recommendedFees) {
+ this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
+ this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
+ try {
+ const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey));
+
+ if (cachedNetworkTransactionFees && cachedNetworkTransactionFees.hasOwnProperty('halfHourFee')) {
+ this.setState({
+ fee: cachedNetworkTransactionFees.halfHourFee,
+ networkTransactionFees: cachedNetworkTransactionFees,
+ feeSliderValue: cachedNetworkTransactionFees.halfHourFee,
+ });
+ }
+ } catch (_) {}
+
+ let recommendedFees = await NetworkTransactionFees.recommendedFees();
+ if (recommendedFees && recommendedFees.hasOwnProperty('halfHourFee')) {
+ await AsyncStorage.setItem(NetworkTransactionFee.StorageKey, JSON.stringify(recommendedFees));
this.setState({
fee: recommendedFees.halfHourFee,
networkTransactionFees: recommendedFees,
feeSliderValue: recommendedFees.halfHourFee,
- isLoading: false,
});
if (this.props.navigation.state.params.uri) {
@@ -148,16 +157,34 @@ export default class SendDetails extends Component {
} else {
try {
const { address, amount, memo } = this.decodeBitcoinUri(this.props.navigation.getParam('uri'));
- this.setState({ address, amount, memo });
+ this.setState({ address, amount, memo, isLoading: false });
} catch (error) {
console.log(error);
+ this.setState({ isLoading: false });
alert('Error: Unable to decode Bitcoin address');
}
}
+ } else {
+ this.setState({ isLoading: false });
}
+ } else {
+ this.setState({ isLoading: false });
}
}
+ componentWillUnmount() {
+ this.keyboardDidShowListener.remove();
+ this.keyboardDidHideListener.remove();
+ }
+
+ _keyboardDidShow = () => {
+ this.setState({ renderWalletSelectionButtonHidden: true });
+ };
+
+ _keyboardDidHide = () => {
+ this.setState({ renderWalletSelectionButtonHidden: false });
+ };
+
decodeBitcoinUri(uri) {
try {
let amount = '';
@@ -382,9 +409,9 @@ export default class SendDetails extends Component {
}
onWalletSelect = wallet => {
- this.setState({ fromAddress: wallet.getAddress(), fromSecret: wallet.getSecret(), fromWallet: wallet }, () =>
- this.props.navigation.goBack(null),
- );
+ this.setState({ fromAddress: wallet.getAddress(), fromSecret: wallet.getSecret(), fromWallet: wallet }, () => {
+ this.props.navigation.pop();
+ });
};
renderFeeSelectionModal = () => {
@@ -392,7 +419,13 @@ export default class SendDetails extends Component {
this.setState({ isFeeSelectionModalVisible: false })}
+ onBackdropPress={() => {
+ if (this.state.fee < 1 || this.state.feeSliderValue < 1) {
+ this.setState({ fee: Number(1), feeSliderValue: Number(1) });
+ }
+ Keyboard.dismiss();
+ this.setState({ isFeeSelectionModalVisible: false });
+ }}
>
@@ -403,12 +436,14 @@ export default class SendDetails extends Component {
this.textInput = ref;
}}
value={this.state.fee.toString()}
+ onEndEditing={() => {
+ if (this.state.fee < 1 || this.state.feeSliderValue < 1) {
+ this.setState({ fee: Number(1), feeSliderValue: Number(1) });
+ }
+ }}
onChangeText={value => {
let newValue = value.replace(/\D/g, '');
- if (newValue.length === 0) {
- newValue = 1;
- }
- this.setState({ fee: newValue, feeSliderValue: newValue });
+ this.setState({ fee: Number(newValue), feeSliderValue: Number(newValue) });
}}
maxLength={9}
editable={!this.state.isLoading}
@@ -466,6 +501,7 @@ export default class SendDetails extends Component {
};
renderWalletSelectionButton = () => {
+ if (this.state.renderWalletSelectionButtonHidden) return;
return (
{!this.state.isLoading && (
@@ -500,7 +536,6 @@ export default class SendDetails extends Component {
);
}
-
return (
@@ -556,6 +591,7 @@ export default class SendDetails extends Component {
numberOfLines={1}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.state.isLoading}
+ onSubmitEditing={Keyboard.dismiss}
/>
{
+ const onBarScanned = this.props.navigation.getParam('onBarScanned');
+ this.props.navigation.goBack();
+ onBarScanned(ret.data);
+ });
} // end
componentDidMount() {
diff --git a/screen/send/success.js b/screen/send/success.js
index ffbe7a782..f55527d8c 100644
--- a/screen/send/success.js
+++ b/screen/send/success.js
@@ -18,7 +18,9 @@ export default class Success extends Component {
this.state = {
amount: props.navigation.getParam('amount'),
- fee: props.navigation.getParam('fee'),
+ fee: props.navigation.getParam('fee') || 0,
+ amountUnit: props.navigation.getParam('amountUnit') || BitcoinUnit.BTC,
+ invoiceDescription: props.navigation.getParam('invoiceDescription') || '',
};
}
@@ -51,21 +53,38 @@ export default class Success extends Component {
alignSelf: 'flex-end',
}}
>
- {' ' + BitcoinUnit.BTC}
+ {' ' + this.state.amountUnit}
-
- {loc.send.create.fee}: {loc.formatBalance(this.state.fee, BitcoinUnit.SATS)}
-
+ {this.state.fee > 0 && (
+
+ {loc.send.create.fee}: {loc.formatBalance(this.state.fee, BitcoinUnit.SATS)}
+
+ )}
+ {this.state.fee <= 0 && (
+
+ {this.state.invoiceDescription}
+
+ )}
@@ -97,10 +115,10 @@ export default class Success extends Component {
Success.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
- getParam: PropTypes.function,
- navigate: PropTypes.function,
- dismiss: PropTypes.function,
+ goBack: PropTypes.func,
+ getParam: PropTypes.func,
+ navigate: PropTypes.func,
+ dismiss: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
amount: PropTypes.string,
diff --git a/screen/settings/about.js b/screen/settings/about.js
index 9c4fce27a..4a595cbe6 100644
--- a/screen/settings/about.js
+++ b/screen/settings/about.js
@@ -127,6 +127,14 @@ export default class About extends Component {
* bignumber.js
+ {
+ this.props.navigation.navigate('ReleaseNotes');
+ }}
+ title="Release notes"
+ />
+
+
{
this.props.navigation.navigate('Selftest');
@@ -135,8 +143,9 @@ export default class About extends Component {
/>
- {DeviceInfo.getApplicationName()} ver {DeviceInfo.getVersion()} (build number {DeviceInfo.getBuildNumber()})
+ {DeviceInfo.getApplicationName()} ver {DeviceInfo.getVersion()} (build {DeviceInfo.getBuildNumber()})
+ {new Date(DeviceInfo.getBuildNumber() * 1000).toGMTString()}
{DeviceInfo.getBundleId()}
w, h = {width}, {height}
diff --git a/screen/settings/releasenotes.js b/screen/settings/releasenotes.js
new file mode 100644
index 000000000..328bda9ea
--- /dev/null
+++ b/screen/settings/releasenotes.js
@@ -0,0 +1,51 @@
+import React, { Component } from 'react';
+import { ScrollView } from 'react-native';
+import { BlueLoading, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle } from '../../BlueComponents';
+import PropTypes from 'prop-types';
+/** @type {AppStorage} */
+const notes = require('../../release-notes');
+
+export default class ReleaseNotes extends Component {
+ static navigationOptions = () => ({
+ ...BlueNavigationStyle(),
+ title: 'Release notes',
+ });
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isLoading: true,
+ };
+ }
+
+ async componentDidMount() {
+ console.log(notes);
+ this.setState({
+ isLoading: false,
+ notes: notes,
+ });
+ }
+
+ render() {
+ if (this.state.isLoading) {
+ return ;
+ }
+
+ return (
+
+
+
+ {this.state.notes}
+
+
+
+ );
+ }
+}
+
+ReleaseNotes.propTypes = {
+ navigation: PropTypes.shape({
+ navigate: PropTypes.func,
+ goBack: PropTypes.func,
+ }),
+};
diff --git a/screen/transactions/RBF-create.js b/screen/transactions/RBF-create.js
index d02a3425b..d100e2449 100644
--- a/screen/transactions/RBF-create.js
+++ b/screen/transactions/RBF-create.js
@@ -234,7 +234,7 @@ export default class SendCreate extends Component {
SendCreate.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
+ goBack: PropTypes.func,
navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
diff --git a/screen/transactions/RBF.js b/screen/transactions/RBF.js
index c879bc2c3..9b6834526 100644
--- a/screen/transactions/RBF.js
+++ b/screen/transactions/RBF.js
@@ -191,7 +191,7 @@ export default class RBF extends Component {
RBF.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
+ goBack: PropTypes.func,
navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
diff --git a/screen/transactions/details.js b/screen/transactions/details.js
index 63352bede..dffa62173 100644
--- a/screen/transactions/details.js
+++ b/screen/transactions/details.js
@@ -89,22 +89,25 @@ export default class TransactionsDetails extends Component {
- {(() => {
- if (this.state.tx.confirmations === 0 && this.state.wallet && this.state.wallet.allowRBF()) {
- return (
-
- this.props.navigation.navigate('RBF', {
- txid: this.state.tx.hash,
- })
- }
- title="Replace-By-Fee (RBF)"
- />
- );
- }
- })()}
-
+ {(() => {
+ if (this.state.tx.confirmations === 0 && this.state.wallet && this.state.wallet.allowRBF()) {
+ return (
+
+
+ this.props.navigation.navigate('RBF', {
+ txid: this.state.tx.hash,
+ })
+ }
+ title="Replace-By-Fee (RBF)"
+ />
+
+
+ );
+ }
+ })()}
+
{(() => {
if (BlueApp.tx_metadata[this.state.tx.hash]) {
if (BlueApp.tx_metadata[this.state.tx.hash]['memo']) {
@@ -176,14 +179,14 @@ export default class TransactionsDetails extends Component {
)}
- {this.state.tx.hasOwnProperty('block_height') && this.state.block_height > 0 && (
+ {this.state.tx.hasOwnProperty('block_height') && this.state.tx.block_height > 0 && (
Block Height
{this.state.tx.block_height}
)}
- {this.state.tx.hasOwnProperty('confirmations') && (
+ {this.state.tx.hasOwnProperty('confirmations') && this.state.tx.confirmations > 0 && (
Confirmations
{this.state.tx.confirmations}
@@ -197,7 +200,7 @@ export default class TransactionsDetails extends Component {
)}
- {this.state.tx.hasOwnProperty('outputs') && (
+ {this.state.tx.hasOwnProperty('outputs') && this.state.tx.outputs.length > 0 && (
Outputs
{this.state.tx.outputs.length}
@@ -212,7 +215,7 @@ export default class TransactionsDetails extends Component {
TransactionsDetails.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
+ goBack: PropTypes.func,
navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
diff --git a/screen/wallets/add.js b/screen/wallets/add.js
index 1ecc9dc72..76f9a25cd 100644
--- a/screen/wallets/add.js
+++ b/screen/wallets/add.js
@@ -72,7 +72,7 @@ export default class WalletsAdd extends Component {
return (
-
+
{loc.wallets.add.wallet_name}
;
}
@@ -107,7 +106,7 @@ export default class BuyBitcoin extends Component {
BuyBitcoin.propTypes = {
navigation: PropTypes.shape({
- goBack: PropTypes.function,
+ goBack: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
address: PropTypes.string,
diff --git a/screen/wallets/import.js b/screen/wallets/import.js
index 6d1c58c06..4462e4116 100644
--- a/screen/wallets/import.js
+++ b/screen/wallets/import.js
@@ -215,38 +215,39 @@ export default class WalletsImport extends Component {
this.setLabel(text);
}}
/>
-
-
- {
- if (!this.state.label) {
- return;
- }
- this.setState({ isLoading: true });
- setTimeout(async () => {
- await this.importMnemonic(this.state.label.trim());
- this.setState({ isLoading: false });
- }, 1);
- }}
- />
- {
- this.props.navigation.navigate('ScanQrWif');
- }}
- />
-
+
+
+
+ {
+ if (!this.state.label) {
+ return;
+ }
+ this.setState({ isLoading: true });
+ setTimeout(async () => {
+ await this.importMnemonic(this.state.label.trim());
+ this.setState({ isLoading: false });
+ }, 1);
+ }}
+ />
+ {
+ this.props.navigation.navigate('ScanQrWif');
+ }}
+ />
+
);
}
diff --git a/screen/wallets/list.js b/screen/wallets/list.js
index 9b0a0079f..b8ddfc104 100644
--- a/screen/wallets/list.js
+++ b/screen/wallets/list.js
@@ -1,20 +1,6 @@
import React, { Component } from 'react';
import { View, TouchableOpacity, Text, FlatList, RefreshControl, ScrollView } from 'react-native';
-import {
- BlueTransactionOnchainIcon,
- BlueLoading,
- SafeBlueArea,
- WalletsCarousel,
- BlueTransactionIncommingIcon,
- BlueTransactionOutgoingIcon,
- BlueTransactionPendingIcon,
- BlueTransactionOffchainIcon,
- BlueTransactionExpiredIcon,
- BlueList,
- BlueListItem,
- BlueHeaderDefaultMain,
- BlueTransactionOffchainIncomingIcon,
-} from '../../BlueComponents';
+import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueListTransactionItem } from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import { NavigationEvents } from 'react-navigation';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
@@ -82,7 +68,10 @@ export default class WalletsList extends Component {
// more responsive
let noErr = true;
try {
+ let balanceStart = +new Date();
await BlueApp.fetchWalletBalances(that.lastSnappedTo || 0);
+ let balanceEnd = +new Date();
+ console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
let start = +new Date();
await BlueApp.fetchWalletTransactions(that.lastSnappedTo || 0);
let end = +new Date();
@@ -229,60 +218,9 @@ export default class WalletsList extends Component {
}
};
- rowTitle = item => {
- 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 loc.formatBalanceWithoutSuffix(item.value && item.value, item.walletPreferredBalanceUnit, true).toString();
- } else if (invoiceExpiration < now) {
- if (item.ispaid) {
- return loc.formatBalanceWithoutSuffix(item.value && item.value, item.walletPreferredBalanceUnit, true).toString();
- } else {
- return loc.lnd.expired;
- }
- }
- } else {
- return loc.formatBalanceWithoutSuffix(item.value && item.value, item.walletPreferredBalanceUnit, true).toString();
- }
- };
-
- rowTitleStyle = item => {
- let color = '#37c0a1';
-
- 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 = '#37c0a1';
- } else if (invoiceExpiration < now) {
- if (item.ispaid) {
- color = '#37c0a1';
- } else {
- color = '#FF0000';
- }
- }
- } else if (item.value / 100000000 < 0) {
- color = BlueApp.settings.foregroundColor;
- }
-
- return {
- fontWeight: '600',
- fontSize: 16,
- color: color,
- };
- };
+ _renderItem = data => ;
render() {
- const { navigate } = this.props.navigation;
-
if (this.state.isLoading) {
return ;
}
@@ -335,117 +273,7 @@ export default class WalletsList extends Component {
data={this.state.dataSource}
extraData={this.state.dataSource}
keyExtractor={this._keyExtractor}
- renderItem={rowData => {
- return (
- {
- // is it lightning refill tx?
- if (rowData.item.category === 'receive' && rowData.item.confirmations < 3) {
- return (
-
-
-
- );
- }
-
- if (rowData.item.type && rowData.item.type === 'bitcoind_tx') {
- return (
-
-
-
- );
- }
- if (rowData.item.type === 'paid_invoice') {
- // is it lightning offchain payment?
- return (
-
-
-
- );
- }
-
- if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') {
- if (!rowData.item.ispaid) {
- const currentDate = new Date();
- const now = (currentDate.getTime() / 1000) | 0;
- const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time;
- if (invoiceExpiration < now) {
- return (
-
-
-
- );
- }
- } else {
- return (
-
-
-
- );
- }
- }
-
- if (!rowData.item.confirmations) {
- return (
-
-
-
- );
- } else if (rowData.item.value < 0) {
- return (
-
-
-
- );
- } else {
- return (
-
-
-
- );
- }
- })()}
- title={loc.transactionTimeToReadable(rowData.item.received)}
- subtitle={
- (rowData.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + rowData.item.confirmations + ' ' : '') +
- this.txMemo(rowData.item.hash) +
- (rowData.item.memo || '')
- }
- onPress={() => {
- if (rowData.item.hash) {
- navigate('TransactionDetails', {
- hash: rowData.item.hash,
- });
- } else if (
- rowData.item.type === 'user_invoice' ||
- rowData.item.type === 'payment_request' ||
- rowData.item.type === 'paid_invoice'
- ) {
- const lightningWallet = this.state.wallets.filter(wallet => {
- if (typeof wallet === 'object') {
- if (wallet.hasOwnProperty('secret')) {
- return wallet.getSecret() === rowData.item.fromWallet;
- }
- }
- });
- this.props.navigation.navigate('LNDViewInvoice', {
- invoice: rowData.item,
- fromWallet: lightningWallet[0],
- isModal: false,
- });
- }
- }}
- badge={{
- value: 3,
- textStyle: { color: 'orange' },
- containerStyle: { marginTop: 0 },
- }}
- hideChevron
- rightTitle={this.rowTitle(rowData.item)}
- rightTitleStyle={this.rowTitleStyle(rowData.item)}
- />
- );
- }}
+ renderItem={this._renderItem}
/>
diff --git a/screen/wallets/scanQrWif.js b/screen/wallets/scanQrWif.js
index 716504af6..6772a0a6b 100644
--- a/screen/wallets/scanQrWif.js
+++ b/screen/wallets/scanQrWif.js
@@ -28,6 +28,7 @@ export default class ScanQrWif extends React.Component {
};
async onBarCodeScanned(ret) {
+ this.setState({ isLoading: true });
if (+new Date() - this.lastTimeIveBeenHere < 6000) {
this.lastTimeIveBeenHere = +new Date();
return;
@@ -57,16 +58,17 @@ export default class ScanQrWif extends React.Component {
ret.data = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
} catch (e) {
console.log(e.message);
- this.setState({ message: false });
+ this.setState({ message: false, isLoading: false });
return alert(loc.wallets.scanQrWif.bad_password);
}
- this.setState({ message: false });
+ this.setState({ message: false, isLoading: false });
}
for (let w of BlueApp.wallets) {
if (w.getSecret() === ret.data) {
// lookig for duplicates
+ this.setState({ isLoading: false });
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
@@ -78,10 +80,10 @@ export default class ScanQrWif extends React.Component {
for (let w of BlueApp.wallets) {
if (w.getSecret() === hd.getSecret()) {
// lookig for duplicates
+ this.setState({ isLoading: false });
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
- this.setState({ isLoading: true });
await hd.fetchTransactions();
if (hd.getTransactions().length !== 0) {
await hd.fetchBalance();
@@ -91,6 +93,7 @@ export default class ScanQrWif extends React.Component {
alert(loc.wallets.import.success);
this.props.navigation.popToTop();
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
+ this.setState({ isLoading: false });
return;
}
}
@@ -103,6 +106,7 @@ export default class ScanQrWif extends React.Component {
for (let w of BlueApp.wallets) {
if (w.getSecret() === hd.getSecret()) {
// lookig for duplicates
+ this.setState({ isLoading: false });
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
}
}
@@ -115,6 +119,7 @@ export default class ScanQrWif extends React.Component {
alert(loc.wallets.import.success);
this.props.navigation.popToTop();
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
+ this.setState({ isLoading: false });
return;
}
// nope
@@ -130,6 +135,7 @@ export default class ScanQrWif extends React.Component {
await lnd.fetchBalance();
} catch (Err) {
console.log(Err);
+ this.setState({ isLoading: false });
return;
}
@@ -138,6 +144,7 @@ export default class ScanQrWif extends React.Component {
this.props.navigation.popToTop();
alert(loc.wallets.import.success);
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
+ this.setState({ isLoading: false });
return;
}
// nope
@@ -165,6 +172,7 @@ export default class ScanQrWif extends React.Component {
await watchOnly.fetchTransactions();
await BlueApp.saveToDisk();
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
+ this.setState({ isLoading: false });
return;
}
// nope
@@ -176,6 +184,7 @@ export default class ScanQrWif extends React.Component {
if (newWallet.getAddress() === false || newLegacyWallet.getAddress() === false) {
alert(loc.wallets.scanQrWif.bad_wif);
+ this.setState({ isLoading: false });
return;
}
@@ -196,7 +205,7 @@ export default class ScanQrWif extends React.Component {
alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newWallet.getAddress());
}
await BlueApp.saveToDisk();
- this.props.navigation.popToTop();
+ this.props.navigation.dismiss();
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
} // end
@@ -210,7 +219,7 @@ export default class ScanQrWif extends React.Component {
render() {
if (this.state.isLoading) {
return (
-
+
);
@@ -275,6 +284,7 @@ ScanQrWif.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.func,
popToTop: PropTypes.func,
+ dismiss: PropTypes.func,
navigate: PropTypes.func,
}),
};
diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js
index 02e6c0931..184d4e38c 100644
--- a/screen/wallets/transactions.js
+++ b/screen/wallets/transactions.js
@@ -3,20 +3,7 @@ import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity, StatusBa
import LinearGradient from 'react-native-linear-gradient';
import PropTypes from 'prop-types';
import { NavigationEvents } from 'react-navigation';
-import {
- BlueText,
- BlueTransactionOnchainIcon,
- ManageFundsBigButton,
- BlueTransactionExpiredIcon,
- BlueTransactionIncommingIcon,
- BlueTransactionOutgoingIcon,
- BlueTransactionPendingIcon,
- BlueTransactionOffchainIcon,
- BlueSendButtonIcon,
- BlueReceiveButtonIcon,
- BlueListItem,
- BlueTransactionOffchainIncomingIcon,
-} from '../../BlueComponents';
+import { BlueText, ManageFundsBigButton, BlueSendButtonIcon, BlueReceiveButtonIcon, BlueTransactionListItem } from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { LightningCustodianWallet } from '../../class';
@@ -150,9 +137,12 @@ export default class WalletTransactions extends Component {
try {
/** @type {LegacyWallet} */
let wallet = that.state.wallet;
+ let balanceStart = +new Date();
const oldBalance = wallet.getBalance();
await wallet.fetchBalance();
if (oldBalance !== wallet.getBalance()) smthChanged = true;
+ let balanceEnd = +new Date();
+ console.log(wallet.getLabel(), 'fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
let start = +new Date();
const oldTxLen = wallet.getTransactions().length;
await wallet.fetchTransactions();
@@ -269,13 +259,6 @@ export default class WalletTransactions extends Component {
);
};
- txMemo(hash) {
- if (BlueApp.tx_metadata[hash] && BlueApp.tx_metadata[hash]['memo']) {
- return BlueApp.tx_metadata[hash]['memo'];
- }
- return '';
- }
-
_keyExtractor = (_item, index) => index.toString();
renderListHeaderComponent = () => {
@@ -305,55 +288,8 @@ export default class WalletTransactions extends Component {
this.onWillBlur();
}
- rowTitle = item => {
- 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 loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit(), true).toString();
- } else if (invoiceExpiration < now) {
- if (item.ispaid) {
- return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit(), true).toString();
- } else {
- return loc.lnd.expired;
- }
- }
- } else {
- return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit(), true).toString();
- }
- };
-
- rowTitleStyle = item => {
- let color = '#37c0a1';
-
- 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 = '#37c0a1';
- } else if (invoiceExpiration < now) {
- if (item.ispaid) {
- color = '#37c0a1';
- } else {
- color = '#FF0000';
- }
- }
- } else if (item.value / 100000000 < 0) {
- color = BlueApp.settings.foregroundColor;
- }
-
- return {
- fontWeight: '600',
- fontSize: 16,
- color: color,
- };
+ renderItem = item => {
+ return ;
};
render() {
@@ -413,8 +349,9 @@ export default class WalletTransactions extends Component {
+
{(this.isLightning() &&
- 'Lightning wallet should be used for your daily\ntransactions. Fees are unfairly cheap and\nspeed is blazing fast.') ||
+ 'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.') ||
loc.wallets.list.empty_txs1}
- {(this.isLightning() && '\nTo start using it tap on "manage funds"\nand topup your balance') ||
+ {(this.isLightning() && '\nTo start using it tap on "manage funds" and topup your balance') ||
loc.wallets.list.empty_txs2}
@@ -462,110 +399,8 @@ export default class WalletTransactions extends Component {
refreshControl={ this.refreshTransactions()} refreshing={this.state.isTransactionsLoading} />}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
- renderItem={rowData => {
- return (
- {
- // is it lightning refill tx?
- if (rowData.item.category === 'receive' && rowData.item.confirmations < 3) {
- return (
-
-
-
- );
- }
-
- if (rowData.item.type && rowData.item.type === 'bitcoind_tx') {
- return (
-
-
-
- );
- }
- if (rowData.item.type === 'paid_invoice') {
- // is it lightning offchain payment?
- return (
-
-
-
- );
- }
-
- if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') {
- if (!rowData.item.ispaid) {
- const currentDate = new Date();
- const now = (currentDate.getTime() / 1000) | 0;
- const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time;
- if (invoiceExpiration < now) {
- return (
-
-
-
- );
- }
- } else {
- return (
-
-
-
- );
- }
- }
-
- if (!rowData.item.confirmations) {
- return (
-
-
-
- );
- } else if (rowData.item.value < 0) {
- return (
-
-
-
- );
- } else {
- return (
-
-
-
- );
- }
- })()}
- title={loc.transactionTimeToReadable(rowData.item.received)}
- subtitle={
- (rowData.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + rowData.item.confirmations + ' ' : '') +
- this.txMemo(rowData.item.hash) +
- (rowData.item.memo || '')
- }
- onPress={() => {
- if (rowData.item.hash) {
- navigate('TransactionDetails', {
- hash: rowData.item.hash,
- });
- } else if (
- rowData.item.type === 'user_invoice' ||
- rowData.item.type === 'payment_request' ||
- rowData.item.type === 'paid_invoice'
- ) {
- this.props.navigation.navigate('LNDViewInvoice', {
- invoice: rowData.item,
- fromWallet: this.state.wallet,
- isModal: false,
- });
- }
- }}
- badge={{
- value: 3,
- textStyle: { color: 'orange' },
- containerStyle: { marginTop: 0 },
- }}
- hideChevron
- rightTitle={this.rowTitle(rowData.item)}
- rightTitleStyle={this.rowTitleStyle(rowData.item)}
- />
- );
- }}
+ initialNumToRender={10}
+ renderItem={this.renderItem}
/>