Merge the suggested actors model with the general suggestion system (#343)

zio/stable
Paul Frazee 2023-03-21 19:18:15 -05:00 committed by GitHub
parent f20fb92dc3
commit 4f814207bc
6 changed files with 54 additions and 84 deletions

View File

@ -1,7 +1,7 @@
import {makeAutoObservable, runInAction} from 'mobx' import {makeAutoObservable, runInAction} from 'mobx'
import {AppBskyActorProfile as Profile} from '@atproto/api' import {AppBskyActorProfile as Profile} from '@atproto/api'
import shuffle from 'lodash.shuffle' import shuffle from 'lodash.shuffle'
import {RootStoreModel} from './root-store' import {RootStoreModel} from '../root-store'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {bundleAsync} from 'lib/async/bundle' import {bundleAsync} from 'lib/async/bundle'
import {SUGGESTED_FOLLOWS} from 'lib/constants' import {SUGGESTED_FOLLOWS} from 'lib/constants'
@ -10,7 +10,7 @@ const PAGE_SIZE = 30
export type SuggestedActor = Profile.ViewBasic | Profile.View export type SuggestedActor = Profile.ViewBasic | Profile.View
export class SuggestedActorsViewModel { export class SuggestedActorsModel {
// state // state
pageSize = PAGE_SIZE pageSize = PAGE_SIZE
isLoading = false isLoading = false

View File

@ -11,7 +11,11 @@ export const SuggestedFollows = ({
suggestions, suggestions,
}: { }: {
title: string title: string
suggestions: (AppBskyActorRef.WithInfo | RefWithInfoAndFollowers)[] suggestions: (
| AppBskyActorRef.WithInfo
| RefWithInfoAndFollowers
| AppBskyActorProfile.View
)[]
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
@ -30,7 +34,11 @@ export const SuggestedFollows = ({
avatar={item.avatar} avatar={item.avatar}
noBg noBg
noBorder noBorder
description="" description={
item.description
? (item as AppBskyActorProfile.View).description
: ''
}
followers={ followers={
item.followers item.followers
? (item.followers as AppBskyActorProfile.View[]) ? (item.followers as AppBskyActorProfile.View[])

View File

@ -1,66 +0,0 @@
import React from 'react'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {SuggestedActorsViewModel} from 'state/models/suggested-actors-view'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {Text} from '../util/text/Text'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
export const WhoToFollow = observer(() => {
const pal = usePalette('default')
const store = useStores()
const suggestedActorsView = React.useMemo<SuggestedActorsViewModel>(
() => new SuggestedActorsViewModel(store, {pageSize: 15}),
[store],
)
React.useEffect(() => {
suggestedActorsView.loadMore(true)
}, [store, suggestedActorsView])
return (
<>
{(suggestedActorsView.hasContent || suggestedActorsView.isLoading) && (
<Text type="title" style={[styles.heading, pal.text]}>
Who to follow
</Text>
)}
{suggestedActorsView.hasContent && (
<>
<View style={[pal.border, styles.bottomBorder]}>
{suggestedActorsView.suggestions.map(item => (
<ProfileCardWithFollowBtn
key={item.did}
did={item.did}
declarationCid={item.declaration.cid}
handle={item.handle}
displayName={item.displayName}
avatar={item.avatar}
description={item.description}
/>
))}
</View>
</>
)}
{suggestedActorsView.isLoading && (
<View style={s.mt10}>
<ActivityIndicator />
</View>
)}
</>
)
})
const styles = StyleSheet.create({
heading: {
fontWeight: 'bold',
paddingHorizontal: 12,
paddingBottom: 8,
},
bottomBorder: {
borderBottomWidth: 1,
},
})

View File

@ -2,15 +2,21 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {FoafsModel} from 'state/models/discovery/foafs' import {FoafsModel} from 'state/models/discovery/foafs'
import {WhoToFollow} from 'view/com/discover/WhoToFollow' import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
import {SuggestedFollows} from 'view/com/discover/SuggestedFollows' import {SuggestedFollows} from 'view/com/discover/SuggestedFollows'
import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' import {ProfileCardFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
export const Suggestions = observer(({foafs}: {foafs: FoafsModel}) => { export const Suggestions = observer(
if (foafs.isLoading) { ({
foafs,
suggestedActors,
}: {
foafs: FoafsModel
suggestedActors: SuggestedActorsModel
}) => {
if (foafs.isLoading || suggestedActors.isLoading) {
return <ProfileCardFeedLoadingPlaceholder /> return <ProfileCardFeedLoadingPlaceholder />
} }
if (foafs.hasContent) {
return ( return (
<> <>
{foafs.popular.length > 0 && ( {foafs.popular.length > 0 && (
@ -21,7 +27,14 @@ export const Suggestions = observer(({foafs}: {foafs: FoafsModel}) => {
/> />
</View> </View>
)} )}
<WhoToFollow /> {suggestedActors.hasContent && (
<View style={styles.suggestions}>
<SuggestedFollows
title="Suggested follows"
suggestions={suggestedActors.suggestions}
/>
</View>
)}
{foafs.sources.map((source, i) => { {foafs.sources.map((source, i) => {
const item = foafs.foafs.get(source) const item = foafs.foafs.get(source)
if (!item || item.follows.length === 0) { if (!item || item.follows.length === 0) {
@ -38,9 +51,8 @@ export const Suggestions = observer(({foafs}: {foafs: FoafsModel}) => {
})} })}
</> </>
) )
} },
return <WhoToFollow /> )
})
const styles = StyleSheet.create({ const styles = StyleSheet.create({
suggestions: { suggestions: {

View File

@ -19,6 +19,7 @@ import {useStores} from 'state/index'
import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view' import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
import {SearchUIModel} from 'state/models/ui/search' import {SearchUIModel} from 'state/models/ui/search'
import {FoafsModel} from 'state/models/discovery/foafs' import {FoafsModel} from 'state/models/discovery/foafs'
import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
import {HeaderWithInput} from 'view/com/search/HeaderWithInput' import {HeaderWithInput} from 'view/com/search/HeaderWithInput'
import {Suggestions} from 'view/com/search/Suggestions' import {Suggestions} from 'view/com/search/Suggestions'
import {SearchResults} from 'view/com/search/SearchResults' import {SearchResults} from 'view/com/search/SearchResults'
@ -44,6 +45,10 @@ export const SearchScreen = withAuthRequired(
() => new FoafsModel(store), () => new FoafsModel(store),
[store], [store],
) )
const suggestedActors = React.useMemo<SuggestedActorsModel>(
() => new SuggestedActorsModel(store),
[store],
)
const [searchUIModel, setSearchUIModel] = React.useState< const [searchUIModel, setSearchUIModel] = React.useState<
SearchUIModel | undefined SearchUIModel | undefined
>() >()
@ -65,9 +70,12 @@ export const SearchScreen = withAuthRequired(
if (!foafs.hasData) { if (!foafs.hasData) {
foafs.fetch() foafs.fetch()
} }
if (!suggestedActors.hasLoaded) {
suggestedActors.loadMore(true)
}
return cleanup return cleanup
}, [store, autocompleteView, foafs]), }, [store, autocompleteView, foafs, suggestedActors]),
) )
const onChangeQuery = React.useCallback( const onChangeQuery = React.useCallback(
@ -163,7 +171,7 @@ export const SearchScreen = withAuthRequired(
</Text> </Text>
</View> </View>
) : ( ) : (
<Suggestions foafs={foafs} /> <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
)} )}
<View style={s.footerSpacer} /> <View style={s.footerSpacer} />
</ScrollView> </ScrollView>

View File

@ -2,6 +2,7 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {SearchUIModel} from 'state/models/ui/search' import {SearchUIModel} from 'state/models/ui/search'
import {FoafsModel} from 'state/models/discovery/foafs' import {FoafsModel} from 'state/models/discovery/foafs'
import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ScrollView} from 'view/com/util/Views' import {ScrollView} from 'view/com/util/Views'
import {Suggestions} from 'view/com/search/Suggestions' import {Suggestions} from 'view/com/search/Suggestions'
@ -24,6 +25,10 @@ export const SearchScreen = withAuthRequired(
() => new FoafsModel(store), () => new FoafsModel(store),
[store], [store],
) )
const suggestedActors = React.useMemo<SuggestedActorsModel>(
() => new SuggestedActorsModel(store),
[store],
)
const searchUIModel = React.useMemo<SearchUIModel | undefined>( const searchUIModel = React.useMemo<SearchUIModel | undefined>(
() => (route.params.q ? new SearchUIModel(store) : undefined), () => (route.params.q ? new SearchUIModel(store) : undefined),
[route.params.q, store], [route.params.q, store],
@ -36,7 +41,10 @@ export const SearchScreen = withAuthRequired(
if (!foafs.hasData) { if (!foafs.hasData) {
foafs.fetch() foafs.fetch()
} }
}, [foafs, searchUIModel, route.params.q]) if (!suggestedActors.hasLoaded) {
suggestedActors.loadMore(true)
}
}, [foafs, suggestedActors, searchUIModel, route.params.q])
if (searchUIModel) { if (searchUIModel) {
return <SearchResults model={searchUIModel} /> return <SearchResults model={searchUIModel} />
@ -47,7 +55,7 @@ export const SearchScreen = withAuthRequired(
testID="searchScrollView" testID="searchScrollView"
style={[pal.view, styles.container]} style={[pal.view, styles.container]}
scrollEventThrottle={100}> scrollEventThrottle={100}>
<Suggestions foafs={foafs} /> <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
<View style={s.footerSpacer} /> <View style={s.footerSpacer} />
</ScrollView> </ScrollView>
) )