From 4ad5a55fa672839702c93b5fb2fb2f4bb439453d Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 26 Feb 2026 13:22:34 -0500 Subject: [PATCH 1/7] wip --- src/components/BotAccountInfoDialog.tsx | 56 ++++++ src/components/BotBadge.tsx | 26 +++ src/components/icons/Robot.tsx | 9 + src/lib/bots.ts | 9 + .../Settings/AutomationLabelSettings.tsx | 183 ++++++++++++++++++ 5 files changed, 283 insertions(+) create mode 100644 src/components/BotAccountInfoDialog.tsx create mode 100644 src/components/BotBadge.tsx create mode 100644 src/components/icons/Robot.tsx create mode 100644 src/lib/bots.ts create mode 100644 src/screens/Settings/AutomationLabelSettings.tsx diff --git a/src/components/BotAccountInfoDialog.tsx b/src/components/BotAccountInfoDialog.tsx new file mode 100644 index 0000000000..5114fd3078 --- /dev/null +++ b/src/components/BotAccountInfoDialog.tsx @@ -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 ( + + + + + ) +} + +function BotAccountInfoDialogInner({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const {_} = useLingui() + const t = useTheme() + + return ( + + + + + + This account has been marked as automated by its owner. + + + + + + + ) +} diff --git a/src/components/BotBadge.tsx b/src/components/BotBadge.tsx new file mode 100644 index 0000000000..a0421015ba --- /dev/null +++ b/src/components/BotBadge.tsx @@ -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 ( + + + + ) +} diff --git a/src/components/icons/Robot.tsx b/src/components/icons/Robot.tsx new file mode 100644 index 0000000000..6bd23acb57 --- /dev/null +++ b/src/components/icons/Robot.tsx @@ -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', +}) diff --git a/src/lib/bots.ts b/src/lib/bots.ts new file mode 100644 index 0000000000..7e96659510 --- /dev/null +++ b/src/lib/bots.ts @@ -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 + ) +} diff --git a/src/screens/Settings/AutomationLabelSettings.tsx b/src/screens/Settings/AutomationLabelSettings.tsx new file mode 100644 index 0000000000..7da10e48bf --- /dev/null +++ b/src/screens/Settings/AutomationLabelSettings.tsx @@ -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 = 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 ( + + + + + + Automation Label + + + + + + + {profile && ( + + + + + + + {profile.displayName || profile.handle} + + {verification.isVerified && ( + + )} + {isBotAccount(profile) && ( + + )} + + {/* */} + + + @{profile.handle} + + + + )} + + + Add automation label to account + + + + 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. + + + + + + + + + Show automation label + + + + + + + ) +} From 3dbdca5939021cf68888b5bcca650be8fb429b64 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Tue, 3 Mar 2026 09:02:38 -0500 Subject: [PATCH 2/7] wip --- src/screens/Settings/AutomationLabelSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Settings/AutomationLabelSettings.tsx b/src/screens/Settings/AutomationLabelSettings.tsx index 7da10e48bf..23f99aa291 100644 --- a/src/screens/Settings/AutomationLabelSettings.tsx +++ b/src/screens/Settings/AutomationLabelSettings.tsx @@ -132,7 +132,7 @@ export function AutomationLabelSettingsScreen({}: Props) { /> )} - {/* */} + Date: Tue, 3 Mar 2026 16:51:49 -0500 Subject: [PATCH 3/7] ui wip --- src/Navigation.tsx | 9 +++ src/components/AccountList.tsx | 6 ++ src/components/BotAccountInfoDialog.tsx | 14 ++-- src/components/BotBadge.tsx | 75 +++++++++++++++++-- .../PostControls/ShareMenu/RecentChats.tsx | 2 + src/components/ProfileCard.tsx | 3 + src/components/ProfileHoverCard/index.web.tsx | 2 + src/components/dms/MessagesListHeader.tsx | 2 + src/components/icons/Robot.tsx | 11 ++- src/components/moderation/LabelsOnMe.tsx | 6 +- src/lib/routes/types.ts | 1 + src/routes.ts | 1 + .../Messages/components/ChatListItem.tsx | 2 + .../components/ThreadItemAnchor.tsx | 2 + .../Profile/Header/ProfileHeaderStandard.tsx | 4 + .../Search/components/SearchHistory.tsx | 2 + src/screens/Settings/AccountSettings.tsx | 17 +++++ .../Settings/AutomationLabelSettings.tsx | 15 +--- src/screens/Settings/Settings.tsx | 9 +++ src/view/com/composer/ComposerReplyTo.tsx | 2 + .../text-input/mobile/Autocomplete.tsx | 2 + .../notifications/NotificationFeedItem.tsx | 3 + src/view/com/util/PostMeta.tsx | 11 +++ src/view/shell/Drawer.tsx | 2 + 24 files changed, 176 insertions(+), 27 deletions(-) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 03ff43d739..156b7986d6 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -99,6 +99,7 @@ import {SearchScreen} from '#/screens/Search' import {AboutSettingsScreen} from '#/screens/Settings/AboutSettings' import {AccessibilitySettingsScreen} from '#/screens/Settings/AccessibilitySettings' import {AccountSettingsScreen} from '#/screens/Settings/AccountSettings' +import {AutomationLabelSettingsScreen} from '#/screens/Settings/AutomationLabelSettings' import {ActivityPrivacySettingsScreen} from '#/screens/Settings/ActivityPrivacySettings' import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings' import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings' @@ -403,6 +404,14 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { requireAuth: true, }} /> + AutomationLabelSettingsScreen} + options={{ + title: title(msg`Automation Label`), + requireAuth: true, + }} + /> PrivacyAndSecuritySettingsScreen} diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index ad0f44809d..2e5e39aaa4 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -12,6 +12,7 @@ import {useProfilesQuery} from '#/state/queries/profile' import {type SessionAccount, useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' +import {BotBadge} from '#/components/BotBadge' import {Button} from '#/components/Button' import {CheckThick_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron' @@ -172,6 +173,11 @@ function AccountItem({ /> )} + {profile && ( + + + + )} - - - - - This account has been marked as automated by its owner. - + + + + + + This account has been marked as automated by its owner + + ) } diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index 3ba2682239..07943d073c 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -15,6 +15,7 @@ import {useListConvosQuery} from '#/state/queries/messages/list-conversations' import {useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, tokens, useTheme} from '#/alf' +import {BotBadge} from '#/components/BotBadge' import {Button} from '#/components/Button' import {useDialogContext} from '#/components/Dialog' import {Text} from '#/components/Typography' @@ -149,6 +150,7 @@ function RecentChatItem({ /> )} + ) diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 1cbfc2b50d..54906e33d5 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -30,6 +30,7 @@ import { useTheme, type ViewStyleProp, } from '#/alf' +import {BotBadge} from '#/components/BotBadge' import { Button, ButtonIcon, @@ -272,6 +273,7 @@ function InlineNameAndHandle({ /> )} + )} + ) } diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 8586607e52..938bdf330c 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -23,6 +23,7 @@ import {formatCount} from '#/view/com/util/numeric/format' import {UserAvatar} from '#/view/com/util/UserAvatar' import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle' import {atoms as a, useTheme} from '#/alf' +import {BotBadge} from '#/components/BotBadge' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {useFollowMethods} from '#/components/hooks/useFollowMethods' import {useRichText} from '#/components/hooks/useRichText' @@ -541,6 +542,7 @@ function Inner({ /> )} + diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index afdeee545e..1f87c77371 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -15,6 +15,7 @@ import {isConvoActive, useConvo} from '#/state/messages/convo' import {type ConvoItem} from '#/state/messages/convo/types' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, web} from '#/alf' +import {BotBadge} from '#/components/BotBadge' import {ConvoMenu} from '#/components/dms/ConvoMenu' import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2' import * as Layout from '#/components/Layout' @@ -169,6 +170,7 @@ function HeaderReady({ /> )} + {!isDeletedAccount && ( !l.val.startsWith('!')) + labels = labels.filter( + l => + !l.val.startsWith('!') && + !(l.val === 'bot' && l.src === currentAccount.did), + ) if (!labels.length) { return null } diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 0a3a0d5458..5bb7265709 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -50,6 +50,7 @@ export type CommonNavigatorParams = { AccessibilitySettings: undefined AppearanceSettings: undefined AccountSettings: undefined + AutomationLabelSettings: undefined PrivacyAndSecuritySettings: undefined ActivityPrivacySettings: undefined ContentAndMediaSettings: undefined diff --git a/src/routes.ts b/src/routes.ts index f325539c71..75eda461c8 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -50,6 +50,7 @@ export const router = new Router({ AppearanceSettings: '/settings/appearance', SavedFeeds: '/settings/saved-feeds', AccountSettings: '/settings/account', + AutomationLabelSettings: '/settings/automation-label', PrivacyAndSecuritySettings: '/settings/privacy-and-security', ActivityPrivacySettings: '/settings/privacy-and-security/activity', ContentAndMediaSettings: '/settings/content-and-media', diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index f09cf23007..30985b21f4 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -31,6 +31,7 @@ import {TimeElapsed} from '#/view/com/util/TimeElapsed' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import * as tokens from '#/alf/tokens' +import {BotBadge} from '#/components/BotBadge' import {useDialogControl} from '#/components/Dialog' import {ConvoMenu} from '#/components/dms/ConvoMenu' import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' @@ -422,6 +423,7 @@ function ChatListItemReady({ /> )} + {lastMessageSentAt && ( diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index 1531784aee..80ebec2b8c 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -34,6 +34,7 @@ import { REPLY_LINE_WIDTH, } from '#/screens/PostThread/const' import {atoms as a, useTheme} from '#/alf' +import {BotBadge} from '#/components/BotBadge' import {Button} from '#/components/Button' import {DebugFieldDisplay} from '#/components/DebugFieldDisplay' import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock' @@ -364,6 +365,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ + + + + diff --git a/src/screens/Search/components/SearchHistory.tsx b/src/screens/Search/components/SearchHistory.tsx index 5b463ae75d..6cae572c93 100644 --- a/src/screens/Search/components/SearchHistory.tsx +++ b/src/screens/Search/components/SearchHistory.tsx @@ -10,6 +10,7 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts' import {UserAvatar} from '#/view/com/util/UserAvatar' import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' import {atoms as a} from '#/alf' +import {BotBadge} from '#/components/BotBadge' import {Button, ButtonIcon} from '#/components/Button' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import * as Layout from '#/components/Layout' @@ -173,6 +174,7 @@ function RecentProfileItem({ /> )} + + {isMe && ( + + )} diff --git a/src/components/BotBadge.tsx b/src/components/BotBadge.tsx index 80b84559f6..41ef567688 100644 --- a/src/components/BotBadge.tsx +++ b/src/components/BotBadge.tsx @@ -3,13 +3,13 @@ import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' -import {useAnalytics} from '#/analytics' import {isBotAccount} from '#/lib/bots' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {BotAccountInfoDialog} from '#/components/BotAccountInfoDialog' import {Button} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {Robot_Filled_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' +import {useAnalytics} from '#/analytics' export function BotBadge({ profile, @@ -36,9 +36,11 @@ export function BotBadge({ export function BotBadgeButton({ profile, size, + isMe, }: { profile: {did: string; labels?: ComAtprotoLabelDefs.Label[]} size: 'lg' | 'md' | 'sm' + isMe?: boolean }) { const t = useTheme() const ax = useAnalytics() @@ -86,7 +88,7 @@ export function BotBadgeButton({ )} - + ) } diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 3eace8132b..12b02423ad 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -147,7 +147,7 @@ let ProfileHeaderStandard = ({ - + diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 526024b826..1e9c07fad4 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -381,7 +381,7 @@ function ProfilePreview({ marginTop: platform({web: 8, ios: 8, android: 10}), }, ]}> - + From 13486b0fca5dcd5230641da19267afd5c03ae63a Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 5 Mar 2026 09:54:41 -0500 Subject: [PATCH 6/7] minor styling fixes --- src/components/BotAccountInfoDialog.tsx | 177 ++++++++++++++---- src/components/BotBadge.tsx | 33 +++- src/components/ProfileCard.tsx | 13 +- .../Settings/AutomationLabelSettings.tsx | 73 +++++--- 4 files changed, 225 insertions(+), 71 deletions(-) diff --git a/src/components/BotAccountInfoDialog.tsx b/src/components/BotAccountInfoDialog.tsx index 8ca56f282c..36dde27e38 100644 --- a/src/components/BotAccountInfoDialog.tsx +++ b/src/components/BotAccountInfoDialog.tsx @@ -1,9 +1,9 @@ -import {View} from 'react-native' +import {Modal, Pressable, 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 {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Robot_Filled_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' @@ -12,58 +12,132 @@ import {navigate} from '#/Navigation' export function BotAccountInfoDialog({ control, - isMe, }: { control: Dialog.DialogControlProps - isMe?: boolean }) { return ( - + ) } -function BotAccountInfoDialogInner({ - control, - isMe, +export function BotAccountOwnAlert({ + visible, + onClose, }: { - control: Dialog.DialogControlProps - isMe?: boolean + visible: boolean + onClose: (cb?: () => void) => void }) { const {_} = useLingui() const t = useTheme() return ( - - - - - - - {isMe ? ( + onClose()} + accessibilityLabel={_(msg`Automated account`)} + accessibilityViewIsModal> + onClose()} + style={[ + a.flex_1, + a.justify_center, + a.align_center, + a.px_xl, + {backgroundColor: 'rgba(0,0,0,0.5)'}, + ]}> + e.stopPropagation()} + style={[ + t.atoms.bg, + a.w_full, + {borderRadius: 48}, + a.p_2xl, + {maxWidth: 280}, + ]}> + + + + + + + + You have marked this account as automated. You can remove it at + any time from your account settings. + + + + + + + + + + ) +} + +export function BotAccountOwnAlertWeb({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const {_} = useLingui() + const t = useTheme() + + return ( + + + + + + + You have marked this account as automated. You can remove it at any time from your account settings. - ) : ( - - This account has been marked as automated by its owner - - )} - - - {isMe && ( + + - )} + + + + + ) +} + +function BotAccountInfoDialogInner({ + control, +}: { + control: Dialog.DialogControlProps +}) { + const {_} = useLingui() + const t = useTheme() + + return ( + + + + + + + This account has been marked as automated by its owner + + diff --git a/src/components/BotBadge.tsx b/src/components/BotBadge.tsx index 41ef567688..0b9e3b4b2a 100644 --- a/src/components/BotBadge.tsx +++ b/src/components/BotBadge.tsx @@ -1,15 +1,21 @@ +import {useState} from 'react' import {View} from 'react-native' import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {useAnalytics} from '#/analytics' +import {IS_NATIVE} from '#/env' import {isBotAccount} from '#/lib/bots' import {atoms as a, useBreakpoints, useTheme} from '#/alf' -import {BotAccountInfoDialog} from '#/components/BotAccountInfoDialog' +import { + BotAccountInfoDialog, + BotAccountOwnAlert, + BotAccountOwnAlertWeb, +} from '#/components/BotAccountInfoDialog' import {Button} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {Robot_Filled_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' -import {useAnalytics} from '#/analytics' export function BotBadge({ profile, @@ -47,6 +53,7 @@ export function BotBadgeButton({ const {_} = useLingui() const {gtPhone} = useBreakpoints() const control = useDialogControl() + const [alertVisible, setAlertVisible] = useState(false) if (!isBotAccount(profile)) { return null @@ -59,6 +66,8 @@ export function BotBadgeButton({ dimensions = 14 } + const useNativeAlert = isMe && IS_NATIVE + return ( <> - + {useNativeAlert ? ( + { + setAlertVisible(false) + cb?.() + }} + /> + ) : isMe ? ( + + ) : ( + + )} ) } diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index 54906e33d5..0e489c5aac 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -273,7 +273,14 @@ function InlineNameAndHandle({ /> )} - + + + )} - + + + ) } diff --git a/src/screens/Settings/AutomationLabelSettings.tsx b/src/screens/Settings/AutomationLabelSettings.tsx index 89ec0ac273..4874f845dc 100644 --- a/src/screens/Settings/AutomationLabelSettings.tsx +++ b/src/screens/Settings/AutomationLabelSettings.tsx @@ -1,6 +1,7 @@ import React from 'react' import {View} from 'react-native' import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api' +import {useQueryClient} from '@tanstack/react-query' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -8,6 +9,8 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useAnalytics} from '#/analytics' import {type CommonNavigatorParams} from '#/lib/routes/types' +import {RQKEY_ROOT as POST_FEED_RQKEY_ROOT} from '#/state/queries/post-feed' +import {postThreadQueryKeyRoot} from '#/state/queries/usePostThread/types' import { useProfileQuery, useProfileUpdateMutation, @@ -32,6 +35,7 @@ export function AutomationLabelSettingsScreen({}: Props) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() + const queryClient = useQueryClient() const {currentAccount} = useSession() const {data: profile} = useProfileQuery({did: currentAccount?.did}) const updateProfile = useProfileUpdateMutation() @@ -48,41 +52,50 @@ export function AutomationLabelSettingsScreen({}: Props) { } let wasAdded = false ax.metric('bot:label:toggle', {state: isBotLabeled ? 'remove' : 'add'}) - updateProfile.mutate({ - profile, - updates: existing => { - const labels: $Typed = bsky.validate( - existing.labels, - ComAtprotoLabelDefs.validateSelfLabels, - ) - ? existing.labels - : { - $type: 'com.atproto.label.defs#selfLabels', - values: [], - } + updateProfile.mutate( + { + profile, + updates: existing => { + const labels: $Typed = + 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'}) - } + 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 - } + if (labels.values.length === 0) { + delete existing.labels + } else { + existing.labels = labels + } - return existing + return existing + }, + checkCommitted: res => { + const exists = !!res.data.labels?.some(l => l.val === 'bot') + return exists === wasAdded + }, }, - 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]}) + }, }, - }) + ) }, [updateProfile, profile]) return ( From 3e413e151d4a2e3cc1f621267cb1cf276c74472b Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 5 Mar 2026 12:59:14 -0500 Subject: [PATCH 7/7] use prompt component --- src/components/BotAccountInfoDialog.tsx | 157 +++++------------------- src/components/BotBadge.tsx | 26 +--- 2 files changed, 35 insertions(+), 148 deletions(-) diff --git a/src/components/BotAccountInfoDialog.tsx b/src/components/BotAccountInfoDialog.tsx index 36dde27e38..88db5574a1 100644 --- a/src/components/BotAccountInfoDialog.tsx +++ b/src/components/BotAccountInfoDialog.tsx @@ -1,12 +1,13 @@ -import {Modal, Pressable, View} from 'react-native' +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, web} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Robot_Filled_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' +import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {navigate} from '#/Navigation' @@ -24,88 +25,6 @@ export function BotAccountInfoDialog({ } export function BotAccountOwnAlert({ - visible, - onClose, -}: { - visible: boolean - onClose: (cb?: () => void) => void -}) { - const {_} = useLingui() - const t = useTheme() - - return ( - onClose()} - accessibilityLabel={_(msg`Automated account`)} - accessibilityViewIsModal> - onClose()} - style={[ - a.flex_1, - a.justify_center, - a.align_center, - a.px_xl, - {backgroundColor: 'rgba(0,0,0,0.5)'}, - ]}> - e.stopPropagation()} - style={[ - t.atoms.bg, - a.w_full, - {borderRadius: 48}, - a.p_2xl, - {maxWidth: 280}, - ]}> - - - - - - - - You have marked this account as automated. You can remove it at - any time from your account settings. - - - - - - - - - - ) -} - -export function BotAccountOwnAlertWeb({ control, }: { control: Dialog.DialogControlProps @@ -114,49 +33,35 @@ export function BotAccountOwnAlertWeb({ const t = useTheme() return ( - - - - - - - - - You have marked this account as automated. You can remove it at - any time from your account settings. - - - - + + + + - - - + + + You have marked this account as automated. You can remove it at any + time from your account settings. + + + + + {}} color="primary" /> + + + ) } diff --git a/src/components/BotBadge.tsx b/src/components/BotBadge.tsx index 0b9e3b4b2a..552ab149c3 100644 --- a/src/components/BotBadge.tsx +++ b/src/components/BotBadge.tsx @@ -1,21 +1,18 @@ -import {useState} from 'react' import {View} from 'react-native' import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' -import {useAnalytics} from '#/analytics' -import {IS_NATIVE} from '#/env' import {isBotAccount} from '#/lib/bots' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import { BotAccountInfoDialog, BotAccountOwnAlert, - BotAccountOwnAlertWeb, } from '#/components/BotAccountInfoDialog' import {Button} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {Robot_Filled_Corner2_Rounded as RobotIcon} from '#/components/icons/Robot' +import {useAnalytics} from '#/analytics' export function BotBadge({ profile, @@ -53,7 +50,6 @@ export function BotBadgeButton({ const {_} = useLingui() const {gtPhone} = useBreakpoints() const control = useDialogControl() - const [alertVisible, setAlertVisible] = useState(false) if (!isBotAccount(profile)) { return null @@ -66,8 +62,6 @@ export function BotBadgeButton({ dimensions = 14 } - const useNativeAlert = isMe && IS_NATIVE - return ( <> - {useNativeAlert ? ( - { - setAlertVisible(false) - cb?.() - }} - /> - ) : isMe ? ( - + {isMe ? ( + ) : ( )}