about summary refs log tree commit diff
path: root/src/state
diff options
context:
space:
mode:
Diffstat (limited to 'src/state')
-rw-r--r--src/state/persisted/legacy.ts1
-rw-r--r--src/state/persisted/schema.ts2
-rw-r--r--src/state/preferences/hidden-posts.tsx64
-rw-r--r--src/state/preferences/index.tsx6
-rw-r--r--src/state/queries/actor-autocomplete.ts5
-rw-r--r--src/state/queries/feed.ts5
-rw-r--r--src/state/queries/notifications/unread.tsx3
-rw-r--r--src/state/queries/notifications/util.ts6
-rw-r--r--src/state/queries/post-feed.ts8
-rw-r--r--src/state/queries/preferences/index.ts11
10 files changed, 97 insertions, 14 deletions
diff --git a/src/state/persisted/legacy.ts b/src/state/persisted/legacy.ts
index f689c3d06..cdb542f5a 100644
--- a/src/state/persisted/legacy.ts
+++ b/src/state/persisted/legacy.ts
@@ -108,6 +108,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
     onboarding: {
       step: legacy.onboarding?.step || defaults.onboarding.step,
     },
+    hiddenPosts: defaults.hiddenPosts,
   }
 }
 
diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts
index 5ed8e01f3..27b1f26bd 100644
--- a/src/state/persisted/schema.ts
+++ b/src/state/persisted/schema.ts
@@ -37,6 +37,7 @@ export const schema = z.object({
   onboarding: z.object({
     step: z.string(),
   }),
+  hiddenPosts: z.array(z.string()).optional(), // should move to server
 })
 export type Schema = z.infer<typeof schema>
 
@@ -66,4 +67,5 @@ export const defaults: Schema = {
   onboarding: {
     step: 'Home',
   },
+  hiddenPosts: [],
 }
diff --git a/src/state/preferences/hidden-posts.tsx b/src/state/preferences/hidden-posts.tsx
new file mode 100644
index 000000000..11119ce75
--- /dev/null
+++ b/src/state/preferences/hidden-posts.tsx
@@ -0,0 +1,64 @@
+import React from 'react'
+import * as persisted from '#/state/persisted'
+
+type SetStateCb = (
+  s: persisted.Schema['hiddenPosts'],
+) => persisted.Schema['hiddenPosts']
+type StateContext = persisted.Schema['hiddenPosts']
+type ApiContext = {
+  hidePost: ({uri}: {uri: string}) => void
+  unhidePost: ({uri}: {uri: string}) => void
+}
+
+const stateContext = React.createContext<StateContext>(
+  persisted.defaults.hiddenPosts,
+)
+const apiContext = React.createContext<ApiContext>({
+  hidePost: () => {},
+  unhidePost: () => {},
+})
+
+export function Provider({children}: React.PropsWithChildren<{}>) {
+  const [state, setState] = React.useState(persisted.get('hiddenPosts'))
+
+  const setStateWrapped = React.useCallback(
+    (fn: SetStateCb) => {
+      const s = fn(persisted.get('hiddenPosts'))
+      setState(s)
+      persisted.write('hiddenPosts', s)
+    },
+    [setState],
+  )
+
+  const api = React.useMemo(
+    () => ({
+      hidePost: ({uri}: {uri: string}) => {
+        setStateWrapped(s => [...(s || []), uri])
+      },
+      unhidePost: ({uri}: {uri: string}) => {
+        setStateWrapped(s => (s || []).filter(u => u !== uri))
+      },
+    }),
+    [setStateWrapped],
+  )
+
+  React.useEffect(() => {
+    return persisted.onUpdate(() => {
+      setState(persisted.get('hiddenPosts'))
+    })
+  }, [setStateWrapped])
+
+  return (
+    <stateContext.Provider value={state}>
+      <apiContext.Provider value={api}>{children}</apiContext.Provider>
+    </stateContext.Provider>
+  )
+}
+
+export function useHiddenPosts() {
+  return React.useContext(stateContext)
+}
+
+export function useHiddenPostsApi() {
+  return React.useContext(apiContext)
+}
diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx
index 1f4348cfc..5ec659031 100644
--- a/src/state/preferences/index.tsx
+++ b/src/state/preferences/index.tsx
@@ -1,17 +1,21 @@
 import React from 'react'
 import {Provider as LanguagesProvider} from './languages'
 import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required'
+import {Provider as HiddenPostsProvider} from '../preferences/hidden-posts'
 
 export {useLanguagePrefs, useLanguagePrefsApi} from './languages'
 export {
   useRequireAltTextEnabled,
   useSetRequireAltTextEnabled,
 } from './alt-text-required'
+export * from './hidden-posts'
 
 export function Provider({children}: React.PropsWithChildren<{}>) {
   return (
     <LanguagesProvider>
-      <AltTextRequiredProvider>{children}</AltTextRequiredProvider>
+      <AltTextRequiredProvider>
+        <HiddenPostsProvider>{children}</HiddenPostsProvider>
+      </AltTextRequiredProvider>
     </LanguagesProvider>
   )
 }
diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts
index fe207371d..785e29765 100644
--- a/src/state/queries/actor-autocomplete.ts
+++ b/src/state/queries/actor-autocomplete.ts
@@ -11,6 +11,7 @@ import {
   getModerationOpts,
   useModerationOpts,
 } from './preferences'
+import {isInvalidHandle} from '#/lib/strings/handles'
 
 const DEFAULT_MOD_OPTS = getModerationOpts({
   userDid: '',
@@ -111,7 +112,7 @@ function computeSuggestions(
   }
   return items.filter(profile => {
     const mod = moderateProfile(profile, moderationOpts)
-    return !mod.account.filter
+    return !mod.account.filter && mod.account.cause?.type !== 'muted'
   })
 }
 
@@ -119,7 +120,7 @@ function prefixMatch(
   prefix: string,
   info: AppBskyActorDefs.ProfileViewBasic,
 ): boolean {
-  if (info.handle.includes(prefix)) {
+  if (!isInvalidHandle(info.handle) && info.handle.includes(prefix)) {
     return true
   }
   if (info.displayName?.toLocaleLowerCase().includes(prefix)) {
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index c87e95f03..7a55b4e18 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -218,11 +218,13 @@ const FOLLOWING_FEED_STUB: FeedSourceInfo = {
 export function usePinnedFeedsInfos(): {
   feeds: FeedSourceInfo[]
   hasPinnedCustom: boolean
+  isLoading: boolean
 } {
   const queryClient = useQueryClient()
   const [tabs, setTabs] = React.useState<FeedSourceInfo[]>([
     FOLLOWING_FEED_STUB,
   ])
+  const [isLoading, setLoading] = React.useState(true)
   const {data: preferences} = usePreferencesQuery()
 
   const hasPinnedCustom = React.useMemo<boolean>(() => {
@@ -284,10 +286,11 @@ export function usePinnedFeedsInfos(): {
       ) as FeedSourceInfo[]
 
       setTabs([FOLLOWING_FEED_STUB].concat(views))
+      setLoading(false)
     }
 
     fetchFeedInfo()
   }, [queryClient, setTabs, preferences?.feeds?.pinned])
 
-  return {feeds: tabs, hasPinnedCustom}
+  return {feeds: tabs, hasPinnedCustom, isLoading}
 }
diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx
index a189f20e4..abaabbf0e 100644
--- a/src/state/queries/notifications/unread.tsx
+++ b/src/state/queries/notifications/unread.tsx
@@ -89,6 +89,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
         // update & broadcast
         setNumUnread('')
         broadcast.postMessage({event: ''})
+        if (isNative) {
+          Notifications.setBadgeCountAsync(0)
+        }
       },
 
       async checkUnread({invalidate}: {invalidate?: boolean} = {}) {
diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts
index cc5943163..411a0f791 100644
--- a/src/state/queries/notifications/util.ts
+++ b/src/state/queries/notifications/util.ts
@@ -2,12 +2,12 @@ import {
   AppBskyNotificationListNotifications,
   ModerationOpts,
   moderateProfile,
-  moderatePost,
   AppBskyFeedDefs,
   AppBskyFeedPost,
   AppBskyFeedRepost,
   AppBskyFeedLike,
 } from '@atproto/api'
+import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
 import chunk from 'lodash.chunk'
 import {QueryClient} from '@tanstack/react-query'
 import {getAgent} from '../../session'
@@ -156,7 +156,7 @@ async function fetchSubjects(
 ): Promise<Map<string, AppBskyFeedDefs.PostView>> {
   const uris = new Set<string>()
   for (const notif of groupedNotifs) {
-    if (notif.subjectUri) {
+    if (notif.subjectUri && !notif.subjectUri.includes('feed.generator')) {
       uris.add(notif.subjectUri)
     }
   }
@@ -216,6 +216,8 @@ function getSubjectUri(
         ? notif.record.subject?.uri
         : undefined
     }
+  } else if (type === 'feedgen-like') {
+    return notif.reasonSubject
   }
 }
 
diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts
index b91af372f..0e943622a 100644
--- a/src/state/queries/post-feed.ts
+++ b/src/state/queries/post-feed.ts
@@ -1,10 +1,5 @@
 import React, {useCallback, useEffect, useRef} from 'react'
-import {
-  AppBskyFeedDefs,
-  AppBskyFeedPost,
-  moderatePost,
-  PostModeration,
-} from '@atproto/api'
+import {AppBskyFeedDefs, AppBskyFeedPost, PostModeration} from '@atproto/api'
 import {
   useInfiniteQuery,
   InfiniteData,
@@ -12,6 +7,7 @@ import {
   QueryClient,
   useQueryClient,
 } from '@tanstack/react-query'
+import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
 import {useFeedTuners} from '../preferences/feed-tuners'
 import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
 import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types'
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 872bb21af..a9aa7f26e 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -19,6 +19,7 @@ import {
 } from '#/state/queries/preferences/const'
 import {getModerationOpts} from '#/state/queries/preferences/moderation'
 import {STALE} from '#/state/queries'
+import {useHiddenPosts} from '#/state/preferences/hidden-posts'
 
 export * from '#/state/queries/preferences/types'
 export * from '#/state/queries/preferences/moderation'
@@ -94,15 +95,21 @@ export function usePreferencesQuery() {
 export function useModerationOpts() {
   const {currentAccount} = useSession()
   const prefs = usePreferencesQuery()
+  const hiddenPosts = useHiddenPosts()
   const opts = useMemo(() => {
     if (!prefs.data) {
       return
     }
-    return getModerationOpts({
+    const moderationOpts = getModerationOpts({
       userDid: currentAccount?.did || '',
       preferences: prefs.data,
     })
-  }, [currentAccount?.did, prefs.data])
+
+    return {
+      ...moderationOpts,
+      hiddenPosts,
+    }
+  }, [currentAccount?.did, prefs.data, hiddenPosts])
   return opts
 }