An ATproto social media client -- with an independent Appview.
7
fork

Configure Feed

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

Allow disabling replies (#8537)

authored by samuel.fm and committed by

GitHub 25baf1f6 dcbcd1bb

+61 -23
+1 -5
src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
··· 53 53 </Admonition> 54 54 </View> 55 55 ) : ( 56 - <PreferenceControls 57 - name="reply" 58 - preference={preferences?.reply} 59 - allowDisableInApp={false} 60 - /> 56 + <PreferenceControls name="reply" preference={preferences?.reply} /> 61 57 )} 62 58 </SettingsList.Container> 63 59 </Layout.Content>
+4 -1
src/state/queries/notifications/settings.ts
··· 14 14 const RQKEY_ROOT = 'notification-settings' 15 15 const RQKEY = [RQKEY_ROOT] 16 16 17 - export function useNotificationSettingsQuery() { 17 + export function useNotificationSettingsQuery({ 18 + enabled, 19 + }: {enabled?: boolean} = {}) { 18 20 const agent = useAgent() 19 21 20 22 return useQuery({ ··· 23 25 const response = await agent.app.bsky.notification.getPreferences() 24 26 return response.data.preferences 25 27 }, 28 + enabled, 26 29 }) 27 30 } 28 31 export function useNotificationSettingsUpdateMutation() {
+2 -2
src/view/com/notifications/NotificationFeed.tsx
··· 16 16 import {useNotificationFeedQuery} from '#/state/queries/notifications/feed' 17 17 import {EmptyState} from '#/view/com/util/EmptyState' 18 18 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 19 - import {List, type ListRef} from '#/view/com/util/List' 19 + import {List, type ListProps, type ListRef} from '#/view/com/util/List' 20 20 import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 21 21 import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' 22 22 import {NotificationFeedItem} from './NotificationFeedItem' ··· 39 39 scrollElRef?: ListRef 40 40 onPressTryAgain?: () => void 41 41 onScrolledDownChange: (isScrolledDown: boolean) => void 42 - ListHeaderComponent?: () => JSX.Element 42 + ListHeaderComponent?: ListProps['ListHeaderComponent'] 43 43 refreshNotifications: () => Promise<void> 44 44 }) { 45 45 const initialNumToRender = useInitialNumToRender()
+54 -15
src/view/screens/Notifications.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' ··· 17 17 import {isNative} from '#/platform/detection' 18 18 import {emitSoftReset, listenSoftReset} from '#/state/events' 19 19 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 20 + import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 20 21 import { 21 22 useUnreadNotifications, 22 23 useUnreadNotificationsApi, ··· 30 31 import {type ListMethods} from '#/view/com/util/List' 31 32 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 32 33 import {MainScrollProvider} from '#/view/com/util/MainScrollProvider' 33 - import {atoms as a} from '#/alf' 34 + import {atoms as a, useTheme} from '#/alf' 34 35 import {web} from '#/alf' 36 + import {Admonition} from '#/components/Admonition' 35 37 import {ButtonIcon} from '#/components/Button' 36 38 import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2' 37 39 import * as Layout from '#/components/Layout' 38 - import {Link} from '#/components/Link' 40 + import {InlineLinkText, Link} from '#/components/Link' 39 41 import {Loader} from '#/components/Loader' 40 42 41 43 // We don't currently persist this across reloads since ··· 53 55 const unreadNotifs = useUnreadNotifications() 54 56 const hasNew = !!unreadNotifs 55 57 const {checkUnread: checkUnreadAll} = useUnreadNotificationsApi() 56 - const [isLoadingAll, setIsLoadingAll] = React.useState(false) 57 - const [isLoadingMentions, setIsLoadingMentions] = React.useState(false) 58 + const [isLoadingAll, setIsLoadingAll] = useState(false) 59 + const [isLoadingMentions, setIsLoadingMentions] = useState(false) 58 60 const initialActiveTab = lastActiveTab 59 - const [activeTab, setActiveTab] = React.useState(initialActiveTab) 61 + const [activeTab, setActiveTab] = useState(initialActiveTab) 60 62 const isLoading = activeTab === 0 ? isLoadingAll : isLoadingMentions 61 63 62 - const onPageSelected = React.useCallback( 64 + const onPageSelected = useCallback( 63 65 (index: number) => { 64 66 setActiveTab(index) 65 67 lastActiveTab = index ··· 68 70 ) 69 71 70 72 const queryClient = useQueryClient() 71 - const checkUnreadMentions = React.useCallback( 73 + const checkUnreadMentions = useCallback( 72 74 async ({invalidate}: {invalidate: boolean}) => { 73 75 if (invalidate) { 74 76 return truncateAndInvalidate(queryClient, NOTIFS_RQKEY('mentions')) ··· 80 82 [queryClient], 81 83 ) 82 84 83 - const sections = React.useMemo(() => { 85 + const sections = useMemo(() => { 84 86 return [ 85 87 { 86 88 title: _(msg`All`), ··· 186 188 }) { 187 189 const {_} = useLingui() 188 190 const setMinimalShellMode = useSetMinimalShellMode() 189 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 190 - const scrollElRef = React.useRef<ListMethods>(null) 191 + const [isScrolledDown, setIsScrolledDown] = useState(false) 192 + const scrollElRef = useRef<ListMethods>(null) 191 193 const queryClient = useQueryClient() 192 194 const isScreenFocused = useIsFocused() 193 195 const isFocusedAndActive = isScreenFocused && isActive 194 196 195 197 // event handlers 196 198 // = 197 - const scrollToTop = React.useCallback(() => { 199 + const scrollToTop = useCallback(() => { 198 200 scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0}) 199 201 setMinimalShellMode(false) 200 202 }, [scrollElRef, setMinimalShellMode]) 201 203 202 - const onPressLoadLatest = React.useCallback(() => { 204 + const onPressLoadLatest = useCallback(() => { 203 205 scrollToTop() 204 206 if (hasNew) { 205 207 // render what we have now ··· 238 240 // on-visible setup 239 241 // = 240 242 useFocusEffect( 241 - React.useCallback(() => { 243 + useCallback(() => { 242 244 if (isFocusedAndActive) { 243 245 setMinimalShellMode(false) 244 246 logger.debug('NotificationsScreen: Focus') ··· 246 248 } 247 249 }, [setMinimalShellMode, onFocusCheckLatest, isFocusedAndActive]), 248 250 ) 249 - React.useEffect(() => { 251 + 252 + useEffect(() => { 250 253 if (!isFocusedAndActive) { 251 254 return 252 255 } ··· 262 265 refreshNotifications={() => checkUnread({invalidate: true})} 263 266 onScrolledDownChange={setIsScrolledDown} 264 267 scrollElRef={scrollElRef} 268 + ListHeaderComponent={ 269 + filter === 'mentions' ? ( 270 + <DisabledNotificationsWarning active={isFocusedAndActive} /> 271 + ) : null 272 + } 265 273 /> 266 274 </MainScrollProvider> 267 275 {(isScrolledDown || hasNew) && ( ··· 274 282 </> 275 283 ) 276 284 } 285 + 286 + function DisabledNotificationsWarning({active}: {active: boolean}) { 287 + const t = useTheme() 288 + const {_} = useLingui() 289 + const {data} = useNotificationSettingsQuery({enabled: active}) 290 + 291 + if (!data) return null 292 + 293 + if (!data.reply.list && !data.quote.list && !data.mention.list) { 294 + // mention tab notifications are disabled 295 + return ( 296 + <View style={[a.py_md, a.px_lg, a.border_b, t.atoms.border_contrast_low]}> 297 + <Admonition type="warning"> 298 + <Trans> 299 + You have completely disabled reply, quote, and mention 300 + notifications, so this tab will no longer update. To adjust this, 301 + visit your{' '} 302 + <InlineLinkText 303 + label={_(msg`Visit your notification settings`)} 304 + to={{screen: 'NotificationSettings'}}> 305 + notification settings 306 + </InlineLinkText> 307 + . 308 + </Trans> 309 + </Admonition> 310 + </View> 311 + ) 312 + } 313 + 314 + return null 315 + }