diff --git a/App.js b/App.js
index aeb3f2d10..ecdda1a4f 100644
--- a/App.js
+++ b/App.js
@@ -1,22 +1,76 @@
import React from 'react';
-import { Linking } from 'react-native';
+import { Linking, AppState, Clipboard, StyleSheet, KeyboardAvoidingView, Platform, View } from 'react-native';
+import Modal from 'react-native-modal';
import { NavigationActions } from 'react-navigation';
import MainBottomTabs from './MainBottomTabs';
import NavigationService from './NavigationService';
+import { BlueTextCentered, BlueButton } from './BlueComponents';
+const bitcoin = require('bitcoinjs-lib');
+const bitcoinModalString = 'Bitcoin address';
+const lightningModalString = 'Lightning Invoice';
+let loc = require('./loc');
export default class App extends React.Component {
navigator = null;
+ state = {
+ appState: AppState.currentState,
+ isClipboardContentModalVisible: false,
+ clipboardContentModalAddressType: bitcoinModalString,
+ clipboardContent: '',
+ };
+
componentDidMount() {
Linking.getInitialURL()
.then(url => this.handleOpenURL({ url }))
.catch(console.error);
Linking.addEventListener('url', this.handleOpenURL);
+ AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
+ AppState.removeEventListener('change', this._handleAppStateChange);
+ }
+
+ _handleAppStateChange = async nextAppState => {
+ if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
+ const clipboard = await Clipboard.getString();
+ if (this.state.clipboardContent !== clipboard && (this.isBitcoinAddress(clipboard) || this.isLightningInvoice(clipboard))) {
+ this.setState({ isClipboardContentModalVisible: true });
+ }
+
+ this.setState({ clipboardContent: clipboard });
+ }
+ this.setState({ appState: nextAppState });
+ };
+
+ isBitcoinAddress(address) {
+ let isValidBitcoinAddress = false;
+ try {
+ bitcoin.address.toOutputScript(address);
+ isValidBitcoinAddress = true;
+ this.setState({ clipboardContentModalAddressType: bitcoinModalString });
+ } catch (err) {
+ isValidBitcoinAddress = false;
+ }
+ if (!isValidBitcoinAddress) {
+ if (address.indexOf('bitcoin:') === 0 || address.indexOf('BITCOIN:') === 0) {
+ isValidBitcoinAddress = true;
+ this.setState({ clipboardContentModalAddressType: bitcoinModalString });
+ }
+ }
+ return isValidBitcoinAddress;
+ }
+
+ isLightningInvoice(invoice) {
+ let isValidLightningInvoice = false;
+ if (invoice.indexOf('lightning:lnb') === 0 || invoice.indexOf('LIGHTNING:lnb') === 0 || invoice.toLowerCase().startsWith('lnb')) {
+ this.setState({ clipboardContentModalAddressType: lightningModalString });
+ isValidLightningInvoice = true;
+ }
+ return isValidLightningInvoice;
}
handleOpenURL = event => {
@@ -26,7 +80,7 @@ export default class App extends React.Component {
if (typeof event.url !== 'string') {
return;
}
- if (event.url.indexOf('bitcoin:') === 0 || event.url.indexOf('BITCOIN:') === 0) {
+ if (this.isBitcoinAddress(event.url)) {
this.navigator &&
this.navigator.dispatch(
NavigationActions.navigate({
@@ -36,7 +90,7 @@ export default class App extends React.Component {
},
}),
);
- } else if (event.url.indexOf('lightning:') === 0 || event.url.indexOf('LIGHTNING:') === 0) {
+ } else if (this.isLightningInvoice(event.url)) {
this.navigator &&
this.navigator.dispatch(
NavigationActions.navigate({
@@ -49,14 +103,79 @@ export default class App extends React.Component {
}
};
+ renderClipboardContentModal = () => {
+ return (
+ {
+ this.setState({ isClipboardContentModalVisible: false });
+ }}
+ >
+
+
+
+ You have a {this.state.clipboardContentModalAddressType} on your clipboard. Would you like to use it for a transaction?
+
+
+ this.setState({ isClipboardContentModalVisible: false })}
+ />
+
+ {
+ this.setState({ isClipboardContentModalVisible: false }, async () => {
+ const clipboard = await Clipboard.getString();
+ setTimeout(() => this.handleOpenURL({ url: clipboard }), 100);
+ });
+ }}
+ />
+
+
+
+
+ );
+ };
+
render() {
return (
- {
- this.navigator = nav;
- NavigationService.setTopLevelNavigator(nav);
- }}
- />
+
+ {
+ this.navigator = nav;
+ NavigationService.setTopLevelNavigator(nav);
+ }}
+ />
+ {this.renderClipboardContentModal()}
+
);
}
}
+
+const styles = StyleSheet.create({
+ modalContent: {
+ backgroundColor: '#FFFFFF',
+ padding: 22,
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ borderColor: 'rgba(0, 0, 0, 0.1)',
+ minHeight: 200,
+ height: 200,
+ },
+ bottomModal: {
+ justifyContent: 'flex-end',
+ margin: 0,
+ },
+ modelContentButtonLayout: {
+ flexDirection: 'row',
+ margin: 16,
+ justifyContent: 'space-between',
+ alignItems: 'flex-end',
+ },
+});
diff --git a/BlueComponents.js b/BlueComponents.js
index dbb03cc8b..746bc4b7f 100644
--- a/BlueComponents.js
+++ b/BlueComponents.js
@@ -51,6 +51,10 @@ export class BlueButton extends Component {
backgroundColor = '#eef0f4';
fontColor = '#9aa0aa';
}
+ let buttonWidth = width / 1.5;
+ if (this.props.hasOwnProperty('noMinWidth')) {
+ buttonWidth = 0;
+ }
return (
= 1.36.0 < 2"
+ "mime-db": ">= 1.38.0 < 2"
}
},
"compression": {
@@ -2769,13 +2769,14 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cosmiconfig": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz",
- "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.1.0.tgz",
+ "integrity": "sha512-kCNPvthka8gvLtzAxQXvWo4FxqRB+ftRZyPZNuab5ngvM9Y7yw7hbEysglptLgpkGX9nAOKTBVkHUAe8xtYR6Q==",
"requires": {
"import-fresh": "^2.0.0",
"is-directory": "^0.3.1",
"js-yaml": "^3.9.0",
+ "lodash.get": "^4.4.2",
"parse-json": "^4.0.0"
}
},
@@ -3944,9 +3945,9 @@
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
},
"fbjs-scripts": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-1.0.1.tgz",
- "integrity": "sha512-x8bfX7k0z5B24Ue0YqjZq/2QxxaKZUNbkGdX//zbQDElMJFqBRrvRi8O3qds7UNNzs78jYqIYCS32Sk/wu5UJg==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-1.1.0.tgz",
+ "integrity": "sha512-VMCpHJd76YI2nYOfVM/d9LDAIFTH4uw4/7sAIGEgxk6kaNmirgTY9bLgpla9DTu+DvV2+ufvDxehGbl2U9bYCA==",
"requires": {
"@babel/core": "^7.0.0",
"ansi-colors": "^1.0.1",
@@ -8212,6 +8213,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
+ "lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
+ },
"lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
@@ -8867,22 +8873,6 @@
"metro-cache": "0.49.2",
"metro-core": "0.49.2",
"pretty-format": "24.0.0-alpha.6"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz",
- "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w=="
- },
- "pretty-format": {
- "version": "24.0.0-alpha.6",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0-alpha.6.tgz",
- "integrity": "sha512-zG2m6YJeuzwBFqb5EIdmwYVf30sap+iMRuYNPytOccEXZMAJbPIFGKVJ/U0WjQegmnQbRo9CI7j6j3HtDaifiA==",
- "requires": {
- "ansi-regex": "^4.0.0",
- "ansi-styles": "^3.2.0"
- }
- }
}
},
"metro-core": {
@@ -9569,9 +9559,9 @@
}
},
"on-headers": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
- "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"once": {
"version": "1.4.0",
@@ -10778,9 +10768,9 @@
}
},
"pretty-format": {
- "version": "24.0.0-alpha.4",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0-alpha.4.tgz",
- "integrity": "sha512-icvbBt3XlLEVqPHdHwR2Ou9+hezS9Eccd+mA+fXfOU7T9t7ClOpq2HgCwlyw+3WogccCubKWnmzyrA/3ZZ/aOA==",
+ "version": "24.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0-alpha.6.tgz",
+ "integrity": "sha512-zG2m6YJeuzwBFqb5EIdmwYVf30sap+iMRuYNPytOccEXZMAJbPIFGKVJ/U0WjQegmnQbRo9CI7j6j3HtDaifiA==",
"requires": {
"ansi-regex": "^4.0.0",
"ansi-styles": "^3.2.0"
@@ -11087,9 +11077,9 @@
}
},
"react-native": {
- "version": "0.58.1",
- "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.58.1.tgz",
- "integrity": "sha512-8aD0PBTney5dKQ4MBOfBEcHmdm2OBCx/9gSbeT4OUXE54fNNmDfbkVnx7EZ1iwvEdOiAl+pEpWqgAb/tvhRwBA==",
+ "version": "0.58.6",
+ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.58.6.tgz",
+ "integrity": "sha512-m/7L0gYXS4yHjs+PKmyurh1LLr7/tpobAX8Iv7Dwu4XT1ZcZFeCATn420E9U3nC2XsT54AmRR2Fv7VGgf+M2vQ==",
"requires": {
"@babel/runtime": "^7.0.0",
"absolute-path": "^0.0.0",
@@ -11129,11 +11119,11 @@
"opn": "^3.0.2",
"optimist": "^0.6.1",
"plist": "^3.0.0",
- "pretty-format": "24.0.0-alpha.4",
+ "pretty-format": "24.0.0-alpha.6",
"promise": "^7.1.1",
"prop-types": "^15.5.8",
"react-clone-referenced-element": "^1.0.1",
- "react-devtools-core": "^3.4.0",
+ "react-devtools-core": "^3.4.2",
"regenerator-runtime": "^0.11.0",
"rimraf": "^2.5.4",
"semver": "^5.0.3",
diff --git a/package.json b/package.json
index 91dc3e38c..cd8e46c85 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
"prop-types": "15.6.2",
"react": "16.7.0",
"react-localization": "1.0.10",
- "react-native": "0.58.1",
+ "react-native": "0.58.6",
"react-native-camera": "1.10.0",
"react-native-device-info": "0.26.1",
"react-native-elements": "0.19.0",
diff --git a/screen/send/details.js b/screen/send/details.js
index 573e572d0..94c57cd6e 100644
--- a/screen/send/details.js
+++ b/screen/send/details.js
@@ -198,11 +198,11 @@ export default class SendDetails extends Component {
};
decodeBitcoinUri(uri) {
+ let amount = '';
+ let parsedBitcoinUri = null;
+ let address = uri || '';
+ let memo = '';
try {
- let amount = '';
- let parsedBitcoinUri = null;
- let address = '';
- let memo = '';
parsedBitcoinUri = bip21.decode(uri);
address = parsedBitcoinUri.hasOwnProperty('address') ? parsedBitcoinUri.address : address;
if (parsedBitcoinUri.hasOwnProperty('options')) {
@@ -213,10 +213,8 @@ export default class SendDetails extends Component {
memo = parsedBitcoinUri.options.label || memo;
}
}
- return { address, amount, memo };
- } catch (_) {
- return undefined;
- }
+ } catch (_) {}
+ return { address, amount, memo };
}
recalculateAvailableBalance(balance, amount, fee) {
@@ -521,7 +519,7 @@ export default class SendDetails extends Component {
renderCreateButton = () => {
return (
-
+
{this.state.isLoading ? (
) : (