Mirror — see github.com/blacksky-algorithms/blacksky.community
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(post): remove two menu items for a button for hiding posts

This nixes two menu items in favor of showing a button that more
prominently allows one to hide a post from their feed.

+176 -104
+99
src/components/PostControls/HideButton.tsx
··· 1 + import {memo} from 'react' 2 + import {type Insets} from 'react-native' 3 + import {type AppBskyFeedDefs} from '@atproto/api' 4 + import {msg} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + import type React from 'react' 7 + 8 + import {useCleanError} from '#/lib/hooks/useCleanError' 9 + import {type Shadow} from '#/state/cache/post-shadow' 10 + import {useFeedFeedbackContext} from '#/state/feed-feedback' 11 + import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' 12 + import {useRequireAuth, useSession} from '#/state/session' 13 + import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' 14 + import * as Prompt from '#/components/Prompt' 15 + import * as toast from '#/components/Toast' 16 + import {useAnalytics} from '#/analytics' 17 + import {PostControlButton, PostControlButtonIcon} from './PostControlButton' 18 + 19 + export const HideButton = memo(function HideButton({ 20 + post, 21 + big, 22 + logContext, 23 + hitSlop, 24 + }: { 25 + post: Shadow<AppBskyFeedDefs.PostView> 26 + big?: boolean 27 + logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' 28 + hitSlop?: Insets 29 + }): React.ReactNode { 30 + const ax = useAnalytics() 31 + const {_} = useLingui() 32 + const {currentAccount} = useSession() 33 + const hiddenPosts = useHiddenPosts() 34 + const {hidePost} = useHiddenPostsApi() 35 + const cleanError = useCleanError() 36 + const requireAuth = useRequireAuth() 37 + const {feedDescriptor} = useFeedFeedbackContext() 38 + const hidePromptControl = Prompt.usePromptControl() 39 + 40 + const isAuthor = currentAccount?.did === post.author.did 41 + const isPostHidden = hiddenPosts?.includes(post.uri) 42 + 43 + const onConfirmHide = () => { 44 + try { 45 + hidePost({uri: post.uri}) 46 + 47 + ax.metric('thread:click:hideReplyForMe', { 48 + uri: post.uri, 49 + authorDid: post.author.did, 50 + logContext, 51 + feedDescriptor, 52 + }) 53 + 54 + toast.show( 55 + <toast.Outer> 56 + <toast.Icon icon={EyeSlash} /> 57 + <toast.Text>{_(msg`Post hidden`)}</toast.Text> 58 + </toast.Outer>, 59 + ) 60 + } catch (e: any) { 61 + const {raw, clean} = cleanError(e) 62 + toast.show(clean || raw || e, { 63 + type: 'error', 64 + }) 65 + } 66 + } 67 + 68 + const onToggleHide = () => 69 + requireAuth(() => { 70 + hidePromptControl.open() 71 + }) 72 + 73 + if (isAuthor || isPostHidden) { 74 + return null 75 + } 76 + 77 + return ( 78 + <> 79 + <PostControlButton 80 + testID="postHideBtn" 81 + big={big} 82 + label={_(msg`Hide post`)} 83 + onPress={onToggleHide} 84 + hitSlop={hitSlop}> 85 + <PostControlButtonIcon icon={EyeSlash} /> 86 + </PostControlButton> 87 + 88 + <Prompt.Basic 89 + control={hidePromptControl} 90 + title={_(msg`Hide this post?`)} 91 + description={_( 92 + msg`This post will be hidden from feeds and threads. This cannot be undone.`, 93 + )} 94 + onConfirm={onConfirmHide} 95 + confirmButtonCta={_(msg`Hide`)} 96 + /> 97 + </> 98 + ) 99 + })
+62 -102
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 32 32 import {type Shadow} from '#/state/cache/post-shadow' 33 33 import {useProfileShadow} from '#/state/cache/profile-shadow' 34 34 import {useFeedFeedbackContext} from '#/state/feed-feedback' 35 - import { 36 - useHiddenPosts, 37 - useHiddenPostsApi, 38 - useLanguagePrefs, 39 - } from '#/state/preferences' 35 + import {useHiddenPosts, useLanguagePrefs} from '#/state/preferences' 40 36 import {usePinnedPostMutation} from '#/state/queries/pinned-post' 41 37 import { 42 38 usePostDeleteMutation, ··· 130 126 usePinnedPostMutation() 131 127 const requireSignIn = useRequireAuth() 132 128 const hiddenPosts = useHiddenPosts() 133 - const {hidePost} = useHiddenPostsApi() 134 129 const feedFeedback = useFeedFeedbackContext() 135 130 const openLink = useOpenLink() 136 131 const translate = useTranslate() ··· 139 134 const blockPromptControl = useDialogControl() 140 135 const reportDialogControl = useReportDialogControl() 141 136 const deletePromptControl = useDialogControl() 142 - const hidePromptControl = useDialogControl() 143 137 const postInteractionSettingsDialogControl = useDialogControl() 144 138 const quotePostDetachConfirmControl = useDialogControl() 145 139 const hideReplyConfirmControl = useDialogControl() ··· 274 268 } 275 269 } 276 270 277 - const onHidePost = () => { 278 - hidePost({uri: postUri}) 279 - ax.metric('thread:click:hideReplyForMe', {}) 280 - } 281 - 282 271 const hideInPWI = !!postAuthor.labels?.find( 283 272 label => label.val === '!no-unauthenticated', 284 273 ) ··· 351 340 } 352 341 } 353 342 354 - const canHidePostForMe = !isAuthor && !isPostHidden 355 343 const canHideReplyForEveryone = 356 344 !isAuthor && isRootPostAuthor && !isPostHidden && isReply 357 345 const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer ··· 593 581 </> 594 582 )} 595 583 596 - {hasSession && 597 - (canHideReplyForEveryone || canDetachQuote || canHidePostForMe) && ( 598 - <> 599 - <Menu.Divider /> 600 - <Menu.Group> 601 - {canHidePostForMe && ( 602 - <Menu.Item 603 - testID="postDropdownHideBtn" 604 - label={ 605 - isReply 606 - ? _(msg`Hide reply for me`) 607 - : _(msg`Hide post for me`) 608 - } 609 - onPress={() => hidePromptControl.open()}> 610 - <Menu.ItemText> 611 - {isReply 612 - ? _(msg`Hide reply for me`) 613 - : _(msg`Hide post for me`)} 614 - </Menu.ItemText> 615 - <Menu.ItemIcon icon={EyeSlash} position="right" /> 616 - </Menu.Item> 617 - )} 618 - {canHideReplyForEveryone && ( 619 - <Menu.Item 620 - testID="postDropdownHideBtn" 621 - label={ 622 - isReplyHiddenByThreadgate 623 - ? _(msg`Show reply for everyone`) 624 - : _(msg`Hide reply for everyone`) 625 - } 626 - onPress={ 627 - isReplyHiddenByThreadgate 628 - ? onToggleReplyVisibility 629 - : () => hideReplyConfirmControl.open() 630 - }> 631 - <Menu.ItemText> 632 - {isReplyHiddenByThreadgate 633 - ? _(msg`Show reply for everyone`) 634 - : _(msg`Hide reply for everyone`)} 635 - </Menu.ItemText> 636 - <Menu.ItemIcon 637 - icon={isReplyHiddenByThreadgate ? Eye : EyeSlash} 638 - position="right" 639 - /> 640 - </Menu.Item> 641 - )} 584 + {hasSession && (canHideReplyForEveryone || canDetachQuote) && ( 585 + <> 586 + <Menu.Divider /> 587 + <Menu.Group> 588 + {canHideReplyForEveryone && ( 589 + <Menu.Item 590 + testID="postDropdownHideBtn" 591 + label={ 592 + isReplyHiddenByThreadgate 593 + ? _(msg`Show reply for everyone`) 594 + : _(msg`Hide reply for everyone`) 595 + } 596 + onPress={ 597 + isReplyHiddenByThreadgate 598 + ? onToggleReplyVisibility 599 + : () => hideReplyConfirmControl.open() 600 + }> 601 + <Menu.ItemText> 602 + {isReplyHiddenByThreadgate 603 + ? _(msg`Show reply for everyone`) 604 + : _(msg`Hide reply for everyone`)} 605 + </Menu.ItemText> 606 + <Menu.ItemIcon 607 + icon={isReplyHiddenByThreadgate ? Eye : EyeSlash} 608 + position="right" 609 + /> 610 + </Menu.Item> 611 + )} 642 612 643 - {canDetachQuote && ( 644 - <Menu.Item 645 - disabled={isDetachPending} 646 - testID="postDropdownHideBtn" 647 - label={ 648 - quoteEmbed.isDetached 649 - ? _(msg`Re-attach quote`) 650 - : _(msg`Detach quote`) 613 + {canDetachQuote && ( 614 + <Menu.Item 615 + disabled={isDetachPending} 616 + testID="postDropdownHideBtn" 617 + label={ 618 + quoteEmbed.isDetached 619 + ? _(msg`Re-attach quote`) 620 + : _(msg`Detach quote`) 621 + } 622 + onPress={ 623 + quoteEmbed.isDetached 624 + ? onToggleQuotePostAttachment 625 + : () => quotePostDetachConfirmControl.open() 626 + }> 627 + <Menu.ItemText> 628 + {quoteEmbed.isDetached 629 + ? _(msg`Re-attach quote`) 630 + : _(msg`Detach quote`)} 631 + </Menu.ItemText> 632 + <Menu.ItemIcon 633 + icon={ 634 + isDetachPending 635 + ? Loader 636 + : quoteEmbed.isDetached 637 + ? Eye 638 + : EyeSlash 651 639 } 652 - onPress={ 653 - quoteEmbed.isDetached 654 - ? onToggleQuotePostAttachment 655 - : () => quotePostDetachConfirmControl.open() 656 - }> 657 - <Menu.ItemText> 658 - {quoteEmbed.isDetached 659 - ? _(msg`Re-attach quote`) 660 - : _(msg`Detach quote`)} 661 - </Menu.ItemText> 662 - <Menu.ItemIcon 663 - icon={ 664 - isDetachPending 665 - ? Loader 666 - : quoteEmbed.isDetached 667 - ? Eye 668 - : EyeSlash 669 - } 670 - position="right" 671 - /> 672 - </Menu.Item> 673 - )} 674 - </Menu.Group> 675 - </> 676 - )} 640 + position="right" 641 + /> 642 + </Menu.Item> 643 + )} 644 + </Menu.Group> 645 + </> 646 + )} 677 647 678 648 {hasSession && ( 679 649 <> ··· 764 734 onConfirm={onDeletePost} 765 735 confirmButtonCta={_(msg`Delete`)} 766 736 confirmButtonColor="negative" 767 - /> 768 - 769 - <Prompt.Basic 770 - control={hidePromptControl} 771 - title={isReply ? _(msg`Hide this reply?`) : _(msg`Hide this post?`)} 772 - description={_( 773 - msg`This post will be hidden from feeds and threads. This cannot be undone.`, 774 - )} 775 - onConfirm={onHidePost} 776 - confirmButtonCta={_(msg`Hide`)} 777 737 /> 778 738 779 739 <ReportDialog
+15 -2
src/components/PostControls/index.tsx
··· 31 31 import * as Skele from '#/components/Skeleton' 32 32 import {useAnalytics} from '#/analytics' 33 33 import {BookmarkButton} from './BookmarkButton' 34 + import {HideButton} from './HideButton' 34 35 import { 35 36 PostControlButton, 36 37 PostControlButtonIcon, ··· 93 94 const playHaptic = useHaptics() 94 95 const isBlocked = Boolean( 95 96 post.author.viewer?.blocking || 96 - post.author.viewer?.blockedBy || 97 - post.author.viewer?.blockingByList, 97 + post.author.viewer?.blockedBy || 98 + post.author.viewer?.blockingByList, 98 99 ) 99 100 const replyDisabled = post.viewer?.replyDisabled 100 101 const {gtPhone} = useBreakpoints() ··· 312 313 <View /> 313 314 </View> 314 315 <View style={[a.flex_row, a.justify_end, secondaryControlSpacingStyles]}> 316 + <HideButton 317 + post={post} 318 + big={big} 319 + logContext={logContext} 320 + hitSlop={{ 321 + right: secondaryControlSpacingStyles.gap / 2, 322 + }} 323 + /> 315 324 <BookmarkButton 316 325 post={post} 317 326 big={big} 318 327 logContext={logContext} 319 328 hitSlop={{ 329 + left: secondaryControlSpacingStyles.gap / 2, 320 330 right: secondaryControlSpacingStyles.gap / 2, 321 331 }} 322 332 /> ··· 401 411 </View> 402 412 </View> 403 413 <View style={[a.flex_row, a.justify_end, secondaryControlSpacingStyles]}> 414 + <View style={itemStyles}> 415 + <Skele.Circle blend size={size} /> 416 + </View> 404 417 <View style={itemStyles}> 405 418 <Skele.Circle blend size={size} /> 406 419 </View>