remove precacheThreadPostProfiles (#3729)

* remove `precacheThreadPostProfiles`

* add `displayName` to `PreviewableUserAvatar`

* memo

* use `precacheProfile`

* pass `profile` directly to `PreviewableUserAvatar`

* update the `UserAvatar`'s props

* remove feed cache

* one more spot

* rm unused queryClient

* Don't call fn unnecessarily

* Preload for display name too

* try notification item

* add to feeditem

* and finally, precache for post threads

* timestamp

* Fix

* onBeforePress

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
zio/stable
Hailey 2024-04-26 22:31:07 -07:00 committed by GitHub
parent ce85375c85
commit 7eb1444f2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 119 additions and 168 deletions

View File

@ -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>

View File

@ -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={[

View File

@ -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.

View File

@ -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!}
},

View File

@ -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,

View File

@ -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'}
/>

View File

@ -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,
)}
&nbsp;
<Text style={[pal.textLight]} lineHeight={1.2}>
{sanitizeHandle(author.handle)}
{sanitizeHandle(author.profile.handle)}
</Text>
</Text>
</View>

View File

@ -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'}
/>

View File

@ -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'}
/>

View File

@ -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'}
/>

View File

@ -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'}
/>

View File

@ -220,8 +220,7 @@ function SuggestedFollow({
]}>
<PreviewableUserAvatar
size={60}
did={profile.did}
handle={profile.handle}
profile={profile}
avatar={profile.avatar}
moderation={moderation.ui('avatar')}
/>

View File

@ -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}
/>
)

View File

@ -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>

View File

@ -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))

View File

@ -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>
)

View File

@ -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')}>