From efdb56b2f3c731ae749a383e5a7d0a16ab6606ae Mon Sep 17 00:00:00 2001 From: dominhquang Date: Mon, 19 Feb 2024 10:38:18 +0700 Subject: [PATCH 1/2] [issue-1234] Sort the token by the balance on mobile app --- src/components/Modal/common/TokenSelector.tsx | 48 ++++++++++++- src/components/TokenSelectItem.tsx | 28 ++++++-- .../common/CancelUnstakeItem/index.tsx | 6 +- src/components/common/SelectModal/index.tsx | 70 ++++++++++-------- .../SelectModal/parts/TokenSelectItem.tsx | 15 +++- .../Staking/useGetSupportedStakingTokens.ts | 51 ++++++------- src/hooks/screen/useAccountBalance.ts | 72 ++++++++++++++----- src/screens/Home/Crypto/BuyToken.tsx | 2 +- src/screens/Transaction/SendFund/index.tsx | 66 ++++++++++------- src/screens/Transaction/Stake/index.tsx | 2 - src/types/balance.ts | 1 + src/utils/sort/token.ts | 12 ++++ 12 files changed, 265 insertions(+), 108 deletions(-) create mode 100644 src/utils/sort/token.ts diff --git a/src/components/Modal/common/TokenSelector.tsx b/src/components/Modal/common/TokenSelector.tsx index e8445cf58..4fd7d70f1 100644 --- a/src/components/Modal/common/TokenSelector.tsx +++ b/src/components/Modal/common/TokenSelector.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { ListRenderItemInfo } from 'react-native'; import i18n from 'utils/i18n/i18n'; import { FullSizeSelectModal } from 'components/common/SelectModal'; @@ -8,12 +8,22 @@ import { EmptyList } from 'components/EmptyList'; import { MagnifyingGlass } from 'phosphor-react-native'; import { useNavigation } from '@react-navigation/native'; import { RootNavigationProps } from 'routes/index'; +import BigN from 'bignumber.js'; +import useAccountBalance from 'hooks/screen/useAccountBalance'; +import useChainAssets from 'hooks/chain/useChainAssets'; +import { _isAssetFungibleToken } from '@subwallet/extension-base/services/chain-service/utils'; +import { sortTokenByValue } from 'utils/sort/token'; +import { useSelector } from 'react-redux'; +import { RootState } from 'stores/index'; export type TokenItemType = { name: string; slug: string; symbol: string; originChain: string; + free?: BigN; + price?: number; + decimals?: number; }; interface Props { @@ -32,8 +42,12 @@ interface Props { acceptDefaultValue?: boolean; onCloseAccountSelector?: () => void; showAddBtn?: boolean; + isShowBalance?: boolean; + selectedAccount?: string; } +const convertChainActivePriority = (active?: boolean) => (active ? 1 : 0); + export const TokenSelector = ({ items, selectedValueMap, @@ -50,11 +64,39 @@ export const TokenSelector = ({ acceptDefaultValue, onCloseAccountSelector, showAddBtn = true, + isShowBalance, + selectedAccount, }: Props) => { const navigation = useNavigation(); useEffect(() => { setAdjustPan(); }, []); + const { chainStateMap } = useSelector((state: RootState) => state.chainStore); + const assetRegistry = useChainAssets({}).chainAssetRegistry; + + const { tokenBalanceMap } = useAccountBalance(true, true, selectedAccount); + + const filteredItems = useMemo((): TokenItemType[] => { + const raw = items.filter(item => { + const chainAsset = assetRegistry[item.slug]; + + return chainAsset ? _isAssetFungibleToken(chainAsset) : false; + }); + + isShowBalance && + raw.sort((a, b) => { + return sortTokenByValue(tokenBalanceMap[a.slug], tokenBalanceMap[b.slug]); + }); + + raw.sort((a, b) => { + return ( + convertChainActivePriority(chainStateMap[b.originChain]?.active) - + convertChainActivePriority(chainStateMap[a.originChain]?.active) + ); + }); + + return raw; + }, [assetRegistry, chainStateMap, isShowBalance, items, tokenBalanceMap]); const _onSelectItem = (item: TokenItemType) => { onSelectItem && onSelectItem(item); @@ -62,7 +104,7 @@ export const TokenSelector = ({ return ( { return ( void; defaultItemKey?: string; iconSize?: number; + isShowBalance?: boolean; + tokenBalance?: TokenBalanceItemType; } export const TokenSelectItem = ({ @@ -31,6 +35,8 @@ export const TokenSelectItem = ({ onSelectNetwork, defaultItemKey, iconSize = 40, + isShowBalance = false, + tokenBalance, }: Props) => { const theme = useSubWalletTheme().swThemes; const styles = useMemo(() => createStyle(theme), [theme]); @@ -52,11 +58,24 @@ export const TokenSelectItem = ({ - {isSelected && ( - - + {!!isShowBalance && tokenBalance && tokenBalance.isChainEnabled && ( + + + )} + + + {isSelected && } + ); @@ -81,6 +100,7 @@ function createStyle(theme: ThemeTypes) { flexDirection: 'row', alignItems: 'center', flex: 1, + paddingRight: theme.paddingXS, }, itemTextStyle: { diff --git a/src/components/common/CancelUnstakeItem/index.tsx b/src/components/common/CancelUnstakeItem/index.tsx index a37f488f5..7cc314dfa 100644 --- a/src/components/common/CancelUnstakeItem/index.tsx +++ b/src/components/common/CancelUnstakeItem/index.tsx @@ -63,7 +63,11 @@ export const CancelUnstakeItem = ({ item, isSelected, onPress }: Props) => { size={theme.fontSize} textStyle={{ ...FontSemiBold }} /> - {isSelected && } + {isSelected ? ( + + ) : ( + + )} ); diff --git a/src/components/common/SelectModal/index.tsx b/src/components/common/SelectModal/index.tsx index 677508965..465d8c163 100644 --- a/src/components/common/SelectModal/index.tsx +++ b/src/components/common/SelectModal/index.tsx @@ -18,6 +18,7 @@ import { RootState } from 'stores/index'; import { ChainInfo } from 'types/index'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; +import useAccountBalance from 'hooks/screen/useAccountBalance'; interface Props { items: T[]; @@ -56,7 +57,9 @@ interface Props { onCloseModal?: () => void; onModalOpened?: () => void; rightIconOption?: RightIconOpt; + isShowBalance?: boolean; level?: number; + selectedAccount?: string; } const LOADING_TIMEOUT = Platform.OS === 'ios' ? 20 : 100; @@ -93,7 +96,9 @@ function _SelectModal(selectModalProps: Props, ref: ForwardedRef) { onCloseModal: _onCloseModal, onModalOpened, rightIconOption, + isShowBalance, level, + selectedAccount, } = selectModalProps; const chainInfoMap = useSelector((root: RootState) => root.chainStore.chainInfoMap); const [isOpen, setOpen] = useState(false); @@ -105,7 +110,7 @@ function _SelectModal(selectModalProps: Props, ref: ForwardedRef) { _onCloseModal && _onCloseModal(); }, [_onCloseModal]); const defaultItem = items[0]; - + const { tokenBalanceMap } = useAccountBalance(true, true, selectedAccount); const theme = useSubWalletTheme().swThemes; useEffect(() => { @@ -190,40 +195,45 @@ function _SelectModal(selectModalProps: Props, ref: ForwardedRef) { } }; - const renderItem = ({ item }: ListRenderItemInfo) => { - if (selectModalItemType === 'account') { - return ( - <> - ) => { + if (selectModalItemType === 'account') { + return ( + <> + closeModalAfterSelect && modalBaseV2Ref?.current?.close()} + /> + + ); + } else if (selectModalItemType === 'token') { + return ( + <_TokenSelectItem item={item} selectedValueMap={selectedValueMap} onSelectItem={_onSelectItem} onCloseModal={() => closeModalAfterSelect && modalBaseV2Ref?.current?.close()} + isShowBalance={isShowBalance} + tokenBalance={tokenBalanceMap ? tokenBalanceMap[(item as TokenItemType).slug] : undefined} /> - - ); - } else if (selectModalItemType === 'token') { - return ( - <_TokenSelectItem - item={item} - selectedValueMap={selectedValueMap} - onSelectItem={_onSelectItem} - onCloseModal={() => closeModalAfterSelect && modalBaseV2Ref?.current?.close()} - /> - ); - } else if (selectModalItemType === 'chain') { - return ( - closeModalAfterSelect && modalBaseV2Ref?.current?.close()} - /> - ); - } else { - return <>; - } - }; + ); + } else if (selectModalItemType === 'chain') { + return ( + closeModalAfterSelect && modalBaseV2Ref?.current?.close()} + /> + ); + } else { + return <>; + } + }, + [_onSelectItem, closeModalAfterSelect, isShowBalance, selectModalItemType, selectedValueMap, tokenBalanceMap], + ); const _renderListEmptyComponent = () => { return ( diff --git a/src/components/common/SelectModal/parts/TokenSelectItem.tsx b/src/components/common/SelectModal/parts/TokenSelectItem.tsx index c0582f624..d03334261 100644 --- a/src/components/common/SelectModal/parts/TokenSelectItem.tsx +++ b/src/components/common/SelectModal/parts/TokenSelectItem.tsx @@ -3,17 +3,28 @@ import { TokenItemType } from 'components/Modal/common/TokenSelector'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import { TokenSelectItem } from 'components/TokenSelectItem'; +import { TokenBalanceItemType } from 'types/balance'; interface Props { item: T; selectedValueMap: Record; onSelectItem?: (item: T) => void; onCloseModal?: () => void; + isShowBalance?: boolean; + tokenBalance?: TokenBalanceItemType; } -export function _TokenSelectItem({ item, selectedValueMap, onSelectItem, onCloseModal }: Props) { +export function _TokenSelectItem({ + item, + selectedValueMap, + onSelectItem, + onCloseModal, + isShowBalance, + tokenBalance, +}: Props) { const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap); const { symbol, originChain, slug, name } = item as TokenItemType; + return ( ({ item, selectedValueMap, onSelectItem, onCl logoKey={slug.toLowerCase()} subLogoKey={originChain} isSelected={!!selectedValueMap[slug]} + isShowBalance={isShowBalance} onSelectNetwork={() => { onSelectItem && onSelectItem(item); onCloseModal && onCloseModal(); }} + tokenBalance={tokenBalance} /> ); } diff --git a/src/hooks/screen/Staking/useGetSupportedStakingTokens.ts b/src/hooks/screen/Staking/useGetSupportedStakingTokens.ts index 9bd4e256d..6dc00c948 100644 --- a/src/hooks/screen/Staking/useGetSupportedStakingTokens.ts +++ b/src/hooks/screen/Staking/useGetSupportedStakingTokens.ts @@ -1,4 +1,4 @@ -import { _ChainAsset, _ChainInfo } from '@subwallet/chain-list/types'; +import { _ChainInfo } from '@subwallet/chain-list/types'; import { StakingType } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson } from '@subwallet/extension-base/background/types'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/chain-service/constants'; @@ -15,7 +15,8 @@ import { ALL_KEY } from 'constants/index'; import { AccountAddressType } from 'types/index'; import { findAccountByAddress, getAccountAddressType } from 'utils/account'; import useChainAssets from 'hooks/chain/useChainAssets'; -import useChainChecker from 'hooks/chain/useChainChecker'; +import { BN_ZERO } from '@polkadot/util'; +import { TokenItemType } from 'components/Modal/common/TokenSelector'; const isChainTypeValid = (chainInfo: _ChainInfo, accounts: AccountJson[], address?: string): boolean => { const addressType = getAccountAddressType(address); @@ -43,13 +44,15 @@ export default function useGetSupportedStakingTokens( type: StakingType, address?: string, chain?: string, -): _ChainAsset[] { +): TokenItemType[] { const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap); const assetRegistryMap = useChainAssets().chainAssetRegistry; - const accounts = useSelector((state: RootState) => state.accountState.accounts); - const { checkChainConnected } = useChainChecker(); + const priceMap = useSelector((state: RootState) => state.price.priceMap); + const { balanceMap } = useSelector((root: RootState) => root.balance); + const { accounts, currentAccount } = useSelector((state: RootState) => state.accountState); return useMemo(() => { - const result: _ChainAsset[] = []; + const result: TokenItemType[] = []; + const accBalanceMap = currentAccount ? balanceMap[currentAccount.address] : undefined; if (type === StakingType.NOMINATED) { Object.values(chainInfoMap).forEach(chainInfo => { @@ -61,7 +64,14 @@ export default function useGetSupportedStakingTokens( isChainTypeValid(chainInfo, accounts, address) && (!chain || chain === ALL_KEY || chain === chainInfo.slug) ) { - result.push(assetRegistryMap[nativeTokenSlug]); + const item = assetRegistryMap[nativeTokenSlug]; + const freeBalance = accBalanceMap[item.slug]?.free || BN_ZERO; + result.push({ + ...item, + price: item.priceId ? priceMap[item.priceId] : 0, + free: accBalanceMap ? freeBalance : BN_ZERO, + decimals: item.decimals || undefined, + }); } } }); @@ -78,26 +88,19 @@ export default function useGetSupportedStakingTokens( isChainTypeValid(chainInfo, accounts, address) && (!chain || chain === ALL_KEY || chain === chainInfo.slug) ) { - result.push(assetRegistryMap[nativeTokenSlug]); + const item = assetRegistryMap[nativeTokenSlug]; + const freeBalance = accBalanceMap[item.slug]?.free || BN_ZERO; + result.push({ + ...item, + price: item.priceId ? priceMap[item.priceId] : 0, + free: accBalanceMap ? freeBalance : BN_ZERO, + decimals: item.decimals || undefined, + }); } } }); } - return result.sort((a, b) => { - if (checkChainConnected(a.originChain)) { - if (checkChainConnected(b.originChain)) { - return 0; - } else { - return -1; - } - } else { - if (checkChainConnected(b.originChain)) { - return 1; - } else { - return 0; - } - } - }); - }, [type, chainInfoMap, assetRegistryMap, accounts, address, chain, checkChainConnected]); + return result; + }, [currentAccount, balanceMap, type, chainInfoMap, assetRegistryMap, accounts, address, chain, priceMap]); } diff --git a/src/hooks/screen/useAccountBalance.ts b/src/hooks/screen/useAccountBalance.ts index 08d009f35..7a318e36c 100644 --- a/src/hooks/screen/useAccountBalance.ts +++ b/src/hooks/screen/useAccountBalance.ts @@ -9,8 +9,10 @@ import { _getAssetPriceId, _getAssetSymbol, _getChainName, + _getMultiChainAsset, _getMultiChainAssetPriceId, _getMultiChainAssetSymbol, + _getOriginChainOfAsset, _isAssetValuable, } from '@subwallet/extension-base/services/chain-service/utils'; import BigN from 'bignumber.js'; @@ -59,6 +61,7 @@ function getDefaultBalanceItem(slug: string, symbol: string, logoKey: string): T logoKey, slug, symbol, + isChainEnabled: false, }; } @@ -95,6 +98,7 @@ function getAccountBalance( assetRegistryMap: AssetRegistryStore['assetRegistry'], multiChainAssetMap: AssetRegistryStore['multiChainAssetMap'], chainInfoMap: ChainStore['chainInfoMap'], + chainStateMap: ChainStore['chainStateMap'], isShowZeroBalance: boolean, ): AccountBalanceHookType { const totalBalanceInfo: AccountBalanceHookType['totalBalanceInfo'] = { @@ -108,6 +112,17 @@ function getAccountBalance( const tokenBalanceMap: Record = {}; const tokenGroupBalanceMap: Record = {}; + const checkChainConnected = (chain: string) => { + const chainState = chainStateMap[chain]; + + if (!chainState) { + // Couldn't get chain state + return false; + } + + return chainState.active; + }; + Object.keys(tokenGroupMap).forEach(tokenGroupKey => { const tokenGroupBalanceReady: boolean[] = []; const tokenGroupNotSupport: boolean[] = []; @@ -124,18 +139,15 @@ function getAccountBalance( return; } - const balanceItem = balanceMap[address]?.[tokenSlug]; - - if (!balanceItem) { - return; - } - const tokenBalance = getDefaultTokenBalance(tokenSlug, chainAsset); const originChain = _getAssetOriginChain(chainAsset); + const balanceItem = balanceMap[address]?.[tokenSlug]; const decimals = _getAssetDecimals(chainAsset); + const chainSlug = _getOriginChainOfAsset(tokenSlug); - const isTokenBalanceReady = balanceItem.state !== APIItemState.PENDING; - const isTokenNotSupport = balanceItem.state === APIItemState.NOT_SUPPORT; + const isTokenBalanceReady = !!balanceItem && balanceItem.state !== APIItemState.PENDING; + const isTokenNotSupport = !!balanceItem && balanceItem.state === APIItemState.NOT_SUPPORT; + const isChainEnable = checkChainConnected(chainSlug); tokenGroupNotSupport.push(isTokenNotSupport); tokenGroupBalanceReady.push(isTokenBalanceReady); @@ -146,6 +158,7 @@ function getAccountBalance( tokenBalance.isReady = isTokenBalanceReady; tokenBalance.isNotSupport = isTokenNotSupport; + tokenBalance.isChainEnabled = isChainEnable; tokenBalance.chain = originChain; tokenBalance.chainDisplayName = _getChainName(chainInfoMap[originChain]); @@ -266,7 +279,9 @@ function getAccountBalance( // make sure priceId exists and token group has monetary value // todo: check if multiChainAsset has monetary value too (after Nampc updates the background) if (!tokenGroupPriceId || (assetRegistryMap[tokenGroupKey] && !_isAssetValuable(assetRegistryMap[tokenGroupKey]))) { - tokenGroupBalanceMap[tokenGroupKey] = tokenGroupBalance; + if (!tokenGroupBalance.isNotSupport) { + tokenGroupBalanceMap[tokenGroupKey] = tokenGroupBalance; + } return; } @@ -283,11 +298,13 @@ function getAccountBalance( tokenGroupBalance.priceChangeStatus = 'decrease'; } - tokenGroupBalanceMap[tokenGroupKey] = tokenGroupBalance; - totalBalanceInfo.convertedValue = totalBalanceInfo.convertedValue.plus(tokenGroupBalance.total.convertedValue); - totalBalanceInfo.converted24hValue = totalBalanceInfo.converted24hValue.plus( - tokenGroupBalance.total.pastConvertedValue, - ); + if (!tokenGroupBalance.isNotSupport) { + tokenGroupBalanceMap[tokenGroupKey] = tokenGroupBalance; + totalBalanceInfo.convertedValue = totalBalanceInfo.convertedValue.plus(tokenGroupBalance.total.convertedValue); + totalBalanceInfo.converted24hValue = totalBalanceInfo.converted24hValue.plus( + tokenGroupBalance.total.pastConvertedValue, + ); + } }); // Compute total balance change @@ -327,19 +344,34 @@ const DEFAULT_RESULT = { } as AccountBalanceHookType; export default function useAccountBalance( - tokenGroupMap: Record, lazy?: boolean, showZero?: boolean, + selectedAccount?: string, ): AccountBalanceHookType { const currentAccount = useSelector((state: RootState) => state.accountState.currentAccount); const balanceMap = useSelector((state: RootState) => state.balance.balanceMap); - const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap); + const { chainInfoMap, chainStateMap } = useSelector((state: RootState) => state.chainStore); const priceMap = useSelector((state: RootState) => state.price.priceMap); const price24hMap = useSelector((state: RootState) => state.price.price24hMap); const assetRegistryMap = useSelector((state: RootState) => state.assetRegistry.assetRegistry); const multiChainAssetMap = useSelector((state: RootState) => state.assetRegistry.multiChainAssetMap); const isShowZeroBalanceSetting = useSelector((state: RootState) => state.settings.isShowZeroBalance); + const tokenGroupMap = useMemo(() => { + return Object.values(assetRegistryMap).reduce((_tokenGroupMap: Record, chainAsset) => { + const multiChainAsset = _getMultiChainAsset(chainAsset); + const tokenGroupKey = multiChainAsset || chainAsset.slug; + + if (_tokenGroupMap[tokenGroupKey]) { + _tokenGroupMap[tokenGroupKey].push(chainAsset.slug); + } else { + _tokenGroupMap[tokenGroupKey] = [chainAsset.slug]; + } + + return _tokenGroupMap; + }, {}); + }, [assetRegistryMap]); + const isShowZeroBalance = useMemo(() => { return showZero || isShowZeroBalanceSetting; }, [isShowZeroBalanceSetting, showZero]); @@ -348,7 +380,7 @@ export default function useAccountBalance( lazy ? DEFAULT_RESULT : getAccountBalance( - currentAccount?.address || '', + selectedAccount || currentAccount?.address || '', tokenGroupMap, balanceMap, priceMap, @@ -356,6 +388,7 @@ export default function useAccountBalance( assetRegistryMap, multiChainAssetMap, chainInfoMap, + chainStateMap, isShowZeroBalance, ), ); @@ -364,7 +397,7 @@ export default function useAccountBalance( const timeoutID = setTimeout(() => { setResult( getAccountBalance( - currentAccount?.address || '', + selectedAccount || currentAccount?.address || '', tokenGroupMap, balanceMap, priceMap, @@ -372,6 +405,7 @@ export default function useAccountBalance( assetRegistryMap, multiChainAssetMap, chainInfoMap, + chainStateMap, isShowZeroBalance, ), ); @@ -381,11 +415,13 @@ export default function useAccountBalance( assetRegistryMap, balanceMap, chainInfoMap, + chainStateMap, currentAccount, isShowZeroBalance, multiChainAssetMap, price24hMap, priceMap, + selectedAccount, tokenGroupMap, ]); diff --git a/src/screens/Home/Crypto/BuyToken.tsx b/src/screens/Home/Crypto/BuyToken.tsx index fffccb7ca..f6317ae1e 100644 --- a/src/screens/Home/Crypto/BuyToken.tsx +++ b/src/screens/Home/Crypto/BuyToken.tsx @@ -4,7 +4,7 @@ import { Button, Icon, PageIcon, Typography } from 'components/design-system-ui' import { ShoppingCartSimple } from 'phosphor-react-native'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { AccountSelector } from 'components/Modal/common/AccountSelectorNew'; -import { TokenSelector } from 'components/Modal/common/TokenSelectorNew'; +import { TokenSelector } from 'components/Modal/common/TokenSelector'; import useBuyToken from 'hooks/screen/Home/Crypto/useBuyToken'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; diff --git a/src/screens/Transaction/SendFund/index.tsx b/src/screens/Transaction/SendFund/index.tsx index 52e95c193..2d2816441 100644 --- a/src/screens/Transaction/SendFund/index.tsx +++ b/src/screens/Transaction/SendFund/index.tsx @@ -6,6 +6,7 @@ import { AssetSetting, ExtrinsicType } from '@subwallet/extension-base/backgroun import { AccountJson } from '@subwallet/extension-base/background/types'; import { _getAssetDecimals, + _getAssetPriceId, _getOriginChainOfAsset, _getTokenMinAmount, _isAssetFungibleToken, @@ -24,14 +25,14 @@ import { SendFundProps } from 'routes/transaction/transactionAction'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import { getMaxTransfer, makeCrossChainTransfer, makeTransfer, saveRecentAccountId } from 'messaging/index'; -import { findAccountByAddress } from 'utils/account'; +import { findAccountByAddress, getAccountAddressType } from 'utils/account'; import { findNetworkJsonByGenesisHash } from 'utils/getNetworkJsonByGenesisHash'; import { balanceFormatter, formatBalance, formatNumber } from 'utils/number'; import useGetChainPrefixBySlug from 'hooks/chain/useGetChainPrefixBySlug'; import { TokenItemType, TokenSelector } from 'components/Modal/common/TokenSelector'; import useHandleSubmitTransaction, { insufficientMessages } from 'hooks/transaction/useHandleSubmitTransaction'; import { isAccountAll } from 'utils/accountAll'; -import { ChainInfo, ChainItemType } from 'types/index'; +import { AccountAddressType, ChainInfo, ChainItemType } from 'types/index'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { useToast } from 'react-native-toast-notifications'; import usePreCheckAction from 'hooks/account/usePreCheckAction'; @@ -74,6 +75,7 @@ import { FreeBalanceDisplay } from 'screens/Transaction/parts/FreeBalanceDisplay import { ModalRef } from 'types/modalRef'; import useChainAssets from 'hooks/chain/useChainAssets'; import { TransactionDone } from 'screens/Transaction/TransactionDone'; +import { BalanceItem } from '@subwallet/extension-base/types'; interface TransferFormValues extends TransactionFormValues { to: string; @@ -98,6 +100,8 @@ function getTokenItems( assetRegistry: Record, assetSettingMap: Record, multiChainAssetMap: Record, + balanceMap: Record, + priceMap: Record, tokenGroupSlug?: string, // is ether a token slug or a multiChainAsset slug ): TokenItemType[] { const account = findAccountByAddress(accounts, address); @@ -112,6 +116,7 @@ function getTokenItems( const isAccountEthereum = isEthereumAddress(address); const isSetTokenSlug = !!tokenGroupSlug && !!assetRegistry[tokenGroupSlug]; const isSetMultiChainAssetSlug = !!tokenGroupSlug && !!multiChainAssetMap[tokenGroupSlug]; + const accBalanceMap = balanceMap[address]; if (tokenGroupSlug) { if (!(isSetTokenSlug || isSetMultiChainAssetSlug)) { @@ -123,14 +128,19 @@ function getTokenItems( if (isSetTokenSlug) { if (isAssetTypeValid(chainAsset, chainInfoMap, isAccountEthereum) && isValidLedger) { - const { name, originChain, slug, symbol } = assetRegistry[tokenGroupSlug]; - + const { name, originChain, slug, symbol, decimals } = assetRegistry[tokenGroupSlug]; + const priceId = _getAssetPriceId(assetRegistry[tokenGroupSlug]); + // @ts-ignore + const freeBalance = accBalanceMap[chainAsset.slug]?.free || BN_ZERO; return [ { name, slug, symbol, originChain, + free: freeBalance, + price: priceMap[priceId] || 0, + decimals: decimals || undefined, }, ]; } else { @@ -144,10 +154,12 @@ function getTokenItems( Object.values(assetRegistry).forEach(chainAsset => { const isValidLedger = isLedger ? isAccountEthereum || validLedgerNetwork.includes(chainAsset?.originChain) : true; const isTokenFungible = _isAssetFungibleToken(chainAsset); - + // @ts-ignore + const freeBalance = accBalanceMap[chainAsset.slug]?.free || BN_ZERO; if (!(isTokenFungible && isAssetTypeValid(chainAsset, chainInfoMap, isAccountEthereum) && isValidLedger)) { return; } + const priceId = _getAssetPriceId(chainAsset); if (isSetMultiChainAssetSlug) { if (chainAsset.multiChainAsset === tokenGroupSlug) { @@ -156,6 +168,9 @@ function getTokenItems( slug: chainAsset.slug, symbol: chainAsset.symbol, originChain: chainAsset.originChain, + free: freeBalance, + price: priceMap[priceId] || 0, + decimals: chainAsset.decimals || undefined, }); } } else { @@ -164,6 +179,9 @@ function getTokenItems( slug: chainAsset.slug, symbol: chainAsset.symbol, originChain: chainAsset.originChain, + free: freeBalance, + price: priceMap[priceId] || 0, + decimals: chainAsset.decimals || undefined, }); } }); @@ -383,6 +401,8 @@ export const SendFund = ({ const { chainInfoMap, chainStateMap } = useSelector((root: RootState) => root.chainStore); const { assetSettingMap, multiChainAssetMap, xcmRefMap } = useSelector((root: RootState) => root.assetRegistry); + const priceMap = useSelector((state: RootState) => state.price.priceMap); + const { balanceMap } = useSelector((root: RootState) => root.balance); const assetRegistry = useChainAssets().chainAssetRegistry; const { accounts, isAllAccount } = useSelector((state: RootState) => state.accountState); const [maxTransfer, setMaxTransfer] = useState('0'); @@ -479,30 +499,19 @@ export const SendFund = ({ assetRegistry, assetSettingMap, multiChainAssetMap, + balanceMap, + priceMap, tokenGroupSlug, - ).sort((a, b) => { - if (checkChainConnected(a.originChain)) { - if (checkChainConnected(b.originChain)) { - return 0; - } else { - return -1; - } - } else { - if (checkChainConnected(b.originChain)) { - return 1; - } else { - return 0; - } - } - }); + ); }, [ accounts, assetRegistry, assetSettingMap, + balanceMap, chainInfoMap, - checkChainConnected, fromValue, multiChainAssetMap, + priceMap, tokenGroupSlug, ]); @@ -976,10 +985,8 @@ export const SendFund = ({ if (tokenItems.length) { let isApplyDefaultAsset = true; - + const account = findAccountByAddress(accounts, from); if (!asset) { - const account = findAccountByAddress(accounts, from); - if (account?.originGenesisHash) { const network = findNetworkJsonByGenesisHash(chainInfoMap, account.originGenesisHash); @@ -998,7 +1005,14 @@ export const SendFund = ({ } if (isApplyDefaultAsset) { - updateInfoWithTokenSlug(tokenItems[0].slug); + const a = getAccountAddressType(account?.address); + if (a === AccountAddressType.ETHEREUM) { + const _defaultItem = tokenItems.find(item => item.slug === 'ethereum-NATIVE-ETH'); + updateInfoWithTokenSlug(_defaultItem?.slug || tokenItems[0].slug); + } else { + const _defaultItem = tokenItems.find(item => item.slug === 'polkadot-NATIVE-DOT'); + updateInfoWithTokenSlug(_defaultItem?.slug || tokenItems[0].slug); + } } } }, [accounts, tokenItems, assetRegistry, setChain, chainInfoMap, getValues, setValue]); @@ -1135,6 +1149,8 @@ export const SendFund = ({ onSelectItem={_onChangeAsset} showAddBtn={false} tokenSelectorRef={tokenSelectorRef} + isShowBalance + selectedAccount={fromValue} renderSelected={() => ( } /> diff --git a/src/types/balance.ts b/src/types/balance.ts index cd234ceb3..b7b38d1cd 100644 --- a/src/types/balance.ts +++ b/src/types/balance.ts @@ -27,4 +27,5 @@ export interface TokenBalanceItemType extends SubstrateBalance { locked: BalanceValueInfo; total: BalanceValueInfo; isReady: boolean; + isChainEnabled: boolean; } diff --git a/src/utils/sort/token.ts b/src/utils/sort/token.ts new file mode 100644 index 000000000..0bccca320 --- /dev/null +++ b/src/utils/sort/token.ts @@ -0,0 +1,12 @@ +import { TokenBalanceItemType } from 'types/balance'; +import { BN_ZERO } from '@subwallet/extension-base/utils'; + +export const sortTokenByValue = (a: TokenBalanceItemType, b: TokenBalanceItemType): number => { + const convertValue = (b?.total?.convertedValue || BN_ZERO).minus(a?.total?.convertedValue || BN_ZERO).toNumber(); + + if (convertValue) { + return convertValue; + } else { + return (b?.total?.value || BN_ZERO).minus(a?.total?.value || BN_ZERO).toNumber(); + } +}; From ce24c2483a446f14b47823a91bfafa50fad717d8 Mon Sep 17 00:00:00 2001 From: dominhquang Date: Fri, 23 Feb 2024 19:58:46 +0700 Subject: [PATCH 2/2] [issue-1234] Fix bug show incorrect balance in EarningOptions screen --- src/hooks/earning/useYieldGroupInfo.ts | 4 +--- src/hooks/screen/useAccountBalance.ts | 2 +- src/screens/Home/Crypto/TokenDetailModal.tsx | 5 ++++- src/screens/Home/Crypto/TokenGroups.tsx | 6 +++--- src/screens/Home/Crypto/TokenGroupsDetail.tsx | 3 +-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/hooks/earning/useYieldGroupInfo.ts b/src/hooks/earning/useYieldGroupInfo.ts index 3647cdaa4..54b4925e0 100644 --- a/src/hooks/earning/useYieldGroupInfo.ts +++ b/src/hooks/earning/useYieldGroupInfo.ts @@ -5,7 +5,6 @@ import { BN_ZERO } from '@polkadot/util'; import { calculateReward } from '@subwallet/extension-base/services/earning-service/utils'; import { useGetChainSlugs } from 'hooks/screen/Home/useGetChainSlugs'; import useAccountBalance from 'hooks/screen/useAccountBalance'; -import useTokenGroup from 'hooks/screen/useTokenGroup'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; @@ -17,8 +16,7 @@ const useYieldGroupInfo = (): YieldGroupInfo[] => { const { assetRegistry, multiChainAssetMap } = useSelector((state: RootState) => state.assetRegistry); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const chainsByAccountType = useGetChainSlugs(); - const { tokenGroupMap } = useTokenGroup(chainsByAccountType); - const { tokenGroupBalanceMap, tokenBalanceMap } = useAccountBalance(tokenGroupMap, undefined, true); + const { tokenGroupBalanceMap, tokenBalanceMap } = useAccountBalance(true, undefined); return useMemo(() => { const result: Record = {}; diff --git a/src/hooks/screen/useAccountBalance.ts b/src/hooks/screen/useAccountBalance.ts index 7a318e36c..f18152e97 100644 --- a/src/hooks/screen/useAccountBalance.ts +++ b/src/hooks/screen/useAccountBalance.ts @@ -373,7 +373,7 @@ export default function useAccountBalance( }, [assetRegistryMap]); const isShowZeroBalance = useMemo(() => { - return showZero || isShowZeroBalanceSetting; + return showZero ?? isShowZeroBalanceSetting; }, [isShowZeroBalanceSetting, showZero]); const [result, setResult] = useState( diff --git a/src/screens/Home/Crypto/TokenDetailModal.tsx b/src/screens/Home/Crypto/TokenDetailModal.tsx index d4a96b525..ea7fb4a60 100644 --- a/src/screens/Home/Crypto/TokenDetailModal.tsx +++ b/src/screens/Home/Crypto/TokenDetailModal.tsx @@ -188,7 +188,10 @@ export const TokenDetailModal = ({ modalVisible, currentTokenInfo, tokenBalanceM diff --git a/src/screens/Home/Crypto/TokenGroups.tsx b/src/screens/Home/Crypto/TokenGroups.tsx index c2cd5b4a6..aa14dd982 100644 --- a/src/screens/Home/Crypto/TokenGroups.tsx +++ b/src/screens/Home/Crypto/TokenGroups.tsx @@ -48,12 +48,12 @@ export const TokenGroups = () => { const navigation = useNavigation(); const tokenSearchRef = useRef(); const chainsByAccountType = useGetChainSlugs(); - const { sortedTokenGroups, tokenGroupMap, sortedTokenSlugs } = useTokenGroup(chainsByAccountType); - const { tokenGroupBalanceMap, totalBalanceInfo, tokenBalanceMap } = useAccountBalance(tokenGroupMap); + const currentAccount = useSelector((state: RootState) => state.accountState.currentAccount); + const { sortedTokenGroups, sortedTokenSlugs } = useTokenGroup(chainsByAccountType); + const { tokenGroupBalanceMap, totalBalanceInfo, tokenBalanceMap } = useAccountBalance(true, undefined); const isShowBalance = useSelector((state: RootState) => state.settings.isShowBalance); const isTotalBalanceDecrease = totalBalanceInfo.change.status === 'decrease'; const [isCustomizationModalVisible, setCustomizationModalVisible] = useState(false); - const currentAccount = useSelector((state: RootState) => state.accountState.currentAccount); const { accountSelectorItems, onOpenReceive, diff --git a/src/screens/Home/Crypto/TokenGroupsDetail.tsx b/src/screens/Home/Crypto/TokenGroupsDetail.tsx index 09aeb7e4c..a12c42e3e 100644 --- a/src/screens/Home/Crypto/TokenGroupsDetail.tsx +++ b/src/screens/Home/Crypto/TokenGroupsDetail.tsx @@ -92,7 +92,7 @@ export const TokenGroupsDetail = ({ tokenBalanceMap, tokenGroupBalanceMap, isComputing: isAccountBalanceComputing, - } = useAccountBalance(tokenGroupMap, true); + } = useAccountBalance(true, true); const tokenBalanceValue = useMemo(() => { if (tokenGroupSlug) { if (tokenGroupBalanceMap[tokenGroupSlug]) { @@ -285,7 +285,6 @@ export const TokenGroupsDetail = ({ tokenBalanceMap={tokenBalanceMap} modalVisible={tokenDetailVisible} setVisible={setTokenDetailVisible} - onClickBack={onClickBack} />