/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
import React, { Component, useState, useMemo, useCallback, useContext, useRef, useEffect, forwardRef } from 'react';
import PropTypes from 'prop-types';
import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements';
import {
ActivityIndicator,
Alert,
Animated,
Dimensions,
Image,
InputAccessoryView,
Keyboard,
KeyboardAvoidingView,
Linking,
PixelRatio,
Platform,
PlatformColor,
SafeAreaView,
StyleSheet,
Switch,
TextInput,
TouchableOpacity,
TouchableWithoutFeedback,
View,
InteractionManager,
I18nManager,
} from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import { LightningCustodianWallet, MultisigHDWallet } from './class';
import { BitcoinUnit } from './models/bitcoinUnits';
import * as NavigationService from './NavigationService';
import WalletGradient from './class/wallet-gradient';
import { BlurView } from '@react-native-community/blur';
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
import Biometric from './class/biometrics';
import { encodeUR } from './blue_modules/ur';
import QRCode from 'react-native-qrcode-svg';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useNavigation, useTheme } from '@react-navigation/native';
import { BlueCurrentTheme } from './components/themes';
import loc, { formatBalance, formatBalanceWithoutSuffix, transactionTimeToReadable } from './loc';
import Lnurl from './class/lnurl';
import { BlueStorageContext } from './blue_modules/storage-context';
import ToolTipMenu from './components/TooltipMenu';
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
} else {
isIpad = true;
}
// eslint-disable-next-line no-unused-expressions
Platform.OS === 'android' ? (ActivityIndicator.defaultProps.color = PlatformColor('?attr/colorControlActivated')) : null;
export const BlueButton = props => {
const { colors } = useTheme();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor || BlueCurrentTheme.colors.mainColor;
let fontColor = props.buttonTextColor || colors.buttonTextColor;
if (props.disabled === true) {
backgroundColor = colors.buttonDisabledBackgroundColor;
fontColor = colors.buttonDisabledTextColor;
}
return (
{props.icon && }
{props.title && {props.title}}
);
};
export const SecondButton = forwardRef((props, ref) => {
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;
}
return (
{props.icon && }
{props.title && {props.title}}
);
});
export const BitcoinButton = props => {
const { colors } = useTheme();
return (
{loc.wallets.add_bitcoin}
{loc.wallets.add_bitcoin_explain}
);
};
export const VaultButton = props => {
const { colors } = useTheme();
return (
{loc.multisig.multisig_vault}
{loc.multisig.multisig_vault_explain}
);
};
export const LightningButton = props => {
const { colors } = useTheme();
return (
{loc.wallets.add_lightning}
{loc.wallets.add_lightning_explain}
);
};
export class BlueWalletNavigationHeader extends Component {
static propTypes = {
wallet: PropTypes.shape().isRequired,
onWalletUnitChange: PropTypes.func,
};
static getDerivedStateFromProps(props) {
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange };
}
static contextType = BlueStorageContext;
walletBalanceText = React.createRef();
tooltip = React.createRef();
constructor(props) {
super(props);
this.state = {
wallet: props.wallet,
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
allowOnchainAddress: false,
};
}
handleCopyPress = _item => {
Clipboard.setString(formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
};
componentDidUpdate(prevState) {
InteractionManager.runAfterInteractions(() => {
if (prevState.wallet.getID() !== this.state.wallet.getID() && this.state.wallet.type === LightningCustodianWallet.type) {
this.verifyIfWalletAllowsOnchainAddress();
}
});
}
verifyIfWalletAllowsOnchainAddress = () => {
if (this.state.wallet.type === LightningCustodianWallet.type) {
this.state.wallet
.allowOnchainAddress()
.then(value => this.setState({ allowOnchainAddress: value }))
.catch(e => {
console.log('This Lndhub wallet does not have an onchain address API.');
this.setState({ allowOnchainAddress: false });
});
}
};
componentDidMount() {
this.verifyIfWalletAllowsOnchainAddress();
}
handleBalanceVisibility = async _item => {
const wallet = this.state.wallet;
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled && wallet.hideBalance) {
if (!(await Biometric.unlockWithBiometrics())) {
return this.props.navigation.goBack();
}
}
wallet.hideBalance = !wallet.hideBalance;
this.setState({ wallet });
await this.context.saveToDisk();
};
changeWalletBalanceUnit = () => {
let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit();
const wallet = this.state.wallet;
if (walletPreviousPreferredUnit === BitcoinUnit.BTC) {
wallet.preferredBalanceUnit = BitcoinUnit.SATS;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
} else if (walletPreviousPreferredUnit === BitcoinUnit.SATS) {
wallet.preferredBalanceUnit = BitcoinUnit.LOCAL_CURRENCY;
walletPreviousPreferredUnit = BitcoinUnit.SATS;
} else if (walletPreviousPreferredUnit === BitcoinUnit.LOCAL_CURRENCY) {
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
} else {
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
}
this.setState({ wallet, walletPreviousPreferredUnit: walletPreviousPreferredUnit }, () => {
this.props.onWalletUnitChange(wallet);
});
};
manageFundsPressed = () => {
this.props.onManageFundsPressed();
};
showToolTipMenu = () => {
this.tooltip.current.showMenu();
};
render() {
const balance =
!this.state.wallet.hideBalance &&
formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString();
return (
{
switch (this.state.wallet.type) {
case LightningCustodianWallet.type:
return I18nManager.isRTL ? require('./img/lnd-shape-rtl.png') : require('./img/lnd-shape.png');
case MultisigHDWallet.type:
return I18nManager.isRTL ? require('./img/vault-shape-rtl.png') : require('./img/vault-shape.png');
default:
return I18nManager.isRTL ? require('./img/btc-shape-rtl.png') : require('./img/btc-shape.png');
}
})()}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
{this.state.wallet.getLabel()}
{this.state.wallet.hideBalance ? (
) : (
{balance}
)}
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
{loc.lnd.title}
)}
{this.state.wallet.type === MultisigHDWallet.type && (
{loc.multisig.manage_keys}
)}
);
}
}
export const BlueButtonLink = forwardRef((props, ref) => {
const { colors } = useTheme();
return (
{props.title}
);
});
export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => {
Alert.alert(
loc.wallets.details_title,
loc.pleasebackup.ask,
[
{ text: loc.pleasebackup.ask_yes, onPress: onSuccess, style: 'cancel' },
{ text: loc.pleasebackup.ask_no, onPress: onFailure },
],
{ cancelable: false },
);
};
export const BluePrivateBalance = () => {
return Platform.select({
ios: (
),
android: (
),
});
};
export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => {
return (
Clipboard.setString(stringToCopy)}>
{displayText || loc.transactions.details_copy}
);
};
export class BlueCopyTextToClipboard extends Component {
static propTypes = {
text: PropTypes.string,
};
static defaultProps = {
text: '',
};
constructor(props) {
super(props);
this.state = { hasTappedText: false, address: props.text };
}
static getDerivedStateFromProps(props, state) {
if (state.hasTappedText) {
return { hasTappedText: state.hasTappedText, address: state.address };
} else {
return { hasTappedText: state.hasTappedText, address: props.text };
}
}
copyToClipboard = () => {
this.setState({ hasTappedText: true }, () => {
Clipboard.setString(this.props.text);
this.setState({ address: loc.wallets.xpub_copiedToClipboard }, () => {
setTimeout(() => {
this.setState({ hasTappedText: false, address: this.props.text });
}, 1000);
});
});
};
render() {
return (
{this.state.address}
);
}
}
const styleCopyTextToClipboard = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
export const SafeBlueArea = props => {
const { style, ...nonStyleProps } = props;
const { colors } = useTheme();
const baseStyle = { flex: 1, backgroundColor: colors.background };
return ;
};
export const BlueCard = props => {
return ;
};
export const BlueText = props => {
const { colors } = useTheme();
return (
);
};
export const BlueTextCentered = props => {
const { colors } = useTheme();
return ;
};
export const BlueListItem = React.memo(props => {
const { colors } = useTheme();
return (
{props.leftAvatar && {props.leftAvatar}}
{props.leftIcon && }
{props.title}
{props.subtitle && (
{props.subtitle}
)}
{props.rightTitle && (
{props.rightTitle}
)}
{props.isLoading ? (
) : (
<>
{props.chevron && }
{props.rightIcon && }
{props.switch && }
{props.checkmark && }
>
)}
);
});
export const BlueFormLabel = props => {
const { colors } = useTheme();
return (
);
};
export const BlueFormInput = props => {
const { colors } = useTheme();
return (
);
};
export const BlueFormMultiInput = props => {
const { colors } = useTheme();
return (
);
};
export const BlueHeader = props => {
return (
);
};
export const BlueHeaderDefaultSub = props => {
const { colors } = useTheme();
return (
{props.leftText}
}
{...props}
/>
);
};
export const BlueHeaderDefaultMain = props => {
const { colors } = useTheme();
const { isDrawerList } = props;
return (
}
/>
);
};
export const BlueSpacing = props => {
return ;
};
export const BlueSpacing40 = props => {
return ;
};
export const BlueSpacingVariable = props => {
if (isIpad) {
return ;
} else {
return ;
}
};
export class is {
static ipad() {
return isIpad;
}
}
export const BlueSpacing20 = props => {
return ;
};
export const BlueSpacing10 = props => {
return ;
};
export const BlueDismissKeyboardInputAccessory = () => {
const { colors } = useTheme();
BlueDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory';
return Platform.OS !== 'ios' ? null : (
);
};
export const BlueDoneAndDismissKeyboardInputAccessory = props => {
const { colors } = useTheme();
BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory';
const onPasteTapped = async () => {
const clipboard = await Clipboard.getString();
props.onPasteTapped(clipboard);
};
const inputView = (
);
if (Platform.OS === 'ios') {
return {inputView};
} else {
return {inputView};
}
};
export const BlueLoading = props => {
return (
);
};
const stylesBlueIcon = StyleSheet.create({
container: {
flex: 1,
},
box1: {
position: 'relative',
top: 15,
},
box: {
alignSelf: 'flex-end',
paddingHorizontal: 14,
paddingTop: 8,
},
boxIncoming: {
position: 'relative',
},
ball: {
width: 30,
height: 30,
borderRadius: 15,
},
ballIncoming: {
width: 30,
height: 30,
borderRadius: 15,
transform: [{ rotate: '-45deg' }],
justifyContent: 'center',
},
ballIncomingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
},
ballReceive: {
width: 30,
height: 30,
borderBottomLeftRadius: 15,
transform: [{ rotate: '-45deg' }],
},
ballOutgoing: {
width: 30,
height: 30,
borderRadius: 15,
transform: [{ rotate: '225deg' }],
justifyContent: 'center',
},
ballOutgoingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
},
ballOutgoingExpired: {
width: 30,
height: 30,
borderRadius: 15,
justifyContent: 'center',
},
ballTransparrent: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'transparent',
},
ballDimmed: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'gray',
},
});
export const BluePlusIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ball: {
backgroundColor: colors.buttonBackgroundColor,
},
});
return (
);
};
export const BlueTransactionIncomingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballIncoming: {
backgroundColor: colors.ballReceive,
},
});
return (
);
};
export const BlueTransactionPendingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ball: {
backgroundColor: colors.buttonBackgroundColor,
},
});
return (
);
};
export const BlueTransactionExpiredIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballOutgoingExpired: {
backgroundColor: colors.ballOutgoingExpired,
},
});
return (
);
};
export const BlueTransactionOnchainIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballIncoming: {
backgroundColor: colors.ballReceive,
},
});
return (
);
};
export const BlueTransactionOffchainIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballOutgoingWithoutRotate: {
backgroundColor: colors.ballOutgoing,
},
});
return (
);
};
export const BlueTransactionOffchainIncomingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballIncomingWithoutRotate: {
backgroundColor: colors.ballReceive,
},
});
return (
);
};
export const BlueTransactionOutgoingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballOutgoing: {
backgroundColor: colors.ballOutgoing,
},
});
return (
);
};
const sendReceiveScanButtonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
? 22
: PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26);
export const BlueReceiveButtonIcon = props => {
const { colors } = useTheme();
return (
{loc.receive.header}
);
};
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();
const { navigate } = useNavigation();
const { txMetadata, wallets, preferredFiatCurrency, language } = useContext(BlueStorageContext);
const containerStyle = useMemo(
() => ({
backgroundColor: 'transparent',
borderBottomColor: colors.lightBorder,
paddingTop: 16,
paddingBottom: 16,
paddingRight: 0,
}),
[colors.lightBorder],
);
const toolTip = useRef();
const copyToolTip = useRef();
const listItemRef = useRef();
const title = useMemo(() => {
if (item.confirmations === 0) {
return loc.transactions.pending;
} else {
return transactionTimeToReadable(item.received);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.confirmations, item.received, language]);
const txMemo = txMetadata[item.hash]?.memo ?? '';
const subtitle = useMemo(() => {
let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
if (sub !== '') sub += ' ';
sub += txMemo;
if (item.memo) sub += item.memo;
return sub || null;
}, [txMemo, item.confirmations, item.memo]);
const rowTitle = useMemo(() => {
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (isNaN(item.value)) {
item.value = '0';
}
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} else if (invoiceExpiration < now) {
if (item.ispaid) {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} else {
return loc.lnd.expired;
}
}
} else {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, itemPriceUnit, preferredFiatCurrency]);
const rowTitleStyle = useMemo(() => {
let color = colors.successColor;
if (item.type === 'user_invoice' || item.type === 'payment_request') {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
color = colors.successColor;
} else if (invoiceExpiration < now) {
if (item.ispaid) {
color = colors.successColor;
} else {
color = '#9AA0AA';
}
}
} else if (item.value / 100000000 < 0) {
color = colors.foregroundColor;
}
return {
color,
fontSize: 14,
fontWeight: '600',
textAlign: 'right',
width: 96,
};
}, [item, colors.foregroundColor, colors.successColor]);
const avatar = useMemo(() => {
// is it lightning refill tx?
if (item.category === 'receive' && item.confirmations < 3) {
return (
);
}
if (item.type && item.type === 'bitcoind_tx') {
return (
);
}
if (item.type === 'paid_invoice') {
// is it lightning offchain payment?
return (
);
}
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (!item.ispaid) {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration < now) {
return (
);
}
} else {
return (
);
}
}
if (!item.confirmations) {
return (
);
} else if (item.value < 0) {
return (
);
} else {
return (
);
}
}, [item]);
useEffect(() => {
setSubtitleNumberOfLines(1);
}, [subtitle]);
const onPress = useCallback(async () => {
if (item.hash) {
navigate('TransactionStatus', { hash: item.hash });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
if (lightningWallet.length === 1) {
try {
// is it a successful lnurl-pay?
const LN = new Lnurl(false, AsyncStorage);
let paymentHash = item.payment_hash;
if (typeof paymentHash === 'object') {
paymentHash = Buffer.from(paymentHash.data).toString('hex');
}
const loaded = await LN.loadSuccessfulPayment(paymentHash);
if (loaded) {
NavigationService.navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPaySuccess',
params: {
paymentHash,
justPaid: false,
fromWalletID: lightningWallet[0].getID(),
},
});
return;
}
} catch (e) {
console.log(e);
}
navigate('LNDViewInvoice', {
invoice: item,
walletID: lightningWallet[0].getID(),
isModal: false,
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, wallets]);
const onLongPress = useCallback(() => {
toolTip.current.showMenu();
}, []);
const handleOnExpandNote = useCallback(() => {
setSubtitleNumberOfLines(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [subtitle]);
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
const handleOnCopyTap = useCallback(() => {
toolTip.current.hideMenu();
setTimeout(copyToolTip.current.showMenu, 205);
}, []);
const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]);
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle), [subtitle]);
const handleOnViewOnBlockExplorer = useCallback(() => {
const url = `https://mempool.space/tx/${item.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}, [item.hash]);
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
Clipboard.setString(`https://mempool.space/tx/${item.hash}`);
}, [item.hash]);
const toolTipActions = useMemo(() => {
const actions = [
{
id: 'copy',
text: loc.transactions.details_copy,
onPress: handleOnCopyTap,
},
];
if (item.hash) {
actions.push({
id: 'open_in_blockExplorer',
text: loc.transactions.details_show_in_block_explorer,
onPress: handleOnViewOnBlockExplorer,
});
}
if (subtitle && subtitleNumberOfLines === 1) {
actions.push({
id: 'expandNote',
text: loc.transactions.expand_note,
onPress: handleOnExpandNote,
});
}
return actions;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
const copyToolTipActions = useMemo(() => {
const actions = [];
if (rowTitle !== loc.lnd.expired) {
actions.push({
id: 'copyAmount',
text: loc.send.create_amount,
onPress: handleOnCopyAmountTap,
});
}
if (item.hash) {
actions.push(
{
id: 'copyTX_ID',
text: loc.transactions.txid,
onPress: handleOnCopyTransactionID,
},
{
id: 'copy_blockExplorer',
text: loc.transactions.block_explorer_link,
onPress: handleCopyOpenInBlockExplorerPress,
},
);
}
if (subtitle) {
actions.push({
id: 'copyNote',
text: loc.transactions.note,
onPress: handleOnCopyNote,
});
}
return actions;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toolTipActions]);
return (
);
});
export class BlueReplaceFeeSuggestions extends Component {
static propTypes = {
onFeeSelected: PropTypes.func.isRequired,
transactionMinimum: PropTypes.number.isRequired,
};
static defaultProps = {
transactionMinimum: 1,
};
state = {
customFeeValue: '1',
};
async componentDidMount() {
try {
const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey));
if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) {
this.setState({ networkFees: cachedNetworkTransactionFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
}
} catch (_) {}
const networkFees = await NetworkTransactionFees.recommendedFees();
this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
}
onFeeSelected = selectedFeeType => {
if (selectedFeeType !== NetworkTransactionFeeType.CUSTOM) {
Keyboard.dismiss();
}
if (selectedFeeType === NetworkTransactionFeeType.FAST) {
this.props.onFeeSelected(this.state.networkFees.fastestFee);
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.fastestFee));
} else if (selectedFeeType === NetworkTransactionFeeType.MEDIUM) {
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.mediumFee));
} else if (selectedFeeType === NetworkTransactionFeeType.SLOW) {
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.slowFee));
} else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) {
this.props.onFeeSelected(Number(this.state.customFeeValue));
}
};
onCustomFeeTextChange = customFee => {
const customFeeValue = customFee.replace(/[^0-9]/g, '');
this.setState({ customFeeValue, selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => {
this.onFeeSelected(NetworkTransactionFeeType.CUSTOM);
});
};
render() {
const { networkFees, selectedFeeType } = this.state;
return (
{networkFees &&
[
{
label: loc.send.fee_fast,
time: loc.send.fee_10m,
type: NetworkTransactionFeeType.FAST,
rate: networkFees.fastestFee,
active: selectedFeeType === NetworkTransactionFeeType.FAST,
},
{
label: loc.send.fee_medium,
time: loc.send.fee_3h,
type: NetworkTransactionFeeType.MEDIUM,
rate: networkFees.mediumFee,
active: selectedFeeType === NetworkTransactionFeeType.MEDIUM,
},
{
label: loc.send.fee_slow,
time: loc.send.fee_1d,
type: NetworkTransactionFeeType.SLOW,
rate: networkFees.slowFee,
active: selectedFeeType === NetworkTransactionFeeType.SLOW,
},
].map(({ label, type, time, rate, active }, index) => (
this.onFeeSelected(type)}
style={[
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
active && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor },
]}
>
{label}
~{time}
{rate} sat/byte
))}
this.customTextInput.focus()}
style={[
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
selectedFeeType === NetworkTransactionFeeType.CUSTOM && {
borderRadius: 8,
backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor,
},
]}
>
{loc.send.fee_custom}
(this.customTextInput = ref)}
maxLength={9}
style={{
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderBottomWidth: 0.5,
borderColor: BlueCurrentTheme.colors.formBorder,
borderRadius: 4,
borderWidth: 1.0,
color: '#81868e',
flex: 1,
marginRight: 10,
minHeight: 33,
paddingRight: 5,
paddingLeft: 5,
}}
onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)}
defaultValue={`${this.props.transactionMinimum}`}
placeholder={loc.send.fee_satbyte}
placeholderTextColor="#81868e"
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
sat/byte
{loc.formatString(loc.send.fee_replace_min, { min: this.props.transactionMinimum })}
);
}
}
const styles = StyleSheet.create({
balanceBlur: {
height: 30,
width: 100,
marginRight: 16,
},
});
export function BlueBigCheckmark({ style }) {
const defaultStyles = {
backgroundColor: '#ccddf9',
width: 120,
height: 120,
borderRadius: 60,
alignSelf: 'center',
justifyContent: 'center',
marginTop: 0,
marginBottom: 0,
};
const mergedStyles = { ...defaultStyles, ...style };
return (
);
}
const tabsStyles = StyleSheet.create({
root: {
flexDirection: 'row',
height: 50,
borderColor: '#e3e3e3',
borderBottomWidth: 1,
},
tabRoot: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
borderColor: 'white',
borderBottomWidth: 2,
},
});
export const BlueTabs = ({ active, onSwitch, tabs }) => (
{tabs.map((Tab, i) => (
onSwitch(i)}
style={[
tabsStyles.tabRoot,
active === i && {
borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor,
borderBottomWidth: 2,
},
]}
>
))}
);
export class DynamicQRCode extends Component {
constructor() {
super();
const qrCodeHeight = height > width ? width - 40 : width / 3;
const qrCodeMaxHeight = 370;
this.state = {
index: 0,
total: 0,
qrCodeHeight: Math.min(qrCodeHeight, qrCodeMaxHeight),
intervalHandler: null,
};
}
fragments = [];
componentDidMount() {
const { value, capacity = 200 } = this.props;
this.fragments = encodeUR(value, capacity);
this.setState(
{
total: this.fragments.length,
},
() => {
this.startAutoMove();
},
);
}
moveToNextFragment = () => {
const { index, total } = this.state;
if (index === total - 1) {
this.setState({
index: 0,
});
} else {
this.setState(state => ({
index: state.index + 1,
}));
}
};
startAutoMove = () => {
if (!this.state.intervalHandler)
this.setState(() => ({
intervalHandler: setInterval(this.moveToNextFragment, 500),
}));
};
stopAutoMove = () => {
clearInterval(this.state.intervalHandler);
this.setState(() => ({
intervalHandler: null,
}));
};
moveToPreviousFragment = () => {
const { index, total } = this.state;
if (index > 0) {
this.setState(state => ({
index: state.index - 1,
}));
} else {
this.setState(state => ({
index: total - 1,
}));
}
};
render() {
const currentFragment = this.fragments[this.state.index];
return currentFragment ? (
{loc.formatString(loc._.of, { number: this.state.index + 1, total: this.state.total })}
{loc.send.dynamic_prev}
{this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start}
{loc.send.dynamic_next}
) : (
{loc.send.dynamic_init}
);
}
}
const animatedQRCodeStyle = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
},
qrcodeContainer: {
alignItems: 'center',
justifyContent: 'center',
borderWidth: 6,
borderRadius: 8,
borderColor: '#FFFFFF',
margin: 6,
},
controller: {
width: '90%',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderRadius: 25,
height: 45,
paddingHorizontal: 18,
},
button: {
alignItems: 'center',
height: 45,
justifyContent: 'center',
},
text: {
fontSize: 14,
color: BlueCurrentTheme.colors.foregroundColor,
fontWeight: 'bold',
},
});