From 6017f4d990d6eed604c8cd4de69d8eb1c0e2f5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Rodriguez=20V=C3=A9lez?= Date: Sat, 2 Mar 2019 07:13:12 -0500 Subject: [PATCH] FIX: Create button was difficult to press on android. (#369) FIX: Create button was difficult to press on android. ADD: Clipboard detection on app state change to foreground OPS: Upgrade to RN 58.6 due to various important fixes FIX: Statusbar restored --- App.js | 137 ++++++++++++++++++++++++++++++++++++++--- BlueComponents.js | 6 +- Privacy.js | 2 +- package-lock.json | 64 ++++++++----------- package.json | 2 +- screen/send/details.js | 16 +++-- 6 files changed, 169 insertions(+), 58 deletions(-) diff --git a/App.js b/App.js index aeb3f2d1..ecdda1a4 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 { } }; - render() { + renderClipboardContentModal = () => { return ( - { - this.navigator = nav; - NavigationService.setTopLevelNavigator(nav); + { + 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.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 dbb03cc8..746bc4b7 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 91dc3e38..cd8e46c8 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 573e572d..94c57cd6 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 ? ( ) : (