Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

[C-1427] Fix multiple issues with Collectibles on mobile #2296

Merged
merged 5 commits into from
Nov 23, 2022
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
13 changes: 10 additions & 3 deletions packages/common/src/services/solana-client/SolanaClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,18 @@ export class SolanaClient {
.filter(Boolean) as { type: SolanaNFTType; url: string }[]

const results = await Promise.all(
metadataUrls.map(async (item) =>
fetch(item!.url)
metadataUrls.map(async (item) => {
// Remove control characters from url
const sanitizedURL = item.url.replace(
// eslint-disable-next-line
/[\u0000-\u001F\u007F-\u009F]/g,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOF

''
)

return fetch(sanitizedURL)
.then((res) => res.json())
.catch(() => null)
)
})
)

const metadatas = results.filter(Boolean).map((metadata, i) => ({
Expand Down
49 changes: 45 additions & 4 deletions packages/mobile/src/screens/profile-screen/CollectiblesCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useCallback, useMemo } from 'react'
import type { ReactNode } from 'react'
import { useState, useCallback, useMemo } from 'react'

import type { ID, Collectible } from '@audius/common'
import {
accountSelectors,
collectibleDetailsUIActions,
modalsActions
} from '@audius/common'
import type { StyleProp, ViewStyle } from 'react-native'
import type { ImageStyle, StyleProp, ViewStyle } from 'react-native'
import { ImageBackground, Text, View } from 'react-native'
import { SvgUri } from 'react-native-svg'
import { useDispatch, useSelector } from 'react-redux'

import LogoEth from 'app/assets/images/logoEth.svg'
Expand Down Expand Up @@ -72,6 +74,45 @@ type CollectiblesCardProps = {
style?: StyleProp<ViewStyle>
}

type CollectibleImageProps = {
uri: string
style: StyleProp<ImageStyle>
children?: ReactNode
}

const CollectibleImage = (props: CollectibleImageProps) => {
const { children, style, uri } = props

const isSvg = uri.match(/.*\.svg$/)
const [size, setSize] = useState(0)

return isSvg ? (
<View
onLayout={(e) => {
setSize(e.nativeEvent.layout.width)
}}
>
<SvgUri
height={size}
width={size}
uri={uri}
style={{ borderRadius: 8, overflow: 'hidden' }}
>
{children}
</SvgUri>
</View>
) : (
<ImageBackground
style={style}
source={{
uri
}}
>
{children}
</ImageBackground>
)
}

export const CollectiblesCard = (props: CollectiblesCardProps) => {
const { collectible, style, ownerId } = props
const { name, frameUrl, isOwned, mediaType, gifUrl, chain } = collectible
Expand Down Expand Up @@ -102,7 +143,7 @@ export const CollectiblesCard = (props: CollectiblesCardProps) => {
>
{url ? (
<View>
<ImageBackground style={styles.image} source={{ uri: url }}>
<CollectibleImage style={styles.image} uri={url}>
{mediaType === 'VIDEO' ? (
<View style={styles.iconPlay}>
<IconPlay
Expand All @@ -120,7 +161,7 @@ export const CollectiblesCard = (props: CollectiblesCardProps) => {
<LogoSol height={16} />
)}
</View>
</ImageBackground>
</CollectibleImage>
</View>
) : null}
<Text style={styles.title}>{name}</Text>
Expand Down
42 changes: 37 additions & 5 deletions packages/mobile/src/screens/profile-screen/CollectiblesTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useContext, useRef } from 'react'
import { useCallback, useContext, useMemo, useRef } from 'react'

import type { Collectible } from '@audius/common'
import { accountSelectors, useProxySelector } from '@audius/common'
import Clipboard from '@react-native-clipboard/clipboard'
import type { FlatList as RNFlatList } from 'react-native'
Expand All @@ -8,6 +9,7 @@ import { useSelector } from 'react-redux'

import IconShare from 'app/assets/images/iconShare.svg'
import { Tile, GradientText, FlatList, Button } from 'app/components/core'
import LoadingSpinner from 'app/components/loading-spinner'
import { ToastContext } from 'app/components/toast/ToastContext'
import UserBadges from 'app/components/user-badges'
import { useScrollToTop } from 'app/hooks/useScrollToTop'
Expand Down Expand Up @@ -64,6 +66,12 @@ const useStyles = makeStyles(({ typography, palette, spacing }) => ({
collectibleListItem: {
marginHorizontal: spacing(4),
paddingVertical: spacing(2)
},
loadingSpinner: {
marginTop: spacing(4),
width: '100%',
alignItems: 'center',
justifyContent: 'center'
}
}))

Expand Down Expand Up @@ -94,11 +102,30 @@ export const CollectiblesTab = () => {
}
}, [profile, toast])

if (!profile) return null
const collectibles = useMemo(() => {
if (!profile) return []

const { collectibleList = [], solanaCollectibleList = [] } = profile
const allCollectibles = [...collectibleList, ...solanaCollectibleList]

if (!profile?.collectibles?.order) {
return allCollectibles
}

const { collectibleList = [], solanaCollectibleList = [], user_id } = profile
const collectibleMap: {
[key: string]: Collectible
} = allCollectibles.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})

const collectibles = [...collectibleList, ...solanaCollectibleList]
const collectibleKeySet = new Set(Object.keys(collectibleMap))

const visible = profile.collectibles.order
.filter((id) => collectibleKeySet.has(id))
.map((id) => collectibleMap[id])

return visible || []
}, [profile])

if (!profile) return null

return (
<FlatList
Expand Down Expand Up @@ -128,9 +155,14 @@ export const CollectiblesTab = () => {
</Tile>
}
data={collectibles}
ListEmptyComponent={
<View style={styles.loadingSpinner}>
<LoadingSpinner />
</View>
}
renderItem={({ item }) => (
<View style={styles.collectibleListItem}>
<CollectiblesCard collectible={item} ownerId={user_id} />
<CollectiblesCard collectible={item} ownerId={profile.user_id} />
</View>
)}
/>
Expand Down