Rework search suggestions for performance (#492)

This commit is contained in:
Paul Frazee 2023-04-18 18:29:54 -05:00 committed by GitHub
parent 1ab8f31517
commit 75fd653be3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 312 additions and 448 deletions

View file

@ -1,68 +0,0 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {RefWithInfoAndFollowers} from 'state/models/discovery/foafs'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
export const SuggestedFollows = ({
title,
suggestions,
}: {
title: string
suggestions: (
| AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileView
| RefWithInfoAndFollowers
)[]
}) => {
const pal = usePalette('default')
return (
<View style={[styles.container, pal.view, pal.border]}>
<Text type="title" style={[styles.heading, pal.text]}>
{title}
</Text>
{suggestions.map(item => (
<View key={item.did} style={[styles.card, pal.view, pal.border]}>
<ProfileCardWithFollowBtn
key={item.did}
did={item.did}
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
labels={item.labels}
noBg
noBorder
description={
item.description
? (item as AppBskyActorDefs.ProfileView).description
: ''
}
followers={
item.followers
? (item.followers as AppBskyActorDefs.ProfileView[])
: undefined
}
/>
</View>
))}
</View>
)
}
const styles = StyleSheet.create({
container: {
borderBottomWidth: 1,
},
heading: {
fontWeight: 'bold',
paddingHorizontal: 12,
paddingBottom: 8,
},
card: {
borderTopWidth: 1,
},
})

View file

@ -1,65 +1,255 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import React, {forwardRef, ForwardedRef} from 'react'
import {RefreshControl, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {AppBskyActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views'
import {FoafsModel} from 'state/models/discovery/foafs'
import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
import {SuggestedFollows} from 'view/com/discover/SuggestedFollows'
import {
SuggestedActorsModel,
SuggestedActor,
} from 'state/models/discovery/suggested-actors'
import {Text} from '../util/text/Text'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {RefWithInfoAndFollowers} from 'state/models/discovery/foafs'
import {usePalette} from 'lib/hooks/usePalette'
interface Heading {
_reactKey: string
type: 'heading'
title: string
}
interface RefWrapper {
_reactKey: string
type: 'ref'
ref: RefWithInfoAndFollowers
}
interface SuggestWrapper {
_reactKey: string
type: 'suggested'
suggested: SuggestedActor
}
interface ProfileView {
_reactKey: string
type: 'profile-view'
view: AppBskyActorDefs.ProfileViewBasic
}
type Item = Heading | RefWrapper | SuggestWrapper | ProfileView
export const Suggestions = observer(
({
foafs,
suggestedActors,
}: {
foafs: FoafsModel
suggestedActors: SuggestedActorsModel
}) => {
if (foafs.isLoading || suggestedActors.isLoading) {
return <ProfileCardFeedLoadingPlaceholder />
}
return (
<>
{foafs.popular.length > 0 && (
<View style={styles.suggestions}>
<SuggestedFollows
title="In your network"
suggestions={foafs.popular}
/>
</View>
)}
{suggestedActors.hasContent && (
<View style={styles.suggestions}>
<SuggestedFollows
title="Suggested follows"
suggestions={suggestedActors.suggestions}
/>
</View>
)}
{foafs.sources.map((source, i) => {
forwardRef(
(
{
foafs,
suggestedActors,
}: {
foafs: FoafsModel
suggestedActors: SuggestedActorsModel
},
flatListRef: ForwardedRef<FlatList>,
) => {
const pal = usePalette('default')
const [refreshing, setRefreshing] = React.useState(false)
const data = React.useMemo(() => {
let items: Item[] = []
if (foafs.popular.length > 0) {
items = items
.concat([
{
_reactKey: '__popular_heading__',
type: 'heading',
title: 'In your network',
},
])
.concat(
foafs.popular.map(ref => ({
_reactKey: `popular-${ref.did}`,
type: 'ref',
ref,
})),
)
}
if (suggestedActors.hasContent) {
items = items
.concat([
{
_reactKey: '__suggested_heading__',
type: 'heading',
title: 'Suggested follows',
},
])
.concat(
suggestedActors.suggestions.map(suggested => ({
_reactKey: `suggested-${suggested.did}`,
type: 'suggested',
suggested,
})),
)
}
for (const source of foafs.sources) {
const item = foafs.foafs.get(source)
if (!item || item.follows.length === 0) {
return <View key={`sf-${item?.did || i}`} />
return
}
return (
<View key={`sf-${item.did}`} style={styles.suggestions}>
<SuggestedFollows
title={`Followed by ${sanitizeDisplayName(
items = items
.concat([
{
_reactKey: `__${item.did}_heading__`,
type: 'heading',
title: `Followed by ${sanitizeDisplayName(
item.displayName || item.handle,
)}`}
suggestions={item.follows.slice(0, 10)}
/>
</View>
)
})}
</>
)
},
)}`,
},
])
.concat(
item.follows.slice(0, 10).map(view => ({
_reactKey: `${item.did}-${view.did}`,
type: 'profile-view',
view,
})),
)
}
return items
}, [
foafs.popular,
suggestedActors.hasContent,
suggestedActors.suggestions,
foafs.sources,
foafs.foafs,
])
const onRefresh = React.useCallback(async () => {
setRefreshing(true)
try {
await foafs.fetch()
} finally {
setRefreshing(false)
}
}, [foafs, setRefreshing])
const renderItem = React.useCallback(
({item}: {item: Item}) => {
if (item.type === 'heading') {
return (
<Text type="title" style={[styles.heading, pal.text]}>
{item.title}
</Text>
)
}
if (item.type === 'ref') {
return (
<View style={[styles.card, pal.view, pal.border]}>
<ProfileCardWithFollowBtn
key={item.ref.did}
did={item.ref.did}
handle={item.ref.handle}
displayName={item.ref.displayName}
avatar={item.ref.avatar}
labels={item.ref.labels}
noBg
noBorder
description={
item.ref.description
? (item.ref as AppBskyActorDefs.ProfileView).description
: ''
}
followers={
item.ref.followers
? (item.ref.followers as AppBskyActorDefs.ProfileView[])
: undefined
}
/>
</View>
)
}
if (item.type === 'profile-view') {
return (
<View style={[styles.card, pal.view, pal.border]}>
<ProfileCardWithFollowBtn
key={item.view.did}
did={item.view.did}
handle={item.view.handle}
displayName={item.view.displayName}
avatar={item.view.avatar}
labels={item.view.labels}
noBg
noBorder
description={
item.view.description
? (item.view as AppBskyActorDefs.ProfileView).description
: ''
}
/>
</View>
)
}
if (item.type === 'suggested') {
return (
<View style={[styles.card, pal.view, pal.border]}>
<ProfileCardWithFollowBtn
key={item.suggested.did}
did={item.suggested.did}
handle={item.suggested.handle}
displayName={item.suggested.displayName}
avatar={item.suggested.avatar}
labels={item.suggested.labels}
noBg
noBorder
description={
item.suggested.description
? (item.suggested as AppBskyActorDefs.ProfileView)
.description
: ''
}
/>
</View>
)
}
return null
},
[pal],
)
if (foafs.isLoading || suggestedActors.isLoading) {
return (
<CenteredView>
<ProfileCardFeedLoadingPlaceholder />
</CenteredView>
)
}
return (
<FlatList
ref={flatListRef}
data={data}
keyExtractor={item => item._reactKey}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor={pal.colors.text}
titleColor={pal.colors.text}
/>
}
renderItem={renderItem}
initialNumToRender={15}
/>
)
},
),
)
const styles = StyleSheet.create({
suggestions: {
marginTop: 10,
marginBottom: 20,
heading: {
fontWeight: 'bold',
paddingHorizontal: 12,
paddingBottom: 8,
paddingTop: 16,
},
card: {
borderTopWidth: 1,
},
})