Skip to content

Commit

Permalink
fix coin icon fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
skylarbarrera committed Aug 25, 2023
1 parent 7fd0467 commit 31d5313
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 132 deletions.
6 changes: 5 additions & 1 deletion src/components/coin-icon/CoinIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { View, ViewProps } from 'react-native';
import ContractInteraction from '../../assets/contractInteraction.png';
import { useTheme } from '../../theme/ThemeContext';
import ChainBadge from './ChainBadge';
import CoinIconFallback from './CoinIconFallback';
import { CoinIconFallback } from './CoinIconFallback';
import { AssetTypes } from '@/entities';
import { useColorForAsset } from '@/hooks';
import { ImgixImage } from '@/components/images';
Expand Down Expand Up @@ -69,6 +69,8 @@ const CoinIcon: React.FC<Props> = ({
symbol,
]);

const theme = useTheme();

return (
<View>
{isNotContractInteraction ? (
Expand All @@ -87,6 +89,8 @@ const CoinIcon: React.FC<Props> = ({
size={size}
symbol={symbol}
type={mainnet_address ? AssetTypes.token : type}
assetType={mainnet_address ? AssetTypes.token : type}
theme={theme}
/>
) : (
<ContractInteractionIcon size={size} source={ContractInteraction} />
Expand Down
242 changes: 111 additions & 131 deletions src/components/coin-icon/CoinIconFallback.js
Original file line number Diff line number Diff line change
@@ -1,151 +1,131 @@
import React, { useMemo } from 'react';
import { Image } from 'react-native';
import { useTheme } from '../../theme/ThemeContext';
import { Centered } from '../layout';
import EthIcon from '@/assets/eth-icon.png';
import { AssetTypes } from '@/entities';
import { useBooleanState, useColorForAsset } from '@/hooks';
import { ImageWithCachedMetadata } from '@/components/images';
import styled from '@/styled-thing';
import { borders, fonts, position, shadow } from '@/styles';
import {
FallbackIcon,
getUrlForTrustIconFallback,
isETH,
magicMemo,
} from '@/utils';

const fallbackTextStyles = {
fontFamily: fonts.family.SFProRounded,
fontWeight: fonts.weight.bold,
letterSpacing: fonts.letterSpacing.roundedTight,
marginBottom: 0.5,
textAlign: 'center',
};
import React, { useCallback } from 'react';
import { StyleSheet, View } from 'react-native';
import { AssetType } from '@/entities';
import { useForceUpdate } from '@/hooks';
import { ImageWithCachedMetadata, ImgixImage } from '@/components/images';
import { ThemeContextProps } from '@/theme';
import { getUrlForTrustIconFallback } from '@/utils';

const FallbackImage = styled(ImageWithCachedMetadata)(
({
size,
layoutSize,
theme: { colors },
shadowColor: color,
shadowOffset: { height: y, width: x },
shadowOpacity: opacity,
shadowRadius: radius,
showImage,
}) => ({
height: layoutSize ?? size,
width: layoutSize ?? size,
...position.coverAsObject,
...shadow.buildAsObject(x, y, radius * 2, color, showImage ? opacity : 0),
backgroundColor: showImage ? colors.white : colors.transparent,
borderRadius: size / 2,
overflow: 'visible',
})
);

// If th size is e.g., 20, we can use 40 that is the default icon size in the (used in discover and wallet list)
const getIconSize = size => {
if (40 % size === 0) {
return 40;
}
return size;
const ImageState = {
ERROR: 'ERROR',
LOADED: 'LOADED',
NOT_FOUND: 'NOT_FOUND',
};

function WrappedFallbackImage({
color,
elevation = 6,
shadowOpacity,
showImage,
size,
eth,
type,
...props
}) {
const { colors } = useTheme();
return (
<Centered
{...props}
{...position.coverAsObject}
{...borders.buildCircleAsObject(size)}
backgroundColor={colors.alpha(color || colors.dark, shadowOpacity || 0.3)}
elevation={showImage ? elevation : 0}
style={{ overflow: 'hidden' }}
>
<FallbackImage
as={eth ? Image : undefined}
source={EthIcon}
{...props}
overlayColor={color || colors.dark}
shadowOpacity={shadowOpacity}
showImage={showImage}
size={getIconSize(size)}
type={type}
/>
</Centered>
);
}

const FallbackImageElement = android ? WrappedFallbackImage : FallbackImage;
const imagesCache = {};

const CoinIconFallback = fallbackProps => {
export const CoinIconFallback = fallbackProps => {
const {
address = '',
mainnet_address,
address,
assetType,
height,
symbol,
shadowColor,
theme,
children,
size = 40,
width,
type,
} = fallbackProps;

const [showImage, showFallbackImage, hideFallbackImage] = useBooleanState(
false
);
const { colors } = theme;
const imageUrl = getUrlForTrustIconFallback(address, assetType);

const fallbackIconColor = useColorForAsset({
address: mainnet_address || address,
type: mainnet_address ? AssetTypes.token : type,
});
const imageUrl = useMemo(
() =>
getUrlForTrustIconFallback(
mainnet_address || address,
mainnet_address ? AssetTypes.token : type
),
[address, mainnet_address, type]
);
const key = `${symbol}-${imageUrl}`;

const shouldShowImage = imagesCache[key] !== ImageState.NOT_FOUND;
const isLoaded = imagesCache[key] === ImageState.LOADED;

// we store data inside the object outside the component
// so we can share it between component instances
// but we still want the component to pick up new changes
const forceUpdate = useForceUpdate();

const eth = isETH(address);
const onLoad = useCallback(() => {
if (imagesCache[key] === ImageState.LOADED) {
return;
}

imagesCache[key] = ImageState.LOADED;
forceUpdate();
}, [key, forceUpdate]);
const onError = useCallback(
// @ts-expect-error passed to an untyped JS component
err => {
const newError = err?.nativeEvent?.message?.includes('404')
? ImageState.NOT_FOUND
: ImageState.ERROR;

if (imagesCache[key] === newError) {
return;
} else {
imagesCache[key] = newError;
}

forceUpdate();
},
[key, forceUpdate]
);

return (
<Centered height={height} width={width}>
{!showImage && (
<FallbackIcon
<View style={[sx.coinIconContainer, sx.withShadow, { shadowColor }]}>
{shouldShowImage && (
<ImageWithCachedMetadata
cache={ImgixImage.cacheControl.immutable}
imageUrl={imageUrl}
onError={onError}
onLoad={onLoad}
size={size}
style={[
sx.coinIconFallback,
isLoaded && { backgroundColor: colors.white },
{ height, width, borderRadius: height / 2 },
]}
{...fallbackProps}
color={fallbackIconColor}
showImage={showImage}
symbol={symbol || ''}
textStyles={fallbackTextStyles}
/>
)}
<FallbackImageElement
{...fallbackProps}
color={fallbackIconColor}
eth={eth}
imageUrl={imageUrl}
onError={hideFallbackImage}
onLoad={showFallbackImage}
showImage={showImage}
{...(ios && { layoutSize: width })}
size={ios ? getIconSize(width) : width}
/>
</Centered>

{!isLoaded && <View style={sx.fallbackWrapper}>{children?.()}</View>}
</View>
);
};

export default magicMemo(CoinIconFallback, [
'address',
'type',
'style',
'symbol',
]);
const sx = StyleSheet.create({
coinIconContainer: {
alignItems: 'center',

justifyContent: 'center',
overflow: 'visible',
},
coinIconFallback: {
overflow: 'hidden',
},
container: {
elevation: 6,

overflow: 'visible',
paddingTop: 9,
},
contract: {},
fallbackWrapper: {
left: 0,
position: 'absolute',
top: 0,
},
reactCoinIconContainer: {
alignItems: 'center',
justifyContent: 'center',
},
reactCoinIconImage: {
height: '100%',
width: '100%',
},
withShadow: {
elevation: 6,
shadowOffset: {
height: 4,
width: 0,
},
shadowOpacity: 0.3,
shadowRadius: 6,
},
});

0 comments on commit 31d5313

Please sign in to comment.