Browse Source

REF: Receive Screen with React Hooks

master
marcosrdz 5 years ago
committed by Overtorment
parent
commit
68dc331eb3
  1. 14
      ios/Podfile.lock
  2. 345
      screen/receive/details.js

14
ios/Podfile.lock

@ -192,6 +192,8 @@ PODS:
- React-jsinspector (0.61.5) - React-jsinspector (0.61.5)
- react-native-biometrics (2.0.0): - react-native-biometrics (2.0.0):
- React - React
- react-native-blue-crypto (1.0.0):
- React
- react-native-blur (0.8.0): - react-native-blur (0.8.0):
- React - React
- react-native-camera (3.21.0): - react-native-camera (3.21.0):
@ -255,7 +257,7 @@ PODS:
- React - React
- RemobileReactNativeQrcodeLocalImage (1.0.4): - RemobileReactNativeQrcodeLocalImage (1.0.4):
- React - React
- RNCAsyncStorage (1.7.1): - RNCAsyncStorage (1.8.1):
- React - React
- RNDefaultPreference (1.4.1): - RNDefaultPreference (1.4.1):
- React - React
@ -263,7 +265,7 @@ PODS:
- React - React
- RNFS (2.16.6): - RNFS (2.16.6):
- React - React
- RNGestureHandler (1.5.6): - RNGestureHandler (1.6.1):
- React - React
- RNHandoff (0.0.3): - RNHandoff (0.0.3):
- React - React
@ -315,6 +317,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-biometrics (from `../node_modules/react-native-biometrics`) - react-native-biometrics (from `../node_modules/react-native-biometrics`)
- react-native-blue-crypto (from `../node_modules/react-native-blue-crypto`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)" - "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-camera (from `../node_modules/react-native-camera`) - react-native-camera (from `../node_modules/react-native-camera`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-document-picker (from `../node_modules/react-native-document-picker`)
@ -398,6 +401,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector" :path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-biometrics: react-native-biometrics:
:path: "../node_modules/react-native-biometrics" :path: "../node_modules/react-native-biometrics"
react-native-blue-crypto:
:path: "../node_modules/react-native-blue-crypto"
react-native-blur: react-native-blur:
:path: "../node_modules/@react-native-community/blur" :path: "../node_modules/@react-native-community/blur"
react-native-camera: react-native-camera:
@ -496,6 +501,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386 React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0 React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5 react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: a9749fa0c1162cdb2f56b93a5d97a72ebd56694f react-native-camera: a9749fa0c1162cdb2f56b93a5d97a72ebd56694f
react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154 react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154
@ -517,11 +523,11 @@ SPEC CHECKSUMS:
ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015 ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
RNCAsyncStorage: 8539fc80a0075fcc9c8e2dff84cd22dc5bf1dacf RNCAsyncStorage: 00bdf63f7f1e0f11d3323533dba4f222e58bf092
RNDefaultPreference: 12d246dd2222e66dadcd76cc1250560663befc3a RNDefaultPreference: 12d246dd2222e66dadcd76cc1250560663befc3a
RNDeviceInfo: 12faae605ba42a1a5041c3c41a77834bc23f049d RNDeviceInfo: 12faae605ba42a1a5041c3c41a77834bc23f049d
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
RNGestureHandler: 911d3b110a7a233a34c4f800e7188a84b75319c6 RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: d44a8bca6ee08f5d890ecccddaec2810955ffbb3 RNRate: d44a8bca6ee08f5d890ecccddaec2810955ffbb3

345
screen/receive/details.js

@ -1,6 +1,7 @@
import React, { Component } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { View, InteractionManager, Platform, TextInput, KeyboardAvoidingView, Keyboard, StyleSheet, ScrollView } from 'react-native'; import { View, InteractionManager, Platform, TextInput, KeyboardAvoidingView, Keyboard, StyleSheet, ScrollView } from 'react-native';
import QRCode from 'react-native-qrcode-svg'; import QRCode from 'react-native-qrcode-svg';
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
import bip21 from 'bip21'; import bip21 from 'bip21';
import { import {
BlueLoading, BlueLoading,
@ -15,7 +16,6 @@ import {
BlueSpacing20, BlueSpacing20,
BlueAlertWalletExportReminder, BlueAlertWalletExportReminder,
} from '../../BlueComponents'; } from '../../BlueComponents';
import PropTypes from 'prop-types';
import Privacy from '../../Privacy'; import Privacy from '../../Privacy';
import Share from 'react-native-share'; import Share from 'react-native-share';
import { Chain, BitcoinUnit } from '../../models/bitcoinUnits'; import { Chain, BitcoinUnit } from '../../models/bitcoinUnits';
@ -26,120 +26,113 @@ import Handoff from 'react-native-handoff';
const BlueApp = require('../../BlueApp'); const BlueApp = require('../../BlueApp');
const loc = require('../../loc'); const loc = require('../../loc');
export default class ReceiveDetails extends Component { const ReceiveDetails = () => {
static navigationOptions = ({ navigation }) => ({ const secret = useNavigationParam('secret');
...BlueNavigationStyle(navigation, true), const [wallet, setWallet] = useState();
title: loc.receive.header, const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
headerLeft: null, const [address, setAddress] = useState('');
}); const [customLabel, setCustomLabel] = useState();
const [customAmount, setCustomAmount] = useState(0);
const [bip21encoded, setBip21encoded] = useState();
const [qrCodeSVG, setQrCodeSVG] = useState();
const [isCustom, setIsCustom] = useState(false);
const [isCustomModalVisible, setIsCustomModalVisible] = useState(false);
const { navigate, goBack } = useNavigation();
constructor(props) { const renderReceiveDetails = useCallback(async () => {
super(props); console.log('receive/details - componentDidMount');
let secret = props.navigation.state.params.secret || ''; wallet.setUserHasSavedExport(true);
this.state = {
secret: secret,
isHandOffUseEnabled: false,
address: '',
customLabel: '',
customAmount: 0,
bip21encoded: undefined,
isCustom: false,
isCustomModalVisible: false,
};
}
renderReceiveDetails = async () => {
this.wallet.setUserHasSavedExport(true);
await BlueApp.saveToDisk(); await BlueApp.saveToDisk();
let address; let address;
if (this.wallet.getAddressAsync) { if (wallet.getAddressAsync) {
if (this.wallet.chain === Chain.ONCHAIN) { if (wallet.chain === Chain.ONCHAIN) {
try { try {
address = await Promise.race([this.wallet.getAddressAsync(), BlueApp.sleep(1000)]); address = await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(1000)]);
} catch (_) {} } catch (_) {}
if (!address) { if (!address) {
// either sleep expired or getAddressAsync threw an exception // either sleep expired or getAddressAsync threw an exception
console.warn('either sleep expired or getAddressAsync threw an exception'); console.warn('either sleep expired or getAddressAsync threw an exception');
address = this.wallet._getExternalAddressByIndex(this.wallet.next_free_address_index); address = wallet._getExternalAddressByIndex(wallet.next_free_address_index);
} else { } else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
} }
this.setState({ setAddress(address);
address: address, } else if (wallet.chain === Chain.OFFCHAIN) {
});
} else if (this.wallet.chain === Chain.OFFCHAIN) {
try { try {
await Promise.race([this.wallet.getAddressAsync(), BlueApp.sleep(1000)]); await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(1000)]);
address = this.wallet.getAddress(); address = wallet.getAddress();
} catch (_) {} } catch (_) {}
if (!address) { if (!address) {
// either sleep expired or getAddressAsync threw an exception // either sleep expired or getAddressAsync threw an exception
console.warn('either sleep expired or getAddressAsync threw an exception'); console.warn('either sleep expired or getAddressAsync threw an exception');
address = this.wallet.getAddress(); address = wallet.getAddress();
} else { } else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
} }
} }
this.setState({ setAddress(address);
address: address, } else if (wallet.getAddress) {
}); setAddress(wallet.getAddress());
} else if (this.wallet.getAddress) {
this.setState({
address: this.wallet.getAddress(),
});
} }
InteractionManager.runAfterInteractions(async () => { InteractionManager.runAfterInteractions(async () => {
const bip21encoded = bip21.encode(this.state.address); const bip21encoded = bip21.encode(address);
this.setState({ bip21encoded }); setBip21encoded(bip21encoded);
}); });
}; }, [wallet]);
componentDidMount() { useEffect(() => {
Privacy.enableBlur(); Privacy.enableBlur();
console.log('receive/details - componentDidMount');
for (let w of BlueApp.getWallets()) { setWallet(BlueApp.getWallets().find(w => w.getSecret() === secret));
if (w.getSecret() === this.state.secret) {
// found our wallet if (wallet) {
this.wallet = w; if (!wallet.getUserHasSavedExport()) {
}
}
if (this.wallet) {
if (!this.wallet.getUserHasSavedExport()) {
BlueAlertWalletExportReminder({ BlueAlertWalletExportReminder({
onSuccess: this.renderReceiveDetails, onSuccess: renderReceiveDetails,
onFailure: () => { onFailure: () => {
this.props.navigation.goBack(); goBack();
this.props.navigation.navigate('WalletExport', { navigate('WalletExport', {
wallet: this.wallet, wallet: wallet,
}); });
}, },
}); });
} else { } else {
this.renderReceiveDetails(); renderReceiveDetails();
} }
} }
HandoffSettings.isHandoffUseEnabled().then(value => this.setState({ isHandOffUseEnabled: value })); HandoffSettings.isHandoffUseEnabled().then(setIsHandOffUseEnabled);
} return () => Privacy.disableBlur();
}, [goBack, navigate, renderReceiveDetails, secret, wallet]);
componentWillUnmount() { const dismissCustomAmountModal = () => {
Privacy.disableBlur(); Keyboard.dismiss();
} setIsCustomModalVisible(false);
};
renderCustomAmountModal = () => { const showCustomAmountModal = () => {
setIsCustomModalVisible(true);
};
const createCustomAmountAddress = () => {
setIsCustom(true);
setIsCustomModalVisible(false);
setBip21encoded(bip21.encode(address, { amount: customAmount, label: customLabel }));
};
const clearCustomAmount = () => {
setIsCustom(false);
setIsCustomModalVisible(false);
setCustomAmount('');
setCustomLabel('');
setBip21encoded(bip21.encode(address));
};
const renderCustomAmountModal = () => {
return ( return (
<Modal <Modal isVisible={isCustomModalVisible} style={styles.bottomModal} onBackdropPress={dismissCustomAmountModal}>
isVisible={this.state.isCustomModalVisible}
style={styles.bottomModal}
onBackdropPress={() => {
Keyboard.dismiss();
this.setState({ isCustomModalVisible: false });
}}
>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}> <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={styles.modalContent}> <View style={styles.modalContent}>
<BlueBitcoinAmount amount={this.state.customAmount || ''} onChangeText={text => this.setState({ customAmount: text })} /> <BlueBitcoinAmount amount={customAmount || ''} onChangeText={setCustomAmount} />
<View <View
style={{ style={{
flexDirection: 'row', flexDirection: 'row',
@ -157,38 +150,18 @@ export default class ReceiveDetails extends Component {
}} }}
> >
<TextInput <TextInput
onChangeText={text => this.setState({ customLabel: text })} onChangeText={setCustomLabel}
placeholder={loc.receive.details.label} placeholder={loc.receive.details.label}
value={this.state.customLabel || ''} value={customLabel || ''}
numberOfLines={1} numberOfLines={1}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
/> />
</View> </View>
<BlueSpacing20 /> <BlueSpacing20 />
<View> <View>
<BlueButton <BlueButton title={loc.receive.details.create} onPress={createCustomAmountAddress} />
title={loc.receive.details.create}
onPress={() => {
this.setState({
isCustom: true,
isCustomModalVisible: false,
bip21encoded: bip21.encode(this.state.address, { amount: this.state.customAmount, label: this.state.customLabel }),
});
}}
/>
<BlueSpacing20 /> <BlueSpacing20 />
<BlueButtonLink <BlueButtonLink title="Reset" onPress={clearCustomAmount} />
title="Reset"
onPress={() => {
this.setState({
isCustom: false,
isCustomModalVisible: false,
customAmount: '',
customLabel: '',
bip21encoded: bip21.encode(this.state.addresss),
});
}}
/>
</View> </View>
<BlueSpacing20 /> <BlueSpacing20 />
</View> </View>
@ -197,87 +170,91 @@ export default class ReceiveDetails extends Component {
); );
}; };
showCustomAmountModal = () => { const handleShareButtonPressed = () => {
this.setState({ isCustomModalVisible: true }); if (qrCodeSVG === undefined) {
Share.open({ message: bip21encoded }).catch(error => console.log(error));
} else {
InteractionManager.runAfterInteractions(async () => {
qrCodeSVG.toDataURL(data => {
let shareImageBase64 = {
message: bip21encoded,
url: `data:image/png;base64,${data}`,
};
Share.open(shareImageBase64).catch(error => console.log(error));
});
});
}
}; };
render() { return (
return ( <SafeBlueArea style={{ flex: 1 }}>
<SafeBlueArea style={{ flex: 1 }}> {isHandOffUseEnabled && address !== undefined && (
{this.state.isHandOffUseEnabled && this.state.address !== undefined && ( <Handoff
<Handoff title={`Bitcoin Transaction ${address}`}
title={`Bitcoin Transaction ${this.state.address}`} type="io.bluewallet.bluewallet"
type="io.bluewallet.bluewallet" url={`https://blockstream.info/address/${address}`}
url={`https://blockstream.info/address/${this.state.address}`} />
/> )}
)} <ScrollView contentContainerStyle={{ justifyContent: 'space-between' }}>
<ScrollView contentContainerStyle={{ justifyContent: 'space-between' }}> <View style={{ marginTop: 32, alignItems: 'center', paddingHorizontal: 16 }}>
<View style={{ marginTop: 32, alignItems: 'center', paddingHorizontal: 16 }}> {isCustom && (
{this.state.isCustom && ( <>
<> <BlueText
<BlueText style={{ color: '#0c2550', fontWeight: '600', fontSize: 36, textAlign: 'center', paddingBottom: 24 }}
style={{ color: '#0c2550', fontWeight: '600', fontSize: 36, textAlign: 'center', paddingBottom: 24 }} numberOfLines={1}
numberOfLines={1} >
> {customAmount} {BitcoinUnit.BTC}
{this.state.customAmount} {BitcoinUnit.BTC} </BlueText>
</BlueText> <BlueText style={{ color: '#0c2550', fontWeight: '600', textAlign: 'center', paddingBottom: 24 }} numberOfLines={1}>
<BlueText style={{ color: '#0c2550', fontWeight: '600', textAlign: 'center', paddingBottom: 24 }} numberOfLines={1}> {customLabel}
{this.state.customLabel} </BlueText>
</BlueText> </>
</> )}
)} {bip21encoded === undefined ? (
{this.state.bip21encoded === undefined ? ( <View style={{ alignItems: 'center', width: 300, height: 300 }}>
<View style={{ alignItems: 'center', width: 300, height: 300 }}> <BlueLoading />
<BlueLoading />
</View>
) : (
<QRCode
value={this.state.bip21encoded}
logo={require('../../img/qr-code.png')}
size={(is.ipad() && 300) || 300}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'H'}
getRef={c => (this.qrCodeSVG = c)}
/>
)}
<BlueCopyTextToClipboard text={this.state.isCustom ? this.state.bip21encoded : this.state.address} />
</View>
<View style={{ alignItems: 'center', alignContent: 'flex-end', marginBottom: 24 }}>
<BlueButtonLink title={loc.receive.details.setAmount} onPress={this.showCustomAmountModal} />
<View>
<BlueButton
icon={{
name: 'share-alternative',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={async () => {
if (this.qrCodeSVG === undefined) {
Share.open({ message: this.state.bip21encoded }).catch(error => console.log(error));
} else {
InteractionManager.runAfterInteractions(async () => {
this.qrCodeSVG.toDataURL(data => {
let shareImageBase64 = {
message: this.state.bip21encoded,
url: `data:image/png;base64,${data}`,
};
Share.open(shareImageBase64).catch(error => console.log(error));
});
});
}
}}
title={loc.receive.details.share}
/>
</View> </View>
) : (
<QRCode
value={bip21encoded}
logo={require('../../img/qr-code.png')}
size={(is.ipad() && 300) || 300}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'H'}
getRef={setQrCodeSVG}
/>
)}
<BlueCopyTextToClipboard text={isCustom ? bip21encoded : address} />
</View>
<View style={{ alignItems: 'center', alignContent: 'flex-end', marginBottom: 24 }}>
<BlueButtonLink title={loc.receive.details.setAmount} onPress={showCustomAmountModal} />
<View>
<BlueButton
icon={{
name: 'share-alternative',
type: 'entypo',
color: BlueApp.settings.buttonTextColor,
}}
onPress={handleShareButtonPressed}
title={loc.receive.details.share}
/>
</View> </View>
{this.renderCustomAmountModal()} </View>
</ScrollView> {renderCustomAmountModal()}
</SafeBlueArea> </ScrollView>
); </SafeBlueArea>
} );
} };
ReceiveDetails.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.receive.header,
headerLeft: null,
});
export default ReceiveDetails;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
modalContent: { modalContent: {
@ -296,15 +273,3 @@ const styles = StyleSheet.create({
margin: 0, margin: 0,
}, },
}); });
ReceiveDetails.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.func,
navigate: PropTypes.func,
state: PropTypes.shape({
params: PropTypes.shape({
secret: PropTypes.string,
}),
}),
}),
};

Loading…
Cancel
Save