From bb0a6a4b6c4e86c62d599c424dae35c9ee9d200d Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 11 Jun 2024 17:42:28 -0500 Subject: Add KnownFollowers component to standard profile header (#4420) * Add KnownFollowers component to standard profile header * Prep for known followers screen * Add known followers screen * Tighten space * Add pressed state * Edit title * Vertically center * Don't show if no known followers * Bump sdk * Use actual followers.length to show * Updates to show logic, space * Prevent fresh data from applying to cached screens * Tighten space * Better label * Oxford comma * Fix count logic * Add bskyweb route * Useless ternary * Minor spacing tweak --------- Co-authored-by: Paul Frazee --- src/components/KnownFollowers.tsx | 200 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/components/KnownFollowers.tsx (limited to 'src/components/KnownFollowers.tsx') diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx new file mode 100644 index 000000000..b99fe3398 --- /dev/null +++ b/src/components/KnownFollowers.tsx @@ -0,0 +1,200 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' +import {msg, plural, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {makeProfileLink} from '#/lib/routes/links' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, useTheme} from '#/alf' +import {Link} from '#/components/Link' +import {Text} from '#/components/Typography' + +const AVI_SIZE = 30 +const AVI_BORDER = 1 + +/** + * Shared logic to determine if `KnownFollowers` should be shown. + * + * Checks the # of actual returned users instead of the `count` value, because + * `count` includes blocked users and `followers` does not. + */ +export function shouldShowKnownFollowers( + knownFollowers?: AppBskyActorDefs.KnownFollowers, +) { + return knownFollowers && knownFollowers.followers.length > 0 +} + +export function KnownFollowers({ + profile, + moderationOpts, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed + moderationOpts: ModerationOpts +}) { + const cache = React.useRef>( + new Map(), + ) + + /* + * Results for `knownFollowers` are not sorted consistently, so when + * revalidating we can see a flash of this data updating. This cache prevents + * this happening for screens that remain in memory. When pushing a new + * screen, or once this one is popped, this cache is empty, so new data is + * displayed. + */ + if (profile.viewer?.knownFollowers && !cache.current.has(profile.did)) { + cache.current.set(profile.did, profile.viewer.knownFollowers) + } + + const cachedKnownFollowers = cache.current.get(profile.did) + + if (cachedKnownFollowers && shouldShowKnownFollowers(cachedKnownFollowers)) { + return ( + + ) + } + + return null +} + +function KnownFollowersInner({ + profile, + moderationOpts, + cachedKnownFollowers, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed + moderationOpts: ModerationOpts + cachedKnownFollowers: AppBskyActorDefs.KnownFollowers +}) { + const t = useTheme() + const {_} = useLingui() + + const textStyle = [ + a.flex_1, + a.text_sm, + a.leading_snug, + t.atoms.text_contrast_medium, + ] + + // list of users, minus blocks + const returnedCount = cachedKnownFollowers.followers.length + // db count, includes blocks + const fullCount = cachedKnownFollowers.count + // knownFollowers can return up to 5 users, but will exclude blocks + // therefore, if we have less 5 users, use whichever count is lower + const count = + returnedCount < 5 ? Math.min(fullCount, returnedCount) : fullCount + + const slice = cachedKnownFollowers.followers.slice(0, 3).map(f => { + const moderation = moderateProfile(f, moderationOpts) + return { + profile: { + ...f, + displayName: sanitizeDisplayName( + f.displayName || f.handle, + moderation.ui('displayName'), + ), + }, + moderation, + } + }) + + return ( + + {({hovered, pressed}) => ( + <> + + {slice.map(({profile: prof, moderation}, i) => ( + + + + ))} + + + + Followed by{' '} + {count > 2 ? ( + <> + {slice.slice(0, 2).map(({profile: prof}, i) => ( + + {prof.displayName} + {i === 0 && ', '} + + ))} + {', '} + {plural(count - 2, { + one: 'and # other', + other: 'and # others', + })} + + ) : count === 2 ? ( + slice.map(({profile: prof}, i) => ( + + {prof.displayName} {i === 0 ? _(msg`and`) + ' ' : ''} + + )) + ) : ( + + {slice[0].profile.displayName} + + )} + + + )} + + ) +} -- cgit 1.4.1