From 2759e20e6795cfacf51214b9d5df72c3b1e55c91 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 13 Jun 2024 16:39:20 -0500 Subject: [PATCH] Add FeedCard, use in Feeds screen --- src/components/FeedCard.tsx | 219 ++++++++++++++++++++++++++++++++++++ src/view/screens/Feeds.tsx | 20 ++-- 2 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 src/components/FeedCard.tsx diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx new file mode 100644 index 0000000000..0300a07781 --- /dev/null +++ b/src/components/FeedCard.tsx @@ -0,0 +1,219 @@ +import React from 'react' +import {GestureResponderEvent, View} from 'react-native' +import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' +import {msg, plural, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' +import { + useAddSavedFeedsMutation, + usePreferencesQuery, + useRemoveFeedMutation, +} from '#/state/queries/preferences' +import {sanitizeHandle} from 'lib/strings/handles' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import * as Toast from 'view/com/util/Toast' +import {useTheme} from '#/alf' +import {atoms as a} from '#/alf' +import {Button, ButtonIcon} from '#/components/Button' +import {useRichText} from '#/components/hooks/useRichText' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' +import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' +import {Link as InternalLink} from '#/components/Link' +import {Loader} from '#/components/Loader' +import * as Prompt from '#/components/Prompt' +import {RichText} from '#/components/RichText' +import {Text} from '#/components/Typography' + +export function Default({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { + return ( + + +
+ + + +
+ + +
+ + ) +} + +export function Link({ + children, + feed, +}: { + children: React.ReactElement + feed: AppBskyFeedDefs.GeneratorView +}) { + const href = React.useMemo(() => { + const urip = new AtUri(feed.uri) + const handleOrDid = feed.creator.handle || feed.creator.did + return `/profile/${handleOrDid}/feed/${urip.rkey}` + }, [feed]) + return {children} +} + +export function Outer({children}: {children: React.ReactNode}) { + return {children} +} + +export function Header({children}: {children: React.ReactNode}) { + return {children} +} + +export function Avatar({src}: {src: string | undefined}) { + return +} + +export function TitleAndByline({ + title, + creator, +}: { + title: string + creator: AppBskyActorDefs.ProfileViewBasic +}) { + const t = useTheme() + + return ( + + + {title} + + + Feed by {sanitizeHandle(creator.handle, '@')} + + + ) +} + +export function Description({description}: {description?: string}) { + const t = useTheme() + const [rt, isResolving] = useRichText(description || '') + if (!description) return null + return isResolving ? ( + + + + + ) : ( + + ) +} + +export function Likes({count}: {count: number}) { + const t = useTheme() + return ( + + {plural(count || 0, { + one: 'Liked by # user', + other: 'Liked by # users', + })} + + ) +} + +export function Action({uri, pin}: {uri: string; pin?: boolean}) { + const {_} = useLingui() + const {data: preferences} = usePreferencesQuery() + const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} = + useAddSavedFeedsMutation() + const {isPending: isRemovePending, mutateAsync: removeFeed} = + useRemoveFeedMutation() + const savedFeedConfig = React.useMemo(() => { + return preferences?.savedFeeds?.find( + feed => feed.type === 'feed' && feed.value === uri, + ) + }, [preferences?.savedFeeds, uri]) + const removePromptControl = Prompt.usePromptControl() + const isPending = isAddSavedFeedPending || isRemovePending + + const toggleSave = React.useCallback( + async (e: GestureResponderEvent) => { + e.preventDefault() + e.stopPropagation() + + try { + if (savedFeedConfig) { + await removeFeed(savedFeedConfig) + } else { + await saveFeeds([ + { + type: 'feed', + value: uri, + pinned: pin || false, + }, + ]) + } + Toast.show(_(msg`Feeds updated!`)) + } catch (e: any) { + logger.error(e, {context: `FeedCard: failed to update feeds`, pin}) + Toast.show(_(msg`Failed to update feeds`)) + } + }, + [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig], + ) + + const onPrompRemoveFeed = React.useCallback( + async (e: GestureResponderEvent) => { + e.preventDefault() + e.stopPropagation() + + removePromptControl.open() + }, + [removePromptControl], + ) + + return ( + <> + + + + + ) +} diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 612559455f..e4091d0f78 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -1,6 +1,6 @@ import React from 'react' import {ActivityIndicator, type FlatList, StyleSheet, View} from 'react-native' -import {AppBskyActorDefs} from '@atproto/api' +import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' @@ -25,7 +25,6 @@ import {ComposeIcon2} from 'lib/icons' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {cleanError} from 'lib/strings/errors' import {s} from 'lib/styles' -import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {FAB} from 'view/com/util/fab/FAB' import {SearchInput} from 'view/com/util/forms/SearchInput' @@ -46,6 +45,8 @@ import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/compon import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass' import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle' import hairlineWidth = StyleSheet.hairlineWidth +import {Divider} from '#/components/Divider' +import * as FeedCard from '#/components/FeedCard' type Props = NativeStackScreenProps @@ -94,6 +95,7 @@ type FlatlistSlice = type: 'popularFeed' key: string feedUri: string + feed: AppBskyFeedDefs.GeneratorView } | { type: 'popularFeedsLoadingMore' @@ -300,6 +302,7 @@ export function FeedsScreen(_props: Props) { key: `popularFeed:${feed.uri}`, type: 'popularFeed', feedUri: feed.uri, + feed, })), ) } @@ -323,6 +326,7 @@ export function FeedsScreen(_props: Props) { key: `popularFeed:${feed.uri}`, type: 'popularFeed', feedUri: feed.uri, + feed, })), ) } @@ -476,13 +480,10 @@ export function FeedsScreen(_props: Props) { return } else if (item.type === 'popularFeed') { return ( - + + + + ) } else if (item.type === 'popularFeedsNoResults') { return ( @@ -525,7 +526,6 @@ export function FeedsScreen(_props: Props) { onPressCancelSearch, onSubmitQuery, onChangeSearchFocus, - hasSession, ], )