refactor: improve reading progress synchronization logic and add implementation guidelines

This commit is contained in:
Rodrigo Verdiani 2026-04-16 14:51:49 -03:00
parent 45c96185a3
commit 326eb513c5
2 changed files with 64 additions and 31 deletions

View File

@ -15,19 +15,14 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
if (jsonString) {
try {
const data: ReadingTrackerData = JSON.parse(jsonString);
return data.chapterPage[chapterId] || 1;
return data.chapterPage[chapterId] ?? null;
} catch (e) {
console.error("Failed to parse local progress", e);
}
}
return 1;
return null;
});
const [serverProgress, setServerProgress] = useState<{
pageNumber: number;
updatedAt: string;
} | null>(null);
const lastSyncedPage = useRef<number | null>(null);
const syncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@ -38,36 +33,36 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
);
const { mutate: updateProgress } = useUpdateProgress();
const serverProgress =
progressData &&
progressData.pageNumber !== undefined &&
progressData.updatedAt !== undefined
? {
pageNumber: progressData.pageNumber,
updatedAt: progressData.updatedAt,
}
: null;
// Sync local progress when chapterId changes
useEffect(() => {
const jsonString = localStorage.getItem("readingTrackerData");
if (jsonString) {
try {
const data: ReadingTrackerData = JSON.parse(jsonString);
const localPage = data.chapterPage[chapterId] || 1;
const localPage = data.chapterPage[chapterId] ?? null;
setCurrentPage(localPage);
lastSyncedPage.current = localPage;
} catch (e) {
// ignore
}
} else {
setCurrentPage(1);
lastSyncedPage.current = 1;
setCurrentPage(null);
lastSyncedPage.current = null;
}
}, [chapterId]);
// Sync server progress when available
useEffect(() => {
if (progressData && progressData.pageNumber !== undefined && progressData.updatedAt !== undefined) {
setServerProgress({
pageNumber: progressData.pageNumber,
updatedAt: progressData.updatedAt,
});
}
}, [progressData]);
const saveLocalProgress = useCallback(
(page: number) => {
(page: number, forceSync = false) => {
setCurrentPage(page);
const jsonString = localStorage.getItem("readingTrackerData");
let data: ReadingTrackerData = { chapterPage: {}, updatedAt: {} };
@ -85,8 +80,9 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
// Debounced backend sync
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
syncTimerRef.current = setTimeout(() => {
if (page !== lastSyncedPage.current) {
const performSync = () => {
if (forceSync || page !== lastSyncedPage.current) {
updateProgress({
data: {
mangaId,
@ -96,11 +92,23 @@ export const useReadingProgressSync = (mangaId: number, chapterId: number) => {
});
lastSyncedPage.current = page;
}
}, 5000);
};
if (forceSync) {
performSync();
} else {
syncTimerRef.current = setTimeout(performSync, 5000);
}
},
[mangaId, chapterId, updateProgress],
);
useEffect(() => {
return () => {
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
};
}, []);
const applyServerProgress = useCallback(() => {
if (serverProgress) {
saveLocalProgress(serverProgress.pageNumber);

View File

@ -53,15 +53,33 @@ const Chapter = () => {
const localPage = savedPage;
const serverPage = serverProgress?.pageNumber;
if (!localPage && !serverPage) {
if (localPage === null) {
// No local history for this chapter, use server or default to 1
const targetPage = serverPage || 1;
setCurrentPage(targetPage);
setVisibleCount(targetPage);
if (targetPage > 1) {
setIsAutoScrolling(true);
}
setHasInitialized(true);
return;
}
if (localPage && serverPage && localPage !== serverPage) {
setShowConflictDialog(true);
if (serverPage && localPage !== serverPage) {
// If local is just starting out (page 1), just take server
if (localPage <= 1) {
const targetPage = serverPage;
setCurrentPage(targetPage);
setVisibleCount(targetPage);
if (targetPage > 1) {
setIsAutoScrolling(true);
}
setHasInitialized(true);
} else {
setShowConflictDialog(true);
}
} else {
const targetPage = localPage || serverPage || 1;
const targetPage = localPage || 1;
setCurrentPage(targetPage);
setVisibleCount(targetPage);
if (targetPage > 1) {
@ -181,7 +199,7 @@ const Chapter = () => {
}
}, [infiniteScroll]);
if (isLoading || !hasInitialized) {
if (isLoading || (isLoadingProgress && !hasInitialized)) {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<MangaLoadingState />
@ -245,7 +263,7 @@ const Chapter = () => {
<Dialog open={showConflictDialog} onOpenChange={setShowConflictDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>Progress Conflict</DialogTitle>
<DialogTitle>Reading Progress Conflict</DialogTitle>
<DialogDescription>
You have different progress saved locally and on the server for
this chapter.
@ -258,10 +276,17 @@ const Chapter = () => {
</span>
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2 sm:gap-0">
<DialogFooter className="flex flex-row justify-end gap-3">
<Button
variant="outline"
onClick={() => {
if (savedPage && savedPage > 1) {
setCurrentPage(savedPage);
setVisibleCount(savedPage);
setIsAutoScrolling(true);
initialJumpDone.current = false;
saveLocalProgress(savedPage, true);
}
setHasInitialized(true);
setShowConflictDialog(false);
}}