import { useCallback, useRef, useState } from "react"; import { DEFAULT_ENTRANCE_DURATION_MS, ENTRANCE_ANIMATION_BASE_DELAY_MS, MAX_ENTRANCE_DELAY_MS, } from "@/shared/entranceAnimation"; import { getVisiblePageIndices } from "@/pages/viewer/utils/visiblePageIndices"; /** حداکثر زمان پخش انیمیشن ورود (برای برداشتن حالت play) */ const MAX_ENTRANCE_PLAY_MS = ENTRANCE_ANIMATION_BASE_DELAY_MS + MAX_ENTRANCE_DELAY_MS + 4000 + 1200; export type EntrancePhase = "idle" | "play" | "settled"; type PageRef = { id: number }; type Options = { pages: PageRef[]; portrait: boolean; showCover: boolean; }; /** * کنترل یک‌بار پخش انیمیشن ورود هر صفحه — state در والد نگه‌داری می‌شود * تا با remount شدن BookPage هنگام ورق‌خوردن دوباره اجرا نشود. */ export function useBookEntranceController({ pages, portrait, showCover }: Options) { const playedPageIdsRef = useRef(new Set()); const [playingPageIds, setPlayingPageIds] = useState>(() => new Set()); const playTimersRef = useRef>>(new Map()); const clearPlayTimer = useCallback((pageId: number) => { const t = playTimersRef.current.get(pageId); if (t) { clearTimeout(t); playTimersRef.current.delete(pageId); } }, []); const finishPlaying = useCallback((pageId: number) => { clearPlayTimer(pageId); setPlayingPageIds((prev) => { if (!prev.has(pageId)) return prev; const next = new Set(prev); next.delete(pageId); return next; }); }, [clearPlayTimer]); const scheduleEntranceForSpread = useCallback( (currentIndex: number) => { const indices = getVisiblePageIndices(currentIndex, pages.length, { portrait, showCover, }); const visibleIds = new Set( indices.map((i) => pages[i]?.id).filter((id): id is number => id != null), ); const newlyPlaying: number[] = []; for (const i of indices) { const page = pages[i]; if (!page) continue; if (playedPageIdsRef.current.has(page.id)) continue; playedPageIdsRef.current.add(page.id); newlyPlaying.push(page.id); } setPlayingPageIds((prev) => { const next = new Set(); for (const id of prev) { if (visibleIds.has(id)) next.add(id); } for (const id of newlyPlaying) next.add(id); if ( next.size === prev.size && newlyPlaying.every((id) => prev.has(id)) ) { return prev; } return next; }); if (newlyPlaying.length === 0) return; for (const pageId of newlyPlaying) { clearPlayTimer(pageId); playTimersRef.current.set( pageId, setTimeout(() => finishPlaying(pageId), MAX_ENTRANCE_PLAY_MS), ); } }, [pages, portrait, showCover, clearPlayTimer, finishPlaying], ); const getEntrancePhase = useCallback( (pageId: number): EntrancePhase => { if (playingPageIds.has(pageId)) return "play"; if (playedPageIdsRef.current.has(pageId)) return "settled"; return "idle"; }, [playingPageIds], ); const reset = useCallback(() => { for (const id of playTimersRef.current.keys()) { clearPlayTimer(id); } playedPageIdsRef.current.clear(); setPlayingPageIds(new Set()); }, [clearPlayTimer]); return { scheduleEntranceForSpread, getEntrancePhase, reset, }; }