diff --git a/BlueComponents.js b/BlueComponents.js index c68c0c4d8..3d6d57306 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -144,42 +144,41 @@ export const BlueButtonHook = props => { ); }; -export class SecondButton extends Component { - render() { - let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : BlueCurrentTheme.colors.buttonBlueBackgroundColor; - let fontColor = BlueCurrentTheme.colors.buttonTextColor; - if (this.props.disabled === true) { - backgroundColor = BlueCurrentTheme.colors.buttonDisabledBackgroundColor; - fontColor = BlueCurrentTheme.colors.buttonDisabledTextColor; - } - // let buttonWidth = this.props.width ? this.props.width : width / 1.5; - // if ('noMinWidth' in this.props) { - // buttonWidth = 0; - // } - return ( - - - {this.props.icon && } - {this.props.title && {this.props.title}} - - - ); +export const SecondButton = props => { + const { colors } = useTheme(); + let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor; + let fontColor = colors.buttonTextColor; + if (props.disabled === true) { + backgroundColor = colors.buttonDisabledBackgroundColor; + fontColor = colors.buttonDisabledTextColor; } -} + // let buttonWidth = this.props.width ? this.props.width : width / 1.5; + // if ('noMinWidth' in this.props) { + // buttonWidth = 0; + // } + return ( + + + {props.icon && } + {props.title && {props.title}} + + + ); +}; export const BitcoinButton = props => { const { colors } = useTheme(); diff --git a/Navigation.js b/Navigation.js index 27850e71f..16670c709 100644 --- a/Navigation.js +++ b/Navigation.js @@ -146,7 +146,7 @@ const WalletsRoot = () => ( component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions} swipeEnabled={false} - gesturesEnabled={false} + gestureEnabled={false} /> ( /> - + ); @@ -205,7 +212,7 @@ const LNDCreateInvoiceRoot = () => ( component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions} swipeEnabled={false} - gesturesEnabled={false} + gestureEnabled={false} /> { + const { txMetadata, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext); + const navigation = useNavigation(); + const route = useRoute(); + const { fromWallet, memo, psbt, deepLinkPSBT } = route.params; + const routeParamsPSBT = useRef(route.params.psbt); + const routeParamsTXHex = route.params.txhex; + const { colors } = useTheme(); + const [isLoading, setIsLoading] = useState(false); + const [txHex, setTxHex] = useState(route.params.txhex); - _combinePSBT = receivedPSBT => { - return this.state.fromWallet.combinePsbt(this.state.psbt, receivedPSBT); + const stylesHook = StyleSheet.create({ + root: { + backgroundColor: colors.elevated, + }, + rootPadding: { + backgroundColor: colors.elevated, + }, + hexWrap: { + backgroundColor: colors.elevated, + }, + hexLabel: { + color: colors.foregroundColor, + }, + hexInput: { + borderColor: colors.formBorder, + backgroundColor: colors.inputBackgroundColor, + color: colors.foregroundColor, + }, + hexText: { + color: colors.foregroundColor, + }, + }); + + const _combinePSBT = receivedPSBT => { + return fromWallet.combinePsbt(psbt, receivedPSBT); }; - onBarScanned = ret => { + const onBarScanned = ret => { if (ret && !ret.data) ret = { data: ret }; if (ret.data.toUpperCase().startsWith('UR')) { alert('BC-UR not decoded. This should never happen'); } if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) { // this looks like NOT base64, so maybe its transaction's hex - this.setState({ txhex: ret.data }); + setTxHex(ret.data); return; } try { - const Tx = this._combinePSBT(ret.data); - this.setState({ txhex: Tx.toHex() }); + const Tx = _combinePSBT(ret.data); + setTxHex(Tx.toHex()); } catch (Err) { alert(Err); } }; - constructor(props) { - super(props); - this.state = { - isLoading: false, - qrCodeHeight: height > width ? width - 40 : width / 3, - memo: props.route.params.memo, - psbt: props.route.params.psbt, - fromWallet: props.route.params.fromWallet, - isSecondPSBTAlreadyBase64: false, - deepLinkPSBT: undefined, - txhex: props.route.params.txhex || undefined, - animatedQRCodeData: [], - }; - this.fileName = `${Date.now()}.psbt`; - } - - static getDerivedStateFromProps(nextProps, prevState) { - if (!prevState.psbt && !nextProps.route.params.txhex) { - alert('There is no transaction signing in progress'); - return { - ...prevState, - isLoading: true, - }; + useEffect(() => { + if (!psbt && !route.params.txhex) { + alert(loc.send.no_tx_signing_in_progress); } - const deepLinkPSBT = nextProps.route.params.deepLinkPSBT; - const txhex = nextProps.route.params.txhex; if (deepLinkPSBT) { const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT); try { - const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, psbt); - return { - ...prevState, - txhex: Tx.toHex(), - }; + const Tx = fromWallet.combinePsbt(routeParamsPSBT.current, psbt); + setTxHex(Tx.toHex()); } catch (Err) { alert(Err); } - } else if (txhex) { - return { - ...prevState, - txhex: txhex, - }; + } else if (routeParamsTXHex) { + setTxHex(routeParamsTXHex); } - return prevState; - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [deepLinkPSBT, routeParamsTXHex]); - componentDidMount() { - console.log('send/psbtWithHardwareWallet - componentDidMount'); - } - - broadcast = () => { - this.setState({ isLoading: true }, async () => { - try { - await BlueElectrum.ping(); - await BlueElectrum.waitTillConnected(); - const result = await this.state.fromWallet.broadcastTx(this.state.txhex); - if (result) { - this.setState({ success: true, isLoading: false }); - const txDecoded = bitcoin.Transaction.fromHex(this.state.txhex); - const txid = txDecoded.getId(); - Notifications.majorTomToGroundControl([], [], [txid]); - if (this.state.memo) { - this.context.txMetadata[txid] = { memo: this.state.memo }; - } - } else { - ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - this.setState({ isLoading: false }); - alert(loc.errors.broadcast); + const broadcast = async () => { + setIsLoading(true); + try { + await BlueElectrum.ping(); + await BlueElectrum.waitTillConnected(); + const result = await fromWallet.broadcastTx(txHex); + if (result) { + setIsLoading(false); + const txDecoded = bitcoin.Transaction.fromHex(txHex); + const txid = txDecoded.getId(); + Notifications.majorTomToGroundControl([], [], [txid]); + if (memo) { + txMetadata[txid] = { memo }; } - } catch (error) { + fetchAndSaveWalletTransactions(fromWallet.getID()); + navigation.navigate('Success', { amount: undefined }); + } else { ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); - this.setState({ isLoading: false }); - alert(error.message); + setIsLoading(false); + alert(loc.errors.broadcast); } - }); + } catch (error) { + ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); + setIsLoading(false); + alert(error.message); + } }; - _renderSuccess() { - return ( - - - - this.props.navigation.dangerouslyGetParent().pop()} title={loc.send.success_done} /> - - - ); - } + const handleOnVerifyPressed = () => { + Linking.openURL('https://coinb.in/?verify=' + txHex); + }; - _renderBroadcastHex() { - return ( - - - {loc.send.create_this_is_hex} - + const copyHexToClipboard = () => { + Clipboard.setString(txHex); + }; - Clipboard.setString(this.state.txhex)}> - {loc.send.create_copy} + const _renderBroadcastHex = () => { + return ( + + + {loc.send.create_this_is_hex} + + + + {loc.send.create_copy} - Linking.openURL('https://coinb.in/?verify=' + this.state.txhex)}> - {loc.send.create_verify} + + {loc.send.create_verify} - + ); - } + }; - exportPSBT = async () => { + const exportPSBT = async () => { + const fileName = `${Date.now()}.psbt`; if (Platform.OS === 'ios') { - const filePath = RNFS.TemporaryDirectoryPath + `/${this.fileName}`; - await RNFS.writeFile(filePath, typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64()); + const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`; + await RNFS.writeFile(filePath, typeof psbt === 'string' ? psbt : psbt.toBase64()); Share.open({ url: 'file://' + filePath, saveToFiles: isDesktop, }) .catch(error => { console.log(error); - alert(error.message); }) .finally(() => { RNFS.unlink(filePath); @@ -286,25 +197,24 @@ export default class PsbtWithHardwareWallet extends Component { if (granted === PermissionsAndroid.RESULTS.GRANTED) { console.log('Storage Permission: Granted'); - const filePath = RNFS.DownloadDirectoryPath + `/${this.fileName}`; - await RNFS.writeFile(filePath, typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64()); - alert(loc.formatString(loc.send.txSaved, { filePath: this.fileName })); + const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`; + await RNFS.writeFile(filePath, typeof psbt === 'string' ? psbt : psbt.toBase64()); + alert(loc.formatString(loc.send.txSaved, { filePath: fileName })); } else { console.log('Storage Permission: Denied'); } } }; - openSignedTransaction = async () => { + const openSignedTransaction = async () => { try { const res = await DocumentPicker.pick({ type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles], }); const file = await RNFS.readFile(res.uri); if (file) { - this.setState({ isSecondPSBTAlreadyBase64: true }, () => this.onBarScanned({ data: file })); + onBarScanned({ data: file }); } else { - this.setState({ isSecondPSBTAlreadyBase64: false }); throw new Error(); } } catch (err) { @@ -314,7 +224,7 @@ export default class PsbtWithHardwareWallet extends Component { } }; - openScanner = () => { + const openScanner = () => { if (isDesktop) { ImagePicker.launchCamera( { @@ -327,7 +237,7 @@ export default class PsbtWithHardwareWallet extends Component { const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString(); LocalQRCode.decode(uri, (error, result) => { if (!error) { - this.onBarScanned(result); + onBarScanned(result); } else { alert(loc.send.qr_error_no_qrcode); } @@ -338,96 +248,127 @@ export default class PsbtWithHardwareWallet extends Component { }, ); } else { - this.props.navigation.navigate('ScanQRCodeRoot', { + navigation.navigate('ScanQRCodeRoot', { screen: 'ScanQRCode', params: { - launchedBy: this.props.route.name, + launchedBy: route.name, showFileImportButton: false, - onBarScanned: this.onBarScanned, + onBarScanned, }, }); } }; - render() { - if (this.state.isLoading) { - return ( - - + if (txHex) return _renderBroadcastHex(); + + return isLoading ? ( + + + + ) : ( + + + + + {loc.send.psbt_this_is_psbt} + + + + + + + + + + + + + - ); - } - - if (this.state.success) return this._renderSuccess(); - if (this.state.txhex) return this._renderBroadcastHex(); - - return ( - - - - - {loc.send.psbt_this_is_psbt} - - - - - - - - - - - - - - - - - ); - } -} - -PsbtWithHardwareWallet.propTypes = { - navigation: PropTypes.shape({ - goBack: PropTypes.func, - navigate: PropTypes.func, - dangerouslyGetParent: PropTypes.func, - }), - route: PropTypes.shape({ - params: PropTypes.object, - name: PropTypes.string, - }), + + + ); }; +export default PsbtWithHardwareWallet; + PsbtWithHardwareWallet.navigationOptions = () => ({ ...BlueNavigationStyle(null, false), title: loc.send.header, }); + +const styles = StyleSheet.create({ + root: { + flex: 1, + }, + scrollViewContent: { + flexGrow: 1, + justifyContent: 'space-between', + }, + container: { + flexDirection: 'row', + justifyContent: 'center', + paddingTop: 16, + paddingBottom: 16, + }, + rootPadding: { + flex: 1, + paddingTop: 20, + }, + hexWrap: { + alignItems: 'center', + flex: 1, + }, + hexLabel: { + fontWeight: '500', + }, + hexInput: { + borderRadius: 4, + marginTop: 20, + fontWeight: '500', + fontSize: 14, + paddingHorizontal: 16, + paddingBottom: 16, + paddingTop: 16, + }, + hexTouch: { + marginVertical: 24, + }, + hexText: { + fontSize: 15, + fontWeight: '500', + alignSelf: 'center', + }, + copyToClipboard: { + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/screen/send/success.js b/screen/send/success.js index adad08ac5..a6d115a47 100644 --- a/screen/send/success.js +++ b/screen/send/success.js @@ -1,9 +1,9 @@ import React, { useEffect, useRef } from 'react'; import LottieView from 'lottie-react-native'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import { View, StyleSheet } from 'react-native'; +import { View, StyleSheet, SafeAreaView } from 'react-native'; import { Text } from 'react-native-elements'; -import { BlueButton, SafeBlueArea, BlueCard } from '../../BlueComponents'; +import { BlueButton, BlueCard } from '../../BlueComponents'; import { BitcoinUnit } from '../../models/bitcoinUnits'; import loc from '../../loc'; import { useNavigation, useRoute, useTheme } from '@react-navigation/native'; @@ -11,7 +11,7 @@ import { useNavigation, useRoute, useTheme } from '@react-navigation/native'; const Success = () => { const { colors } = useTheme(); const { dangerouslyGetParent } = useNavigation(); - const { amount, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '' } = useRoute().params; + const { amount = 0, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '' } = useRoute().params; const animationRef = useRef(); const stylesHook = StyleSheet.create({ root: { @@ -39,14 +39,16 @@ const Success = () => { }, [colors]); return ( - - - {amount > 0 && ( - - {amount} - {' ' + amountUnit} - - )} + + + + {amount > 0 && ( + <> + {amount} + {' ' + amountUnit} + + )} + {fee > 0 && ( {loc.send.create_fee}: {fee} {BitcoinUnit.BTC} @@ -81,28 +83,26 @@ const Success = () => { ]} /> - + - - + + ); }; -Success.navigationOptions = { - headerShown: false, - gesturesEnabled: false, -}; - export default Success; const styles = StyleSheet.create({ root: { flex: 1, paddingTop: 19, + justifyContent: 'space-between', }, - amout: { + buttonContainer: { + padding: 58, + }, + amount: { alignItems: 'center', - flex: 1, }, view: { flexDirection: 'row',