This commit is contained in:
vineyardbovines
2026-02-26 13:22:34 -05:00
parent 423f1a7ad7
commit d5d3076dda
5 changed files with 283 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
import {View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {Robot_Stroke2_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot'
import {Text} from '#/components/Typography'
export function BotAccountInfoDialog({
control,
}: {
control: Dialog.DialogControlProps
}) {
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<BotAccountInfoDialogInner control={control} />
</Dialog.Outer>
)
}
function BotAccountInfoDialogInner({
control,
}: {
control: Dialog.DialogControlProps
}) {
const {_} = useLingui()
const t = useTheme()
return (
<Dialog.ScrollableInner label={_(msg`Automated account`)}>
<View style={[a.align_center, a.gap_lg, a.py_md]}>
<RobotIcon width={48} fill={t.atoms.text_contrast_medium.color} />
<Text style={[a.text_lg, a.text_center, a.leading_snug]}>
<Trans>
This account has been marked as automated by its owner.
</Trans>
</Text>
<Button
label={_(msg`Okay`)}
onPress={() => control.close()}
color="primary"
size="large"
style={[a.w_full]}>
<ButtonText>
<Trans>Okay</Trans>
</ButtonText>
</Button>
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
)
}
+26
View File
@@ -0,0 +1,26 @@
import {View} from 'react-native'
import {type ComAtprotoLabelDefs} from '@atproto/api'
import {isBotAccount} from '#/lib/bots'
import {atoms as a, useTheme} from '#/alf'
import {Robot_Stroke2_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot'
export function BotBadge({
profile,
size = 12,
}: {
profile: {did: string; labels?: ComAtprotoLabelDefs.Label[]}
size?: number
}) {
const t = useTheme()
if (!isBotAccount(profile)) {
return null
}
return (
<View style={[a.pl_2xs, a.self_center]}>
<RobotIcon width={size} fill={t.atoms.text_contrast_medium.color} />
</View>
)
}
+9
View File
@@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Robot_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M9 2a1 1 0 0 1 1 1v1.07A7.002 7.002 0 0 1 16.93 11H18a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-.174A7.004 7.004 0 0 1 12 21a7.004 7.004 0 0 1-5.826-4H6a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1.07A7.002 7.002 0 0 1 14 4.07V3a1 1 0 0 1 1-1h-6Zm3 4a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm-2.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm5 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z',
})
export const Robot_Stroke2_Corner2_Rounded = createSinglePathSVG({
path: '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',
})
+9
View File
@@ -0,0 +1,9 @@
import {type ComAtprotoLabelDefs} from '@atproto/api'
export function isBotAccount(
profile: {did: string; labels?: ComAtprotoLabelDefs.Label[]},
): boolean {
return (
profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false
)
}
@@ -0,0 +1,183 @@
import React from 'react'
import {View} from 'react-native'
import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {isBotAccount} from '#/lib/bots'
import {type CommonNavigatorParams} from '#/lib/routes/types'
import {
useProfileQuery,
useProfileUpdateMutation,
} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import {BotBadge} from '#/components/BotBadge'
import * as Toggle from '#/components/forms/Toggle'
import {Robot_Stroke2_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot'
import * as Layout from '#/components/Layout'
import {Text} from '#/components/Typography'
import {VerificationCheck} from '#/components/verification/VerificationCheck'
import * as bsky from '#/types/bsky'
import {getVerificationState} from '../../../bskyembed/src/util/verification-state'
type Props = NativeStackScreenProps<
CommonNavigatorParams,
'AutomationLabelSettings'
>
export function AutomationLabelSettingsScreen({}: Props) {
const t = useTheme()
const {_} = useLingui()
const {currentAccount} = useSession()
const {data: profile} = useProfileQuery({did: currentAccount?.did})
const updateProfile = useProfileUpdateMutation()
const verification = getVerificationState({profile: profile})
const isBotLabeled =
profile?.labels?.some(l => l.val === 'bot' && l.src === profile.did) ??
false
const canToggle = profile && !updateProfile.isPending
const onToggle = React.useCallback(() => {
if (!profile) {
return
}
let wasAdded = false
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
},
})
}, [updateProfile, profile])
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.p_5xl,
a.rounded_lg,
t.atoms.bg_contrast_50,
a.border,
t.atoms.border_contrast_low,
]}>
<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]}
numberOfLines={1}>
{profile.displayName || profile.handle}
</Text>
{verification.isVerified && (
<VerificationCheck
verifier={verification.role === 'verifier'}
size="xs"
/>
)}
{isBotAccount(profile) && (
<RobotIcon
width={15}
fill={t.atoms.text_contrast_medium.color}
/>
)}
</View>
{/* <BotBadge profile={profile} /> */}
</View>
<Text
style={[a.text_md, 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={_(msg`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>
)
}