BlueWallet/screen/receive/details.js

360 lines
11 KiB
JavaScript
Raw Normal View History

2020-06-18 17:49:36 +02:00
import React, { useEffect, useState, useCallback, useRef } from 'react';
import {
View,
InteractionManager,
StatusBar,
Platform,
TextInput,
KeyboardAvoidingView,
Keyboard,
StyleSheet,
ScrollView,
} from 'react-native';
2019-02-14 06:15:56 +01:00
import QRCode from 'react-native-qrcode-svg';
2020-06-09 16:08:18 +02:00
import { useNavigation, useRoute } from '@react-navigation/native';
2019-01-21 14:55:39 +01:00
import {
BlueLoading,
SafeBlueArea,
BlueCopyTextToClipboard,
BlueButton,
BlueButtonLink,
BlueNavigationStyle,
is,
2019-12-15 07:10:22 +01:00
BlueBitcoinAmount,
BlueText,
BlueSpacing20,
BlueAlertWalletExportReminder,
2019-01-21 14:55:39 +01:00
} from '../../BlueComponents';
import Privacy from '../../Privacy';
2019-08-06 06:20:33 +02:00
import Share from 'react-native-share';
2019-12-15 07:10:22 +01:00
import { Chain, BitcoinUnit } from '../../models/bitcoinUnits';
import Modal from 'react-native-modal';
2020-04-10 18:14:50 +02:00
import HandoffSettings from '../../class/handoff';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
2020-04-10 18:14:50 +02:00
import Handoff from 'react-native-handoff';
2018-06-25 00:22:46 +02:00
/** @type {AppStorage} */
2019-12-15 07:10:22 +01:00
const BlueApp = require('../../BlueApp');
const loc = require('../../loc');
2020-06-09 16:08:18 +02:00
const currency = require('../../blue_modules/currency');
2018-01-30 23:42:38 +01:00
2020-04-20 06:03:36 +02:00
const ReceiveDetails = () => {
2020-05-27 13:12:17 +02:00
const { secret } = useRoute().params;
2020-06-09 16:08:18 +02:00
const wallet = BlueApp.getWallets().find(w => w.getSecret() === secret);
2020-04-20 06:03:36 +02:00
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const [address, setAddress] = useState('');
const [customLabel, setCustomLabel] = useState();
const [customAmount, setCustomAmount] = useState(0);
2020-06-09 16:08:18 +02:00
const [customUnit, setCustomUnit] = useState(BitcoinUnit.BTC);
2020-04-20 06:03:36 +02:00
const [bip21encoded, setBip21encoded] = useState();
2020-06-18 17:49:36 +02:00
const qrCodeSVG = useRef();
2020-04-20 06:03:36 +02:00
const [isCustom, setIsCustom] = useState(false);
const [isCustomModalVisible, setIsCustomModalVisible] = useState(false);
const { navigate, goBack } = useNavigation();
2018-01-30 23:42:38 +01:00
2020-04-20 06:03:36 +02:00
const renderReceiveDetails = useCallback(async () => {
console.log('receive/details - componentDidMount');
wallet.setUserHasSavedExport(true);
await BlueApp.saveToDisk();
let address;
2020-04-20 06:03:36 +02:00
if (wallet.getAddressAsync) {
if (wallet.chain === Chain.ONCHAIN) {
try {
2020-04-20 06:03:36 +02:00
address = await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(1000)]);
} catch (_) {}
if (!address) {
// either sleep expired or getAddressAsync threw an exception
console.warn('either sleep expired or getAddressAsync threw an exception');
2020-04-20 06:03:36 +02:00
address = wallet._getExternalAddressByIndex(wallet.next_free_address_index);
} else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
2019-10-01 00:13:22 +02:00
}
2020-04-20 06:03:36 +02:00
setAddress(address);
} else if (wallet.chain === Chain.OFFCHAIN) {
try {
2020-04-20 06:03:36 +02:00
await Promise.race([wallet.getAddressAsync(), BlueApp.sleep(1000)]);
address = wallet.getAddress();
} catch (_) {}
if (!address) {
// either sleep expired or getAddressAsync threw an exception
console.warn('either sleep expired or getAddressAsync threw an exception');
2020-04-20 06:03:36 +02:00
address = wallet.getAddress();
} else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
2019-09-12 04:05:01 +02:00
}
2019-05-02 22:33:03 +02:00
}
2020-04-20 06:03:36 +02:00
setAddress(address);
} else if (wallet.getAddress) {
setAddress(wallet.getAddress());
}
2019-05-02 22:33:03 +02:00
InteractionManager.runAfterInteractions(async () => {
const bip21encoded = DeeplinkSchemaMatch.bip21encode(address);
2020-04-20 06:03:36 +02:00
setBip21encoded(bip21encoded);
2019-05-02 22:33:03 +02:00
});
2020-04-20 06:03:36 +02:00
}, [wallet]);
2020-04-20 06:03:36 +02:00
useEffect(() => {
if (wallet) {
if (!wallet.getUserHasSavedExport()) {
BlueAlertWalletExportReminder({
2020-04-20 06:03:36 +02:00
onSuccess: renderReceiveDetails,
onFailure: () => {
2020-04-20 06:03:36 +02:00
goBack();
navigate('WalletExport', {
wallet: wallet,
});
},
});
} else {
2020-04-20 06:03:36 +02:00
renderReceiveDetails();
}
}
2020-04-20 06:03:36 +02:00
HandoffSettings.isHandoffUseEnabled().then(setIsHandOffUseEnabled);
2020-06-09 16:08:18 +02:00
Privacy.enableBlur();
2020-04-20 06:03:36 +02:00
return () => Privacy.disableBlur();
}, [goBack, navigate, renderReceiveDetails, secret, wallet]);
2018-01-30 23:42:38 +01:00
2020-04-20 06:03:36 +02:00
const dismissCustomAmountModal = () => {
Keyboard.dismiss();
setIsCustomModalVisible(false);
};
2020-04-20 06:03:36 +02:00
const showCustomAmountModal = () => {
setIsCustomModalVisible(true);
};
const createCustomAmountAddress = () => {
setIsCustom(true);
setIsCustomModalVisible(false);
2020-06-09 16:08:18 +02:00
let amount = customAmount;
switch (customUnit) {
case BitcoinUnit.BTC:
// nop
break;
case BitcoinUnit.SATS:
amount = currency.satoshiToBTC(customAmount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
if (BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]) {
// cache hit! we reuse old value that supposedly doesnt have rounding errors
amount = currency.satoshiToBTC(BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]);
} else {
amount = currency.fiatToBTC(customAmount);
}
break;
}
setBip21encoded(DeeplinkSchemaMatch.bip21encode(address, { amount, label: customLabel }));
2020-04-20 06:03:36 +02:00
};
const clearCustomAmount = () => {
setIsCustom(false);
setIsCustomModalVisible(false);
setCustomAmount('');
setCustomLabel('');
setBip21encoded(DeeplinkSchemaMatch.bip21encode(address));
2020-04-20 06:03:36 +02:00
};
const renderCustomAmountModal = () => {
2019-12-15 07:10:22 +01:00
return (
2020-04-20 06:03:36 +02:00
<Modal isVisible={isCustomModalVisible} style={styles.bottomModal} onBackdropPress={dismissCustomAmountModal}>
2019-12-15 07:10:22 +01:00
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={styles.modalContent}>
2020-06-09 16:08:18 +02:00
<BlueBitcoinAmount
unit={customUnit}
amount={customAmount || ''}
onChangeText={setCustomAmount}
onAmountUnitChange={setCustomUnit}
/>
<View style={styles.customAmount}>
2019-12-15 07:10:22 +01:00
<TextInput
2020-04-20 06:03:36 +02:00
onChangeText={setCustomLabel}
2020-06-09 17:55:19 +02:00
placeholderTextColor="#81868e"
2019-12-15 07:10:22 +01:00
placeholder={loc.receive.details.label}
2020-04-20 06:03:36 +02:00
value={customLabel || ''}
2019-12-15 07:10:22 +01:00
numberOfLines={1}
style={styles.customAmountText}
2019-12-15 07:10:22 +01:00
/>
</View>
<BlueSpacing20 />
<View>
2020-04-20 06:03:36 +02:00
<BlueButton title={loc.receive.details.create} onPress={createCustomAmountAddress} />
2019-12-15 07:10:22 +01:00
<BlueSpacing20 />
2020-04-20 06:03:36 +02:00
<BlueButtonLink title="Reset" onPress={clearCustomAmount} />
2019-12-15 07:10:22 +01:00
</View>
<BlueSpacing20 />
</View>
</KeyboardAvoidingView>
</Modal>
);
};
2020-04-20 06:03:36 +02:00
const handleShareButtonPressed = () => {
if (qrCodeSVG === undefined) {
Share.open({ message: bip21encoded }).catch(error => console.log(error));
} else {
InteractionManager.runAfterInteractions(async () => {
2020-06-18 17:49:36 +02:00
qrCodeSVG.current.toDataURL(data => {
const shareImageBase64 = {
2020-04-20 06:03:36 +02:00
message: bip21encoded,
url: `data:image/png;base64,${data}`,
};
Share.open(shareImageBase64).catch(error => console.log(error));
});
});
}
2019-12-15 07:10:22 +01:00
};
2020-06-09 16:08:18 +02:00
/**
* @returns {string} BTC amount, accounting for current `customUnit` and `customUnit`
*/
const getDisplayAmount = () => {
switch (customUnit) {
case BitcoinUnit.BTC:
return customAmount + ' BTC';
case BitcoinUnit.SATS:
return currency.satoshiToBTC(customAmount) + ' BTC';
case BitcoinUnit.LOCAL_CURRENCY:
return currency.fiatToBTC(customAmount) + ' BTC';
}
return customAmount + ' ' + customUnit;
};
2020-04-20 06:03:36 +02:00
return (
<SafeBlueArea style={styles.root}>
<StatusBar barStyle="light-content" />
2020-04-20 06:03:36 +02:00
{isHandOffUseEnabled && address !== undefined && (
<Handoff
title={`Bitcoin Transaction ${address}`}
type="io.bluewallet.bluewallet"
url={`https://blockstream.info/address/${address}`}
/>
)}
2020-06-09 16:08:18 +02:00
<ScrollView contentContainerStyle={styles.scroll} keyboardShouldPersistTaps="always">
<View style={styles.scrollBody}>
2020-04-20 06:03:36 +02:00
{isCustom && (
<>
<BlueText style={styles.amount} numberOfLines={1}>
2020-06-09 16:08:18 +02:00
{getDisplayAmount()}
2020-04-20 06:03:36 +02:00
</BlueText>
<BlueText style={styles.label} numberOfLines={1}>
2020-04-20 06:03:36 +02:00
{customLabel}
</BlueText>
</>
)}
2020-06-09 16:08:18 +02:00
{bip21encoded === undefined ? (
<View style={styles.loading}>
2020-04-20 06:03:36 +02:00
<BlueLoading />
2019-05-02 22:33:03 +02:00
</View>
2020-04-20 06:03:36 +02:00
) : (
<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"
2020-06-18 17:49:36 +02:00
getRef={qrCodeSVG}
2020-04-20 06:03:36 +02:00
/>
)}
<BlueCopyTextToClipboard text={isCustom ? bip21encoded : address} />
</View>
<View style={styles.share}>
2020-04-20 06:03:36 +02:00
<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>
2020-04-20 06:03:36 +02:00
</View>
{renderCustomAmountModal()}
</ScrollView>
</SafeBlueArea>
);
};
ReceiveDetails.navigationOptions = ({ navigation }) => ({
...BlueNavigationStyle(navigation, true),
title: loc.receive.header,
headerLeft: null,
});
export default ReceiveDetails;
2018-03-18 00:09:33 +01:00
2019-12-15 07:10:22 +01:00
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: 350,
height: 350,
},
bottomModal: {
justifyContent: 'flex-end',
margin: 0,
},
customAmount: {
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
},
customAmountText: {
flex: 1,
marginHorizontal: 8,
color: '#81868e',
minHeight: 33,
},
root: {
flex: 1,
},
scroll: {
justifyContent: 'space-between',
},
scrollBody: {
marginTop: 32,
alignItems: 'center',
paddingHorizontal: 16,
},
amount: {
color: '#0c2550',
fontWeight: '600',
fontSize: 36,
textAlign: 'center',
paddingBottom: 24,
},
label: {
color: '#0c2550',
fontWeight: '600',
textAlign: 'center',
paddingBottom: 24,
},
loading: {
alignItems: 'center',
width: 300,
height: 300,
},
share: {
alignItems: 'center',
alignContent: 'flex-end',
marginBottom: 24,
},
2019-12-15 07:10:22 +01:00
});