Skip to content

Commit

Permalink
Add feedgens tab to profile
Browse files Browse the repository at this point in the history
  • Loading branch information
pfrazee committed Nov 13, 2023
1 parent 06eb8b9 commit 2c48910
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/state/queries/profile-extra-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function useProfileExtraInfoQuery(did: string) {
])
return {
hasLists: listsRes.data.lists.length > 0,
hasFeeds: feedsRes.data.feeds.length > 0,
hasFeedgens: feedsRes.data.feeds.length > 0,
}
},
})
Expand Down
31 changes: 31 additions & 0 deletions src/state/queries/profile-feedgens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {AppBskyFeedGetActorFeeds} from '@atproto/api'
import {useInfiniteQuery, InfiniteData, QueryKey} from '@tanstack/react-query'
import {useSession} from '../session'

const PAGE_SIZE = 30
type RQPageParam = string | undefined

export const RQKEY = (did: string) => ['profile-feedgens', did]

export function useProfileFeedgensQuery(did: string) {
const {agent} = useSession()
return useInfiniteQuery<
AppBskyFeedGetActorFeeds.OutputSchema,
Error,
InfiniteData<AppBskyFeedGetActorFeeds.OutputSchema>,
QueryKey,
RQPageParam
>({
queryKey: RQKEY(did),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
const res = await agent.app.bsky.feed.getActorFeeds({
actor: did,
limit: PAGE_SIZE,
cursor: pageParam,
})
return res.data
},
initialPageParam: undefined,
getNextPageParam: lastPage => lastPage.cursor,
})
}
93 changes: 62 additions & 31 deletions src/view/com/feeds/FeedSourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {RichText} from '../util/text/RichText'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {UserAvatar} from '../util/UserAvatar'
import {observer} from 'mobx-react-lite'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {pluralize} from 'lib/strings/helpers'
Expand All @@ -16,13 +15,14 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
import {
UsePreferencesQueryResponse,
usePreferencesQuery,
useSaveFeedMutation,
useRemoveFeedMutation,
} from '#/state/queries/preferences'
import {useFeedSourceInfoQuery} from '#/state/queries/feed'
import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed'

export const FeedSourceCard = observer(function FeedSourceCardImpl({
export function FeedSourceCard({
feedUri,
style,
showSaveBtn = false,
Expand All @@ -34,31 +34,62 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
showSaveBtn?: boolean
showDescription?: boolean
showLikes?: boolean
}) {
const {data: preferences} = usePreferencesQuery()
const {data: feed} = useFeedSourceInfoQuery({uri: feedUri})

if (!feed || !preferences) return null

return (
<FeedSourceCardLoaded
feed={feed}
preferences={preferences}
style={style}
showSaveBtn={showSaveBtn}
showDescription={showDescription}
showLikes={showLikes}
/>
)
}

export function FeedSourceCardLoaded({
feed,
preferences,
style,
showSaveBtn = false,
showDescription = false,
showLikes = false,
}: {
feed: FeedSourceInfo
preferences: UsePreferencesQueryResponse
style?: StyleProp<ViewStyle>
showSaveBtn?: boolean
showDescription?: boolean
showLikes?: boolean
}) {
const pal = usePalette('default')
const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
const {data: preferences} = usePreferencesQuery()
const {data: info} = useFeedSourceInfoQuery({uri: feedUri})

const {isPending: isSavePending, mutateAsync: saveFeed} =
useSaveFeedMutation()
const {isPending: isRemovePending, mutateAsync: removeFeed} =
useRemoveFeedMutation()

const isSaved = Boolean(preferences?.feeds?.saved?.includes(feedUri))
const isSaved = Boolean(preferences?.feeds?.saved?.includes(feed.uri))

const onToggleSaved = React.useCallback(async () => {
// Only feeds can be un/saved, lists are handled elsewhere
if (info?.type !== 'feed') return
if (feed?.type !== 'feed') return

if (isSaved) {
openModal({
name: 'confirm',
title: 'Remove from my feeds',
message: `Remove ${info?.displayName} from my feeds?`,
message: `Remove ${feed?.displayName} from my feeds?`,
onPressConfirm: async () => {
try {
await removeFeed({uri: feedUri})
await removeFeed({uri: feed.uri})
// await item.unsave()
Toast.show('Removed from my feeds')
} catch (e) {
Expand All @@ -69,51 +100,51 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
})
} else {
try {
await saveFeed({uri: feedUri})
await saveFeed({uri: feed.uri})
Toast.show('Added to my feeds')
} catch (e) {
Toast.show('There was an issue contacting your server')
logger.error('Failed to save feed', {error: e})
}
}
}, [isSaved, openModal, info, feedUri, removeFeed, saveFeed])
}, [isSaved, openModal, feed, removeFeed, saveFeed])

if (!info || !preferences) return null
if (!feed || !preferences) return null

return (
<Pressable
testID={`feed-${info.displayName}`}
testID={`feed-${feed.displayName}`}
accessibilityRole="button"
style={[styles.container, pal.border, style]}
onPress={() => {
if (info.type === 'feed') {
if (feed.type === 'feed') {
navigation.push('ProfileFeed', {
name: info.creatorDid,
rkey: new AtUri(info.uri).rkey,
name: feed.creatorDid,
rkey: new AtUri(feed.uri).rkey,
})
} else if (info.type === 'list') {
} else if (feed.type === 'list') {
navigation.push('ProfileList', {
name: info.creatorDid,
rkey: new AtUri(info.uri).rkey,
name: feed.creatorDid,
rkey: new AtUri(feed.uri).rkey,
})
}
}}
key={info.uri}>
key={feed.uri}>
<View style={[styles.headerContainer]}>
<View style={[s.mr10]}>
<UserAvatar type="algo" size={36} avatar={info.avatar} />
<UserAvatar type="algo" size={36} avatar={feed.avatar} />
</View>
<View style={[styles.headerTextContainer]}>
<Text style={[pal.text, s.bold]} numberOfLines={3}>
{info.displayName}
{feed.displayName}
</Text>
<Text style={[pal.textLight]} numberOfLines={3}>
{info.type === 'feed' ? 'Feed' : 'List'} by{' '}
{sanitizeHandle(info.creatorHandle, '@')}
{feed.type === 'feed' ? 'Feed' : 'List'} by{' '}
{sanitizeHandle(feed.creatorHandle, '@')}
</Text>
</View>

{showSaveBtn && info.type === 'feed' && (
{showSaveBtn && feed.type === 'feed' && (
<View>
<Pressable
disabled={isSavePending || isRemovePending}
Expand Down Expand Up @@ -143,23 +174,23 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
)}
</View>

{showDescription && info.description ? (
{showDescription && feed.description ? (
<RichText
style={[pal.textLight, styles.description]}
richText={info.description}
richText={feed.description}
numberOfLines={3}
/>
) : null}

{showLikes && info.type === 'feed' ? (
{showLikes && feed.type === 'feed' ? (
<Text type="sm-medium" style={[pal.text, pal.textLight]}>
Liked by {info.likeCount || 0}{' '}
{pluralize(info.likeCount || 0, 'user')}
Liked by {feed.likeCount || 0}{' '}
{pluralize(feed.likeCount || 0, 'user')}
</Text>
) : null}
</Pressable>
)
})
}

const styles = StyleSheet.create({
container: {
Expand Down
Loading

0 comments on commit 2c48910

Please sign in to comment.