diff options
-rw-r--r-- | src/components/moderation/PostHider.tsx | 23 | ||||
-rw-r--r-- | src/screens/Messages/List/index.tsx | 7 | ||||
-rw-r--r-- | src/state/queries/post-feed.ts | 4 | ||||
-rw-r--r-- | src/state/queries/post-thread.ts | 5 | ||||
-rw-r--r-- | src/state/queries/profile.ts | 54 | ||||
-rw-r--r-- | src/view/com/composer/ComposerReplyTo.tsx | 4 | ||||
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 70 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 11 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 10 | ||||
-rw-r--r-- | src/view/com/posts/FeedItem.tsx | 14 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 15 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeaderSuggestedFollows.tsx | 3 | ||||
-rw-r--r-- | src/view/com/util/Link.tsx | 7 | ||||
-rw-r--r-- | src/view/com/util/PostMeta.tsx | 17 | ||||
-rw-r--r-- | src/view/com/util/TimeElapsed.tsx | 5 | ||||
-rw-r--r-- | src/view/com/util/UserAvatar.tsx | 32 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/QuoteEmbed.tsx | 6 |
17 files changed, 119 insertions, 168 deletions
diff --git a/src/components/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx index 464ee2077..05cb8464e 100644 --- a/src/components/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -1,25 +1,27 @@ import React, {ComponentProps} from 'react' -import {StyleSheet, Pressable, View, ViewStyle, StyleProp} from 'react-native' -import {ModerationUI} from '@atproto/api' +import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import {AppBskyActorDefs, ModerationUI} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {Trans, msg} from '@lingui/macro' +import {useQueryClient} from '@tanstack/react-query' import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {addStyle} from 'lib/styles' - -import {useTheme, atoms as a} from '#/alf' +import {precacheProfile} from 'state/queries/profile' +// import {Link} from '#/components/Link' TODO this imposes some styles that screw things up +import {Link} from '#/view/com/util/Link' +import {atoms as a, useTheme} from '#/alf' import { ModerationDetailsDialog, useModerationDetailsDialogControl, } from '#/components/moderation/ModerationDetailsDialog' import {Text} from '#/components/Typography' -// import {Link} from '#/components/Link' TODO this imposes some styles that screw things up -import {Link} from '#/view/com/util/Link' interface Props extends ComponentProps<typeof Link> { iconSize: number iconStyles: StyleProp<ViewStyle> modui: ModerationUI + profile: AppBskyActorDefs.ProfileViewBasic } export function PostHider({ @@ -30,8 +32,10 @@ export function PostHider({ children, iconSize, iconStyles, + profile, ...props }: Props) { + const queryClient = useQueryClient() const t = useTheme() const {_} = useLingui() const [override, setOverride] = React.useState(false) @@ -39,6 +43,10 @@ export function PostHider({ const blur = modui.blurs[0] const desc = useModerationCauseDescription(blur) + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, profile) + }, [queryClient, profile]) + if (!blur) { return ( <Link @@ -46,6 +54,7 @@ export function PostHider({ style={style} href={href} accessible={false} + onBeforePress={onBeforePress} {...props}> {children} </Link> diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index 56dfb76c2..c4490aa5c 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -113,12 +113,7 @@ export function MessagesListScreen({}: Props) { <Link to={`/messages/${item.profile.handle}`} style={[a.flex_1, a.pl_md, a.py_sm, a.gap_md, a.pr_2xl]}> - <PreviewableUserAvatar - did={item.profile.did} - handle={item.profile.handle} - size={44} - avatar={item.profile.avatar} - /> + <PreviewableUserAvatar profile={item.profile} size={44} /> <View style={[a.flex_1]}> <View style={[ diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 42d7dbb9e..747dba02e 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -12,7 +12,6 @@ import { QueryClient, QueryKey, useInfiniteQuery, - useQueryClient, } from '@tanstack/react-query' import {HomeFeedAPI} from '#/lib/api/feed/home' @@ -33,7 +32,6 @@ import {BSKY_FEED_OWNER_DIDS} from 'lib/constants' import {KnownError} from '#/view/com/posts/FeedErrorMessage' import {useFeedTuners} from '../preferences/feed-tuners' import {useModerationOpts} from './preferences' -import {precacheFeedPostProfiles} from './profile' import {embedViewRecordToPostView, getEmbeddedPost} from './util' type ActorDid = string @@ -102,7 +100,6 @@ export function usePostFeedQuery( params?: FeedParams, opts?: {enabled?: boolean; ignoreFilterFor?: string}, ) { - const queryClient = useQueryClient() const feedTuners = useFeedTuners(feedDesc) const moderationOpts = useModerationOpts() const {getAgent} = useAgent() @@ -151,7 +148,6 @@ export function usePostFeedQuery( try { const res = await api.fetch({cursor, limit: PAGE_SIZE}) - precacheFeedPostProfiles(queryClient, res.feed) /* * If this is a public view, we need to check if posts fail moderation. diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts index 46c7445ba..133304d2e 100644 --- a/src/state/queries/post-thread.ts +++ b/src/state/queries/post-thread.ts @@ -11,7 +11,6 @@ import {useAgent} from '#/state/session' import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from 'state/queries/search-posts' import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from './notifications/feed' import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from './post-feed' -import {precacheThreadPostProfiles} from './profile' import {embedViewRecordToPostView, getEmbeddedPost} from './util' const RQKEY_ROOT = 'post-thread' @@ -73,9 +72,7 @@ export function usePostThreadQuery(uri: string | undefined) { async queryFn() { const res = await getAgent().getPostThread({uri: uri!}) if (res.success) { - const nodes = responseToThreadNodes(res.data.thread) - precacheThreadPostProfiles(queryClient, nodes) - return nodes + return responseToThreadNodes(res.data.thread) } return {type: 'unknown', uri: uri!} }, diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 6c801426e..103d34733 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -4,9 +4,6 @@ import { AppBskyActorDefs, AppBskyActorGetProfile, AppBskyActorProfile, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedDefs, AtUri, BskyAgent, } from '@atproto/api' @@ -29,7 +26,6 @@ import {updateProfileShadow} from '../cache/profile-shadow' import {useAgent, useSession} from '../session' import {RQKEY as RQKEY_MY_BLOCKED} from './my-blocked-accounts' import {RQKEY as RQKEY_MY_MUTED} from './my-muted-accounts' -import {ThreadNode} from './post-thread' const RQKEY_ROOT = 'profile' export const RQKEY = (did: string) => [RQKEY_ROOT, did] @@ -477,56 +473,6 @@ export function precacheProfile( queryClient.setQueryData(profileBasicQueryKey(profile.did), profile) } -export function precacheFeedPostProfiles( - queryClient: QueryClient, - posts: AppBskyFeedDefs.FeedViewPost[], -) { - for (const post of posts) { - // Save the author of the post every time - precacheProfile(queryClient, post.post.author) - precachePostEmbedProfile(queryClient, post.post.embed) - - // Cache parent author and embeds - const parent = post.reply?.parent - if (AppBskyFeedDefs.isPostView(parent)) { - precacheProfile(queryClient, parent.author) - precachePostEmbedProfile(queryClient, parent.embed) - } - } -} - -function precachePostEmbedProfile( - queryClient: QueryClient, - embed: AppBskyFeedDefs.PostView['embed'], -) { - if (AppBskyEmbedRecord.isView(embed)) { - if (AppBskyEmbedRecord.isViewRecord(embed.record)) { - precacheProfile(queryClient, embed.record.author) - } - } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { - if (AppBskyEmbedRecord.isViewRecord(embed.record.record)) { - precacheProfile(queryClient, embed.record.record.author) - } - } -} - -export function precacheThreadPostProfiles( - queryClient: QueryClient, - node: ThreadNode, -) { - if (node.type === 'post') { - precacheProfile(queryClient, node.post.author) - if (node.parent) { - precacheThreadPostProfiles(queryClient, node.parent) - } - if (node.replies?.length) { - for (const reply of node.replies) { - precacheThreadPostProfiles(queryClient, reply) - } - } - } -} - async function whenAppViewReady( getAgent: () => BskyAgent, actor: string, diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 24a2373f5..7dc17fd4a 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -86,9 +86,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { )}> <PreviewableUserAvatar size={50} - did={replyTo.author.did} - handle={replyTo.author.handle} - avatar={replyTo.author.avatar} + profile={replyTo.author} moderation={replyTo.moderation?.ui('avatar')} type={replyTo.author.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 3c9c64061..94844cb1a 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -24,6 +24,7 @@ import { } from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {FeedNotification} from '#/state/queries/notifications/feed' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' @@ -36,6 +37,7 @@ import {pluralize} from 'lib/strings/helpers' import {niceDate} from 'lib/strings/time' import {colors, s} from 'lib/styles' import {isWeb} from 'platform/detection' +import {precacheProfile} from 'state/queries/profile' import {Link as NewLink} from '#/components/Link' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {FeedSourceCard} from '../feeds/FeedSourceCard' @@ -52,13 +54,9 @@ const MAX_AUTHORS = 5 const EXPANDED_AUTHOR_EL_HEIGHT = 35 interface Author { + profile: AppBskyActorDefs.ProfileViewBasic href: string - did: string - handle: string - displayName?: string - avatar?: string moderation: ModerationDecision - associated?: AppBskyActorDefs.ProfileAssociated } let FeedItem = ({ @@ -68,6 +66,7 @@ let FeedItem = ({ item: FeedNotification moderationOpts: ModerationOpts }): React.ReactNode => { + const queryClient = useQueryClient() const pal = usePalette('default') const {_} = useLingui() const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false) @@ -95,28 +94,22 @@ let FeedItem = ({ setAuthorsExpanded(currentlyExpanded => !currentlyExpanded) } + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, item.notification.author) + }, [queryClient, item.notification.author]) + const authors: Author[] = useMemo(() => { return [ { + profile: item.notification.author, href: makeProfileLink(item.notification.author), - did: item.notification.author.did, - handle: item.notification.author.handle, - displayName: item.notification.author.displayName, - avatar: item.notification.author.avatar, moderation: moderateProfile(item.notification.author, moderationOpts), - associated: item.notification.author.associated, }, - ...(item.additional?.map(({author}) => { - return { - href: makeProfileLink(author), - did: author.did, - handle: author.handle, - displayName: author.displayName, - avatar: author.avatar, - moderation: moderateProfile(author, moderationOpts), - associated: author.associated, - } - }) || []), + ...(item.additional?.map(({author}) => ({ + profile: author, + href: makeProfileLink(author), + moderation: moderateProfile(author, moderationOpts), + })) || []), ] }, [item, moderationOpts]) @@ -201,7 +194,8 @@ let FeedItem = ({ accessible={ (item.type === 'post-like' && authors.length === 1) || item.type === 'repost' - }> + } + onBeforePress={onBeforePress}> <View style={styles.layoutIcon}> {/* TODO: Prevent conditional rendering and move toward composable notifications for clearer accessibility labeling */} @@ -231,7 +225,7 @@ let FeedItem = ({ style={[pal.text, s.bold]} href={authors[0].href} text={sanitizeDisplayName( - authors[0].displayName || authors[0].handle, + authors[0].profile.displayName || authors[0].profile.handle, )} disableMismatchWarning /> @@ -339,11 +333,9 @@ function CondensedAuthorsList({ <View style={styles.avis}> <PreviewableUserAvatar size={35} - did={authors[0].did} - handle={authors[0].handle} - avatar={authors[0].avatar} + profile={authors[0].profile} moderation={authors[0].moderation.ui('avatar')} - type={authors[0].associated?.labeler ? 'labeler' : 'user'} + type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'} /> </View> ) @@ -360,11 +352,9 @@ function CondensedAuthorsList({ <View key={author.href} style={s.mr5}> <PreviewableUserAvatar size={35} - did={author.did} - handle={author.handle} - avatar={author.avatar} + profile={author.profile} moderation={author.moderation.ui('avatar')} - type={author.associated?.labeler ? 'labeler' : 'user'} + type={author.profile.associated?.labeler ? 'labeler' : 'user'} /> </View> ))} @@ -415,20 +405,20 @@ function ExpandedAuthorsList({ ]}> {authors.map(author => ( <NewLink - key={author.did} + key={author.profile.did} label={_(msg`See profile`)} to={makeProfileLink({ - did: author.did, - handle: author.handle, + did: author.profile.did, + handle: author.profile.handle, })} style={styles.expandedAuthor}> <View style={styles.expandedAuthorAvi}> - <ProfileHoverCard did={author.did}> + <ProfileHoverCard did={author.profile.did}> <UserAvatar size={35} - avatar={author.avatar} + avatar={author.profile.avatar} moderation={author.moderation.ui('avatar')} - type={author.associated?.labeler ? 'labeler' : 'user'} + type={author.profile.associated?.labeler ? 'labeler' : 'user'} /> </ProfileHoverCard> </View> @@ -438,10 +428,12 @@ function ExpandedAuthorsList({ numberOfLines={1} style={pal.text} lineHeight={1.2}> - {sanitizeDisplayName(author.displayName || author.handle)} + {sanitizeDisplayName( + author.profile.displayName || author.profile.handle, + )} <Text style={[pal.textLight]} lineHeight={1.2}> - {sanitizeHandle(author.handle)} + {sanitizeHandle(author.profile.handle)} </Text> </Text> </View> diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 4c11fdff3..564e37e7a 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -249,9 +249,7 @@ let PostThreadItemLoaded = ({ <View style={[styles.layoutAvi, {paddingBottom: 8}]}> <PreviewableUserAvatar size={42} - did={post.author.did} - handle={post.author.handle} - avatar={post.author.avatar} + profile={post.author} moderation={moderation.ui('avatar')} type={post.author.associated?.labeler ? 'labeler' : 'user'} /> @@ -399,7 +397,8 @@ let PostThreadItemLoaded = ({ isThreadedChild ? {marginRight: 4} : {marginLeft: 2, marginRight: 2} - }> + } + profile={post.author}> <View style={{ flexDirection: 'row', @@ -440,9 +439,7 @@ let PostThreadItemLoaded = ({ <View style={styles.layoutAvi}> <PreviewableUserAvatar size={38} - did={post.author.did} - handle={post.author.handle} - avatar={post.author.avatar} + profile={post.author} moderation={moderation.ui('avatar')} type={post.author.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index b46586941..546eb2821 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -21,7 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette' import {makeProfileLink} from 'lib/routes/links' import {countLines} from 'lib/strings/helpers' import {colors, s} from 'lib/styles' -import {RQKEY as RQKEY_URI} from 'state/queries/resolve-uri' +import {precacheProfile} from 'state/queries/profile' import {atoms as a} from '#/alf' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichText} from '#/components/RichText' @@ -135,8 +135,8 @@ function PostInner({ }, [setLimitLines]) const onBeforePress = React.useCallback(() => { - queryClient.setQueryData(RQKEY_URI(post.author.handle), post.author.did) - }, [queryClient, post.author.handle, post.author.did]) + precacheProfile(queryClient, post.author) + }, [queryClient, post.author]) return ( <Link @@ -148,9 +148,7 @@ function PostInner({ <View style={styles.layoutAvi}> <PreviewableUserAvatar size={52} - did={post.author.did} - handle={post.author.handle} - avatar={post.author.avatar} + profile={post.author} moderation={moderation.ui('avatar')} type={post.author.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 7694b5024..605dffde9 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -13,6 +13,7 @@ import { } from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' import {useComposerControls} from '#/state/shell/composer' @@ -24,6 +25,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {countLines} from 'lib/strings/helpers' import {s} from 'lib/styles' +import {precacheProfile} from 'state/queries/profile' import {atoms as a} from '#/alf' import {ContentHider} from '#/components/moderation/ContentHider' import {ProfileHoverCard} from '#/components/ProfileHoverCard' @@ -106,6 +108,7 @@ let FeedItemInner = ({ isThreadLastChild?: boolean isThreadParent?: boolean }): React.ReactNode => { + const queryClient = useQueryClient() const {openComposer} = useComposerControls() const pal = usePalette('default') const {_} = useLingui() @@ -135,6 +138,10 @@ let FeedItemInner = ({ }) }, [post, record, openComposer, moderation]) + const onBeforePress = React.useCallback(() => { + precacheProfile(queryClient, post.author) + }, [queryClient, post.author]) + const outerStyles = [ styles.outer, { @@ -153,7 +160,8 @@ let FeedItemInner = ({ style={outerStyles} href={href} noFeedback - accessible={false}> + accessible={false} + onBeforePress={onBeforePress}> <View style={{flexDirection: 'row', gap: 10, paddingLeft: 8}}> <View style={{width: 52}}> {isThreadChild && ( @@ -240,9 +248,7 @@ let FeedItemInner = ({ <View style={styles.layoutAvi}> <PreviewableUserAvatar size={52} - did={post.author.did} - handle={post.author.handle} - avatar={post.author.avatar} + profile={post.author} moderation={moderation.ui('avatar')} type={post.author.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index b52573a01..90ab9b738 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -20,8 +20,7 @@ import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {s} from 'lib/styles' -import {profileBasicQueryKey as RQKEY_PROFILE_BASIC} from 'state/queries/profile' -import {RQKEY as RQKEY_URI} from 'state/queries/resolve-uri' +import {precacheProfile} from 'state/queries/profile' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {PreviewableUserAvatar} from '../util/UserAvatar' @@ -58,9 +57,7 @@ export function ProfileCard({ const onBeforePress = React.useCallback(() => { onPress?.() - - queryClient.setQueryData(RQKEY_URI(profile.handle), profile.did) - queryClient.setQueryData(RQKEY_PROFILE_BASIC(profile.did), profile) + precacheProfile(queryClient, profile) }, [onPress, profile, queryClient]) if (!moderationOpts) { @@ -91,9 +88,7 @@ export function ProfileCard({ <View style={styles.layoutAvi}> <PreviewableUserAvatar size={40} - did={profile.did} - handle={profile.handle} - avatar={profile.avatar} + profile={profile} moderation={moderation.ui('avatar')} type={isLabeler ? 'labeler' : 'user'} /> @@ -238,9 +233,7 @@ function FollowersList({ <View style={[styles.followedByAvi, pal.view]}> <PreviewableUserAvatar size={32} - did={f.did} - handle={f.handle} - avatar={f.avatar} + profile={f} moderation={mod.ui('avatar')} type={f.associated?.labeler ? 'labeler' : 'user'} /> diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index cf35885cd..4c9d164f7 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -220,8 +220,7 @@ function SuggestedFollow({ ]}> <PreviewableUserAvatar size={60} - did={profile.did} - handle={profile.handle} + profile={profile} avatar={profile.avatar} moderation={moderation.ui('avatar')} /> diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index d35d0fcc6..78d995ee8 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -148,6 +148,7 @@ export const TextLink = memo(function TextLink({ dataSet, title, onPress, + onBeforePress, disableMismatchWarning, navigationAction, anchorNoUnderline, @@ -165,6 +166,7 @@ export const TextLink = memo(function TextLink({ disableMismatchWarning?: boolean navigationAction?: 'push' | 'replace' | 'navigate' anchorNoUnderline?: boolean + onBeforePress?: () => void } & TextProps) { const {...props} = useLinkProps({to: sanitizeUrl(href)}) const navigation = useNavigationDeduped() @@ -202,6 +204,7 @@ export const TextLink = memo(function TextLink({ // Let the browser handle opening in new tab etc. return } + onBeforePress?.() if (onPress) { e?.preventDefault?.() // @ts-ignore function signature differs by platform -prf @@ -226,6 +229,7 @@ export const TextLink = memo(function TextLink({ disableMismatchWarning, navigationAction, openLink, + onBeforePress, ], ) const hrefAttrs = useMemo(() => { @@ -274,6 +278,7 @@ interface TextLinkOnWebOnlyProps extends TextProps { title?: string navigationAction?: 'push' | 'replace' | 'navigate' disableMismatchWarning?: boolean + onBeforePress?: () => void onPointerEnter?: () => void anchorNoUnderline?: boolean } @@ -287,6 +292,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ lineHeight, navigationAction, disableMismatchWarning, + onBeforePress, ...props }: TextLinkOnWebOnlyProps) { if (isWeb) { @@ -302,6 +308,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({ title={props.title} navigationAction={navigationAction} disableMismatchWarning={disableMismatchWarning} + onBeforePress={onBeforePress} {...props} /> ) diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index ed3d3e5b0..db16ff066 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,8 +1,9 @@ -import React, {memo} from 'react' +import React, {memo, useCallback} from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api' +import {useQueryClient} from '@tanstack/react-query' -import {usePrefetchProfileQuery} from '#/state/queries/profile' +import {precacheProfile, usePrefetchProfileQuery} from '#/state/queries/profile' import {usePalette} from 'lib/hooks/usePalette' import {makeProfileLink} from 'lib/routes/links' import {sanitizeDisplayName} from 'lib/strings/display-names' @@ -40,15 +41,18 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { ? () => prefetchProfileQuery(opts.author.did) : undefined + const queryClient = useQueryClient() + const onBeforePress = useCallback(() => { + precacheProfile(queryClient, opts.author) + }, [queryClient, opts.author]) + return ( <View style={[styles.container, opts.style]}> {opts.showAvatar && ( <View style={styles.avatar}> <PreviewableUserAvatar size={opts.avatarSize || 16} - did={opts.author.did} - handle={opts.author.handle} - avatar={opts.author.avatar} + profile={opts.author} moderation={opts.avatarModeration} type={opts.author.associated?.labeler ? 'labeler' : 'user'} /> @@ -71,6 +75,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { </> } href={profileLink} + onBeforePress={onBeforePress} onPointerEnter={onPointerEnter} /> <TextLinkOnWebOnly @@ -79,6 +84,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { style={[pal.textLight, {flexShrink: 4}]} text={'\xa0' + sanitizeHandle(handle, '@')} href={profileLink} + onBeforePress={onBeforePress} onPointerEnter={onPointerEnter} anchorNoUnderline /> @@ -103,6 +109,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { title={niceDate(opts.timestamp)} accessibilityHint="" href={opts.postHref} + onBeforePress={onBeforePress} /> )} </TimeElapsed> diff --git a/src/view/com/util/TimeElapsed.tsx b/src/view/com/util/TimeElapsed.tsx index aa3a09223..6ea41b82b 100644 --- a/src/view/com/util/TimeElapsed.tsx +++ b/src/view/com/util/TimeElapsed.tsx @@ -1,6 +1,7 @@ import React from 'react' -import {ago} from 'lib/strings/time' + import {useTickEveryMinute} from '#/state/shell' +import {ago} from 'lib/strings/time' // FIXME(dan): Figure out why the false positives @@ -12,7 +13,7 @@ export function TimeElapsed({ children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element }) { const tick = useTickEveryMinute() - const [timeElapsed, setTimeAgo] = React.useState(ago(timestamp)) + const [timeElapsed, setTimeAgo] = React.useState(() => ago(timestamp)) React.useEffect(() => { setTimeAgo(ago(timestamp)) diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 89aa56b73..118e2ce2b 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -2,10 +2,11 @@ import React, {memo, useMemo} from 'react' import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' import {Image as RNImage} from 'react-native-image-crop-picker' import Svg, {Circle, Path, Rect} from 'react-native-svg' -import {ModerationUI} from '@atproto/api' +import {AppBskyActorDefs, ModerationUI} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {usePalette} from 'lib/hooks/usePalette' import { @@ -15,6 +16,7 @@ import { import {makeProfileLink} from 'lib/routes/links' import {colors} from 'lib/styles' import {isAndroid, isNative, isWeb} from 'platform/detection' +import {precacheProfile} from 'state/queries/profile' import {HighPriorityImage} from 'view/com/util/images/Image' import {tokens, useTheme} from '#/alf' import { @@ -47,8 +49,7 @@ interface EditableUserAvatarProps extends BaseUserAvatarProps { interface PreviewableUserAvatarProps extends BaseUserAvatarProps { moderation?: ModerationUI - did: string - handle: string + profile: AppBskyActorDefs.ProfileViewBasic } const BLUR_AMOUNT = isWeb ? 5 : 100 @@ -371,19 +372,28 @@ let EditableUserAvatar = ({ EditableUserAvatar = memo(EditableUserAvatar) export {EditableUserAvatar} -let PreviewableUserAvatar = ( - props: PreviewableUserAvatarProps, -): React.ReactNode => { +let PreviewableUserAvatar = ({ + moderation, + profile, + ...rest +}: PreviewableUserAvatarProps): React.ReactNode => { const {_} = useLingui() + const queryClient = useQueryClient() + + const onPress = React.useCallback(() => { + precacheProfile(queryClient, profile) + }, [profile, queryClient]) + return ( - <ProfileHoverCard did={props.did}> + <ProfileHoverCard did={profile.did}> <Link label={_(msg`See profile`)} to={makeProfileLink({ - did: props.did, - handle: props.handle, - })}> - <UserAvatar {...props} /> + did: profile.did, + handle: profile.handle, + })} + onPress={onPress}> + <UserAvatar avatar={profile.avatar} moderation={moderation} {...rest} /> </Link> </ProfileHoverCard> ) diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 935696ab7..e0178f34b 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -26,10 +26,10 @@ import {useQueryClient} from '@tanstack/react-query' import {HITSLOP_20} from '#/lib/constants' import {s} from '#/lib/styles' import {useModerationOpts} from '#/state/queries/preferences' -import {RQKEY as RQKEY_URI} from '#/state/queries/resolve-uri' import {usePalette} from 'lib/hooks/usePalette' import {InfoCircleIcon} from 'lib/icons' import {makeProfileLink} from 'lib/routes/links' +import {precacheProfile} from 'state/queries/profile' import {ComposerOptsQuote} from 'state/shell/composer' import {atoms as a} from '#/alf' import {RichText} from '#/components/RichText' @@ -149,8 +149,8 @@ export function QuoteEmbed({ }, [quote.embeds]) const onBeforePress = React.useCallback(() => { - queryClient.setQueryData(RQKEY_URI(quote.author.handle), quote.author.did) - }, [queryClient, quote.author.did, quote.author.handle]) + precacheProfile(queryClient, quote.author) + }, [queryClient, quote.author]) return ( <ContentHider modui={moderation?.ui('contentList')}> |