From 07b028ee668afee13d878c8ff4c579276fd69f6c Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 9 Jul 2025 10:14:30 +0300 Subject: Fix quote+list card padding (#8623) * fix quote padding not being pressable * fix list padding not being pressable * Fix unnecessary loading of feeds (#8578) * stop layout shifts in feed loading * don't load feed data if we already have it * adjust styles, alf stuff * remove unused button, massively simplify * fix layout shifting in notifs * use feedcard for feed post embeds * use bold text to match other style * use Link component rather than jank Pressable * prevent nested anchors in notifs * match following text size * add space between content hider * Better dead feed handling (#8579) * add space between content hider * add handling for feeds that fail to load * cleanError, in case of network funkiness * handle deleted lists * split out missingfeed --- src/view/com/feeds/FeedSourceCard.tsx | 451 ++++++++++++---------------------- 1 file changed, 158 insertions(+), 293 deletions(-) (limited to 'src/view/com/feeds/FeedSourceCard.tsx') diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 3a658755a..18e2807a8 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -1,50 +1,33 @@ -import React from 'react' +import {type StyleProp, View, type ViewStyle} from 'react-native' import { - Linking, - Pressable, - StyleProp, - StyleSheet, - View, - ViewStyle, -} from 'react-native' -import {AtUri} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' + type $Typed, + AppBskyFeedDefs, + type AppBskyGraphDefs, + AtUri, +} from '@atproto/api' import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped' -import {usePalette} from '#/lib/hooks/usePalette' import {sanitizeHandle} from '#/lib/strings/handles' -import {s} from '#/lib/styles' -import {logger} from '#/logger' -import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed' import { - useAddSavedFeedsMutation, - usePreferencesQuery, - UsePreferencesQueryResponse, - useRemoveFeedMutation, -} from '#/state/queries/preferences' + type FeedSourceInfo, + hydrateFeedGenerator, + hydrateList, + useFeedSourceInfoQuery, +} from '#/state/queries/feed' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' -import * as Toast from '#/view/com/util/Toast' -import {useTheme} from '#/alf' -import {atoms as a} from '#/alf' -import {shouldClickOpenNewTab} from '#/components/Link' -import * as Prompt from '#/components/Prompt' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, useTheme} from '#/alf' +import {Link} from '#/components/Link' import {RichText} from '#/components/RichText' -import {Text} from '../util/text/Text' -import {UserAvatar} from '../util/UserAvatar' +import {Text} from '#/components/Typography' +import {MissingFeed} from './MissingFeed' -export function FeedSourceCard({ - feedUri, - style, - showSaveBtn = false, - showDescription = false, - showLikes = false, - pinOnSave = false, - showMinimalPlaceholder, - hideTopBorder, -}: { +type FeedSourceCardProps = { feedUri: string + feedData?: + | $Typed + | $Typed style?: StyleProp showSaveBtn?: boolean showDescription?: boolean @@ -52,22 +35,41 @@ export function FeedSourceCard({ pinOnSave?: boolean showMinimalPlaceholder?: boolean hideTopBorder?: boolean -}) { - const {data: preferences} = usePreferencesQuery() - const {data: feed} = useFeedSourceInfoQuery({uri: feedUri}) + link?: boolean +} + +export function FeedSourceCard({ + feedUri, + feedData, + ...props +}: FeedSourceCardProps) { + if (feedData) { + let feed: FeedSourceInfo + if (AppBskyFeedDefs.isGeneratorView(feedData)) { + feed = hydrateFeedGenerator(feedData) + } else { + feed = hydrateList(feedData) + } + return + } else { + return + } +} + +export function FeedSourceCardWithoutData({ + feedUri, + ...props +}: Omit) { + const {data: feed, error} = useFeedSourceInfoQuery({ + uri: feedUri, + }) return ( ) } @@ -75,80 +77,26 @@ export function FeedSourceCard({ export function FeedSourceCardLoaded({ feedUri, feed, - preferences, style, - showSaveBtn = false, showDescription = false, showLikes = false, - pinOnSave = false, showMinimalPlaceholder, hideTopBorder, + link = true, + error, }: { feedUri: string feed?: FeedSourceInfo - preferences?: UsePreferencesQueryResponse style?: StyleProp - showSaveBtn?: boolean showDescription?: boolean showLikes?: boolean - pinOnSave?: boolean showMinimalPlaceholder?: boolean hideTopBorder?: boolean + link?: boolean + error?: unknown }) { const t = useTheme() - const pal = usePalette('default') const {_} = useLingui() - const removePromptControl = Prompt.usePromptControl() - const navigation = useNavigationDeduped() - - const {isPending: isAddSavedFeedPending, mutateAsync: addSavedFeeds} = - useAddSavedFeedsMutation() - const {isPending: isRemovePending, mutateAsync: removeFeed} = - useRemoveFeedMutation() - - const savedFeedConfig = preferences?.savedFeeds?.find( - f => f.value === feedUri, - ) - const isSaved = Boolean(savedFeedConfig) - - const onSave = React.useCallback(async () => { - if (!feed || isSaved) return - - try { - await addSavedFeeds([ - { - type: 'feed', - value: feed.uri, - pinned: pinOnSave, - }, - ]) - Toast.show(_(msg`Added to my feeds`)) - } catch (e) { - Toast.show(_(msg`There was an issue contacting your server`), 'xmark') - logger.error('Failed to save feed', {message: e}) - } - }, [_, feed, pinOnSave, addSavedFeeds, isSaved]) - - const onUnsave = React.useCallback(async () => { - if (!savedFeedConfig) return - - try { - await removeFeed(savedFeedConfig) - // await item.unsave() - Toast.show(_(msg`Removed from my feeds`)) - } catch (e) { - Toast.show(_(msg`There was an issue contacting your server`), 'xmark') - logger.error('Failed to unsave feed', {message: e}) - } - }, [_, removeFeed, savedFeedConfig]) - - const onToggleSaved = React.useCallback(async () => { - if (isSaved) { - removePromptControl.open() - } else { - await onSave() - } - }, [isSaved, removePromptControl, onSave]) /* * LOAD STATE @@ -156,200 +104,117 @@ export function FeedSourceCardLoaded({ * This state also captures the scenario where a feed can't load for whatever * reason. */ - if (!feed || !preferences) - return ( - - {showMinimalPlaceholder ? ( - - ) : ( - - )} - - {showSaveBtn && ( - - - - )} - - ) + if (!feed) { + if (error) { + return ( + + ) + } - return ( - <> - { - const shouldOpenInNewTab = shouldClickOpenNewTab(e) - if (feed.type === 'feed') { - if (shouldOpenInNewTab) { - Linking.openURL( - `/profile/${feed.creatorDid}/feed/${new AtUri(feed.uri).rkey}`, - ) - } else { - navigation.push('ProfileFeed', { - name: feed.creatorDid, - rkey: new AtUri(feed.uri).rkey, - }) - } - } else if (feed.type === 'list') { - if (shouldOpenInNewTab) { - Linking.openURL( - `/profile/${feed.creatorDid}/lists/${new AtUri(feed.uri).rkey}`, - ) - } else { - navigation.push('ProfileList', { - name: feed.creatorDid, - rkey: new AtUri(feed.uri).rkey, - }) - } - } - }} - key={feed.uri}> - - - - - - - {feed.displayName} - - - {feed.type === 'feed' ? ( - Feed by {sanitizeHandle(feed.creatorHandle, '@')} - ) : ( - List by {sanitizeHandle(feed.creatorHandle, '@')} - )} - - + showTopBorder={false} + showLowerPlaceholder={!showMinimalPlaceholder} + /> + ) + } - {showSaveBtn && ( - - - {isSaved ? ( - - ) : ( - - )} - - - )} + const inner = ( + <> + + + - - {showDescription && feed.description ? ( - - ) : null} - - {showLikes && feed.type === 'feed' ? ( - - - Liked by{' '} - - + + + {feed.displayName} - ) : null} - - - + + {feed.type === 'feed' ? ( + Feed by {sanitizeHandle(feed.creatorHandle, '@')} + ) : ( + List by {sanitizeHandle(feed.creatorHandle, '@')} + )} + + + + {showDescription && feed.description ? ( + + ) : null} + {showLikes && feed.type === 'feed' ? ( + + + Liked by{' '} + + + + ) : null} ) -} -const styles = StyleSheet.create({ - container: { - paddingHorizontal: 18, - paddingVertical: 20, - flexDirection: 'column', - flex: 1, - gap: 14, - }, - border: { - borderTopWidth: StyleSheet.hairlineWidth, - }, - headerContainer: { - flexDirection: 'row', - }, - headerTextContainer: { - flexDirection: 'column', - columnGap: 4, - flex: 1, - }, - description: { - flex: 1, - flexWrap: 'wrap', - }, - btn: { - paddingVertical: 6, - }, -}) + if (link) { + return ( + + {inner} + + ) + } else { + return ( + + {inner} + + ) + } +} -- cgit 1.4.1