diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/discover/SuggestedFollows.tsx | 68 | ||||
-rw-r--r-- | src/view/com/search/Suggestions.tsx | 290 |
2 files changed, 240 insertions, 118 deletions
diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx deleted file mode 100644 index ae5605c5c..000000000 --- a/src/view/com/discover/SuggestedFollows.tsx +++ /dev/null @@ -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, - }, -}) diff --git a/src/view/com/search/Suggestions.tsx b/src/view/com/search/Suggestions.tsx index e9999e1d2..c1355bfb5 100644 --- a/src/view/com/search/Suggestions.tsx +++ b/src/view/com/search/Suggestions.tsx @@ -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, }, }) |