Skip to content

Commit

Permalink
Show placeholder when there is an error loading the NFT image (#2405)
Browse files Browse the repository at this point in the history
* Show placeholder when there is an error loading the image

* Add error boundary

* Fix image scaling

* Refactor placeholder
  • Loading branch information
michaeljscript committed Mar 22, 2023
1 parent e8b3100 commit f6e9201
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 22 deletions.
16 changes: 10 additions & 6 deletions src/NftDetails/NftDetails.tsx
@@ -1,7 +1,7 @@
import {RouteProp, useRoute} from '@react-navigation/native'
import React, {ReactNode, useState} from 'react'
import {defineMessages, useIntl} from 'react-intl'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Dimensions, StyleSheet, TouchableOpacity, View} from 'react-native'
import {ScrollView} from 'react-native-gesture-handler'

import {CopyButton, FadeIn, Icon, Link, Spacer, Text} from '../components'
Expand Down Expand Up @@ -64,7 +64,7 @@ const UnModeratedNftImage = ({nft}: {nft: YoroiNft}) => {
const navigateTo = useNavigateTo()
return (
<TouchableOpacity onPress={() => navigateTo.nftZoom(nft.id)} style={styles.imageWrapper}>
<NftPreview nft={nft} style={styles.image} height={380} />
<NftPreview nft={nft} style={styles.image} height={IMAGE_HEIGHT} width={IMAGE_WIDTH} />
</TouchableOpacity>
)
}
Expand All @@ -78,14 +78,14 @@ const ModeratedNftImage = ({nft}: {nft: YoroiNft}) => {
if (!canShowNft) {
return (
<View style={styles.imageWrapper}>
<NftPreview nft={nft} style={styles.image} height={380} showPlaceholder />
<NftPreview nft={nft} style={styles.image} height={IMAGE_HEIGHT} width={IMAGE_WIDTH} showPlaceholder />
</View>
)
}

return (
<TouchableOpacity onPress={() => navigateTo.nftZoom(nft.id)} style={styles.imageWrapper}>
<NftPreview nft={nft} style={styles.image} height={380} />
<NftPreview nft={nft} style={styles.image} height={IMAGE_HEIGHT} width={IMAGE_WIDTH} />
</TouchableOpacity>
)
}
Expand Down Expand Up @@ -202,6 +202,10 @@ const NftMetadata = ({nft}: {nft: YoroiNft}) => {
)
}

const IMAGE_HEIGHT = 380
const IMAGE_PADDING = 16
const IMAGE_WIDTH = Dimensions.get('window').width - IMAGE_PADDING * 2

const styles = StyleSheet.create({
copyButton: {
flex: 1,
Expand Down Expand Up @@ -234,10 +238,10 @@ const styles = StyleSheet.create({
flexGrow: 1,
},
contentContainer: {
paddingHorizontal: 16,
paddingHorizontal: IMAGE_PADDING,
},
rowContainer: {
paddingVertical: 16,
paddingVertical: IMAGE_PADDING,
},
rowTitleContainer: {
display: 'flex',
Expand Down
54 changes: 38 additions & 16 deletions src/components/NftPreview/NftPreview.tsx
@@ -1,4 +1,5 @@
import React from 'react'
import React, {useState} from 'react'
import {ErrorBoundary} from 'react-error-boundary'
import {Image, ImageResizeMode, ImageStyle, StyleProp} from 'react-native'
import {SvgUri} from 'react-native-svg'

Expand All @@ -24,41 +25,62 @@ export const NftPreview = ({
resizeMode?: ImageResizeMode
blurRadius?: number
}) => {
const [error, setError] = useState(false)
const uri = showThumbnail ? nft.thumbnail : nft.logo
const isUriSvg =
isString(uri) &&
(uri.toLowerCase().endsWith('.svg') ||
isSvgMediaType(nft.metadata.originalMetadata?.mediaType) ||
isSvgMediaType(getNftFilenameMediaType(nft, uri)))
const shouldShowPlaceholder = !isString(uri) || showPlaceholder || (isUriSvg && blurRadius !== undefined)
const shouldShowPlaceholder = !isString(uri) || showPlaceholder || (isUriSvg && blurRadius !== undefined) || error

if (shouldShowPlaceholder) {
// Since SvgUri does not support blur radius, we show a placeholder
return <Image source={placeholder} style={[style, {width, height}]} resizeMode={resizeMode ?? 'contain'} />
return <PlaceholderImage height={height} style={style} width={width} resizeMode={resizeMode} />
}

if (isUriSvg) {
// passing width or height with value undefined has a different behavior than not passing it at all
return (
<SvgUri
{...(width !== undefined ? {width} : undefined)}
height={height}
uri={uri}
style={style}
preserveAspectRatio="xMinYMin meet"
/>
<ErrorBoundary
fallback={<PlaceholderImage height={height} style={style} width={width} resizeMode={resizeMode} />}
>
<SvgUri
{...(width !== undefined ? {width} : undefined)}
height={height}
uri={uri}
style={style}
preserveAspectRatio="xMinYMin meet"
onError={() => setError(true)}
/>
</ErrorBoundary>
)
}
return (
<Image
blurRadius={blurRadius}
source={{uri}}
style={[style, {width, height}]}
resizeMode={resizeMode ?? 'contain'}
/>
<ErrorBoundary fallback={<PlaceholderImage height={height} style={style} width={width} resizeMode={resizeMode} />}>
<Image
blurRadius={blurRadius}
source={{uri}}
style={[style, {width, height}]}
resizeMode={resizeMode ?? 'contain'}
onError={() => setError(true)}
/>
</ErrorBoundary>
)
}

const PlaceholderImage = ({
style,
width,
height,
resizeMode,
}: {
style?: StyleProp<ImageStyle>
height: number
width?: number
resizeMode?: ImageResizeMode
}) => <Image source={placeholder} style={[style, {width, height}]} resizeMode={resizeMode ?? 'contain'} />

const isSvgMediaType = (mediaType: string | undefined): boolean => {
return mediaType === 'image/svg+xml'
}
Expand Down

0 comments on commit f6e9201

Please sign in to comment.