Add toggle to hide posts that can't be replied to from feeds #25

merged
opened by maxine.puppykitty.racing targeting main

Adds a toggle to hide posts that can't be replied to (e.g they are postgated to you). I have no idea how to test this one.

Changed files
+112 -7
src
screens
Settings
state
view
com
posts
+25
src/screens/Settings/DeerSettings.tsx
··· 88 88 useHideSimilarAccountsRecomm, 89 89 useSetHideSimilarAccountsRecomm, 90 90 } from '#/state/preferences/hide-similar-accounts-recommendations' 91 + import { 92 + useHideUnreplyablePosts, 93 + useSetHideUnreplyablePosts, 94 + } from '#/state/preferences/hide-unreplyable-posts' 91 95 import { 92 96 useHighQualityImages, 93 97 useSetHighQualityImages, ··· 443 447 const hideSimilarAccountsRecomm = useHideSimilarAccountsRecomm() 444 448 const setHideSimilarAccountsRecomm = useSetHideSimilarAccountsRecomm() 445 449 450 + const hideUnreplyablePosts = useHideUnreplyablePosts() 451 + const setHideUnreplyablePosts = useSetHideUnreplyablePosts() 452 + 446 453 const disableVerifyEmailReminder = useDisableVerifyEmailReminder() 447 454 const setDisableVerifyEmailReminder = useSetDisableVerifyEmailReminder() 448 455 ··· 754 761 <Toggle.Platform /> 755 762 </Toggle.Item> 756 763 764 + <Toggle.Item 765 + name="hide_unreplyable_posts" 766 + label={_(msg`Hide posts that cannot be replied to from feeds`)} 767 + value={hideUnreplyablePosts} 768 + onChange={value => setHideUnreplyablePosts(value)} 769 + style={[a.w_full]}> 770 + <Toggle.LabelText style={[a.flex_1]}> 771 + <Trans>Hide posts that cannot be replied to from feeds</Trans> 772 + </Toggle.LabelText> 773 + <Toggle.Platform /> 774 + </Toggle.Item> 775 + <Admonition type="info" style={[a.flex_1]}> 776 + <Trans> 777 + Hides posts from feeds where replies are disabled (e.g. due to 778 + postgates or other restrictions). Does not affect thread views. 779 + </Trans> 780 + </Admonition> 781 + 757 782 <Toggle.Item 758 783 name="disable_verify_email_reminder" 759 784 label={_(msg`Disable verify email reminder`)}
+2
src/state/persisted/schema.ts
··· 166 166 }) 167 167 .optional(), 168 168 highQualityImages: z.boolean().optional(), 169 + hideUnreplyablePosts: z.boolean().optional(), 169 170 170 171 showExternalShareButtons: z.boolean().optional(), 171 172 ··· 269 270 ], 270 271 }, 271 272 highQualityImages: false, 273 + hideUnreplyablePosts: false, 272 274 showExternalShareButtons: false, 273 275 } 274 276
+51
src/state/preferences/hide-unreplyable-posts.tsx
··· 1 + import React from 'react' 2 + 3 + import * as persisted from '#/state/persisted' 4 + 5 + type StateContext = persisted.Schema['hideUnreplyablePosts'] 6 + type SetContext = (v: persisted.Schema['hideUnreplyablePosts']) => void 7 + 8 + const stateContext = React.createContext<StateContext>( 9 + persisted.defaults.hideUnreplyablePosts, 10 + ) 11 + const setContext = React.createContext<SetContext>( 12 + (_: persisted.Schema['hideUnreplyablePosts']) => {}, 13 + ) 14 + 15 + export function Provider({children}: React.PropsWithChildren<{}>) { 16 + const [state, setState] = React.useState( 17 + persisted.get('hideUnreplyablePosts'), 18 + ) 19 + 20 + const setStateWrapped = React.useCallback( 21 + (hideUnreplyablePosts: persisted.Schema['hideUnreplyablePosts']) => { 22 + setState(hideUnreplyablePosts) 23 + persisted.write('hideUnreplyablePosts', hideUnreplyablePosts) 24 + }, 25 + [setState], 26 + ) 27 + 28 + React.useEffect(() => { 29 + return persisted.onUpdate('hideUnreplyablePosts', nextValue => { 30 + setState(nextValue) 31 + }) 32 + }, [setStateWrapped]) 33 + 34 + return ( 35 + <stateContext.Provider value={state}> 36 + <setContext.Provider value={setStateWrapped}> 37 + {children} 38 + </setContext.Provider> 39 + </stateContext.Provider> 40 + ) 41 + } 42 + 43 + export function useHideUnreplyablePosts() { 44 + return ( 45 + React.useContext(stateContext) ?? persisted.defaults.hideUnreplyablePosts 46 + ) 47 + } 48 + 49 + export function useSetHideUnreplyablePosts() { 50 + return React.useContext(setContext) 51 + }
+10 -7
src/state/preferences/index.tsx
··· 26 26 import {Provider as HiddenPostsProvider} from './hidden-posts' 27 27 import {Provider as HideFeedsPromoTabProvider} from './hide-feeds-promo-tab' 28 28 import {Provider as HideSimilarAccountsRecommProvider} from './hide-similar-accounts-recommendations' 29 + import {Provider as HideUnreplyablePostsProvider} from './hide-unreplyable-posts' 29 30 import {Provider as HighQualityImagesProvider} from './high-quality-images' 30 31 import {Provider as InAppBrowserProvider} from './in-app-browser' 31 32 import {Provider as KawaiiProvider} from './kawaii' ··· 96 97 <DisableFollowedByMetricsProvider> 97 98 <DisablePostsMetricsProvider> 98 99 <HideSimilarAccountsRecommProvider> 99 - <EnableSquareAvatarsProvider> 100 - <EnableSquareButtonsProvider> 101 - <DisableVerifyEmailReminderProvider> 102 - {children} 103 - </DisableVerifyEmailReminderProvider> 104 - </EnableSquareButtonsProvider> 105 - </EnableSquareAvatarsProvider> 100 + <HideUnreplyablePostsProvider> 101 + <EnableSquareAvatarsProvider> 102 + <EnableSquareButtonsProvider> 103 + <DisableVerifyEmailReminderProvider> 104 + {children} 105 + </DisableVerifyEmailReminderProvider> 106 + </EnableSquareButtonsProvider> 107 + </EnableSquareAvatarsProvider> 108 + </HideUnreplyablePostsProvider> 106 109 </HideSimilarAccountsRecommProvider> 107 110 </DisablePostsMetricsProvider> 108 111 </DisableFollowedByMetricsProvider>
+24
src/view/com/posts/PostFeed.tsx
··· 38 38 import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow' 39 39 import {listenPostCreated} from '#/state/events' 40 40 import {useFeedFeedbackContext} from '#/state/feed-feedback' 41 + import {useHideUnreplyablePosts} from '#/state/preferences/hide-unreplyable-posts' 41 42 import {useRepostCarouselEnabled} from '#/state/preferences/repost-carousel-enabled' 42 43 import {useTrendingSettings} from '#/state/preferences/trending' 43 44 import {STALE} from '#/state/queries' ··· 433 434 const {trendingDisabled, trendingVideoDisabled} = useTrendingSettings() 434 435 435 436 const repostCarouselEnabled = useRepostCarouselEnabled() 437 + const hideUnreplyablePosts = useHideUnreplyablePosts() 436 438 437 439 if (feedType === 'following') { 438 440 useRepostCarousel = repostCarouselEnabled ··· 562 564 ? groupReposts(page.slices) 563 565 : (page.slices as FeedPostSliceOrGroup[]) 564 566 567 + // Filter out posts that cannot be replied to if the setting is enabled 568 + if (hideUnreplyablePosts) { 569 + slices = slices.filter(slice => { 570 + if (slice.isRepostSlice) { 571 + // For repost slices, filter the inner slices 572 + slice.slices = slice.slices.filter(innerSlice => { 573 + // Check if any item in the slice has replyDisabled 574 + return !innerSlice.items.some( 575 + item => item.post.viewer?.replyDisabled === true, 576 + ) 577 + }) 578 + return slice.slices.length > 0 579 + } else { 580 + // For regular slices, check if any item has replyDisabled 581 + return !slice.items.some( 582 + item => item.post.viewer?.replyDisabled === true, 583 + ) 584 + } 585 + }) 586 + } 587 + 565 588 for (const slice of slices) { 566 589 sliceIndex++ 567 590 ··· 769 792 isCurrentFeedAtStartupSelected, 770 793 gate, 771 794 blockedOrMutedAuthors, 795 + hideUnreplyablePosts, 772 796 ]) 773 797 774 798 // events