about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-06-20 22:44:53 +0300
committerGitHub <noreply@github.com>2025-06-20 22:44:53 +0300
commit25baf1f6612103a9e777dc7175a9057ce3c399ae (patch)
tree06a3ff7c106de42a61d5b80e68a0fd875cb2d4b2
parentdcbcd1bb80b7f0c45cea93ed9c10d2b33ac2f0fa (diff)
downloadvoidsky-25baf1f6612103a9e777dc7175a9057ce3c399ae.tar.zst
Allow disabling replies (#8537)
-rw-r--r--src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx6
-rw-r--r--src/state/queries/notifications/settings.ts5
-rw-r--r--src/view/com/notifications/NotificationFeed.tsx4
-rw-r--r--src/view/screens/Notifications.tsx69
4 files changed, 61 insertions, 23 deletions
diff --git a/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
index b3e7c6cff..dddf838c6 100644
--- a/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
+++ b/src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx
@@ -53,11 +53,7 @@ export function ReplyNotificationSettingsScreen({}: Props) {
               </Admonition>
             </View>
           ) : (
-            <PreferenceControls
-              name="reply"
-              preference={preferences?.reply}
-              allowDisableInApp={false}
-            />
+            <PreferenceControls name="reply" preference={preferences?.reply} />
           )}
         </SettingsList.Container>
       </Layout.Content>
diff --git a/src/state/queries/notifications/settings.ts b/src/state/queries/notifications/settings.ts
index 9661bed1b..986d0b01d 100644
--- a/src/state/queries/notifications/settings.ts
+++ b/src/state/queries/notifications/settings.ts
@@ -14,7 +14,9 @@ import * as Toast from '#/view/com/util/Toast'
 const RQKEY_ROOT = 'notification-settings'
 const RQKEY = [RQKEY_ROOT]
 
-export function useNotificationSettingsQuery() {
+export function useNotificationSettingsQuery({
+  enabled,
+}: {enabled?: boolean} = {}) {
   const agent = useAgent()
 
   return useQuery({
@@ -23,6 +25,7 @@ export function useNotificationSettingsQuery() {
       const response = await agent.app.bsky.notification.getPreferences()
       return response.data.preferences
     },
+    enabled,
   })
 }
 export function useNotificationSettingsUpdateMutation() {
diff --git a/src/view/com/notifications/NotificationFeed.tsx b/src/view/com/notifications/NotificationFeed.tsx
index 1f87b3186..bad09929a 100644
--- a/src/view/com/notifications/NotificationFeed.tsx
+++ b/src/view/com/notifications/NotificationFeed.tsx
@@ -16,7 +16,7 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
 import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
 import {EmptyState} from '#/view/com/util/EmptyState'
 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
-import {List, type ListRef} from '#/view/com/util/List'
+import {List, type ListProps, type ListRef} from '#/view/com/util/List'
 import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
 import {NotificationFeedItem} from './NotificationFeedItem'
@@ -39,7 +39,7 @@ export function NotificationFeed({
   scrollElRef?: ListRef
   onPressTryAgain?: () => void
   onScrolledDownChange: (isScrolledDown: boolean) => void
-  ListHeaderComponent?: () => JSX.Element
+  ListHeaderComponent?: ListProps['ListHeaderComponent']
   refreshNotifications: () => Promise<void>
 }) {
   const initialNumToRender = useInitialNumToRender()
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index 528d6be87..efad2c665 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -17,6 +17,7 @@ import {logger} from '#/logger'
 import {isNative} from '#/platform/detection'
 import {emitSoftReset, listenSoftReset} from '#/state/events'
 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
+import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
 import {
   useUnreadNotifications,
   useUnreadNotificationsApi,
@@ -30,12 +31,13 @@ import {FAB} from '#/view/com/util/fab/FAB'
 import {type ListMethods} from '#/view/com/util/List'
 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
 import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
-import {atoms as a} from '#/alf'
+import {atoms as a, useTheme} from '#/alf'
 import {web} from '#/alf'
+import {Admonition} from '#/components/Admonition'
 import {ButtonIcon} from '#/components/Button'
 import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
 import * as Layout from '#/components/Layout'
-import {Link} from '#/components/Link'
+import {InlineLinkText, Link} from '#/components/Link'
 import {Loader} from '#/components/Loader'
 
 // We don't currently persist this across reloads since
@@ -53,13 +55,13 @@ export function NotificationsScreen({}: Props) {
   const unreadNotifs = useUnreadNotifications()
   const hasNew = !!unreadNotifs
   const {checkUnread: checkUnreadAll} = useUnreadNotificationsApi()
-  const [isLoadingAll, setIsLoadingAll] = React.useState(false)
-  const [isLoadingMentions, setIsLoadingMentions] = React.useState(false)
+  const [isLoadingAll, setIsLoadingAll] = useState(false)
+  const [isLoadingMentions, setIsLoadingMentions] = useState(false)
   const initialActiveTab = lastActiveTab
-  const [activeTab, setActiveTab] = React.useState(initialActiveTab)
+  const [activeTab, setActiveTab] = useState(initialActiveTab)
   const isLoading = activeTab === 0 ? isLoadingAll : isLoadingMentions
 
-  const onPageSelected = React.useCallback(
+  const onPageSelected = useCallback(
     (index: number) => {
       setActiveTab(index)
       lastActiveTab = index
@@ -68,7 +70,7 @@ export function NotificationsScreen({}: Props) {
   )
 
   const queryClient = useQueryClient()
-  const checkUnreadMentions = React.useCallback(
+  const checkUnreadMentions = useCallback(
     async ({invalidate}: {invalidate: boolean}) => {
       if (invalidate) {
         return truncateAndInvalidate(queryClient, NOTIFS_RQKEY('mentions'))
@@ -80,7 +82,7 @@ export function NotificationsScreen({}: Props) {
     [queryClient],
   )
 
-  const sections = React.useMemo(() => {
+  const sections = useMemo(() => {
     return [
       {
         title: _(msg`All`),
@@ -186,20 +188,20 @@ function NotificationsTab({
 }) {
   const {_} = useLingui()
   const setMinimalShellMode = useSetMinimalShellMode()
-  const [isScrolledDown, setIsScrolledDown] = React.useState(false)
-  const scrollElRef = React.useRef<ListMethods>(null)
+  const [isScrolledDown, setIsScrolledDown] = useState(false)
+  const scrollElRef = useRef<ListMethods>(null)
   const queryClient = useQueryClient()
   const isScreenFocused = useIsFocused()
   const isFocusedAndActive = isScreenFocused && isActive
 
   // event handlers
   // =
-  const scrollToTop = React.useCallback(() => {
+  const scrollToTop = useCallback(() => {
     scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
     setMinimalShellMode(false)
   }, [scrollElRef, setMinimalShellMode])
 
-  const onPressLoadLatest = React.useCallback(() => {
+  const onPressLoadLatest = useCallback(() => {
     scrollToTop()
     if (hasNew) {
       // render what we have now
@@ -238,7 +240,7 @@ function NotificationsTab({
   // on-visible setup
   // =
   useFocusEffect(
-    React.useCallback(() => {
+    useCallback(() => {
       if (isFocusedAndActive) {
         setMinimalShellMode(false)
         logger.debug('NotificationsScreen: Focus')
@@ -246,7 +248,8 @@ function NotificationsTab({
       }
     }, [setMinimalShellMode, onFocusCheckLatest, isFocusedAndActive]),
   )
-  React.useEffect(() => {
+
+  useEffect(() => {
     if (!isFocusedAndActive) {
       return
     }
@@ -262,6 +265,11 @@ function NotificationsTab({
           refreshNotifications={() => checkUnread({invalidate: true})}
           onScrolledDownChange={setIsScrolledDown}
           scrollElRef={scrollElRef}
+          ListHeaderComponent={
+            filter === 'mentions' ? (
+              <DisabledNotificationsWarning active={isFocusedAndActive} />
+            ) : null
+          }
         />
       </MainScrollProvider>
       {(isScrolledDown || hasNew) && (
@@ -274,3 +282,34 @@ function NotificationsTab({
     </>
   )
 }
+
+function DisabledNotificationsWarning({active}: {active: boolean}) {
+  const t = useTheme()
+  const {_} = useLingui()
+  const {data} = useNotificationSettingsQuery({enabled: active})
+
+  if (!data) return null
+
+  if (!data.reply.list && !data.quote.list && !data.mention.list) {
+    // mention tab notifications are disabled
+    return (
+      <View style={[a.py_md, a.px_lg, a.border_b, t.atoms.border_contrast_low]}>
+        <Admonition type="warning">
+          <Trans>
+            You have completely disabled reply, quote, and mention
+            notifications, so this tab will no longer update. To adjust this,
+            visit your{' '}
+            <InlineLinkText
+              label={_(msg`Visit your notification settings`)}
+              to={{screen: 'NotificationSettings'}}>
+              notification settings
+            </InlineLinkText>
+            .
+          </Trans>
+        </Admonition>
+      </View>
+    )
+  }
+
+  return null
+}