mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 18:00:17 +01:00
2325 lines
70 KiB
JavaScript
2325 lines
70 KiB
JavaScript
/* eslint react/prop-types: 0 */
|
|
import React, { Component, useEffect, useState } from 'react';
|
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
import PropTypes from 'prop-types';
|
|
import { Icon, Input, Text, Header, ListItem } from 'react-native-elements';
|
|
import {
|
|
TouchableOpacity,
|
|
TouchableWithoutFeedback,
|
|
Animated,
|
|
Alert,
|
|
ActivityIndicator,
|
|
View,
|
|
KeyboardAvoidingView,
|
|
UIManager,
|
|
StyleSheet,
|
|
Dimensions,
|
|
Image,
|
|
Keyboard,
|
|
SafeAreaView,
|
|
InputAccessoryView,
|
|
Clipboard,
|
|
Platform,
|
|
FlatList,
|
|
TextInput,
|
|
} from 'react-native';
|
|
import LinearGradient from 'react-native-linear-gradient';
|
|
import { LightningCustodianWallet, PlaceholderWallet } from './class';
|
|
import Carousel from 'react-native-snap-carousel';
|
|
import { BitcoinUnit } from './models/bitcoinUnits';
|
|
import NavigationService from './NavigationService';
|
|
import WalletGradient from './class/walletGradient';
|
|
import ToolTip from 'react-native-tooltip';
|
|
import { BlurView } from '@react-native-community/blur';
|
|
import showPopupMenu from 'react-native-popup-menu-android';
|
|
import NetworkTransactionFees, { NetworkTransactionFeeType } from './models/networkTransactionFees';
|
|
import Biometric from './class/biometrics';
|
|
let loc = require('./loc/');
|
|
/** @type {AppStorage} */
|
|
let BlueApp = require('./BlueApp');
|
|
const { height, width } = Dimensions.get('window');
|
|
const aspectRatio = height / width;
|
|
const BigNumber = require('bignumber.js');
|
|
let isIpad;
|
|
if (aspectRatio > 1.6) {
|
|
isIpad = false;
|
|
} else {
|
|
isIpad = true;
|
|
}
|
|
|
|
export class BlueButton extends Component {
|
|
render() {
|
|
let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : BlueApp.settings.buttonBackgroundColor;
|
|
let fontColor = BlueApp.settings.buttonTextColor;
|
|
if (this.props.hasOwnProperty('disabled') && this.props.disabled === true) {
|
|
backgroundColor = BlueApp.settings.buttonDisabledBackgroundColor;
|
|
fontColor = BlueApp.settings.buttonDisabledTextColor;
|
|
}
|
|
let buttonWidth = this.props.width ? this.props.width : width / 1.5;
|
|
if (this.props.hasOwnProperty('noMinWidth')) {
|
|
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',
|
|
}}
|
|
{...this.props}
|
|
>
|
|
<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>}
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BitcoinButton extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity
|
|
testID={this.props.testID}
|
|
onPress={() => {
|
|
// eslint-disable-next-line
|
|
if (this.props.onPress) this.props.onPress();
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
// eslint-disable-next-line
|
|
borderColor: BlueApp.settings.hdborderColor,
|
|
borderWidth: 1,
|
|
borderRadius: 5,
|
|
backgroundColor: (this.props.active && BlueApp.settings.hdbackgroundColor) || BlueApp.settings.brandingColor,
|
|
// eslint-disable-next-line
|
|
minWidth: this.props.style.width,
|
|
// eslint-disable-next-line
|
|
minHeight: this.props.style.height,
|
|
height: this.props.style.height,
|
|
flex: 1,
|
|
}}
|
|
>
|
|
<View style={{ marginTop: 16, marginLeft: 16, marginBottom: 16 }}>
|
|
<Text style={{ color: BlueApp.settings.hdborderColor, fontWeight: 'bold' }}>{loc.wallets.add.bitcoin}</Text>
|
|
</View>
|
|
<Image
|
|
style={{ width: 34, height: 34, marginRight: 8, marginBottom: 8, justifyContent: 'flex-end', alignSelf: 'flex-end' }}
|
|
source={require('./img/addWallet/bitcoin.png')}
|
|
/>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class LightningButton extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity
|
|
onPress={() => {
|
|
// eslint-disable-next-line
|
|
if (this.props.onPress) this.props.onPress();
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
// eslint-disable-next-line
|
|
borderColor: BlueApp.settings.lnborderColor,
|
|
borderWidth: 1,
|
|
borderRadius: 5,
|
|
backgroundColor: (this.props.active && BlueApp.settings.lnbackgroundColor) || BlueApp.settings.brandingColor,
|
|
// eslint-disable-next-line
|
|
minWidth: this.props.style.width,
|
|
// eslint-disable-next-line
|
|
minHeight: this.props.style.height,
|
|
height: this.props.style.height,
|
|
flex: 1,
|
|
}}
|
|
>
|
|
<View style={{ marginTop: 16, marginLeft: 16, marginBottom: 16 }}>
|
|
<Text style={{ color: BlueApp.settings.lnborderColor, fontWeight: 'bold' }}>{loc.wallets.add.lightning}</Text>
|
|
</View>
|
|
<Image
|
|
style={{ width: 34, height: 34, marginRight: 8, marginBottom: 8, justifyContent: 'flex-end', alignSelf: 'flex-end' }}
|
|
source={require('./img/addWallet/lightning.png')}
|
|
/>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueWalletNavigationHeader extends Component {
|
|
static propTypes = {
|
|
wallet: PropTypes.shape().isRequired,
|
|
onWalletUnitChange: PropTypes.func,
|
|
};
|
|
|
|
static getDerivedStateFromProps(props, state) {
|
|
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange, allowOnchainAddress: state.allowOnchainAddress };
|
|
}
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
wallet: props.wallet,
|
|
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
|
|
showManageFundsButton: false,
|
|
};
|
|
}
|
|
|
|
handleCopyPress = _item => {
|
|
Clipboard.setString(loc.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 BlueApp.saveToDisk();
|
|
};
|
|
|
|
showAndroidTooltip = () => {
|
|
showPopupMenu(this.toolTipMenuOptions(), this.handleToolTipSelection, this.walletBalanceText);
|
|
};
|
|
|
|
handleToolTipSelection = item => {
|
|
if (item === loc.transactions.details.copy || item.id === loc.transactions.details.copy) {
|
|
this.handleCopyPress();
|
|
} else if (item === 'balancePrivacy' || item.id === 'balancePrivacy') {
|
|
this.handleBalanceVisibility();
|
|
}
|
|
};
|
|
|
|
toolTipMenuOptions() {
|
|
return Platform.select({
|
|
// NOT WORKING ATM.
|
|
// ios: [
|
|
// { text: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance', onPress: this.handleBalanceVisibility },
|
|
// { text: loc.transactions.details.copy, onPress: this.handleCopyPress },
|
|
// ],
|
|
android: this.state.wallet.hideBalance
|
|
? [{ id: 'balancePrivacy', label: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance' }]
|
|
: [
|
|
{ id: 'balancePrivacy', label: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance' },
|
|
{ id: loc.transactions.details.copy, 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);
|
|
});
|
|
}
|
|
|
|
manageFundsPressed = () => {
|
|
this.props.onManageFundsPressed();
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<LinearGradient
|
|
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
|
|
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
|
|
>
|
|
<Image
|
|
source={
|
|
(LightningCustodianWallet.type === this.state.wallet.type && require('./img/lnd-shape.png')) || 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 ? 'Show Balance' : 'Hide Balance', onPress: this.handleBalanceVisibility }]
|
|
: [
|
|
{ text: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance', 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',
|
|
}}
|
|
>
|
|
{loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString()}
|
|
</Text>
|
|
)}
|
|
</TouchableOpacity>
|
|
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
|
|
<TouchableOpacity onPress={this.manageFundsPressed}>
|
|
<View
|
|
style={{
|
|
marginTop: 14,
|
|
marginBottom: 10,
|
|
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
borderRadius: 9,
|
|
minWidth: 119,
|
|
minHeight: 39,
|
|
width: 119,
|
|
height: 39,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<Text
|
|
style={{
|
|
fontWeight: '500',
|
|
fontSize: 14,
|
|
color: '#FFFFFF',
|
|
}}
|
|
>
|
|
{loc.lnd.title}
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
)}
|
|
</LinearGradient>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueButtonLink extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity
|
|
style={{
|
|
minHeight: 60,
|
|
minWidth: 100,
|
|
height: 60,
|
|
justifyContent: 'center',
|
|
}}
|
|
{...this.props}
|
|
>
|
|
<Text style={{ color: BlueApp.settings.foregroundColor, textAlign: 'center', fontSize: 16 }}>{this.props.title}</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => {
|
|
Alert.alert(
|
|
'Wallet',
|
|
`Have you saved your wallet's backup phrase? This backup phrase is required to access your funds in case you lose this device. Without the backup phrase, your funds will be permanently lost.`,
|
|
[
|
|
{ text: 'Yes, I have', onPress: onSuccess, style: 'cancel' },
|
|
{
|
|
text: 'No, I have not',
|
|
onPress: onFailure,
|
|
},
|
|
],
|
|
{ cancelable: false },
|
|
);
|
|
};
|
|
|
|
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => ({
|
|
headerStyle: {
|
|
backgroundColor: BlueApp.settings.brandingColor,
|
|
borderBottomWidth: 0,
|
|
elevation: 0,
|
|
},
|
|
headerTitleStyle: {
|
|
fontWeight: '600',
|
|
color: BlueApp.settings.foregroundColor,
|
|
},
|
|
headerTintColor: BlueApp.settings.foregroundColor,
|
|
headerRight: withNavigationCloseButton ? (
|
|
<TouchableOpacity
|
|
style={{ width: 40, height: 40, padding: 14 }}
|
|
onPress={
|
|
customCloseButtonFunction === undefined
|
|
? () => {
|
|
Keyboard.dismiss();
|
|
navigation.goBack(null);
|
|
}
|
|
: customCloseButtonFunction
|
|
}
|
|
>
|
|
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
|
|
</TouchableOpacity>
|
|
) : null,
|
|
headerBackTitle: null,
|
|
});
|
|
|
|
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => ({
|
|
headerStyle: {
|
|
backgroundColor: BlueApp.settings.brandingColor,
|
|
borderBottomWidth: 0,
|
|
elevation: 0,
|
|
},
|
|
headerTitleStyle: {
|
|
fontWeight: '600',
|
|
color: BlueApp.settings.foregroundColor,
|
|
},
|
|
headerTintColor: BlueApp.settings.foregroundColor,
|
|
headerLeft: (
|
|
<TouchableOpacity
|
|
style={{ minWwidth: 40, height: 40, padding: 14 }}
|
|
onPress={() => {
|
|
Keyboard.dismiss();
|
|
navigation.goBack(null);
|
|
}}
|
|
>
|
|
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
|
|
</TouchableOpacity>
|
|
),
|
|
headerRight: withAdvancedOptionsMenuButton ? (
|
|
<TouchableOpacity style={{ minWidth: 40, height: 40, padding: 14 }} onPress={advancedOptionsMenuButtonAction}>
|
|
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueApp.settings.foregroundColor} />
|
|
</TouchableOpacity>
|
|
) : null,
|
|
headerBackTitle: null,
|
|
});
|
|
|
|
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>
|
|
),
|
|
});
|
|
};
|
|
|
|
export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => {
|
|
return (
|
|
<TouchableOpacity {...this.props} onPress={() => Clipboard.setString(stringToCopy)}>
|
|
<Text style={{ fontSize: 13, fontWeight: '400', color: '#68bbe1' }}>{displayText || loc.transactions.details.copy}</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
export class BlueCopyTextToClipboard extends Component {
|
|
static propTypes = {
|
|
text: PropTypes.string,
|
|
};
|
|
|
|
static defaultProps = {
|
|
text: '',
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
if (Platform.OS === 'android') {
|
|
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
}
|
|
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 (
|
|
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
|
|
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText}>
|
|
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
|
|
{this.state.address}
|
|
</Animated.Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const styleCopyTextToClipboard = StyleSheet.create({
|
|
address: {
|
|
marginVertical: 32,
|
|
fontSize: 15,
|
|
color: '#9aa0aa',
|
|
textAlign: 'center',
|
|
},
|
|
});
|
|
|
|
export class SafeBlueArea extends Component {
|
|
render() {
|
|
return (
|
|
<SafeAreaView
|
|
{...this.props}
|
|
forceInset={{ horizontal: 'always' }}
|
|
style={{ flex: 1, backgroundColor: BlueApp.settings.brandingColor }}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueCard extends Component {
|
|
render() {
|
|
return <View {...this.props} style={{ padding: 20 }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueText extends Component {
|
|
render() {
|
|
return (
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.foregroundColor,
|
|
|
|
// eslint-disable-next-line
|
|
...this.props.style,
|
|
}}
|
|
{...this.props}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
export class BlueTextCentered extends Component {
|
|
render() {
|
|
return <Text {...this.props} style={{ color: BlueApp.settings.foregroundColor, textAlign: 'center' }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueListItem extends Component {
|
|
render() {
|
|
return (
|
|
<ListItem
|
|
testID={this.props.testID}
|
|
bottomDivider
|
|
containerStyle={{
|
|
backgroundColor: 'transparent',
|
|
borderBottomColor: '#ededed',
|
|
paddingTop: 16,
|
|
paddingBottom: 16,
|
|
}}
|
|
titleStyle={{
|
|
color: this.props.disabled ? BlueApp.settings.buttonDisabledTextColor : BlueApp.settings.foregroundColor,
|
|
fontSize: 16,
|
|
fontWeight: '500',
|
|
}}
|
|
subtitleStyle={{ flexWrap: 'wrap', color: BlueApp.settings.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
|
|
subtitleNumberOfLines={1}
|
|
titleNumberOfLines={0}
|
|
Component={TouchableOpacity}
|
|
{...this.props}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueFormLabel extends Component {
|
|
render() {
|
|
return <Text {...this.props} style={{ color: BlueApp.settings.foregroundColor, fontWeight: '400', marginLeft: 20 }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueFormInput extends Component {
|
|
render() {
|
|
return (
|
|
<Input
|
|
{...this.props}
|
|
inputStyle={{ color: BlueApp.settings.foregroundColor, maxWidth: width - 105 }}
|
|
containerStyle={{
|
|
marginTop: 5,
|
|
borderColor: BlueApp.settings.inputBorderColor,
|
|
borderBottomColor: BlueApp.settings.inputBorderColor,
|
|
borderWidth: 0.5,
|
|
borderBottomWidth: 0.5,
|
|
backgroundColor: BlueApp.settings.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={{
|
|
flex: 1,
|
|
marginTop: 5,
|
|
marginHorizontal: 20,
|
|
borderColor: BlueApp.settings.inputBorderColor,
|
|
borderBottomColor: BlueApp.settings.inputBorderColor,
|
|
borderWidth: 0.5,
|
|
borderBottomWidth: 0.5,
|
|
backgroundColor: BlueApp.settings.inputBackgroundColor,
|
|
color: BlueApp.settings.foregroundColor,
|
|
}}
|
|
autoCorrect={false}
|
|
autoCapitalize="none"
|
|
spellCheck={false}
|
|
{...this.props}
|
|
selectTextOnFocus={false}
|
|
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueHeader extends Component {
|
|
render() {
|
|
return (
|
|
<Header
|
|
{...this.props}
|
|
backgroundColor="transparent"
|
|
outerContainerStyles={{
|
|
borderBottomColor: 'transparent',
|
|
borderBottomWidth: 0,
|
|
}}
|
|
statusBarProps={{ barStyle: 'default' }}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueHeaderDefaultSub extends Component {
|
|
render() {
|
|
return (
|
|
<SafeAreaView style={{ backgroundColor: BlueApp.settings.brandingColor }}>
|
|
<Header
|
|
backgroundColor={BlueApp.settings.brandingColor}
|
|
leftContainerStyle={{ minWidth: '100%' }}
|
|
outerContainerStyles={{
|
|
borderBottomColor: 'transparent',
|
|
borderBottomWidth: 0,
|
|
}}
|
|
statusBarProps={{ barStyle: 'default' }}
|
|
leftComponent={
|
|
<Text
|
|
adjustsFontSizeToFit
|
|
style={{
|
|
fontWeight: 'bold',
|
|
fontSize: 30,
|
|
color: BlueApp.settings.foregroundColor,
|
|
}}
|
|
>
|
|
{
|
|
// eslint-disable-next-line
|
|
this.props.leftText
|
|
}
|
|
</Text>
|
|
}
|
|
rightComponent={
|
|
<TouchableOpacity
|
|
onPress={() => {
|
|
// eslint-disable-next-line
|
|
if (this.props.onClose) this.props.onClose();
|
|
}}
|
|
>
|
|
<View style={stylesBlueIcon.box}>
|
|
<View style={stylesBlueIcon.ballTransparrent}>
|
|
<Image source={require('./img/close.png')} />
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
}
|
|
{...this.props}
|
|
/>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueHeaderDefaultMain extends Component {
|
|
render() {
|
|
return (
|
|
<SafeAreaView style={{ backgroundColor: BlueApp.settings.brandingColor }}>
|
|
<Header
|
|
{...this.props}
|
|
statusBarProps={{ barStyle: 'default' }}
|
|
leftComponent={{
|
|
// eslint-disable-next-line
|
|
text: this.props.leftText,
|
|
style: {
|
|
fontWeight: 'bold',
|
|
fontSize: 34,
|
|
color: BlueApp.settings.foregroundColor,
|
|
},
|
|
}}
|
|
leftContainerStyle={{
|
|
minWidth: '70%',
|
|
height: 70,
|
|
}}
|
|
bottomDivider={false}
|
|
containerStyle={{
|
|
height: 64,
|
|
flexDirection: 'row',
|
|
borderBottomColor: BlueApp.settings.brandingColor,
|
|
backgroundColor: '#ffffff',
|
|
}}
|
|
rightComponent={
|
|
this.props.onNewWalletPress && (
|
|
<TouchableOpacity
|
|
onPress={this.props.onNewWalletPress}
|
|
style={{
|
|
height: 90,
|
|
}}
|
|
>
|
|
<BluePlusIcon />
|
|
</TouchableOpacity>
|
|
)
|
|
}
|
|
/>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueSpacing extends Component {
|
|
render() {
|
|
return <View {...this.props} style={{ height: 60, backgroundColor: BlueApp.settings.brandingColor }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueSpacing40 extends Component {
|
|
render() {
|
|
return <View {...this.props} style={{ height: 50, backgroundColor: BlueApp.settings.brandingColor }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueSpacingVariable extends Component {
|
|
render() {
|
|
if (isIpad) {
|
|
return <BlueSpacing40 {...this.props} />;
|
|
} else {
|
|
return <BlueSpacing {...this.props} />;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class is {
|
|
static ipad() {
|
|
return isIpad;
|
|
}
|
|
}
|
|
|
|
export class BlueSpacing20 extends Component {
|
|
render() {
|
|
return <View {...this.props} style={{ height: 20, opacity: 0 }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueSpacing10 extends Component {
|
|
render() {
|
|
return <View {...this.props} style={{ height: 10, opacity: 0 }} />;
|
|
}
|
|
}
|
|
|
|
export class BlueList extends Component {
|
|
render() {
|
|
return <FlatList {...this.props} />;
|
|
}
|
|
}
|
|
|
|
export class BlueUseAllFundsButton extends Component {
|
|
static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
|
|
static propTypes = {
|
|
wallet: PropTypes.shape().isRequired,
|
|
onUseAllPressed: PropTypes.func.isRequired,
|
|
};
|
|
|
|
render() {
|
|
const inputView = (
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
maxHeight: 44,
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
backgroundColor: '#eef0f4',
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-start' }}>
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.alternativeTextColor,
|
|
fontSize: 16,
|
|
marginLeft: 8,
|
|
marginRight: 0,
|
|
paddingRight: 0,
|
|
paddingLeft: 0,
|
|
paddingTop: 12,
|
|
paddingBottom: 12,
|
|
}}
|
|
>
|
|
Total:
|
|
</Text>
|
|
{this.props.wallet.allowSendMax() && this.props.wallet.getBalance() > 0 ? (
|
|
<BlueButtonLink
|
|
onPress={this.props.onUseAllPressed}
|
|
style={{ marginLeft: 8, paddingRight: 0, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
|
|
title={`${loc.formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} ${
|
|
BitcoinUnit.BTC
|
|
}`}
|
|
/>
|
|
) : (
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.alternativeTextColor,
|
|
fontSize: 16,
|
|
marginLeft: 8,
|
|
marginRight: 0,
|
|
paddingRight: 0,
|
|
paddingLeft: 0,
|
|
paddingTop: 12,
|
|
paddingBottom: 12,
|
|
}}
|
|
>
|
|
{loc.formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} {BitcoinUnit.BTC}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'flex-end' }}>
|
|
<BlueButtonLink
|
|
style={{ paddingRight: 8, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
|
|
title="Done"
|
|
onPress={() => Keyboard.dismiss()}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
if (Platform.OS === 'ios') {
|
|
return <InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
|
|
} else {
|
|
return <KeyboardAvoidingView style={{ height: 44 }}>{inputView}</KeyboardAvoidingView>;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class BlueDismissKeyboardInputAccessory extends Component {
|
|
static InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory';
|
|
|
|
render() {
|
|
return Platform.OS !== 'ios' ? null : (
|
|
<InputAccessoryView nativeID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}>
|
|
<View
|
|
style={{
|
|
backgroundColor: '#eef0f4',
|
|
height: 44,
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
justifyContent: 'flex-end',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<BlueButtonLink title="Done" onPress={() => Keyboard.dismiss()} />
|
|
</View>
|
|
</InputAccessoryView>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueDoneAndDismissKeyboardInputAccessory extends Component {
|
|
static InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory';
|
|
|
|
onPasteTapped = async () => {
|
|
const clipboard = await Clipboard.getString();
|
|
this.props.onPasteTapped(clipboard);
|
|
};
|
|
|
|
render() {
|
|
const inputView = (
|
|
<View
|
|
style={{
|
|
backgroundColor: '#eef0f4',
|
|
flexDirection: 'row',
|
|
justifyContent: 'flex-end',
|
|
alignItems: 'center',
|
|
maxHeight: 44,
|
|
}}
|
|
>
|
|
<BlueButtonLink title="Clear" onPress={this.props.onClearTapped} />
|
|
<BlueButtonLink title="Paste" onPress={this.onPasteTapped} />
|
|
<BlueButtonLink title="Done" onPress={() => Keyboard.dismiss()} />
|
|
</View>
|
|
);
|
|
|
|
if (Platform.OS === 'ios') {
|
|
return <InputAccessoryView nativeID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
|
|
} else {
|
|
return <KeyboardAvoidingView>{inputView}</KeyboardAvoidingView>;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class BlueLoading extends Component {
|
|
render() {
|
|
return (
|
|
<SafeBlueArea>
|
|
<View style={{ flex: 1, paddingTop: 200 }} {...this.props}>
|
|
<ActivityIndicator />
|
|
</View>
|
|
</SafeBlueArea>
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
backgroundColor: '#ccddf9',
|
|
},
|
|
ballIncoming: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: '#d2f8d6',
|
|
transform: [{ rotate: '-45deg' }],
|
|
justifyContent: 'center',
|
|
},
|
|
ballIncomingWithoutRotate: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: '#d2f8d6',
|
|
},
|
|
ballReceive: {
|
|
width: 30,
|
|
height: 30,
|
|
borderBottomLeftRadius: 15,
|
|
backgroundColor: '#d2f8d6',
|
|
transform: [{ rotate: '-45deg' }],
|
|
},
|
|
ballOutgoing: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: '#f8d2d2',
|
|
transform: [{ rotate: '225deg' }],
|
|
justifyContent: 'center',
|
|
},
|
|
ballOutgoingWithoutRotate: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: '#f8d2d2',
|
|
},
|
|
ballOutgoingExpired: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: '#EEF0F4',
|
|
justifyContent: 'center',
|
|
},
|
|
ballTransparrent: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: 'transparent',
|
|
},
|
|
ballDimmed: {
|
|
width: 30,
|
|
height: 30,
|
|
borderRadius: 15,
|
|
backgroundColor: 'gray',
|
|
},
|
|
});
|
|
export class BluePlusIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props} style={stylesBlueIcon.container}>
|
|
<View style={stylesBlueIcon.box1}>
|
|
<View style={stylesBlueIcon.ball}>
|
|
<Ionicons
|
|
{...this.props}
|
|
name={'ios-add'}
|
|
size={26}
|
|
style={{
|
|
color: BlueApp.settings.foregroundColor,
|
|
backgroundColor: 'transparent',
|
|
left: 8,
|
|
top: 1,
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionIncomingIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ballIncoming}>
|
|
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color={BlueApp.settings.incomingForegroundColor} />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionPendingIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ball}>
|
|
<Icon
|
|
{...this.props}
|
|
name="kebab-horizontal"
|
|
size={16}
|
|
type="octicon"
|
|
color={BlueApp.settings.foregroundColor}
|
|
iconStyle={{ left: 0, top: 7 }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionExpiredIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ballOutgoingExpired}>
|
|
<Icon {...this.props} name="clock" size={16} type="octicon" color="#9AA0AA" iconStyle={{ left: 0, top: 0 }} />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionOnchainIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ballIncoming}>
|
|
<Icon
|
|
{...this.props}
|
|
name="link"
|
|
size={16}
|
|
type="font-awesome"
|
|
color={BlueApp.settings.incomingForegroundColor}
|
|
iconStyle={{ left: 0, top: 0, transform: [{ rotate: '-45deg' }] }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionOffchainIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ballOutgoingWithoutRotate}>
|
|
<Icon
|
|
{...this.props}
|
|
name="bolt"
|
|
size={16}
|
|
type="font-awesome"
|
|
color={BlueApp.settings.outgoingForegroundColor}
|
|
iconStyle={{ left: 0, marginTop: 6 }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionOffchainIncomingIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ballIncomingWithoutRotate}>
|
|
<Icon
|
|
{...this.props}
|
|
name="bolt"
|
|
size={16}
|
|
type="font-awesome"
|
|
color={BlueApp.settings.incomingForegroundColor}
|
|
iconStyle={{ left: 0, marginTop: 6 }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueTransactionOutgoingIcon extends Component {
|
|
render() {
|
|
return (
|
|
<View {...this.props}>
|
|
<View style={stylesBlueIcon.boxIncoming}>
|
|
<View style={stylesBlueIcon.ballOutgoing}>
|
|
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color={BlueApp.settings.outgoingForegroundColor} />
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
export class BlueReceiveButtonIcon extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity {...this.props}>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
minWidth: 130,
|
|
backgroundColor: BlueApp.settings.buttonBackgroundColor,
|
|
}}
|
|
>
|
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
|
|
<View
|
|
style={{
|
|
minWidth: 30,
|
|
minHeight: 30,
|
|
left: 5,
|
|
backgroundColor: 'transparent',
|
|
transform: [{ rotate: '-45deg' }],
|
|
alignItems: 'center',
|
|
marginBottom: -11,
|
|
}}
|
|
>
|
|
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color={BlueApp.settings.buttonAlternativeTextColor} />
|
|
</View>
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.buttonAlternativeTextColor,
|
|
fontSize: (isIpad && 10) || 16,
|
|
fontWeight: '500',
|
|
left: 5,
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
{loc.receive.header}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueScanButton extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity {...this.props}>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
minWidth: 130,
|
|
backgroundColor: BlueApp.settings.buttonBackgroundColor,
|
|
}}
|
|
>
|
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
|
|
<View
|
|
style={{
|
|
minWidth: 30,
|
|
minHeight: 30,
|
|
backgroundColor: 'transparent',
|
|
alignItems: 'center',
|
|
marginBottom: -15,
|
|
}}
|
|
>
|
|
<Image source={require('./img/scan.png')} />
|
|
</View>
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.buttonAlternativeTextColor,
|
|
fontSize: (isIpad && 10) || 16,
|
|
fontWeight: '500',
|
|
left: 5,
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
{loc.send.details.scan}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueSendButtonIcon extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity {...this.props} testID={'SendButton'}>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
minWidth: 130,
|
|
backgroundColor: BlueApp.settings.buttonBackgroundColor,
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
|
|
<View
|
|
style={{
|
|
minWidth: 30,
|
|
minHeight: 30,
|
|
left: 5,
|
|
backgroundColor: 'transparent',
|
|
transform: [{ rotate: '225deg' }],
|
|
marginBottom: 11,
|
|
}}
|
|
>
|
|
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color={BlueApp.settings.buttonAlternativeTextColor} />
|
|
</View>
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.buttonAlternativeTextColor,
|
|
fontSize: (isIpad && 10) || 16,
|
|
fontWeight: '500',
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
{loc.send.header}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class ManageFundsBigButton extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity {...this.props}>
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
width: 168,
|
|
backgroundColor: BlueApp.settings.buttonBackgroundColor,
|
|
}}
|
|
>
|
|
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
|
|
<View
|
|
style={{
|
|
minWidth: 30,
|
|
minHeight: 30,
|
|
right: 5,
|
|
backgroundColor: 'transparent',
|
|
transform: [{ rotate: '90deg' }],
|
|
}}
|
|
>
|
|
<Icon {...this.props} name="link" size={16} type="font-awesome" color={BlueApp.settings.buttonAlternativeTextColor} />
|
|
</View>
|
|
<Text
|
|
style={{
|
|
color: BlueApp.settings.buttonAlternativeTextColor,
|
|
fontSize: (isIpad && 10) || 16,
|
|
fontWeight: '500',
|
|
backgroundColor: 'transparent',
|
|
}}
|
|
>
|
|
{loc.lnd.title}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class NewWalletPanel extends Component {
|
|
render() {
|
|
return (
|
|
<TouchableOpacity testID="CreateAWallet" {...this.props} onPress={this.props.onPress} style={{ marginVertical: 17 }}>
|
|
<LinearGradient
|
|
colors={WalletGradient.createWallet}
|
|
style={{
|
|
paddingHorizontal: 24,
|
|
paddingVertical: 16,
|
|
borderRadius: 10,
|
|
minHeight: Platform.OS === 'ios' ? 164 : 181,
|
|
justifyContent: 'center',
|
|
alignItems: 'flex-start',
|
|
}}
|
|
>
|
|
<Text
|
|
style={{
|
|
fontWeight: '600',
|
|
fontSize: 24,
|
|
color: BlueApp.settings.foregroundColor,
|
|
marginBottom: 4,
|
|
}}
|
|
>
|
|
{loc.wallets.list.create_a_wallet}
|
|
</Text>
|
|
<Text
|
|
style={{
|
|
fontSize: 13,
|
|
color: BlueApp.settings.alternativeTextColor,
|
|
}}
|
|
>
|
|
{loc.wallets.list.create_a_wallet1}
|
|
</Text>
|
|
<Text
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontSize: 13,
|
|
color: BlueApp.settings.alternativeTextColor,
|
|
}}
|
|
>
|
|
{loc.wallets.list.create_a_wallet2}
|
|
</Text>
|
|
<View style={{ marginTop: 12, backgroundColor: '#007AFF', paddingHorizontal: 32, paddingVertical: 12, borderRadius: 8 }}>
|
|
<Text style={{ color: BlueApp.settings.brandingColor, fontWeight: '500' }}>{loc.wallets.list.create_a_button}</Text>
|
|
</View>
|
|
</LinearGradient>
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
}
|
|
|
|
export const BlueTransactionListItem = ({ item, itemPriceUnit = BitcoinUnit.BTC, shouldRefresh }) => {
|
|
const [transactionTimeToReadable, setTransactionTimeToReadable] = useState('...');
|
|
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
|
|
|
|
useEffect(() => {
|
|
const transactionTimeToReadable = loc.transactionTimeToReadable(item.received);
|
|
return setTransactionTimeToReadable(transactionTimeToReadable);
|
|
}, [item, itemPriceUnit, shouldRefresh]);
|
|
|
|
const txMemo = () => {
|
|
if (BlueApp.tx_metadata[item.hash] && BlueApp.tx_metadata[item.hash]['memo']) {
|
|
return BlueApp.tx_metadata[item.hash]['memo'];
|
|
}
|
|
return '';
|
|
};
|
|
|
|
const rowTitle = () => {
|
|
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 loc.formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
|
} else if (invoiceExpiration < now) {
|
|
if (item.ispaid) {
|
|
return loc.formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
|
} else {
|
|
return loc.lnd.expired;
|
|
}
|
|
}
|
|
} else {
|
|
return loc.formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
|
}
|
|
};
|
|
|
|
const rowTitleStyle = () => {
|
|
let color = BlueApp.settings.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 = BlueApp.settings.successColor;
|
|
} else if (invoiceExpiration < now) {
|
|
if (item.ispaid) {
|
|
color = BlueApp.settings.successColor;
|
|
} else {
|
|
color = '#9AA0AA';
|
|
}
|
|
}
|
|
} else if (item.value / 100000000 < 0) {
|
|
color = BlueApp.settings.foregroundColor;
|
|
}
|
|
|
|
return {
|
|
fontWeight: '600',
|
|
fontSize: 14,
|
|
color: color,
|
|
textAlign: 'right',
|
|
width: 96,
|
|
};
|
|
};
|
|
|
|
const avatar = () => {
|
|
// is it lightning refill tx?
|
|
if (item.category === 'receive' && item.confirmations < 3) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionPendingIcon />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (item.type && item.type === 'bitcoind_tx') {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOnchainIcon />
|
|
</View>
|
|
);
|
|
}
|
|
if (item.type === 'paid_invoice') {
|
|
// is it lightning offchain payment?
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOffchainIcon />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionExpiredIcon />
|
|
</View>
|
|
);
|
|
}
|
|
} else {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOffchainIncomingIcon />
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!item.confirmations) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionPendingIcon />
|
|
</View>
|
|
);
|
|
} else if (item.value < 0) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOutgoingIcon />
|
|
</View>
|
|
);
|
|
} else {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionIncomingIcon />
|
|
</View>
|
|
);
|
|
}
|
|
};
|
|
|
|
const subtitle = () => {
|
|
return (item.confirmations < 7 ? loc.transactions.list.conf + ': ' + item.confirmations + ' ' : '') + txMemo() + (item.memo || '');
|
|
};
|
|
|
|
const onPress = () => {
|
|
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 = BlueApp.getWallets().filter(wallet => {
|
|
if (typeof wallet === 'object') {
|
|
if (wallet.hasOwnProperty('secret')) {
|
|
return wallet.getSecret() === item.fromWallet;
|
|
}
|
|
}
|
|
});
|
|
if (lightningWallet.length === 1) {
|
|
NavigationService.navigate('LNDViewInvoice', {
|
|
invoice: item,
|
|
fromWallet: lightningWallet[0],
|
|
isModal: false,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const onLongPress = () => {
|
|
if (subtitleNumberOfLines === 1) {
|
|
setSubtitleNumberOfLines(0);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<BlueListItem
|
|
leftAvatar={avatar()}
|
|
title={transactionTimeToReadable}
|
|
titleNumberOfLines={subtitleNumberOfLines}
|
|
subtitle={subtitle()}
|
|
subtitleProps={{ numberOfLines: subtitleNumberOfLines }}
|
|
onPress={onPress}
|
|
onLongPress={onLongPress}
|
|
chevron={false}
|
|
Component={TouchableOpacity}
|
|
rightTitle={rowTitle()}
|
|
rightTitleStyle={rowTitleStyle()}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export class BlueListTransactionItem extends Component {
|
|
static propTypes = {
|
|
item: PropTypes.shape().isRequired,
|
|
itemPriceUnit: PropTypes.string,
|
|
};
|
|
|
|
static defaultProps = {
|
|
itemPriceUnit: BitcoinUnit.BTC,
|
|
};
|
|
|
|
txMemo = () => {
|
|
if (BlueApp.tx_metadata[this.props.item.hash] && BlueApp.tx_metadata[this.props.item.hash]['memo']) {
|
|
return BlueApp.tx_metadata[this.props.item.hash]['memo'];
|
|
}
|
|
return '';
|
|
};
|
|
|
|
rowTitle = () => {
|
|
const item = this.props.item;
|
|
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 loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
|
} else if (invoiceExpiration < now) {
|
|
if (item.ispaid) {
|
|
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
|
} else {
|
|
return loc.lnd.expired;
|
|
}
|
|
}
|
|
} else {
|
|
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
|
}
|
|
};
|
|
|
|
rowTitleStyle = () => {
|
|
const item = this.props.item;
|
|
let color = '#37c0a1';
|
|
|
|
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 = '#37c0a1';
|
|
} else if (invoiceExpiration < now) {
|
|
if (item.ispaid) {
|
|
color = '#37c0a1';
|
|
} else {
|
|
color = '#9AA0AA';
|
|
}
|
|
}
|
|
} else if (item.value / 100000000 < 0) {
|
|
color = BlueApp.settings.foregroundColor;
|
|
}
|
|
|
|
return {
|
|
fontWeight: '600',
|
|
fontSize: 14,
|
|
color: color,
|
|
};
|
|
};
|
|
|
|
avatar = () => {
|
|
// is it lightning refill tx?
|
|
if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionPendingIcon />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOnchainIcon />
|
|
</View>
|
|
);
|
|
}
|
|
if (this.props.item.type === 'paid_invoice') {
|
|
// is it lightning offchain payment?
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOffchainIcon />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (this.props.item.type === 'user_invoice' || this.props.item.type === 'payment_request') {
|
|
if (!this.props.item.ispaid) {
|
|
const currentDate = new Date();
|
|
const now = (currentDate.getTime() / 1000) | 0;
|
|
const invoiceExpiration = this.props.item.timestamp + this.props.item.expire_time;
|
|
if (invoiceExpiration < now) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionExpiredIcon />
|
|
</View>
|
|
);
|
|
}
|
|
} else {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOffchainIncomingIcon />
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!this.props.item.confirmations) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionPendingIcon />
|
|
</View>
|
|
);
|
|
} else if (this.props.item.value < 0) {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionOutgoingIcon />
|
|
</View>
|
|
);
|
|
} else {
|
|
return (
|
|
<View style={{ width: 25 }}>
|
|
<BlueTransactionIncomingIcon />
|
|
</View>
|
|
);
|
|
}
|
|
};
|
|
|
|
subtitle = () => {
|
|
return (
|
|
(this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
|
|
this.txMemo() +
|
|
(this.props.item.memo || '')
|
|
);
|
|
};
|
|
|
|
onPress = () => {
|
|
if (this.props.item.hash) {
|
|
NavigationService.navigate('TransactionStatus', { hash: this.props.item.hash });
|
|
} else if (
|
|
this.props.item.type === 'user_invoice' ||
|
|
this.props.item.type === 'payment_request' ||
|
|
this.props.item.type === 'paid_invoice'
|
|
) {
|
|
const lightningWallet = BlueApp.getWallets().filter(wallet => {
|
|
if (typeof wallet === 'object') {
|
|
if (wallet.hasOwnProperty('secret')) {
|
|
return wallet.getSecret() === this.props.item.fromWallet;
|
|
}
|
|
}
|
|
});
|
|
NavigationService.navigate('LNDViewInvoice', {
|
|
invoice: this.props.item,
|
|
fromWallet: lightningWallet[0],
|
|
isModal: false,
|
|
});
|
|
}
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<BlueListItem
|
|
avatar={this.avatar()}
|
|
title={loc.transactionTimeToReadable(this.props.item.received)}
|
|
subtitle={this.subtitle()}
|
|
onPress={this.onPress}
|
|
hideChevron
|
|
rightTitle={this.rowTitle()}
|
|
rightTitleStyle={this.rowTitleStyle()}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
const sliderWidth = width * 1;
|
|
const itemWidth = width * 0.82;
|
|
const sliderHeight = 190;
|
|
|
|
export class WalletsCarousel extends Component {
|
|
walletsCarousel = React.createRef();
|
|
|
|
_renderItem = ({ item, index }) => {
|
|
let scaleValue = new Animated.Value(1.0);
|
|
let props = { duration: 50 };
|
|
if (Platform.OS === 'android') {
|
|
props['useNativeDriver'] = true;
|
|
}
|
|
this.onPressedIn = () => {
|
|
props.toValue = 0.9;
|
|
Animated.spring(scaleValue, props).start();
|
|
};
|
|
this.onPressedOut = () => {
|
|
props.toValue = 1.0;
|
|
Animated.spring(scaleValue, props).start();
|
|
};
|
|
|
|
if (!item) {
|
|
return (
|
|
<NewWalletPanel
|
|
onPress={() => {
|
|
this.onPressedOut();
|
|
this.props.onPress(index);
|
|
this.onPressedOut();
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (item.type === PlaceholderWallet.type) {
|
|
return (
|
|
<Animated.View
|
|
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
|
shadowOpacity={40 / 100}
|
|
shadowOffset={{ width: 0, height: 0 }}
|
|
shadowRadius={5}
|
|
>
|
|
<TouchableWithoutFeedback
|
|
onPressIn={item.getIsFailure() ? this.onPressedIn : null}
|
|
onPressOut={item.getIsFailure() ? this.onPressedOut : null}
|
|
onPress={() => {
|
|
if (item.getIsFailure()) {
|
|
this.onPressedOut();
|
|
this.props.onPress(index);
|
|
this.onPressedOut();
|
|
}
|
|
}}
|
|
>
|
|
<LinearGradient
|
|
shadowColor={BlueApp.settings.shadowColor}
|
|
colors={WalletGradient.gradientsFor(item.type)}
|
|
style={{
|
|
padding: 15,
|
|
borderRadius: 10,
|
|
minHeight: 164,
|
|
elevation: 5,
|
|
}}
|
|
>
|
|
<Image
|
|
source={require('./img/btc-shape.png')}
|
|
style={{
|
|
width: 99,
|
|
height: 94,
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
right: 0,
|
|
}}
|
|
/>
|
|
<Text style={{ backgroundColor: 'transparent' }} />
|
|
<Text
|
|
numberOfLines={1}
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontSize: 19,
|
|
color: BlueApp.settings.inverseForegroundColor,
|
|
}}
|
|
>
|
|
{item.getLabel()}
|
|
</Text>
|
|
{item.getIsFailure() ? (
|
|
<Text
|
|
numberOfLines={0}
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontSize: 19,
|
|
marginTop: 40,
|
|
color: BlueApp.settings.inverseForegroundColor,
|
|
}}
|
|
>
|
|
An error was encountered when attempting to import this wallet.
|
|
</Text>
|
|
) : (
|
|
<ActivityIndicator style={{ marginTop: 40 }} />
|
|
)}
|
|
</LinearGradient>
|
|
</TouchableWithoutFeedback>
|
|
</Animated.View>
|
|
);
|
|
} else {
|
|
return (
|
|
<Animated.View
|
|
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
|
|
shadowOpacity={40 / 100}
|
|
shadowOffset={{ width: 0, height: 0 }}
|
|
shadowRadius={5}
|
|
>
|
|
<TouchableWithoutFeedback
|
|
testID={item.getLabel()}
|
|
onPressIn={this.onPressedIn}
|
|
onPressOut={this.onPressedOut}
|
|
onLongPress={this.props.handleLongPress}
|
|
onPress={() => {
|
|
this.onPressedOut();
|
|
this.props.onPress(index);
|
|
this.onPressedOut();
|
|
}}
|
|
>
|
|
<LinearGradient
|
|
shadowColor={BlueApp.settings.shadowColor}
|
|
colors={WalletGradient.gradientsFor(item.type)}
|
|
style={{
|
|
padding: 15,
|
|
borderRadius: 10,
|
|
minHeight: 164,
|
|
elevation: 5,
|
|
}}
|
|
>
|
|
<Image
|
|
source={(LightningCustodianWallet.type === item.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')}
|
|
style={{
|
|
width: 99,
|
|
height: 94,
|
|
position: 'absolute',
|
|
bottom: 0,
|
|
right: 0,
|
|
}}
|
|
/>
|
|
|
|
<Text style={{ backgroundColor: 'transparent' }} />
|
|
<Text
|
|
numberOfLines={1}
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontSize: 19,
|
|
color: BlueApp.settings.inverseForegroundColor,
|
|
}}
|
|
>
|
|
{item.getLabel()}
|
|
</Text>
|
|
{item.hideBalance ? (
|
|
<BluePrivateBalance />
|
|
) : (
|
|
<Text
|
|
numberOfLines={1}
|
|
adjustsFontSizeToFit
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontWeight: 'bold',
|
|
fontSize: 36,
|
|
color: BlueApp.settings.inverseForegroundColor,
|
|
}}
|
|
>
|
|
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
|
|
</Text>
|
|
)}
|
|
<Text style={{ backgroundColor: 'transparent' }} />
|
|
<Text
|
|
numberOfLines={1}
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontSize: 13,
|
|
color: BlueApp.settings.inverseForegroundColor,
|
|
}}
|
|
>
|
|
{loc.wallets.list.latest_transaction}
|
|
</Text>
|
|
<Text
|
|
numberOfLines={1}
|
|
style={{
|
|
backgroundColor: 'transparent',
|
|
fontWeight: 'bold',
|
|
fontSize: 16,
|
|
color: BlueApp.settings.inverseForegroundColor,
|
|
}}
|
|
>
|
|
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
|
|
</Text>
|
|
</LinearGradient>
|
|
</TouchableWithoutFeedback>
|
|
</Animated.View>
|
|
);
|
|
}
|
|
};
|
|
|
|
snapToItem = item => {
|
|
this.walletsCarousel.current.snapToItem(item);
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<Carousel
|
|
{...this.props}
|
|
ref={this.walletsCarousel}
|
|
renderItem={this._renderItem}
|
|
sliderWidth={sliderWidth}
|
|
sliderHeight={sliderHeight}
|
|
itemWidth={itemWidth}
|
|
inactiveSlideScale={1}
|
|
inactiveSlideOpacity={0.7}
|
|
contentContainerCustomStyle={{ left: -20 }}
|
|
onSnapToItem={this.onSnapToItem}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueAddressInput extends Component {
|
|
static propTypes = {
|
|
isLoading: PropTypes.bool,
|
|
onChangeText: PropTypes.func,
|
|
onBarScanned: PropTypes.func.isRequired,
|
|
launchedBy: PropTypes.string.isRequired,
|
|
address: PropTypes.string,
|
|
placeholder: PropTypes.string,
|
|
};
|
|
|
|
static defaultProps = {
|
|
isLoading: false,
|
|
address: '',
|
|
placeholder: loc.send.details.address,
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<View
|
|
style={{
|
|
flexDirection: 'row',
|
|
borderColor: BlueApp.settings.inputBorderColor,
|
|
borderBottomColor: BlueApp.settings.inputBorderColor,
|
|
borderWidth: 1.0,
|
|
borderBottomWidth: 0.5,
|
|
backgroundColor: BlueApp.settings.inputBackgroundColor,
|
|
minHeight: 44,
|
|
height: 44,
|
|
marginHorizontal: 20,
|
|
alignItems: 'center',
|
|
marginVertical: 8,
|
|
borderRadius: 4,
|
|
}}
|
|
>
|
|
<TextInput
|
|
testID={'AddressInput'}
|
|
onChangeText={text => {
|
|
this.props.onChangeText(text);
|
|
}}
|
|
placeholder={this.props.placeholder}
|
|
numberOfLines={1}
|
|
value={this.props.address}
|
|
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
|
editable={!this.props.isLoading}
|
|
onSubmitEditing={Keyboard.dismiss}
|
|
{...this.props}
|
|
/>
|
|
<TouchableOpacity
|
|
disabled={this.props.isLoading}
|
|
onPress={() => {
|
|
NavigationService.navigate('ScanQRCode', { onBarScanned: this.props.onBarScanned, launchedBy: this.props.launchedBy });
|
|
Keyboard.dismiss();
|
|
}}
|
|
style={{
|
|
height: 36,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
backgroundColor: '#9AA0AA',
|
|
borderRadius: 4,
|
|
paddingVertical: 4,
|
|
paddingHorizontal: 8,
|
|
marginHorizontal: 4,
|
|
}}
|
|
>
|
|
<Icon name="qrcode" size={22} type="font-awesome" color={BlueApp.settings.inverseForegroundColor} />
|
|
<Text style={{ marginLeft: 4, color: BlueApp.settings.inverseForegroundColor }}>{loc.send.details.scan}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueReplaceFeeSuggestions extends Component {
|
|
static propTypes = {
|
|
onFeeSelected: PropTypes.func.isRequired,
|
|
transactionMinimum: PropTypes.number.isRequired,
|
|
};
|
|
|
|
static defaultProps = {
|
|
onFeeSelected: undefined,
|
|
transactionMinimum: 1,
|
|
};
|
|
|
|
state = { networkFees: undefined, selectedFeeType: NetworkTransactionFeeType.FAST, customFeeValue: 0 };
|
|
|
|
async componentDidMount() {
|
|
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(this.state.customFeeValue);
|
|
}
|
|
};
|
|
|
|
onCustomFeeTextChange = customFee => {
|
|
this.setState({ customFeeValue: Number(customFee), selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => {
|
|
this.onFeeSelected(NetworkTransactionFeeType.CUSTOM);
|
|
});
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<View>
|
|
{this.state.networkFees && (
|
|
<>
|
|
<BlueText>Suggestions</BlueText>
|
|
<BlueListItem
|
|
onPress={() => this.onFeeSelected(NetworkTransactionFeeType.FAST)}
|
|
containerStyle={{ paddingHorizontal: 0, marginHorizontal: 0 }}
|
|
bottomDivider={false}
|
|
title={'Fast'}
|
|
rightTitle={`${this.state.networkFees.fastestFee} sat/b`}
|
|
rightTitleStyle={{ fontSize: 13, color: BlueApp.settings.alternativeTextColor }}
|
|
{...(this.state.selectedFeeType === NetworkTransactionFeeType.FAST
|
|
? { rightIcon: <Icon name="check" type="octaicon" color="#0070FF" /> }
|
|
: { hideChevron: true })}
|
|
/>
|
|
<BlueListItem
|
|
onPress={() => this.onFeeSelected(NetworkTransactionFeeType.MEDIUM)}
|
|
containerStyle={{ paddingHorizontal: 0, marginHorizontal: 0 }}
|
|
bottomDivider={false}
|
|
title={'Medium'}
|
|
rightTitle={`${this.state.networkFees.mediumFee} sat/b`}
|
|
rightTitleStyle={{ fontSize: 13, color: BlueApp.settings.alternativeTextColor }}
|
|
{...(this.state.selectedFeeType === NetworkTransactionFeeType.MEDIUM
|
|
? { rightIcon: <Icon name="check" type="octaicon" color="#0070FF" /> }
|
|
: { hideChevron: true })}
|
|
/>
|
|
<BlueListItem
|
|
onPress={() => this.onFeeSelected(NetworkTransactionFeeType.SLOW)}
|
|
containerStyle={{ paddingHorizontal: 0, marginHorizontal: 0 }}
|
|
bottomDivider={false}
|
|
title={'Slow'}
|
|
rightTitle={`${this.state.networkFees.slowFee} sat/b`}
|
|
rightTitleStyle={{ fontSize: 13, color: BlueApp.settings.alternativeTextColor }}
|
|
{...(this.state.selectedFeeType === NetworkTransactionFeeType.SLOW
|
|
? { rightIcon: <Icon name="check" type="octaicon" color="#0070FF" /> }
|
|
: { hideChevron: true })}
|
|
/>
|
|
</>
|
|
)}
|
|
<TouchableOpacity onPress={() => this.customTextInput.focus()}>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginHorizontal: 0, alignItems: 'center' }}>
|
|
<Text style={{ color: BlueApp.settings.foregroundColor, fontSize: 16, fontWeight: '500' }}>Custom</Text>
|
|
<View
|
|
style={{
|
|
flexDirection: 'row',
|
|
minHeight: 44,
|
|
height: 44,
|
|
minWidth: 48,
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-end',
|
|
marginVertical: 8,
|
|
}}
|
|
>
|
|
<TextInput
|
|
onChangeText={this.onCustomFeeTextChange}
|
|
keyboardType={'numeric'}
|
|
value={this.state.customFeeValue}
|
|
ref={ref => (this.customTextInput = ref)}
|
|
maxLength={9}
|
|
style={{
|
|
borderColor: '#d2d2d2',
|
|
borderBottomColor: '#d2d2d2',
|
|
borderWidth: 1.0,
|
|
borderBottomWidth: 0.5,
|
|
borderRadius: 4,
|
|
minHeight: 33,
|
|
maxWidth: 100,
|
|
minWidth: 44,
|
|
backgroundColor: '#f5f5f5',
|
|
textAlign: 'right',
|
|
}}
|
|
onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)}
|
|
defaultValue={`${this.props.transactionMinimum}`}
|
|
placeholder="Custom sat/b"
|
|
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
|
/>
|
|
<Text style={{ color: BlueApp.settings.alternativeTextColor, marginHorizontal: 8 }}>sat/b</Text>
|
|
{this.state.selectedFeeType === NetworkTransactionFeeType.CUSTOM && <Icon name="check" type="octaicon" color="#0070FF" />}
|
|
</View>
|
|
<BlueDismissKeyboardInputAccessory />
|
|
</View>
|
|
</TouchableOpacity>
|
|
<BlueText style={{ color: BlueApp.settings.alternativeTextColor }}>
|
|
The total fee rate (satoshi per byte) you want to pay should be higher than {this.props.transactionMinimum} sat/byte
|
|
</BlueText>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BlueBitcoinAmount extends Component {
|
|
static propTypes = {
|
|
isLoading: PropTypes.bool,
|
|
amount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
onChangeText: PropTypes.func,
|
|
disabled: PropTypes.bool,
|
|
unit: PropTypes.string,
|
|
};
|
|
|
|
static defaultProps = {
|
|
unit: BitcoinUnit.BTC,
|
|
};
|
|
|
|
render() {
|
|
const amount = this.props.amount || 0;
|
|
let localCurrency = loc.formatBalanceWithoutSuffix(amount, BitcoinUnit.LOCAL_CURRENCY, false);
|
|
if (this.props.unit === BitcoinUnit.BTC) {
|
|
let sat = new BigNumber(amount);
|
|
sat = sat.multipliedBy(100000000).toString();
|
|
localCurrency = loc.formatBalanceWithoutSuffix(sat, BitcoinUnit.LOCAL_CURRENCY, false);
|
|
} else {
|
|
localCurrency = loc.formatBalanceWithoutSuffix(amount.toString(), BitcoinUnit.LOCAL_CURRENCY, false);
|
|
}
|
|
if (amount === BitcoinUnit.MAX) localCurrency = ''; // we dont want to display NaN
|
|
return (
|
|
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
|
|
<View>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 2 }}>
|
|
<TextInput
|
|
{...this.props}
|
|
testID={'BitcoinAmountInput'}
|
|
keyboardType="numeric"
|
|
onChangeText={text => {
|
|
text = text.trim();
|
|
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.props.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.props.unit !== BitcoinUnit.BTC) {
|
|
text = text.replace(/[^0-9.]/g, '');
|
|
}
|
|
this.props.onChangeText(text);
|
|
}}
|
|
onBlur={() => {
|
|
if (this.props.onBlur) this.props.onBlur();
|
|
}}
|
|
onFocus={() => {
|
|
if (this.props.onFocus) this.props.onFocus();
|
|
}}
|
|
placeholder="0"
|
|
maxLength={10}
|
|
ref={textInput => (this.textInput = textInput)}
|
|
editable={!this.props.isLoading && !this.props.disabled}
|
|
value={amount}
|
|
placeholderTextColor={this.props.disabled ? BlueApp.settings.buttonDisabledTextColor : BlueApp.settings.alternativeTextColor2}
|
|
style={{
|
|
color: this.props.disabled ? BlueApp.settings.buttonDisabledTextColor : BlueApp.settings.alternativeTextColor2,
|
|
fontSize: 36,
|
|
fontWeight: '600',
|
|
}}
|
|
/>
|
|
<Text
|
|
style={{
|
|
color: this.props.disabled ? BlueApp.settings.buttonDisabledTextColor : BlueApp.settings.alternativeTextColor2,
|
|
fontSize: 16,
|
|
marginHorizontal: 4,
|
|
paddingBottom: 6,
|
|
fontWeight: '600',
|
|
alignSelf: 'flex-end',
|
|
}}
|
|
>
|
|
{' ' + this.props.unit}
|
|
</Text>
|
|
</View>
|
|
<View style={{ alignItems: 'center', marginBottom: 22, marginTop: 4 }}>
|
|
<Text style={{ fontSize: 18, color: '#d4d4d4', fontWeight: '600' }}>{localCurrency}</Text>
|
|
</View>
|
|
</View>
|
|
</TouchableWithoutFeedback>
|
|
);
|
|
}
|
|
}
|
|
|
|
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 (
|
|
<View style={mergedStyles}>
|
|
<Icon name="check" size={50} type="font-awesome" color="#0f5cc0" />
|
|
</View>
|
|
);
|
|
}
|