diff --git a/__tests__/migration/contactMigration.test.ts b/__tests__/migration/contactMigration.test.ts index 8d96668dc1b..a4b71a9fccf 100644 --- a/__tests__/migration/contactMigration.test.ts +++ b/__tests__/migration/contactMigration.test.ts @@ -55,6 +55,10 @@ const data: { preference: PreferenceStore; contactBook } = { walletSavedList: [], watchAddressPreference: {}, testnetBalanceMap: {}, + addressSortStore: { + search: '', + sortType: 'usd', + }, }, contactBook: { '0x10b26700b0a2d3f5ef12fa250aba818ee3b43bf4': { @@ -188,6 +192,10 @@ test('should migrate when no alians', () => { walletSavedList: [], watchAddressPreference: {}, testnetBalanceMap: {}, + addressSortStore: { + search: '', + sortType: 'usd', + }, }, contactBook: { '0x10b26700b0a2d3f5ef12fa250aba818ee3b43bf4': { @@ -316,6 +324,10 @@ test('should migrate when no contacts', () => { walletSavedList: [], watchAddressPreference: {}, testnetBalanceMap: {}, + addressSortStore: { + search: '', + sortType: 'usd', + }, }, contactBook: {}, }; diff --git a/_raw/locales/en/messages.json b/_raw/locales/en/messages.json index 47ae9ad3124..81928783b9c 100644 --- a/_raw/locales/en/messages.json +++ b/_raw/locales/en/messages.json @@ -690,7 +690,11 @@ "backup-seed-phrase": "Backup Seed Phrase", "delete-all-addresses-but-keep-the-seed-phrase": "Delete all addresses, but keep the seed phrase", "delete-all-addresses-and-the-seed-phrase": "Delete all addresses and the seed phrase", - "seed-phrase-delete-title": "Delete seed phrase?" + "seed-phrase-delete-title": "Delete seed phrase?", + "sort-by-balance": "Sort by balance", + "sort-by-address-type": "Sort by address type", + "sort-by-address-note": "Sort by address note", + "sort-address": "Sort Address" }, "dashboard": { "home": { diff --git a/_raw/locales/zh_CN/messages.json b/_raw/locales/zh_CN/messages.json index 791525aaa6f..efc91d94e36 100644 --- a/_raw/locales/zh_CN/messages.json +++ b/_raw/locales/zh_CN/messages.json @@ -809,7 +809,11 @@ "seed-phrase-delete-title": "删除助记词?", "update-balance-data": "刷新所有余额", "watch-address": "观察地址", - "whitelisted-address": "白名单地址" + "whitelisted-address": "白名单地址", + "sort-by-balance": "按地址金额排序", + "sort-by-address-type": "按地址类型排序", + "sort-by-address-note": "按地址备注首字母排序", + "sort-address": "地址排序" }, "receive": { "title": "在 {{chain}} 上接收{{token}}", diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 6952bfaf557..3de9bbe0884 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -1110,6 +1110,9 @@ export class WalletController extends BaseController { getLastSelectedGasTopUpChain = preferenceService.getLastSelectedGasTopUpChain; setLastSelectedGasTopUpChain = preferenceService.setLastSelectedGasTopUpChain; + getAddressSortStoreValue = preferenceService.getAddressSortStoreValue; + setAddressSortStoreValue = preferenceService.setAddressSortStoreValue; + getLastSelectedSwapChain = swapService.getSelectedChain; setLastSelectedSwapChain = swapService.setSelectedChain; getSwap = swapService.getSwap; diff --git a/src/background/service/preference.ts b/src/background/service/preference.ts index 4f5112a0a46..801b94db766 100644 --- a/src/background/service/preference.ts +++ b/src/background/service/preference.ts @@ -91,8 +91,20 @@ export interface PreferenceStore { autoLockTime?: number; hiddenBalance?: boolean; isShowTestnet?: boolean; + addressSortStore: AddressSortStore; } +export interface AddressSortStore { + search: string; + sortType: 'usd' | 'addressType' | 'alphabet'; + lastCurrent?: string; +} + +const defaultAddressSortStore: AddressSortStore = { + search: '', + sortType: 'usd', +}; + class PreferenceService { store!: PreferenceStore; popupOpen = false; @@ -133,8 +145,14 @@ class PreferenceService { collectionStarred: [], hiddenBalance: false, isShowTestnet: false, + addressSortStore: { + ...defaultAddressSortStore, + }, }, }); + //lifetime in background + this.store.addressSortStore = { ...defaultAddressSortStore }; + if ( !this.store.locale || !LANGS.find((item) => item.code === this.store.locale) @@ -691,6 +709,19 @@ class PreferenceService { resetCurrentCoboSafeAddress = async () => { this.setCurrentAccount(this.currentCoboSafeAddress ?? null); }; + + getAddressSortStoreValue = (key: keyof AddressSortStore) => + this.store.addressSortStore[key]; + + setAddressSortStoreValue = ( + key: K, + value: AddressSortStore[K] + ) => { + this.store.addressSortStore = { + ...this.store.addressSortStore, + [key]: value, + }; + }; } export default new PreferenceService(); diff --git a/src/constant/index.ts b/src/constant/index.ts index d4e83e15be4..6f0b9a91952 100644 --- a/src/constant/index.ts +++ b/src/constant/index.ts @@ -641,7 +641,7 @@ export const KEYRING_PURPLE_LOGOS = { }; export const KEYRINGS_LOGOS = { - [KEYRING_CLASS.MNEMONIC]: LogoMnemonic, + [KEYRING_CLASS.MNEMONIC]: IconMnemonicWhite, [KEYRING_CLASS.PRIVATE_KEY]: LogoPrivateKey, [KEYRING_CLASS.WATCH]: IconWatchWhite, [HARDWARE_KEYRING_TYPES.BitBox02.type]: IconBitBox02WithBorder, diff --git a/src/ui/assets/address/checked.svg b/src/ui/assets/address/checked.svg new file mode 100644 index 00000000000..dcaa34558f3 --- /dev/null +++ b/src/ui/assets/address/checked.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/ui/assets/address/sort-by-alphabet.svg b/src/ui/assets/address/sort-by-alphabet.svg new file mode 100644 index 00000000000..47b35ba4136 --- /dev/null +++ b/src/ui/assets/address/sort-by-alphabet.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/src/ui/assets/address/sort-by-type.svg b/src/ui/assets/address/sort-by-type.svg new file mode 100644 index 00000000000..c364a683b39 --- /dev/null +++ b/src/ui/assets/address/sort-by-type.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/address/sort-by-usd-l.svg b/src/ui/assets/address/sort-by-usd-l.svg new file mode 100644 index 00000000000..1c989f6a6e3 --- /dev/null +++ b/src/ui/assets/address/sort-by-usd-l.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/ui/assets/address/sort-by-usd.svg b/src/ui/assets/address/sort-by-usd.svg new file mode 100644 index 00000000000..54d9bfcaadf --- /dev/null +++ b/src/ui/assets/address/sort-by-usd.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/walletlogo/IconMnemonic-white.svg b/src/ui/assets/walletlogo/IconMnemonic-white.svg index bd653db5ae9..667675bb0e0 100644 --- a/src/ui/assets/walletlogo/IconMnemonic-white.svg +++ b/src/ui/assets/walletlogo/IconMnemonic-white.svg @@ -1,4 +1,10 @@ - - + + + + \ No newline at end of file diff --git a/src/ui/models/accountToDisplay.ts b/src/ui/models/accountToDisplay.ts index 36726de1331..73919e249e5 100644 --- a/src/ui/models/accountToDisplay.ts +++ b/src/ui/models/accountToDisplay.ts @@ -4,12 +4,15 @@ import { RootModel } from '.'; import { DisplayedKeryring } from '@/background/service/keyring'; import { sortAccountsByBalance } from '../utils/account'; import PQueue from 'p-queue'; +import { TotalBalanceResponse } from '@/background/service/openapi'; type IDisplayedAccount = Required; export type IDisplayedAccountWithBalance = IDisplayedAccount & { balance: number; byImport?: boolean; publicKey?: string; + hdPathBasePublicKey?: string; + hdPathType?: string; }; type IState = { @@ -42,7 +45,6 @@ export const accountToDisplay = createModel()({ store.app.wallet.getAllVisibleAccounts(), store.app.wallet.getAllAlianNameByMap(), ]); - const result = await Promise.all( displayedKeyrings .map((item) => { @@ -60,9 +62,30 @@ export const accountToDisplay = createModel()({ }) .flat(1) .map(async (item) => { - let balance = await store.app.wallet.getAddressCacheBalance( - item?.address - ); + let balance: TotalBalanceResponse | null = null; + + let accountInfo = {} as { + hdPathBasePublicKey?: string; + hdPathType?: string; + }; + + await Promise.allSettled([ + store.app.wallet.getAddressCacheBalance(item?.address), + store.app.wallet.requestKeyring( + item.type, + 'getAccountInfo', + null, + item.address + ), + ]).then(([res1, res2]) => { + if (res1.status === 'fulfilled') { + balance = res1.value; + } + if (res2.status === 'fulfilled') { + accountInfo = res2.value; + } + }); + if (!balance) { balance = { total_usd_value: 0, @@ -72,6 +95,8 @@ export const accountToDisplay = createModel()({ return { ...item, balance: balance?.total_usd_value || 0, + hdPathBasePublicKey: accountInfo?.hdPathBasePublicKey, + hdPathType: accountInfo?.hdPathType, }; }) ); diff --git a/src/ui/models/preference.ts b/src/ui/models/preference.ts index 025a76737d7..92d43e691f3 100644 --- a/src/ui/models/preference.ts +++ b/src/ui/models/preference.ts @@ -1,7 +1,11 @@ import { createModel } from '@rematch/core'; import { RootModel } from '.'; import { TokenItem } from 'background/service/openapi'; -import { GasCache, addedToken } from 'background/service/preference'; +import { + AddressSortStore, + GasCache, + addedToken, +} from 'background/service/preference'; import { CHAINS_ENUM } from 'consts'; import i18n from '@/i18n'; @@ -22,6 +26,7 @@ interface PreferenceState { autoLockTime: number; hiddenBalance: boolean; isShowTestnet: boolean; + addressSortStore: AddressSortStore; } export const preference = createModel()({ @@ -44,6 +49,7 @@ export const preference = createModel()({ autoLockTime: 0, hiddenBalance: false, isShowTestnet: false, + addressSortStore: {} as AddressSortStore, } as PreferenceState, reducers: { @@ -179,6 +185,19 @@ export const preference = createModel()({ dispatch.preference.getPreference('locale'); }, + async getAddressSortStoreValue(key: keyof AddressSortStore, store?) { + const value = await store.app.wallet.getAddressSortStoreValue(key); + return value; + }, + + async setAddressSortStoreValue( + { key, value }: { key: K; value: AddressSortStore[K] }, + store? + ) { + await store.app.wallet.setAddressSortStoreValue(key, value); + dispatch.preference.getPreference('addressSortStore'); + }, + // async setOpenapiHost(value: string, store?) { // dispatch.preference.setField({ // isShowTestnet: value, diff --git a/src/ui/views/AddressManagement/SortInput.tsx b/src/ui/views/AddressManagement/SortInput.tsx new file mode 100644 index 00000000000..56067fa8e78 --- /dev/null +++ b/src/ui/views/AddressManagement/SortInput.tsx @@ -0,0 +1,61 @@ +import React, { useMemo } from 'react'; +import { useRabbySelector } from '@/ui/store'; +import { Input } from 'antd'; +import { useTranslation } from 'react-i18next'; +import IconSearch from 'ui/assets/search.svg'; +import { AddressSortPopup } from './SortPopup'; +import { useSwitch } from '@/ui/utils/switch'; + +import { ReactComponent as IconSortByUsd } from '@/ui/assets/address/sort-by-usd.svg'; +import { ReactComponent as IconSortByType } from '@/ui/assets/address/sort-by-type.svg'; +import { ReactComponent as IconSortByAlphabet } from '@/ui/assets/address/sort-by-alphabet.svg'; +import { AddressSortStore } from '@/background/service/preference'; + +export const AddressSortIconMapping: Record< + AddressSortStore['sortType'], + React.FC> +> = { + usd: IconSortByUsd, + addressType: IconSortByType, + alphabet: IconSortByAlphabet, +}; + +export const SortInput = ({ + value, + onChange, +}: { + value: string; + onChange: React.ChangeEventHandler; +}) => { + const { t } = useTranslation(); + const sortType = useRabbySelector( + (s) => s.preference.addressSortStore.sortType + ); + const { on, turnOff, turnOn } = useSwitch(false); + + const SortIcon = useMemo(() => { + const Icon = AddressSortIconMapping[sortType]; + return ; + }, [sortType]); + + return ( + <> +
+
+ {/* */} + {SortIcon} +
+ } + suffix={null} + onChange={onChange} + value={value} + allowClear + /> +
+ + + ); +}; diff --git a/src/ui/views/AddressManagement/SortPopup.tsx b/src/ui/views/AddressManagement/SortPopup.tsx new file mode 100644 index 00000000000..34bf0855d74 --- /dev/null +++ b/src/ui/views/AddressManagement/SortPopup.tsx @@ -0,0 +1,80 @@ +import { Item, Popup } from '@/ui/component'; +import React, { useMemo } from 'react'; +import ImgSortByUsd from '@/ui/assets/address/sort-by-usd-l.svg'; +import ImgSortByType from '@/ui/assets/address/sort-by-type.svg'; +import ImgSortByAlphabet from '@/ui/assets/address/sort-by-alphabet.svg'; +import ImgChecked from '@/ui/assets/address/checked.svg'; + +import { useRabbyDispatch, useRabbySelector } from '@/ui/store'; +import { AddressSortStore } from '@/background/service/preference'; +import { useTranslation } from 'react-i18next'; + +const AddressSortImgMapping: Record = { + usd: ImgSortByUsd, + addressType: ImgSortByType, + alphabet: ImgSortByAlphabet, +}; + +export const AddressSortPopup = ({ + open, + onCancel, +}: { + open: boolean; + onCancel: () => void; +}) => { + const { t } = useTranslation(); + const sortType = useRabbySelector( + (s) => s.preference.addressSortStore.sortType + ); + const dispath = useRabbyDispatch(); + const handleChange = (value: AddressSortStore['sortType']) => () => { + dispath.preference.setAddressSortStoreValue({ key: 'sortType', value }); + onCancel?.(); + }; + + const arr = useMemo( + () => + [ + { + key: 'usd', + label: t('page.manageAddress.sort-by-balance'), + }, + { + key: 'addressType', + label: t('page.manageAddress.sort-by-address-type'), + }, + { + key: 'alphabet', + label: t('page.manageAddress.sort-by-address-note'), + }, + ] as const, + [] + ); + return ( + +
+ {arr.map((e) => ( + + {e.label} + + ))} +
+
+ ); +}; diff --git a/src/ui/views/AddressManagement/index.tsx b/src/ui/views/AddressManagement/index.tsx index 589d817ce7d..c6cae584a6f 100644 --- a/src/ui/views/AddressManagement/index.tsx +++ b/src/ui/views/AddressManagement/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory, useLocation } from 'react-router-dom'; import { VariableSizeList as VList } from 'react-window'; @@ -6,7 +6,6 @@ import { PageHeader } from 'ui/component'; import AddressItem from './AddressItem'; import IconPinned from 'ui/assets/icon-pinned.svg'; import IconPinnedFill from 'ui/assets/icon-pinned-fill.svg'; -import IconSearch from 'ui/assets/search.svg'; import './style.less'; import { obj2query } from '@/ui/utils/url'; @@ -18,15 +17,20 @@ import { ReactComponent as IconRefresh } from '@/ui/assets/address/refresh.svg'; import { ReactComponent as IconLoading } from '@/ui/assets/address/loading.svg'; import { ReactComponent as IconRight } from '@/ui/assets/address/right.svg'; -import { groupBy } from 'lodash'; -import { KEYRING_CLASS } from '@/constant'; -import { Input, Tooltip } from 'antd'; +import { Dictionary, groupBy, omit } from 'lodash'; +import { KEYRING_CLASS, KEYRING_TYPE } from '@/constant'; +import { Tooltip } from 'antd'; import { useRequest } from 'ahooks'; import { SessionStatusBar } from '@/ui/component/WalletConnect/SessionStatusBar'; import { LedgerStatusBar } from '@/ui/component/ConnectStatus/LedgerStatusBar'; import { GridPlusStatusBar } from '@/ui/component/ConnectStatus/GridPlusStatusBar'; import useDebounceValue from '@/ui/hooks/useDebounceValue'; import LessPalette from '@/ui/style/var-defs'; +// import { AddressSortIconMapping, AddressSortPopup } from './SortPopup'; +import { getWalletScore } from '../ManageAddress/hooks'; +import { IDisplayedAccountWithBalance } from '@/ui/models/accountToDisplay'; +import { SortInput } from './SortInput'; +import { nanoid } from 'nanoid'; function NoAddressUI() { const { t } = useTranslation(); @@ -71,6 +75,10 @@ const AddressManagement = () => { const location = useLocation(); const enableSwitch = location.pathname === '/switch-address'; + const addressSortStore = useRabbySelector( + (s) => s.preference.addressSortStore + ); + // todo: store redesign const { accountsList, @@ -110,13 +118,64 @@ const AddressManagement = () => { watchModeHighlightedAccounts ); + const normalAccounts = highlightedAccounts + .concat(data['0'] || []) + .filter((e) => !!e); + const watchModeAccounts = watchModeHighlightedAccounts + .concat(data['1'] || []) + .filter((e) => !!e); + if (addressSortStore.sortType === 'usd') { + return [normalAccounts, watchModeAccounts]; + } + if (addressSortStore.sortType === 'alphabet') { + return [ + normalAccounts.sort((a, b) => + (a?.alianName || '').localeCompare(b?.alianName || '', 'en', { + numeric: true, + }) + ), + watchModeAccounts.sort((a, b) => + (a?.alianName || '').localeCompare(b?.alianName || '', 'en', { + numeric: true, + }) + ), + ]; + } + + const normalArr = groupBy( + sortAccountsByBalance(normalAccounts), + (e) => e.brandName + ); + + const hdKeyringGroup = groupBy( + normalArr[KEYRING_TYPE.HdKeyring], + (a) => a.publicKey + ); + const ledgersGroup = groupBy( + normalArr[KEYRING_CLASS.HARDWARE.LEDGER], + (a) => a.hdPathBasePublicKey || nanoid() + ) as Dictionary; return [ - highlightedAccounts.concat(data['0'] || []).filter((e) => !!e), - watchModeHighlightedAccounts.concat(data['1'] || []).filter((e) => !!e), + [ + ...Object.values(ledgersGroup).sort((a, b) => b.length - a.length), + ...Object.values(hdKeyringGroup).sort((a, b) => b.length - a.length), + ...Object.values( + omit(normalArr, [ + KEYRING_TYPE.HdKeyring, + KEYRING_CLASS.HARDWARE.LEDGER, + ]) + ), + sortAccountsByBalance(watchModeAccounts), + ] + .filter((e) => Array.isArray(e) && e.length > 0) + .sort((a, b) => getWalletScore(a) - getWalletScore(b)), + [], ]; - }, [accountsList, highlightedAddresses]); + }, [accountsList, highlightedAddresses, addressSortStore.sortType]); - const [searchKeyword, setSearchKeyword] = React.useState(''); + const [searchKeyword, setSearchKeyword] = React.useState( + addressSortStore?.search || '' + ); const debouncedSearchKeyword = useDebounceValue(searchKeyword, 250); const { @@ -127,34 +186,61 @@ const AddressManagement = () => { } = useMemo(() => { const result = { accountList: [ - ...(sortedAccountsList || []), + ...(sortedAccountsList?.flat() || []), ...(watchSortedAccountsList || []), ], filteredAccounts: [] as typeof sortedAccountsList, noAnyAccount: false, noAnySearchedAccount: false, }; + result.filteredAccounts = [...result.accountList]; + if (addressSortStore.sortType === 'addressType') { + result.filteredAccounts = sortedAccountsList; + } if (debouncedSearchKeyword) { const lKeyword = debouncedSearchKeyword.toLowerCase(); - result.filteredAccounts = result.accountList.filter((account) => { - const lowerAddress = account.address.toLowerCase(); - const aliasName = account.alianName?.toLowerCase(); - let addrIncludeKw = false; - if (lKeyword.replace(/^0x/, '').length >= 2) { - addrIncludeKw = account.address - .toLowerCase() - .includes(lKeyword.toLowerCase()); - } + if (addressSortStore.sortType === 'addressType') { + result.filteredAccounts = (result.filteredAccounts as IDisplayedAccountWithBalance[][]) + .map((group) => + group.filter((account) => { + const lowerAddress = account.address.toLowerCase(); + const aliasName = account.alianName?.toLowerCase(); + let addrIncludeKw = false; + if (lKeyword.replace(/^0x/, '').length >= 2) { + addrIncludeKw = account.address + .toLowerCase() + .includes(lKeyword.toLowerCase()); + } + + return ( + lowerAddress === lKeyword || + aliasName?.includes(lKeyword) || + addrIncludeKw + ); + }) + ) + .filter((group) => group.length > 0); + } else { + result.filteredAccounts = result.accountList.filter((account) => { + const lowerAddress = account.address.toLowerCase(); + const aliasName = account.alianName?.toLowerCase(); + let addrIncludeKw = false; + if (lKeyword.replace(/^0x/, '').length >= 2) { + addrIncludeKw = account.address + .toLowerCase() + .includes(lKeyword.toLowerCase()); + } - return ( - lowerAddress === lKeyword || - aliasName?.includes(lKeyword) || - addrIncludeKw - ); - }); + return ( + lowerAddress === lKeyword || + aliasName?.includes(lKeyword) || + addrIncludeKw + ); + }); + } } result.noAnyAccount = result.accountList.length <= 0 && !loadingAccounts; @@ -162,7 +248,12 @@ const AddressManagement = () => { result.filteredAccounts.length <= 0 && !loadingAccounts; return result; - }, [sortedAccountsList, watchSortedAccountsList, debouncedSearchKeyword]); + }, [ + sortedAccountsList, + watchSortedAccountsList, + debouncedSearchKeyword, + addressSortStore.sortType, + ]); const dispatch = useRabbyDispatch(); @@ -203,7 +294,7 @@ const AddressManagement = () => { history.push('/settings/address?back=true'); }; - const switchAccount = async (account: typeof sortedAccountsList[number]) => { + const switchAccount = async (account: typeof accountsList[number]) => { await dispatch.account.changeAccountAsync(account); history.push('/dashboard'); }; @@ -212,62 +303,95 @@ const AddressManagement = () => { dispatch.whitelist.init(); }, []); - const Row = (props) => { + const recordLatestAddress = (value: string) => { + dispatch.preference.setAddressSortStoreValue({ + key: 'lastCurrent', + value, + }); + }; + + const Row = ( + props: any //ListChildComponentProps + ) => { const { data, index, style } = props; const account = data[index]; - const favorited = highlightedAddresses.some( - (highlighted) => - account.address === highlighted.address && - account.brandName === highlighted.brandName - ); - return ( -
- { - e.stopPropagation(); - dispatch.addressManagement.toggleHighlightedAddressAsync({ + const render = (account: typeof accountsList[number], isGroup = false) => { + const favorited = highlightedAddresses.some( + (highlighted) => + account.address === highlighted.address && + account.brandName === highlighted.brandName + ); + + return ( +
{ + recordLatestAddress(`${account.type}-${account.address}`); + }} + > + { + e.stopPropagation(); + dispatch.addressManagement.toggleHighlightedAddressAsync({ + address: account.address, + brandName: account.brandName, + }); + }} + > + +
+ } + onClick={() => { + history.push( + `/settings/address-detail?${obj2query({ address: account.address, + type: account.type, brandName: account.brandName, - }); - }} - > - -
- } - onClick={() => { - history.push( - `/settings/address-detail?${obj2query({ - address: account.address, - type: account.type, - brandName: account.brandName, - byImport: account.byImport || '', - })}` - ); - }} - onSwitchCurrentAccount={() => { - switchAccount(account); - }} - enableSwitch={enableSwitch} - /> - - ); + //@ts-expect-error byImport is boolean + byImport: account.byImport || '', + })}` + ); + }} + onSwitchCurrentAccount={() => { + switchAccount(account); + }} + enableSwitch={enableSwitch} + /> + + ); + }; + + if (addressSortStore.sortType === 'addressType') { + return ( +
+ {(account as typeof accountsList)?.map((e) => render(e, true))} +
+ ); + } + + return render(account as typeof accountsList[number]); }; const isWalletConnect = @@ -278,6 +402,64 @@ const AddressManagement = () => { accountList[currentAccountIndex]?.type === KEYRING_CLASS.HARDWARE.GRIDPLUS; const hasStatusBar = isWalletConnect || isLedger || isGridPlus; + useEffect(() => { + dispatch.preference.setAddressSortStoreValue({ + key: 'search', + value: searchKeyword, + }); + }, [searchKeyword]); + + const listRef = useRef< + VList + >(null); + + useEffect(() => { + if (addressSortStore.lastCurrent && filteredAccounts?.length) { + let index = -1; + let secondIndex = -1; + let sum = 0; + if (addressSortStore.sortType === 'addressType') { + const accounts = filteredAccounts as IDisplayedAccountWithBalance[][]; + index = accounts.findIndex((arr) => { + const i = arr.findIndex( + (e) => `${e.type}-${e.address}` === addressSortStore.lastCurrent + ); + if (i !== -1) { + secondIndex = i; + return true; + } + return false; + }); + if (index !== -1) { + for (let i = 0; i < index; i++) { + sum += accounts[i].length * 56 + 16; + } + sum += secondIndex * 56; + } + } else { + index = (filteredAccounts as IDisplayedAccountWithBalance[]).findIndex( + (e) => `${e.type}-${e.address}` === addressSortStore.lastCurrent + ); + if (index !== -1) { + sum = index * 64; + } + } + + listRef.current?.scrollTo(sum); + } + }, []); + + const getItemSize = React.useCallback( + (i: number) => { + if (addressSortStore.sortType === 'addressType') { + return 52 * (filteredAccounts as typeof accountsList[])[i].length + 16; + } + + return i !== sortedAccountsList.length - 1 ? 60 : 76; + }, + [filteredAccounts, sortedAccountsList, addressSortStore.sortType] + ); + return (
@@ -361,16 +543,10 @@ const AddressManagement = () => {
-
- } - onChange={(e) => setSearchKeyword(e.target.value)} - value={searchKeyword} - allowClear - /> -
+ setSearchKeyword(e.target.value)} + />
{ ) : (
(i !== sortedAccountsList.length - 1 ? 64 : 78)} + itemSize={getItemSize} className="scroll-container" > {Row} diff --git a/src/ui/views/AddressManagement/style.less b/src/ui/views/AddressManagement/style.less index e710e8b47b4..430a0d3c1dc 100644 --- a/src/ui/views/AddressManagement/style.less +++ b/src/ui/views/AddressManagement/style.less @@ -5,6 +5,38 @@ height: 100vh; padding-bottom: 118px; + .address-wrap-with-padding { + margin-bottom: 0; + + .rabby-address-item-container .rabby-address-item-left { + height: 52px; + } + + &.row-group:nth-child(n + 2) { + .rabby-address-item-container .rabby-address-item { + position: relative; + border-top: 0.5px solid var(--r-neutral-line, #d3d8e0); + } + } + + &.row-group:nth-child(1) { + .rabby-address-item-container .rabby-address-item { + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + } + &.row-group:nth-last-child(1) { + .rabby-address-item-container .rabby-address-item { + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + } + + &.row-group .rabby-address-item-container .rabby-address-item { + border-radius: 0; + } + } + .rabby-address-item { border-radius: 6px; background-color: #fff; @@ -219,23 +251,21 @@ } .search-address-wrapper .ant-input-affix-wrapper { + width: 160px; border-radius: 6px; + border: 0.5px solid var(--r-neutral-line, #d3d8e0); } .search-address-wrapper .ant-input-affix-wrapper:hover, .search-address-wrapper .ant-input-affix-wrapper:focus { - background-color: rgba(134, 151, 255, 0.10); - - .ant-input { - background-color: transparent; - } + border: 0.5px solid var(--r-blue-default, #7084ff); } - + .no-matched-address { display: flex; flex-direction: column; align-items: center; - + padding-top: 84px; } } @@ -252,3 +282,60 @@ overflow: auto; } } + +.sort-input { + display: inline-flex; + border-radius: 6px; + border: 0.5px solid var(--r-neutral-line, #d3d8e0); + background: var(--r-neutral-card-1, #fff); + width: 160px; + height: 32px; + transition: width 0.3s; + padding-right: 4px; + + &:has(> .search-input:hover), + &:has(> .search-input.ant-input-affix-wrapper-focused) { + width: 220px; + border-color: var(--r-blue-default, #7084ff); + .ant-input-suffix { + opacity: 1; + } + } + + .sort { + width: 32px; + height: 32px; + padding: 8px; + cursor: pointer; + color: var(--r-neutral-foot); + border-right: 0.5px solid var(--r-neutral-line, #d3d8e0); + + &:hover { + color: var(--r-blue-default, #7084ff); + } + + .icon { + width: 16px; + height: 16px; + } + } + + .search-input { + padding-left: 8px; + border: none; + + .ant-input { + font-size: 13px; + font-weight: 500; + + &:placeholder-shown { + font-size: 12px; + font-weight: 400; + color: var(--r-neutral-foot); + } + } + .ant-input-suffix { + opacity: 0; + } + } +} diff --git a/src/ui/views/Dashboard/components/RecentConnections/index.tsx b/src/ui/views/Dashboard/components/RecentConnections/index.tsx index 5ca7b23274a..67246c1a19a 100644 --- a/src/ui/views/Dashboard/components/RecentConnections/index.tsx +++ b/src/ui/views/Dashboard/components/RecentConnections/index.tsx @@ -158,51 +158,53 @@ const RecentConnections = ({ {t('page.dashboard.recentConnection.title')} - {visible && ( +
+ {visible && ( + + {t('page.dashboard.recentConnection.noPinnedDapps')} +
+ } + extra={ + pinnedList.length > 0 + ? t('page.dashboard.recentConnection.dragToSort') + : null + } + onClick={handleClick} + onSort={handleSort} + sortable={true} + > + )} 0 ? ( + + {t('page.dashboard.recentConnection.disconnectAll')} + + ) : null + } empty={ -
- {t('page.dashboard.recentConnection.noPinnedDapps')} +
+
} - extra={ - pinnedList.length > 0 - ? t('page.dashboard.recentConnection.dragToSort') - : null - } - onClick={handleClick} - onSort={handleSort} - sortable={true} > - )} - 0 ? ( - - {t('page.dashboard.recentConnection.disconnectAll')} - - ) : null - } - empty={ -
- -
- } - >
+
); }; diff --git a/src/ui/views/Dashboard/components/RecentConnections/style.less b/src/ui/views/Dashboard/components/RecentConnections/style.less index 15cd1f96612..cd69e171f03 100644 --- a/src/ui/views/Dashboard/components/RecentConnections/style.less +++ b/src/ui/views/Dashboard/components/RecentConnections/style.less @@ -4,6 +4,12 @@ font-size: 12px; margin: 0; } + + .scroll-container { + max-height: var(100% - 48px); + overflow: auto; + } + .list { &:not(:last-child) { margin-bottom: 16px; diff --git a/src/ui/views/ManageAddress/hooks.tsx b/src/ui/views/ManageAddress/hooks.tsx index 1fe0e7b75ba..8ed44e413a2 100644 --- a/src/ui/views/ManageAddress/hooks.tsx +++ b/src/ui/views/ManageAddress/hooks.tsx @@ -100,7 +100,9 @@ const sortScore = [ } ); -const getWalletScore = (s: TypeKeyringGroup[]) => { +export const getWalletScore = ( + s: TypeKeyringGroup[] | IDisplayedAccountWithBalance[] +) => { return sortScore[s?.[0]?.brandName || s?.[0]?.type] || DEFAULT_SCORE; };