Skip to content

Commit

Permalink
UX: Display total fiat balance on home screen
Browse files Browse the repository at this point in the history
  • Loading branch information
darkwing committed Sep 18, 2023
1 parent 62c7353 commit 931a191
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 77 deletions.
12 changes: 11 additions & 1 deletion shared/modules/conversion.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function getValueFromWeiHex({
fromCurrency = EtherDenomination.ETH,
toCurrency,
conversionRate,
numberOfDecimals,
numberOfDecimals, // 18
toDenomination = EtherDenomination.ETH,
}: {
value: NumericValue;
Expand Down Expand Up @@ -156,6 +156,16 @@ export function sumHexes(first: string, ...args: string[]) {
return total.toPrefixedHexString();
}

export function sumDecimals(first: string, ...args: string[]) {
const firstValue = new Numeric(first, 10);
const total = args.reduce(
(acc, hexAmount) => acc.add(new Numeric(hexAmount, 10)),
firstValue,
);

return total;
}

export function hexWEIToDecGWEI(value: number | string | BigNumber | BN) {
return new Numeric(value, 16, EtherDenomination.WEI)
.toBase(10)
Expand Down
79 changes: 76 additions & 3 deletions ui/components/app/asset-list/asset-list.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';
import { isEqual } from 'lodash';
import TokenList from '../token-list';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency';
Expand All @@ -10,8 +11,15 @@ import {
getNativeCurrencyImage,
getDetectedTokensInCurrentNetwork,
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
getShouldHideZeroBalanceTokens,
getTokenExchangeRates,
getCurrentCurrency,
} from '../../../selectors';
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
import {
getConversionRate,
getNativeCurrency,
getTokens,
} from '../../../ducks/metamask/metamask';
import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay';
import Box from '../../ui/box/box';
import { MetaMetricsContext } from '../../../contexts/metametrics';
Expand All @@ -26,6 +34,15 @@ import {
ImportTokenLink,
BalanceOverview,
} from '../../multichain';
import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils';
import { getTokenFiatAmount } from '../../../helpers/utils/token-util';
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
import {
getValueFromWeiHex,
sumDecimals,
} from '../../../../shared/modules/conversion.utils';
import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { EtherDenomination } from '../../../../shared/constants/common';

const AssetList = ({ onClickAsset }) => {
const [showDetectedTokens, setShowDetectedTokens] = useState(false);
Expand Down Expand Up @@ -66,9 +83,63 @@ const AssetList = ({ onClickAsset }) => {
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
);

const contractExchangeRates = useSelector(
getTokenExchangeRates,
shallowEqual,
);
const conversionRate = useSelector(getConversionRate);
const currentCurrency = useSelector(getCurrentCurrency);

const nativeFiat = getValueFromWeiHex({
value: balance,
toCurrency: currentCurrency,
conversionRate,
numberOfDecimals: 2,
});

const shouldHideZeroBalanceTokens = useSelector(
getShouldHideZeroBalanceTokens,
);
// use `isEqual` comparison function because the token array is serialized
// from the background so it has a new reference with each background update,
// even if the tokens haven't changed
const tokens = useSelector(getTokens, isEqual);
const { loading, tokensWithBalances } = useTokenTracker(
tokens,
true,
shouldHideZeroBalanceTokens,
);

const dollarBalances = tokensWithBalances.map((token) => {
const contractExchangeTokenKey = Object.keys(contractExchangeRates).find(
(key) => isEqualCaseInsensitive(key, token.address),
);
const tokenExchangeRate =
(contractExchangeTokenKey &&
contractExchangeRates[contractExchangeTokenKey]) ??
0;

const fiat = getTokenFiatAmount(
tokenExchangeRate,
conversionRate,
currentCurrency,
token.string,
token.symbol,
false,
false,
);

return fiat;
});

const totalFiat = formatCurrency(
sumDecimals(nativeFiat, ...dollarBalances).toString(10),
currentCurrency,
);

return (
<>
{process.env.MULTICHAIN ? <BalanceOverview /> : null}
{process.env.MULTICHAIN ? <BalanceOverview balance={totalFiat} /> : null}
<TokenListItem
onClick={() => onClickAsset(nativeCurrency)}
title={nativeCurrency}
Expand All @@ -80,6 +151,8 @@ const AssetList = ({ onClickAsset }) => {
tokenImage={balanceIsLoading ? null : primaryTokenImage}
/>
<TokenList
tokens={tokensWithBalances}
loading={loading}
onTokenClick={(tokenAddress) => {
onClickAsset(tokenAddress);
trackEvent({
Expand Down
29 changes: 7 additions & 22 deletions ui/components/app/token-list/token-list.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import { isEqual } from 'lodash';

import { useSelector } from 'react-redux';
import TokenCell from '../token-cell';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useTokenTracker } from '../../../hooks/useTokenTracker';
import { getShouldHideZeroBalanceTokens } from '../../../selectors';
import { getTokens } from '../../../ducks/metamask/metamask';
import { Box } from '../../component-library';
import {
AlignItems,
Display,
JustifyContent,
} from '../../../helpers/constants/design-system';

export default function TokenList({ onTokenClick }) {
export default function TokenList({ onTokenClick, tokens, loading = false }) {
const t = useI18nContext();
const shouldHideZeroBalanceTokens = useSelector(
getShouldHideZeroBalanceTokens,
);
// use `isEqual` comparison function because the token array is serialized
// from the background so it has a new reference with each background update,
// even if the tokens haven't changed
const tokens = useSelector(getTokens, isEqual);
const { loading, tokensWithBalances } = useTokenTracker(
tokens,
true,
shouldHideZeroBalanceTokens,
);

if (loading) {
return (
<Box
Expand All @@ -45,13 +28,15 @@ export default function TokenList({ onTokenClick }) {

return (
<div>
{tokensWithBalances.map((tokenData, index) => {
return <TokenCell key={index} {...tokenData} onClick={onTokenClick} />;
})}
{tokens.map((tokenData, index) => (
<TokenCell key={index} {...tokenData} onClick={onTokenClick} />
))}
</div>
);
}

TokenList.propTypes = {
onTokenClick: PropTypes.func.isRequired,
tokens: PropTypes.array.isRequired,
loading: PropTypes.bool,
};
72 changes: 21 additions & 51 deletions ui/components/multichain/balance-overview/balance-overview.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { Box, ButtonSecondary, IconName } from '../../component-library';
import { Box, ButtonSecondary, IconName, Text } from '../../component-library';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import {
getMmiPortfolioEnabled,
Expand All @@ -22,12 +23,9 @@ import {
getCurrentChainId,
getMetaMetricsId,
///: END:ONLY_INCLUDE_IN
getSelectedAccountCachedBalance,
isBalanceCached,
} from '../../../selectors';
import Spinner from '../../ui/spinner';
import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-display';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import {
AlignItems,
Display,
Expand All @@ -39,15 +37,14 @@ import {
import { CURRENCY_SYMBOLS } from '../../../../shared/constants/network';
///: END:ONLY_INCLUDE_IN

export const BalanceOverview = () => {
export const BalanceOverview = ({ balance }) => {
const trackEvent = useContext(MetaMetricsContext);
const t = useI18nContext();
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
const metaMetricsId = useSelector(getMetaMetricsId);
const chainId = useSelector(getCurrentChainId);
///: END:ONLY_INCLUDE_IN
const balanceIsCached = useSelector(isBalanceCached);
const balance = useSelector(getSelectedAccountCachedBalance);

///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
const mmiPortfolioEnabled = useSelector(getMmiPortfolioEnabled);
Expand Down Expand Up @@ -85,56 +82,25 @@ export const BalanceOverview = () => {
>
<Box className="token-balance-overview__balance">
{balance ? (
<UserPreferencedCurrencyDisplay
<Text
variant={TextVariant.headingLg}
color={TextColor.textDefault}
className={classnames({
'token-balance-overview__cached-secondary-balance':
balanceIsCached,
'token-balance-overview__secondary-balance': !balanceIsCached,
})}
data-testid="token-balance-overview__secondary-currency"
value={balance}
type={PRIMARY}
ethNumberOfDecimals={4}
textProps={{
variant: TextVariant.headingLg,
color: TextColor.textDefault,
}}
suffixProps={{
variant: TextVariant.headingLg,
color: TextColor.textDefault,
}}
>
{balance}
</Text>
) : (
<Spinner
color="var(--color-secondary-default)"
className="loading-overlay__spinner"
/>
)}

{balanceIsCached ? (
<span className="token-balance-overview__cached-star">*</span>
) : null}
<Box className="token-balance-overview__primary-container">
{balance ? (
<UserPreferencedCurrencyDisplay
className={classnames('token-balance-overview__primary-balance', {
'token-balance-overview__cached-balance': balanceIsCached,
})}
data-testid="token-balance-overview__primary-currency"
value={balance}
type={SECONDARY}
ethNumberOfDecimals={4}
hideTitle
textProps={{
variant: TextVariant.bodyMd,
color: TextColor.textAlternative,
}}
suffixProps={{
variant: TextVariant.bodyMd,
color: TextColor.textAlternative,
}}
/>
) : (
<Spinner
color="var(--color-secondary-default)"
className="loading-overlay__spinner"
/>
)}
{balanceIsCached ? (
<span className="token-balance-overview__cached-star">*</span>
) : null}
</Box>
</Box>
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Expand Down Expand Up @@ -172,3 +138,7 @@ export const BalanceOverview = () => {
</Box>
);
};

BalanceOverview.propTypes = {
balance: PropTypes.string.isRequired,
};

0 comments on commit 931a191

Please sign in to comment.