Merge the suggested actors model with the general suggestion system (#343)
parent
f20fb92dc3
commit
4f814207bc
|
@ -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
|
|
@ -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[])
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -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) {
|
({
|
||||||
return <ProfileCardFeedLoadingPlaceholder />
|
foafs,
|
||||||
}
|
suggestedActors,
|
||||||
if (foafs.hasContent) {
|
}: {
|
||||||
|
foafs: FoafsModel
|
||||||
|
suggestedActors: SuggestedActorsModel
|
||||||
|
}) => {
|
||||||
|
if (foafs.isLoading || suggestedActors.isLoading) {
|
||||||
|
return <ProfileCardFeedLoadingPlaceholder />
|
||||||
|
}
|
||||||
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: {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue