BlueWallet/BlueComponents.js

2647 lines
82 KiB
JavaScript
Raw Normal View History

/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
/* global alert */
import React, { Component, useState, useMemo, useCallback, useContext } from 'react';
import PropTypes from 'prop-types';
2020-09-22 03:53:28 +02:00
import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements';
2018-12-12 04:33:28 +01:00
import {
ActivityIndicator,
2020-09-09 19:55:13 +02:00
Alert,
Animated,
2018-12-12 04:33:28 +01:00
Dimensions,
2020-09-09 19:55:13 +02:00
FlatList,
2018-12-12 04:33:28 +01:00
Image,
2019-01-31 07:52:21 +01:00
InputAccessoryView,
2020-09-09 19:55:13 +02:00
Keyboard,
KeyboardAvoidingView,
PixelRatio,
2018-12-12 04:33:28 +01:00
Platform,
2020-10-08 09:55:17 +02:00
PlatformColor,
2020-09-09 19:55:13 +02:00
SafeAreaView,
StyleSheet,
2020-09-22 03:53:28 +02:00
Switch,
TextInput,
2020-09-09 19:55:13 +02:00
TouchableOpacity,
TouchableWithoutFeedback,
UIManager,
2020-10-06 19:54:35 +02:00
useWindowDimensions,
2020-09-09 19:55:13 +02:00
View,
2018-12-12 04:33:28 +01:00
} from 'react-native';
2020-06-09 23:02:25 +02:00
import Clipboard from '@react-native-community/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import ActionSheet from './screen/ActionSheet';
2020-10-15 18:25:24 +02:00
import { LightningCustodianWallet, MultisigHDWallet } from './class';
import { BitcoinUnit } from './models/bitcoinUnits';
import * as NavigationService from './NavigationService';
import WalletGradient from './class/wallet-gradient';
import ToolTip from 'react-native-tooltip';
2019-08-03 23:29:15 +02:00
import { BlurView } from '@react-native-community/blur';
import ImagePicker from 'react-native-image-picker';
import showPopupMenu from 'react-native-popup-menu-android';
2020-09-14 12:49:08 +02:00
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
import Biometric from './class/biometrics';
import { getSystemName } from 'react-native-device-info';
import { encodeUR } from 'bc-ur/dist';
import QRCode from 'react-native-qrcode-svg';
2020-09-14 12:49:08 +02:00
import AsyncStorage from '@react-native-community/async-storage';
2020-07-15 19:32:59 +02:00
import { useTheme } from '@react-navigation/native';
import { BlueCurrentTheme } from './components/themes';
2020-07-20 15:38:46 +02:00
import loc, { formatBalance, formatBalanceWithoutSuffix, formatBalancePlain, removeTrailingZeros, transactionTimeToReadable } from './loc';
2020-07-23 20:06:13 +02:00
import Lnurl from './class/lnurl';
import { BlueStorageContext } from './blue_modules/storage-context';
2020-10-13 07:49:30 +02:00
import { presentCameraNotAuthorizedAlert } from './class/camera';
2018-05-22 19:10:53 +02:00
/** @type {AppStorage} */
2018-05-12 22:27:34 +02:00
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
2019-05-02 22:33:03 +02:00
const BigNumber = require('bignumber.js');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
2020-06-09 16:08:18 +02:00
const currency = require('./blue_modules/currency');
2018-05-12 22:27:34 +02:00
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
} else {
isIpad = true;
}
2020-10-07 14:24:36 +02:00
// eslint-disable-next-line no-unused-expressions
Platform.OS === 'android' ? (ActivityIndicator.defaultProps.color = PlatformColor('?attr/colorControlActivated')) : null;
2018-01-30 23:42:38 +01:00
2020-10-06 19:54:35 +02:00
export const BlueButton = props => {
const { colors } = useTheme();
const { width } = useWindowDimensions();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor || BlueCurrentTheme.colors.mainColor;
let fontColor = props.buttonTextColor || colors.buttonTextColor;
2020-10-06 19:54:35 +02:00
if (props.disabled === true) {
backgroundColor = colors.buttonDisabledBackgroundColor;
fontColor = colors.buttonDisabledTextColor;
}
2020-10-06 19:54:35 +02:00
let buttonWidth = props.width ? props.width : width / 1.5;
if ('noMinWidth' in props) {
buttonWidth = 0;
2020-07-15 19:32:59 +02:00
}
2020-10-06 19:54:35 +02:00
return (
<TouchableOpacity
style={{
flex: 1,
borderWidth: 0.7,
borderColor: 'transparent',
backgroundColor: backgroundColor,
minHeight: 45,
height: 45,
maxHeight: 45,
borderRadius: 25,
minWidth: buttonWidth,
justifyContent: 'center',
alignItems: 'center',
}}
{...props}
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
{props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor, fontWeight: '500' }}>{props.title}</Text>}
2020-10-06 19:54:35 +02:00
</View>
</TouchableOpacity>
);
};
2020-07-15 19:32:59 +02:00
export const BlueButtonHook = props => {
const { width } = useWindowDimensions();
const { colors } = useTheme();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor;
let fontColor = colors.buttonTextColor;
if (props.disabled === true) {
backgroundColor = colors.buttonDisabledBackgroundColor;
fontColor = colors.buttonDisabledTextColor;
}
let buttonWidth = props.width ? props.width : width / 1.5;
if ('noMinWidth' in props) {
buttonWidth = 0;
}
return (
<TouchableOpacity
style={{
flex: 1,
borderWidth: 0.7,
borderColor: 'transparent',
backgroundColor: backgroundColor,
minHeight: 45,
height: 45,
maxHeight: 45,
borderRadius: 25,
minWidth: buttonWidth,
justifyContent: 'center',
alignItems: 'center',
}}
{...props}
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
{props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{props.title}</Text>}
</View>
</TouchableOpacity>
);
};
2020-07-15 19:32:59 +02:00
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;
// }
2018-01-30 23:42:38 +01:00
return (
2019-01-24 20:31:30 +01:00
<TouchableOpacity
2018-03-17 21:39:21 +01:00
style={{
2019-01-24 20:31:30 +01:00
flex: 1,
2018-03-17 21:39:21 +01:00
borderWidth: 0.7,
2018-06-30 15:36:13 +02:00
borderColor: 'transparent',
backgroundColor: backgroundColor,
2019-01-24 20:31:30 +01:00
minHeight: 45,
height: 45,
2019-01-25 19:56:43 +01:00
maxHeight: 45,
2019-01-24 20:31:30 +01:00
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
2018-03-17 21:39:21 +01:00
}}
2019-01-24 19:44:33 +01:00
{...this.props}
2019-01-24 20:31:30 +01:00
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
{this.props.icon && <Icon name={this.props.icon.name} type={this.props.icon.type} color={this.props.icon.color} />}
{this.props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{this.props.title}</Text>}
2019-01-24 20:31:30 +01:00
</View>
</TouchableOpacity>
2018-07-14 20:54:27 +02:00
);
}
}
export const BitcoinButton = props => {
const { colors } = useTheme();
return (
<TouchableOpacity testID={props.testID} onPress={props.onPress}>
<View
style={{
borderColor: (props.active && colors.newBlue) || colors.buttonDisabledBackgroundColor,
borderWidth: 1.5,
borderRadius: 8,
backgroundColor: colors.buttonDisabledBackgroundColor,
minWidth: props.style.width,
minHeight: props.style.height,
height: props.style.height,
flex: 1,
marginBottom: 8,
2018-07-14 20:54:27 +02:00
}}
>
<View style={{ marginHorizontal: 16, marginVertical: 10, flexDirection: 'row', alignItems: 'center' }}>
<View>
<Image style={{ width: 34, height: 34, marginRight: 8 }} source={require('./img/addWallet/bitcoin.png')} />
</View>
<View>
<Text style={{ color: colors.newBlue, fontWeight: 'bold', fontSize: 18 }}>{loc.wallets.add_bitcoin}</Text>
<Text style={{ color: colors.alternativeTextColor, fontSize: 13, fontWeight: '500' }}>{loc.wallets.add_bitcoin_explain}</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
};
export const VaultButton = props => {
const { colors } = useTheme();
return (
<TouchableOpacity testID={props.testID} onPress={props.onPress}>
<View
style={{
borderColor: (props.active && colors.foregroundColor) || colors.buttonDisabledBackgroundColor,
borderWidth: 1.5,
borderRadius: 8,
backgroundColor: colors.buttonDisabledBackgroundColor,
minWidth: props.style.width,
minHeight: props.style.height,
height: props.style.height,
flex: 1,
}}
>
<View style={{ marginHorizontal: 16, marginVertical: 10, flexDirection: 'row', alignItems: 'center' }}>
<View>
<Image style={{ width: 34, height: 34, marginRight: 8 }} source={require('./img/addWallet/vault.png')} />
</View>
<View>
<Text style={{ color: colors.foregroundColor, fontWeight: 'bold', fontSize: 18 }}>{loc.multisig.multisig_vault}</Text>
<Text style={{ color: colors.alternativeTextColor, fontSize: 13, fontWeight: '500' }}>
{loc.multisig.multisig_vault_explain}
</Text>
</View>
2018-07-14 20:54:27 +02:00
</View>
</View>
</TouchableOpacity>
);
};
2018-07-14 20:54:27 +02:00
export const LightningButton = props => {
const { colors } = useTheme();
return (
<TouchableOpacity onPress={props.onPress}>
<View
style={{
borderColor: (props.active && colors.lnborderColor) || colors.buttonDisabledBackgroundColor,
borderWidth: 1.5,
borderRadius: 8,
backgroundColor: colors.buttonDisabledBackgroundColor,
minWidth: props.style.width,
minHeight: props.style.height,
height: props.style.height,
flex: 1,
marginBottom: 8,
2018-07-14 20:54:27 +02:00
}}
>
<View style={{ marginHorizontal: 16, marginVertical: 10, flexDirection: 'row', alignItems: 'center' }}>
<View>
<Image style={{ width: 34, height: 34, marginRight: 8 }} source={require('./img/addWallet/lightning.png')} />
</View>
<View>
<Text style={{ color: colors.lnborderColor, fontWeight: 'bold', fontSize: 18 }}>{loc.wallets.add_lightning}</Text>
<Text style={{ color: colors.alternativeTextColor, fontSize: 13, fontWeight: '500' }}>{loc.wallets.add_lightning_explain}</Text>
</View>
2018-07-14 20:54:27 +02:00
</View>
</View>
</TouchableOpacity>
);
};
2018-07-14 20:54:27 +02:00
export class BlueWalletNavigationHeader extends Component {
static propTypes = {
wallet: PropTypes.shape().isRequired,
2019-08-04 08:42:05 +02:00
onWalletUnitChange: PropTypes.func,
};
static getDerivedStateFromProps(props, state) {
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange, allowOnchainAddress: state.allowOnchainAddress };
}
static contextType = BlueStorageContext;
constructor(props) {
super(props);
this.state = {
wallet: props.wallet,
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
showManageFundsButton: false,
};
}
handleCopyPress = _item => {
2020-07-20 15:38:46 +02:00
Clipboard.setString(formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
};
componentDidMount() {
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.'));
}
}
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();
};
showAndroidTooltip = () => {
showPopupMenu(this.toolTipMenuOptions(), this.handleToolTipSelection, this.walletBalanceText);
};
handleToolTipSelection = item => {
if (item === 'balanceCopy' || item.id === 'balanceCopy') {
this.handleCopyPress();
} else if (item === 'balancePrivacy' || item.id === 'balancePrivacy') {
this.handleBalanceVisibility();
}
};
toolTipMenuOptions() {
return Platform.select({
// NOT WORKING ATM.
// ios: [
// { text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide, onPress: this.handleBalanceVisibility },
2020-07-20 15:38:46 +02:00
// { text: loc.transactions.details_copy, onPress: this.handleCopyPress },
// ],
android: this.state.wallet.hideBalance
? [
{
id: 'balancePrivacy',
label: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
},
]
: [
{
id: 'balancePrivacy',
label: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
},
{
id: 'balanceCopy',
label: loc.transactions.details_copy,
},
],
});
}
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);
2019-08-04 08:42:05 +02:00
});
}
2019-09-29 22:01:27 +02:00
manageFundsPressed = () => {
this.props.onManageFundsPressed();
};
render() {
return (
<LinearGradient
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
>
<Image
2020-10-05 23:25:14 +02:00
source={(() => {
switch (this.state.wallet.type) {
case LightningCustodianWallet.type:
return require('./img/lnd-shape.png');
case MultisigHDWallet.type:
return require('./img/vault-shape.png');
default:
return require('./img/btc-shape.png');
}
})()}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: '#fff',
}}
>
{this.state.wallet.getLabel()}
</Text>
{Platform.OS === 'ios' && (
<ToolTip
ref={tooltip => (this.tooltip = tooltip)}
actions={
this.state.wallet.hideBalance
? [
{
text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
onPress: this.handleBalanceVisibility,
},
]
: [
{
text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
onPress: this.handleBalanceVisibility,
},
{
text: loc.transactions.details_copy,
onPress: this.handleCopyPress,
},
]
}
/>
)}
<TouchableOpacity
style={styles.balance}
onPress={() => this.changeWalletBalanceUnit()}
ref={ref => (this.walletBalanceText = ref)}
onLongPress={() => (Platform.OS === 'ios' ? this.tooltip.showMenu() : this.showAndroidTooltip())}
>
{this.state.wallet.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
testID="WalletBalance"
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
}}
>
2020-07-20 15:38:46 +02:00
{formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString()}
</Text>
)}
</TouchableOpacity>
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
2019-09-29 22:01:27 +02:00
<TouchableOpacity onPress={this.manageFundsPressed}>
<View
style={{
marginTop: 14,
marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9,
minHeight: 39,
alignSelf: 'flex-start',
paddingHorizontal: 12,
height: 39,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text
style={{
fontWeight: '500',
fontSize: 14,
color: '#FFFFFF',
}}
>
{loc.lnd.title}
</Text>
</View>
</TouchableOpacity>
)}
</LinearGradient>
);
}
}
export const BlueButtonLinkHook = props => {
2020-07-15 19:32:59 +02:00
const { colors } = useTheme();
return (
<TouchableOpacity
style={{
minHeight: 60,
minWidth: 100,
height: 60,
justifyContent: 'center',
}}
onPress={props.onPress}
{...props}
2020-07-15 19:32:59 +02:00
>
<Text style={{ color: colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{props.title}</Text>
2020-07-15 19:32:59 +02:00
</TouchableOpacity>
);
};
2018-07-14 20:54:27 +02:00
export class BlueButtonLink extends Component {
render() {
return (
2019-01-31 01:56:34 +01:00
<TouchableOpacity
2018-07-14 20:54:27 +02:00
style={{
2019-01-31 01:56:34 +01:00
minHeight: 60,
minWidth: 100,
justifyContent: 'center',
2018-07-14 20:54:27 +02:00
}}
2019-01-31 01:56:34 +01:00
{...this.props}
>
2020-07-15 19:32:59 +02:00
<Text style={{ color: BlueCurrentTheme.colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{this.props.title}</Text>
2019-01-31 01:56:34 +01:00
</TouchableOpacity>
2018-03-17 21:39:21 +01:00
);
2018-01-30 23:42:38 +01:00
}
}
export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => {
Alert.alert(
2020-07-23 15:09:48 +02:00
loc.wallets.details_title,
loc.pleasebackup.ask,
[
2020-07-23 15:09:48 +02:00
{ text: loc.pleasebackup.ask_yes, onPress: onSuccess, style: 'cancel' },
{ text: loc.pleasebackup.ask_no, onPress: onFailure },
],
{ cancelable: false },
);
};
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => {
let headerRight;
2020-07-15 19:32:59 +02:00
const { colors, closeImage } = useTheme();
if (withNavigationCloseButton) {
headerRight = () => (
2020-05-27 13:12:17 +02:00
<TouchableOpacity
style={{ width: 40, height: 40, padding: 14 }}
onPress={
customCloseButtonFunction === undefined
? () => {
Keyboard.dismiss();
navigation.goBack(null);
}
: customCloseButtonFunction
}
>
2020-07-15 19:32:59 +02:00
<Image style={{ alignSelf: 'center' }} source={closeImage} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
2020-06-24 05:17:27 +02:00
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
2020-07-15 19:32:59 +02:00
color: colors.foregroundColor,
},
headerRight,
headerBackTitleVisible: false,
2020-07-15 19:32:59 +02:00
headerTintColor: colors.foregroundColor,
};
};
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => {
let headerRight;
if (withAdvancedOptionsMenuButton) {
headerRight = () => (
<TouchableOpacity
style={{ minWidth: 40, height: 40, justifyContent: 'center' }}
onPress={advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
2020-07-15 19:32:59 +02:00
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueCurrentTheme.colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
2020-05-28 15:57:01 +02:00
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.foregroundColor,
},
2020-07-15 19:32:59 +02:00
headerTintColor: BlueCurrentTheme.colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
2020-07-15 19:32:59 +02:00
style={{ minWwidth: 40, height: 40, justifyContent: 'center', paddingHorizontal: 14 }}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
2020-07-15 19:32:59 +02:00
<Image style={{}} source={BlueCurrentTheme.closeImage} />
</TouchableOpacity>
),
headerRight,
headerBackTitle: null,
};
};
2019-08-03 23:29:15 +02:00
export const BluePrivateBalance = () => {
return Platform.select({
ios: (
<View style={{ flexDirection: 'row' }}>
<BlurView style={styles.balanceBlur} blurType="light" blurAmount={25} />
<Icon name="eye-slash" type="font-awesome" color="#FFFFFF" />
</View>
),
android: (
<View style={{ flexDirection: 'row' }}>
<View style={{ backgroundColor: '#FFFFFF', opacity: 0.5, height: 30, width: 100, marginRight: 8 }} />
<Icon name="eye-slash" type="font-awesome" color="#FFFFFF" />
</View>
),
});
};
2019-09-27 16:49:56 +02:00
export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => {
2018-09-29 06:58:09 +02:00
return (
<TouchableOpacity onPress={() => Clipboard.setString(stringToCopy)}>
2020-07-20 15:38:46 +02:00
<Text style={{ fontSize: 13, fontWeight: '400', color: '#68bbe1' }}>{displayText || loc.transactions.details_copy}</Text>
2018-09-29 06:58:09 +02:00
</TouchableOpacity>
);
};
2019-01-21 14:55:39 +01:00
export class BlueCopyTextToClipboard extends Component {
static propTypes = {
text: PropTypes.string,
};
static defaultProps = {
text: '',
};
2019-02-02 04:45:18 +01:00
constructor(props) {
super(props);
2019-01-31 07:52:21 +01:00
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}
2019-02-02 04:45:18 +01:00
this.state = { hasTappedText: false, address: props.text };
}
2019-05-02 22:33:03 +02:00
static getDerivedStateFromProps(props, state) {
if (state.hasTappedText) {
return { hasTappedText: state.hasTappedText, address: state.address };
} else {
return { hasTappedText: state.hasTappedText, address: props.text };
}
}
2019-01-21 14:55:39 +01:00
copyToClipboard = () => {
2019-02-02 04:45:18 +01:00
this.setState({ hasTappedText: true }, () => {
2019-01-21 14:55:39 +01:00
Clipboard.setString(this.props.text);
2020-07-20 15:38:46 +02:00
this.setState({ address: loc.wallets.xpub_copiedToClipboard }, () => {
2019-02-02 04:45:18 +01:00
setTimeout(() => {
this.setState({ hasTappedText: false, address: this.props.text });
}, 1000);
});
2019-01-21 14:55:39 +01:00
});
};
render() {
return (
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText}>
2019-02-02 04:45:18 +01:00
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
{this.state.address}
</Animated.Text>
2019-01-21 14:55:39 +01:00
</TouchableOpacity>
</View>
);
}
}
const styleCopyTextToClipboard = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
2019-01-21 14:55:39 +01:00
textAlign: 'center',
},
});
2018-01-30 23:42:38 +01:00
export class SafeBlueArea extends Component {
2018-03-17 21:39:21 +01:00
render() {
2018-01-30 23:42:38 +01:00
return (
<SafeAreaView
2018-03-17 21:39:21 +01:00
forceInset={{ horizontal: 'always' }}
2020-07-15 19:32:59 +02:00
style={{ flex: 1, backgroundColor: BlueCurrentTheme.colors.background }}
{...this.props}
2018-01-30 23:42:38 +01:00
/>
2018-03-17 21:39:21 +01:00
);
2018-01-30 23:42:38 +01:00
}
}
export class BlueCard extends Component {
2018-03-17 21:39:21 +01:00
render() {
return <View {...this.props} style={{ padding: 20 }} />;
2018-01-30 23:42:38 +01:00
}
}
export class BlueText extends Component {
2018-03-17 21:39:21 +01:00
render() {
return (
<Text
2019-01-24 20:48:40 +01:00
style={{
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.foregroundColor,
2019-01-24 20:48:40 +01:00
...this.props.style,
}}
{...this.props}
/>
);
2018-01-30 23:42:38 +01:00
}
}
2020-07-15 19:32:59 +02:00
export const BlueTextHooks = props => {
const { colors } = useTheme();
return (
<Text
style={{
color: colors.foregroundColor,
...props.style,
}}
{...props}
/>
);
};
2018-07-14 20:54:27 +02:00
export class BlueTextCentered extends Component {
render() {
2020-07-15 19:32:59 +02:00
return <Text {...this.props} style={{ color: BlueCurrentTheme.colors.foregroundColor, textAlign: 'center' }} />;
2018-07-14 20:54:27 +02:00
}
}
2018-01-30 23:42:38 +01:00
export const BlueTextCenteredHooks = props => {
const { colors } = useTheme();
return <Text {...props} style={{ color: colors.foregroundColor, textAlign: 'center' }} />;
};
2020-09-22 03:53:28 +02:00
export const BlueListItem = React.memo(props => {
2020-07-15 19:32:59 +02:00
const { colors } = useTheme();
return (
<ListItem
2020-09-22 03:53:28 +02:00
containerStyle={props.containerStyle ?? { backgroundColor: 'transparent' }}
Component={props.Component ?? TouchableOpacity}
bottomDivider={props.bottomDivider !== undefined ? props.bottomDivider : true}
topDivider={props.topDivider !== undefined ? props.topDivider : false}
2020-09-22 03:53:28 +02:00
testID={props.testID}
onPress={props.onPress}
2020-11-10 01:51:27 +01:00
disabled={props.disabled}
2020-09-22 03:53:28 +02:00
>
{props.leftAvatar && <Avatar>{props.leftAvatar}</Avatar>}
{props.leftIcon && <Avatar icon={props.leftIcon} />}
<ListItem.Content>
<ListItem.Title
style={{
color: props.disabled ? colors.buttonDisabledTextColor : colors.foregroundColor,
fontSize: 16,
fontWeight: '500',
}}
numberOfLines={0}
>
{props.title}
</ListItem.Title>
{props.subtitle && (
<ListItem.Subtitle
numberOfLines={1}
style={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
>
{props.subtitle}
</ListItem.Subtitle>
)}
</ListItem.Content>
<ListItem.Content right>
{props.rightTitle && (
<ListItem.Title style={props.rightTitleStyle} numberOfLines={0} right>
{props.rightTitle}
</ListItem.Title>
)}
</ListItem.Content>
{props.isLoading ? (
<ActivityIndicator />
) : (
<>
{props.chevron && <ListItem.Chevron />}
{props.rightIcon && <Avatar icon={props.rightIcon} />}
{props.switch && <Switch {...props.switch} />}
{props.checkmark && <ListItem.CheckBox iconType="octaicon" checkedColor="#0070FF" checkedIcon="check" checked />}
</>
)}
2020-09-22 03:53:28 +02:00
</ListItem>
2020-07-15 19:32:59 +02:00
);
2020-09-22 03:53:28 +02:00
});
2020-07-15 19:32:59 +02:00
export const BlueFormLabel = props => {
const { colors } = useTheme();
2020-09-20 20:49:27 +02:00
return <Text {...props} style={{ color: colors.foregroundColor, fontWeight: '400', marginHorizontal: 20 }} />;
};
2018-01-30 23:42:38 +01:00
export class BlueFormInput extends Component {
2018-03-17 21:39:21 +01:00
render() {
2018-05-06 19:14:22 +02:00
return (
2020-05-03 20:17:49 +02:00
<Input
2018-05-06 19:14:22 +02:00
{...this.props}
2020-07-15 19:32:59 +02:00
inputStyle={{ color: BlueCurrentTheme.colors.foregroundColor, maxWidth: width - 105 }}
containerStyle={{
marginTop: 5,
2020-07-15 19:32:59 +02:00
borderColor: BlueCurrentTheme.colors.inputBorderColor,
borderBottomColor: BlueCurrentTheme.colors.inputBorderColor,
borderWidth: 0.5,
borderBottomWidth: 0.5,
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
}}
/>
);
}
}
export class BlueFormMultiInput extends Component {
constructor(props) {
super(props);
this.state = {
selection: { start: 0, end: 0 },
};
}
render() {
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
style={{
2020-07-15 19:32:59 +02:00
paddingHorizontal: 8,
paddingVertical: 16,
2020-04-27 09:11:33 +02:00
flex: 1,
2018-07-14 20:54:27 +02:00
marginTop: 5,
marginHorizontal: 20,
2020-07-15 19:32:59 +02:00
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1,
2018-05-22 19:12:16 +02:00
borderBottomWidth: 0.5,
2020-07-15 19:32:59 +02:00
borderRadius: 4,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
color: BlueCurrentTheme.colors.foregroundColor,
2020-08-16 14:41:56 +02:00
textAlignVertical: 'top',
2018-05-22 19:12:16 +02:00
}}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...this.props}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
2018-05-06 19:14:22 +02:00
/>
);
2018-05-06 19:12:14 +02:00
}
}
2018-01-30 23:42:38 +01:00
export class BlueHeader extends Component {
2018-03-17 21:39:21 +01:00
render() {
2018-01-30 23:42:38 +01:00
return (
<Header
{...this.props}
2018-06-25 00:22:46 +02:00
backgroundColor="transparent"
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
2018-06-28 03:43:28 +02:00
/>
);
}
}
export class BlueHeaderDefaultSub extends Component {
render() {
return (
2020-07-15 19:32:59 +02:00
<SafeAreaView style={{ backgroundColor: BlueCurrentTheme.colors.brandingColor }}>
<Header
2020-07-15 19:32:59 +02:00
backgroundColor={BlueCurrentTheme.colors.background}
2020-05-03 20:17:49 +02:00
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
leftComponent={
<Text
2018-10-04 03:49:29 +02:00
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
2019-11-10 14:09:30 +01:00
fontSize: 30,
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.foregroundColor,
}}
>
{this.props.leftText}
</Text>
}
rightComponent={
<TouchableOpacity
onPress={() => {
if (this.props.onClose) this.props.onClose();
}}
>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ballTransparrent}>
<Image source={require('./img/close.png')} />
</View>
2018-06-28 03:43:28 +02:00
</View>
</TouchableOpacity>
}
{...this.props}
/>
</SafeAreaView>
2018-06-28 03:43:28 +02:00
);
}
}
2020-07-15 19:32:59 +02:00
export const BlueHeaderDefaultSubHooks = props => {
const { colors } = useTheme();
return (
<SafeAreaView>
<Header
backgroundColor={colors.background}
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
leftComponent={
<Text
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
fontSize: 30,
color: colors.foregroundColor,
}}
>
{props.leftText}
</Text>
}
{...props}
/>
</SafeAreaView>
);
};
export const BlueHeaderDefaultMain = props => {
const { colors } = useTheme();
2020-10-07 22:53:50 +02:00
const { isDrawerList } = props;
return (
<Header
leftComponent={{
text: props.leftText,
style: {
fontWeight: 'bold',
fontSize: 34,
color: colors.foregroundColor,
paddingHorizontal: 4,
},
}}
placement="left"
containerStyle={{
2020-10-07 22:53:50 +02:00
borderTopColor: isDrawerList ? colors.elevated : colors.background,
borderBottomColor: isDrawerList ? colors.elevated : colors.background,
maxHeight: 44,
height: 44,
paddingTop: 0,
marginBottom: 8,
}}
bottomDivider={false}
topDivider={false}
2020-10-07 22:53:50 +02:00
backgroundColor={isDrawerList ? colors.elevated : colors.background}
rightComponent={<BluePlusIcon onPress={props.onNewWalletPress} Component={TouchableOpacity} />}
/>
);
};
2018-01-30 23:42:38 +01:00
export class BlueSpacing extends Component {
2018-03-17 21:39:21 +01:00
render() {
2020-07-15 19:32:59 +02:00
return <View {...this.props} style={{ height: 60 }} />;
2018-01-30 23:42:38 +01:00
}
}
2018-06-29 00:17:14 +02:00
2018-05-12 22:27:34 +02:00
export class BlueSpacing40 extends Component {
render() {
2020-07-15 19:32:59 +02:00
return <View {...this.props} style={{ height: 50 }} />;
2018-05-12 22:27:34 +02:00
}
}
2018-01-30 23:42:38 +01:00
2018-06-29 00:17:14 +02:00
export class BlueSpacingVariable extends Component {
render() {
if (isIpad) {
return <BlueSpacing40 {...this.props} />;
} else {
return <BlueSpacing {...this.props} />;
}
}
}
export class is {
static ipad() {
return isIpad;
}
}
2018-01-30 23:42:38 +01:00
export class BlueSpacing20 extends Component {
2018-06-25 00:22:46 +02:00
render() {
return <View {...this.props} style={{ height: 20, opacity: 0 }} />;
}
}
2019-05-22 14:08:31 +02:00
export class BlueSpacing10 extends Component {
render() {
return <View {...this.props} style={{ height: 10, opacity: 0 }} />;
}
}
2018-06-25 00:22:46 +02:00
export class BlueList extends Component {
2018-03-17 21:39:21 +01:00
render() {
2020-05-03 20:17:49 +02:00
return <FlatList {...this.props} />;
2018-01-30 23:42:38 +01:00
}
}
2019-01-31 07:52:21 +01:00
export class BlueUseAllFundsButton extends Component {
static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
static propTypes = {
wallet: PropTypes.shape().isRequired,
onUseAllPressed: PropTypes.func.isRequired,
};
2020-06-09 16:08:18 +02:00
static defaultProps = {
unit: BitcoinUnit.BTC,
};
2019-01-31 07:52:21 +01:00
render() {
2019-08-06 00:25:36 +02:00
const inputView = (
<View
style={{
flex: 1,
flexDirection: 'row',
maxHeight: 44,
justifyContent: 'space-between',
alignItems: 'center',
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
2019-08-06 00:25:36 +02:00
}}
>
<View style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-start' }}>
2019-08-07 03:42:41 +02:00
<Text
style={{
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.alternativeTextColor,
2019-08-07 03:42:41 +02:00
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
2020-07-23 15:09:48 +02:00
{loc.send.input_total}
2019-01-31 07:52:21 +01:00
</Text>
2019-08-07 16:29:28 +02:00
{this.props.wallet.allowSendMax() && this.props.wallet.getBalance() > 0 ? (
2019-08-07 03:42:41 +02:00
<BlueButtonLink
onPress={this.props.onUseAllPressed}
style={{ marginLeft: 8, paddingRight: 0, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
2020-07-20 15:38:46 +02:00
title={`${formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} ${BitcoinUnit.BTC}`}
2019-08-07 03:42:41 +02:00
/>
) : (
<Text
style={{
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
2020-07-20 15:38:46 +02:00
{formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} {BitcoinUnit.BTC}
</Text>
2019-08-06 00:25:36 +02:00
)}
2019-01-31 07:52:21 +01:00
</View>
2019-08-06 00:25:36 +02:00
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'flex-end' }}>
2019-08-07 03:42:41 +02:00
<BlueButtonLink
style={{ paddingRight: 8, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
2020-07-23 15:09:48 +02:00
title={loc.send.input_done}
onPress={() => Keyboard.dismiss()}
2019-08-07 03:42:41 +02:00
/>
2019-08-06 00:25:36 +02:00
</View>
</View>
2019-01-31 07:52:21 +01:00
);
2019-08-06 00:25:36 +02:00
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView style={{ height: 44 }}>{inputView}</KeyboardAvoidingView>;
}
2019-01-31 07:52:21 +01:00
}
}
export class BlueDismissKeyboardInputAccessory extends Component {
static InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory';
render() {
return Platform.OS !== 'ios' ? null : (
<InputAccessoryView nativeID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}>
<View
style={{
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
height: 44,
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
2020-07-23 15:09:48 +02:00
<BlueButtonLink title={loc.send.input_done} onPress={() => Keyboard.dismiss()} />
</View>
</InputAccessoryView>
);
}
}
2019-08-29 06:18:32 +02:00
export class BlueDoneAndDismissKeyboardInputAccessory extends Component {
static InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory';
onPasteTapped = async () => {
const clipboard = await Clipboard.getString();
this.props.onPasteTapped(clipboard);
};
render() {
const inputView = (
<View
style={{
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
2019-08-29 06:18:32 +02:00
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
2020-04-27 09:11:33 +02:00
maxHeight: 44,
2019-08-29 06:18:32 +02:00
}}
>
2020-07-23 15:09:48 +02:00
<BlueButtonLink title={loc.send.input_clear} onPress={this.props.onClearTapped} />
<BlueButtonLink title={loc.send.input_paste} onPress={this.onPasteTapped} />
<BlueButtonLink title={loc.send.input_done} onPress={() => Keyboard.dismiss()} />
2019-08-29 06:18:32 +02:00
</View>
);
if (Platform.OS === 'ios') {
2019-08-30 02:42:49 +02:00
return <InputAccessoryView nativeID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
2019-08-29 06:18:32 +02:00
} else {
2020-04-27 09:11:33 +02:00
return <KeyboardAvoidingView>{inputView}</KeyboardAvoidingView>;
2019-08-29 06:18:32 +02:00
}
}
}
2018-06-25 00:22:46 +02:00
export class BlueLoading extends Component {
2018-03-17 21:39:21 +01:00
render() {
2018-06-25 00:22:46 +02:00
return (
2020-07-16 02:18:35 +02:00
<View style={{ flex: 1, paddingTop: 200 }} {...this.props}>
2020-07-15 19:32:59 +02:00
<ActivityIndicator />
</View>
2018-06-25 00:22:46 +02:00
);
2018-01-30 23:42:38 +01:00
}
}
2020-07-15 19:32:59 +02:00
export const BlueLoadingHook = () => {
return (
2020-07-16 02:18:35 +02:00
<View style={{ flex: 1, paddingTop: 200 }}>
2020-07-15 19:32:59 +02:00
<ActivityIndicator />
</View>
);
};
2018-06-25 00:22:46 +02:00
const stylesBlueIcon = StyleSheet.create({
container: {
flex: 1,
},
box1: {
position: 'relative',
top: 15,
},
2018-06-28 03:43:28 +02:00
box: {
alignSelf: 'flex-end',
paddingHorizontal: 14,
paddingTop: 8,
2018-06-28 03:43:28 +02:00
},
2019-01-31 07:52:21 +01:00
boxIncoming: {
2018-06-25 00:22:46 +02:00
position: 'relative',
},
ball: {
width: 30,
height: 30,
borderRadius: 15,
},
2019-01-31 07:52:21 +01:00
ballIncoming: {
2018-06-25 00:22:46 +02:00
width: 30,
height: 30,
borderRadius: 15,
2018-12-31 23:29:36 +01:00
transform: [{ rotate: '-45deg' }],
2020-05-03 20:17:49 +02:00
justifyContent: 'center',
2018-12-31 23:29:36 +01:00
},
2019-01-31 07:52:21 +01:00
ballIncomingWithoutRotate: {
2018-12-31 23:29:36 +01:00
width: 30,
height: 30,
borderRadius: 15,
2018-06-25 00:22:46 +02:00
},
ballReceive: {
width: 30,
height: 30,
borderBottomLeftRadius: 15,
transform: [{ rotate: '-45deg' }],
},
ballOutgoing: {
width: 30,
height: 30,
borderRadius: 15,
2018-12-31 23:29:36 +01:00
transform: [{ rotate: '225deg' }],
2020-05-03 20:17:49 +02:00
justifyContent: 'center',
2018-06-25 00:22:46 +02:00
},
2018-12-28 22:43:38 +01:00
ballOutgoingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
},
ballOutgoingExpired: {
width: 30,
height: 30,
borderRadius: 15,
2020-05-03 20:17:49 +02:00
justifyContent: 'center',
},
2018-06-25 00:22:46 +02:00
ballTransparrent: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'transparent',
},
ballDimmed: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'gray',
},
});
2020-09-09 01:36:35 +02:00
export const BluePlusIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ball: {
backgroundColor: colors.buttonBackgroundColor,
},
});
return (
<Avatar
rounded
containerStyle={[stylesBlueIcon.ball, stylesBlueIconHooks.ball]}
icon={{ name: 'add', size: 22, type: 'ionicons', color: colors.foregroundColor }}
{...props}
/>
2020-09-09 01:36:35 +02:00
);
};
2018-01-30 23:42:38 +01:00
2020-09-09 01:36:35 +02:00
export const BlueTransactionIncomingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballIncoming: {
backgroundColor: colors.ballReceive,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ballIncoming, stylesBlueIconHooks.ballIncoming]}>
<Icon {...props} name="arrow-down" size={16} type="font-awesome" color={colors.incomingForegroundColor} />
2018-01-30 23:42:38 +01:00
</View>
2018-06-25 00:22:46 +02:00
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2018-06-25 00:22:46 +02:00
2020-09-09 01:36:35 +02:00
export const BlueTransactionPendingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ball: {
backgroundColor: colors.buttonBackgroundColor,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ball, stylesBlueIconHooks.ball]}>
<Icon
{...props}
name="kebab-horizontal"
size={16}
type="octicon"
color={colors.foregroundColor}
iconStyle={{ left: 0, top: 7 }}
/>
2018-06-25 00:22:46 +02:00
</View>
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2018-06-25 00:22:46 +02:00
2020-09-09 01:36:35 +02:00
export const BlueTransactionExpiredIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballOutgoingExpired: {
backgroundColor: colors.ballOutgoingExpired,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ballOutgoingExpired, stylesBlueIconHooks.ballOutgoingExpired]}>
<Icon {...props} name="clock" size={16} type="octicon" color="#9AA0AA" iconStyle={{ left: 0, top: 0 }} />
2018-12-28 22:43:38 +01:00
</View>
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2018-12-28 22:43:38 +01:00
2020-09-09 01:36:35 +02:00
export const BlueTransactionOnchainIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballIncoming: {
backgroundColor: colors.ballReceive,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ballIncoming, stylesBlueIconHooks.ballIncoming]}>
<Icon
{...props}
name="link"
size={16}
type="font-awesome"
color={colors.incomingForegroundColor}
iconStyle={{ left: 0, top: 0, transform: [{ rotate: '-45deg' }] }}
/>
</View>
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2020-09-09 01:36:35 +02:00
export const BlueTransactionOffchainIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballOutgoingWithoutRotate: {
backgroundColor: colors.ballOutgoing,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ballOutgoingWithoutRotate, stylesBlueIconHooks.ballOutgoingWithoutRotate]}>
<Icon
{...props}
name="bolt"
size={16}
type="font-awesome"
color={colors.outgoingForegroundColor}
iconStyle={{ left: 0, marginTop: 6 }}
/>
2018-09-05 00:18:24 +02:00
</View>
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2018-09-05 00:18:24 +02:00
2020-09-09 01:36:35 +02:00
export const BlueTransactionOffchainIncomingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballIncomingWithoutRotate: {
backgroundColor: colors.ballReceive,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ballIncomingWithoutRotate, stylesBlueIconHooks.ballIncomingWithoutRotate]}>
<Icon
{...props}
name="bolt"
size={16}
type="font-awesome"
color={colors.incomingForegroundColor}
iconStyle={{ left: 0, marginTop: 6 }}
/>
2018-09-05 00:18:24 +02:00
</View>
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2018-09-05 00:18:24 +02:00
2020-09-09 01:36:35 +02:00
export const BlueTransactionOutgoingIcon = props => {
const { colors } = useTheme();
const stylesBlueIconHooks = StyleSheet.create({
ballOutgoing: {
backgroundColor: colors.ballOutgoing,
},
});
return (
<View {...props}>
<View style={stylesBlueIcon.boxIncoming}>
<View style={[stylesBlueIcon.ballOutgoing, stylesBlueIconHooks.ballOutgoing]}>
<Icon {...props} name="arrow-down" size={16} type="font-awesome" color={colors.outgoingForegroundColor} />
2018-06-25 00:22:46 +02:00
</View>
</View>
2020-09-09 01:36:35 +02:00
</View>
);
};
2018-06-25 00:22:46 +02:00
2020-08-21 00:50:38 +02:00
const sendReceiveScanButtonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
? 22
: PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26);
2020-09-09 01:36:35 +02:00
export const BlueReceiveButtonIcon = props => {
const { colors } = useTheme();
return (
<TouchableOpacity {...props} style={{ flex: 1 }}>
<View
style={{
flex: 1,
backgroundColor: colors.buttonBackgroundColor,
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '-45deg' }],
alignItems: 'center',
marginRight: 8,
}}
>
<Icon
{...props}
name="arrow-down"
size={sendReceiveScanButtonFontSize}
type="font-awesome"
color={colors.buttonAlternativeTextColor}
/>
2018-06-25 00:22:46 +02:00
</View>
2020-09-09 01:36:35 +02:00
<Text
style={{
color: colors.buttonAlternativeTextColor,
fontWeight: '500',
fontSize: sendReceiveScanButtonFontSize,
left: 5,
backgroundColor: 'transparent',
}}
>
{loc.receive.header}
</Text>
2018-06-25 00:22:46 +02:00
</View>
2020-09-09 01:36:35 +02:00
</View>
</TouchableOpacity>
);
};
2018-06-25 00:22:46 +02:00
2020-05-20 20:04:28 +02:00
export class BlueScanButton extends Component {
render() {
return (
2020-08-11 01:21:02 +02:00
<TouchableOpacity {...this.props} style={{ flex: 1 }}>
2020-05-20 20:04:28 +02:00
<View
style={{
flex: 1,
minWidth: 130,
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
2020-05-20 20:04:28 +02:00
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
minWidth: 24,
2020-05-20 20:04:28 +02:00
minHeight: 30,
backgroundColor: 'transparent',
alignItems: 'center',
marginBottom: -15,
marginLeft: -8,
2020-05-20 20:04:28 +02:00
}}
>
2020-08-11 01:21:02 +02:00
<Image resizeMode="stretch" source={BlueCurrentTheme.scanImage} />
2020-05-20 20:04:28 +02:00
</View>
<Text
style={{
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
2020-08-11 01:21:02 +02:00
fontSize: sendReceiveScanButtonFontSize,
fontWeight: '600',
2020-05-20 20:04:28 +02:00
left: 5,
backgroundColor: 'transparent',
}}
>
2020-07-20 15:38:46 +02:00
{loc.send.details_scan}
2020-05-20 20:04:28 +02:00
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
2018-06-25 00:22:46 +02:00
export class BlueSendButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props} testID="SendButton" style={{ flex: 1 }}>
2019-01-05 03:14:23 +01:00
<View
style={{
flex: 1,
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
alignItems: 'center',
2019-01-05 03:14:23 +01:00
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
2018-06-25 00:22:46 +02:00
<View
style={{
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '225deg' }],
2020-08-11 01:21:02 +02:00
marginRight: 8,
2018-06-25 00:22:46 +02:00
}}
>
2020-07-15 19:32:59 +02:00
<Icon
{...this.props}
name="arrow-down"
2020-08-11 01:21:02 +02:00
size={sendReceiveScanButtonFontSize}
2020-07-15 19:32:59 +02:00
type="font-awesome"
color={BlueCurrentTheme.colors.buttonAlternativeTextColor}
/>
2018-06-25 00:22:46 +02:00
</View>
<Text
style={{
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
2020-08-11 01:21:02 +02:00
fontSize: sendReceiveScanButtonFontSize,
2018-06-25 00:22:46 +02:00
fontWeight: '500',
backgroundColor: 'transparent',
}}
>
2019-01-05 03:14:23 +01:00
{loc.send.header}
2018-06-25 00:22:46 +02:00
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export class ManageFundsBigButton extends Component {
render() {
return (
2018-10-02 05:40:23 +02:00
<TouchableOpacity {...this.props}>
2019-01-05 03:14:23 +01:00
<View
style={{
flex: 1,
width: 168,
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
2019-01-05 03:14:23 +01:00
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
2019-01-05 03:14:23 +01:00
minWidth: 30,
minHeight: 30,
right: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '90deg' }],
}}
>
2020-07-15 19:32:59 +02:00
<Icon {...this.props} name="link" size={16} type="font-awesome" color={BlueCurrentTheme.colors.buttonAlternativeTextColor} />
</View>
<Text
style={{
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: (isIpad && 10) || 16,
fontWeight: '500',
backgroundColor: 'transparent',
}}
>
2018-10-04 00:32:42 +02:00
{loc.lnd.title}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
2020-06-26 21:27:13 +02:00
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
2019-12-28 17:22:43 +01:00
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
2020-09-09 01:36:35 +02:00
const { colors } = useTheme();
const { txMetadata, wallets } = useContext(BlueStorageContext);
const containerStyle = useMemo(
() => ({
backgroundColor: 'transparent',
borderBottomColor: colors.lightBorder,
paddingTop: 16,
paddingBottom: 16,
2020-10-14 10:22:51 +02:00
paddingRight: 0,
}),
[colors.lightBorder],
);
2019-01-30 03:13:45 +01:00
const title = useMemo(() => transactionTimeToReadable(item.received), [item.received]);
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(() => {
2019-01-30 03:13:45 +01:00
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) {
2020-07-20 15:38:46 +02:00
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
2019-01-30 03:13:45 +01:00
} else if (invoiceExpiration < now) {
if (item.ispaid) {
2020-07-20 15:38:46 +02:00
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
2019-01-30 03:13:45 +01:00
} else {
return loc.lnd.expired;
}
}
} else {
2020-07-20 15:38:46 +02:00
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
2019-01-30 03:13:45 +01:00
}
}, [item, itemPriceUnit]);
2019-01-30 03:13:45 +01:00
const rowTitleStyle = useMemo(() => {
2020-09-09 01:36:35 +02:00
let color = colors.successColor;
2019-01-30 03:13:45 +01:00
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) {
2020-09-09 01:36:35 +02:00
color = colors.successColor;
2019-01-30 03:13:45 +01:00
} else if (invoiceExpiration < now) {
if (item.ispaid) {
2020-09-09 01:36:35 +02:00
color = colors.successColor;
2019-01-30 03:13:45 +01:00
} else {
color = '#9AA0AA';
2019-01-30 03:13:45 +01:00
}
}
} else if (item.value / 100000000 < 0) {
2020-09-09 01:36:35 +02:00
color = colors.foregroundColor;
2019-01-30 03:13:45 +01:00
}
return {
color,
2020-05-14 20:44:23 +02:00
fontSize: 14,
fontWeight: '600',
2020-05-14 20:44:23 +02:00
textAlign: 'right',
width: 96,
2019-01-30 03:13:45 +01:00
};
}, [item, colors.foregroundColor, colors.successColor]);
2019-01-30 03:13:45 +01:00
const avatar = useMemo(() => {
2019-01-30 03:13:45 +01:00
// is it lightning refill tx?
if (item.category === 'receive' && item.confirmations < 3) {
2019-01-30 03:13:45 +01:00
return (
<View style={{ width: 25 }}>
<BlueTransactionPendingIcon />
</View>
);
}
if (item.type && item.type === 'bitcoind_tx') {
2019-01-30 03:13:45 +01:00
return (
<View style={{ width: 25 }}>
<BlueTransactionOnchainIcon />
</View>
);
}
if (item.type === 'paid_invoice') {
2019-01-30 03:13:45 +01:00
// is it lightning offchain payment?
return (
<View style={{ width: 25 }}>
<BlueTransactionOffchainIcon />
</View>
);
}
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (!item.ispaid) {
2019-01-30 03:13:45 +01:00
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = item.timestamp + item.expire_time;
2019-01-30 03:13:45 +01:00
if (invoiceExpiration < now) {
return (
<View style={{ width: 25 }}>
<BlueTransactionExpiredIcon />
</View>
);
}
} else {
return (
<View style={{ width: 25 }}>
<BlueTransactionOffchainIncomingIcon />
</View>
);
}
}
if (!item.confirmations) {
2019-01-30 03:13:45 +01:00
return (
<View style={{ width: 25 }}>
<BlueTransactionPendingIcon />
</View>
);
} else if (item.value < 0) {
2019-01-30 03:13:45 +01:00
return (
<View style={{ width: 25 }}>
<BlueTransactionOutgoingIcon />
</View>
);
} else {
return (
<View style={{ width: 25 }}>
2019-01-31 07:52:21 +01:00
<BlueTransactionIncomingIcon />
2019-01-30 03:13:45 +01:00
</View>
);
}
}, [item]);
2019-01-30 03:13:45 +01:00
const onPress = useCallback(async () => {
if (item.hash) {
NavigationService.navigate('TransactionStatus', { hash: item.hash });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
const lightningWallet = wallets.filter(wallet => {
2019-01-30 03:13:45 +01:00
if (typeof wallet === 'object') {
if ('secret' in wallet) {
return wallet.getSecret() === item.fromWallet;
2019-01-30 03:13:45 +01:00
}
}
});
2019-01-31 01:56:34 +01:00
if (lightningWallet.length === 1) {
2020-07-23 20:06:13 +02:00
// 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,
2020-07-23 20:06:13 +02:00
justPaid: false,
fromWalletID: lightningWallet[0].getID(),
},
});
return;
}
2019-01-31 01:56:34 +01:00
NavigationService.navigate('LNDViewInvoice', {
invoice: item,
2019-01-31 01:56:34 +01:00
fromWallet: lightningWallet[0],
isModal: false,
});
}
2019-01-30 03:13:45 +01:00
}
}, [item, wallets]);
2019-01-30 03:13:45 +01:00
const onLongPress = useCallback(() => {
if (subtitleNumberOfLines === 1) {
setSubtitleNumberOfLines(0);
}
}, [subtitleNumberOfLines]);
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
return (
2020-06-26 21:27:13 +02:00
<View style={{ marginHorizontal: 4 }}>
<BlueListItem
leftAvatar={avatar}
title={title}
2020-06-26 21:27:13 +02:00
titleNumberOfLines={subtitleNumberOfLines}
subtitle={subtitle}
subtitleProps={subtitleProps}
2020-06-26 21:27:13 +02:00
onPress={onPress}
onLongPress={onLongPress}
chevron={false}
Component={TouchableOpacity}
rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle}
containerStyle={containerStyle}
2020-06-26 21:27:13 +02:00
/>
</View>
);
2020-06-19 18:31:59 +02:00
});
2019-01-30 03:13:45 +01:00
const isDesktop = getSystemName() === 'Mac OS X';
2019-01-24 08:36:01 +01:00
export class BlueAddressInput extends Component {
static propTypes = {
isLoading: PropTypes.bool,
onChangeText: PropTypes.func,
2019-12-28 01:53:34 +01:00
onBarScanned: PropTypes.func.isRequired,
launchedBy: PropTypes.string.isRequired,
2019-01-24 08:36:01 +01:00
address: PropTypes.string,
2019-02-17 08:50:23 +01:00
placeholder: PropTypes.string,
2019-01-24 08:36:01 +01:00
};
static defaultProps = {
isLoading: false,
address: '',
2020-07-20 15:38:46 +02:00
placeholder: loc.send.details_address,
2019-01-24 08:36:01 +01:00
};
choosePhoto = () => {
ImagePicker.launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.props.onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
}
},
);
};
takePhoto = () => {
ImagePicker.launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.props.onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
2020-10-13 07:49:30 +02:00
presentCameraNotAuthorizedAlert(response.error);
}
},
);
};
copyFromClipbard = async () => {
this.props.onBarScanned(await Clipboard.getString());
};
showActionSheet = async () => {
const isClipboardEmpty = (await Clipboard.getString()).trim().length === 0;
let copyFromClipboardIndex;
if (Platform.OS === 'ios') {
const options = [loc._.cancel, loc.wallets.list_long_choose, isDesktop ? loc.wallets.take_photo : loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
copyFromClipboardIndex = options.length - 1;
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
this.choosePhoto();
} else if (buttonIndex === 2) {
this.takePhoto();
} else if (buttonIndex === copyFromClipboardIndex) {
this.copyFromClipbard();
}
});
}
};
2019-01-24 08:36:01 +01:00
render() {
return (
<View
style={{
flexDirection: 'row',
2020-07-15 19:32:59 +02:00
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
2019-01-24 08:36:01 +01:00
borderWidth: 1.0,
borderBottomWidth: 0.5,
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
2019-01-24 08:36:01 +01:00
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}}
>
<TextInput
testID="AddressInput"
2019-01-24 08:36:01 +01:00
onChangeText={text => {
this.props.onChangeText(text);
}}
2019-02-17 08:50:23 +01:00
placeholder={this.props.placeholder}
2019-01-24 08:36:01 +01:00
numberOfLines={1}
2020-06-09 14:23:21 +02:00
placeholderTextColor="#81868e"
2019-01-24 08:36:01 +01:00
value={this.props.address}
2020-06-09 14:23:21 +02:00
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, color: '#81868e' }}
2019-01-24 08:36:01 +01:00
editable={!this.props.isLoading}
2020-01-20 04:33:17 +01:00
onSubmitEditing={Keyboard.dismiss}
{...this.props}
2019-01-24 08:36:01 +01:00
/>
<TouchableOpacity
testID="BlueAddressInputScanQrButton"
2019-01-24 08:36:01 +01:00
disabled={this.props.isLoading}
onPress={() => {
Keyboard.dismiss();
if (isDesktop) {
this.showActionSheet();
} else {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy: this.props.launchedBy,
onBarScanned: this.props.onBarScanned,
},
});
}
}}
2019-01-24 08:36:01 +01:00
style={{
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.scanLabel,
2019-01-24 08:36:01 +01:00
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
2020-07-15 19:32:59 +02:00
<Image style={{}} source={require('./img/scan-white.png')} />
2020-07-20 15:38:46 +02:00
<Text style={{ marginLeft: 4, color: BlueCurrentTheme.colors.inverseForegroundColor }}>{loc.send.details_scan}</Text>
2019-01-24 08:36:01 +01:00
</TouchableOpacity>
</View>
);
}
}
2019-08-27 06:05:27 +02:00
export class BlueReplaceFeeSuggestions extends Component {
static propTypes = {
onFeeSelected: PropTypes.func.isRequired,
transactionMinimum: PropTypes.number.isRequired,
};
static defaultProps = {
transactionMinimum: 1,
};
2020-09-14 12:49:08 +02:00
state = {
customFeeValue: '1',
};
2019-08-27 06:05:27 +02:00
async componentDidMount() {
2020-09-14 12:49:08 +02:00
try {
const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey));
if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) {
this.setState({ networkFees: cachedNetworkTransactionFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
2020-09-14 12:49:08 +02:00
}
} catch (_) {}
2019-08-27 06:05:27 +02:00
const networkFees = await NetworkTransactionFees.recommendedFees();
this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
2019-08-27 06:05:27 +02:00
}
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) {
2020-05-18 16:45:31 +02:00
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.mediumFee));
2019-08-27 06:05:27 +02:00
} else if (selectedFeeType === NetworkTransactionFeeType.SLOW) {
2020-05-18 16:45:31 +02:00
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.slowFee));
2019-08-27 06:05:27 +02:00
} else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) {
2020-09-14 12:49:08 +02:00
this.props.onFeeSelected(Number(this.state.customFeeValue));
2019-08-27 06:05:27 +02:00
}
};
onCustomFeeTextChange = customFee => {
2020-09-14 12:49:08 +02:00
const customFeeValue = customFee.replace(/[^0-9]/g, '');
this.setState({ customFeeValue, selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => {
2019-08-27 06:05:27 +02:00
this.onFeeSelected(NetworkTransactionFeeType.CUSTOM);
});
};
render() {
2020-09-14 12:49:08 +02:00
const { networkFees, selectedFeeType } = this.state;
2019-08-27 06:05:27 +02:00
return (
<View>
2020-09-14 12:49:08 +02:00
{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) => (
<TouchableOpacity
key={label}
onPress={() => this.onFeeSelected(type)}
style={[
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
active && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor },
]}
>
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ fontSize: 22, color: BlueCurrentTheme.colors.successColor, fontWeight: '600' }}>{label}</Text>
<View
style={{
backgroundColor: BlueCurrentTheme.colors.successColor,
borderRadius: 5,
paddingHorizontal: 6,
paddingVertical: 3,
}}
>
<Text style={{ color: BlueCurrentTheme.colors.background }}>~{time}</Text>
</View>
</View>
<View style={{ justifyContent: 'flex-end', flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ color: BlueCurrentTheme.colors.successColor }}>{rate} sat/byte</Text>
</View>
</TouchableOpacity>
))}
<TouchableOpacity
onPress={() => this.customTextInput.focus()}
style={[
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
selectedFeeType === NetworkTransactionFeeType.CUSTOM && {
borderRadius: 8,
backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor,
},
]}
>
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ fontSize: 22, color: BlueCurrentTheme.colors.successColor, fontWeight: '600' }}>{loc.send.fee_custom}</Text>
</View>
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center', marginTop: 5 }}>
<TextInput
onChangeText={this.onCustomFeeTextChange}
keyboardType="numeric"
value={this.state.customFeeValue}
ref={ref => (this.customTextInput = ref)}
maxLength={9}
style={{
2020-09-14 12:49:08 +02:00
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,
}}
2020-09-14 12:49:08 +02:00
onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)}
defaultValue={`${this.props.transactionMinimum}`}
placeholder={loc.send.fee_satbyte}
placeholderTextColor="#81868e"
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
<Text style={{ color: BlueCurrentTheme.colors.successColor }}>sat/byte</Text>
</View>
</TouchableOpacity>
2020-07-15 19:32:59 +02:00
<BlueText style={{ color: BlueCurrentTheme.colors.alternativeTextColor }}>
2020-09-14 12:49:08 +02:00
{loc.formatString(loc.send.fee_replace_min, { min: this.props.transactionMinimum })}
</BlueText>
</View>
2019-08-27 06:05:27 +02:00
);
}
}
export class BlueBitcoinAmount extends Component {
static propTypes = {
isLoading: PropTypes.bool,
2020-06-09 16:08:18 +02:00
/**
* amount is a sting thats always in current unit denomination, e.g. '0.001' or '9.43' or '10000'
*/
2019-05-02 22:33:03 +02:00
amount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
2020-06-09 16:08:18 +02:00
/**
* callback that returns currently typed amount, in current denomination, e.g. 0.001 or 10000 or $9.34
* (btc, sat, fiat)
*/
onChangeText: PropTypes.func,
2020-06-09 16:08:18 +02:00
/**
* callback thats fired to notify of currently selected denomination, returns <BitcoinUnit.*>
*/
onAmountUnitChange: PropTypes.func,
disabled: PropTypes.bool,
};
2020-06-09 16:08:18 +02:00
/**
* cache of conversions fiat amount => satoshi
* @type {{}}
*/
static conversionCache = {};
static getCachedSatoshis(amount) {
return BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] || false;
}
static setCachedSatoshis(amount, sats) {
BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] = sats;
}
2020-06-09 16:08:18 +02:00
constructor(props) {
super(props);
this.state = { unit: props.unit || BitcoinUnit.BTC, previousUnit: BitcoinUnit.SATS };
}
/**
* here we must recalculate old amont value (which was denominated in `previousUnit`) to new denomination `newUnit`
* and fill this value in input box, so user can switch between, for example, 0.001 BTC <=> 100000 sats
*
* @param previousUnit {string} one of {BitcoinUnit.*}
* @param newUnit {string} one of {BitcoinUnit.*}
*/
onAmountUnitChange(previousUnit, newUnit) {
const amount = this.props.amount || 0;
console.log('was:', amount, previousUnit, '; converting to', newUnit);
let sats = 0;
switch (previousUnit) {
case BitcoinUnit.BTC:
sats = new BigNumber(amount).multipliedBy(100000000).toString();
break;
case BitcoinUnit.SATS:
sats = amount;
break;
case BitcoinUnit.LOCAL_CURRENCY:
sats = new BigNumber(currency.fiatToBTC(amount)).multipliedBy(100000000).toString();
break;
}
if (previousUnit === BitcoinUnit.LOCAL_CURRENCY && BlueBitcoinAmount.conversionCache[amount + previousUnit]) {
// cache hit! we reuse old value that supposedly doesnt have rounding errors
sats = BlueBitcoinAmount.conversionCache[amount + previousUnit];
}
console.log('so, in sats its', sats);
2020-07-20 15:38:46 +02:00
const newInputValue = formatBalancePlain(sats, newUnit, false);
2020-06-09 16:08:18 +02:00
console.log('and in', newUnit, 'its', newInputValue);
if (newUnit === BitcoinUnit.LOCAL_CURRENCY && previousUnit === BitcoinUnit.SATS) {
// we cache conversion, so when we will need reverse conversion there wont be a rounding error
BlueBitcoinAmount.conversionCache[newInputValue + newUnit] = amount;
}
this.props.onChangeText(newInputValue);
if (this.props.onAmountUnitChange) this.props.onAmountUnitChange(newUnit);
}
/**
* responsible for cycling currently selected denomination, BTC->SAT->LOCAL_CURRENCY->BTC
*/
changeAmountUnit = () => {
let previousUnit = this.state.unit;
let newUnit;
if (previousUnit === BitcoinUnit.BTC) {
newUnit = BitcoinUnit.SATS;
} else if (previousUnit === BitcoinUnit.SATS) {
newUnit = BitcoinUnit.LOCAL_CURRENCY;
} else if (previousUnit === BitcoinUnit.LOCAL_CURRENCY) {
newUnit = BitcoinUnit.BTC;
} else {
newUnit = BitcoinUnit.BTC;
previousUnit = BitcoinUnit.SATS;
}
this.setState({ unit: newUnit, previousUnit }, () => this.onAmountUnitChange(previousUnit, newUnit));
};
maxLength = () => {
switch (this.state.unit) {
case BitcoinUnit.BTC:
return 10;
case BitcoinUnit.SATS:
return 15;
default:
return 15;
}
};
2020-05-31 20:33:48 +02:00
textInput = React.createRef();
handleTextInputOnPress = () => {
this.textInput.current.focus();
};
render() {
2020-06-09 16:08:18 +02:00
const amount = this.props.amount || 0;
2020-07-20 15:38:46 +02:00
let secondaryDisplayCurrency = formatBalanceWithoutSuffix(amount, BitcoinUnit.LOCAL_CURRENCY, false);
2020-06-09 16:08:18 +02:00
// if main display is sat or btc - secondary display is fiat
// if main display is fiat - secondary dislay is btc
let sat;
switch (this.state.unit) {
case BitcoinUnit.BTC:
sat = new BigNumber(amount).multipliedBy(100000000).toString();
2020-07-20 15:38:46 +02:00
secondaryDisplayCurrency = formatBalanceWithoutSuffix(sat, BitcoinUnit.LOCAL_CURRENCY, false);
2020-06-09 16:08:18 +02:00
break;
case BitcoinUnit.SATS:
2020-07-20 15:38:46 +02:00
secondaryDisplayCurrency = formatBalanceWithoutSuffix(amount.toString(), BitcoinUnit.LOCAL_CURRENCY, false);
2020-06-09 16:08:18 +02:00
break;
case BitcoinUnit.LOCAL_CURRENCY:
secondaryDisplayCurrency = currency.fiatToBTC(parseFloat(amount));
if (BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY]) {
// cache hit! we reuse old value that supposedly doesnt have rounding errors
const sats = BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY];
secondaryDisplayCurrency = currency.satoshiToBTC(sats);
}
break;
2019-05-02 22:33:03 +02:00
}
2020-06-09 16:08:18 +02:00
if (amount === BitcoinUnit.MAX) secondaryDisplayCurrency = ''; // we dont want to display NaN
return (
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
2020-09-16 03:20:48 +02:00
{!this.props.disabled && <View style={{ alignSelf: 'center', padding: amount === BitcoinUnit.MAX ? 0 : 15 }} />}
2020-06-09 16:08:18 +02:00
<View style={{ flex: 1 }}>
<View
style={{ flexDirection: 'row', alignContent: 'space-between', justifyContent: 'center', paddingTop: 16, paddingBottom: 2 }}
>
2020-09-16 03:20:48 +02:00
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
2020-06-09 16:08:18 +02:00
<Text
style={{
2020-07-15 19:32:59 +02:00
color: this.props.disabled
? BlueCurrentTheme.colors.buttonDisabledTextColor
: BlueCurrentTheme.colors.alternativeTextColor2,
2020-06-09 16:08:18 +02:00
fontSize: 18,
marginHorizontal: 4,
fontWeight: 'bold',
alignSelf: 'center',
justifyContent: 'center',
}}
>
{currency.getCurrencySymbol() + ' '}
</Text>
)}
<TextInput
{...this.props}
testID="BitcoinAmountInput"
keyboardType="numeric"
adjustsFontSizeToFit
onChangeText={text => {
text = text.trim();
if (this.state.unit !== BitcoinUnit.LOCAL_CURRENCY) {
text = text.replace(',', '.');
const split = text.split('.');
if (split.length >= 2) {
text = `${parseInt(split[0], 10)}.${split[1]}`;
} else {
text = `${parseInt(split[0], 10)}`;
}
text = this.state.unit === BitcoinUnit.BTC ? text.replace(/[^0-9.]/g, '') : text.replace(/[^0-9]/g, '');
text = text.replace(/(\..*)\./g, '$1');
if (text.startsWith('.')) {
text = '0.';
}
text = text.replace(/(0{1,}.)\./g, '$1');
if (this.state.unit !== BitcoinUnit.BTC) {
text = text.replace(/[^0-9.]/g, '');
}
} else if (this.state.unit === BitcoinUnit.LOCAL_CURRENCY) {
text = text.replace(/,/gi, '');
if (text.split('.').length > 2) {
// too many dots. stupid code to remove all but first dot:
let rez = '';
let first = true;
for (const part of text.split('.')) {
rez += part;
if (first) {
rez += '.';
first = false;
}
}
text = rez;
}
text = text.replace(/[^\d.,-]/g, ''); // remove all but numberd, dots & commas
}
this.props.onChangeText(text);
}}
onBlur={() => {
if (this.props.onBlur) this.props.onBlur();
}}
onFocus={() => {
if (this.props.onFocus) this.props.onFocus();
}}
placeholder="0"
maxLength={this.maxLength()}
ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled}
value={parseFloat(amount) > 0 || amount === BitcoinUnit.MAX ? amount : undefined}
placeholderTextColor={
2020-07-15 19:32:59 +02:00
this.props.disabled ? BlueCurrentTheme.colors.buttonDisabledTextColor : BlueCurrentTheme.colors.alternativeTextColor2
2020-06-09 16:08:18 +02:00
}
style={{
2020-07-15 19:32:59 +02:00
color: this.props.disabled
? BlueCurrentTheme.colors.buttonDisabledTextColor
: BlueCurrentTheme.colors.alternativeTextColor2,
2020-06-09 16:08:18 +02:00
fontWeight: 'bold',
fontSize: amount.length > 10 ? 20 : 36,
}}
/>
2020-09-16 03:20:48 +02:00
{this.state.unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
2020-06-09 16:08:18 +02:00
<Text
style={{
2020-07-15 19:32:59 +02:00
color: this.props.disabled
? BlueCurrentTheme.colors.buttonDisabledTextColor
: BlueCurrentTheme.colors.alternativeTextColor2,
2020-06-09 16:08:18 +02:00
fontSize: 15,
marginHorizontal: 4,
fontWeight: '600',
alignSelf: 'center',
justifyContent: 'center',
}}
>
{' ' + this.state.unit}
</Text>
)}
</View>
<View style={{ alignItems: 'center', marginBottom: 22 }}>
<Text style={{ fontSize: 16, color: '#9BA0A9', fontWeight: '600' }}>
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX
2020-07-20 15:38:46 +02:00
? removeTrailingZeros(secondaryDisplayCurrency)
2020-06-09 16:08:18 +02:00
: secondaryDisplayCurrency}
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${BitcoinUnit.BTC}` : null}
</Text>
</View>
</View>
2020-09-16 03:20:48 +02:00
{!this.props.disabled && amount !== BitcoinUnit.MAX && (
2020-06-09 16:08:18 +02:00
<TouchableOpacity
testID="changeAmountUnitButton"
2020-06-09 16:08:18 +02:00
style={{ alignSelf: 'center', marginRight: 16, paddingLeft: 16, paddingVertical: 16 }}
onPress={this.changeAmountUnit}
>
<Image source={require('./img/round-compare-arrows-24-px.png')} />
</TouchableOpacity>
)}
</View>
2019-01-04 03:06:02 +01:00
</TouchableWithoutFeedback>
);
}
}
2019-08-03 23:29:15 +02:00
const styles = StyleSheet.create({
balanceBlur: {
height: 30,
width: 100,
marginRight: 16,
},
});
2020-04-28 18:27:35 +02:00
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 (
<View style={mergedStyles}>
<Icon name="check" size={50} type="font-awesome" color="#0f5cc0" />
</View>
);
}
2020-06-29 14:58:43 +02:00
const tabsStyles = StyleSheet.create({
root: {
flexDirection: 'row',
height: 50,
borderColor: '#e3e3e3',
borderBottomWidth: 1,
},
tabRoot: {
flex: 1,
2020-06-29 14:58:43 +02:00
justifyContent: 'center',
alignItems: 'center',
borderColor: 'white',
borderBottomWidth: 2,
},
});
export const BlueTabs = ({ active, onSwitch, tabs }) => (
<View style={[tabsStyles.root, isIpad && { marginBottom: 30 }]}>
2020-06-29 14:58:43 +02:00
{tabs.map((Tab, i) => (
<TouchableOpacity
key={i}
onPress={() => onSwitch(i)}
style={[
tabsStyles.tabRoot,
active === i && {
2020-07-15 19:32:59 +02:00
borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor,
2020-06-29 14:58:43 +02:00
borderBottomWidth: 2,
},
]}
>
<Tab active={active === i} />
</TouchableOpacity>
))}
</View>
);
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 = 800 } = 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 ? (
<View style={animatedQRCodeStyle.container}>
<BlueSpacing20 />
<View style={animatedQRCodeStyle.qrcodeContainer}>
<QRCode
value={currentFragment.toUpperCase()}
size={this.state.qrCodeHeight}
2020-08-03 19:26:16 +02:00
color="#000000"
2020-07-15 19:32:59 +02:00
logoBackgroundColor={BlueCurrentTheme.colors.brandingColor}
2020-08-03 19:26:16 +02:00
backgroundColor="#FFFFFF"
ecl="L"
/>
</View>
<BlueSpacing20 />
<View>
<Text style={animatedQRCodeStyle.text}>
2020-07-23 15:09:48 +02:00
{loc.formatString(loc._.of, { number: this.state.index + 1, total: this.state.total })}
</Text>
</View>
<BlueSpacing20 />
<View style={animatedQRCodeStyle.controller}>
<TouchableOpacity
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-start' }]}
onPress={this.moveToPreviousFragment}
>
2020-07-23 15:09:48 +02:00
<Text style={animatedQRCodeStyle.text}>{loc.send.dynamic_prev}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[animatedQRCodeStyle.button, { width: '50%' }]}
onPress={this.state.intervalHandler ? this.stopAutoMove : this.startAutoMove}
>
2020-07-23 15:09:48 +02:00
<Text style={animatedQRCodeStyle.text}>{this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-end' }]}
onPress={this.moveToNextFragment}
>
2020-07-23 15:09:48 +02:00
<Text style={animatedQRCodeStyle.text}>{loc.send.dynamic_next}</Text>
</TouchableOpacity>
</View>
</View>
) : (
<View>
2020-07-23 15:09:48 +02:00
<Text>{loc.send.dynamic_init}</Text>
</View>
);
}
}
const animatedQRCodeStyle = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
},
qrcodeContainer: {
alignItems: 'center',
justifyContent: 'center',
2020-08-03 19:26:16 +02:00
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: {
2020-07-15 19:32:59 +02:00
fontSize: 14,
color: BlueCurrentTheme.colors.foregroundColor,
fontWeight: 'bold',
},
});