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 ··· 742 749 <Toggle.Platform /> 743 750 </Toggle.Item> 744 751 752 + <Toggle.Item 753 + name="hide_unreplyable_posts" 754 + label={_(msg`Hide posts that cannot be replied to from feeds`)} 755 + value={hideUnreplyablePosts} 756 + onChange={value => setHideUnreplyablePosts(value)} 757 + style={[a.w_full]}> 758 + <Toggle.LabelText style={[a.flex_1]}> 759 + <Trans>Hide posts that cannot be replied to from feeds</Trans> 760 + </Toggle.LabelText> 761 + <Toggle.Platform /> 762 + </Toggle.Item> 763 + <Admonition type="info" style={[a.flex_1]}> 764 + <Trans> 765 + Hides posts from feeds where replies are disabled (e.g. due to 766 + postgates or other restrictions). Does not affect thread views. 767 + </Trans> 768 + </Admonition> 769 + 745 770 <Toggle.Item 746 771 name="disable_verify_email_reminder" 747 772 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
··· 37 37 import {isIOS, isNative, isWeb} from '#/platform/detection' 38 38 import {listenPostCreated} from '#/state/events' 39 39 import {useFeedFeedbackContext} from '#/state/feed-feedback' 40 + import {useHideUnreplyablePosts} from '#/state/preferences/hide-unreplyable-posts' 40 41 import {useRepostCarouselEnabled} from '#/state/preferences/repost-carousel-enabled' 41 42 import {useTrendingSettings} from '#/state/preferences/trending' 42 43 import {STALE} from '#/state/queries' ··· 426 427 const {trendingDisabled, trendingVideoDisabled} = useTrendingSettings() 427 428 428 429 const repostCarouselEnabled = useRepostCarouselEnabled() 430 + const hideUnreplyablePosts = useHideUnreplyablePosts() 429 431 430 432 if (feedType === 'following') { 431 433 useRepostCarousel = repostCarouselEnabled ··· 546 548 ? groupReposts(page.slices) 547 549 : (page.slices as FeedPostSliceOrGroup[]) 548 550 551 + // Filter out posts that cannot be replied to if the setting is enabled 552 + if (hideUnreplyablePosts) { 553 + slices = slices.filter(slice => { 554 + if (slice.isRepostSlice) { 555 + // For repost slices, filter the inner slices 556 + slice.slices = slice.slices.filter(innerSlice => { 557 + // Check if any item in the slice has replyDisabled 558 + return !innerSlice.items.some( 559 + item => item.post.viewer?.replyDisabled === true, 560 + ) 561 + }) 562 + return slice.slices.length > 0 563 + } else { 564 + // For regular slices, check if any item has replyDisabled 565 + return !slice.items.some( 566 + item => item.post.viewer?.replyDisabled === true, 567 + ) 568 + } 569 + }) 570 + } 571 + 549 572 for (const slice of slices) { 550 573 sliceIndex++ 551 574 ··· 722 745 hasPressedShowLessUris, 723 746 ageAssuranceBannerState, 724 747 isCurrentFeedAtStartupSelected, 748 + hideUnreplyablePosts, 725 749 ]) 726 750 727 751 // events