Skip to content

Commit

Permalink
feat: handle fetching token images (#643)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinoosss authored Nov 17, 2024
1 parent 30c4cb8 commit 5c76697
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 44 deletions.
3 changes: 3 additions & 0 deletions packages/adena-extension/src/App/use-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useLocation } from 'react-router-dom';
import { useAccountName } from '@hooks/use-account-name';
import { useWalletContext } from '@hooks/use-context';
import { useCurrentAccount } from '@hooks/use-current-account';
import { useLoadImages } from '@hooks/use-load-images';
import { useNetwork } from '@hooks/use-network';
import useScrollHistory from '@hooks/use-scroll-history';
import { useTokenMetainfo } from '@hooks/use-token-metainfo';
Expand All @@ -14,6 +15,7 @@ const useApp = (): void => {
const { currentAccount } = useCurrentAccount();
const { currentNetwork, checkNetworkState } = useNetwork();
const { initTokenMetainfos } = useTokenMetainfo();
const { clear: clearLoadingImages } = useLoadImages();
const { pathname, key } = useLocation();
const { scrollMove } = useScrollHistory();

Expand All @@ -34,6 +36,7 @@ const useApp = (): void => {
return;
}

clearLoadingImages();
initTokenMetainfos();
}, [currentAccount?.id, currentNetwork.networkId]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ export interface WalletContextProps {
export const WalletContext = createContext<WalletContextProps | null>(null);

export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const { walletService, balanceService, accountService, chainService, tokenService } =
useAdenaContext();
const { walletService, accountService, chainService } = useAdenaContext();

const [gnoProvider, setGnoProvider] = useState<GnoProvider>();

const [wallet, setWallet] = useRecoilState(WalletState.wallet);

const [walletStatus, setWalletStatus] = useRecoilState(WalletState.state);

const [tokenMetainfos, setTokenMetainfos] = useRecoilState(TokenState.tokenMetainfos);
const [tokenMetainfos] = useRecoilState(TokenState.tokenMetainfos);

const [networkMetainfos, setNetworkMetainfos] = useRecoilState(NetworkState.networkMetainfos);

Expand Down Expand Up @@ -95,7 +94,6 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi
if (currentAccount) {
setCurrentAccount(currentAccount);
await accountService.changeCurrentAccount(currentAccount);
await initTokenMetainfos(currentAccount.id);
}
return true;
}
Expand Down Expand Up @@ -124,13 +122,6 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi
return true;
}

async function initTokenMetainfos(accountId: string): Promise<void> {
await tokenService.initAccountTokenMetainfos(accountId);
const tokenMetainfos = await tokenService.getTokenMetainfosByAccountId(accountId);
setTokenMetainfos(tokenMetainfos);
balanceService.setTokenMetainfos(tokenMetainfos);
}

async function changeNetwork(networkMetainfo: NetworkMetainfo): Promise<NetworkMetainfo> {
const rpcUrl = networkMetainfo.rpcUrl;
const gnoProvider = new GnoProvider(rpcUrl, networkMetainfo.networkId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ describe('TokenListItem Component', () => {
it('TokenListItem render', () => {
const args: TokenListItemProps = {
token,
completeImageLoading: () => {
return;
},
onClickTokenItem: () => {
return;
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
import React from 'react';
import { TokenListItemWrapper } from './token-list-item.styles';
import TokenListItemBalance from '@components/pages/wallet-main/token-list-item-balance/token-list-item-balance';
import { MainToken } from '@types';
import React from 'react';
import { TokenListItemWrapper } from './token-list-item.styles';

export interface TokenListItemProps {
token: MainToken;
completeImageLoading: (imageUrl: string) => void;
onClickTokenItem: (tokenId: string) => void;
}

const TokenListItem: React.FC<TokenListItemProps> = ({ token, onClickTokenItem }) => {
const TokenListItem: React.FC<TokenListItemProps> = ({
token,
completeImageLoading,
onClickTokenItem,
}) => {
const { tokenId, logo, name, balanceAmount } = token;

const onLoadImage = (): void => {
completeImageLoading(logo);
};

return (
<TokenListItemWrapper onClick={(): void => onClickTokenItem(tokenId)}>
<div className='logo-wrapper'>
<img className='logo' src={logo} alt='token img' />
<img
className='logo'
src={logo}
onLoad={onLoadImage}
onError={onLoadImage}
loading='eager'
decoding='sync'
alt='token img'
/>
</div>

<div className='name-wrapper'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ describe('TokenList Component', () => {
it('TokenList render', () => {
const args: TokenListProps = {
tokens,
completeImageLoading: () => {
return;
},
onClickTokenItem: () => {
return;
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { MainToken } from '@types';
import React from 'react';
import { TokenListWrapper } from './token-list.styles';
import TokenListItem from '../token-list-item/token-list-item';
import { MainToken } from '@types';
import { TokenListWrapper } from './token-list.styles';

export interface TokenListProps {
tokens: Array<MainToken>;
completeImageLoading: (imageUrl: string) => void;
onClickTokenItem: (tokenId: string) => void;
}

const TokenList: React.FC<TokenListProps> = ({ tokens, onClickTokenItem }) => {
const TokenList: React.FC<TokenListProps> = ({
tokens,
completeImageLoading,
onClickTokenItem,
}) => {
return (
<TokenListWrapper>
{tokens.map((token, index) => (
<TokenListItem key={index} token={token} onClickTokenItem={onClickTokenItem} />
<TokenListItem
key={index}
token={token}
completeImageLoading={completeImageLoading}
onClickTokenItem={onClickTokenItem}
/>
))}
</TokenListWrapper>
);
Expand Down
38 changes: 38 additions & 0 deletions packages/adena-extension/src/hooks/use-load-images.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useRecoilState } from 'recoil';

import { CommonState } from '@states';
import { useMemo } from 'react';

export type UseLoadAccountsReturn = {
isLoading: boolean;
addLoadingImages: (imageUrls: string[]) => void;
completeImageLoading: (imageUrl: string) => void;
clear: () => void;
};

export const useLoadImages = (): UseLoadAccountsReturn => {
const [loadingImageUrls, setLoadingImageUrls] = useRecoilState(CommonState.loadingImageUrls);
const [loadedImageUrls, setLoadedImageUrls] = useRecoilState(CommonState.loadedImageUrls);

const isLoading = useMemo(() => {
if (loadedImageUrls.length === 0) {
return true;
}
return loadedImageUrls.length < loadingImageUrls.length;
}, [loadedImageUrls, loadingImageUrls]);

const addLoadingImages = (imageUrls: string[]): void => {
setLoadingImageUrls([...new Set(imageUrls.filter((url) => !!url))]);
};

const completeImageLoading = (imageUrl: string): void => {
setLoadedImageUrls((prev) => [...new Set([...prev, imageUrl])]);
};

const clear = (): void => {
setLoadingImageUrls([]);
setLoadedImageUrls([]);
};

return { isLoading, addLoadingImages, completeImageLoading, clear };
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { useRecoilState } from 'recoil';
import BigNumber from 'bignumber.js';
import { isAirgapAccount } from 'adena-module';
import BigNumber from 'bignumber.js';
import { useCallback, useEffect, useMemo } from 'react';
import { useRecoilState } from 'recoil';
import styled from 'styled-components';

import { RoutePath } from '@types';
import { useTokenBalance } from '@hooks/use-token-balance';
import UnknownTokenIcon from '@assets/common-unknown-token.svg';
import { Button, Row, Text } from '@components/atoms';
import IconThunder from '@components/atoms/icon/icon-assets/icon-thunder';
import LoadingButton from '@components/atoms/loading-button/loading-button';
import MainManageTokenButton from '@components/pages/main/main-manage-token-button/main-manage-token-button';
import MainNetworkLabel from '@components/pages/main/main-network-label/main-network-label';
import MainTokenBalance from '@components/pages/main/main-token-balance/main-token-balance';
import TokenList from '@components/pages/wallet-main/token-list/token-list';
import MainManageTokenButton from '@components/pages/main/main-manage-token-button/main-manage-token-button';
import UnknownTokenIcon from '@assets/common-unknown-token.svg';
import { useCurrentAccount } from '@hooks/use-current-account';
import { WalletState } from '@states';
import { usePreventHistoryBack } from '@hooks/use-prevent-history-back';
import useAppNavigate from '@hooks/use-app-navigate';
import { useNetwork } from '@hooks/use-network';
import MainNetworkLabel from '@components/pages/main/main-network-label/main-network-label';
import { Button, Row, Text } from '@components/atoms';
import mixins from '@styles/mixins';
import { useCurrentAccount } from '@hooks/use-current-account';
import { useFaucet } from '@hooks/use-faucet';
import { useLoadImages } from '@hooks/use-load-images';
import { useNetwork } from '@hooks/use-network';
import { usePreventHistoryBack } from '@hooks/use-prevent-history-back';
import { useToast } from '@hooks/use-toast';
import LoadingButton from '@components/atoms/loading-button/loading-button';
import IconThunder from '@components/atoms/icon/icon-assets/icon-thunder';
import { useTokenBalance } from '@hooks/use-token-balance';
import { useTokenMetainfo } from '@hooks/use-token-metainfo';
import { WalletState } from '@states';
import mixins from '@styles/mixins';
import { RoutePath } from '@types';

const Wrapper = styled.main`
padding-top: 37px;
Expand Down Expand Up @@ -74,6 +75,8 @@ export const WalletMain = (): JSX.Element => {
const { isSupported: supportedFaucet, isLoading: isFaucetLoading, faucet } = useFaucet();
const { show } = useToast();

const { addLoadingImages, completeImageLoading } = useLoadImages();

const onClickFaucetButton = (): void => {
if (isFaucetLoading) {
return;
Expand Down Expand Up @@ -119,6 +122,10 @@ export const WalletMain = (): JSX.Element => {
});
}, [currentBalances, getTokenImage]);

const tokenImages = useMemo(() => {
return tokens.map((token) => token.logo);
}, [tokens]);

const onClickTokenListItem = useCallback(
(tokenId: string) => {
const tokenBalance = currentBalances.find((tokenBalance) => tokenBalance.tokenId === tokenId);
Expand All @@ -137,6 +144,10 @@ export const WalletMain = (): JSX.Element => {
navigate(RoutePath.ManageToken);
}, [navigate]);

useEffect(() => {
addLoadingImages(tokenImages);
}, [tokenImages.length]);

return (
<Wrapper>
<div className='network-label-wrapper'>
Expand Down Expand Up @@ -175,7 +186,11 @@ export const WalletMain = (): JSX.Element => {
</div>

<div className='token-list-wrapper'>
<TokenList tokens={tokens} onClickTokenItem={onClickTokenListItem} />
<TokenList
tokens={tokens}
completeImageLoading={completeImageLoading}
onClickTokenItem={onClickTokenListItem}
/>
</div>

<div className='manage-token-button-wrapper'>
Expand Down
27 changes: 20 additions & 7 deletions packages/adena-extension/src/router/popup/loading-main.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React, { ReactElement, useMemo } from 'react';
import { ReactElement, useMemo } from 'react';
import { useMatch } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import styled from 'styled-components';
import { useMatch } from 'react-router-dom';

import { Loading, SkeletonBoxStyle } from '@components/atoms';
import { RoutePath } from '@types';
import { GhostButtons } from '@components/molecules';
import { useLoadImages } from '@hooks/use-load-images';
import { useNetwork } from '@hooks/use-network';
import { useTokenBalance } from '@hooks/use-token-balance';
import { WalletState } from '@states';
import { useNetwork } from '@hooks/use-network';
import { GhostButtons } from '@components/molecules';
import { getTheme } from '@styles/theme';
import mixins from '@styles/mixins';
import { getTheme } from '@styles/theme';
import { RoutePath } from '@types';

const Wrapper = styled.main`
${mixins.flex({ justify: 'stretch' })};
Expand Down Expand Up @@ -49,6 +50,7 @@ const LoadingMain = (): ReactElement => {
const { currentBalances } = useTokenBalance();
const isNotMatch = useMatch('/approve/wallet/*');
const isPopupMatch = useMatch('/popup/*');
const { isLoading: isLoadingImage } = useLoadImages();

const loading = useMemo(() => {
if (isApproveHardwarePath || isNotMatch || isPopupMatch) {
Expand All @@ -57,6 +59,9 @@ const LoadingMain = (): ReactElement => {
if (state === 'CREATE' || state === 'LOGIN') {
return false;
}
if (isLoadingImage) {
return true;
}
if (state === 'FINISH') {
// If `failedNetwork` is null, it is loading.
if (failedNetwork) {
Expand All @@ -69,7 +74,15 @@ const LoadingMain = (): ReactElement => {
}
}
return true;
}, [isPopupMatch, state, currentBalances, failedNetwork, currentNetwork.id, useMatch]);
}, [
isPopupMatch,
state,
currentBalances,
failedNetwork,
currentNetwork.id,
isLoadingImage,
useMatch,
]);

return loading ? (
<Wrapper>
Expand Down
10 changes: 10 additions & 0 deletions packages/adena-extension/src/states/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,13 @@ export const windowSizeType = atom<WindowSizeType>({
key: 'common/windowSizeType',
default: 'DEFAULT',
});

export const loadingImageUrls = atom<string[]>({
key: 'common/loadingImageUrls',
default: [],
});

export const loadedImageUrls = atom<string[]>({
key: 'common/loadedImageUrls',
default: [],
});

0 comments on commit 5c76697

Please sign in to comment.