Skip to content

Commit

Permalink
Extract search logic from nfts part2 (#2424)
Browse files Browse the repository at this point in the history
  • Loading branch information
banklesss committed Apr 4, 2023
1 parent 14b9d67 commit a8cf8f4
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 67 deletions.
6 changes: 3 additions & 3 deletions src/Nfts/ImageGallery/ImageGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {useModeratedNftImage} from '../hooks'

type Props = {
nfts: YoroiNft[]
onSelect: (index: number) => void
onSelect: (id: string) => void
onRefresh: () => void
isRefreshing: boolean
}
Expand All @@ -33,9 +33,9 @@ export const ImageGallery = ({nfts = [], onSelect, onRefresh, isRefreshing}: Pro
refreshing={isRefreshing}
renderItem={(nft) =>
features.moderatingNftsEnabled ? (
<ModeratedImage onPress={() => onSelect(nfts.indexOf(nft))} nft={nft} key={nft.id} />
<ModeratedImage onPress={() => onSelect(nft.id)} nft={nft} key={nft.id} />
) : (
<UnModeratedImage onPress={() => onSelect(nfts.indexOf(nft))} nft={nft} key={nft.id} />
<UnModeratedImage onPress={() => onSelect(nft.id)} nft={nft} key={nft.id} />
)
}
/>
Expand Down
81 changes: 48 additions & 33 deletions src/Nfts/Nfts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,72 @@ import {RefreshControl, ScrollView, StyleSheet, Text, View} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'

import {Icon, Spacer} from '../components'
import {useFilteredNfts} from './hooks'
import {useSearch} from '../Search/SearchContext'
import {useSelectedWallet} from '../SelectedWallet'
import {useNfts} from '../yoroi-wallets'
import {filterNfts} from './filterNfts'
import {ImageGallery, SkeletonGallery} from './ImageGallery'
import {useNavigateTo} from './navigation'
import {NoNftsScreen} from './NoNftsScreen'

export const Nfts = () => {
const navigateTo = useNavigateTo()
const handleNftSelect = (index: number) => navigateTo.nftDetails(nfts[index].id)
const [isManualRefreshing, setIsManualRefreshing] = React.useState(false)
const wallet = useSelectedWallet()
const strings = useStrings()

const {search, nfts, isLoading, refetch, isError} = useFilteredNfts({
const {isLoading, nfts, refetch, isError} = useNfts(wallet, {
onSettled: () => {
if (isManualRefreshing) setIsManualRefreshing(false)
},
})

const onRefresh = React.useCallback(() => {
const {search: nftsSearchTerm} = useSearch()
const filteredNfts = filterNfts(nftsSearchTerm, nfts)
const sortedNfts = filteredNfts.sort((NftA, NftB) => sortNfts(NftA.name, NftB.name))
const nftsSearchResult = filterNfts(nftsSearchTerm, sortedNfts)

const hasEmptySearchResult = nftsSearchTerm.length > 0 && nftsSearchResult.length === 0
const hasNotNfts = nftsSearchResult.length === 0

const onRefresh = () => {
setIsManualRefreshing(true)
refetch()
}, [refetch])
}

if (isError) {
return (
<ScreenWrapper>
<Wrapper>
<ErrorScreen onRefresh={onRefresh} isRefreshing={isManualRefreshing} />
</ScreenWrapper>
</Wrapper>
)
}

if (isLoading) {
return (
<ScreenWrapper>
<LoadingScreen nftsCount={nfts.length} />
</ScreenWrapper>
<Wrapper>
<LoadingScreen nftsCount={nftsSearchResult.length} />
</Wrapper>
)
}

if (search.length > 0 && nfts.length === 0) {
if (hasEmptySearchResult) {
return (
<ScreenWrapper>
<Wrapper>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewError}
refreshControl={<RefreshControl onRefresh={onRefresh} refreshing={isManualRefreshing} />}
>
<NoNftsScreen message={strings.noNftsFound} />
</ScrollView>
</ScreenWrapper>
</Wrapper>
)
}

if (search.length === 0 && nfts.length === 0) {
if (hasNotNfts) {
return (
<ScreenWrapper>
<Wrapper>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewError}
Expand All @@ -68,35 +79,40 @@ export const Nfts = () => {
message={strings.noNftsInWallet}
heading={
<View>
<NftCount count={nfts.length} />
<NftCount count={0} />

<Spacer height={16} />
</View>
}
/>
</ScrollView>
</ScreenWrapper>
</Wrapper>
)
}

return (
<ScreenWrapper>
<Wrapper>
<View style={styles.galleryContainer}>
{search.length === 0 && (
<View>
<NftCount count={nfts.length} />
{nftsSearchTerm.length === 0 && (
<>
<NftCount count={nftsSearchResult.length} />

<Spacer height={16} />
</View>
</>
)}

<ImageGallery nfts={nfts} onSelect={handleNftSelect} onRefresh={onRefresh} isRefreshing={isManualRefreshing} />
<ImageGallery
nfts={nftsSearchResult}
onSelect={navigateTo.nftDetails}
onRefresh={onRefresh}
isRefreshing={isManualRefreshing}
/>
</View>
</ScreenWrapper>
</Wrapper>
)
}

function ScreenWrapper({children}: {children: ReactNode}) {
const Wrapper = ({children}: {children: ReactNode}) => {
return (
<SafeAreaView edges={['left', 'right', 'bottom']} style={styles.safeAreaView}>
<View style={styles.container}>
Expand All @@ -108,7 +124,7 @@ function ScreenWrapper({children}: {children: ReactNode}) {
)
}

function ErrorScreen({onRefresh, isRefreshing}: {onRefresh: () => void; isRefreshing: boolean}) {
const ErrorScreen = ({onRefresh, isRefreshing}: {onRefresh: () => void; isRefreshing: boolean}) => {
const strings = useStrings()

return (
Expand Down Expand Up @@ -140,20 +156,17 @@ function ErrorScreen({onRefresh, isRefreshing}: {onRefresh: () => void; isRefres
)
}

function NftCount({count}: {count?: number | string}) {
const NftCount = ({count}: {count?: number | string}) => {
const strings = useStrings()
const countText = `${strings.nftCount}: ${count ?? '-'}`

return (
<View>
<View style={styles.countBar}>
<Text style={styles.count}>{countText}</Text>
</View>
<View style={styles.countBar}>
<Text style={styles.count}>{`${strings.nftCount}: ${count ?? '-'}`}</Text>
</View>
)
}

function LoadingScreen({nftsCount}: {nftsCount: number}) {
const LoadingScreen = ({nftsCount}: {nftsCount: number}) => {
return (
<View style={styles.galleryContainer}>
<NftCount count={nftsCount} />
Expand All @@ -165,6 +178,8 @@ function LoadingScreen({nftsCount}: {nftsCount: number}) {
)
}

const sortNfts = (nftNameA: string, nftNameB: string): number => nftNameA.localeCompare(nftNameB)

const styles = StyleSheet.create({
safeAreaView: {
flex: 1,
Expand Down
5 changes: 4 additions & 1 deletion src/Nfts/NftsNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export const NftsNavigator = () => {

const Routes = () => {
const strings = useStrings()
const {searchHeaderOptions} = useSearchHeaderOptions({placeHolderText: strings.search, title: strings.title})
const {searchHeaderOptions} = useSearchHeaderOptions({
placeHolderText: strings.search,
title: strings.title,
})

return (
<Stack.Navigator>
Expand Down
26 changes: 26 additions & 0 deletions src/Nfts/filterNfts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {YoroiNft} from '../yoroi-wallets'
import {nft} from '../yoroi-wallets/mocks'
import {filterNfts} from './filterNfts'

describe('filterNfts', () => {
const cryptoWolf = {...nft, id: '0', fingerprint: 'fakefingerprint1', name: 'CryptoWolf #1234'}
const boredMonkey = {...nft, id: '1', fingerprint: 'fakefingerprint2', name: 'Bored Monkey #4567'}
const appleBlocks = {...nft, id: '2', fingerprint: 'fakefingerprint3', name: 'Apple Blocks #7890'}

const nfts: YoroiNft[] = [cryptoWolf, boredMonkey, appleBlocks]

it('filters NFTs correctly with case-insensitive search term', () => {
const filteredNfts = filterNfts('APple bLOcks', nfts)
expect(filteredNfts).toEqual([appleBlocks])
})

it('returns empty array when nft array is empty', () => {
const filteredNfts = filterNfts('Bored Monkey #4567', [])
expect(filteredNfts).toEqual([])
})

it('returns all NFTs when search term is empty', () => {
const filteredNfts = filterNfts('', nfts)
expect(filteredNfts).toEqual(nfts)
})
})
8 changes: 8 additions & 0 deletions src/Nfts/filterNfts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {YoroiNft} from '../yoroi-wallets'

export const filterNfts = (searchTerm: string, nfts: YoroiNft[]): YoroiNft[] => {
const searchTermLowerCase = searchTerm.toLowerCase()
const filteredNfts =
searchTermLowerCase.length > 0 ? nfts.filter((nft) => nft.name.toLowerCase().includes(searchTermLowerCase)) : nfts
return filteredNfts
}
20 changes: 1 addition & 19 deletions src/Nfts/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,4 @@
import {UseQueryOptions} from 'react-query'

import {useSearch} from '../Search'
import {useSelectedWallet} from '../SelectedWallet'
import {useNftModerationStatus, useNfts, YoroiNft, YoroiWallet} from '../yoroi-wallets'

export const useFilteredNfts = (options: UseQueryOptions<YoroiNft[], Error> = {}) => {
const {search} = useSearch()
const searchTermLowerCase = search.toLowerCase()
const wallet = useSelectedWallet()
const {nfts, isLoading, refetch, isRefetching, isError} = useNfts(wallet, options)
const filteredNfts =
searchTermLowerCase.length > 0 && nfts.length > 0
? nfts.filter((n) => n.name.toLowerCase().includes(searchTermLowerCase))
: nfts
const sortedNfts = filteredNfts.sort((a, b) => a.name.localeCompare(b.name))

return {nfts: sortedNfts, isLoading, refetch, isRefetching, isError, search} as const
}
import {useNftModerationStatus, YoroiWallet} from '../yoroi-wallets'

export const useModeratedNftImage = ({wallet, fingerprint}: {wallet: YoroiWallet; fingerprint: string}) => {
return useNftModerationStatus(
Expand Down
2 changes: 1 addition & 1 deletion src/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ type Guard<Params> = (params: Params | object) => params is Params

// OPTIONS
export const defaultStackNavigationOptionsV2: StackNavigationOptions = {
headerTintColor: COLORS.ERROR_TEXT_COLOR_DARK,
headerTitleStyle: {
fontSize: 16,
fontFamily: 'Rubik-Medium',
},
headerTintColor: COLORS.ERROR_TEXT_COLOR_DARK,
headerTitleContainerStyle: {
width: '70%',
alignItems: 'center',
Expand Down
23 changes: 13 additions & 10 deletions src/yoroi-wallets/mocks/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,20 +292,23 @@ const fetchPoolInfo = {
},
}

export const generateManyNfts = (): YoroiNft[] => {
return Array(30)
.fill(undefined)
.map((_, index) => ({
...nft,
name: 'NFT ' + index,
id: index + '',
fingerprint: getTokenFingerprint({policyId: nft.metadata.policyId, assetNameHex: asciiToHex('NFT ' + index)}),
metadata: {...nft.metadata, policyId: nft.metadata.policyId, assetNameHex: asciiToHex('NFT ' + index)},
}))
}

const fetchNfts = {
success: {
many: async (...args) => {
action('fetchNfts')(...args)
const nfts = Array(30)
.fill(undefined)
.map((_, index) => ({
...nft,
name: 'NFT ' + index,
id: index + '',
fingerprint: getTokenFingerprint({policyId: nft.metadata.policyId, assetNameHex: asciiToHex('NFT ' + index)}),
metadata: {...nft.metadata, policyId: nft.metadata.policyId, assetNameHex: asciiToHex('NFT ' + index)},
}))
return nfts
return generateManyNfts()
},
empty: async (...args) => {
action('fetchNfts')(...args)
Expand Down

0 comments on commit a8cf8f4

Please sign in to comment.