2018-01-30 23:42:38 +01:00
import React , { Component } from 'react' ;
2020-05-17 03:41:38 +02:00
import { View , StatusBar , TouchableOpacity , Text , StyleSheet , InteractionManager , RefreshControl , SectionList , Alert } from 'react-native' ;
import { SafeBlueArea , WalletsCarousel , BlueHeaderDefaultMain , BlueTransactionListItem } from '../../BlueComponents' ;
2018-09-01 01:28:19 +02:00
import { Icon } from 'react-native-elements' ;
2018-12-11 23:52:46 +01:00
import { NavigationEvents } from 'react-navigation' ;
2018-12-18 07:05:03 +01:00
import ReactNativeHapticFeedback from 'react-native-haptic-feedback' ;
2018-03-18 03:48:23 +01:00
import PropTypes from 'prop-types' ;
2019-12-27 03:21:07 +01:00
import { PlaceholderWallet } from '../../class' ;
import WalletImport from '../../class/walletImport' ;
2020-03-25 03:23:58 +01:00
import ViewPager from '@react-native-community/viewpager' ;
2020-03-13 15:23:12 +01:00
import ScanQRCode from '../send/ScanQRCode' ;
2020-04-28 16:48:36 +02:00
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match' ;
2018-03-17 21:39:21 +01:00
let EV = require ( '../../events' ) ;
2019-01-10 16:04:16 +01:00
let A = require ( '../../analytics' ) ;
2018-03-18 03:48:23 +01:00
/** @type {AppStorage} */
let BlueApp = require ( '../../BlueApp' ) ;
2018-05-28 21:18:11 +02:00
let loc = require ( '../../loc' ) ;
2019-03-16 13:48:17 +01:00
let BlueElectrum = require ( '../../BlueElectrum' ) ;
2018-01-30 23:42:38 +01:00
2020-05-17 03:41:38 +02:00
const WalletsListSections = { CAROUSEL : 'CAROUSEL' , LOCALTRADER : 'LOCALTRADER' , TRANSACTIONS : 'TRANSACTIONS' } ;
2018-01-30 23:42:38 +01:00
export default class WalletsList extends Component {
2019-12-14 09:02:40 +01:00
walletsCarousel = React . createRef ( ) ;
2020-03-25 03:23:58 +01:00
viewPagerRef = React . createRef ( ) ;
2019-12-14 09:02:40 +01:00
2018-01-30 23:42:38 +01:00
constructor ( props ) {
super ( props ) ;
this . state = {
isLoading : true ,
2019-02-17 02:22:14 +01:00
isFlatListRefreshControlHidden : true ,
2018-12-11 23:52:46 +01:00
wallets : BlueApp . getWallets ( ) . concat ( false ) ,
2018-12-27 07:12:19 +01:00
lastSnappedTo : 0 ,
2019-12-28 17:22:43 +01:00
timeElpased : 0 ,
2020-05-17 03:41:38 +02:00
dataSource : [ ] ,
2020-01-08 10:29:51 +01:00
cameraPreviewIsPaused : true ,
2020-03-25 04:08:48 +01:00
viewPagerIndex : 1 ,
2018-03-17 21:39:21 +01:00
} ;
2019-12-14 09:02:40 +01:00
EV ( EV . enum . WALLETS _COUNT _CHANGED , ( ) => this . redrawScreen ( true ) ) ;
2018-10-09 06:25:36 +02:00
// here, when we receive TRANSACTIONS_COUNT_CHANGED we do not query
// remote server, we just redraw the screen
2019-12-14 09:02:40 +01:00
EV ( EV . enum . TRANSACTIONS _COUNT _CHANGED , this . redrawScreen ) ;
2018-01-30 23:42:38 +01:00
}
2018-12-11 23:52:46 +01:00
componentDidMount ( ) {
2019-03-16 13:48:17 +01:00
// the idea is that upon wallet launch we will refresh
// all balances and all transactions here:
InteractionManager . runAfterInteractions ( async ( ) => {
try {
await BlueElectrum . waitTillConnected ( ) ;
let balanceStart = + new Date ( ) ;
await BlueApp . fetchWalletBalances ( ) ;
let balanceEnd = + new Date ( ) ;
console . log ( 'fetch all wallet balances took' , ( balanceEnd - balanceStart ) / 1000 , 'sec' ) ;
let start = + new Date ( ) ;
await BlueApp . fetchWalletTransactions ( ) ;
let end = + new Date ( ) ;
console . log ( 'fetch all wallet txs took' , ( end - start ) / 1000 , 'sec' ) ;
2019-12-25 22:06:26 +01:00
} catch ( error ) {
2020-01-21 00:58:33 +01:00
console . log ( error ) ;
2019-03-16 13:48:17 +01:00
}
} ) ;
2019-12-28 17:22:43 +01:00
this . interval = setInterval ( ( ) => {
this . setState ( prev => ( { timeElapsed : prev . timeElapsed + 1 } ) ) ;
} , 60000 ) ;
}
componentWillUnmount ( ) {
clearInterval ( this . interval ) ;
2018-10-09 06:25:36 +02:00
}
2018-01-30 23:42:38 +01:00
2018-07-02 13:09:34 +02:00
/ * *
2018-12-25 20:07:43 +01:00
* Forcefully fetches TXs and balance for lastSnappedTo ( i . e . current ) wallet .
* Triggered manually by user on pull - to - refresh .
2018-07-02 13:09:34 +02:00
* /
2018-06-25 00:22:46 +02:00
refreshTransactions ( ) {
2019-02-17 02:22:14 +01:00
if ( ! ( this . lastSnappedTo < BlueApp . getWallets ( ) . length ) && this . lastSnappedTo !== undefined ) {
2018-10-09 06:25:36 +02:00
// last card, nop
console . log ( 'last card, nop' ) ;
return ;
}
2018-03-17 21:39:21 +01:00
this . setState (
2018-06-25 00:22:46 +02:00
{
2019-02-23 21:16:18 +01:00
isFlatListRefreshControlHidden : false ,
2018-06-25 00:22:46 +02:00
} ,
2019-02-17 02:22:14 +01:00
( ) => {
InteractionManager . runAfterInteractions ( async ( ) => {
2018-06-25 00:22:46 +02:00
let noErr = true ;
try {
2019-07-13 17:21:03 +02:00
await BlueElectrum . ping ( ) ;
await BlueElectrum . waitTillConnected ( ) ;
2019-01-29 03:27:07 +01:00
let balanceStart = + new Date ( ) ;
2019-02-17 02:22:14 +01:00
await BlueApp . fetchWalletBalances ( this . lastSnappedTo || 0 ) ;
2019-01-29 03:27:07 +01:00
let balanceEnd = + new Date ( ) ;
console . log ( 'fetch balance took' , ( balanceEnd - balanceStart ) / 1000 , 'sec' ) ;
2018-07-22 16:49:59 +02:00
let start = + new Date ( ) ;
2019-02-17 02:22:14 +01:00
await BlueApp . fetchWalletTransactions ( this . lastSnappedTo || 0 ) ;
2018-07-22 16:49:59 +02:00
let end = + new Date ( ) ;
2018-10-09 06:25:36 +02:00
console . log ( 'fetch tx took' , ( end - start ) / 1000 , 'sec' ) ;
2018-06-25 00:22:46 +02:00
} catch ( err ) {
noErr = false ;
console . warn ( err ) ;
}
if ( noErr ) await BlueApp . saveToDisk ( ) ; // caching
2019-02-17 02:22:14 +01:00
this . redrawScreen ( ) ;
} ) ;
2018-06-25 00:22:46 +02:00
} ,
) ;
}
2019-12-14 09:02:40 +01:00
redrawScreen = ( scrollToEnd = false ) => {
2019-02-17 02:22:14 +01:00
console . log ( 'wallets/list redrawScreen()' ) ;
2019-01-10 16:04:16 +01:00
if ( BlueApp . getBalance ( ) !== 0 ) {
A ( A . ENUM . GOT _NONZERO _BALANCE ) ;
2019-11-29 00:16:04 +01:00
} else {
A ( A . ENUM . GOT _ZERO _BALANCE ) ;
2019-01-10 16:04:16 +01:00
}
2018-07-06 17:41:48 +02:00
2019-12-14 09:02:40 +01:00
const wallets = BlueApp . getWallets ( ) . concat ( false ) ;
if ( scrollToEnd ) {
scrollToEnd = wallets . length > this . state . wallets . length ;
}
this . setState (
{
isLoading : false ,
isFlatListRefreshControlHidden : true ,
dataSource : BlueApp . getTransactions ( null , 10 ) ,
wallets : BlueApp . getWallets ( ) . concat ( false ) ,
} ,
( ) => {
if ( scrollToEnd ) {
2020-05-17 03:41:38 +02:00
this . walletsCarousel . current . snapToItem ( this . state . wallets . length - 2 ) ;
2019-12-14 09:02:40 +01:00
}
} ,
) ;
} ;
2018-06-25 00:22:46 +02:00
txMemo ( hash ) {
if ( BlueApp . tx _metadata [ hash ] && BlueApp . tx _metadata [ hash ] [ 'memo' ] ) {
2018-06-28 03:43:28 +02:00
return BlueApp . tx _metadata [ hash ] [ 'memo' ] ;
2018-06-25 00:22:46 +02:00
}
return '' ;
}
2020-05-17 03:41:38 +02:00
handleClick = ( index ) => {
2018-10-09 06:25:36 +02:00
console . log ( 'click' , index ) ;
2018-06-25 00:22:46 +02:00
let wallet = BlueApp . wallets [ index ] ;
if ( wallet ) {
2019-12-27 03:21:07 +01:00
if ( wallet . type === PlaceholderWallet . type ) {
Alert . alert (
loc . wallets . add . details ,
'There was a problem importing this wallet.' ,
[
{
text : loc . wallets . details . delete ,
onPress : ( ) => {
WalletImport . removePlaceholderWallet ( ) ;
EV ( EV . enum . WALLETS _COUNT _CHANGED ) ;
} ,
style : 'destructive' ,
} ,
{
text : 'Try Again' ,
onPress : ( ) => {
this . props . navigation . navigate ( 'ImportWallet' , { label : wallet . getSecret ( ) } ) ;
WalletImport . removePlaceholderWallet ( ) ;
EV ( EV . enum . WALLETS _COUNT _CHANGED ) ;
} ,
style : 'default' ,
} ,
] ,
{ cancelable : false } ,
) ;
} else {
this . props . navigation . navigate ( 'WalletTransactions' , {
wallet : wallet ,
key : ` WalletTransactions- ${ wallet . getID ( ) } ` ,
} ) ;
}
2018-06-25 00:22:46 +02:00
} else {
// if its out of index - this must be last card with incentive to create wallet
2019-12-27 03:21:07 +01:00
if ( ! BlueApp . getWallets ( ) . some ( wallet => wallet . type === PlaceholderWallet . type ) ) {
this . props . navigation . navigate ( 'AddWallet' ) ;
}
2018-06-25 00:22:46 +02:00
}
}
2020-05-17 03:41:38 +02:00
onSnapToItem = ( index ) => {
2018-06-25 00:22:46 +02:00
console . log ( 'onSnapToItem' , index ) ;
this . lastSnappedTo = index ;
2018-12-27 07:12:19 +01:00
this . setState ( { lastSnappedTo : index } ) ;
2018-06-25 00:22:46 +02:00
if ( index < BlueApp . getWallets ( ) . length ) {
2018-10-09 06:25:36 +02:00
// not the last
2018-06-25 00:22:46 +02:00
}
2018-07-02 15:51:24 +02:00
2019-12-27 03:21:07 +01:00
if ( this . state . wallets [ index ] . type === PlaceholderWallet . type ) {
return ;
}
2018-07-02 15:51:24 +02:00
// now, lets try to fetch balance and txs for this wallet in case it has changed
this . lazyRefreshWallet ( index ) ;
}
/ * *
* Decides whether wallet with such index shoud be refreshed ,
* refreshes if yes and redraws the screen
* @ param index { Integer } Index of the wallet .
* @ return { Promise . < void > }
* /
async lazyRefreshWallet ( index ) {
/** @type {Array.<AbstractWallet>} wallets */
let wallets = BlueApp . getWallets ( ) ;
2018-07-05 02:56:31 +02:00
if ( ! wallets [ index ] ) {
return ;
}
2018-10-09 06:25:36 +02:00
2018-07-02 15:51:24 +02:00
let oldBalance = wallets [ index ] . getBalance ( ) ;
let noErr = true ;
2018-07-14 22:32:36 +02:00
let didRefresh = false ;
2018-07-02 15:51:24 +02:00
try {
2019-12-27 03:21:07 +01:00
if ( wallets && wallets [ index ] && wallets [ index ] . type !== PlaceholderWallet . type && wallets [ index ] . timeToRefreshBalance ( ) ) {
2018-07-02 15:51:24 +02:00
console . log ( 'snapped to, and now its time to refresh wallet #' , index ) ;
await wallets [ index ] . fetchBalance ( ) ;
2018-07-07 15:04:32 +02:00
if ( oldBalance !== wallets [ index ] . getBalance ( ) || wallets [ index ] . getUnconfirmedBalance ( ) !== 0 ) {
2018-07-05 02:56:31 +02:00
console . log ( 'balance changed, thus txs too' ) ;
2018-07-02 15:51:24 +02:00
// balance changed, thus txs too
await wallets [ index ] . fetchTransactions ( ) ;
2019-02-17 02:22:14 +01:00
this . redrawScreen ( ) ;
2018-07-14 22:32:36 +02:00
didRefresh = true ;
} else if ( wallets [ index ] . timeToRefreshTransaction ( ) ) {
2018-09-01 01:28:19 +02:00
console . log ( wallets [ index ] . getLabel ( ) , 'thinks its time to refresh TXs' ) ;
2018-07-14 22:32:36 +02:00
await wallets [ index ] . fetchTransactions ( ) ;
2018-09-01 01:28:19 +02:00
if ( wallets [ index ] . fetchPendingTransactions ) {
await wallets [ index ] . fetchPendingTransactions ( ) ;
}
2018-12-25 20:07:43 +01:00
if ( wallets [ index ] . fetchUserInvoices ) {
await wallets [ index ] . fetchUserInvoices ( ) ;
2019-06-01 22:45:01 +02:00
await wallets [ index ] . fetchBalance ( ) ; // chances are, paid ln invoice was processed during `fetchUserInvoices()` call and altered user's balance, so its worth fetching balance again
2018-12-25 20:07:43 +01:00
}
2019-02-17 02:22:14 +01:00
this . redrawScreen ( ) ;
2018-07-14 22:32:36 +02:00
didRefresh = true ;
2018-07-03 22:11:02 +02:00
} else {
2018-07-05 02:56:31 +02:00
console . log ( 'balance not changed' ) ;
2018-07-02 15:51:24 +02:00
}
}
} catch ( Err ) {
noErr = false ;
console . warn ( Err ) ;
}
2018-07-14 22:32:36 +02:00
if ( noErr && didRefresh ) {
2018-07-02 15:51:24 +02:00
await BlueApp . saveToDisk ( ) ; // caching
}
2018-01-30 23:42:38 +01:00
}
2018-12-11 23:52:46 +01:00
_keyExtractor = ( _item , index ) => index . toString ( ) ;
2018-09-18 09:24:42 +02:00
2018-10-09 06:25:36 +02:00
renderListHeaderComponent = ( ) => {
return (
< View >
< Text
style = { {
2020-05-06 12:26:59 +02:00
paddingLeft : 16 ,
2018-10-09 06:25:36 +02:00
fontWeight : 'bold' ,
fontSize : 24 ,
marginVertical : 8 ,
color : BlueApp . settings . foregroundColor ,
} }
>
{ loc . transactions . list . title }
< / T e x t >
< / V i e w >
) ;
} ;
2018-12-11 23:52:46 +01:00
handleLongPress = ( ) => {
2019-12-27 03:21:07 +01:00
if ( BlueApp . getWallets ( ) . length > 1 && ! BlueApp . getWallets ( ) . some ( wallet => wallet . type === PlaceholderWallet . type ) ) {
2018-12-11 23:52:46 +01:00
this . props . navigation . navigate ( 'ReorderWallets' ) ;
2018-12-18 07:05:03 +01:00
} else {
2019-05-03 14:36:11 +02:00
ReactNativeHapticFeedback . trigger ( 'notificationError' , { ignoreAndroidSystemSettings : false } ) ;
2018-12-11 23:52:46 +01:00
}
} ;
2020-03-25 04:11:28 +01:00
onPageSelected = e => {
const index = e . nativeEvent . position ;
2019-12-28 01:53:34 +01:00
StatusBar . setBarStyle ( index === 1 ? 'dark-content' : 'light-content' ) ;
2020-03-25 04:08:48 +01:00
this . setState ( { cameraPreviewIsPaused : index === 1 || index === undefined , viewPagerIndex : index } ) ;
2019-12-28 01:53:34 +01:00
} ;
onBarScanned = value => {
DeeplinkSchemaMatch . navigationRouteFor ( { url : value } , completionValue => {
ReactNativeHapticFeedback . trigger ( 'impactLight' , { ignoreAndroidSystemSettings : false } ) ;
this . props . navigation . navigate ( completionValue ) ;
} ) ;
} ;
2020-05-17 03:41:38 +02:00
renderTransactionListsRow = data => {
2020-03-26 21:15:00 +01:00
return (
< View style = { { marginHorizontal : 4 } } >
< BlueTransactionListItem item = { data . item } itemPriceUnit = { data . item . walletPreferredBalanceUnit } / >
< / V i e w >
) ;
2019-02-17 02:22:14 +01:00
} ;
2019-12-28 01:53:34 +01:00
renderNavigationHeader = ( ) => {
return (
< View style = { { height : 44 , alignItems : 'flex-end' , justifyContent : 'center' } } >
2020-03-18 16:38:52 +01:00
< TouchableOpacity
testID = "SettingsButton"
style = { { marginHorizontal : 16 } }
onPress = { ( ) => this . props . navigation . navigate ( 'Settings' ) }
>
2019-12-28 01:53:34 +01:00
< 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 >
< / V i e w >
) ;
} ;
2020-04-15 23:10:24 +02:00
renderLocalTrader = ( ) => {
if ( BlueApp . getWallets ( ) . length > 0 && ! BlueApp . getWallets ( ) . some ( wallet => wallet . type === PlaceholderWallet . type ) ) {
return (
< TouchableOpacity
onPress = { ( ) => {
this . props . navigation . navigate ( 'HodlHodl' , { fromWallet : this . state . wallet } ) ;
} }
style = { {
flexDirection : 'row' ,
justifyContent : 'space-between' ,
alignItems : 'center' ,
marginHorizontal : 16 ,
2020-05-06 12:26:59 +02:00
marginVertical : 16 ,
2020-04-15 23:10:24 +02:00
backgroundColor : '#eef0f4' ,
padding : 16 ,
borderRadius : 6 ,
} }
>
< View style = { { flexDirection : 'column' } } >
< Text style = { { fontSize : 16 , fontWeight : '600' , color : '#0C2550' } } > Local Trader < / T e x t >
2020-04-29 14:37:09 +02:00
< Text style = { { fontSize : 13 , fontWeight : '500' , color : '#9AA0AA' } } > A p2p exchange < / T e x t >
2020-04-15 23:10:24 +02:00
< / V i e w >
< View style = { { flexDirection : 'column' , backgroundColor : '#007AFF' , borderRadius : 16 } } >
< Text style = { { paddingHorizontal : 16 , paddingVertical : 8 , fontSize : 13 , color : '#fff' , fontWeight : '600' } } > New < / T e x t >
< / V i e w >
< / T o u c h a b l e O p a c i t y >
) ;
2020-05-17 03:41:38 +02:00
} else {
return null ;
2020-04-15 23:10:24 +02:00
}
} ;
2020-05-17 03:41:38 +02:00
renderWalletsCarousel = ( ) => {
return (
< WalletsCarousel
removeClippedSubviews = { false }
data = { this . state . wallets }
handleClick = { this . handleClick }
handleLongPress = { this . handleLongPress }
onSnapToItem = { this . onSnapToItem }
ref = { this . walletsCarousel }
/ >
) ;
} ;
renderSectionItem = item => {
switch ( item . section . key ) {
case WalletsListSections . CAROUSEL :
return this . renderWalletsCarousel ( ) ;
case WalletsListSections . LOCALTRADER :
return this . renderLocalTrader ( ) ;
case WalletsListSections . TRANSACTIONS :
return this . renderTransactionListsRow ( item ) ;
default :
return null ;
}
} ;
renderSectionHeader = ( { section } ) => {
switch ( section . key ) {
case WalletsListSections . CAROUSEL :
return (
< BlueHeaderDefaultMain
leftText = { loc . wallets . list . title }
onNewWalletPress = {
! BlueApp . getWallets ( ) . some ( wallet => wallet . type === PlaceholderWallet . type )
? ( ) => this . props . navigation . navigate ( 'AddWallet' )
: null
}
/ >
) ;
case WalletsListSections . TRANSACTIONS :
return this . renderListHeaderComponent ( ) ;
default :
return null ;
2018-01-30 23:42:38 +01:00
}
2020-05-17 03:41:38 +02:00
} ;
sectionListKeyExtractor = ( item , index ) => {
return ` ${ item } ${ index } } ` ;
} ;
render ( ) {
2018-01-30 23:42:38 +01:00
return (
2020-03-27 02:37:19 +01:00
< SafeBlueArea >
2020-05-17 03:41:38 +02:00
< NavigationEvents
onDidFocus = { ( ) => {
this . redrawScreen ( ) ;
this . setState ( { cameraPreviewIsPaused : this . state . viewPagerIndex === 1 || this . viewPagerRef . current . index === undefined } ) ;
} }
onWillBlur = { ( ) => this . setState ( { cameraPreviewIsPaused : true } ) }
/ >
< ViewPager
style = { styles . wrapper }
onPageSelected = { this . onPageSelected }
initialPage = { 1 }
ref = { this . viewPagerRef }
showPageIndicator = { false }
testID = "WalletsList"
accessible
>
< View style = { styles . scanQRWrapper } >
< ScanQRCode
cameraPreviewIsPaused = { this . state . cameraPreviewIsPaused }
onBarScanned = { this . onBarScanned }
showCloseButton = { false }
initialCameraStatusReady = { false }
launchedBy = { this . props . navigation . state . routeName }
/ >
< / V i e w >
< View style = { styles . walletsListWrapper } >
{ this . renderNavigationHeader ( ) }
< SectionList
refreshControl = {
< RefreshControl onRefresh = { ( ) => this . refreshTransactions ( ) } refreshing = { ! this . state . isFlatListRefreshControlHidden } / >
}
renderItem = { this . renderSectionItem }
keyExtractor = { this . sectionListKeyExtractor }
renderSectionHeader = { this . renderSectionHeader }
sections = { [
{ key : WalletsListSections . CAROUSEL , data : [ WalletsListSections . CAROUSEL ] } ,
{ key : WalletsListSections . LOCALTRADER , data : [ WalletsListSections . LOCALTRADER ] } ,
{ key : WalletsListSections . TRANSACTIONS , data : this . state . dataSource } ,
] }
/ >
< / V i e w >
< / V i e w P a g e r >
2020-03-27 02:37:19 +01:00
< / S a f e B l u e A r e a >
2018-01-30 23:42:38 +01:00
) ;
}
2018-03-17 21:39:21 +01:00
}
2018-03-18 03:48:23 +01:00
2019-12-28 01:53:34 +01:00
const styles = StyleSheet . create ( {
wrapper : {
backgroundColor : '#FFFFFF' ,
2020-03-25 03:23:58 +01:00
flex : 1 ,
2019-12-28 01:53:34 +01:00
} ,
walletsListWrapper : {
flex : 1 ,
backgroundColor : '#FFFFFF' ,
} ,
scanQRWrapper : {
flex : 1 ,
backgroundColor : '#000000' ,
} ,
} ) ;
2018-03-18 03:48:23 +01:00
WalletsList . propTypes = {
navigation : PropTypes . shape ( {
2019-12-28 01:53:34 +01:00
state : PropTypes . shape ( {
routeName : PropTypes . string ,
} ) ,
2018-03-18 03:48:23 +01:00
navigate : PropTypes . func ,
} ) ,
} ;