Skip to content

Commit

Permalink
[issue-1234] Sort the token by the balance on mobile app
Browse files Browse the repository at this point in the history
  • Loading branch information
dominhquang committed Feb 17, 2024
1 parent 91ffbc7 commit deb5814
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 61 deletions.
40 changes: 38 additions & 2 deletions src/components/Modal/common/TokenSelector.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,12 +8,18 @@ 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 { BN_ZERO } from 'utils/chainBalances';
import { getInputValuesFromString } from 'screens/Transaction/SendFund/Amount';

export type TokenItemType = {
name: string;
slug: string;
symbol: string;
originChain: string;
free?: BigN;
price?: number;
decimals?: number;
};

interface Props {
Expand All @@ -32,8 +38,30 @@ interface Props {
acceptDefaultValue?: boolean;
onCloseAccountSelector?: () => void;
showAddBtn?: boolean;
isShowBalance?: boolean;
}

const convertTokenBalance = (a: TokenItemType, b: TokenItemType) => {
const aFree = new BigN(a.free || BN_ZERO);
const bFree = new BigN(b.free || BN_ZERO);
const aResult = new BigN(a.free || BN_ZERO).multipliedBy(new BigN(a.price || BN_ZERO));
const bResult = new BigN(b.free || BN_ZERO).multipliedBy(new BigN(b.price || BN_ZERO));

if (aResult.eq(bResult)) {
return Number(getInputValuesFromString(aFree.toFixed(), a.decimals || 0)) -
Number(getInputValuesFromString(bFree.toFixed(), b.decimals || 0)) >
0
? -1
: 1;
}

return Number(getInputValuesFromString(aResult.toFixed(), a.decimals || 0)) -
Number(getInputValuesFromString(bResult.toFixed(), b.decimals || 0)) >
0
? -1
: 1;
};

export const TokenSelector = ({
items,
selectedValueMap,
Expand All @@ -50,19 +78,26 @@ export const TokenSelector = ({
acceptDefaultValue,
onCloseAccountSelector,
showAddBtn = true,
isShowBalance,
}: Props) => {
const navigation = useNavigation<RootNavigationProps>();
useEffect(() => {
setAdjustPan();
}, []);

const filteredItems = useMemo((): TokenItemType[] => {
return items.sort((a, b) => {
return convertTokenBalance(a, b);
});
}, [items]);

const _onSelectItem = (item: TokenItemType) => {
onSelectItem && onSelectItem(item);
};

return (
<FullSizeSelectModal
items={items}
items={filteredItems}
selectedValueMap={selectedValueMap}
onBackButtonPress={() => tokenSelectorRef?.current?.onCloseModal()}
selectModalType={'single'}
Expand All @@ -78,6 +113,7 @@ export const TokenSelector = ({
renderCustomItem={renderCustomItem}
defaultValue={defaultValue}
acceptDefaultValue={acceptDefaultValue}
isShowBalance={isShowBalance}
renderListEmptyComponent={() => {
return (
<EmptyList
Expand Down
32 changes: 28 additions & 4 deletions src/components/TokenSelectItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import Text from 'components/Text';
import { ColorMap } from 'styles/color';
import { FontMedium, FontSemiBold } from 'styles/sharedStyles';
import { CheckCircle } from 'phosphor-react-native';
import { Icon, Typography } from 'components/design-system-ui';
import { Icon, Typography, Number } from 'components/design-system-ui';
import { useSubWalletTheme } from 'hooks/useSubWalletTheme';
import { ThemeTypes } from 'styles/themes';
import BigN from 'bignumber.js';
import { BN_ZERO } from 'utils/chainBalances';

interface Props extends TouchableOpacityProps {
symbol: string;
Expand All @@ -19,6 +21,10 @@ interface Props extends TouchableOpacityProps {
onSelectNetwork: () => void;
defaultItemKey?: string;
iconSize?: number;
free?: BigN;
decimals?: number;
price?: number;
isShowBalance?: boolean;
}

export const TokenSelectItem = ({
Expand All @@ -31,6 +37,10 @@ export const TokenSelectItem = ({
onSelectNetwork,
defaultItemKey,
iconSize = 40,
free,
decimals,
price,
isShowBalance = false,
}: Props) => {
const theme = useSubWalletTheme().swThemes;
const styles = useMemo(() => createStyle(theme), [theme]);
Expand All @@ -52,11 +62,24 @@ export const TokenSelectItem = ({
</View>
</View>

{isSelected && (
<View style={styles.selectedIconWrapper}>
<Icon phosphorIcon={CheckCircle} weight={'fill'} size={'sm'} iconColor={theme.colorSuccess} />
{typeof free !== undefined && isShowBalance && (
<View style={{ alignItems: 'flex-end' }}>
<Number size={16} value={free || BN_ZERO} decimal={decimals || 0} />
<Number
prefix={'$'}
size={12}
unitOpacity={0.45}
intOpacity={0.45}
decimalOpacity={0.45}
value={new BigN(free || 0).multipliedBy(new BigN(price || 0))}
decimal={decimals || 0}
/>
</View>
)}

<View style={styles.selectedIconWrapper}>
{isSelected && <Icon phosphorIcon={CheckCircle} weight={'fill'} size={'sm'} iconColor={theme.colorSuccess} />}
</View>
</View>
</TouchableOpacity>
);
Expand All @@ -81,6 +104,7 @@ function createStyle(theme: ThemeTypes) {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
paddingRight: theme.paddingXS,
},

itemTextStyle: {
Expand Down
6 changes: 5 additions & 1 deletion src/components/common/CancelUnstakeItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ export const CancelUnstakeItem = ({ item, isSelected, onPress }: Props) => {
size={theme.fontSize}
textStyle={{ ...FontSemiBold }}
/>
{isSelected && <Icon phosphorIcon={CheckCircle} weight={'fill'} size={'sm'} iconColor={theme.colorSuccess} />}
{isSelected ? (
<Icon phosphorIcon={CheckCircle} weight={'fill'} size={'sm'} iconColor={theme.colorSuccess} />
) : (
<View style={{ width: 20 }} />
)}
</View>
</TouchableOpacity>
);
Expand Down
3 changes: 3 additions & 0 deletions src/components/common/SelectModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface Props<T> {
onCloseModal?: () => void;
onModalOpened?: () => void;
rightIconOption?: RightIconOpt;
isShowBalance?: boolean;
level?: number;
}
const LOADING_TIMEOUT = Platform.OS === 'ios' ? 20 : 100;
Expand Down Expand Up @@ -93,6 +94,7 @@ function _SelectModal<T>(selectModalProps: Props<T>, ref: ForwardedRef<any>) {
onCloseModal: _onCloseModal,
onModalOpened,
rightIconOption,
isShowBalance,
level,
} = selectModalProps;
const chainInfoMap = useSelector((root: RootState) => root.chainStore.chainInfoMap);
Expand Down Expand Up @@ -209,6 +211,7 @@ function _SelectModal<T>(selectModalProps: Props<T>, ref: ForwardedRef<any>) {
selectedValueMap={selectedValueMap}
onSelectItem={_onSelectItem}
onCloseModal={() => closeModalAfterSelect && modalBaseV2Ref?.current?.close()}
isShowBalance={isShowBalance}
/>
);
} else if (selectModalItemType === 'chain') {
Expand Down
10 changes: 8 additions & 2 deletions src/components/common/SelectModal/parts/TokenSelectItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ interface Props<T> {
selectedValueMap: Record<string, boolean>;
onSelectItem?: (item: T) => void;
onCloseModal?: () => void;
isShowBalance?: boolean;
}

export function _TokenSelectItem<T>({ item, selectedValueMap, onSelectItem, onCloseModal }: Props<T>) {
export function _TokenSelectItem<T>({ item, selectedValueMap, onSelectItem, onCloseModal, isShowBalance }: Props<T>) {
const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap);
const { symbol, originChain, slug, name } = item as TokenItemType;
const { symbol, originChain, slug, name, free, price, decimals } = item as TokenItemType;

return (
<TokenSelectItem
key={`${symbol}-${originChain}`}
Expand All @@ -23,10 +25,14 @@ export function _TokenSelectItem<T>({ item, selectedValueMap, onSelectItem, onCl
logoKey={slug.toLowerCase()}
subLogoKey={originChain}
isSelected={!!selectedValueMap[slug]}
free={free}
isShowBalance={isShowBalance}
onSelectNetwork={() => {
onSelectItem && onSelectItem(item);
onCloseModal && onCloseModal();
}}
decimals={decimals}
price={price}
/>
);
}
51 changes: 27 additions & 24 deletions src/hooks/screen/Staking/useGetSupportedStakingTokens.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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 => {
Expand All @@ -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,
});
}
}
});
Expand All @@ -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]);
}
2 changes: 1 addition & 1 deletion src/screens/Home/Crypto/BuyToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading

0 comments on commit deb5814

Please sign in to comment.