@ -1,7 +1,6 @@
/* eslint react/prop-types: 0 */
/* global alert */
/** @type {AppStorage} */
import React , { Component } from 'react' ;
import React , { Component , useEffect , useState } from 'react' ;
import Ionicons from 'react-native-vector-icons/Ionicons' ;
import PropTypes from 'prop-types' ;
import { Icon , FormLabel , FormInput , Text , Header , List , ListItem } from 'react-native-elements' ;
@ -11,6 +10,7 @@ import {
Animated ,
ActivityIndicator ,
View ,
KeyboardAvoidingView ,
UIManager ,
StyleSheet ,
Dimensions ,
@ -27,9 +27,12 @@ import { LightningCustodianWallet } from './class';
import Carousel from 'react-native-snap-carousel' ;
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' ) ;
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' ) ;
@ -98,7 +101,6 @@ export class BitcoinButton extends Component {
borderRadius : 5 ,
backgroundColor : ( this . props . active && BlueApp . settings . hdbackgroundColor ) || BlueApp . settings . brandingColor ,
// eslint-disable-next-line
width : this . props . style . width ,
minWidth : this . props . style . width ,
// eslint-disable-next-line
minHeight : this . props . style . height ,
@ -136,7 +138,6 @@ export class LightningButton extends Component {
borderRadius : 5 ,
backgroundColor : ( this . props . active && BlueApp . settings . lnbackgroundColor ) || BlueApp . settings . brandingColor ,
// eslint-disable-next-line
width : this . props . style . width ,
minWidth : this . props . style . width ,
// eslint-disable-next-line
minHeight : this . props . style . height ,
@ -157,6 +158,193 @@ export class LightningButton extends Component {
}
}
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 } ;
}
constructor ( props ) {
super ( props ) ;
this . state = { wallet : props . wallet , walletPreviousPreferredUnit : props . wallet . getPreferredBalanceUnit ( ) } ;
}
handleCopyPress = _ item => {
Clipboard . setString ( loc . formatBalance ( this . state . wallet . getBalance ( ) , this . state . wallet . getPreferredBalanceUnit ( ) ) . toString ( ) ) ;
} ;
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 ( ) }
< / T e x t >
{ 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
numberOfLines = { 1 }
adjustsFontSizeToFit
style = { {
backgroundColor : 'transparent' ,
fontWeight : 'bold' ,
fontSize : 36 ,
color : '#fff' ,
} }
>
{ loc . formatBalance ( this . state . wallet . getBalance ( ) , this . state . wallet . getPreferredBalanceUnit ( ) , true ) . toString ( ) }
< / T e x t >
) }
< / T o u c h a b l e O p a c i t y >
{ this . state . wallet . type === LightningCustodianWallet . type && (
< 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 }
< / T e x t >
< / V i e w >
< / T o u c h a b l e O p a c i t y >
) }
< / L i n e a r G r a d i e n t >
) ;
}
}
export class BlueButtonLink extends Component {
render ( ) {
return (
@ -204,10 +392,57 @@ export const BlueNavigationStyle = (navigation, withNavigationCloseButton = fals
headerBackTitle : null ,
} ) ;
export const BlueCopyToClipboardButton = ( { stringToCopy } ) => {
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' ) } / >
< / T o u c h a b l e O p a c i t y >
) ,
headerRight : withAdvancedOptionsMenuButton ? (
< TouchableOpacity style = { { minWidth : 40 , height : 40 , padding : 14 } } onPress = { advancedOptionsMenuButtonAction } >
< Icon size = { 22 } name = "kebab-horizontal" type = "octicon" color = { BlueApp . settings . foregroundColor } / >
< / T o u c h a b l e O p a c i t y >
) : 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" / >
< / V i e w >
) ,
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" / >
< / V i e w >
) ,
} ) ;
} ;
export const BlueCopyToClipboardButton = ( { stringToCopy , displayText = false } ) => {
return (
< TouchableOpacity { ... this . props } onPress = { ( ) => Clipboard . setString ( stringToCopy ) } >
< Text style = { { fontSize : 13 , fontWeight : '400' , color : '#68bbe1' } } > { loc . transactions . details . copy } < / T e x t >
< Text style = { { fontSize : 13 , fontWeight : '400' , color : '#68bbe1' } } > { displayText || loc . transactions . details . copy } < / T e x t >
< / T o u c h a b l e O p a c i t y >
) ;
} ;
@ -366,10 +601,6 @@ export class BlueFormMultiInput extends Component {
} ;
}
onSelectionChange = ( { nativeEvent : { selection , text } } ) => {
this . setState ( { selection : { start : selection . end , end : selection . end } } ) ;
} ;
render ( ) {
return (
< TextInput
@ -392,8 +623,6 @@ export class BlueFormMultiInput extends Component {
spellCheck = { false }
{ ... this . props }
selectTextOnFocus = { false }
onSelectionChange = { this . onSelectionChange }
selection = { this . state . selection }
keyboardType = { Platform . OS === 'android' ? 'visible-password' : 'default' }
/ >
) ;
@ -571,16 +800,71 @@ export class BlueUseAllFundsButton extends Component {
} ;
render ( ) {
return (
< InputAccessoryView nativeID = { BlueUseAllFundsButton . InputAccessoryViewID } >
< View style = { { flex : 1 , flexDirection : 'row' , justifyContent : 'space-between' , alignItems : 'center' } } >
< Text style = { { color : BlueApp . settings . alternativeTextColor , fontSize : 16 , marginHorizontal : 8 } } >
Total : { this . props . wallet . getBalance ( ) } { BitcoinUnit . BTC }
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 :
< / T e x t >
< BlueButtonLink title = "Use All" onPress = { this . props . onUseAllPressed } / >
{ 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 }
< / T e x t >
) }
< / V i e w >
< / I n p u t A c c e s s o r y V i e w >
< 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 ( ) }
/ >
< / V i e w >
< / V i e w >
) ;
if ( Platform . OS === 'ios' ) {
return < InputAccessoryView nativeID = { BlueUseAllFundsButton . InputAccessoryViewID } > { inputView } < / I n p u t A c c e s s o r y V i e w > ;
} else {
return < KeyboardAvoidingView style = { { height : 44 } } > { inputView } < / K e y b o a r d A v o i d i n g V i e w > ;
}
}
}
@ -600,13 +884,47 @@ export class BlueDismissKeyboardInputAccessory extends Component {
alignItems : 'center' ,
} }
>
< BlueButtonLink title = "Done" onPress = { Keyboard . dismiss } / >
< BlueButtonLink title = "Done" onPress = { ( ) => Keyboard . dismiss ( ) } / >
< / V i e w >
< / I n p u t A c c e s s o r y V i e w >
) ;
}
}
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' ,
height : 44 ,
flex : 1 ,
flexDirection : 'row' ,
justifyContent : 'flex-end' ,
alignItems : 'center' ,
} }
>
< BlueButtonLink title = "Clear" onPress = { this . props . onClearTapped } / >
< BlueButtonLink title = "Paste" onPress = { this . onPasteTapped } / >
< BlueButtonLink title = "Done" onPress = { ( ) => Keyboard . dismiss ( ) } / >
< / V i e w >
) ;
if ( Platform . OS === 'ios' ) {
return < InputAccessoryView nativeID = { BlueDoneAndDismissKeyboardInputAccessory . InputAccessoryViewID } > { inputView } < / I n p u t A c c e s s o r y V i e w > ;
} else {
return < KeyboardAvoidingView style = { { height : 44 } } > { inputView } < / K e y b o a r d A v o i d i n g V i e w > ;
}
}
}
export class BlueLoading extends Component {
render ( ) {
return (
@ -740,11 +1058,11 @@ export class BlueTransactionPendingIcon extends Component {
< View style = { stylesBlueIcon . ball } >
< Icon
{ ... this . props }
name = "ellipsis-h "
name = "kebab-horizontal "
size = { 16 }
type = "font-awesome "
type = "octicon "
color = { BlueApp . settings . foregroundColor }
iconStyle = { { left : 0 , top : 6 } }
iconStyle = { { left : 0 , top : 7 } }
/ >
< / V i e w >
< / V i e w >
@ -1074,25 +1392,28 @@ export class NewWalletPanel extends Component {
}
}
export class BlueTransactionListItem extends Component {
static propTypes = {
item : PropTypes . shape ( ) . isRequired ,
itemPriceUnit : PropTypes . string ,
} ;
static defaultProps = {
itemPriceUnit : BitcoinUnit . BTC ,
export const BlueTransactionListItem = ( { item , itemPriceUnit = BitcoinUnit . BTC } ) => {
const calculateTimeLabel = ( ) => {
const transactionTimeToReadable = loc . transactionTimeToReadable ( item . received ) ;
return setTransactionTimeToReadable ( transactionTimeToReadable ) ;
} ;
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' ] ;
const interval = setInterval ( ( ) => calculateTimeLabel ( ) , 60000 ) ;
const [ transactionTimeToReadable , setTransactionTimeToReadable ] = useState ( '...' ) ;
const [ subtitleNumberOfLines , setSubtitleNumberOfLines ] = useState ( 1 ) ;
useEffect ( ( ) => {
calculateTimeLabel ( ) ;
return ( ) => clearInterval ( interval ) ;
} , [ item , itemPriceUnit ] ) ;
const txMemo = ( ) => {
if ( BlueApp . tx_metadata [ item . hash ] && BlueApp . tx_metadata [ item . hash ] [ 'memo' ] ) {
return BlueApp . tx_metadata [ item . hash ] [ 'memo' ] ;
}
return '' ;
} ;
rowTitle = ( ) => {
const item = this . props . item ;
const rowTitle = ( ) => {
if ( item . type === 'user_invoice' || item . type === 'payment_request' ) {
if ( isNaN ( item . value ) ) {
item . value = '0' ;
@ -1102,21 +1423,20 @@ export class BlueTransactionListItem extends Component {
const invoiceExpiration = item . timestamp + item . expire_time ;
if ( invoiceExpiration > now ) {
return loc . formatBalanceWithoutSuffix ( item . value && item . value , this . props . itemPriceUnit , true ) . toString ( ) ;
return loc . formatBalanceWithoutSuffix ( item . value && item . value , itemPriceUnit , true ) . toString ( ) ;
} else if ( invoiceExpiration < now ) {
if ( item . ispaid ) {
return loc . formatBalanceWithoutSuffix ( item . value && item . value , this . props . itemPriceUnit , true ) . toString ( ) ;
return loc . formatBalanceWithoutSuffix ( item . value && item . value , itemPriceUnit , true ) . toString ( ) ;
} else {
return loc . lnd . expired ;
}
}
} else {
return loc . formatBalanceWithoutSuffix ( item . value && item . value , this . props . itemPriceUnit , true ) . toString ( ) ;
return loc . formatBalanceWithoutSuffix ( item . value && item . value , itemPriceUnit , true ) . toString ( ) ;
}
} ;
rowTitleStyle = ( ) => {
const item = this . props . item ;
const rowTitleStyle = ( ) => {
let color = BlueApp . settings . successColor ;
if ( item . type === 'user_invoice' || item . type === 'payment_request' ) {
@ -1144,9 +1464,9 @@ export class BlueTransactionListItem extends Component {
} ;
} ;
avatar = ( ) => {
const avatar = ( ) => {
// is it lightning refill tx?
if ( this . props . item . category === 'receive' && this . props . item . confirmations < 3 ) {
if ( item . category === 'receive' && item . confirmations < 3 ) {
return (
< View style = { { width : 25 } } >
< BlueTransactionPendingIcon / >
@ -1154,14 +1474,14 @@ export class BlueTransactionListItem extends Component {
) ;
}
if ( this . props . item . type && this . props . item . type === 'bitcoind_tx' ) {
if ( item . type && item . type === 'bitcoind_tx' ) {
return (
< View style = { { width : 25 } } >
< BlueTransactionOnchainIcon / >
< / V i e w >
) ;
}
if ( this . props . item . type === 'paid_invoice' ) {
if ( item . type === 'paid_invoice' ) {
// is it lightning offchain payment?
return (
< View style = { { width : 25 } } >
@ -1170,11 +1490,11 @@ export class BlueTransactionListItem extends Component {
) ;
}
if ( this . props . item . type === 'user_invoice' || this . props . item . type === 'payment_request' ) {
if ( ! this . props . item . ispaid ) {
if ( item . type === 'user_invoice' || item . type === 'payment_request' ) {
if ( ! item . ispaid ) {
const currentDate = new Date ( ) ;
const now = ( currentDate . getTime ( ) / 1000 ) | 0 ;
const invoiceExpiration = this . props . item . timestamp + this . props . item . expire_time ;
const invoiceExpiration = item . timestamp + item . expire_time ;
if ( invoiceExpiration < now ) {
return (
< View style = { { width : 25 } } >
@ -1191,13 +1511,13 @@ export class BlueTransactionListItem extends Component {
}
}
if ( ! this . props . item . confirmations ) {
if ( ! item . confirmations ) {
return (
< View style = { { width : 25 } } >
< BlueTransactionPendingIcon / >
< / V i e w >
) ;
} else if ( this . props . item . value < 0 ) {
} else if ( item . value < 0 ) {
return (
< View style = { { width : 25 } } >
< BlueTransactionOutgoingIcon / >
@ -1212,32 +1532,24 @@ export class BlueTransactionListItem extends Component {
}
} ;
subtitle = ( ) => {
return (
( this . props . item . confirmations < 7 ? loc . transactions . list . conf + ': ' + this . props . item . confirmations + ' ' : '' ) +
this . txMemo ( ) +
( this . props . item . memo || '' )
) ;
const subtitle = ( ) => {
return ( item . confirmations < 7 ? loc . transactions . list . conf + ': ' + item . confirmations + ' ' : '' ) + txMemo ( ) + ( 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 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 ( ) === this . props . item . fromWallet ;
return wallet . getSecret ( ) === item . fromWallet ;
}
}
} ) ;
if ( lightningWallet . length === 1 ) {
NavigationService . navigate ( 'LNDViewInvoice' , {
invoice : this . props . item ,
invoice : item ,
fromWallet : lightningWallet [ 0 ] ,
isModal : false ,
} ) ;
@ -1245,25 +1557,32 @@ export class BlueTransactionListItem extends Component {
}
} ;
render ( ) {
return (
< BlueListItem
avatar = { this . avatar ( ) }
title = { loc . transactionTimeToReadable ( this . props . item . received ) }
subtitle = { this . subtitle ( ) }
onPress = { this . onPress }
badge = { {
value : 3 ,
textStyle : { color : 'orange' } ,
containerStyle : { marginTop : 0 } ,
} }
hideChevron
rightTitle = { this . rowTitle ( ) }
rightTitleStyle = { this . rowTitleStyle ( ) }
/ >
) ;
}
}
const onLongPress = ( ) => {
if ( subtitleNumberOfLines === 1 ) {
setSubtitleNumberOfLines ( 0 ) ;
}
} ;
return (
< BlueListItem
avatar = { avatar ( ) }
title = { transactionTimeToReadable }
titleNumberOfLines = { subtitleNumberOfLines }
subtitle = { subtitle ( ) }
subtitleNumberOfLines = { subtitleNumberOfLines }
onPress = { onPress }
onLongPress = { onLongPress }
badge = { {
value : 3 ,
textStyle : { color : 'orange' } ,
containerStyle : { marginTop : 0 } ,
} }
hideChevron
rightTitle = { rowTitle ( ) }
rightTitleStyle = { rowTitleStyle ( ) }
/ >
) ;
} ;
export class BlueListTransactionItem extends Component {
static propTypes = {
@ -1413,7 +1732,7 @@ export class BlueListTransactionItem extends Component {
onPress = ( ) => {
if ( this . props . item . hash ) {
NavigationService . navigate ( 'TransactionDetail s' , { hash : this . props . item . hash } ) ;
NavigationService . navigate ( 'TransactionStatu s' , { hash : this . props . item . hash } ) ;
} else if (
this . props . item . type === 'user_invoice' ||
this . props . item . type === 'payment_request' ||
@ -1544,18 +1863,22 @@ export class WalletsCarousel extends Component {
>
{ item . getLabel ( ) }
< / T e x t >
< Text
numberOfLines = { 1 }
adjustsFontSizeToFit
style = { {
backgroundColor : 'transparent' ,
fontWeight : 'bold' ,
fontSize : 36 ,
color : BlueApp . settings . inverseForegroundColor ,
} }
>
{ loc . formatBalance ( Number ( item . getBalance ( ) ) , item . getPreferredBalanceUnit ( ) , true ) }
< / T e x t >
{ 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 ) }
< / T e x t >
) }
< Text style = { { backgroundColor : 'transparent' } } / >
< Text
numberOfLines = { 1 }
@ -1651,43 +1974,21 @@ export class BlueAddressInput extends Component {
value = { this . props . address }
style = { { flex : 1 , marginHorizontal : 8 , minHeight : 33 } }
editable = { ! this . props . isLoading }
onSubmitEditing = { Keyboard . dismiss }
onSubmitEditing = { ( ) => Keyboard . dismiss ( ) }
{ ... this . props }
/ >
< TouchableOpacity
disabled = { this . props . isLoading }
onPress = { ( ) => {
NavigationService . navigate ( 'ScanQrAddress' , { onBarScanned : this . props . onBarScanned } ) ;
Keyboard . dismiss ( ) ;
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 ' ,
backgroundColor : '#9AA0AA' ,
borderRadius : 4 ,
paddingVertical : 4 ,
paddingHorizontal : 8 ,
@ -1695,13 +1996,139 @@ export class BlueAddressInput extends Component {
} }
>
< Icon name = "qrcode" size = { 22 } type = "font-awesome" color = { BlueApp . settings . inverseForegroundColor } / >
< Text style = { { color : BlueApp . settings . inverseForegroundColor } } > { loc . send . details . scan } < / T e x t >
< Text style = { { marginLeft : 4 , color : BlueApp . settings . inverseForegroundColor } } > { loc . send . details . scan } < / T e x t >
< / T o u c h a b l e O p a c i t y >
< / V i e w >
) ;
}
}
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 . halfHourFee ) ) ;
} else if ( selectedFeeType === NetworkTransactionFeeType . SLOW ) {
this . setState ( { selectedFeeType } , ( ) => this . props . onFeeSelected ( this . state . networkFees . hourFee ) ) ;
} 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 < / B l u e T e x t >
< TouchableOpacity onPress = { ( ) => this . onFeeSelected ( NetworkTransactionFeeType . FAST ) } >
< BlueListItem
title = { 'Fast' }
rightTitle = { ` ${ this . state . networkFees . fastestFee } sat/b ` }
{ ... ( this . state . selectedFeeType === NetworkTransactionFeeType . FAST
? { rightIcon : < Icon name = "check" type = "font-awesome" color = "#0c2550" / > }
: { hideChevron : true } ) }
/ >
< / T o u c h a b l e O p a c i t y >
< TouchableOpacity onPress = { ( ) => this . onFeeSelected ( NetworkTransactionFeeType . MEDIUM ) } >
< BlueListItem
title = { 'Medium' }
rightTitle = { ` ${ this . state . networkFees . halfHourFee } sat/b ` }
{ ... ( this . state . selectedFeeType === NetworkTransactionFeeType . MEDIUM
? { rightIcon : < Icon name = "check" type = "font-awesome" color = "#0c2550" / > }
: { hideChevron : true } ) }
/ >
< / T o u c h a b l e O p a c i t y >
< TouchableOpacity onPress = { ( ) => this . onFeeSelected ( NetworkTransactionFeeType . SLOW ) } >
< BlueListItem
title = { 'Slow' }
rightTitle = { ` ${ this . state . networkFees . hourFee } sat/b ` }
{ ... ( this . state . selectedFeeType === NetworkTransactionFeeType . SLOW
? { rightIcon : < Icon name = "check" type = "font-awesome" color = "#0c2550" / > }
: { hideChevron : true } ) }
/ >
< / T o u c h a b l e O p a c i t y >
< / >
) }
< TouchableOpacity onPress = { ( ) => this . customTextInput . focus ( ) } >
< View style = { { flexDirection : 'row' , justifyContent : 'space-between' , marginLeft : 18 , marginRight : 18 , alignItems : 'center' } } >
< Text style = { { color : BlueApp . settings . foregroundColor , fontSize : 16 , fontWeight : '500' } } > Custom < / T e x t >
< 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 < / T e x t >
{ this . state . selectedFeeType === NetworkTransactionFeeType . CUSTOM && < Icon name = "check" type = "font-awesome" color = "#0c2550" / > }
< / V i e w >
< BlueDismissKeyboardInputAccessory / >
< / V i e w >
< / T o u c h a b l e O p a c i t y >
< BlueText >
The total fee rate ( satoshi per byte ) you want to pay should be higher than { this . props . transactionMinimum } sat / byte
< / B l u e T e x t >
< / V i e w >
) ;
}
}
export class BlueBitcoinAmount extends Component {
static propTypes = {
isLoading : PropTypes . bool ,
@ -1725,22 +2152,41 @@ export class BlueBitcoinAmount extends Component {
} 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 : 16 } } >
< View style = { { flexDirection : 'row' , justifyContent : 'center' , paddingTop : 16 , paddingBottom : 2 } } >
< TextInput
{ ... this . props }
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 ) }
@ -1774,3 +2220,11 @@ export class BlueBitcoinAmount extends Component {
) ;
}
}
const styles = StyleSheet . create ( {
balanceBlur : {
height : 30 ,
width : 100 ,
marginRight : 16 ,
} ,
} ) ;