Skip to content

Commit

Permalink
extract balance logic into a hook
Browse files Browse the repository at this point in the history
  • Loading branch information
owencraston committed Jan 20, 2025
1 parent fde5b0d commit ae62459
Show file tree
Hide file tree
Showing 6 changed files with 642 additions and 195 deletions.
211 changes: 16 additions & 195 deletions app/components/UI/Tokens/TokenList/PortfolioBalance/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
import React from 'react';
import { View, TouchableOpacity } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useSelector } from 'react-redux';
import useIsOriginalNativeTokenSymbol from '../../../../hooks/useIsOriginalNativeTokenSymbol/useIsOriginalNativeTokenSymbol';
import { useMetrics } from '../../../../hooks/useMetrics';
import { useTheme } from '../../../../../util/theme';
import AppConstants from '../../../../../core/AppConstants';
import Engine from '../../../../../core/Engine';
import Routes from '../../../../../constants/navigation/Routes';
import { MetaMetricsEvents } from '../../../../../core/Analytics';
import {
selectChainId,
selectIsPopularNetwork,
selectProviderConfig,
selectTicker,
} from '../../../../../selectors/networkController';
import { selectCurrentCurrency } from '../../../../../selectors/currencyRateController';
import {
selectIsTokenNetworkFilterEqualCurrentNetwork,
selectPrivacyMode,
} from '../../../../../selectors/preferencesController';
import { RootState } from '../../../../../reducers';
import { renderFiat } from '../../../../../util/number';
import {
isPortfolioViewEnabled,
isTestNet,
} from '../../../../../util/networks';
import { isPortfolioUrl } from '../../../../../util/url';
import createStyles from '../../styles';
import Button, {
ButtonVariants,
Expand All @@ -42,224 +16,71 @@ import Icon, {
IconSize,
IconName,
} from '../../../../../component-library/components/Icons/Icon';
import { BrowserTab } from '../../types';
import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors';
import { strings } from '../../../../../../locales/i18n';
import { EYE_SLASH_ICON_TEST_ID, EYE_ICON_TEST_ID } from './index.constants';
import { selectSelectedInternalAccount } from '../../../../../selectors/accountsController';
import { useGetFormattedTokensPerChain } from '../../../../hooks/useGetFormattedTokensPerChain';
import {
TotalFiatBalancesCrossChains,
useGetTotalFiatBalanceCrossChains,
} from '../../../../hooks/useGetTotalFiatBalanceCrossChains';
import { InternalAccount } from '@metamask/keyring-internal-api';
import { getChainIdsToPoll } from '../../../../../selectors/tokensController';
import AggregatedPercentageCrossChains from '../../../../../component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentageCrossChains';
import {
selectMultichainSelectedAccountCachedBalance,
selectMultichainIsEvm,
selectMultichainDefaultToken,
} from '../../../../../selectors/multichain';
import { usePortfolioBalance } from '../../../../hooks/usePortfolioBalance';

export const PortfolioBalance = () => {
const { PreferencesController } = Engine.context;
const { colors } = useTheme();
const styles = createStyles(colors);
const balance = Engine.getTotalFiatAccountBalance();

const selectedInternalAccount: InternalAccount | undefined = useSelector(
selectSelectedInternalAccount,
);
const allChainIDs = useSelector(getChainIdsToPoll);
const isTokenNetworkFilterEqualCurrentNetwork = useSelector(
selectIsTokenNetworkFilterEqualCurrentNetwork,
);
const isPopularNetwork = useSelector(selectIsPopularNetwork);

const formattedTokensWithBalancesPerChain = useGetFormattedTokensPerChain(
[selectedInternalAccount as InternalAccount],
!isTokenNetworkFilterEqualCurrentNetwork && isPopularNetwork,
allChainIDs,
);

const totalFiatBalancesCrossChain: TotalFiatBalancesCrossChains =
useGetTotalFiatBalanceCrossChains(
[selectedInternalAccount as InternalAccount],
formattedTokensWithBalancesPerChain,
);

const tokenFiatBalancesCrossChains =
totalFiatBalancesCrossChain[selectedInternalAccount?.address as string]
?.tokenFiatBalancesCrossChains ?? [];
const totalFiatBalance =
totalFiatBalancesCrossChain[selectedInternalAccount?.address as string]
?.totalFiatBalance ?? 0;
const totalTokenFiat =
totalFiatBalancesCrossChain[selectedInternalAccount?.address as string]
?.totalTokenFiat ?? 0;

const navigation = useNavigation();
const { trackEvent, isEnabled, createEventBuilder } = useMetrics();

const { type } = useSelector(selectProviderConfig);
const chainId = useSelector(selectChainId);
const ticker = useSelector(selectTicker);
const isDataCollectionForMarketingEnabled = useSelector(
(state: RootState) => state.security.dataCollectionForMarketing,
);
const currentCurrency = useSelector(selectCurrentCurrency);
const browserTabs = useSelector((state: RootState) => state.browser.tabs);
const privacyMode = useSelector(selectPrivacyMode);

const multichainSelectedAccountCachedBalance = useSelector(
selectMultichainSelectedAccountCachedBalance,
);

const isEvm = useSelector(selectMultichainIsEvm);
const { symbol } = useSelector(selectMultichainDefaultToken);
console.log('isEvm', isEvm);
console.log('symbol', symbol);

const isOriginalNativeTokenSymbol = useIsOriginalNativeTokenSymbol(
chainId,
ticker,
type,
);

let displayBalance;
if (isEvm) {
let total;
if (isOriginalNativeTokenSymbol) {
if (isPortfolioViewEnabled()) {
total = totalFiatBalance ?? 0;
} else {
const tokenFiatTotal = balance?.tokenFiat ?? 0;
const ethFiatTotal = balance?.ethFiat ?? 0;
total = tokenFiatTotal + ethFiatTotal;
}
} else if (isPortfolioViewEnabled()) {
total = totalTokenFiat ?? 0;
} else {
total = balance?.tokenFiat ?? 0;
}
displayBalance = `${renderFiat(total, currentCurrency)}`;
} else {
// For non-EVM accounts, display the native balance with symbol
displayBalance = `${multichainSelectedAccountCachedBalance} ${symbol}`;
}

const onOpenPortfolio = () => {
const existingPortfolioTab = browserTabs.find(({ url }: BrowserTab) =>
isPortfolioUrl(url),
);

let existingTabId;
let newTabUrl;
if (existingPortfolioTab) {
existingTabId = existingPortfolioTab.id;
} else {
const analyticsEnabled = isEnabled();
const portfolioUrl = new URL(AppConstants.PORTFOLIO.URL);

portfolioUrl.searchParams.append('metamaskEntry', 'mobile');

// Append user's privacy preferences for metrics + marketing on user navigation to Portfolio.
portfolioUrl.searchParams.append(
'metricsEnabled',
String(analyticsEnabled),
);
portfolioUrl.searchParams.append(
'marketingEnabled',
String(!!isDataCollectionForMarketingEnabled),
);

newTabUrl = portfolioUrl.href;
}
const params = {
...(newTabUrl && { newTabUrl }),
...(existingTabId && { existingTabId, newTabUrl: undefined }),
timestamp: Date.now(),
};
navigation.navigate(Routes.BROWSER.HOME, {
screen: Routes.BROWSER.VIEW,
params,
});
trackEvent(
createEventBuilder(MetaMetricsEvents.PORTFOLIO_LINK_CLICKED)
.addProperties({
portfolioUrl: AppConstants.PORTFOLIO.URL,
})
.build(),
);
};
const { data, actions } = usePortfolioBalance();

const renderAggregatedPercentage = () => {
if (!isEvm || isTestNet(chainId)) {
if (!data.shouldShowAggregatedPercentage) {
return null;
}

if (isPortfolioViewEnabled()) {
return (
<AggregatedPercentageCrossChains
privacyMode={privacyMode}
totalFiatCrossChains={totalFiatBalance}
tokenFiatBalancesCrossChains={tokenFiatBalancesCrossChains}
/>
);
}
return (
<AggregatedPercentage
privacyMode={privacyMode}
ethFiat={balance?.ethFiat}
tokenFiat={balance?.tokenFiat}
tokenFiat1dAgo={balance?.tokenFiat1dAgo}
ethFiat1dAgo={balance?.ethFiat1dAgo}
<AggregatedPercentageCrossChains
privacyMode={data.privacyMode}
totalFiatCrossChains={data.totalFiatBalance}
tokenFiatBalancesCrossChains={data.tokenFiatBalancesCrossChains}
/>
);
};

const toggleIsBalanceAndAssetsHidden = (value: boolean) => {
PreferencesController.setPrivacyMode(value);
};

return (
<View style={styles.portfolioBalance}>
<View>
<View>
<View style={styles.balanceContainer}>
<SensitiveText
isHidden={privacyMode}
isHidden={data.privacyMode}
length={SensitiveTextLength.Long}
testID={WalletViewSelectorsIDs.TOTAL_BALANCE_TEXT}
variant={TextVariant.DisplayMD}
>
{displayBalance}
{data.displayBalance}
</SensitiveText>
<TouchableOpacity
onPress={() => toggleIsBalanceAndAssetsHidden(!privacyMode)}
onPress={() => actions.togglePrivacyMode(!data.privacyMode)}
testID="balance-container"
>
<Icon
style={styles.privacyIcon}
name={privacyMode ? IconName.EyeSlash : IconName.Eye}
name={data.privacyMode ? IconName.EyeSlash : IconName.Eye}
size={IconSize.Md}
color={colors.text.muted}
testID={privacyMode ? EYE_SLASH_ICON_TEST_ID : EYE_ICON_TEST_ID}
testID={
data.privacyMode ? EYE_SLASH_ICON_TEST_ID : EYE_ICON_TEST_ID
}
/>
</TouchableOpacity>
</View>

{renderAggregatedPercentage()}
</View>
</View>
{isEvm && (
{data.shouldShowPortfolioButton && (
<View style={styles.portfolioButtonContainer}>
<Button
variant={ButtonVariants.Secondary}
size={ButtonSize.Md}
width={ButtonWidthTypes.Full}
style={styles.buyButton}
onPress={onOpenPortfolio}
onPress={actions.onOpenPortfolio}
label={strings('asset_overview.portfolio_button')}
testID={WalletViewSelectorsIDs.PORTFOLIO_BUTTON}
endIconName={IconName.Export}
Expand Down
7 changes: 7 additions & 0 deletions app/components/hooks/usePortfolioBalance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { default as usePortfolioBalance } from './usePortfolioBalance';
export type {
UsePortfolioBalanceHook,
PortfolioBalanceData,
PortfolioBalanceActions,
TokenFiatBalancesCrossChains,
} from './usePortfolioBalance.types';
Loading

0 comments on commit ae62459

Please sign in to comment.