about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx6
-rw-r--r--src/view/com/post/Post.tsx22
-rw-r--r--src/view/com/posts/PostFeedItem.tsx9
-rw-r--r--src/view/com/profile/ProfileMenu.tsx29
-rw-r--r--src/view/com/util/forms/PostDropdownBtn.tsx118
-rw-r--r--src/view/com/util/forms/PostDropdownBtnMenuItems.tsx872
-rw-r--r--src/view/com/util/post-ctrls/PostCtrls.tsx394
-rw-r--r--src/view/com/util/post-ctrls/RepostButton.tsx221
-rw-r--r--src/view/com/util/post-ctrls/RepostButton.web.tsx147
9 files changed, 39 insertions, 1779 deletions
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 3925ce9bd..82852aa62 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -43,7 +43,6 @@ import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
 import {Link, TextLink} from '#/view/com/util/Link'
 import {formatCount} from '#/view/com/util/numeric/format'
-import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
 import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
 import {PostMeta} from '#/view/com/util/PostMeta'
 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
@@ -60,6 +59,7 @@ import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
 import {PostAlerts} from '#/components/moderation/PostAlerts'
 import {PostHider} from '#/components/moderation/PostHider'
 import {type AppModerationCause} from '#/components/Pills'
+import {PostControls} from '#/components/PostControls'
 import * as Prompt from '#/components/Prompt'
 import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
@@ -494,7 +494,7 @@ let PostThreadItemLoaded = ({
                   marginLeft: -5,
                 },
               ]}>
-              <PostCtrls
+              <PostControls
                 big
                 post={post}
                 record={record}
@@ -642,7 +642,7 @@ let PostThreadItemLoaded = ({
                   />
                 </View>
               )}
-              <PostCtrls
+              <PostControls
                 post={post}
                 record={record}
                 richText={richText}
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 03463f977..1a48d64d8 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -27,21 +27,21 @@ import {
 import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {precacheProfile} from '#/state/queries/profile'
 import {useSession} from '#/state/session'
+import {Link, TextLink} from '#/view/com/util/Link'
+import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
+import {PostMeta} from '#/view/com/util/PostMeta'
+import {Text} from '#/view/com/util/text/Text'
+import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
+import {UserInfoText} from '#/view/com/util/UserInfoText'
 import {atoms as a} from '#/alf'
+import {ContentHider} from '#/components/moderation/ContentHider'
+import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
+import {PostAlerts} from '#/components/moderation/PostAlerts'
+import {PostControls} from '#/components/PostControls'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
 import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
 import * as bsky from '#/types/bsky'
-import {ContentHider} from '../../../components/moderation/ContentHider'
-import {LabelsOnMyPost} from '../../../components/moderation/LabelsOnMe'
-import {PostAlerts} from '../../../components/moderation/PostAlerts'
-import {Link, TextLink} from '../util/Link'
-import {PostCtrls} from '../util/post-ctrls/PostCtrls'
-import {PostEmbeds, PostEmbedViewContext} from '../util/post-embeds'
-import {PostMeta} from '../util/PostMeta'
-import {Text} from '../util/text/Text'
-import {PreviewableUserAvatar} from '../util/UserAvatar'
-import {UserInfoText} from '../util/UserInfoText'
 
 export function Post({
   post,
@@ -255,7 +255,7 @@ function PostInner({
               />
             ) : null}
           </ContentHider>
-          <PostCtrls
+          <PostControls
             post={post}
             record={record}
             richText={richText}
diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx
index 2cc749404..3735bbb5a 100644
--- a/src/view/com/posts/PostFeedItem.tsx
+++ b/src/view/com/posts/PostFeedItem.tsx
@@ -37,7 +37,7 @@ import {precacheProfile} from '#/state/queries/profile'
 import {useSession} from '#/state/session'
 import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
 import {FeedNameText} from '#/view/com/util/FeedInfoText'
-import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
+import {Link, TextLink, TextLinkOnWebOnly} from '#/view/com/util/Link'
 import {PostEmbeds, PostEmbedViewContext} from '#/view/com/util/post-embeds'
 import {PostMeta} from '#/view/com/util/PostMeta'
 import {Text} from '#/view/com/util/text/Text'
@@ -49,11 +49,12 @@ import {ContentHider} from '#/components/moderation/ContentHider'
 import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
 import {PostAlerts} from '#/components/moderation/PostAlerts'
 import {type AppModerationCause} from '#/components/Pills'
+import {PostControls} from '#/components/PostControls'
+import {DiscoverDebug} from '#/components/PostControls/DiscoverDebug'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
 import {RichText} from '#/components/RichText'
 import {SubtleWebHover} from '#/components/SubtleWebHover'
 import * as bsky from '#/types/bsky'
-import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
 
 interface FeedItemProps {
   record: AppBskyFeedPost.Record
@@ -439,7 +440,7 @@ let FeedItemInner = ({
             post={post}
             threadgateRecord={threadgateRecord}
           />
-          <PostCtrls
+          <PostControls
             post={post}
             record={record}
             richText={richText}
@@ -451,6 +452,8 @@ let FeedItemInner = ({
             onShowLess={onShowLess}
           />
         </View>
+
+        <DiscoverDebug feedContext={feedContext} />
       </View>
     </Link>
   )
diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx
index f1fd237ec..d18ba12c1 100644
--- a/src/view/com/profile/ProfileMenu.tsx
+++ b/src/view/com/profile/ProfileMenu.tsx
@@ -12,6 +12,7 @@ import {type NavigationProp} from '#/lib/routes/types'
 import {shareText, shareUrl} from '#/lib/sharing'
 import {toShareUrl} from '#/lib/strings/url-helpers'
 import {logger} from '#/logger'
+import {isWeb} from '#/platform/detection'
 import {type Shadow} from '#/state/cache/types'
 import {useModalControls} from '#/state/modals'
 import {
@@ -26,9 +27,11 @@ import {EventStopper} from '#/view/com/util/EventStopper'
 import * as Toast from '#/view/com/util/Toast'
 import {Button, ButtonIcon} from '#/components/Button'
 import {useDialogControl} from '#/components/Dialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
-import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck'
-import {CircleX_Stroke2_Corner0_Rounded as CircleX} from '#/components/icons/CircleX'
+import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
+import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
+import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck'
+import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
+import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
 import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
 import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
 import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle'
@@ -236,7 +239,9 @@ let ProfileMenu = ({
           <Menu.Group>
             <Menu.Item
               testID="profileHeaderDropdownShareBtn"
-              label={_(msg`Share`)}
+              label={
+                isWeb ? _(msg`Copy link to profile`) : _(msg`Share via...`)
+              }
               onPress={() => {
                 if (showLoggedOutWarning) {
                   loggedOutWarningPromptControl.open()
@@ -245,9 +250,13 @@ let ProfileMenu = ({
                 }
               }}>
               <Menu.ItemText>
-                <Trans>Share</Trans>
+                {isWeb ? (
+                  <Trans>Copy link to profile</Trans>
+                ) : (
+                  <Trans>Share via...</Trans>
+                )}
               </Menu.ItemText>
-              <Menu.ItemIcon icon={Share} />
+              <Menu.ItemIcon icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} />
             </Menu.Item>
             <Menu.Item
               testID="profileHeaderDropdownSearchBtn"
@@ -329,7 +338,7 @@ let ProfileMenu = ({
                       <Menu.ItemText>
                         <Trans>Remove verification</Trans>
                       </Menu.ItemText>
-                      <Menu.ItemIcon icon={CircleX} />
+                      <Menu.ItemIcon icon={CircleXIcon} />
                     </Menu.Item>
                   ) : (
                     <Menu.Item
@@ -339,7 +348,7 @@ let ProfileMenu = ({
                       <Menu.ItemText>
                         <Trans>Verify account</Trans>
                       </Menu.ItemText>
-                      <Menu.ItemIcon icon={CircleCheck} />
+                      <Menu.ItemIcon icon={CircleCheckIcon} />
                     </Menu.Item>
                   ))}
                 {!isSelf && (
@@ -414,7 +423,7 @@ let ProfileMenu = ({
                   <Menu.ItemText>
                     <Trans>Copy at:// URI</Trans>
                   </Menu.ItemText>
-                  <Menu.ItemIcon icon={Share} />
+                  <Menu.ItemIcon icon={ClipboardIcon} />
                 </Menu.Item>
                 <Menu.Item
                   testID="profileHeaderDropdownShareDIDBtn"
@@ -423,7 +432,7 @@ let ProfileMenu = ({
                   <Menu.ItemText>
                     <Trans>Copy DID</Trans>
                   </Menu.ItemText>
-                  <Menu.ItemIcon icon={Share} />
+                  <Menu.ItemIcon icon={ClipboardIcon} />
                 </Menu.Item>
               </Menu.Group>
             </>
diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx
deleted file mode 100644
index 57ee95e31..000000000
--- a/src/view/com/util/forms/PostDropdownBtn.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import {memo, useMemo, useState} from 'react'
-import {
-  Pressable,
-  type PressableProps,
-  type StyleProp,
-  type ViewStyle,
-} from 'react-native'
-import {
-  type AppBskyFeedDefs,
-  type AppBskyFeedPost,
-  type AppBskyFeedThreadgate,
-  type RichText as RichTextAPI,
-} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import type React from 'react'
-
-import {useTheme} from '#/lib/ThemeContext'
-import {type Shadow} from '#/state/cache/post-shadow'
-import {atoms as a, useTheme as useAlf} from '#/alf'
-import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
-import {useMenuControl} from '#/components/Menu'
-import * as Menu from '#/components/Menu'
-import {EventStopper} from '../EventStopper'
-import {PostDropdownMenuItems} from './PostDropdownBtnMenuItems'
-
-let PostDropdownBtn = ({
-  testID,
-  post,
-  postFeedContext,
-  postReqId,
-  record,
-  richText,
-  style,
-  hitSlop,
-  size,
-  timestamp,
-  threadgateRecord,
-  onShowLess,
-}: {
-  testID: string
-  post: Shadow<AppBskyFeedDefs.PostView>
-  postFeedContext: string | undefined
-  postReqId: string | undefined
-  record: AppBskyFeedPost.Record
-  richText: RichTextAPI
-  style?: StyleProp<ViewStyle>
-  hitSlop?: PressableProps['hitSlop']
-  size?: 'lg' | 'md' | 'sm'
-  timestamp: string
-  threadgateRecord?: AppBskyFeedThreadgate.Record
-  onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
-}): React.ReactNode => {
-  const theme = useTheme()
-  const alf = useAlf()
-  const {_} = useLingui()
-  const defaultCtrlColor = theme.palette.default.postCtrl
-  const menuControl = useMenuControl()
-  const [hasBeenOpen, setHasBeenOpen] = useState(false)
-  const lazyMenuControl = useMemo(
-    () => ({
-      ...menuControl,
-      open() {
-        setHasBeenOpen(true)
-        // HACK. We need the state update to be flushed by the time
-        // menuControl.open() fires but RN doesn't expose flushSync.
-        setTimeout(menuControl.open)
-      },
-    }),
-    [menuControl, setHasBeenOpen],
-  )
-  return (
-    <EventStopper onKeyDown={false}>
-      <Menu.Root control={lazyMenuControl}>
-        <Menu.Trigger label={_(msg`Open post options menu`)}>
-          {({props, state}) => {
-            return (
-              <Pressable
-                {...props}
-                hitSlop={hitSlop}
-                testID={testID}
-                style={[
-                  style,
-                  a.rounded_full,
-                  (state.hovered || state.pressed) && [
-                    alf.atoms.bg_contrast_25,
-                  ],
-                ]}>
-                <DotsHorizontal
-                  fill={defaultCtrlColor}
-                  style={{pointerEvents: 'none'}}
-                  size={size}
-                />
-              </Pressable>
-            )
-          }}
-        </Menu.Trigger>
-        {hasBeenOpen && (
-          // Lazily initialized. Once mounted, they stay mounted.
-          <PostDropdownMenuItems
-            testID={testID}
-            post={post}
-            postFeedContext={postFeedContext}
-            postReqId={postReqId}
-            record={record}
-            richText={richText}
-            timestamp={timestamp}
-            threadgateRecord={threadgateRecord}
-            onShowLess={onShowLess}
-          />
-        )}
-      </Menu.Root>
-    </EventStopper>
-  )
-}
-
-PostDropdownBtn = memo(PostDropdownBtn)
-export {PostDropdownBtn}
diff --git a/src/view/com/util/forms/PostDropdownBtnMenuItems.tsx b/src/view/com/util/forms/PostDropdownBtnMenuItems.tsx
deleted file mode 100644
index a5f41ea7a..000000000
--- a/src/view/com/util/forms/PostDropdownBtnMenuItems.tsx
+++ /dev/null
@@ -1,872 +0,0 @@
-import React, {memo} from 'react'
-import {
-  Platform,
-  type PressableProps,
-  type StyleProp,
-  type ViewStyle,
-} from 'react-native'
-import * as Clipboard from 'expo-clipboard'
-import {
-  type AppBskyFeedDefs,
-  AppBskyFeedPost,
-  type AppBskyFeedThreadgate,
-  AtUri,
-  type RichText as RichTextAPI,
-} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useNavigation} from '@react-navigation/native'
-
-import {IS_INTERNAL} from '#/lib/app-info'
-import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
-import {useOpenLink} from '#/lib/hooks/useOpenLink'
-import {getCurrentRoute} from '#/lib/routes/helpers'
-import {makeProfileLink} from '#/lib/routes/links'
-import {
-  type CommonNavigatorParams,
-  type NavigationProp,
-} from '#/lib/routes/types'
-import {shareText, shareUrl} from '#/lib/sharing'
-import {logEvent} from '#/lib/statsig/statsig'
-import {richTextToString} from '#/lib/strings/rich-text-helpers'
-import {toShareUrl} from '#/lib/strings/url-helpers'
-import {getTranslatorLink} from '#/locale/helpers'
-import {logger} from '#/logger'
-import {isWeb} from '#/platform/detection'
-import {type Shadow} from '#/state/cache/post-shadow'
-import {useProfileShadow} from '#/state/cache/profile-shadow'
-import {useFeedFeedbackContext} from '#/state/feed-feedback'
-import {useLanguagePrefs} from '#/state/preferences'
-import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences'
-import {usePinnedPostMutation} from '#/state/queries/pinned-post'
-import {
-  usePostDeleteMutation,
-  useThreadMuteMutationQueue,
-} from '#/state/queries/post'
-import {useToggleQuoteDetachmentMutation} from '#/state/queries/postgate'
-import {getMaybeDetachedQuoteEmbed} from '#/state/queries/postgate/util'
-import {
-  useProfileBlockMutationQueue,
-  useProfileMuteMutationQueue,
-} from '#/state/queries/profile'
-import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate'
-import {useSession} from '#/state/session'
-import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
-import {useBreakpoints} from '#/alf'
-import {useDialogControl} from '#/components/Dialog'
-import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
-import {EmbedDialog} from '#/components/dialogs/Embed'
-import {
-  PostInteractionSettingsDialog,
-  usePrefetchPostInteractionSettings,
-} from '#/components/dialogs/PostInteractionSettingsDialog'
-import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
-import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
-import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
-import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
-import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets'
-import {
-  EmojiSad_Stroke2_Corner0_Rounded as EmojiSad,
-  EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmile,
-} from '#/components/icons/Emoji'
-import {Eye_Stroke2_Corner0_Rounded as Eye} from '#/components/icons/Eye'
-import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
-import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
-import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
-import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
-import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane'
-import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Person'
-import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin'
-import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
-import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
-import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
-import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
-import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
-import {Loader} from '#/components/Loader'
-import * as Menu from '#/components/Menu'
-import {
-  ReportDialog,
-  useReportDialogControl,
-} from '#/components/moderation/ReportDialog'
-import * as Prompt from '#/components/Prompt'
-import {useDevMode} from '#/storage/hooks/dev-mode'
-import * as bsky from '#/types/bsky'
-import * as Toast from '../Toast'
-
-let PostDropdownMenuItems = ({
-  post,
-  postFeedContext,
-  postReqId,
-  record,
-  richText,
-  timestamp,
-  threadgateRecord,
-  onShowLess,
-}: {
-  testID: string
-  post: Shadow<AppBskyFeedDefs.PostView>
-  postFeedContext: string | undefined
-  postReqId: string | undefined
-  record: AppBskyFeedPost.Record
-  richText: RichTextAPI
-  style?: StyleProp<ViewStyle>
-  hitSlop?: PressableProps['hitSlop']
-  size?: 'lg' | 'md' | 'sm'
-  timestamp: string
-  threadgateRecord?: AppBskyFeedThreadgate.Record
-  onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
-}): React.ReactNode => {
-  const {hasSession, currentAccount} = useSession()
-  const {gtMobile} = useBreakpoints()
-  const {_} = useLingui()
-  const langPrefs = useLanguagePrefs()
-  const {mutateAsync: deletePostMutate} = usePostDeleteMutation()
-  const {mutateAsync: pinPostMutate, isPending: isPinPending} =
-    usePinnedPostMutation()
-  const hiddenPosts = useHiddenPosts()
-  const {hidePost} = useHiddenPostsApi()
-  const feedFeedback = useFeedFeedbackContext()
-  const openLink = useOpenLink()
-  const navigation = useNavigation<NavigationProp>()
-  const {mutedWordsDialogControl} = useGlobalDialogsControlContext()
-  const blockPromptControl = useDialogControl()
-  const reportDialogControl = useReportDialogControl()
-  const deletePromptControl = useDialogControl()
-  const hidePromptControl = useDialogControl()
-  const loggedOutWarningPromptControl = useDialogControl()
-  const embedPostControl = useDialogControl()
-  const sendViaChatControl = useDialogControl()
-  const postInteractionSettingsDialogControl = useDialogControl()
-  const quotePostDetachConfirmControl = useDialogControl()
-  const hideReplyConfirmControl = useDialogControl()
-  const {mutateAsync: toggleReplyVisibility} =
-    useToggleReplyVisibilityMutation()
-  const [devModeEnabled] = useDevMode()
-
-  const postUri = post.uri
-  const postCid = post.cid
-  const postAuthor = useProfileShadow(post.author)
-  const quoteEmbed = React.useMemo(() => {
-    if (!currentAccount || !post.embed) return
-    return getMaybeDetachedQuoteEmbed({
-      viewerDid: currentAccount.did,
-      post,
-    })
-  }, [post, currentAccount])
-
-  const rootUri = record.reply?.root?.uri || postUri
-  const isReply = Boolean(record.reply)
-  const [isThreadMuted, muteThread, unmuteThread] = useThreadMuteMutationQueue(
-    post,
-    rootUri,
-  )
-  const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri)
-  const isAuthor = postAuthor.did === currentAccount?.did
-  const isRootPostAuthor = new AtUri(rootUri).host === currentAccount?.did
-  const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({
-    threadgateRecord,
-  })
-  const isReplyHiddenByThreadgate = threadgateHiddenReplies.has(postUri)
-  const isPinned = post.viewer?.pinned
-
-  const {mutateAsync: toggleQuoteDetachment, isPending: isDetachPending} =
-    useToggleQuoteDetachmentMutation()
-
-  const [queueBlock] = useProfileBlockMutationQueue(postAuthor)
-  const [queueMute, queueUnmute] = useProfileMuteMutationQueue(postAuthor)
-
-  const prefetchPostInteractionSettings = usePrefetchPostInteractionSettings({
-    postUri: post.uri,
-    rootPostUri: rootUri,
-  })
-
-  const href = React.useMemo(() => {
-    const urip = new AtUri(postUri)
-    return makeProfileLink(postAuthor, 'post', urip.rkey)
-  }, [postUri, postAuthor])
-
-  const translatorUrl = getTranslatorLink(
-    record.text,
-    langPrefs.primaryLanguage,
-  )
-
-  const onDeletePost = () => {
-    deletePostMutate({uri: postUri}).then(
-      () => {
-        Toast.show(_(msg({message: 'Post deleted', context: 'toast'})))
-
-        const route = getCurrentRoute(navigation.getState())
-        if (route.name === 'PostThread') {
-          const params = route.params as CommonNavigatorParams['PostThread']
-          if (
-            currentAccount &&
-            isAuthor &&
-            (params.name === currentAccount.handle ||
-              params.name === currentAccount.did)
-          ) {
-            const currentHref = makeProfileLink(postAuthor, 'post', params.rkey)
-            if (currentHref === href && navigation.canGoBack()) {
-              navigation.goBack()
-            }
-          }
-        }
-      },
-      e => {
-        logger.error('Failed to delete post', {message: e})
-        Toast.show(_(msg`Failed to delete post, please try again`), 'xmark')
-      },
-    )
-  }
-
-  const onToggleThreadMute = () => {
-    try {
-      if (isThreadMuted) {
-        unmuteThread()
-        Toast.show(_(msg`You will now receive notifications for this thread`))
-      } else {
-        muteThread()
-        Toast.show(
-          _(msg`You will no longer receive notifications for this thread`),
-        )
-      }
-    } catch (e: any) {
-      if (e?.name !== 'AbortError') {
-        logger.error('Failed to toggle thread mute', {message: e})
-        Toast.show(
-          _(msg`Failed to toggle thread mute, please try again`),
-          'xmark',
-        )
-      }
-    }
-  }
-
-  const onCopyPostText = () => {
-    const str = richTextToString(richText, true)
-
-    Clipboard.setStringAsync(str)
-    Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
-  }
-
-  const onPressTranslate = async () => {
-    await openLink(translatorUrl, true)
-
-    if (
-      bsky.dangerousIsType<AppBskyFeedPost.Record>(
-        post.record,
-        AppBskyFeedPost.isRecord,
-      )
-    ) {
-      logger.metric('translate', {
-        sourceLanguages: post.record.langs ?? [],
-        targetLanguage: langPrefs.primaryLanguage,
-        textLength: post.record.text.length,
-      })
-    }
-  }
-
-  const onHidePost = () => {
-    hidePost({uri: postUri})
-  }
-
-  const hideInPWI = !!postAuthor.labels?.find(
-    label => label.val === '!no-unauthenticated',
-  )
-
-  const showLoggedOutWarning =
-    postAuthor.did !== currentAccount?.did && hideInPWI
-
-  const onSharePost = () => {
-    const url = toShareUrl(href)
-    shareUrl(url)
-  }
-
-  const onPressShowMore = () => {
-    feedFeedback.sendInteraction({
-      event: 'app.bsky.feed.defs#requestMore',
-      item: postUri,
-      feedContext: postFeedContext,
-      reqId: postReqId,
-    })
-    Toast.show(_(msg({message: 'Feedback sent!', context: 'toast'})))
-  }
-
-  const onPressShowLess = () => {
-    feedFeedback.sendInteraction({
-      event: 'app.bsky.feed.defs#requestLess',
-      item: postUri,
-      feedContext: postFeedContext,
-      reqId: postReqId,
-    })
-    if (onShowLess) {
-      onShowLess({
-        item: postUri,
-        feedContext: postFeedContext,
-      })
-    } else {
-      Toast.show(_(msg({message: 'Feedback sent!', context: 'toast'})))
-    }
-  }
-
-  const onSelectChatToShareTo = (conversation: string) => {
-    navigation.navigate('MessagesConversation', {
-      conversation,
-      embed: postUri,
-    })
-  }
-
-  const onToggleQuotePostAttachment = async () => {
-    if (!quoteEmbed) return
-
-    const action = quoteEmbed.isDetached ? 'reattach' : 'detach'
-    const isDetach = action === 'detach'
-
-    try {
-      await toggleQuoteDetachment({
-        post,
-        quoteUri: quoteEmbed.uri,
-        action: quoteEmbed.isDetached ? 'reattach' : 'detach',
-      })
-      Toast.show(
-        isDetach
-          ? _(msg`Quote post was successfully detached`)
-          : _(msg`Quote post was re-attached`),
-      )
-    } catch (e: any) {
-      Toast.show(
-        _(msg({message: 'Updating quote attachment failed', context: 'toast'})),
-      )
-      logger.error(`Failed to ${action} quote`, {safeMessage: e.message})
-    }
-  }
-
-  const canHidePostForMe = !isAuthor && !isPostHidden
-  const canEmbed = isWeb && gtMobile && !hideInPWI
-  const canHideReplyForEveryone =
-    !isAuthor && isRootPostAuthor && !isPostHidden && isReply
-  const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer
-
-  const onToggleReplyVisibility = async () => {
-    // TODO no threadgate?
-    if (!canHideReplyForEveryone) return
-
-    const action = isReplyHiddenByThreadgate ? 'show' : 'hide'
-    const isHide = action === 'hide'
-
-    try {
-      await toggleReplyVisibility({
-        postUri: rootUri,
-        replyUri: postUri,
-        action,
-      })
-      Toast.show(
-        isHide
-          ? _(msg`Reply was successfully hidden`)
-          : _(msg({message: 'Reply visibility updated', context: 'toast'})),
-      )
-    } catch (e: any) {
-      Toast.show(
-        _(msg({message: 'Updating reply visibility failed', context: 'toast'})),
-      )
-      logger.error(`Failed to ${action} reply`, {safeMessage: e.message})
-    }
-  }
-
-  const onPressPin = () => {
-    logEvent(isPinned ? 'post:unpin' : 'post:pin', {})
-    pinPostMutate({
-      postUri,
-      postCid,
-      action: isPinned ? 'unpin' : 'pin',
-    })
-  }
-
-  const onBlockAuthor = async () => {
-    try {
-      await queueBlock()
-      Toast.show(_(msg({message: 'Account blocked', context: 'toast'})))
-    } catch (e: any) {
-      if (e?.name !== 'AbortError') {
-        logger.error('Failed to block account', {message: e})
-        Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
-      }
-    }
-  }
-
-  const onMuteAuthor = async () => {
-    if (postAuthor.viewer?.muted) {
-      try {
-        await queueUnmute()
-        Toast.show(_(msg({message: 'Account unmuted', context: 'toast'})))
-      } catch (e: any) {
-        if (e?.name !== 'AbortError') {
-          logger.error('Failed to unmute account', {message: e})
-          Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
-        }
-      }
-    } else {
-      try {
-        await queueMute()
-        Toast.show(_(msg({message: 'Account muted', context: 'toast'})))
-      } catch (e: any) {
-        if (e?.name !== 'AbortError') {
-          logger.error('Failed to mute account', {message: e})
-          Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
-        }
-      }
-    }
-  }
-
-  const onShareATURI = () => {
-    shareText(postUri)
-  }
-
-  const onShareAuthorDID = () => {
-    shareText(postAuthor.did)
-  }
-
-  const onReportMisclassification = () => {
-    const url = `https://docs.google.com/forms/d/e/1FAIpQLSd0QPqhNFksDQf1YyOos7r1ofCLvmrKAH1lU042TaS3GAZaWQ/viewform?entry.1756031717=${toShareUrl(
-      href,
-    )}`
-    openLink(url)
-  }
-
-  return (
-    <>
-      <Menu.Outer>
-        {isAuthor && (
-          <>
-            <Menu.Group>
-              <Menu.Item
-                testID="pinPostBtn"
-                label={
-                  isPinned
-                    ? _(msg`Unpin from profile`)
-                    : _(msg`Pin to your profile`)
-                }
-                disabled={isPinPending}
-                onPress={onPressPin}>
-                <Menu.ItemText>
-                  {isPinned
-                    ? _(msg`Unpin from profile`)
-                    : _(msg`Pin to your profile`)}
-                </Menu.ItemText>
-                <Menu.ItemIcon
-                  icon={isPinPending ? Loader : PinIcon}
-                  position="right"
-                />
-              </Menu.Item>
-            </Menu.Group>
-            <Menu.Divider />
-          </>
-        )}
-
-        <Menu.Group>
-          {(!hideInPWI || hasSession) && (
-            <>
-              <Menu.Item
-                testID="postDropdownTranslateBtn"
-                label={_(msg`Translate`)}
-                onPress={onPressTranslate}>
-                <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText>
-                <Menu.ItemIcon icon={Translate} position="right" />
-              </Menu.Item>
-
-              <Menu.Item
-                testID="postDropdownCopyTextBtn"
-                label={_(msg`Copy post text`)}
-                onPress={onCopyPostText}>
-                <Menu.ItemText>{_(msg`Copy post text`)}</Menu.ItemText>
-                <Menu.ItemIcon icon={ClipboardIcon} position="right" />
-              </Menu.Item>
-            </>
-          )}
-
-          {hasSession && (
-            <Menu.Item
-              testID="postDropdownSendViaDMBtn"
-              label={_(msg`Send via direct message`)}
-              onPress={() => sendViaChatControl.open()}>
-              <Menu.ItemText>
-                <Trans>Send via direct message</Trans>
-              </Menu.ItemText>
-              <Menu.ItemIcon icon={Send} position="right" />
-            </Menu.Item>
-          )}
-
-          <Menu.Item
-            testID="postDropdownShareBtn"
-            label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
-            onPress={() => {
-              if (showLoggedOutWarning) {
-                loggedOutWarningPromptControl.open()
-              } else {
-                onSharePost()
-              }
-            }}>
-            <Menu.ItemText>
-              {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)}
-            </Menu.ItemText>
-            <Menu.ItemIcon icon={Share} position="right" />
-          </Menu.Item>
-
-          {canEmbed && (
-            <Menu.Item
-              testID="postDropdownEmbedBtn"
-              label={_(msg`Embed post`)}
-              onPress={() => embedPostControl.open()}>
-              <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText>
-              <Menu.ItemIcon icon={CodeBrackets} position="right" />
-            </Menu.Item>
-          )}
-        </Menu.Group>
-
-        {hasSession && feedFeedback.enabled && (
-          <>
-            <Menu.Divider />
-            <Menu.Group>
-              <Menu.Item
-                testID="postDropdownShowMoreBtn"
-                label={_(msg`Show more like this`)}
-                onPress={onPressShowMore}>
-                <Menu.ItemText>{_(msg`Show more like this`)}</Menu.ItemText>
-                <Menu.ItemIcon icon={EmojiSmile} position="right" />
-              </Menu.Item>
-
-              <Menu.Item
-                testID="postDropdownShowLessBtn"
-                label={_(msg`Show less like this`)}
-                onPress={onPressShowLess}>
-                <Menu.ItemText>{_(msg`Show less like this`)}</Menu.ItemText>
-                <Menu.ItemIcon icon={EmojiSad} position="right" />
-              </Menu.Item>
-            </Menu.Group>
-          </>
-        )}
-
-        {hasSession &&
-          IS_INTERNAL &&
-          DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
-            <Menu.Item
-              testID="postDropdownReportMisclassificationBtn"
-              label={_(msg`Assign topic - help train Discover!`)}
-              onPress={onReportMisclassification}>
-              <Menu.ItemText>
-                {_(msg`Assign topic - help train Discover!`)}
-              </Menu.ItemText>
-              <Menu.ItemIcon icon={AtomIcon} position="right" />
-            </Menu.Item>
-          )}
-
-        {hasSession && (
-          <>
-            <Menu.Divider />
-            <Menu.Group>
-              <Menu.Item
-                testID="postDropdownMuteThreadBtn"
-                label={
-                  isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`)
-                }
-                onPress={onToggleThreadMute}>
-                <Menu.ItemText>
-                  {isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`)}
-                </Menu.ItemText>
-                <Menu.ItemIcon
-                  icon={isThreadMuted ? Unmute : Mute}
-                  position="right"
-                />
-              </Menu.Item>
-
-              <Menu.Item
-                testID="postDropdownMuteWordsBtn"
-                label={_(msg`Mute words & tags`)}
-                onPress={() => mutedWordsDialogControl.open()}>
-                <Menu.ItemText>{_(msg`Mute words & tags`)}</Menu.ItemText>
-                <Menu.ItemIcon icon={Filter} position="right" />
-              </Menu.Item>
-            </Menu.Group>
-          </>
-        )}
-
-        {hasSession &&
-          (canHideReplyForEveryone || canDetachQuote || canHidePostForMe) && (
-            <>
-              <Menu.Divider />
-              <Menu.Group>
-                {canHidePostForMe && (
-                  <Menu.Item
-                    testID="postDropdownHideBtn"
-                    label={
-                      isReply
-                        ? _(msg`Hide reply for me`)
-                        : _(msg`Hide post for me`)
-                    }
-                    onPress={() => hidePromptControl.open()}>
-                    <Menu.ItemText>
-                      {isReply
-                        ? _(msg`Hide reply for me`)
-                        : _(msg`Hide post for me`)}
-                    </Menu.ItemText>
-                    <Menu.ItemIcon icon={EyeSlash} position="right" />
-                  </Menu.Item>
-                )}
-                {canHideReplyForEveryone && (
-                  <Menu.Item
-                    testID="postDropdownHideBtn"
-                    label={
-                      isReplyHiddenByThreadgate
-                        ? _(msg`Show reply for everyone`)
-                        : _(msg`Hide reply for everyone`)
-                    }
-                    onPress={
-                      isReplyHiddenByThreadgate
-                        ? onToggleReplyVisibility
-                        : () => hideReplyConfirmControl.open()
-                    }>
-                    <Menu.ItemText>
-                      {isReplyHiddenByThreadgate
-                        ? _(msg`Show reply for everyone`)
-                        : _(msg`Hide reply for everyone`)}
-                    </Menu.ItemText>
-                    <Menu.ItemIcon
-                      icon={isReplyHiddenByThreadgate ? Eye : EyeSlash}
-                      position="right"
-                    />
-                  </Menu.Item>
-                )}
-
-                {canDetachQuote && (
-                  <Menu.Item
-                    disabled={isDetachPending}
-                    testID="postDropdownHideBtn"
-                    label={
-                      quoteEmbed.isDetached
-                        ? _(msg`Re-attach quote`)
-                        : _(msg`Detach quote`)
-                    }
-                    onPress={
-                      quoteEmbed.isDetached
-                        ? onToggleQuotePostAttachment
-                        : () => quotePostDetachConfirmControl.open()
-                    }>
-                    <Menu.ItemText>
-                      {quoteEmbed.isDetached
-                        ? _(msg`Re-attach quote`)
-                        : _(msg`Detach quote`)}
-                    </Menu.ItemText>
-                    <Menu.ItemIcon
-                      icon={
-                        isDetachPending
-                          ? Loader
-                          : quoteEmbed.isDetached
-                          ? Eye
-                          : EyeSlash
-                      }
-                      position="right"
-                    />
-                  </Menu.Item>
-                )}
-              </Menu.Group>
-            </>
-          )}
-
-        {hasSession && (
-          <>
-            <Menu.Divider />
-            <Menu.Group>
-              {!isAuthor && (
-                <>
-                  <Menu.Item
-                    testID="postDropdownMuteBtn"
-                    label={
-                      postAuthor.viewer?.muted
-                        ? _(msg`Unmute account`)
-                        : _(msg`Mute account`)
-                    }
-                    onPress={onMuteAuthor}>
-                    <Menu.ItemText>
-                      {postAuthor.viewer?.muted
-                        ? _(msg`Unmute account`)
-                        : _(msg`Mute account`)}
-                    </Menu.ItemText>
-                    <Menu.ItemIcon
-                      icon={postAuthor.viewer?.muted ? UnmuteIcon : MuteIcon}
-                      position="right"
-                    />
-                  </Menu.Item>
-
-                  {!postAuthor.viewer?.blocking && (
-                    <Menu.Item
-                      testID="postDropdownBlockBtn"
-                      label={_(msg`Block account`)}
-                      onPress={() => blockPromptControl.open()}>
-                      <Menu.ItemText>{_(msg`Block account`)}</Menu.ItemText>
-                      <Menu.ItemIcon icon={PersonX} position="right" />
-                    </Menu.Item>
-                  )}
-
-                  <Menu.Item
-                    testID="postDropdownReportBtn"
-                    label={_(msg`Report post`)}
-                    onPress={() => reportDialogControl.open()}>
-                    <Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Warning} position="right" />
-                  </Menu.Item>
-                </>
-              )}
-
-              {isAuthor && (
-                <>
-                  <Menu.Item
-                    testID="postDropdownEditPostInteractions"
-                    label={_(msg`Edit interaction settings`)}
-                    onPress={() => postInteractionSettingsDialogControl.open()}
-                    {...(isAuthor
-                      ? Platform.select({
-                          web: {
-                            onHoverIn: prefetchPostInteractionSettings,
-                          },
-                          native: {
-                            onPressIn: prefetchPostInteractionSettings,
-                          },
-                        })
-                      : {})}>
-                    <Menu.ItemText>
-                      {_(msg`Edit interaction settings`)}
-                    </Menu.ItemText>
-                    <Menu.ItemIcon icon={Gear} position="right" />
-                  </Menu.Item>
-                  <Menu.Item
-                    testID="postDropdownDeleteBtn"
-                    label={_(msg`Delete post`)}
-                    onPress={() => deletePromptControl.open()}>
-                    <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Trash} position="right" />
-                  </Menu.Item>
-                </>
-              )}
-            </Menu.Group>
-
-            {devModeEnabled ? (
-              <>
-                <Menu.Divider />
-                <Menu.Group>
-                  <Menu.Item
-                    testID="postAtUriShareBtn"
-                    label={_(msg`Copy post at:// URI`)}
-                    onPress={onShareATURI}>
-                    <Menu.ItemText>{_(msg`Copy post at:// URI`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Share} position="right" />
-                  </Menu.Item>
-                  <Menu.Item
-                    testID="postAuthorDIDShareBtn"
-                    label={_(msg`Copy author DID`)}
-                    onPress={onShareAuthorDID}>
-                    <Menu.ItemText>{_(msg`Copy author DID`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Share} position="right" />
-                  </Menu.Item>
-                </Menu.Group>
-              </>
-            ) : null}
-          </>
-        )}
-      </Menu.Outer>
-
-      <Prompt.Basic
-        control={deletePromptControl}
-        title={_(msg`Delete this post?`)}
-        description={_(
-          msg`If you remove this post, you won't be able to recover it.`,
-        )}
-        onConfirm={onDeletePost}
-        confirmButtonCta={_(msg`Delete`)}
-        confirmButtonColor="negative"
-      />
-
-      <Prompt.Basic
-        control={hidePromptControl}
-        title={isReply ? _(msg`Hide this reply?`) : _(msg`Hide this post?`)}
-        description={_(
-          msg`This post will be hidden from feeds and threads. This cannot be undone.`,
-        )}
-        onConfirm={onHidePost}
-        confirmButtonCta={_(msg`Hide`)}
-      />
-
-      <ReportDialog
-        control={reportDialogControl}
-        subject={{
-          ...post,
-          $type: 'app.bsky.feed.defs#postView',
-        }}
-      />
-
-      <Prompt.Basic
-        control={loggedOutWarningPromptControl}
-        title={_(msg`Note about sharing`)}
-        description={_(
-          msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
-        )}
-        onConfirm={onSharePost}
-        confirmButtonCta={_(msg`Share anyway`)}
-      />
-
-      {canEmbed && (
-        <EmbedDialog
-          control={embedPostControl}
-          postCid={postCid}
-          postUri={postUri}
-          record={record}
-          postAuthor={postAuthor}
-          timestamp={timestamp}
-        />
-      )}
-
-      <SendViaChatDialog
-        control={sendViaChatControl}
-        onSelectChat={onSelectChatToShareTo}
-      />
-
-      <PostInteractionSettingsDialog
-        control={postInteractionSettingsDialogControl}
-        postUri={post.uri}
-        rootPostUri={rootUri}
-        initialThreadgateView={post.threadgate}
-      />
-
-      <Prompt.Basic
-        control={quotePostDetachConfirmControl}
-        title={_(msg`Detach quote post?`)}
-        description={_(
-          msg`This will remove your post from this quote post for all users, and replace it with a placeholder.`,
-        )}
-        onConfirm={onToggleQuotePostAttachment}
-        confirmButtonCta={_(msg`Yes, detach`)}
-      />
-
-      <Prompt.Basic
-        control={hideReplyConfirmControl}
-        title={_(msg`Hide this reply?`)}
-        description={_(
-          msg`This reply will be sorted into a hidden section at the bottom of your thread and will mute notifications for subsequent replies - both for yourself and others.`,
-        )}
-        onConfirm={onToggleReplyVisibility}
-        confirmButtonCta={_(msg`Yes, hide`)}
-      />
-
-      <Prompt.Basic
-        control={blockPromptControl}
-        title={_(msg`Block Account?`)}
-        description={_(
-          msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
-        )}
-        onConfirm={onBlockAuthor}
-        confirmButtonCta={_(msg`Block`)}
-        confirmButtonColor="negative"
-      />
-    </>
-  )
-}
-PostDropdownMenuItems = memo(PostDropdownMenuItems)
-export {PostDropdownMenuItems}
diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx
deleted file mode 100644
index 3f82eb294..000000000
--- a/src/view/com/util/post-ctrls/PostCtrls.tsx
+++ /dev/null
@@ -1,394 +0,0 @@
-import React, {memo} from 'react'
-import {
-  Pressable,
-  type PressableStateCallbackType,
-  type StyleProp,
-  View,
-  type ViewStyle,
-} from 'react-native'
-import * as Clipboard from 'expo-clipboard'
-import {
-  type AppBskyFeedDefs,
-  type AppBskyFeedPost,
-  type AppBskyFeedThreadgate,
-  AtUri,
-  type RichText as RichTextAPI,
-} from '@atproto/api'
-import {msg, plural} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {IS_INTERNAL} from '#/lib/app-info'
-import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants'
-import {CountWheel} from '#/lib/custom-animations/CountWheel'
-import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
-import {useHaptics} from '#/lib/haptics'
-import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
-import {makeProfileLink} from '#/lib/routes/links'
-import {shareUrl} from '#/lib/sharing'
-import {useGate} from '#/lib/statsig/statsig'
-import {toShareUrl} from '#/lib/strings/url-helpers'
-import {type Shadow} from '#/state/cache/types'
-import {useFeedFeedbackContext} from '#/state/feed-feedback'
-import {
-  usePostLikeMutationQueue,
-  usePostRepostMutationQueue,
-} from '#/state/queries/post'
-import {useRequireAuth, useSession} from '#/state/session'
-import {
-  ProgressGuideAction,
-  useProgressGuideControls,
-} from '#/state/shell/progress-guide'
-import {atoms as a, useTheme} from '#/alf'
-import {useDialogControl} from '#/components/Dialog'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
-import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble'
-import * as Prompt from '#/components/Prompt'
-import {PostDropdownBtn} from '../forms/PostDropdownBtn'
-import {formatCount} from '../numeric/format'
-import {Text} from '../text/Text'
-import * as Toast from '../Toast'
-import {RepostButton} from './RepostButton'
-
-let PostCtrls = ({
-  big,
-  post,
-  record,
-  richText,
-  feedContext,
-  reqId,
-  style,
-  onPressReply,
-  onPostReply,
-  logContext,
-  threadgateRecord,
-  onShowLess,
-}: {
-  big?: boolean
-  post: Shadow<AppBskyFeedDefs.PostView>
-  record: AppBskyFeedPost.Record
-  richText: RichTextAPI
-  feedContext?: string | undefined
-  reqId?: string | undefined
-  style?: StyleProp<ViewStyle>
-  onPressReply: () => void
-  onPostReply?: (postUri: string | undefined) => void
-  logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
-  threadgateRecord?: AppBskyFeedThreadgate.Record
-  onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
-}): React.ReactNode => {
-  const t = useTheme()
-  const {_, i18n} = useLingui()
-  const {openComposer} = useOpenComposer()
-  const {currentAccount} = useSession()
-  const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
-  const [queueRepost, queueUnrepost] = usePostRepostMutationQueue(
-    post,
-    logContext,
-  )
-  const requireAuth = useRequireAuth()
-  const loggedOutWarningPromptControl = useDialogControl()
-  const {sendInteraction} = useFeedFeedbackContext()
-  const {captureAction} = useProgressGuideControls()
-  const playHaptic = useHaptics()
-  const gate = useGate()
-  const isDiscoverDebugUser =
-    IS_INTERNAL ||
-    DISCOVER_DEBUG_DIDS[currentAccount?.did || ''] ||
-    gate('debug_show_feedcontext')
-  const isBlocked = Boolean(
-    post.author.viewer?.blocking ||
-      post.author.viewer?.blockedBy ||
-      post.author.viewer?.blockingByList,
-  )
-  const replyDisabled = post.viewer?.replyDisabled
-
-  const shouldShowLoggedOutWarning = React.useMemo(() => {
-    return (
-      post.author.did !== currentAccount?.did &&
-      !!post.author.labels?.find(label => label.val === '!no-unauthenticated')
-    )
-  }, [currentAccount, post])
-
-  const defaultCtrlColor = React.useMemo(
-    () => ({
-      color: t.palette.contrast_500,
-    }),
-    [t],
-  ) as StyleProp<ViewStyle>
-
-  const [hasLikeIconBeenToggled, setHasLikeIconBeenToggled] =
-    React.useState(false)
-
-  const onPressToggleLike = async () => {
-    if (isBlocked) {
-      Toast.show(
-        _(msg`Cannot interact with a blocked user`),
-        'exclamation-circle',
-      )
-      return
-    }
-
-    try {
-      setHasLikeIconBeenToggled(true)
-      if (!post.viewer?.like) {
-        playHaptic('Light')
-        sendInteraction({
-          item: post.uri,
-          event: 'app.bsky.feed.defs#interactionLike',
-          feedContext,
-          reqId,
-        })
-        captureAction(ProgressGuideAction.Like)
-        await queueLike()
-      } else {
-        await queueUnlike()
-      }
-    } catch (e: any) {
-      if (e?.name !== 'AbortError') {
-        throw e
-      }
-    }
-  }
-
-  const onRepost = async () => {
-    if (isBlocked) {
-      Toast.show(
-        _(msg`Cannot interact with a blocked user`),
-        'exclamation-circle',
-      )
-      return
-    }
-
-    try {
-      if (!post.viewer?.repost) {
-        sendInteraction({
-          item: post.uri,
-          event: 'app.bsky.feed.defs#interactionRepost',
-          feedContext,
-          reqId,
-        })
-        await queueRepost()
-      } else {
-        await queueUnrepost()
-      }
-    } catch (e: any) {
-      if (e?.name !== 'AbortError') {
-        throw e
-      }
-    }
-  }
-
-  const onQuote = () => {
-    if (isBlocked) {
-      Toast.show(
-        _(msg`Cannot interact with a blocked user`),
-        'exclamation-circle',
-      )
-      return
-    }
-
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#interactionQuote',
-      feedContext,
-      reqId,
-    })
-    openComposer({
-      quote: post,
-      onPost: onPostReply,
-    })
-  }
-
-  const onShare = () => {
-    const urip = new AtUri(post.uri)
-    const href = makeProfileLink(post.author, 'post', urip.rkey)
-    const url = toShareUrl(href)
-    shareUrl(url)
-    sendInteraction({
-      item: post.uri,
-      event: 'app.bsky.feed.defs#interactionShare',
-      feedContext,
-      reqId,
-    })
-  }
-
-  const btnStyle = React.useCallback(
-    ({pressed, hovered}: PressableStateCallbackType) => [
-      a.gap_xs,
-      a.rounded_full,
-      a.flex_row,
-      a.justify_center,
-      a.align_center,
-      a.overflow_hidden,
-      {padding: 5},
-      (pressed || hovered) && t.atoms.bg_contrast_25,
-    ],
-    [t.atoms.bg_contrast_25],
-  )
-
-  return (
-    <View style={[a.flex_row, a.justify_between, a.align_center, style]}>
-      <View
-        style={[
-          big ? a.align_center : [a.flex_1, a.align_start, {marginLeft: -6}],
-          replyDisabled ? {opacity: 0.5} : undefined,
-        ]}>
-        <Pressable
-          testID="replyBtn"
-          style={btnStyle}
-          onPress={() => {
-            if (!replyDisabled) {
-              playHaptic('Light')
-              requireAuth(() => onPressReply())
-            }
-          }}
-          accessibilityRole="button"
-          accessibilityLabel={_(
-            msg`Reply (${plural(post.replyCount || 0, {
-              one: '# reply',
-              other: '# replies',
-            })})`,
-          )}
-          accessibilityHint=""
-          hitSlop={POST_CTRL_HITSLOP}>
-          <Bubble
-            style={[defaultCtrlColor, {pointerEvents: 'none'}]}
-            width={big ? 22 : 18}
-          />
-          {typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? (
-            <Text
-              style={[
-                defaultCtrlColor,
-                big ? a.text_md : {fontSize: 15},
-                a.user_select_none,
-              ]}>
-              {formatCount(i18n, post.replyCount)}
-            </Text>
-          ) : undefined}
-        </Pressable>
-      </View>
-      <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
-        <RepostButton
-          isReposted={!!post.viewer?.repost}
-          repostCount={(post.repostCount ?? 0) + (post.quoteCount ?? 0)}
-          onRepost={onRepost}
-          onQuote={onQuote}
-          big={big}
-          embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)}
-        />
-      </View>
-      <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
-        <Pressable
-          testID="likeBtn"
-          style={btnStyle}
-          onPress={() => requireAuth(() => onPressToggleLike())}
-          accessibilityRole="button"
-          accessibilityLabel={
-            post.viewer?.like
-              ? _(
-                  msg`Unlike (${plural(post.likeCount || 0, {
-                    one: '# like',
-                    other: '# likes',
-                  })})`,
-                )
-              : _(
-                  msg`Like (${plural(post.likeCount || 0, {
-                    one: '# like',
-                    other: '# likes',
-                  })})`,
-                )
-          }
-          accessibilityHint=""
-          hitSlop={POST_CTRL_HITSLOP}>
-          <AnimatedLikeIcon
-            isLiked={Boolean(post.viewer?.like)}
-            big={big}
-            hasBeenToggled={hasLikeIconBeenToggled}
-          />
-          <CountWheel
-            likeCount={post.likeCount ?? 0}
-            big={big}
-            isLiked={Boolean(post.viewer?.like)}
-            hasBeenToggled={hasLikeIconBeenToggled}
-          />
-        </Pressable>
-      </View>
-      {big && (
-        <>
-          <View style={a.align_center}>
-            <Pressable
-              testID="shareBtn"
-              style={btnStyle}
-              onPress={() => {
-                if (shouldShowLoggedOutWarning) {
-                  loggedOutWarningPromptControl.open()
-                } else {
-                  onShare()
-                }
-              }}
-              accessibilityRole="button"
-              accessibilityLabel={_(msg`Share`)}
-              accessibilityHint=""
-              hitSlop={POST_CTRL_HITSLOP}>
-              <ArrowOutOfBox
-                style={[defaultCtrlColor, {pointerEvents: 'none'}]}
-                width={22}
-              />
-            </Pressable>
-          </View>
-          <Prompt.Basic
-            control={loggedOutWarningPromptControl}
-            title={_(msg`Note about sharing`)}
-            description={_(
-              msg`This post is only visible to logged-in users. It won't be visible to people who aren't signed in.`,
-            )}
-            onConfirm={onShare}
-            confirmButtonCta={_(msg`Share anyway`)}
-          />
-        </>
-      )}
-      <View style={big ? a.align_center : [a.flex_1, a.align_start]}>
-        <PostDropdownBtn
-          testID="postDropdownBtn"
-          post={post}
-          postFeedContext={feedContext}
-          postReqId={reqId}
-          record={record}
-          richText={richText}
-          style={{padding: 5}}
-          hitSlop={POST_CTRL_HITSLOP}
-          timestamp={post.indexedAt}
-          threadgateRecord={threadgateRecord}
-          onShowLess={onShowLess}
-        />
-      </View>
-      {isDiscoverDebugUser && feedContext && (
-        <Pressable
-          accessible={false}
-          style={{
-            position: 'absolute',
-            top: 0,
-            bottom: 0,
-            right: 0,
-            display: 'flex',
-            justifyContent: 'center',
-          }}
-          onPress={e => {
-            e.stopPropagation()
-            Clipboard.setStringAsync(feedContext)
-            Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
-          }}>
-          <Text
-            style={{
-              color: t.palette.contrast_400,
-              fontSize: 7,
-            }}>
-            {feedContext}
-          </Text>
-        </Pressable>
-      )}
-    </View>
-  )
-}
-PostCtrls = memo(PostCtrls)
-export {PostCtrls}
diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx
deleted file mode 100644
index ca1647a99..000000000
--- a/src/view/com/util/post-ctrls/RepostButton.tsx
+++ /dev/null
@@ -1,221 +0,0 @@
-import React, {memo, useCallback} from 'react'
-import {View} from 'react-native'
-import {msg, plural, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {POST_CTRL_HITSLOP} from '#/lib/constants'
-import {useHaptics} from '#/lib/haptics'
-import {useRequireAuth} from '#/state/session'
-import {atoms as a, useTheme} from '#/alf'
-import {Button, ButtonText} from '#/components/Button'
-import * as Dialog from '#/components/Dialog'
-import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote'
-import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
-import {Text} from '#/components/Typography'
-import {formatCount} from '../numeric/format'
-
-interface Props {
-  isReposted: boolean
-  repostCount?: number
-  onRepost: () => void
-  onQuote: () => void
-  big?: boolean
-  embeddingDisabled: boolean
-}
-
-let RepostButton = ({
-  isReposted,
-  repostCount,
-  onRepost,
-  onQuote,
-  big,
-  embeddingDisabled,
-}: Props): React.ReactNode => {
-  const t = useTheme()
-  const {_, i18n} = useLingui()
-  const requireAuth = useRequireAuth()
-  const dialogControl = Dialog.useDialogControl()
-  const playHaptic = useHaptics()
-  const color = React.useMemo(
-    () => ({
-      color: isReposted ? t.palette.positive_600 : t.palette.contrast_500,
-    }),
-    [t, isReposted],
-  )
-  return (
-    <>
-      <Button
-        testID="repostBtn"
-        onPress={() => {
-          playHaptic('Light')
-          requireAuth(() => dialogControl.open())
-        }}
-        onLongPress={() => {
-          playHaptic('Heavy')
-          requireAuth(() => onQuote())
-        }}
-        style={[
-          a.flex_row,
-          a.align_center,
-          a.gap_xs,
-          a.bg_transparent,
-          {padding: 5},
-        ]}
-        hoverStyle={t.atoms.bg_contrast_25}
-        label={
-          isReposted
-            ? _(
-                msg`Undo repost (${plural(repostCount || 0, {
-                  one: '# repost',
-                  other: '# reposts',
-                })})`,
-              )
-            : _(
-                msg`Repost (${plural(repostCount || 0, {
-                  one: '# repost',
-                  other: '# reposts',
-                })})`,
-              )
-        }
-        shape="round"
-        variant="ghost"
-        color="secondary"
-        hitSlop={POST_CTRL_HITSLOP}>
-        <Repost style={color} width={big ? 22 : 18} />
-        {typeof repostCount !== 'undefined' && repostCount > 0 ? (
-          <Text
-            testID="repostCount"
-            style={[
-              color,
-              big ? a.text_md : {fontSize: 15},
-              isReposted && a.font_bold,
-            ]}>
-            {formatCount(i18n, repostCount)}
-          </Text>
-        ) : undefined}
-      </Button>
-      <Dialog.Outer
-        control={dialogControl}
-        nativeOptions={{preventExpansion: true}}>
-        <Dialog.Handle />
-        <RepostButtonDialogInner
-          isReposted={isReposted}
-          onRepost={onRepost}
-          onQuote={onQuote}
-          embeddingDisabled={embeddingDisabled}
-        />
-      </Dialog.Outer>
-    </>
-  )
-}
-RepostButton = memo(RepostButton)
-export {RepostButton}
-
-let RepostButtonDialogInner = ({
-  isReposted,
-  onRepost,
-  onQuote,
-  embeddingDisabled,
-}: {
-  isReposted: boolean
-  onRepost: () => void
-  onQuote: () => void
-  embeddingDisabled: boolean
-}): React.ReactNode => {
-  const t = useTheme()
-  const {_} = useLingui()
-  const playHaptic = useHaptics()
-  const control = Dialog.useDialogContext()
-
-  const onPressRepost = useCallback(() => {
-    if (!isReposted) playHaptic()
-
-    control.close(() => {
-      onRepost()
-    })
-  }, [control, isReposted, onRepost, playHaptic])
-
-  const onPressQuote = useCallback(() => {
-    playHaptic()
-    control.close(() => {
-      onQuote()
-    })
-  }, [control, onQuote, playHaptic])
-
-  const onPressClose = useCallback(() => control.close(), [control])
-
-  return (
-    <Dialog.ScrollableInner label={_(msg`Repost or quote post`)}>
-      <View style={a.gap_xl}>
-        <View style={a.gap_xs}>
-          <Button
-            style={[a.justify_start, a.px_md]}
-            label={
-              isReposted
-                ? _(msg`Remove repost`)
-                : _(msg({message: `Repost`, context: 'action'}))
-            }
-            onPress={onPressRepost}
-            size="large"
-            variant="ghost"
-            color="primary">
-            <Repost size="lg" fill={t.palette.primary_500} />
-            <Text style={[a.font_bold, a.text_xl]}>
-              {isReposted ? (
-                <Trans>Remove repost</Trans>
-              ) : (
-                <Trans context="action">Repost</Trans>
-              )}
-            </Text>
-          </Button>
-          <Button
-            disabled={embeddingDisabled}
-            testID="quoteBtn"
-            style={[a.justify_start, a.px_md]}
-            label={
-              embeddingDisabled
-                ? _(msg`Quote posts disabled`)
-                : _(msg`Quote post`)
-            }
-            onPress={onPressQuote}
-            size="large"
-            variant="ghost"
-            color="primary">
-            <Quote
-              size="lg"
-              fill={
-                embeddingDisabled
-                  ? t.atoms.text_contrast_low.color
-                  : t.palette.primary_500
-              }
-            />
-            <Text
-              style={[
-                a.font_bold,
-                a.text_xl,
-                embeddingDisabled && t.atoms.text_contrast_low,
-              ]}>
-              {embeddingDisabled ? (
-                <Trans>Quote posts disabled</Trans>
-              ) : (
-                <Trans>Quote post</Trans>
-              )}
-            </Text>
-          </Button>
-        </View>
-        <Button
-          label={_(msg`Cancel quote post`)}
-          onPress={onPressClose}
-          size="large"
-          variant="outline"
-          color="primary">
-          <ButtonText>
-            <Trans>Cancel</Trans>
-          </ButtonText>
-        </Button>
-      </View>
-    </Dialog.ScrollableInner>
-  )
-}
-RepostButtonDialogInner = memo(RepostButtonDialogInner)
-export {RepostButtonDialogInner}
diff --git a/src/view/com/util/post-ctrls/RepostButton.web.tsx b/src/view/com/util/post-ctrls/RepostButton.web.tsx
deleted file mode 100644
index 54119b532..000000000
--- a/src/view/com/util/post-ctrls/RepostButton.web.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import React from 'react'
-import {Pressable, View} from 'react-native'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {useRequireAuth} from '#/state/session'
-import {useSession} from '#/state/session'
-import {atoms as a, useTheme} from '#/alf'
-import {Button} from '#/components/Button'
-import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote'
-import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
-import * as Menu from '#/components/Menu'
-import {Text} from '#/components/Typography'
-import {EventStopper} from '../EventStopper'
-import {formatCount} from '../numeric/format'
-
-interface Props {
-  isReposted: boolean
-  repostCount?: number
-  onRepost: () => void
-  onQuote: () => void
-  big?: boolean
-  embeddingDisabled: boolean
-}
-
-export const RepostButton = ({
-  isReposted,
-  repostCount,
-  onRepost,
-  onQuote,
-  big,
-  embeddingDisabled,
-}: Props) => {
-  const t = useTheme()
-  const {_} = useLingui()
-  const {hasSession} = useSession()
-  const requireAuth = useRequireAuth()
-
-  const color = React.useMemo(
-    () => ({
-      color: isReposted ? t.palette.positive_600 : t.palette.contrast_500,
-    }),
-    [t, isReposted],
-  )
-
-  return hasSession ? (
-    <EventStopper onKeyDown={false}>
-      <Menu.Root>
-        <Menu.Trigger label={_(msg`Repost or quote post`)}>
-          {({props, state}) => {
-            return (
-              <Pressable
-                {...props}
-                style={[
-                  a.rounded_full,
-                  (state.hovered || state.pressed) && {
-                    backgroundColor: t.palette.contrast_25,
-                  },
-                ]}>
-                <RepostInner
-                  isReposted={isReposted}
-                  color={color}
-                  repostCount={repostCount}
-                  big={big}
-                />
-              </Pressable>
-            )
-          }}
-        </Menu.Trigger>
-        <Menu.Outer style={{minWidth: 170}}>
-          <Menu.Item
-            label={isReposted ? _(msg`Undo repost`) : _(msg`Repost`)}
-            testID="repostDropdownRepostBtn"
-            onPress={onRepost}>
-            <Menu.ItemText>
-              {isReposted ? _(msg`Undo repost`) : _(msg`Repost`)}
-            </Menu.ItemText>
-            <Menu.ItemIcon icon={Repost} position="right" />
-          </Menu.Item>
-          <Menu.Item
-            disabled={embeddingDisabled}
-            label={
-              embeddingDisabled
-                ? _(msg`Quote posts disabled`)
-                : _(msg`Quote post`)
-            }
-            testID="repostDropdownQuoteBtn"
-            onPress={onQuote}>
-            <Menu.ItemText>
-              {embeddingDisabled
-                ? _(msg`Quote posts disabled`)
-                : _(msg`Quote post`)}
-            </Menu.ItemText>
-            <Menu.ItemIcon icon={Quote} position="right" />
-          </Menu.Item>
-        </Menu.Outer>
-      </Menu.Root>
-    </EventStopper>
-  ) : (
-    <Button
-      onPress={() => {
-        requireAuth(() => {})
-      }}
-      label={_(msg`Repost or quote post`)}
-      style={{padding: 0}}
-      hoverStyle={t.atoms.bg_contrast_25}
-      shape="round">
-      <RepostInner
-        isReposted={isReposted}
-        color={color}
-        repostCount={repostCount}
-        big={big}
-      />
-    </Button>
-  )
-}
-
-const RepostInner = ({
-  isReposted,
-  color,
-  repostCount,
-  big,
-}: {
-  isReposted: boolean
-  color: {color: string}
-  repostCount?: number
-  big?: boolean
-}) => {
-  const {i18n} = useLingui()
-  return (
-    <View style={[a.flex_row, a.align_center, a.gap_xs, {padding: 5}]}>
-      <Repost style={color} width={big ? 22 : 18} />
-      {typeof repostCount !== 'undefined' && repostCount > 0 ? (
-        <Text
-          testID="repostCount"
-          style={[
-            color,
-            big ? a.text_md : {fontSize: 15},
-            isReposted && [a.font_bold],
-            a.user_select_none,
-          ]}>
-          {formatCount(i18n, repostCount)}
-        </Text>
-      ) : undefined}
-    </View>
-  )
-}