[APP-1928] add bot/automated account badge and self-labeling settings (#10008)
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 0a2 2 0 0 1 1 3.73V5h4.2c1.68 0 2.52 0 3.162.327a3 3 0 0 1 1.31 1.31C22 7.28 22 8.12 22 9.8v.25a2.501 2.501 0 0 1 0 4.9V15c0 2.8 0 4.2-.545 5.27a5 5 0 0 1-2.185 2.185C18.2 23 16.8 23 14 23h-4c-2.8 0-4.2 0-5.27-.545a5 5 0 0 1-2.185-2.185C2 19.2 2 17.8 2 15v-.05a2.5 2.5 0 0 1 0-4.9V9.8c0-1.68 0-2.52.327-3.162a3 3 0 0 1 1.31-1.31C4.28 5 5.12 5 6.8 5H11V3.73A2 2 0 0 1 12 0M8 10a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0v-2a2 2 0 0 0-2-2m8 0a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0v-2a2 2 0 0 0-2-2" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 621 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12 2a1 1 0 0 1 1 1v2h3.2l1.113.005c.975.015 1.568.077 2.05.322a3 3 0 0 1 1.31 1.31C21 7.28 21 8.12 21 9.8v.287a1.498 1.498 0 0 1-.005 2.827c-.006 2.204-.058 3.41-.54 4.356l-.093.174a5 5 0 0 1-2.092 2.011l-.205.096c-.766.33-1.72.417-3.21.44L13 20h-2l-1.854-.009c-1.49-.023-2.445-.11-3.211-.44l-.205-.096a5 5 0 0 1-2.185-2.185c-.409-.803-.51-1.79-.536-3.415l-.005-.94A1.498 1.498 0 0 1 3 10.086V9.8c0-1.575 0-2.412.27-3.04l.057-.122a3 3 0 0 1 1.105-1.196l.206-.115C5.279 5 6.12 5 7.8 5H11V3a1 1 0 0 1 1-1M7.8 7c-.873 0-1.408.002-1.808.034a3 3 0 0 0-.367.051l-.063.018-.016.006a1 1 0 0 0-.437.437l-.006.016-.018.063a3 3 0 0 0-.05.367C5.001 8.392 5 8.927 5 9.8V12c0 1.433.002 2.388.062 3.121.058.71.16 1.036.265 1.241a3 3 0 0 0 1.31 1.31c.207.106.532.209 1.242.267.733.06 1.688.061 3.121.061h2c1.433 0 2.388-.002 3.121-.061.71-.058 1.036-.161 1.241-.266a3 3 0 0 0 1.31-1.31c.106-.206.209-.532.267-1.242.06-.733.061-1.688.061-3.121V9.8c0-.873-.002-1.408-.034-1.808a2.5 2.5 0 0 0-.051-.367l-.017-.063-.007-.016a1 1 0 0 0-.437-.437l-.015-.006-.064-.018a3 3 0 0 0-.367-.05C17.608 7.001 17.073 7 16.2 7zM9 10a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1m6 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -10,6 +10,7 @@ import logo from '../../assets/logo_full_name.svg'
|
|||||||
import {Like as LikeIcon} from '../icons/Like'
|
import {Like as LikeIcon} from '../icons/Like'
|
||||||
import {Reply as ReplyIcon} from '../icons/Reply'
|
import {Reply as ReplyIcon} from '../icons/Reply'
|
||||||
import {Repost as RepostIcon} from '../icons/Repost'
|
import {Repost as RepostIcon} from '../icons/Repost'
|
||||||
|
import {Robot as RobotIcon} from '../icons/Robot'
|
||||||
import {CONTENT_LABELS} from '../labels'
|
import {CONTENT_LABELS} from '../labels'
|
||||||
import * as bsky from '../types/bsky'
|
import * as bsky from '../types/bsky'
|
||||||
import {niceDate} from '../util/nice-date'
|
import {niceDate} from '../util/nice-date'
|
||||||
@@ -43,6 +44,9 @@ export function Post({thread}: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const verification = getVerificationState({profile: post.author})
|
const verification = getVerificationState({profile: post.author})
|
||||||
|
const isBot = post.author.labels?.some(
|
||||||
|
l => l.val === 'bot' && l.src === post.author.did,
|
||||||
|
)
|
||||||
|
|
||||||
const href = `/profile/${post.author.did}/post/${getRkey(post)}`
|
const href = `/profile/${post.author.did}/post/${getRkey(post)}`
|
||||||
|
|
||||||
@@ -76,6 +80,12 @@ export function Post({thread}: Props) {
|
|||||||
size={15}
|
size={15}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isBot && (
|
||||||
|
<RobotIcon
|
||||||
|
className="pl-[3px] mt-px shrink-0 text-slate-500 dark:text-slate-400"
|
||||||
|
size={15}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href={`/profile/${post.author.did}`}
|
href={`/profile/${post.author.did}`}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import {h} from 'preact'
|
||||||
|
|
||||||
|
export const Robot = ({
|
||||||
|
size = 14,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
size?: number
|
||||||
|
className?: string
|
||||||
|
}) => (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M12 0C13.1046 0 14 0.89543 14 2C14 2.73976 13.5971 3.3835 13 3.72949V5H17.2002C18.8802 5 19.7206 5.00018 20.3623 5.32715C20.9265 5.61472 21.3853 6.07347 21.6729 6.6377C21.9998 7.27941 22 8.11978 22 9.7998V10.0498C23.1411 10.2814 24 11.2905 24 12.5C24 13.7094 23.141 14.7175 22 14.9492V15C22 17.8 21.9999 19.2 21.4551 20.2695C20.9757 21.2103 20.2103 21.9757 19.2695 22.4551C18.2 22.9999 16.8 23 14 23H10C7.20005 23 5.79998 22.9999 4.73047 22.4551C3.78966 21.9757 3.02429 21.2103 2.54492 20.2695C2.00013 19.2 2 17.8 2 15V14.9492C0.858955 14.7175 0 13.7094 0 12.5C0 11.2905 0.85886 10.2814 2 10.0498V9.7998C2 8.11978 2.00018 7.27941 2.32715 6.6377C2.61472 6.07347 3.07347 5.61472 3.6377 5.32715C4.27941 5.00018 5.11978 5 6.7998 5H11V3.72949C10.4029 3.3835 10 2.73976 10 2C10 0.89543 10.8954 0 12 0ZM8 10C6.89543 10 6 10.8954 6 12V14C6 15.1046 6.89543 16 8 16C9.10457 16 10 15.1046 10 14V12C10 10.8954 9.10457 10 8 10ZM16 10C14.8954 10 14 10.8954 14 12V14C14 15.1046 14.8954 16 16 16C17.1046 16 18 15.1046 18 14V12C18 10.8954 17.1046 10 16 10Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
@@ -292,6 +292,7 @@ func serve(cctx *cli.Context) error {
|
|||||||
e.GET("/settings/accessibility", server.WebGeneric)
|
e.GET("/settings/accessibility", server.WebGeneric)
|
||||||
e.GET("/settings/appearance", server.WebGeneric)
|
e.GET("/settings/appearance", server.WebGeneric)
|
||||||
e.GET("/settings/account", server.WebGeneric)
|
e.GET("/settings/account", server.WebGeneric)
|
||||||
|
e.GET("/settings/automation-label", server.WebGeneric)
|
||||||
e.GET("/settings/privacy-and-security", server.WebGeneric)
|
e.GET("/settings/privacy-and-security", server.WebGeneric)
|
||||||
e.GET("/settings/privacy-and-security/activity", server.WebGeneric)
|
e.GET("/settings/privacy-and-security/activity", server.WebGeneric)
|
||||||
e.GET("/settings/content-and-media", server.WebGeneric)
|
e.GET("/settings/content-and-media", server.WebGeneric)
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ import {ActivityPrivacySettingsScreen} from '#/screens/Settings/ActivityPrivacyS
|
|||||||
import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
|
import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
|
||||||
import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings'
|
import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings'
|
||||||
import {AppPasswordsScreen} from '#/screens/Settings/AppPasswords'
|
import {AppPasswordsScreen} from '#/screens/Settings/AppPasswords'
|
||||||
|
import {AutomationLabelSettingsScreen} from '#/screens/Settings/AutomationLabelSettings'
|
||||||
import {ContentAndMediaSettingsScreen} from '#/screens/Settings/ContentAndMediaSettings'
|
import {ContentAndMediaSettingsScreen} from '#/screens/Settings/ContentAndMediaSettings'
|
||||||
import {ExternalMediaPreferencesScreen} from '#/screens/Settings/ExternalMediaPreferences'
|
import {ExternalMediaPreferencesScreen} from '#/screens/Settings/ExternalMediaPreferences'
|
||||||
import {FindContactsSettingsScreen} from '#/screens/Settings/FindContactsSettings'
|
import {FindContactsSettingsScreen} from '#/screens/Settings/FindContactsSettings'
|
||||||
@@ -403,6 +404,14 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
|
|||||||
requireAuth: true,
|
requireAuth: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="AutomationLabelSettings"
|
||||||
|
getComponent={() => AutomationLabelSettingsScreen}
|
||||||
|
options={{
|
||||||
|
title: title(msg`Automation Label`),
|
||||||
|
requireAuth: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="PrivacyAndSecuritySettings"
|
name="PrivacyAndSecuritySettings"
|
||||||
getComponent={() => PrivacyAndSecuritySettingsScreen}
|
getComponent={() => PrivacyAndSecuritySettingsScreen}
|
||||||
|
|||||||
@@ -732,6 +732,9 @@ export type Events = {
|
|||||||
'verification:settings:hideBadges': {}
|
'verification:settings:hideBadges': {}
|
||||||
'verification:settings:unHideBadges': {}
|
'verification:settings:unHideBadges': {}
|
||||||
|
|
||||||
|
'bot:label:toggle': {state: 'add' | 'remove'}
|
||||||
|
'bot:badge:click': {}
|
||||||
|
|
||||||
'live:create': {duration: number}
|
'live:create': {duration: number}
|
||||||
'live:edit': {}
|
'live:edit': {}
|
||||||
'live:remove': {}
|
'live:remove': {}
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ import {Button} from '#/components/Button'
|
|||||||
import {CheckThick_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
import {CheckThick_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron'
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron'
|
||||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
|
|
||||||
export function AccountList({
|
export function AccountList({
|
||||||
@@ -116,7 +115,6 @@ function AccountItem({
|
|||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
const {isActive: live} = useActorStatus(profile)
|
const {isActive: live} = useActorStatus(profile)
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
@@ -164,13 +162,12 @@ function AccountItem({
|
|||||||
profile?.displayName || profile?.handle || account.handle,
|
profile?.displayName || profile?.handle || account.handle,
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
{profile && (
|
||||||
<View>
|
<ProfileBadges
|
||||||
<VerificationCheck
|
profile={profile}
|
||||||
width={12}
|
size="sm"
|
||||||
verifier={verification.role === 'verifier'}
|
style={[{marginTop: -2}]}
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import {View} from 'react-native'
|
||||||
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {Bot_Filled as RobotIcon} from '#/components/icons/Bot'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {navigate} from '#/Navigation'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
|
export function BotAccountAlert({
|
||||||
|
control,
|
||||||
|
profile,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
profile: bsky.profile.AnyProfileView
|
||||||
|
}) {
|
||||||
|
const {t: l} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
|
const isSelf = profile.did === currentAccount?.did
|
||||||
|
const description = isSelf
|
||||||
|
? l`You have marked this account as automated. You can remove it at any time from your account settings.`
|
||||||
|
: l`This account has been marked as automated by its owner.`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={l`Automated account`}
|
||||||
|
style={[web({maxWidth: 320})]}>
|
||||||
|
<View style={[a.align_center, a.pb_md, a.shadow_sm]}>
|
||||||
|
<RobotIcon width={48} fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
</View>
|
||||||
|
<View style={[a.align_center]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.leading_snug,
|
||||||
|
a.text_center,
|
||||||
|
a.pb_xl,
|
||||||
|
a.text_md,
|
||||||
|
t.atoms.text_contrast_high,
|
||||||
|
{maxWidth: 300},
|
||||||
|
]}>
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[a.w_full, a.gap_sm]}>
|
||||||
|
<Button
|
||||||
|
label={l`Okay`}
|
||||||
|
onPress={() => control.close()}
|
||||||
|
color="primary"
|
||||||
|
size="large">
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Okay</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
{isSelf ? (
|
||||||
|
<Button
|
||||||
|
label={l`Open settings`}
|
||||||
|
onPress={() => {
|
||||||
|
control.close(() => {
|
||||||
|
navigate('AutomationLabelSettings')
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
color="secondary"
|
||||||
|
size="large">
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Open settings</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import {View} from 'react-native'
|
||||||
|
import {type ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
|
import {useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {BotAccountAlert} from '#/components/BotAccountAlert'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {Bot_Filled as RobotIcon} from '#/components/icons/Bot'
|
||||||
|
import {useAnalytics} from '#/analytics'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
|
export function isBotAccount(profile: {
|
||||||
|
did: string
|
||||||
|
labels?: ComAtprotoLabelDefs.Label[]
|
||||||
|
}): boolean {
|
||||||
|
return (
|
||||||
|
profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BotBadge({
|
||||||
|
profile,
|
||||||
|
alwaysShow = false,
|
||||||
|
width,
|
||||||
|
}: {
|
||||||
|
profile: bsky.profile.AnyProfileView
|
||||||
|
alwaysShow?: boolean
|
||||||
|
width: number
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
if (!isBotAccount(profile) && !alwaysShow) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<RobotIcon width={width} fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BotBadgeButton({
|
||||||
|
profile,
|
||||||
|
width,
|
||||||
|
}: {
|
||||||
|
profile: bsky.profile.AnyProfileView
|
||||||
|
width: number
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const ax = useAnalytics()
|
||||||
|
const {t: l} = useLingui()
|
||||||
|
const control = useDialogControl()
|
||||||
|
|
||||||
|
if (!isBotAccount(profile)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
label={l`Automated account`}
|
||||||
|
hitSlop={20}
|
||||||
|
onPress={evt => {
|
||||||
|
evt.preventDefault()
|
||||||
|
ax.metric('bot:badge:click', {})
|
||||||
|
control.open()
|
||||||
|
}}>
|
||||||
|
{({hovered}) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.justify_end,
|
||||||
|
a.align_end,
|
||||||
|
a.transition_transform,
|
||||||
|
{
|
||||||
|
width: width,
|
||||||
|
height: width,
|
||||||
|
transform: [{scale: hovered ? 1.1 : 1}],
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<RobotIcon
|
||||||
|
width={width}
|
||||||
|
fill={t.atoms.text_contrast_medium.color}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<BotAccountAlert control={control} profile={profile} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -17,9 +17,8 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
|
|||||||
import {atoms as a, tokens, useTheme} from '#/alf'
|
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {useDialogContext} from '#/components/Dialog'
|
import {useDialogContext} from '#/components/Dialog'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
@@ -111,7 +110,6 @@ function RecentChatItem({
|
|||||||
profile.displayName || sanitizeHandle(profile.handle),
|
profile.displayName || sanitizeHandle(profile.handle),
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)
|
)
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
|
|
||||||
if (isBlockedOrBlocking(profile) || isMuted(profile)) {
|
if (isBlockedOrBlocking(profile) || isMuted(profile)) {
|
||||||
return null
|
return null
|
||||||
@@ -141,14 +139,7 @@ function RecentChatItem({
|
|||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges profile={profile} size="xs" style={[a.pl_2xs]} />
|
||||||
<View style={[a.pl_2xs]}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={10}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import {View} from 'react-native'
|
||||||
|
|
||||||
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
|
import {atoms as a, type ViewStyleProp} from '#/alf'
|
||||||
|
import {BotBadge, BotBadgeButton, isBotAccount} from '#/components/BotBadge'
|
||||||
|
import {useSimpleVerificationState} from '#/components/verification'
|
||||||
|
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
||||||
|
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
|
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
|
|
||||||
|
const verificationIconSizes: Record<Size, number> = {
|
||||||
|
xs: 10,
|
||||||
|
sm: 12,
|
||||||
|
md: 14,
|
||||||
|
lg: 18,
|
||||||
|
xl: 22,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const botIconSizes: Record<Size, number> = {
|
||||||
|
xs: 11,
|
||||||
|
sm: 13,
|
||||||
|
md: 15,
|
||||||
|
lg: 19,
|
||||||
|
xl: 23,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export function ProfileBadges({
|
||||||
|
profile,
|
||||||
|
interactive = false,
|
||||||
|
size,
|
||||||
|
style,
|
||||||
|
}: ViewStyleProp & {
|
||||||
|
profile: bsky.profile.AnyProfileView
|
||||||
|
interactive?: boolean
|
||||||
|
size: Size
|
||||||
|
}) {
|
||||||
|
const shadowed = useProfileShadow(profile)
|
||||||
|
const verification = useSimpleVerificationState({profile})
|
||||||
|
|
||||||
|
// if nothing to show, don't render the container at all
|
||||||
|
if (!verification.showBadge && !isBotAccount(shadowed)) return null
|
||||||
|
|
||||||
|
const isOnTheSmallSide = size === 'xs' || size === 'sm'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
isOnTheSmallSide ? a.gap_2xs : a.gap_xs,
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
{interactive ? (
|
||||||
|
<>
|
||||||
|
<VerificationCheckButton
|
||||||
|
profile={shadowed}
|
||||||
|
width={verificationIconSizes[size]}
|
||||||
|
/>
|
||||||
|
<BotBadgeButton profile={shadowed} width={botIconSizes[size]} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{verification.showBadge && (
|
||||||
|
<VerificationCheck
|
||||||
|
verifier={verification.role === 'verifier'}
|
||||||
|
width={verificationIconSizes[size]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<BotBadge profile={shadowed} width={botIconSizes[size]} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -41,10 +41,9 @@ import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
|||||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||||
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
||||||
import * as Pills from '#/components/Pills'
|
import * as Pills from '#/components/Pills'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {type Metrics} from '#/analytics'
|
import {type Metrics} from '#/analytics'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
@@ -242,7 +241,6 @@ function InlineNameAndHandle({
|
|||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
const moderation = moderateProfile(profile, moderationOpts)
|
const moderation = moderateProfile(profile, moderationOpts)
|
||||||
const name = sanitizeDisplayName(
|
const name = sanitizeDisplayName(
|
||||||
profile.displayName || sanitizeHandle(profile.handle),
|
profile.displayName || sanitizeHandle(profile.handle),
|
||||||
@@ -262,19 +260,15 @@ function InlineNameAndHandle({
|
|||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{forceLTR(name)}
|
{forceLTR(name)}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges
|
||||||
<View
|
profile={profile}
|
||||||
style={[
|
size="md"
|
||||||
a.pl_2xs,
|
style={[
|
||||||
a.self_center,
|
a.pl_2xs,
|
||||||
{marginTop: platform({default: 0, android: -1})},
|
a.self_center,
|
||||||
]}>
|
{marginTop: platform({default: 0, android: -1})},
|
||||||
<VerificationCheck
|
]}
|
||||||
width={platform({android: 13, default: 12})}
|
/>
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Text
|
<Text
|
||||||
emoji
|
emoji
|
||||||
style={[
|
style={[
|
||||||
@@ -305,7 +299,6 @@ export function Name({
|
|||||||
profile.displayName || sanitizeHandle(profile.handle),
|
profile.displayName || sanitizeHandle(profile.handle),
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)
|
)
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.flex_row, a.align_center, a.max_w_full, style]}>
|
<View style={[a.flex_row, a.align_center, a.max_w_full, style]}>
|
||||||
<Text
|
<Text
|
||||||
@@ -321,14 +314,7 @@ export function Name({
|
|||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges profile={profile} size="md" style={[a.pl_xs]} />
|
||||||
<View style={[a.pl_xs]}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={14}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ import {InlineLinkText, Link} from '#/components/Link'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as Pills from '#/components/Pills'
|
import * as Pills from '#/components/Pills'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {IS_WEB_TOUCH_DEVICE} from '#/env'
|
import {IS_WEB_TOUCH_DEVICE} from '#/env'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog'
|
import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog'
|
||||||
@@ -459,7 +458,6 @@ function Inner({
|
|||||||
[currentAccount, profile],
|
[currentAccount, profile],
|
||||||
)
|
)
|
||||||
const isLabeler = profile.associated?.labeler
|
const isLabeler = profile.associated?.labeler
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
@@ -527,20 +525,16 @@ function Inner({
|
|||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges
|
||||||
<View
|
profile={profile}
|
||||||
style={[
|
size="md"
|
||||||
a.pl_xs,
|
style={[
|
||||||
{
|
a.pl_xs,
|
||||||
marginTop: -2,
|
{
|
||||||
},
|
marginTop: -1,
|
||||||
]}>
|
},
|
||||||
<VerificationCheck
|
]}
|
||||||
width={16}
|
/>
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ProfileHeaderHandle profile={profileShadow} disableTaps />
|
<ProfileHeaderHandle profile={profileShadow} disableTaps />
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/
|
|||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
|
|
||||||
const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE
|
const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE
|
||||||
@@ -112,9 +111,6 @@ function HeaderReady({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const convoState = useConvo()
|
const convoState = useConvo()
|
||||||
const verification = useSimpleVerificationState({
|
|
||||||
profile,
|
|
||||||
})
|
|
||||||
|
|
||||||
const isDeletedAccount = profile?.handle === 'missing.invalid'
|
const isDeletedAccount = profile?.handle === 'missing.invalid'
|
||||||
const displayName = isDeletedAccount
|
const displayName = isDeletedAccount
|
||||||
@@ -161,14 +157,7 @@ function HeaderReady({
|
|||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges profile={profile} size="md" style={[a.pl_xs]} />
|
||||||
<View style={[a.pl_xs]}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={14}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
{!isDeletedAccount && (
|
{!isDeletedAccount && (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const Bot_Stroke = createSinglePathSVG({
|
||||||
|
path: 'M12 2a1 1 0 0 1 1 1v2h3.2l1.113.005c.975.015 1.568.077 2.05.322a3 3 0 0 1 1.31 1.31C21 7.28 21 8.12 21 9.8v.287a1.498 1.498 0 0 1-.005 2.827c-.006 2.204-.058 3.41-.54 4.356l-.093.174a5 5 0 0 1-2.092 2.011l-.205.096c-.766.33-1.72.417-3.21.44L13 20h-2l-1.854-.009c-1.49-.023-2.445-.11-3.211-.44l-.205-.096a5 5 0 0 1-2.185-2.185c-.409-.803-.51-1.79-.536-3.415l-.005-.94A1.498 1.498 0 0 1 3 10.086V9.8c0-1.575 0-2.412.27-3.04l.057-.122a3 3 0 0 1 1.105-1.196l.206-.115C5.279 5 6.12 5 7.8 5H11V3a1 1 0 0 1 1-1M7.8 7c-.873 0-1.408.002-1.808.034a2.6 2.6 0 0 0-.367.051l-.063.018-.016.006a1 1 0 0 0-.437.437q0 0-.006.016l-.018.063a2.6 2.6 0 0 0-.05.367C5.001 8.392 5 8.927 5 9.8V12c0 1.433.002 2.388.062 3.121.058.71.16 1.036.265 1.241a3 3 0 0 0 1.31 1.31c.207.106.532.209 1.242.267.733.06 1.688.061 3.121.061h2c1.433 0 2.388-.002 3.121-.061.71-.058 1.036-.161 1.241-.266a3 3 0 0 0 1.31-1.31c.106-.206.209-.532.267-1.242.06-.733.061-1.688.061-3.121V9.8c0-.873-.002-1.408-.034-1.808a2.5 2.5 0 0 0-.051-.367l-.017-.063-.007-.016a1 1 0 0 0-.437-.437l-.015-.006-.064-.018a2.6 2.6 0 0 0-.367-.05C17.608 7.001 17.073 7 16.2 7zM9 10a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1m6 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Bot_Filled = createSinglePathSVG({
|
||||||
|
path: 'M12 0a2 2 0 0 1 1 3.73V5h4.2c1.68 0 2.52 0 3.162.327a3 3 0 0 1 1.31 1.31C22 7.28 22 8.12 22 9.8v.25a2.501 2.501 0 0 1 0 4.9V15c0 2.8 0 4.2-.545 5.27a5 5 0 0 1-2.185 2.185C18.2 23 16.8 23 14 23h-4c-2.8 0-4.2 0-5.27-.545a5 5 0 0 1-2.185-2.185C2 19.2 2 17.8 2 15v-.05a2.5 2.5 0 0 1 0-4.9V9.8c0-1.68 0-2.52.327-3.162a3 3 0 0 1 1.31-1.31C4.28 5 5.12 5 6.8 5H11V3.73A2 2 0 0 1 12 0M8 10a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0v-2a2 2 0 0 0-2-2m8 0a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0v-2a2 2 0 0 0-2-2',
|
||||||
|
})
|
||||||
@@ -36,7 +36,11 @@ export function LabelsOnMe({
|
|||||||
if (!labels || !currentAccount) {
|
if (!labels || !currentAccount) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
labels = labels.filter(l => !l.val.startsWith('!'))
|
labels = labels.filter(
|
||||||
|
l =>
|
||||||
|
!l.val.startsWith('!') &&
|
||||||
|
!(l.val === 'bot' && l.src === currentAccount.did),
|
||||||
|
)
|
||||||
if (!labels.length) {
|
if (!labels.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {msg} from '@lingui/core/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {type Shadow} from '#/state/cache/types'
|
import {type Shadow} from '#/state/cache/types'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {useFullVerificationState} from '#/components/verification'
|
import {useFullVerificationState} from '#/components/verification'
|
||||||
@@ -51,43 +51,36 @@ export function shouldShowVerificationCheckButton(
|
|||||||
|
|
||||||
export function VerificationCheckButton({
|
export function VerificationCheckButton({
|
||||||
profile,
|
profile,
|
||||||
size,
|
width,
|
||||||
}: {
|
}: {
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
size: 'lg' | 'md' | 'sm'
|
width: number
|
||||||
}) {
|
}) {
|
||||||
const state = useFullVerificationState({
|
const state = useFullVerificationState({
|
||||||
profile,
|
profile,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldShowVerificationCheckButton(state)) {
|
if (shouldShowVerificationCheckButton(state)) {
|
||||||
return <Badge profile={profile} verificationState={state} size={size} />
|
return <Badge profile={profile} verificationState={state} width={width} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Badge({
|
function Badge({
|
||||||
profile,
|
profile,
|
||||||
verificationState: state,
|
verificationState: state,
|
||||||
size,
|
width,
|
||||||
}: {
|
}: {
|
||||||
profile: Shadow<bsky.profile.AnyProfileView>
|
profile: Shadow<bsky.profile.AnyProfileView>
|
||||||
verificationState: FullVerificationState
|
verificationState: FullVerificationState
|
||||||
size: 'lg' | 'md' | 'sm'
|
width: number
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const verificationsDialogControl = useDialogControl()
|
const verificationsDialogControl = useDialogControl()
|
||||||
const verifierDialogControl = useDialogControl()
|
const verifierDialogControl = useDialogControl()
|
||||||
const {gtPhone} = useBreakpoints()
|
|
||||||
let dimensions = 12
|
|
||||||
if (size === 'lg') {
|
|
||||||
dimensions = gtPhone ? 20 : 18
|
|
||||||
} else if (size === 'md') {
|
|
||||||
dimensions = 14
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifiedByHidden = !state.profile.showBadge && state.profile.isViewer
|
const verifiedByHidden = !state.profile.showBadge && state.profile.isViewer
|
||||||
|
|
||||||
@@ -116,8 +109,8 @@ export function Badge({
|
|||||||
a.align_end,
|
a.align_end,
|
||||||
a.transition_transform,
|
a.transition_transform,
|
||||||
{
|
{
|
||||||
width: dimensions,
|
width: width,
|
||||||
height: dimensions,
|
height: width,
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: hovered ? 1.1 : 1,
|
scale: hovered ? 1.1 : 1,
|
||||||
@@ -126,7 +119,7 @@ export function Badge({
|
|||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<VerificationCheck
|
<VerificationCheck
|
||||||
width={dimensions}
|
width={width}
|
||||||
fill={
|
fill={
|
||||||
verifiedByHidden
|
verifiedByHidden
|
||||||
? t.atoms.bg_contrast_100.backgroundColor
|
? t.atoms.bg_contrast_100.backgroundColor
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export type CommonNavigatorParams = {
|
|||||||
AccessibilitySettings: undefined
|
AccessibilitySettings: undefined
|
||||||
AppearanceSettings: undefined
|
AppearanceSettings: undefined
|
||||||
AccountSettings: undefined
|
AccountSettings: undefined
|
||||||
|
AutomationLabelSettings: undefined
|
||||||
PrivacyAndSecuritySettings: undefined
|
PrivacyAndSecuritySettings: undefined
|
||||||
ActivityPrivacySettings: undefined
|
ActivityPrivacySettings: undefined
|
||||||
ContentAndMediaSettings: undefined
|
ContentAndMediaSettings: undefined
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const router = new Router<AllNavigatableRoutes>({
|
|||||||
AppearanceSettings: '/settings/appearance',
|
AppearanceSettings: '/settings/appearance',
|
||||||
SavedFeeds: '/settings/saved-feeds',
|
SavedFeeds: '/settings/saved-feeds',
|
||||||
AccountSettings: '/settings/account',
|
AccountSettings: '/settings/account',
|
||||||
|
AutomationLabelSettings: '/settings/automation-label',
|
||||||
PrivacyAndSecuritySettings: '/settings/privacy-and-security',
|
PrivacyAndSecuritySettings: '/settings/privacy-and-security',
|
||||||
ActivityPrivacySettings: '/settings/privacy-and-security/activity',
|
ActivityPrivacySettings: '/settings/privacy-and-security/activity',
|
||||||
ContentAndMediaSettings: '/settings/content-and-media',
|
ContentAndMediaSettings: '/settings/content-and-media',
|
||||||
|
|||||||
@@ -41,9 +41,8 @@ import {Link} from '#/components/Link'
|
|||||||
import {useMenuControl} from '#/components/Menu'
|
import {useMenuControl} from '#/components/Menu'
|
||||||
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
import {PostAlerts} from '#/components/moderation/PostAlerts'
|
||||||
import {createPortalGroup} from '#/components/Portal'
|
import {createPortalGroup} from '#/components/Portal'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_NATIVE} from '#/env'
|
import {IS_NATIVE} from '#/env'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
@@ -112,9 +111,6 @@ function ChatListItemReady({
|
|||||||
const playHaptic = useHaptics()
|
const playHaptic = useHaptics()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const isUnread = convo.unreadCount > 0
|
const isUnread = convo.unreadCount > 0
|
||||||
const verification = useSimpleVerificationState({
|
|
||||||
profile,
|
|
||||||
})
|
|
||||||
|
|
||||||
const blockInfo = useMemo(() => {
|
const blockInfo = useMemo(() => {
|
||||||
const modui = moderation.ui('profileView')
|
const modui = moderation.ui('profileView')
|
||||||
@@ -414,14 +410,11 @@ function ChatListItemReady({
|
|||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{verification.showBadge && (
|
<ProfileBadges
|
||||||
<View style={[a.pl_xs, a.self_center]}>
|
profile={profile}
|
||||||
<VerificationCheck
|
size="md"
|
||||||
width={14}
|
style={[a.pl_xs, a.self_center]}
|
||||||
verifier={verification.role === 'verifier'}
|
/>
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{lastMessageSentAt && (
|
{lastMessageSentAt && (
|
||||||
<View style={[a.pl_xs]}>
|
<View style={[a.pl_xs]}>
|
||||||
<TimeElapsed timestamp={lastMessageSentAt}>
|
<TimeElapsed timestamp={lastMessageSentAt}>
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
|
|||||||
import {TranslatedPost} from '#/components/Post/Translated'
|
import {TranslatedPost} from '#/components/Post/Translated'
|
||||||
import {PostControls, PostControlsSkeleton} from '#/components/PostControls'
|
import {PostControls, PostControlsSkeleton} from '#/components/PostControls'
|
||||||
import {useFormatPostStatCount} from '#/components/PostControls/util'
|
import {useFormatPostStatCount} from '#/components/PostControls/util'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import * as Skele from '#/components/Skeleton'
|
import * as Skele from '#/components/Skeleton'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
|
||||||
import {WhoCanReply} from '#/components/WhoCanReply'
|
import {WhoCanReply} from '#/components/WhoCanReply'
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
@@ -362,7 +362,11 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={[a.pl_xs]}>
|
<View style={[a.pl_xs]}>
|
||||||
<VerificationCheckButton profile={authorShadow} size="md" />
|
<ProfileBadges
|
||||||
|
profile={authorShadow}
|
||||||
|
size="md"
|
||||||
|
interactive
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ import {
|
|||||||
KnownFollowers,
|
KnownFollowers,
|
||||||
shouldShowKnownFollowers,
|
shouldShowKnownFollowers,
|
||||||
} from '#/components/KnownFollowers'
|
} from '#/components/KnownFollowers'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton'
|
|
||||||
import {IS_IOS} from '#/env'
|
import {IS_IOS} from '#/env'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
import {GermButton} from '../components/GermButton'
|
import {GermButton} from '../components/GermButton'
|
||||||
@@ -143,7 +143,7 @@ let ProfileHeaderStandard = ({
|
|||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}
|
)}
|
||||||
<View style={[a.pl_xs, {marginTop: platform({ios: 2})}]}>
|
<View style={[a.pl_xs, {marginTop: platform({ios: 2})}]}>
|
||||||
<VerificationCheckButton profile={profile} size="lg" />
|
<ProfileBadges profile={profile} size="lg" interactive />
|
||||||
</View>
|
</View>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ import {Button, ButtonIcon} from '#/components/Button'
|
|||||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
@@ -139,7 +138,6 @@ function RecentProfileItem({
|
|||||||
profile.displayName || sanitizeHandle(profile.handle),
|
profile.displayName || sanitizeHandle(profile.handle),
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)
|
)
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.relative]}>
|
<View style={[a.relative]}>
|
||||||
@@ -165,14 +163,7 @@ function RecentProfileItem({
|
|||||||
<Text emoji style={[a.text_xs, a.leading_snug]} numberOfLines={1}>
|
<Text emoji style={[a.text_xs, a.leading_snug]} numberOfLines={1}>
|
||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges profile={profile} size="xs" style={[a.pl_xs]} />
|
||||||
<View style={[a.pl_2xs]}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={10}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import {Trans} from '@lingui/react/macro'
|
|||||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
||||||
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {AgeAssuranceAccountCard} from '#/components/ageAssurance/AgeAssuranceAccountCard'
|
import {AgeAssuranceAccountCard} from '#/components/ageAssurance/AgeAssuranceAccountCard'
|
||||||
|
import {isBotAccount} from '#/components/BotBadge'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||||
import {
|
import {
|
||||||
@@ -16,6 +18,7 @@ import {
|
|||||||
} from '#/components/dialogs/EmailDialog'
|
} from '#/components/dialogs/EmailDialog'
|
||||||
import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
|
import {At_Stroke2_Corner2_Rounded as AtIcon} from '#/components/icons/At'
|
||||||
import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake'
|
import {BirthdayCake_Stroke2_Corner2_Rounded as BirthdayCakeIcon} from '#/components/icons/BirthdayCake'
|
||||||
|
import {Bot_Stroke as RobotIcon} from '#/components/icons/Bot'
|
||||||
import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car'
|
import {Car_Stroke2_Corner2_Rounded as CarIcon} from '#/components/icons/Car'
|
||||||
import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope'
|
import {Envelope_Stroke2_Corner2_Rounded as EnvelopeIcon} from '#/components/icons/Envelope'
|
||||||
import {Freeze_Stroke2_Corner2_Rounded as FreezeIcon} from '#/components/icons/Freeze'
|
import {Freeze_Stroke2_Corner2_Rounded as FreezeIcon} from '#/components/icons/Freeze'
|
||||||
@@ -35,6 +38,7 @@ export function AccountSettingsScreen({}: Props) {
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||||
const emailDialogControl = useEmailDialogControl()
|
const emailDialogControl = useEmailDialogControl()
|
||||||
const birthdayControl = useDialogControl()
|
const birthdayControl = useDialogControl()
|
||||||
const changeHandleControl = useDialogControl()
|
const changeHandleControl = useDialogControl()
|
||||||
@@ -148,6 +152,19 @@ export function AccountSettingsScreen({}: Props) {
|
|||||||
/>
|
/>
|
||||||
</SettingsList.Item>
|
</SettingsList.Item>
|
||||||
<AgeAssuranceAccountCard style={[a.px_xl, a.pt_xs, a.pb_md]} />
|
<AgeAssuranceAccountCard style={[a.px_xl, a.pt_xs, a.pb_md]} />
|
||||||
|
<SettingsList.LinkItem
|
||||||
|
to="/settings/automation-label"
|
||||||
|
label={_(msg`Automation label`)}>
|
||||||
|
<SettingsList.ItemIcon icon={RobotIcon} />
|
||||||
|
<SettingsList.ItemText>
|
||||||
|
<Trans>Automation label</Trans>
|
||||||
|
</SettingsList.ItemText>
|
||||||
|
{profile && (
|
||||||
|
<SettingsList.BadgeText>
|
||||||
|
{isBotAccount(profile) ? _(msg`On`) : _(msg`Off`)}
|
||||||
|
</SettingsList.BadgeText>
|
||||||
|
)}
|
||||||
|
</SettingsList.LinkItem>
|
||||||
<SettingsList.Divider />
|
<SettingsList.Divider />
|
||||||
<SettingsList.PressableItem
|
<SettingsList.PressableItem
|
||||||
label={_(msg`Export my data`)}
|
label={_(msg`Export my data`)}
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import {View} from 'react-native'
|
||||||
|
import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
||||||
|
import {RQKEY_ROOT as POST_FEED_RQKEY_ROOT} from '#/state/queries/post-feed'
|
||||||
|
import {
|
||||||
|
useProfileQuery,
|
||||||
|
useProfileUpdateMutation,
|
||||||
|
} from '#/state/queries/profile'
|
||||||
|
import {postThreadQueryKeyRoot} from '#/state/queries/usePostThread/types'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
|
import {atoms as a, platform, useTheme} from '#/alf'
|
||||||
|
import {BotBadge} from '#/components/BotBadge'
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import {Bot_Filled as RobotIcon} from '#/components/icons/Bot'
|
||||||
|
import * as Layout from '#/components/Layout'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useSimpleVerificationState} from '#/components/verification'
|
||||||
|
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
||||||
|
import {useAnalytics} from '#/analytics'
|
||||||
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
|
type Props = NativeStackScreenProps<
|
||||||
|
CommonNavigatorParams,
|
||||||
|
'AutomationLabelSettings'
|
||||||
|
>
|
||||||
|
export function AutomationLabelSettingsScreen({}: Props) {
|
||||||
|
const t = useTheme()
|
||||||
|
const ax = useAnalytics()
|
||||||
|
const {t: l} = useLingui()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||||
|
const updateProfile = useProfileUpdateMutation()
|
||||||
|
const verification = useSimpleVerificationState({profile})
|
||||||
|
|
||||||
|
const isBotLabeled =
|
||||||
|
profile?.labels?.some(l => l.val === 'bot' && l.src === profile.did) ??
|
||||||
|
false
|
||||||
|
const canToggle = profile && !updateProfile.isPending
|
||||||
|
|
||||||
|
const onToggle = () => {
|
||||||
|
if (!profile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let wasAdded = false
|
||||||
|
ax.metric('bot:label:toggle', {state: isBotLabeled ? 'remove' : 'add'})
|
||||||
|
updateProfile.mutate(
|
||||||
|
{
|
||||||
|
profile,
|
||||||
|
updates: existing => {
|
||||||
|
const labels: $Typed<ComAtprotoLabelDefs.SelfLabels> = bsky.validate(
|
||||||
|
existing.labels,
|
||||||
|
ComAtprotoLabelDefs.validateSelfLabels,
|
||||||
|
)
|
||||||
|
? existing.labels
|
||||||
|
: {
|
||||||
|
$type: 'com.atproto.label.defs#selfLabels',
|
||||||
|
values: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasLabel = labels.values.some(l => l.val === 'bot')
|
||||||
|
if (hasLabel) {
|
||||||
|
wasAdded = false
|
||||||
|
labels.values = labels.values.filter(l => l.val !== 'bot')
|
||||||
|
} else {
|
||||||
|
wasAdded = true
|
||||||
|
labels.values.push({val: 'bot'})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labels.values.length === 0) {
|
||||||
|
delete existing.labels
|
||||||
|
} else {
|
||||||
|
existing.labels = labels
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing
|
||||||
|
},
|
||||||
|
checkCommitted: res => {
|
||||||
|
const exists = !!res.data.labels?.some(l => l.val === 'bot')
|
||||||
|
return exists === wasAdded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess() {
|
||||||
|
queryClient.invalidateQueries({queryKey: [POST_FEED_RQKEY_ROOT]})
|
||||||
|
queryClient.invalidateQueries({queryKey: [postThreadQueryKeyRoot]})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Screen>
|
||||||
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Automation Label</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
<Layout.Content>
|
||||||
|
<View style={[a.p_xl, a.gap_xl]}>
|
||||||
|
{profile && (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.justify_center,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.rounded_lg,
|
||||||
|
a.border,
|
||||||
|
t.atoms.bg_contrast_50,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
{
|
||||||
|
height: 160,
|
||||||
|
paddingRight: 20, // helps visually center
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<UserAvatar size={42} avatar={profile.avatar} type="user" />
|
||||||
|
<View>
|
||||||
|
<View style={[a.flex_row, a.align_baseline]}>
|
||||||
|
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||||
|
<Text
|
||||||
|
emoji
|
||||||
|
style={[
|
||||||
|
a.text_xl,
|
||||||
|
a.font_semi_bold,
|
||||||
|
a.flex_shrink,
|
||||||
|
a.leading_tight,
|
||||||
|
]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
{profile.displayName || profile.handle}
|
||||||
|
</Text>
|
||||||
|
{verification.isVerified && (
|
||||||
|
<VerificationCheck
|
||||||
|
verifier={verification.role === 'verifier'}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<View style={{top: platform({ios: -1})}}>
|
||||||
|
<BotBadge profile={profile} alwaysShow width={17} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}
|
||||||
|
numberOfLines={1}>
|
||||||
|
@{profile.handle}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Text style={[a.text_2xl, a.font_bold]}>
|
||||||
|
<Trans>Add automation label to account</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
This label lets the world know that this account is automated.
|
||||||
|
If turned on, this label appears next to the account's name on
|
||||||
|
their profile and posts. It can be turned on or off at any time.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Toggle.Item
|
||||||
|
name="automation_label"
|
||||||
|
disabled={!canToggle || updateProfile.isPending}
|
||||||
|
value={isBotLabeled}
|
||||||
|
onChange={onToggle}
|
||||||
|
label={l`Show automation label`}
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_lg,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
t.atoms.bg_contrast_50,
|
||||||
|
]}>
|
||||||
|
<View style={[a.pr_xs]}>
|
||||||
|
<RobotIcon width={24} fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
</View>
|
||||||
|
<Toggle.LabelText style={[a.flex_1, a.text_md, a.font_medium]}>
|
||||||
|
<Trans>Show automation label</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
<Toggle.Platform />
|
||||||
|
</Toggle.Item>
|
||||||
|
</View>
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout.Screen>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -61,13 +61,9 @@ import * as Layout from '#/components/Layout'
|
|||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import {ID as PolicyUpdate202508} from '#/components/PolicyUpdateOverlay/updates/202508/config'
|
import {ID as PolicyUpdate202508} from '#/components/PolicyUpdateOverlay/updates/202508/config'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useFullVerificationState} from '#/components/verification'
|
|
||||||
import {
|
|
||||||
shouldShowVerificationCheckButton,
|
|
||||||
VerificationCheckButton,
|
|
||||||
} from '#/components/verification/VerificationCheckButton'
|
|
||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env'
|
import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
@@ -321,9 +317,6 @@ function ProfilePreview({
|
|||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const shadow = useProfileShadow(profile)
|
const shadow = useProfileShadow(profile)
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const verificationState = useFullVerificationState({
|
|
||||||
profile: shadow,
|
|
||||||
})
|
|
||||||
const {isActive: live} = useActorStatus(profile)
|
const {isActive: live} = useActorStatus(profile)
|
||||||
|
|
||||||
if (!moderationOpts) return null
|
if (!moderationOpts) return null
|
||||||
@@ -364,16 +357,16 @@ function ProfilePreview({
|
|||||||
]}>
|
]}>
|
||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
{shouldShowVerificationCheckButton(verificationState) && (
|
<ProfileBadges
|
||||||
<View
|
profile={shadow}
|
||||||
style={[
|
size="xl"
|
||||||
{
|
interactive
|
||||||
marginTop: platform({web: 8, ios: 8, android: 10}),
|
style={[
|
||||||
},
|
{
|
||||||
]}>
|
marginTop: platform({web: 8, ios: 8, android: 10}),
|
||||||
<VerificationCheckButton profile={shadow} size="lg" />
|
},
|
||||||
</View>
|
]}
|
||||||
)}
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
|
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||||
{sanitizeHandle(profile.handle, '@')}
|
{sanitizeHandle(profile.handle, '@')}
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ import {type ComposerOptsPostRef} from '#/state/shell/composer'
|
|||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {QuoteEmbed} from '#/components/Post/Embed'
|
import {QuoteEmbed} from '#/components/Post/Embed'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {parseEmbed} from '#/types/bsky/post'
|
import {parseEmbed} from '#/types/bsky/post'
|
||||||
|
|
||||||
export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
||||||
@@ -70,8 +69,6 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
|||||||
}
|
}
|
||||||
}, [embed])
|
}, [embed])
|
||||||
|
|
||||||
const verification = useSimpleVerificationState({profile: replyTo.author})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[
|
style={[
|
||||||
@@ -109,14 +106,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
|||||||
sanitizeHandle(replyTo.author.handle),
|
sanitizeHandle(replyTo.author.handle),
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges profile={replyTo.author} size="sm" style={[a.pl_xs]} />
|
||||||
<View style={[a.pl_xs]}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={14}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
<View style={[a.flex_row, a.gap_md]}>
|
<View style={[a.flex_row, a.gap_md]}>
|
||||||
<View style={[a.flex_1, a.flex_grow]}>
|
<View style={[a.flex_1, a.flex_grow]}>
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import {sanitizeHandle} from '#/lib/strings/handles'
|
|||||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, platform, useTheme} from '#/alf'
|
import {atoms as a, platform, useTheme} from '#/alf'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
|
|
||||||
export function Autocomplete({
|
export function Autocomplete({
|
||||||
prefix,
|
prefix,
|
||||||
@@ -77,7 +76,6 @@ function AutocompleteProfileCard({
|
|||||||
onPress: () => void
|
onPress: () => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const state = useSimpleVerificationState({profile})
|
|
||||||
const displayName = sanitizeDisplayName(
|
const displayName = sanitizeDisplayName(
|
||||||
profile.displayName || sanitizeHandle(profile.handle),
|
profile.displayName || sanitizeHandle(profile.handle),
|
||||||
)
|
)
|
||||||
@@ -115,19 +113,15 @@ function AutocompleteProfileCard({
|
|||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
{state.isVerified && (
|
<ProfileBadges
|
||||||
<View
|
profile={profile}
|
||||||
style={[
|
size="sm"
|
||||||
{
|
style={[
|
||||||
marginTop: platform({android: -2}),
|
{
|
||||||
},
|
marginTop: platform({android: -2}),
|
||||||
]}>
|
},
|
||||||
<VerificationCheck
|
]}
|
||||||
width={12}
|
/>
|
||||||
verifier={state.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -63,12 +63,11 @@ import {StarterPack} from '#/components/icons/StarterPack'
|
|||||||
import {VerifiedCheck} from '#/components/icons/VerifiedCheck'
|
import {VerifiedCheck} from '#/components/icons/VerifiedCheck'
|
||||||
import {InlineLinkText, Link} from '#/components/Link'
|
import {InlineLinkText, Link} from '#/components/Link'
|
||||||
import * as MediaPreview from '#/components/MediaPreview'
|
import * as MediaPreview from '#/components/MediaPreview'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
||||||
import {SubtleHover} from '#/components/SubtleHover'
|
import {SubtleHover} from '#/components/SubtleHover'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
const MAX_AUTHORS = 5
|
const MAX_AUTHORS = 5
|
||||||
@@ -173,9 +172,6 @@ let NotificationFeedItem = ({
|
|||||||
|
|
||||||
const niceTimestamp = niceDate(i18n, item.notification.indexedAt)
|
const niceTimestamp = niceDate(i18n, item.notification.indexedAt)
|
||||||
const firstAuthor = authors[0]
|
const firstAuthor = authors[0]
|
||||||
const firstAuthorVerification = useSimpleVerificationState({
|
|
||||||
profile: firstAuthor.profile,
|
|
||||||
})
|
|
||||||
const firstAuthorName = sanitizeDisplayName(
|
const firstAuthorName = sanitizeDisplayName(
|
||||||
firstAuthor.profile.displayName || firstAuthor.profile.handle,
|
firstAuthor.profile.displayName || firstAuthor.profile.handle,
|
||||||
)
|
)
|
||||||
@@ -246,24 +242,21 @@ let NotificationFeedItem = ({
|
|||||||
emoji
|
emoji
|
||||||
label={_(msg`Go to ${firstAuthorName}'s profile`)}>
|
label={_(msg`Go to ${firstAuthorName}'s profile`)}>
|
||||||
{forceLTR(firstAuthorName)}
|
{forceLTR(firstAuthorName)}
|
||||||
{firstAuthorVerification.showBadge && (
|
<ProfileBadges
|
||||||
<View
|
profile={firstAuthor.profile}
|
||||||
style={[
|
size="md"
|
||||||
a.relative,
|
style={[
|
||||||
{
|
a.relative,
|
||||||
paddingTop: platform({android: 2}),
|
{
|
||||||
marginBottom: platform({ios: -7}),
|
// weird stuff here
|
||||||
top: platform({web: 1}),
|
paddingTop: platform({android: 2}),
|
||||||
paddingLeft: 3,
|
marginBottom: platform({ios: -6}),
|
||||||
paddingRight: 2,
|
top: platform({web: 2}),
|
||||||
},
|
paddingLeft: 3,
|
||||||
]}>
|
paddingRight: 2,
|
||||||
<VerificationCheck
|
},
|
||||||
width={14}
|
]}
|
||||||
verifier={firstAuthorVerification.role === 'verifier'}
|
/>
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
</ProfileHoverCard>
|
</ProfileHoverCard>
|
||||||
)
|
)
|
||||||
@@ -1017,9 +1010,6 @@ function ExpandedAuthorsList({
|
|||||||
function ExpandedAuthorCard({author}: {author: Author}) {
|
function ExpandedAuthorCard({author}: {author: Author}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const verification = useSimpleVerificationState({
|
|
||||||
profile: author.profile,
|
|
||||||
})
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={author.profile.did}
|
key={author.profile.did}
|
||||||
@@ -1055,14 +1045,11 @@ function ExpandedAuthorCard({author}: {author: Author}) {
|
|||||||
author.profile.displayName || author.profile.handle,
|
author.profile.displayName || author.profile.handle,
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
<ProfileBadges
|
||||||
<View style={[a.pl_xs, a.self_center]}>
|
profile={author.profile}
|
||||||
<VerificationCheck
|
size="md"
|
||||||
width={14}
|
style={[a.pl_2xs, a.self_center]}
|
||||||
verifier={verification.role === 'verifier'}
|
/>
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ import {sanitizeHandle} from '#/lib/strings/handles'
|
|||||||
import {niceDate} from '#/lib/strings/time'
|
import {niceDate} from '#/lib/strings/time'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {unstableCacheProfileView} from '#/state/queries/profile'
|
import {unstableCacheProfileView} from '#/state/queries/profile'
|
||||||
import {atoms as a, platform, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {WebOnlyInlineLinkText} from '#/components/Link'
|
import {WebOnlyInlineLinkText} from '#/components/Link'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {IS_ANDROID} from '#/env'
|
import {IS_ANDROID} from '#/env'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
import {TimeElapsed} from './TimeElapsed'
|
import {TimeElapsed} from './TimeElapsed'
|
||||||
@@ -55,7 +54,6 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
}, [queryClient, author])
|
}, [queryClient, author])
|
||||||
|
|
||||||
const timestampLabel = niceDate(i18n, opts.timestamp)
|
const timestampLabel = niceDate(i18n, opts.timestamp)
|
||||||
const verification = useSimpleVerificationState({profile: author})
|
|
||||||
const {isActive: live} = useActorStatus(author)
|
const {isActive: live} = useActorStatus(author)
|
||||||
|
|
||||||
const MaybeLinkText = opts.linkDisabled ? Text : WebOnlyInlineLinkText
|
const MaybeLinkText = opts.linkDisabled ? Text : WebOnlyInlineLinkText
|
||||||
@@ -109,21 +107,11 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</MaybeLinkText>
|
</MaybeLinkText>
|
||||||
{verification.showBadge && (
|
<ProfileBadges
|
||||||
<View
|
profile={author}
|
||||||
style={[
|
size="sm"
|
||||||
a.pl_2xs,
|
style={[a.pl_2xs, a.self_center]}
|
||||||
a.self_center,
|
/>
|
||||||
{
|
|
||||||
marginTop: platform({web: 0, ios: 0, android: -1}),
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={platform({android: 13, default: 12})}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<MaybeLinkText
|
<MaybeLinkText
|
||||||
emoji
|
emoji
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
|
|||||||
@@ -53,9 +53,8 @@ import {
|
|||||||
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
|
UserCircle_Stroke2_Corner0_Rounded as UserCircle,
|
||||||
} from '#/components/icons/UserCircle'
|
} from '#/components/icons/UserCircle'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {useSimpleVerificationState} from '#/components/verification'
|
|
||||||
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_WEB} from '#/env'
|
||||||
import {useActorStatus} from '#/features/liveNow'
|
import {useActorStatus} from '#/features/liveNow'
|
||||||
|
|
||||||
@@ -71,7 +70,6 @@ let DrawerProfileCard = ({
|
|||||||
const {_, i18n} = useLingui()
|
const {_, i18n} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {data: profile} = useProfileQuery({did: account.did})
|
const {data: profile} = useProfileQuery({did: account.did})
|
||||||
const verification = useSimpleVerificationState({profile})
|
|
||||||
const {isActive: live} = useActorStatus(profile)
|
const {isActive: live} = useActorStatus(profile)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -97,17 +95,7 @@ let DrawerProfileCard = ({
|
|||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{profile?.displayName || account.handle}
|
{profile?.displayName || account.handle}
|
||||||
</Text>
|
</Text>
|
||||||
{verification.showBadge && (
|
{profile && <ProfileBadges profile={profile} size="lg" />}
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
top: 0,
|
|
||||||
}}>
|
|
||||||
<VerificationCheck
|
|
||||||
width={16}
|
|
||||||
verifier={verification.role === 'verifier'}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
emoji
|
emoji
|
||||||
|
|||||||
Reference in New Issue
Block a user