Skip to content

Commit d9a426b

Browse files
authored
fix(mobile): stabilize timeline initial load and scroll position (#4856)
1 parent d4aa9f4 commit d9a426b

File tree

6 files changed

+70
-11
lines changed

6 files changed

+70
-11
lines changed

apps/mobile/src/modules/entry-list/EntryListContentArticle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const EntryListContentArticle = ({
3939
const extraData: EntryExtraData = useMemo(() => ({ entryIds }), [entryIds])
4040

4141
const { fetchNextPage, isFetching, refetch, isRefetching, hasNextPage, fetchedTime, isReady } =
42-
useEntries()
42+
useEntries({ viewId: view, active })
4343

4444
const renderItem = useCallback(
4545
({ item: id, extraData, index }: ListRenderItemInfo<string>) => (

apps/mobile/src/modules/entry-list/EntryListContentPicture.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { FeedViewType } from "@follow/constants"
12
import { isFreeRole } from "@follow/constants"
23
import { useTypeScriptHappyCallback } from "@follow/hooks"
34
import { usePrefetchEntryTranslation } from "@follow/store/translation/hooks"
@@ -30,15 +31,19 @@ export const EntryListContentPicture = ({
3031
ref: forwardRef,
3132
entryIds,
3233
active,
34+
view,
3335
...rest
34-
}: { entryIds: string[] | null; active?: boolean } & Omit<
36+
}: { entryIds: string[] | null; active?: boolean; view: FeedViewType } & Omit<
3537
FlashListProps<string>,
3638
"data" | "renderItem"
3739
> & { ref?: React.Ref<ElementRef<typeof TimelineSelectorMasonryList> | null> }) => {
3840
const ref = useRef<FlashListRef<any>>(null)
3941

4042
useImperativeHandle(forwardRef, () => ref.current!)
41-
const { fetchNextPage, refetch, isRefetching, hasNextPage, isFetching, isReady } = useEntries()
43+
const { fetchNextPage, refetch, isRefetching, hasNextPage, isFetching, isReady } = useEntries({
44+
viewId: view,
45+
active,
46+
})
4247
const { onViewableItemsChanged, onScroll, viewableItems } = useOnViewableItemsChanged({
4348
disabled: active === false || isFetching,
4449
})

apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { FeedViewType } from "@follow/constants"
12
import { isFreeRole } from "@follow/constants"
23
import { usePrefetchEntryTranslation } from "@follow/store/translation/hooks"
34
import { useUserRole } from "@follow/store/user/hooks"
@@ -20,10 +21,14 @@ export const EntryListContentSocial = ({
2021
ref: forwardRef,
2122
entryIds,
2223
active,
23-
}: { entryIds: string[] | null; active?: boolean } & {
24+
view,
25+
}: { entryIds: string[] | null; active?: boolean; view: FeedViewType } & {
2426
ref?: React.Ref<ElementRef<typeof TimelineSelectorList> | null>
2527
}) => {
26-
const { fetchNextPage, isFetching, refetch, isRefetching, hasNextPage, isReady } = useEntries()
28+
const { fetchNextPage, isFetching, refetch, isRefetching, hasNextPage, isReady } = useEntries({
29+
viewId: view,
30+
active,
31+
})
2732
const extraData: EntryExtraData = useMemo(() => ({ entryIds }), [entryIds])
2833

2934
const ref = useRef<FlashListRef<any>>(null)

apps/mobile/src/modules/entry-list/EntryListContentVideo.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { FeedViewType } from "@follow/constants"
12
import { isFreeRole } from "@follow/constants"
23
import { useTypeScriptHappyCallback } from "@follow/hooks"
34
import { usePrefetchEntryTranslation } from "@follow/store/translation/hooks"
@@ -23,14 +24,18 @@ export const EntryListContentVideo = ({
2324
ref: forwardRef,
2425
entryIds,
2526
active,
27+
view,
2628
...rest
27-
}: { entryIds: string[] | null; active?: boolean } & Omit<
29+
}: { entryIds: string[] | null; active?: boolean; view: FeedViewType } & Omit<
2830
FlashListProps<string>,
2931
"data" | "renderItem"
3032
> & { ref?: React.Ref<ElementRef<typeof TimelineSelectorMasonryList> | null> }) => {
3133
const ref = useRef<FlashListRef<any>>(null)
3234
useImperativeHandle(forwardRef, () => ref.current!)
33-
const { fetchNextPage, refetch, isRefetching, isFetching, hasNextPage, isReady } = useEntries()
35+
const { fetchNextPage, refetch, isRefetching, isFetching, hasNextPage, isReady } = useEntries({
36+
viewId: view,
37+
active,
38+
})
3439
const { onViewableItemsChanged, onScroll, viewableItems } = useOnViewableItemsChanged({
3540
disabled: active === false || isFetching,
3641
})

apps/mobile/src/modules/entry-list/EntryListSelector.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { FeedViewType } from "@follow/constants"
22
import { useWhoami } from "@follow/store/user/hooks"
33
import type { FlashListRef } from "@shopify/flash-list"
44
import type { RefObject } from "react"
5-
import { useEffect } from "react"
5+
import { useEffect, useRef } from "react"
66

77
import { useGeneralSettingKey } from "@/src/atoms/settings/general"
88
import { withErrorBoundary } from "@/src/components/common/ErrorBoundary"
@@ -60,17 +60,33 @@ function EntryListSelectorImpl({ entryIds, viewId, active = true }: EntryListSel
6060
useEffect(() => {
6161
ref?.current?.scrollToOffset({
6262
offset: 0,
63+
animated: false,
6364
})
6465
}, [unreadOnly, ref])
6566

66-
const { isRefetching } = useEntries()
67+
const { isReady } = useEntries({ viewId, active })
68+
const hasResetAfterReadyRef = useRef(false)
6769
useEffect(() => {
68-
if (isRefetching) {
70+
if (!active) return
71+
if (!isReady) {
72+
hasResetAfterReadyRef.current = false
73+
return
74+
}
75+
if (!entryIds?.length) return
76+
if (hasResetAfterReadyRef.current) return
77+
78+
const frameId = requestAnimationFrame(() => {
6979
ref?.current?.scrollToOffset({
7080
offset: 0,
81+
animated: false,
7182
})
83+
})
84+
hasResetAfterReadyRef.current = true
85+
86+
return () => {
87+
cancelAnimationFrame(frameId)
7288
}
73-
}, [isRefetching, ref])
89+
}, [active, entryIds, isReady, ref, viewId])
7490

7591
useEffect(() => {
7692
if (!active) return

apps/mobile/src/modules/screen/atoms.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,34 @@ export function useEntries(props?: UseEntriesProps): UseEntriesReturn {
318318
const { viewId, active = true } = props || {}
319319
const remoteQuery = useRemoteEntries({ viewId, active })
320320
const localQuery = useLocalEntries({ viewId, active })
321+
const entryListContext = useEntryListContext()
322+
const selectedFeed = useSelectedFeed()
323+
324+
const isTimelineViewQuery = entryListContext.type === "timeline" && selectedFeed?.type === "view"
325+
326+
if (isTimelineViewQuery) {
327+
if (remoteQuery.isReady) {
328+
return remoteQuery
329+
}
330+
331+
if (remoteQuery.error) {
332+
return {
333+
...localQuery,
334+
error: remoteQuery.error,
335+
isReady: true,
336+
}
337+
}
338+
339+
return {
340+
...fallbackReturn,
341+
refetch: remoteQuery.refetch,
342+
fetchNextPage: remoteQuery.fetchNextPage,
343+
isLoading: remoteQuery.isLoading,
344+
isFetching: remoteQuery.isFetching,
345+
isRefetching: remoteQuery.isRefetching,
346+
}
347+
}
348+
321349
const query = remoteQuery.isReady ? remoteQuery : localQuery
322350
return {
323351
...query,

0 commit comments

Comments
 (0)