Verification (#8226)

* WIP

* Alignment with icon

* Add create/remove prompts

* Fill out check dialog a bit

* Reorg

* Handle was verified state

* Add warning to edit profile

* Add warning to handle dialog

* Decent alignment in posts on all platforms

* Refactor alignment for posts, chatlist, hover card

* Disable on profile

* Convo header

* Compute simple verification state

* Add other icon, rename, integrate

* Swap in simple state for profile edits

* Clean up utility hooks

* Add verifications UI to dialog

* Add edu nux

* Revert change

* Fix wrapping of check on profile

* Rename

* Fix gap under PostMeta

* Update check dialogs

* Handle takendown verifiers in check dialog

* alf composer reply to

* Refactor verification state

* Add create/remove mutations, non-functional for now

* Fix up post-rebase

* Add check to first author noty

* Do cache updates after mutations

* DRY up hook, add to profile updates too

* Add to drawer

* Update account list

* Adapt to new types

* Hook up mutations

* Use profile shadow in feeds

* Add to settings

* Shadow currentAccountProfile

* Add invalid state to verifications

* Fix alignment and overflow in Settings and Drawer

* Re-integrate post rebase

* Remove debug code

* Update copy

* Add unverified notification support

* Remove link

* Make sure dialog closes

* Update URL

* Add settings screen

* Integrate new setting into verification states

* Add metrics, bump package, fix bad import

* NUX fixes

* Update copy

* Fixes

* Update types

* fix search autocomplete

* fix lint

* add display name warning to new dialog

* update default prefs

* Add parsing support for notifications

* Bump pkg

* Tweak noty styles

* Adjust check alignment

* Tweak check alignment

* Fix badge for verifier

* Modify copy

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
Eric Bailey
2025-04-18 21:15:32 -05:00
committed by GitHub
parent f1e44ee12e
commit 0ac15920a4
63 changed files with 2332 additions and 395 deletions
@@ -49,7 +49,7 @@ import {Post} from '#/view/com/post/Post'
import {formatCount} from '#/view/com/util/numeric/format'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {PreviewableUserAvatar, UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, platform, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
@@ -59,12 +59,15 @@ import {Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled} from '#/compon
import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
import {StarterPack} from '#/components/icons/StarterPack'
import {VerifiedCheck} from '#/components/icons/VerifiedCheck'
import {InlineLinkText, Link} from '#/components/Link'
import * as MediaPreview from '#/components/MediaPreview'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {SubtleWebHover} from '#/components/SubtleWebHover'
import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck'
import * as bsky from '#/types/bsky'
const MAX_AUTHORS = 5
@@ -145,6 +148,9 @@ let NotificationFeedItem = ({
const niceTimestamp = niceDate(i18n, item.notification.indexedAt)
const firstAuthor = authors[0]
const firstAuthorVerification = useSimpleVerificationState({
profile: firstAuthor.profile,
})
const firstAuthorName = sanitizeDisplayName(
firstAuthor.profile.displayName || firstAuthor.profile.handle,
)
@@ -186,6 +192,24 @@ let NotificationFeedItem = ({
emoji
label={_(msg`Go to ${firstAuthorName}'s profile`)}>
{forceLTR(firstAuthorName)}
{firstAuthorVerification.showBadge && (
<View
style={[
a.relative,
{
paddingTop: platform({android: 2}),
marginBottom: platform({ios: -7}),
top: platform({web: 1}),
paddingLeft: 3,
paddingRight: 2,
},
]}>
<VerificationCheck
width={14}
verifier={firstAuthorVerification.role === 'verifier'}
/>
</View>
)}
</InlineLinkText>
)
const additionalAuthorsCount = authors.length - 1
@@ -366,6 +390,60 @@ let NotificationFeedItem = ({
<StarterPack width={30} gradient="sky" />
</View>
)
// @ts-ignore TODO
} else if (item.type === 'verified') {
a11yLabel = hasMultipleAuthors
? _(
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
one: `${formattedAuthorsCount} other`,
other: `${formattedAuthorsCount} others`,
})} verified you`,
)
: _(msg`${firstAuthorName} verified you`)
notificationContent = hasMultipleAuthors ? (
<Trans>
{firstAuthorLink} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={additionalAuthorsCount}
one={`${formattedAuthorsCount} other`}
other={`${formattedAuthorsCount} others`}
/>
</Text>{' '}
verified you
</Trans>
) : (
<Trans>{firstAuthorLink} verified you</Trans>
)
icon = <VerifiedCheck size="xl" />
// @ts-ignore TODO
} else if (item.type === 'unverified') {
a11yLabel = hasMultipleAuthors
? _(
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
one: `${formattedAuthorsCount} other`,
other: `${formattedAuthorsCount} others`,
})} removed their verifications from your account`,
)
: _(msg`${firstAuthorName} removed their verification from your account`)
notificationContent = hasMultipleAuthors ? (
<Trans>
{firstAuthorLink} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={additionalAuthorsCount}
one={`${formattedAuthorsCount} other`}
other={`${formattedAuthorsCount} others`}
/>
</Text>{' '}
removed their verifications from your account
</Trans>
) : (
<Trans>
{firstAuthorLink} removed their verification from your account
</Trans>
)
icon = <VerifiedCheck size="xl" fill={t.palette.contrast_500} />
} else {
return null
}
@@ -447,7 +525,6 @@ let NotificationFeedItem = ({
style={[
a.flex_row,
a.flex_wrap,
a.pb_2xs,
{paddingTop: 6},
a.self_start,
a.text_md,
@@ -475,7 +552,9 @@ let NotificationFeedItem = ({
</Text>
</ExpandListPressable>
{item.type === 'post-like' || item.type === 'repost' ? (
<AdditionalPostText post={item.subject} />
<View style={[a.pt_2xs]}>
<AdditionalPostText post={item.subject} />
</View>
) : null}
{item.type === 'feedgen-like' && item.subjectUri ? (
<FeedSourceCard
@@ -672,8 +751,6 @@ function ExpandedAuthorsList({
visible: boolean
authors: Author[]
}) {
const {_} = useLingui()
const t = useTheme()
const heightInterp = useAnimatedValue(visible ? 1 : 0)
const targetHeight =
authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/
@@ -692,59 +769,78 @@ function ExpandedAuthorsList({
<Animated.View style={[a.overflow_hidden, heightStyle]}>
{visible &&
authors.map(author => (
<Link
key={author.profile.did}
label={author.profile.displayName || author.profile.handle}
accessibilityHint={_(msg`Opens this profile`)}
to={makeProfileLink({
did: author.profile.did,
handle: author.profile.handle,
})}
style={styles.expandedAuthor}>
<View style={[a.mr_sm]}>
<ProfileHoverCard did={author.profile.did}>
<UserAvatar
size={35}
avatar={author.profile.avatar}
moderation={author.moderation.ui('avatar')}
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
/>
</ProfileHoverCard>
</View>
<View style={[a.flex_1]}>
<View style={[a.flex_row, a.align_end]}>
<Text
numberOfLines={1}
emoji
style={[
a.text_md,
a.font_bold,
a.leading_tight,
{maxWidth: '70%'},
]}>
{sanitizeDisplayName(
author.profile.displayName || author.profile.handle,
)}
</Text>
<Text
numberOfLines={1}
style={[
a.pl_xs,
a.text_md,
a.leading_tight,
a.flex_shrink,
t.atoms.text_contrast_medium,
]}>
{sanitizeHandle(author.profile.handle, '@')}
</Text>
</View>
</View>
</Link>
<ExpandedAuthorCard key={author.profile.did} author={author} />
))}
</Animated.View>
)
}
function ExpandedAuthorCard({author}: {author: Author}) {
const t = useTheme()
const {_} = useLingui()
const verification = useSimpleVerificationState({
profile: author.profile,
})
return (
<Link
key={author.profile.did}
label={author.profile.displayName || author.profile.handle}
accessibilityHint={_(msg`Opens this profile`)}
to={makeProfileLink({
did: author.profile.did,
handle: author.profile.handle,
})}
style={styles.expandedAuthor}>
<View style={[a.mr_sm]}>
<ProfileHoverCard did={author.profile.did}>
<UserAvatar
size={35}
avatar={author.profile.avatar}
moderation={author.moderation.ui('avatar')}
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
/>
</ProfileHoverCard>
</View>
<View style={[a.flex_1]}>
<View style={[a.flex_row, a.align_end]}>
<Text
numberOfLines={1}
emoji
style={[
a.text_md,
a.font_bold,
a.leading_tight,
{maxWidth: '70%'},
]}>
{sanitizeDisplayName(
author.profile.displayName || author.profile.handle,
)}
</Text>
{verification.showBadge && (
<View style={[a.pl_xs, a.self_center]}>
<VerificationCheck
width={14}
verifier={verification.role === 'verifier'}
/>
</View>
)}
<Text
numberOfLines={1}
style={[
a.pl_xs,
a.text_md,
a.leading_tight,
a.flex_shrink,
t.atoms.text_contrast_medium,
]}>
{sanitizeHandle(author.profile.handle, '@')}
</Text>
</View>
</View>
</Link>
)
}
function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
const t = useTheme()
if (
@@ -761,7 +857,7 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
{text?.length > 0 && (
<Text
emoji
style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
{text}
</Text>
)}