diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1657db0d3..58c1be582 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -843,6 +843,9 @@ "message": "Hide $1", "description": "$1 is the symbol for a token (e.g. 'DAI')" }, + "hideZeroBalanceTokens": { + "message": "Hide Tokens Without Balance" + }, "history": { "message": "History" }, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 539978bca..240aaaa18 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -61,6 +61,7 @@ export default class PreferencesController { autoLockTimeLimit: undefined, showFiatInTestnets: false, useNativeCurrencyAsPrimaryCurrency: true, + hideZeroBalanceTokens: false, }, completedOnboarding: false, // ENS decentralized website resolution diff --git a/ui/app/components/app/token-list/token-list.js b/ui/app/components/app/token-list/token-list.js index 298dda081..3fcc71d38 100644 --- a/ui/app/components/app/token-list/token-list.js +++ b/ui/app/components/app/token-list/token-list.js @@ -6,17 +6,31 @@ import { useSelector } from 'react-redux'; import TokenCell from '../token-cell'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { useTokenTracker } from '../../../hooks/useTokenTracker'; -import { getAssetImages } from '../../../selectors'; -import { getTokens } from '../../../ducks/metamask/metamask'; +import { + getAssetImages, + getShouldHideZeroBalanceTokens, +} from '../../../selectors'; +import { + getTokens, + getTokensWithBalance, +} from '../../../ducks/metamask/metamask'; export default function TokenList({ onTokenClick }) { const t = useI18nContext(); const assetImages = useSelector(getAssetImages); + 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); + const tokensWithBalance = useSelector(getTokensWithBalance, isEqual); + + const { loading, tokensWithBalances } = useTokenTracker( + shouldHideZeroBalanceTokens ? tokensWithBalance : tokens, + true, + ); if (loading) { return ( diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js index 31c808877..63591e5ce 100644 --- a/ui/app/ducks/metamask/metamask.js +++ b/ui/app/ducks/metamask/metamask.js @@ -386,3 +386,6 @@ export const getUnconnectedAccountAlertShown = (state) => state.metamask.unconnectedAccountAlertShownOrigins; export const getTokens = (state) => state.metamask.tokens; + +export const getTokensWithBalance = (state) => + state.metamask.tokens.filter((token) => Number(token.balance) > 0); diff --git a/ui/app/pages/settings/settings-tab/settings-tab.component.js b/ui/app/pages/settings/settings-tab/settings-tab.component.js index 74e9c8563..71aae33a6 100644 --- a/ui/app/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/app/pages/settings/settings-tab/settings-tab.component.js @@ -41,6 +41,8 @@ export default class SettingsTab extends PureComponent { nativeCurrency: PropTypes.string, useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func, + hideZeroBalanceTokens: PropTypes.bool, + setHideZeroBalanceTokens: PropTypes.func, }; renderCurrentConversion() { @@ -101,12 +103,35 @@ export default class SettingsTab extends PureComponent { ); } + renderHideZeroBalanceTokensOptIn() { + const { t } = this.context; + const { hideZeroBalanceTokens, setHideZeroBalanceTokens } = this.props; + + return ( +
+
+ {t('hideZeroBalanceTokens')} +
+
+
+ setHideZeroBalanceTokens(!value)} + offLabel={t('off')} + onLabel={t('on')} + /> +
+
+
+ ); + } + renderBlockieOptIn() { const { t } = this.context; const { useBlockie, setUseBlockie } = this.props; return ( -
+
{this.context.t('blockiesIdenticon')}
@@ -192,6 +217,7 @@ export default class SettingsTab extends PureComponent { {this.renderUsePrimaryCurrencyOptions()} {this.renderCurrentLocale()} {this.renderBlockieOptIn()} + {this.renderHideZeroBalanceTokensOptIn()}
); } diff --git a/ui/app/pages/settings/settings-tab/settings-tab.container.js b/ui/app/pages/settings/settings-tab/settings-tab.container.js index ce8de1363..2e964e551 100644 --- a/ui/app/pages/settings/settings-tab/settings-tab.container.js +++ b/ui/app/pages/settings/settings-tab/settings-tab.container.js @@ -4,6 +4,7 @@ import { setUseBlockie, updateCurrentLocale, setUseNativeCurrencyAsPrimaryCurrencyPreference, + setHideZeroBalanceTokens, setParticipateInMetaMetrics, } from '../../../store/actions'; import { getPreferences } from '../../../selectors'; @@ -21,7 +22,10 @@ const mapStateToProps = (state) => { useBlockie, currentLocale, } = metamask; - const { useNativeCurrencyAsPrimaryCurrency } = getPreferences(state); + const { + useNativeCurrencyAsPrimaryCurrency, + hideZeroBalanceTokens, + } = getPreferences(state); return { warning, @@ -31,6 +35,7 @@ const mapStateToProps = (state) => { nativeCurrency, useBlockie, useNativeCurrencyAsPrimaryCurrency, + hideZeroBalanceTokens, }; }; @@ -44,6 +49,8 @@ const mapDispatchToProps = (dispatch) => { }, setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), + setHideZeroBalanceTokens: (value) => + dispatch(setHideZeroBalanceTokens(value)), }; }; diff --git a/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js b/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js index da3630a24..969044da7 100644 --- a/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js +++ b/ui/app/pages/settings/settings-tab/tests/settings-tab.test.js @@ -13,6 +13,7 @@ describe('Settings Tab', function () { setUseBlockie: sinon.spy(), updateCurrentLocale: sinon.spy(), setUseNativeCurrencyAsPrimaryCurrencyPreference: sinon.spy(), + setHideZeroBalanceTokens: sinon.spy(), warning: '', currentLocale: 'en', useBlockie: false, @@ -51,9 +52,16 @@ describe('Settings Tab', function () { }); it('toggles blockies', function () { - const toggleBlockies = wrapper.find({ type: 'checkbox' }); + const toggleBlockies = wrapper.find('#blockie-optin input'); toggleBlockies.simulate('click'); assert(props.setUseBlockie.calledOnce); }); + + it('toggles hiding zero balance', function () { + const toggleBlockies = wrapper.find('#toggle-zero-balance input'); + + toggleBlockies.simulate('click'); + assert(props.setHideZeroBalanceTokens.calledOnce); + }); }); diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index af6e942ff..a2bbbc5a8 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -320,6 +320,11 @@ export function getShouldShowFiat(state) { return Boolean(isMainNet || showFiatInTestnets); } +export function getShouldHideZeroBalanceTokens(state) { + const { hideZeroBalanceTokens } = getPreferences(state); + return hideZeroBalanceTokens; +} + export function getAdvancedInlineGasShown(state) { return Boolean(state.metamask.featureFlags.advancedInlineGas); } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index d0dae9c76..56fcd3473 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -2046,6 +2046,10 @@ export function setUseNativeCurrencyAsPrimaryCurrencyPreference(value) { return setPreference('useNativeCurrencyAsPrimaryCurrency', value); } +export function setHideZeroBalanceTokens(value) { + return setPreference('hideZeroBalanceTokens', value); +} + export function setShowFiatConversionOnTestnetsPreference(value) { return setPreference('showFiatInTestnets', value); }