import React, { Component } from 'react'; import { View, TouchableOpacity, Text, FlatList, InteractionManager, RefreshControl, ScrollView } from 'react-native'; import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents'; import { Icon } from 'react-native-elements'; import { NavigationEvents } from 'react-navigation'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import PropTypes from 'prop-types'; import WalletGradient from '../../class/walletGradient'; let EV = require('../../events'); let A = require('../../analytics'); /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); let loc = require('../../loc'); let BlueElectrum = require('../../BlueElectrum'); export default class WalletsList extends Component { static navigationOptions = ({ navigation }) => ({ headerStyle: { backgroundColor: '#FFFFFF', borderBottomWidth: 0, elevation: 0, }, headerRight: ( navigation.navigate('Settings')} > ), }); constructor(props) { super(props); this.state = { isLoading: true, isFlatListRefreshControlHidden: true, wallets: BlueApp.getWallets().concat(false), lastSnappedTo: 0, }; EV(EV.enum.WALLETS_COUNT_CHANGED, this.redrawScreen.bind(this)); // here, when we receive TRANSACTIONS_COUNT_CHANGED we do not query // remote server, we just redraw the screen EV(EV.enum.TRANSACTIONS_COUNT_CHANGED, this.redrawScreen.bind(this)); } componentDidMount() { this.redrawScreen(); // the idea is that upon wallet launch we will refresh // all balances and all transactions here: InteractionManager.runAfterInteractions(async () => { let noErr = true; 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'); } catch (_) { noErr = false; } if (noErr) this.redrawScreen(); }); } /** * Forcefully fetches TXs and balance for lastSnappedTo (i.e. current) wallet. * Triggered manually by user on pull-to-refresh. */ refreshTransactions() { if (!(this.lastSnappedTo < BlueApp.getWallets().length) && this.lastSnappedTo !== undefined) { // last card, nop console.log('last card, nop'); return; } this.setState( { isFlatListRefreshControlHidden: false, }, () => { InteractionManager.runAfterInteractions(async () => { // more responsive let noErr = true; try { let balanceStart = +new Date(); await BlueApp.fetchWalletBalances(this.lastSnappedTo || 0); let balanceEnd = +new Date(); console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); let start = +new Date(); await BlueApp.fetchWalletTransactions(this.lastSnappedTo || 0); let end = +new Date(); console.log('fetch tx took', (end - start) / 1000, 'sec'); } catch (err) { noErr = false; console.warn(err); } if (noErr) await BlueApp.saveToDisk(); // caching this.redrawScreen(); }); }, ); } redrawScreen() { console.log('wallets/list redrawScreen()'); if (BlueApp.getBalance() !== 0) { A(A.ENUM.GOT_NONZERO_BALANCE); } this.setState({ isLoading: false, isFlatListRefreshControlHidden: true, dataSource: BlueApp.getTransactions(null, 10), wallets: BlueApp.getWallets().concat(false), }); } txMemo(hash) { if (BlueApp.tx_metadata[hash] && BlueApp.tx_metadata[hash]['memo']) { return BlueApp.tx_metadata[hash]['memo']; } return ''; } handleClick(index) { console.log('click', index); let wallet = BlueApp.wallets[index]; if (wallet) { this.props.navigation.navigate('WalletTransactions', { wallet: wallet, headerColor: WalletGradient.headerColorFor(wallet.type), }); } else { // if its out of index - this must be last card with incentive to create wallet this.props.navigation.navigate('AddWallet'); } } onSnapToItem(index) { console.log('onSnapToItem', index); this.lastSnappedTo = index; this.setState({ lastSnappedTo: index }); if (index < BlueApp.getWallets().length) { // not the last } // 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.} */ async lazyRefreshWallet(index) { /** @type {Array.} wallets */ let wallets = BlueApp.getWallets(); if (!wallets[index]) { return; } let oldBalance = wallets[index].getBalance(); let noErr = true; let didRefresh = false; try { if (wallets && wallets[index] && wallets[index].timeToRefreshBalance()) { console.log('snapped to, and now its time to refresh wallet #', index); await wallets[index].fetchBalance(); if (oldBalance !== wallets[index].getBalance() || wallets[index].getUnconfirmedBalance() !== 0) { console.log('balance changed, thus txs too'); // balance changed, thus txs too await wallets[index].fetchTransactions(); this.redrawScreen(); didRefresh = true; } else if (wallets[index].timeToRefreshTransaction()) { console.log(wallets[index].getLabel(), 'thinks its time to refresh TXs'); await wallets[index].fetchTransactions(); if (wallets[index].fetchPendingTransactions) { await wallets[index].fetchPendingTransactions(); } if (wallets[index].fetchUserInvoices) { await wallets[index].fetchUserInvoices(); } this.redrawScreen(); didRefresh = true; } else { console.log('balance not changed'); } } } catch (Err) { noErr = false; console.warn(Err); } if (noErr && didRefresh) { await BlueApp.saveToDisk(); // caching } } _keyExtractor = (_item, index) => index.toString(); renderListHeaderComponent = () => { return ( {loc.transactions.list.title} ); }; handleLongPress = () => { if (BlueApp.getWallets().length > 1) { this.props.navigation.navigate('ReorderWallets'); } else { ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false }); } }; _renderItem = data => { return ; }; render() { if (this.state.isLoading) { return ; } return ( { this.redrawScreen(); }} /> this.refreshTransactions()} refreshing={!this.state.isFlatListRefreshControlHidden} /> } > this.props.navigation.navigate('AddWallet')} /> { this.handleClick(index); }} handleLongPress={this.handleLongPress} onSnapToItem={index => { this.onSnapToItem(index); }} /> {loc.wallets.list.empty_txs1} {loc.wallets.list.empty_txs2} } data={this.state.dataSource} extraData={this.state.dataSource} keyExtractor={this._keyExtractor} renderItem={this._renderItem} /> ); } } WalletsList.propTypes = { navigation: PropTypes.shape({ navigate: PropTypes.func, }), };