[Notifications] Add a Mentions tab (#7044)

* Split out NotificationsTab

* Remove unused route parameter

* Refine the split between components

* Hoist some logic out of NotificationFeed

* Remove unused option

* Add all|conversations to query, hardcode "all"

* Add a Conversations tab

* Rename to Mentions

* Bump packages

* Rename fields

* Fix oopsie

* Simplify header

* Track active tab

* Fix types

* Separate logic for tabs

* Better border for first unread

* Highlight unread for all only

* Fix spinner races

* Fix fetchPage races

* Fix bottom bar border being obscured by glimmer

* Remember last tab within the session

* One tab at a time

* Fix TS

* Handle all RQKEY usages

* Nit
This commit is contained in:
dan
2024-12-12 17:37:07 +00:00
committed by GitHub
parent 10e241e7eb
commit f8cdd6b9ae
15 changed files with 409 additions and 267 deletions
+20 -14
View File
@@ -52,25 +52,22 @@ const PAGE_SIZE = 30
type RQPageParam = string | undefined
const RQKEY_ROOT = 'notification-feed'
export function RQKEY(priority?: false) {
return [RQKEY_ROOT, priority]
export function RQKEY(filter: 'all' | 'mentions') {
return [RQKEY_ROOT, filter]
}
export function useNotificationFeedQuery(opts?: {
export function useNotificationFeedQuery(opts: {
enabled?: boolean
overridePriorityNotifications?: boolean
filter: 'all' | 'mentions'
}) {
const agent = useAgent()
const queryClient = useQueryClient()
const moderationOpts = useModerationOpts()
const unreads = useUnreadNotificationsApi()
const enabled = opts?.enabled !== false
const enabled = opts.enabled !== false
const filter = opts.filter
const {uris: hiddenReplyUris} = useThreadgateHiddenReplyUris()
// false: force showing all notifications
// undefined: let the server decide
const priority = opts?.overridePriorityNotifications ? false : undefined
const selectArgs = useMemo(() => {
return {
moderationOpts,
@@ -91,14 +88,23 @@ export function useNotificationFeedQuery(opts?: {
RQPageParam
>({
staleTime: STALE.INFINITY,
queryKey: RQKEY(priority),
queryKey: RQKEY(filter),
async queryFn({pageParam}: {pageParam: RQPageParam}) {
let page
if (!pageParam) {
if (filter === 'all' && !pageParam) {
// for the first page, we check the cached page held by the unread-checker first
page = unreads.getCachedUnreadPage()
}
if (!page) {
let reasons: string[] = []
if (filter === 'mentions') {
reasons = [
// Anything that's a post
'mention',
'reply',
'quote',
]
}
const {page: fetchedPage} = await fetchPage({
agent,
limit: PAGE_SIZE,
@@ -106,13 +112,13 @@ export function useNotificationFeedQuery(opts?: {
queryClient,
moderationOpts,
fetchAdditionalData: true,
priority,
reasons,
})
page = fetchedPage
}
// if the first page has an unread, mark all read
if (!pageParam) {
if (filter === 'all' && !pageParam) {
// if the first page has an unread, mark all read
unreads.markAllRead()
}
+6 -3
View File
@@ -45,7 +45,8 @@ export function useNotificationSettingsMutation() {
},
onSettled: () => {
invalidateCachedUnreadPage()
queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS()})
queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS('all')})
queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS('mentions')})
},
})
}
@@ -54,7 +55,7 @@ function eagerlySetCachedPriority(
queryClient: ReturnType<typeof useQueryClient>,
enabled: boolean,
) {
queryClient.setQueryData(RQKEY_NOTIFS(), (old: any) => {
function updateData(old: any) {
if (!old) return old
return {
...old,
@@ -65,5 +66,7 @@ function eagerlySetCachedPriority(
}
}),
}
})
}
queryClient.setQueryData(RQKEY_NOTIFS('all'), updateData)
queryClient.setQueryData(RQKEY_NOTIFS('mentions'), updateData)
}
+14 -2
View File
@@ -2,7 +2,7 @@
* A kind of companion API to ./feed.ts. See that file for more info.
*/
import React from 'react'
import React, {useRef} from 'react'
import {AppState} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import EventEmitter from 'eventemitter3'
@@ -105,6 +105,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
}
}, [setNumUnread])
const isFetchingRef = useRef(false)
// create API
const api = React.useMemo<ApiContext>(() => {
return {
@@ -138,6 +140,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
}
}
if (isFetchingRef.current) {
return
}
// Do not move this without ensuring it gets a symmetrical reset in the finally block.
isFetchingRef.current = true
// count
const {page, indexedAt: lastIndexed} = await fetchPage({
agent,
@@ -145,6 +153,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
limit: 40,
queryClient,
moderationOpts,
reasons: [],
// only fetch subjects when the page is going to be used
// in the notifications query, otherwise skip it
@@ -174,11 +183,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
// update & broadcast
setNumUnread(unreadCountStr)
if (invalidate) {
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
truncateAndInvalidate(queryClient, RQKEY_NOTIFS('all'))
truncateAndInvalidate(queryClient, RQKEY_NOTIFS('mentions'))
}
broadcast.postMessage({event: unreadCountStr})
} catch (e) {
logger.warn('Failed to check unread notifications', {error: e})
} finally {
isFetchingRef.current = false
}
},
+3 -2
View File
@@ -31,6 +31,7 @@ export async function fetchPage({
queryClient,
moderationOpts,
fetchAdditionalData,
reasons,
}: {
agent: BskyAgent
cursor: string | undefined
@@ -38,7 +39,7 @@ export async function fetchPage({
queryClient: QueryClient
moderationOpts: ModerationOpts | undefined
fetchAdditionalData: boolean
priority?: boolean
reasons: string[]
}): Promise<{
page: FeedPage
indexedAt: string | undefined
@@ -46,7 +47,7 @@ export async function fetchPage({
const res = await agent.listNotifications({
limit,
cursor,
// priority,
reasons,
})
const indexedAt = res.data.notifications[0]?.indexedAt