import React, { useEffect, useRef, useState, useCallback, useContext, } from 'react'; import { RefreshControl, ScrollView, InteractionManager, ActivityIndicator, StyleSheet, View, } from 'react-native'; import { useSelector } from 'react-redux'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import DefaultTabBar from 'react-native-scrollable-tab-view/DefaultTabBar'; import { fontStyles, baseStyles } from '../../../styles/common'; import AccountOverview from '../../UI/AccountOverview'; import Tokens from '../../UI/Tokens'; import { getWalletNavbarOptions } from '../../UI/Navbar'; import { strings } from '../../../../locales/i18n'; import { renderFromWei, weiToFiat, hexToBN } from '../../../util/number'; import Engine from '../../../core/Engine'; import CollectibleContracts from '../../UI/CollectibleContracts'; import Analytics from '../../../core/Analytics/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import { getTicker } from '../../../util/transactions'; import OnboardingWizard from '../../UI/OnboardingWizard'; import ErrorBoundary from '../ErrorBoundary'; import { DrawerContext } from '../../Nav/Main/MainNavigator'; import { useAppThemeFromContext, mockTheme } from '../../../util/theme'; import { shouldShowWhatsNewModal } from '../../../util/onboarding'; import Logger from '../../../util/Logger'; import Routes from '../../../constants/navigation/Routes'; const createStyles = (colors: any) => StyleSheet.create({ wrapper: { flex: 1, backgroundColor: colors.background.default, }, tabUnderlineStyle: { height: 2, backgroundColor: colors.primary.default, }, tabStyle: { paddingBottom: 0, }, tabBar: { borderColor: colors.border.muted, }, textStyle: { fontSize: 12, letterSpacing: 0.5, ...(fontStyles.bold as any), }, loader: { backgroundColor: colors.background.default, flex: 1, justifyContent: 'center', alignItems: 'center', }, }); /** * Main view for the wallet */ const Wallet = ({ navigation }: any) => { const { drawerRef } = useContext(DrawerContext); const [refreshing, setRefreshing] = useState(false); const accountOverviewRef = useRef(null); const { colors } = useAppThemeFromContext() || mockTheme; const styles = createStyles(colors); /** * Map of accounts to information objects including balances */ const accounts = useSelector( (state: any) => state.engine.backgroundState.AccountTrackerController.accounts, ); /** * ETH to current currency conversion rate */ const conversionRate = useSelector( (state: any) => state.engine.backgroundState.CurrencyRateController.conversionRate, ); /** * Currency code of the currently-active currency */ const currentCurrency = useSelector( (state: any) => state.engine.backgroundState.CurrencyRateController.currentCurrency, ); /** * An object containing each identity in the format address => account */ const identities = useSelector( (state: any) => state.engine.backgroundState.PreferencesController.identities, ); /** * A string that represents the selected address */ const selectedAddress = useSelector( (state: any) => state.engine.backgroundState.PreferencesController.selectedAddress, ); /** * An array that represents the user tokens */ const tokens = useSelector( (state: any) => state.engine.backgroundState.TokensController.tokens, ); /** * Current provider ticker */ const ticker = useSelector( (state: any) => state.engine.backgroundState.NetworkController.provider.ticker, ); /** * Current onboarding wizard step */ const wizardStep = useSelector((state: any) => state.wizard.step); const { colors: themeColors } = useAppThemeFromContext() || mockTheme; /** * Check to see if we need to show What's New modal */ useEffect(() => { if (wizardStep > 0) { // Do not check since it will conflict with the onboarding wizard return; } const checkWhatsNewModal = async () => { try { const shouldShowWhatsNew = await shouldShowWhatsNewModal(); if (shouldShowWhatsNew) { navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { screen: Routes.MODAL.WHATS_NEW, }); } } catch (error) { Logger.log(error, "Error while checking What's New modal!"); } }; checkWhatsNewModal(); }, [wizardStep, navigation]); useEffect( () => { requestAnimationFrame(async () => { const { TokenDetectionController, CollectibleDetectionController, AccountTrackerController, } = Engine.context as any; TokenDetectionController.detectTokens(); CollectibleDetectionController.detectCollectibles(); AccountTrackerController.refresh(); }); }, /* eslint-disable-next-line */ [navigation], ); useEffect(() => { navigation.setOptions( getWalletNavbarOptions( 'wallet.title', navigation, drawerRef, themeColors, ), ); /* eslint-disable-next-line */ }, [navigation, themeColors]); const onRefresh = useCallback(async () => { requestAnimationFrame(async () => { setRefreshing(true); const { TokenDetectionController, CollectibleDetectionController, AccountTrackerController, CurrencyRateController, TokenRatesController, } = Engine.context as any; const actions = [ TokenDetectionController.detectTokens(), CollectibleDetectionController.detectCollectibles(), AccountTrackerController.refresh(), CurrencyRateController.start(), TokenRatesController.poll(), ]; await Promise.all(actions); setRefreshing(false); }); }, [setRefreshing]); const renderTabBar = useCallback( () => ( ), [styles, colors], ); const onChangeTab = useCallback((obj) => { InteractionManager.runAfterInteractions(() => { if (obj.ref.props.tabLabel === strings('wallet.tokens')) { Analytics.trackEvent(ANALYTICS_EVENT_OPTS.WALLET_TOKENS); } else { Analytics.trackEvent(ANALYTICS_EVENT_OPTS.WALLET_COLLECTIBLES); } }); }, []); const onRef = useCallback((ref) => { accountOverviewRef.current = ref; }, []); const renderContent = useCallback(() => { let balance: any = 0; let assets = tokens; if (accounts[selectedAddress]) { balance = renderFromWei(accounts[selectedAddress].balance); assets = [ { name: 'Ether', // FIXME: use 'Ether' for mainnet only, what should it be for custom networks? symbol: getTicker(ticker), isETH: true, balance, balanceFiat: weiToFiat( hexToBN(accounts[selectedAddress].balance) as any, conversionRate, currentCurrency, ), logo: '../images/eth-logo.png', }, ...(tokens || []), ]; } else { assets = tokens; } const account = { address: selectedAddress, ...identities[selectedAddress], ...accounts[selectedAddress], }; return ( ); }, [ renderTabBar, accounts, conversionRate, currentCurrency, identities, navigation, onChangeTab, onRef, selectedAddress, ticker, tokens, styles, ]); const renderLoader = useCallback( () => ( ), [styles], ); /** * Return current step of onboarding wizard if not step 5 nor 0 */ const renderOnboardingWizard = useCallback( () => [1, 2, 3, 4].includes(wizardStep) && ( ), [navigation, wizardStep], ); return ( } > {selectedAddress ? renderContent() : renderLoader()} {renderOnboardingWizard()} ); }; export default Wallet;