import { useHeaderHeight } from '@react-navigation/elements'
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView, StyleSheet, Text, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import Touchable from 'src/components/Touchable'
import ImageErrorIcon from 'src/icons/ImageErrorIcon'
import OpenLinkIcon from 'src/icons/OpenLinkIcon'
import { headerWithBackButton } from 'src/navigator/Headers'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'
import NftMedia from 'src/nfts/NftMedia'
import NftsLoadError from 'src/nfts/NftsLoadError'
import { Nft, NftOrigin } from 'src/nfts/types'
import colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import variables from 'src/styles/variables'
import { NetworkId } from 'src/transactions/types'
import { blockExplorerUrls } from 'src/web3/networkConfig'
const DEFAULT_HEIGHT = 360
interface NftThumbnailProps {
nft: Nft
isActive: boolean
onPress: () => void
}
function NftThumbnail({ nft, isActive, onPress }: NftThumbnailProps) {
return (
}
width={40}
height={40}
borderRadius={8}
testID="NftsInfoCarousel/ThumbnailImage"
origin={NftOrigin.NftsInfoCarouselThumbnail}
mediaType="image"
/>
)
}
interface NftImageCarouselProps {
nfts: Nft[]
handleOnPress: (nft: Nft) => void
activeNft: Nft
}
function NftImageCarousel({ nfts, handleOnPress, activeNft }: NftImageCarouselProps) {
return (
{nfts.map((nft) => {
return (
handleOnPress(nft)}
isActive={
`${activeNft.contractAddress}-${activeNft.tokenId}` ===
`${nft.contractAddress}-${nft.tokenId}`
}
nft={nft}
/>
)
})}
)
}
type Props = NativeStackScreenProps
export default function NftsInfoCarousel({ route }: Props) {
const { nfts, networkId } = route.params
const [activeNft, setActiveNft] = useState(nfts[0] ?? null)
const { t } = useTranslation()
const headerHeight = useHeaderHeight()
const blockExplorerUri = useMemo(() => {
if (
!activeNft?.tokenId ||
!activeNft.contractAddress ||
!/^(0|[1-9]\d*|0x[0-9a-fA-F]+)$/.test(activeNft.tokenId)
) {
return null
}
// tokenId could be decimal or hex string of 256 bit integers, parse it as a
// BigInt and convert back to string
const tokenId = BigInt(activeNft.tokenId).toString()
switch (networkId) {
case NetworkId['celo-mainnet']:
case NetworkId['celo-alfajores']:
return `${blockExplorerUrls[networkId].baseNftUrl}${activeNft.contractAddress}/instance/${tokenId}/metadata`
case NetworkId['ethereum-mainnet']:
case NetworkId['ethereum-sepolia']:
return `${blockExplorerUrls[networkId].baseNftUrl}${activeNft.contractAddress}/${tokenId}`
default:
return `${blockExplorerUrls[networkId].baseNftUrl}${activeNft.contractAddress}?a=${tokenId}`
}
}, [activeNft, networkId])
function pressExplorerLink() {
if (blockExplorerUri) {
navigate(Screens.WebViewScreen, { uri: blockExplorerUri })
}
}
function handleThumbnailPress(nft: Nft) {
setActiveNft(nft)
}
// Full page error screen shown when ntfs === []
if (!activeNft) {
return
}
const networkIdToExplorerString: Record = {
[NetworkId['celo-mainnet']]: t('nftInfoCarousel.viewOnCeloExplorer'),
[NetworkId['celo-alfajores']]: t('nftInfoCarousel.viewOnCeloExplorer'),
[NetworkId['ethereum-mainnet']]: t('viewOnEthereumBlockExplorer'),
[NetworkId['ethereum-sepolia']]: t('viewOnEthereumBlockExplorer'),
[NetworkId['arbitrum-one']]: t('viewOnArbiscan'),
[NetworkId['arbitrum-sepolia']]: t('viewOnArbiscan'),
[NetworkId['op-mainnet']]: t('viewOnOPMainnetExplorer'),
[NetworkId['op-sepolia']]: t('viewOnOPSepoliaExplorer'),
[NetworkId['polygon-pos-mainnet']]: t('viewOnPolygonPoSScan'),
[NetworkId['polygon-pos-amoy']]: t('viewOnPolygonPoSScan'),
[NetworkId['base-mainnet']]: t('viewOnBaseScan'),
[NetworkId['base-sepolia']]: t('viewOnBaseScan'),
}
return (
{/* Main Nft Video or Image */}
{t('nftInfoCarousel.nftImageLoadError')}
}
testID={
activeNft.metadata?.animation_url
? 'NftsInfoCarousel/MainVideo'
: 'NftsInfoCarousel/MainImage'
}
/>
{/* Display a carousel selection if multiple images */}
{nfts.length > 1 && (
)}
{/* Nft Details */}
{activeNft.metadata && (
<>
{!!activeNft.metadata?.name && (
{activeNft.metadata?.name}
)}
{!!activeNft.metadata?.description && (
{t('nftInfoCarousel.description')}
{activeNft.metadata?.description}
)}
{activeNft.metadata?.attributes && (
{t('nftInfoCarousel.attributes')}
{activeNft.metadata?.attributes.map((attribute, index) => (
{attribute.trait_type}
{attribute.value}
))}
)}
>
)}
{/* Nft Explorer Link */}
{!!blockExplorerUri && (
{networkIdToExplorerString[networkId]}
)}
)
}
NftsInfoCarousel.navigationOptions = () => ({
...headerWithBackButton,
headerTransparent: true,
headerShown: true,
headerStyle: {
backgroundColor: 'transparent',
},
animation: 'slide_from_right',
animationDuration: 130,
})
const styles = StyleSheet.create({
attributeText: {
...typeScale.bodyMedium,
},
attributeTitle: {
...typeScale.labelSmall,
color: colors.contentSecondary,
},
attributesContainer: {
paddingBottom: Spacing.Thick24,
},
carouselScrollViewContentContainer: {
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center',
marginLeft: Spacing.Regular16,
},
errorThumbnail: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.backgroundTertiary,
},
errorImageText: {
marginTop: Spacing.Regular16,
...typeScale.bodyMedium,
color: colors.contentSecondary,
},
explorerLink: {
...typeScale.labelSmall,
color: colors.successPrimary,
paddingRight: Spacing.Smallest8,
},
explorerLinkContainer: {
alignItems: 'center',
flexDirection: 'row',
},
nftImageCarousel: {
flexDirection: 'row',
paddingHorizontal: Spacing.Regular16,
paddingTop: Spacing.Smallest8,
},
nftImageCarouselContainer: {
flex: 1,
},
nftImageLoadingErrorContainer: {
width: '100%',
height: DEFAULT_HEIGHT,
backgroundColor: colors.backgroundSecondary,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
},
nftThumbnailSelected: {
height: 40,
width: 40,
justifyContent: 'center',
alignItems: 'center',
},
nftThumbnailSharedContainer: {
borderRadius: Spacing.Smallest8,
marginRight: Spacing.Smallest8,
overflow: 'hidden',
},
nftThumbnailUnselected: {
height: 32,
opacity: 0.5,
width: 32,
justifyContent: 'center',
alignItems: 'center',
},
safeAreaView: {
flex: 1,
},
sectionContainer: {
marginHorizontal: Spacing.Thick24,
marginTop: Spacing.Regular16,
},
sectionContainerLast: {
marginBottom: Spacing.Large32,
},
subSectionTitle: {
...typeScale.labelSemiBoldLarge,
marginBottom: Spacing.Regular16,
},
text: {
...typeScale.bodyMedium,
},
title: {
...typeScale.titleMedium,
},
})