/* eslint react/prop-types: 0 */
/* global alert */
/** @type {AppStorage} */
import React, { Component } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types';
import { Icon, Button, FormLabel, FormInput, Text, Header, List, ListItem } from 'react-native-elements';
import {
TouchableOpacity,
TouchableWithoutFeedback,
Animated,
ActivityIndicator,
View,
UIManager,
StyleSheet,
Dimensions,
Image,
SafeAreaView,
Clipboard,
Platform,
LayoutAnimation,
TextInput,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { LightningCustodianWallet } from './class';
import Carousel from 'react-native-snap-carousel';
import DeviceInfo from 'react-native-device-info';
import { BitcoinUnit } from './models/bitcoinUnits';
import NavigationService from './NavigationService';
import ImagePicker from 'react-native-image-picker';
import WalletGradient from './class/walletGradient';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
let loc = require('./loc/');
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
} else {
isIpad = true;
}
export class BlueButton extends Component {
render() {
let backgroundColor = '#ccddf9';
let fontColor = '#0c2550';
if (this.props.hasOwnProperty('disabled') && this.props.disabled === true) {
backgroundColor = '#eef0f4';
fontColor = '#9aa0aa';
}
return (
{this.props.icon && }
{this.props.title && {this.props.title}}
);
}
}
export class BitcoinButton extends Component {
render() {
return (
{
// eslint-disable-next-line
if (this.props.onPress) this.props.onPress();
}}
>
{loc.wallets.add.bitcoin}
);
}
}
export class LightningButton extends Component {
render() {
return (
{
// eslint-disable-next-line
if (this.props.onPress) this.props.onPress();
}}
>
{loc.wallets.add.lightning}
);
}
}
export class BlueButtonLink extends Component {
render() {
// eslint-disable-next-line
this.props.buttonStyle = this.props.buttonStyle || {};
return (
);
}
}
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => ({
headerStyle: {
backgroundColor: '#FFFFFF',
borderBottomWidth: 0,
elevation: 0,
},
headerTitleStyle: {
fontWeight: '600',
color: '#0c2550',
},
headerTintColor: '#0c2550',
headerRight: withNavigationCloseButton ? (
navigation.goBack(null) : customCloseButtonFunction}
>
) : null,
headerBackTitle: null,
});
export const BlueCopyToClipboardButton = ({ stringToCopy }) => {
return (
Clipboard.setString(stringToCopy)}>
{loc.transactions.details.copy}
);
};
export class BlueCopyTextToClipboard extends Component {
static propTypes = {
text: PropTypes.string,
};
static defaultProps = {
text: '',
};
state = { hasTappedText: false };
constructor() {
super();
if (Platform.OS === 'android') UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}
copyToClipboard = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring, () => {
Clipboard.setString(this.props.text);
setTimeout(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
this.setState({ hasTappedText: false });
}, 1000);
});
this.setState({ hasTappedText: true });
};
render() {
return (
{this.props.text}
{this.state.hasTappedText && (
{loc.wallets.xpub.copiedToClipboard}
)}
);
}
}
const styleCopyTextToClipboard = StyleSheet.create({
address: {
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});
export class SafeBlueArea extends Component {
render() {
return (
);
}
}
export class BlueCard extends Component {
render() {
return ;
}
}
export class BlueText extends Component {
render() {
return (
);
}
}
export class BlueTextCentered extends Component {
render() {
return ;
}
}
export class BlueListItem extends Component {
render() {
return (
);
}
}
export class BlueFormLabel extends Component {
render() {
return ;
}
}
export class BlueFormInput extends Component {
render() {
return (
);
}
}
export class BlueFormMultiInput extends Component {
render() {
return (
);
}
}
export class BlueFormInputAddress extends Component {
render() {
return (
);
}
}
export class BlueHeader extends Component {
render() {
return (
);
}
}
export class BlueHeaderDefaultSub extends Component {
render() {
return (
{
// eslint-disable-next-line
this.props.leftText
}
}
rightComponent={
{
// eslint-disable-next-line
if (this.props.onClose) this.props.onClose();
}}
>
}
{...this.props}
/>
);
}
}
export class BlueHeaderDefaultMain extends Component {
render() {
return (
{
// eslint-disable-next-line
this.props.leftText
}
}
rightComponent={
}
/>
);
}
}
export class BlueSpacing extends Component {
render() {
return ;
}
}
export class BlueSpacing40 extends Component {
render() {
return ;
}
}
export class BlueSpacingVariable extends Component {
render() {
if (isIpad) {
return ;
} else {
return ;
}
}
}
export class is {
static ipad() {
return isIpad;
}
static iphone8() {
if (Platform.OS !== 'ios') {
return false;
}
return DeviceInfo.getDeviceId() === 'iPhone10,4';
}
}
export class BlueSpacing20 extends Component {
render() {
return ;
}
}
export class BlueList extends Component {
render() {
return (
);
}
}
export class BlueLoading extends Component {
render() {
return (
);
}
}
const stylesBlueIcon = StyleSheet.create({
container: {
flex: 1,
},
box1: {
position: 'relative',
top: 15,
},
box: {
alignSelf: 'flex-end',
paddingHorizontal: 14,
paddingTop: 8,
},
boxIncomming: {
position: 'relative',
},
ball: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#ccddf9',
},
ballIncomming: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#d2f8d6',
transform: [{ rotate: '-45deg' }],
},
ballIncommingWithoutRotate: {
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' }],
},
ballOutgoingWithoutRotate: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#f8d2d2',
},
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 (
);
}
}
export class BlueTransactionIncommingIcon extends Component {
render() {
return (
);
}
}
export class BlueTransactionPendingIcon extends Component {
render() {
return (
);
}
}
export class BlueTransactionExpiredIcon extends Component {
render() {
return (
);
}
}
export class BlueTransactionOnchainIcon extends Component {
render() {
return (
);
}
}
export class BlueTransactionOffchainIcon extends Component {
render() {
return (
);
}
}
export class BlueTransactionOffchainIncomingIcon extends Component {
render() {
return (
);
}
}
export class BlueTransactionOutgoingIcon extends Component {
render() {
return (
);
}
}
//
export class BlueReceiveButtonIcon extends Component {
render() {
return (
{loc.receive.header}
);
}
}
export class BlueSendButtonIcon extends Component {
render() {
return (
{loc.send.header}
);
}
}
export class ManageFundsBigButton extends Component {
render() {
return (
{loc.lnd.title}
);
}
}
export class BluePlusIconDimmed extends Component {
render() {
return (
);
}
}
export class NewWalletPanel extends Component {
constructor(props) {
super(props);
// WalletsCarousel.handleClick = props.handleClick // because cant access `this` from _renderItem
// eslint-disable-next-line
this.handleClick = props.onPress;
}
render() {
return (
{
if (this.handleClick) {
this.handleClick();
}
}}
style={{ marginVertical: 17 }}
>
{loc.wallets.list.create_a_wallet}
{loc.wallets.list.create_a_wallet1}
{loc.wallets.list.create_a_wallet2}
);
}
}
export class BlueTransactionListItem 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 = '#FF0000';
}
}
} else if (item.value / 100000000 < 0) {
color = BlueApp.settings.foregroundColor;
}
return {
fontWeight: '600',
fontSize: 16,
color: color,
};
};
avatar = () => {
// is it lightning refill tx?
if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
return (
);
}
if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
return (
);
}
if (this.props.item.type === 'paid_invoice') {
// is it lightning offchain payment?
return (
);
}
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 (
);
}
} else {
return (
);
}
}
if (!this.props.item.confirmations) {
return (
);
} else if (this.props.item.value < 0) {
return (
);
} else {
return (
);
}
};
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('TransactionDetails', { 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 = BlueAddressInput.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 (
);
}
}
const sliderWidth = width * 1;
const itemWidth = width * 0.82;
const sliderHeight = 190;
export class WalletsCarousel extends Component {
constructor(props) {
super(props);
// eslint-disable-next-line
WalletsCarousel.handleClick = props.handleClick; // because cant access `this` from _renderItem
WalletsCarousel.handleLongPress = props.handleLongPress;
// eslint-disable-next-line
this.onSnapToItem = props.onSnapToItem;
}
_renderItem({ item, index }) {
let scaleValue = new Animated.Value(1.0);
let props = { duration: 50 };
if (Platform.OS === 'android') {
props.push({ 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 (
{
if (WalletsCarousel.handleClick) {
WalletsCarousel.handleClick(index);
}
}}
/>
);
}
return (
{
if (WalletsCarousel.handleClick) {
WalletsCarousel.handleClick(index);
}
}}
>
{item.getLabel()}
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
{loc.wallets.list.latest_transaction}
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
);
}
render() {
return (
{
WalletsCarousel.carousel = c;
}}
renderItem={this._renderItem}
sliderWidth={sliderWidth}
sliderHeight={sliderHeight}
itemWidth={itemWidth}
inactiveSlideScale={1}
inactiveSlideOpacity={0.7}
contentContainerCustomStyle={{ left: -20 }}
onSnapToItem={index => {
if (this.onSnapToItem) {
this.onSnapToItem(index);
}
console.log('snapped to card #', index);
}}
/>
);
}
}
export class BlueAddressInput extends Component {
static propTypes = {
isLoading: PropTypes.bool,
onChangeText: PropTypes.func,
onBarScanned: PropTypes.func,
address: PropTypes.string,
};
static defaultProps = {
isLoading: false,
address: '',
};
render() {
return (
{
this.props.onChangeText(text);
}}
placeholder={loc.send.details.address}
numberOfLines={1}
value={this.props.address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.props.isLoading}
/>
{
ImagePicker.showImagePicker(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
customButtons: [{ name: 'navigatetoQRScan', title: 'Use Camera' }],
},
response => {
if (response.customButton) {
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned });
} else if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.props.onBarScanned(result);
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
},
);
}}
style={{
width: 75,
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#bebebe',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
{loc.send.details.scan}
);
}
}
export class BlueBitcoinAmount extends Component {
static propTypes = {
isLoading: PropTypes.bool,
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChangeText: PropTypes.func,
disabled: PropTypes.bool,
unit: PropTypes.string,
};
static defaultProps = {
unit: BitcoinUnit.BTC,
};
render() {
const amount = typeof this.props.amount === 'number' ? this.props.amount.toString() : this.props.amount;
return (
this.textInput.focus()}>
{
text = text.replace(',', '.');
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.';
}
this.props.onChangeText(text);
}}
placeholder="0"
maxLength={10}
ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled}
value={amount}
autoFocus={this.props.pointerEvents !== 'none'}
placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'}
style={{
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
fontSize: 36,
fontWeight: '600',
}}
/>
{' ' + this.props.unit}
{loc.formatBalance(
this.props.unit === BitcoinUnit.BTC ? amount || 0 : loc.formatBalanceWithoutSuffix(amount || 0, BitcoinUnit.BTC, false),
BitcoinUnit.LOCAL_CURRENCY,
false,
)}
);
}
}