Skip to content

Commit

Permalink
feat(wallet): Multichain Portfolio Network Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
Douglashdaniel committed Mar 29, 2022
1 parent 63d6c2c commit 67e2265
Show file tree
Hide file tree
Showing 23 changed files with 519 additions and 57 deletions.
5 changes: 4 additions & 1 deletion components/brave_wallet/browser/brave_wallet_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,10 @@ constexpr webui::LocalizedString kLocalizedStrings[] = {
IDS_BRAVE_WALLET_SWEEPSTAKES_DESCRIPTION},
{"braveWalletSweepstakesCallToAction",
IDS_BRAVE_WALLET_SWEEPSTAKES_CALL_TO_ACTION},
{"braveWalletNotValidFilAddress", IDS_BRAVE_WALLET_NOT_VALID_FIL_ADDRESS}};
{"braveWalletNotValidFilAddress", IDS_BRAVE_WALLET_NOT_VALID_FIL_ADDRESS},
{"braveWalletNetworkFilterAll", IDS_BRAVE_WALLET_NETWORK_FILTER_ALL},
{"braveWalletNetworkFilterSecondary",
IDS_BRAVE_WALLET_NETWORK_FILTER_SECONDARY}};

// Swap constants
const char kRopstenSwapBaseAPIURL[] = "https://ropsten.api.0x.org/";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,4 @@ export const refreshBalancesAndPriceHistory = createAction('refreshBalancesAndPr
export const setTransactionProviderError = createAction<SetTransactionProviderErrorType>('setTransactionProviderError')
export const setSelectedCoin = createAction<BraveWallet.CoinType>('setSelectedCoin')
export const setDefaultNetworks = createAction<BraveWallet.NetworkInfo[]>('setDefaultNetworks')
export const setSelectedNetworkFilter = createAction<BraveWallet.NetworkInfo>('setSelectedNetworkFilter')
6 changes: 6 additions & 0 deletions components/brave_wallet_ui/common/async/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,4 +590,10 @@ handler.on(WalletActions.expandWalletNetworks.getType(), async (store) => {
})
})

handler.on(WalletActions.setSelectedNetworkFilter.getType(), async (store: Store, payload: BraveWallet.NetworkInfo) => {
const state = getWalletState(store)
const { selectedPortfolioTimeline } = state
await store.dispatch(refreshTokenPriceHistory(selectedPortfolioTimeline))
})

export default handler.middleware
30 changes: 20 additions & 10 deletions components/brave_wallet_ui/common/async/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import * as WalletActions from '../actions/wallet_actions'

// Utils
import { getNetworkInfo, getNetworksByCoinType } from '../../utils/network-utils'
import { getNetworkInfo, getNetworksByCoinType, getTokensCoinType } from '../../utils/network-utils'
import { getTokenParam, getFlattenedAccountBalances } from '../../utils/api-utils'
import Amount from '../../utils/amount'

Expand All @@ -33,6 +33,7 @@ import { GetAccountsHardwareOperationResult } from '../hardware/types'
import LedgerBridgeKeyring from '../hardware/ledgerjs/eth_ledger_bridge_keyring'
import TrezorBridgeKeyring from '../hardware/trezor/trezor_bridge_keyring'
import FilecoinLedgerKeyring from '../hardware/ledgerjs/filecoin_ledger_keyring'
import { AllNetworksOption } from '../../options/network-filter-options'

export const getERC20Allowance = (
contractAddress: string,
Expand Down Expand Up @@ -368,14 +369,25 @@ export function refreshTokenPriceHistory (selectedPortfolioTimeline: BraveWallet
const apiProxy = getAPIProxy()
const { assetRatioService } = apiProxy

const { wallet: { accounts, defaultCurrencies, userVisibleTokensInfo } } = getState()
const { wallet: { accounts, defaultCurrencies, userVisibleTokensInfo, selectedNetworkFilter, networkList } } = getState()

// By default we do not fetch Price history for Test Networks Tokens if
// Selected Network Filter is all
const filteredTokenInfo = selectedNetworkFilter.chainId === AllNetworksOption.chainId
? userVisibleTokensInfo.filter((token) => !SupportedTestNetworks.includes(token.chainId))
// If chainId is Localhost we also do a check for coinType to only
// fetch Price History for for the correct tokens
: selectedNetworkFilter.chainId === BraveWallet.LOCALHOST_CHAIN_ID
? userVisibleTokensInfo.filter((token) =>
token.chainId === selectedNetworkFilter.chainId &&
getTokensCoinType(networkList, token) === selectedNetworkFilter.coin)
// Fetch Price History for Tokens by Selected Network Filter's chainId
: userVisibleTokensInfo.filter((token) => token.chainId === selectedNetworkFilter.chainId)

// Get all Price History
const priceHistory = await Promise.all(getFlattenedAccountBalances(accounts, userVisibleTokensInfo)
const priceHistory = await Promise.all(getFlattenedAccountBalances(accounts, filteredTokenInfo)
// If a tokens balance is 0 we do not make an unnecessary api call for price history of that token
// Will remove testnetwork filter when this is implemented
// https://github.com/brave/brave-browser/issues/20780
.filter(({ token, balance }) => !token.isErc721 && balance > 0 && !SupportedTestNetworks.includes(token.chainId))
.filter(({ token, balance }) => !token.isErc721 && balance > 0)
.map(async ({ token }) => ({
// If a visible asset has a contractAddress of ''
// it is a native asset so we use a symbol instead.
Expand All @@ -388,10 +400,8 @@ export function refreshTokenPriceHistory (selectedPortfolioTimeline: BraveWallet

// Combine Price History and Balances
const priceHistoryWithBalances = accounts.map((account) => {
return userVisibleTokensInfo
// Will remove testnetwork filter when this is implemented
// https://github.com/brave/brave-browser/issues/20780
.filter((token) => !token.isErc721 && !SupportedTestNetworks.includes(token.chainId))
return filteredTokenInfo
.filter((token) => !token.isErc721)
.map((token) => {
const balance = token.contractAddress
? account.tokenBalanceRegistry[token.contractAddress.toLowerCase()]
Expand Down
16 changes: 13 additions & 3 deletions components/brave_wallet_ui/common/reducers/wallet_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import * as WalletActions from '../actions/wallet_actions'
import { mojoTimeDeltaToJSDate } from '../../../common/mojomUtils'
import { sortTransactionByDate } from '../../utils/tx-utils'
import Amount from '../../utils/amount'
import { AllNetworksOption } from '../../options/network-filter-options'

const defaultState: WalletState = {
hasInitialized: false,
Expand Down Expand Up @@ -86,7 +87,8 @@ const defaultState: WalletState = {
crypto: ''
},
transactionProviderErrorRegistry: {},
defaultNetworks: [] as BraveWallet.NetworkInfo[]
defaultNetworks: [] as BraveWallet.NetworkInfo[],
selectedNetworkFilter: AllNetworksOption
}

const getAccountType = (info: AccountInfo) => {
Expand Down Expand Up @@ -309,8 +311,8 @@ export const createWalletReducer = (initialState: WalletState) => {

reducer.on(WalletActions.transactionStatusChanged, (state: WalletState, payload: TransactionStatusChanged): WalletState => {
const newPendingTransactions = state.pendingTransactions
.filter((tx: BraveWallet.TransactionInfo) => tx.id !== payload.txInfo.id)
.concat(payload.txInfo.txStatus === BraveWallet.TransactionStatus.Unapproved ? [payload.txInfo] : [])
.filter((tx: BraveWallet.TransactionInfo) => tx.id !== payload.txInfo.id)
.concat(payload.txInfo.txStatus === BraveWallet.TransactionStatus.Unapproved ? [payload.txInfo] : [])

const sortedTransactionList = sortTransactionByDate(newPendingTransactions)

Expand Down Expand Up @@ -481,6 +483,14 @@ export const createWalletReducer = (initialState: WalletState) => {
}
})

reducer.on(WalletActions.setSelectedNetworkFilter, (state: WalletState, payload: BraveWallet.NetworkInfo): WalletState => {
return {
...state,
isFetchingPortfolioPriceHistory: true,
selectedNetworkFilter: payload
}
})

return reducer
}

Expand Down
4 changes: 3 additions & 1 deletion components/brave_wallet_ui/components/desktop/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import SelectNetworkDropdown from './select-network-dropdown'
import TransactionPopup from './transaction-popup'
import SwapTooltip from './swap-tooltip'
import WithHideBalancePlaceholder from './with-hide-balance-placeholder'
import NetworkFilterSelector from './network-filter-selector'
import { CryptoView, PortfolioView } from './views'
import {
OnboardingWelcome,
Expand Down Expand Up @@ -68,5 +69,6 @@ export {
OnboardingImportMetaMaskOrLegacy,
TransactionPopup,
SwapTooltip,
WithHideBalancePlaceholder
WithHideBalancePlaceholder,
NetworkFilterSelector
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import * as React from 'react'
import { useSelector, useDispatch } from 'react-redux'

// Types
import { BraveWallet, SupportedTestNetworks, WalletState } from '../../../constants/types'

// Components
import NetworkFilterItem from './network-filter-item'
import { CreateNetworkIcon } from '../../shared'

// Utils
import { reduceNetworkDisplayName } from '../../../utils/network-utils'
import { WalletActions } from '../../../common/actions'
import { getLocale } from '../../../../common/locale'
import {
AllNetworksOption,
SupportedTopLevelChainIds
} from '../../../options/network-filter-options'

// Styled Components
import {
StyledWrapper,
DropDown,
DropDownButton,
DropDownIcon,
LeftSide,
SubDropDown,
SecondaryNetworkText,
ClickAwayArea
} from './style'

function NetworkFilterSelector () {
const [showNetworkFilter, setShowNetworkFilter] = React.useState<boolean>(false)

// redux
const {
selectedNetworkFilter,
networkList
} = useSelector(({ wallet }: { wallet: WalletState }) => wallet)

const dispatch = useDispatch()

const sortedNetworks = React.useMemo(() => {
const onlyMainnets = networkList.filter((network) => SupportedTopLevelChainIds.includes(network.chainId))
const removedMainnets = networkList.filter((network) => !SupportedTopLevelChainIds.includes(network.chainId))
return [AllNetworksOption, ...onlyMainnets, ...removedMainnets]
}, [networkList])

const primaryNetworks = React.useMemo(() => {
const onlyMainnets = networkList.filter((network) => SupportedTopLevelChainIds.includes(network.chainId))
return [AllNetworksOption, ...onlyMainnets]
}, [sortedNetworks])

const secondaryNetworks = React.useMemo(() => {
const primaryList = [AllNetworksOption.chainId, ...SupportedTopLevelChainIds, ...SupportedTestNetworks]
return sortedNetworks.filter((network) => !primaryList.includes(network.chainId))
}, [sortedNetworks])

const toggleShowNetworkFilter = () => {
setShowNetworkFilter(!showNetworkFilter)
}

const onSelectAndClose = (network: BraveWallet.NetworkInfo) => {
dispatch(WalletActions.setSelectedNetworkFilter(network))
toggleShowNetworkFilter()
}

const hideNetworkFilter = () => {
setShowNetworkFilter(false)
}

return (
<StyledWrapper>
<DropDownButton onClick={toggleShowNetworkFilter}>
<LeftSide>
{selectedNetworkFilter.chainId !== AllNetworksOption.chainId &&
<CreateNetworkIcon network={selectedNetworkFilter} marginRight={14} size='big' />
}
{selectedNetworkFilter.chainId !== AllNetworksOption.chainId ? reduceNetworkDisplayName(selectedNetworkFilter.chainName) : selectedNetworkFilter.chainName}
</LeftSide>
<DropDownIcon />
</DropDownButton>
{showNetworkFilter &&
<DropDown>
{primaryNetworks.map((network: BraveWallet.NetworkInfo) =>
<NetworkFilterItem
key={`${network.chainId + network.chainName}`}
network={network}
onSelectNetwork={onSelectAndClose}
selectedNetwork={selectedNetworkFilter}
isSubItem={!SupportedTopLevelChainIds.includes(network.chainId)}
>
<SubDropDown>
{sortedNetworks.filter((n) =>
n.coin === network.coin &&
n.symbol.toLowerCase() === network.symbol.toLowerCase())
.map((subNetwork) =>
<NetworkFilterItem
key={`${subNetwork.chainId + subNetwork.chainName}`}
network={subNetwork}
onSelectNetwork={onSelectAndClose}
selectedNetwork={selectedNetworkFilter}
isSubItem={true}
/>
)}
</SubDropDown>
</NetworkFilterItem>
)}
{secondaryNetworks.length > 0 &&
<>
<SecondaryNetworkText>{getLocale('braveWalletNetworkFilterSecondary')}</SecondaryNetworkText>
{secondaryNetworks.map((network) =>
<NetworkFilterItem
key={`${network.chainId + network.chainName}`}
network={network}
onSelectNetwork={onSelectAndClose}
selectedNetwork={selectedNetworkFilter}
isSubItem={true}
/>
)}
</>
}
</DropDown>
}
{showNetworkFilter &&
<ClickAwayArea onClick={hideNetworkFilter} />
}
</StyledWrapper >
)
}

export default NetworkFilterSelector
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as React from 'react'

// Types
import { BraveWallet } from '../../../constants/types'

// Components
import { CreateNetworkIcon } from '../../shared'

// Utils
import { reduceNetworkDisplayName } from '../../../utils/network-utils'
import { AllNetworksOption } from '../../../options/network-filter-options'

// Styled Components
import {
NetworkItemButton,
NetworkName,
LeftSide,
NetworkItemWrapper,
BigCheckMark
} from './style'

export interface Props {
children?: React.ReactNode
selectedNetwork: BraveWallet.NetworkInfo
network: BraveWallet.NetworkInfo
isSubItem: boolean
onSelectNetwork: (network?: BraveWallet.NetworkInfo) => void
}

function NetworkFilterItem (props: Props) {
const { network, onSelectNetwork, children, selectedNetwork, isSubItem } = props
const [showSubMenu, setShowSubMenu] = React.useState<boolean>(false)

const showTip = () => {
if (!isSubItem) {
setShowSubMenu(true)
}
}

const hideTip = () => {
if (!isSubItem) {
setShowSubMenu(false)
}
}

const onClickSelectNetwork = () => {
if (!isSubItem) {
return
}
setShowSubMenu(false)
onSelectNetwork(network)
}

return (
<NetworkItemWrapper
onMouseEnter={showTip}
onMouseLeave={hideTip}
>
<NetworkItemButton onClick={onClickSelectNetwork}>
<LeftSide>
{network.chainId !== AllNetworksOption.chainId &&
<CreateNetworkIcon network={network} marginRight={14} size='big' />
}
<NetworkName>{isSubItem ? network.chainName : reduceNetworkDisplayName(network.chainName)}</NetworkName>
</LeftSide>
{network.chainId === selectedNetwork.chainId &&
network.symbol.toLowerCase() === selectedNetwork.symbol.toLowerCase() &&
isSubItem &&
<BigCheckMark />
}
</NetworkItemButton>
{showSubMenu && !isSubItem &&
<>{children}</>
}
</NetworkItemWrapper>
)
}

export default NetworkFilterItem
Loading

0 comments on commit 67e2265

Please sign in to comment.