diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/hooks/useEntriesByView.ts b/apps/desktop/layer/renderer/src/modules/entry-column/hooks/useEntriesByView.ts index b25491f08..ce5e8de04 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/hooks/useEntriesByView.ts +++ b/apps/desktop/layer/renderer/src/modules/entry-column/hooks/useEntriesByView.ts @@ -99,18 +99,12 @@ const useRemoteEntries = (): UseEntriesReturn => { const refetch = useCallback(async () => void query.refetch(), [query]) const fetchNextPage = useCallback(async () => void query.fetchNextPage(), [query]) - const entriesIds = useMemo(() => { - if (!query.data || query.isLoading || query.isError) { - return [] - } - return query.data?.pages?.map((page) => page.data?.map((entry) => entry.entries.id)).flat() - }, [query.data, query.isLoading, query.isError]) if (!query.data || query.isLoading) { return fallbackReturn } return { - entriesIds, + entriesIds: query.entriesIds, hasNext: query.hasNextPage, hasUpdate, refetch, diff --git a/apps/mobile/src/modules/screen/atoms.ts b/apps/mobile/src/modules/screen/atoms.ts index 4750cf3d5..b381b0391 100644 --- a/apps/mobile/src/modules/screen/atoms.ts +++ b/apps/mobile/src/modules/screen/atoms.ts @@ -178,24 +178,13 @@ function useRemoteEntries(props?: UseEntriesProps): UseEntriesReturn { const refetch = useCallback(async () => void query.refetch(), [query]) const fetchNextPage = useCallback(async () => void query.fetchNextPage(), [query]) - const entriesIds = useMemo(() => { - if (!query.data || query.isLoading || query.isError) { - return [] - } - return ( - query.data?.pages - ?.map((page) => page.data?.map((entry) => entry.entries.id)) - .flat() - .filter((id) => typeof id === "string") || [] - ) - }, [query.data, query.isLoading, query.isError]) if (!query.data || query.isLoading) { return fallbackReturn } return { - entriesIds, + entriesIds: query.entriesIds, hasNext: query.hasNextPage, hasUpdate: false, refetch, diff --git a/packages/internal/store/src/entry/hooks.ts b/packages/internal/store/src/entry/hooks.ts index dca313b5d..79935d395 100644 --- a/packages/internal/store/src/entry/hooks.ts +++ b/packages/internal/store/src/entry/hooks.ts @@ -1,11 +1,12 @@ import type { FeedViewType } from "@follow/constants" import { useInfiniteQuery, useQuery } from "@tanstack/react-query" -import { useCallback } from "react" +import { useCallback, useMemo } from "react" import { useFeedUnreadIsDirty } from "../atoms/feed" import { FEED_COLLECTION_LIST } from "../constants/app" import { queryClient } from "../context" import { getSubscriptionByEntryId } from "../subscription/getter" +import { useSyncUnreadWhenUnMatch } from "../unread/hooks" import { getEntry } from "./getter" import { entrySyncServices, useEntryStore } from "./store" import type { EntryModel, FetchEntriesProps, FetchEntriesPropsSettings } from "./types" @@ -62,7 +63,7 @@ export const useEntriesQuery = ( const isPop = "history" in globalThis && "isPop" in globalThis.history && !!globalThis.history.isPop - return useInfiniteQuery({ + const query = useInfiniteQuery({ queryKey: [ "entries", feedId, @@ -96,6 +97,27 @@ export const useEntriesQuery = ( isPop ? Infinity : fetchUnread && feedUnreadDirty ? 0 : defaultStaleTime, enabled: !!props, }) + + const entriesIds = useMemo(() => { + if (!query.data || query.isLoading || query.isError) { + return [] + } + return ( + query.data?.pages + ?.map((page) => page.data?.map((entry) => entry.entries.id)) + .flat() + .filter((id) => typeof id === "string") || [] + ) + }, [query.data, query.isLoading, query.isError]) + + useSyncUnreadWhenUnMatch(entriesIds) + + return useMemo(() => { + return { + ...query, + entriesIds, + } + }, [entriesIds, query]) } export const usePrefetchEntryDetail = (entryId: string) => { diff --git a/packages/internal/store/src/unread/hooks.ts b/packages/internal/store/src/unread/hooks.ts index 94990e11f..d9054f867 100644 --- a/packages/internal/store/src/unread/hooks.ts +++ b/packages/internal/store/src/unread/hooks.ts @@ -2,6 +2,7 @@ import type { FeedViewType } from "@follow/constants" import { useMutation, useQuery } from "@tanstack/react-query" import { useCallback, useEffect } from "react" +import { getEntry } from "../entry/getter" import { useListFeedIds } from "../list/hooks" import { useSubscriptionIdsByView } from "../subscription/hooks" import { unreadCountAllSelector, unreadCountIdSelector, unreadCountIdsSelector } from "./selectors" @@ -15,6 +16,32 @@ export const usePrefetchUnread = () => { }) } +export const useSyncUnreadWhenUnMatch = (entryIds: string[]) => { + useEffect(() => { + const entries = entryIds.map((id) => getEntry(id)) + const unreadCountMap = entries.reduce( + (acc, entry) => { + if (entry && entry.feedId && !entry?.read) { + acc[entry.feedId] = (acc[entry.feedId] || 0) + 1 + } + return acc + }, + {} as Record, + ) + + const unread = useUnreadStore.getState().data + + const hasUnreadMismatch = Object.keys(unreadCountMap).some( + (feedId) => + !unread[feedId] || (unreadCountMap[feedId] && unreadCountMap[feedId] > unread[feedId]), + ) + + if (hasUnreadMismatch) { + unreadSyncService.resetFromRemote() + } + }, [entryIds.toString()]) +} + export const useAutoMarkAsRead = (entryId: string) => { const { mutate } = useMutation({ mutationFn: (entryId: string) => unreadSyncService.markEntryAsRead(entryId), diff --git a/packages/internal/store/src/unread/store.ts b/packages/internal/store/src/unread/store.ts index a029d9dc0..46300587f 100644 --- a/packages/internal/store/src/unread/store.ts +++ b/packages/internal/store/src/unread/store.ts @@ -2,6 +2,7 @@ import type { FeedViewType } from "@follow/constants" import type { UnreadSchema } from "@follow/database/schemas/types" import { EntryService } from "@follow/database/services/entry" import { UnreadService } from "@follow/database/services/unread" +import { isEqual } from "es-toolkit" import { setFeedUnreadDirty } from "../atoms/feed" import { apiClient } from "../context" @@ -34,6 +35,10 @@ class UnreadSyncService { query: {}, }) + if (isEqual(res.data, get().data)) { + return res.data + } + await unreadActions.upsertMany(res.data, { reset: true }) return res.data }