nicolium: reintroduce pull to refresh to timelines

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-09 11:38:47 +01:00
parent 7192fda058
commit 62f343d37c
4 changed files with 64 additions and 29 deletions

View File

@ -3,6 +3,7 @@ import clsx from 'clsx';
import React, { useMemo, useRef, useState } from 'react';
import { defineMessages, FormattedList, FormattedMessage, useIntl } from 'react-intl';
import PullToRefresh from '@/components/pull-to-refresh';
import ScrollTopButton from '@/components/scroll-top-button';
import ScrollableList, { type IScrollableList } from '@/components/scrollable-list';
import Status, { StatusFollowedTagInfo } from '@/components/statuses/status';
@ -397,6 +398,7 @@ const Timeline: React.FC<ITimeline> = ({
isFetching,
isPending,
hasNextPage,
refetch,
} = query;
const handleMoveUp = (index: number) => {
@ -542,21 +544,23 @@ const Timeline: React.FC<ITimeline> = ({
{featuredStatusIds && featuredStatusIds.length > 3 && entries?.length > 0 && (
<SkipPinned onClick={handleSkipPinned} />
)}
<ScrollableList
id='status-list'
key='scrollable-list'
scrollKey={timelineId}
isLoading={isFetching}
showLoading={isPending}
placeholderComponent={() => <PlaceholderTimelineStatus />}
placeholderCount={20}
ref={node}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
{...props}
>
{renderedEntries}
</ScrollableList>
<PullToRefresh onRefresh={refetch} isPullable={!isFetching}>
<ScrollableList
id='status-list'
key='scrollable-list'
scrollKey={timelineId}
isLoading={isFetching}
showLoading={isPending}
placeholderComponent={() => <PlaceholderTimelineStatus />}
placeholderCount={20}
ref={node}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
{...props}
>
{renderedEntries}
</ScrollableList>
</PullToRefresh>
</>
);
};

View File

@ -33,16 +33,25 @@ const useTimeline = (
fetchInitial();
}, [timelineId]);
const fetchInitial = useCallback(async () => {
timelineActions.setLoading(timelineId, true);
try {
const response = await fetcher();
importEntities({ statuses: response.items });
timelineActions.expandTimeline(timelineId, response.items, !!response.next, true, restoring);
} catch (error) {
timelineActions.setError(timelineId, true);
}
}, [timelineId, restoring]);
const fetchInitial = useCallback(
async (isRestoring = restoring) => {
timelineActions.setLoading(timelineId, true);
try {
const response = await fetcher();
importEntities({ statuses: response.items });
timelineActions.expandTimeline(
timelineId,
response.items,
!!response.next,
true,
isRestoring,
);
} catch (error) {
timelineActions.setError(timelineId, true);
}
},
[timelineId, restoring],
);
const fetchNextPage = useCallback(async () => {
timelineActions.setLoading(timelineId, true);
@ -82,9 +91,14 @@ const useTimeline = (
[timelineId, fetcher],
);
const refetch = useCallback(() => {
timelineActions.resetTimeline(timelineId);
return fetchInitial(false);
}, [timelineId, fetchInitial]);
return useMemo(
() => ({ ...timeline, timelineId, fetchNextPage, dequeueEntries, fillGap }),
[timeline, timelineId, fetchNextPage, dequeueEntries, fillGap],
() => ({ ...timeline, timelineId, fetchNextPage, dequeueEntries, fillGap, refetch }),
[timeline, timelineId, fetchNextPage, dequeueEntries, fillGap, refetch],
);
};

View File

@ -1,3 +1,5 @@
import { useRef } from 'react';
import { useClient } from '@/hooks/use-client';
import { useTimeline } from './use-timeline';
@ -23,11 +25,21 @@ const useHomeTimeline = (
) => {
const client = useClient();
const stream = 'user';
const restoreMaxId = useRef(maxId);
return useTimeline(
'home',
(paginationParams) =>
client.timelines.homeTimeline({ ...params, ...(paginationParams || { max_id: maxId }) }),
(paginationParams) => {
const initialPagination = restoreMaxId.current ? { max_id: restoreMaxId.current } : undefined;
if (paginationParams == null) {
restoreMaxId.current = undefined;
}
return client.timelines.homeTimeline({
...params,
...(paginationParams ?? initialPagination),
});
},
{ stream },
!!maxId,
);

View File

@ -72,6 +72,7 @@ interface State {
replacePendingStatus: (idempotencyKey: string, status: Status) => void;
deletePendingStatus: (idempotencyKey: string) => void;
filterTimelines: (accountId: string) => void;
resetTimeline: (timelineId: string) => void;
};
}
@ -397,6 +398,10 @@ const useTimelinesStore = create<State>()(
timeline.queuedCount = timeline.queuedEntries.length;
}
}),
resetTimeline: (timelineId) =>
set((state) => {
state.timelines[timelineId] = createEmptyTimeline();
}),
},
})),
);