Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle fetching token images #643

Merged
merged 2 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: [],
});
Loading