|
| 1 | +import type { |
| 2 | + default as PagerView, |
| 3 | + PageScrollStateChangedNativeEvent, |
| 4 | + PagerViewOnPageScrollEventData, |
| 5 | + PagerViewOnPageSelectedEventData, |
| 6 | +} from 'react-native-pager-view'; |
| 7 | +import { Animated } from 'react-native'; |
| 8 | +import { createPage, CreatePage } from '../utils'; |
| 9 | +import { useCallback, useMemo, useRef, useState } from 'react'; |
| 10 | + |
| 11 | +export type UseNavigationPanelProps = ReturnType<typeof useNavigationPanel>; |
| 12 | + |
| 13 | +export interface EventLog { |
| 14 | + event: 'scroll' | 'select' | 'statusChanged'; |
| 15 | + text: string; |
| 16 | + timestamp: Date; |
| 17 | +} |
| 18 | + |
| 19 | +const getBasePages = (pages: number) => |
| 20 | + new Array(pages).fill('').map((_v, index) => createPage(index)); |
| 21 | + |
| 22 | +export function useNavigationPanel( |
| 23 | + pagesAmount: number = 10, |
| 24 | + onPageSelectedCallback: (position: number) => void = () => {} |
| 25 | +) { |
| 26 | + const ref = useRef<PagerView>(null); |
| 27 | + const [pages, setPages] = useState<CreatePage[]>( |
| 28 | + useMemo(() => getBasePages(pagesAmount), [pagesAmount]) |
| 29 | + ); |
| 30 | + const [activePage, setActivePage] = useState(0); |
| 31 | + const [isAnimated, setIsAnimated] = useState(true); |
| 32 | + const [overdragEnabled, setOverdragEnabled] = useState(false); |
| 33 | + const [scrollEnabled, setScrollEnabled] = useState(true); |
| 34 | + const [scrollState, setScrollState] = useState('idle'); |
| 35 | + const [dotsEnabled, setDotsEnabled] = useState(false); |
| 36 | + const [logs, setLogs] = useState<EventLog[]>([]); |
| 37 | + const [progress, setProgress] = useState({ position: 0, offset: 0 }); |
| 38 | + const onPageScrollOffset = useRef(new Animated.Value(0)).current; |
| 39 | + const onPageScrollPosition = useRef(new Animated.Value(0)).current; |
| 40 | + const onPageSelectedPosition = useRef(new Animated.Value(0)).current; |
| 41 | + |
| 42 | + const setPage = useCallback( |
| 43 | + (page: number) => |
| 44 | + isAnimated |
| 45 | + ? ref.current?.setPage(page) |
| 46 | + : ref.current?.setPageWithoutAnimation(page), |
| 47 | + [isAnimated] |
| 48 | + ); |
| 49 | + |
| 50 | + const addLog = useCallback((log: EventLog) => { |
| 51 | + setLogs((text) => [log, ...text].slice(0, 100)); |
| 52 | + }, []); |
| 53 | + |
| 54 | + const addPage = useCallback( |
| 55 | + () => setPages((prevPages) => [...prevPages, createPage(prevPages.length)]), |
| 56 | + [] |
| 57 | + ); |
| 58 | + const removePage = useCallback( |
| 59 | + () => setPages((prevPages) => prevPages.slice(0, prevPages.length - 1)), |
| 60 | + [] |
| 61 | + ); |
| 62 | + const toggleAnimation = useCallback( |
| 63 | + () => setIsAnimated((animated) => !animated), |
| 64 | + [] |
| 65 | + ); |
| 66 | + const toggleScroll = useCallback( |
| 67 | + () => setScrollEnabled((enabled) => !enabled), |
| 68 | + [] |
| 69 | + ); |
| 70 | + const toggleDots = useCallback( |
| 71 | + () => setDotsEnabled((enabled) => !enabled), |
| 72 | + [] |
| 73 | + ); |
| 74 | + const toggleOverdrag = useCallback( |
| 75 | + () => setOverdragEnabled((enabled) => !enabled), |
| 76 | + [] |
| 77 | + ); |
| 78 | + |
| 79 | + const onPageScroll = useMemo( |
| 80 | + () => |
| 81 | + Animated.event<PagerViewOnPageScrollEventData>( |
| 82 | + [ |
| 83 | + { |
| 84 | + nativeEvent: { |
| 85 | + offset: onPageScrollOffset, |
| 86 | + position: onPageScrollPosition, |
| 87 | + }, |
| 88 | + }, |
| 89 | + ], |
| 90 | + { |
| 91 | + listener: ({ nativeEvent: { offset, position } }) => { |
| 92 | + addLog({ |
| 93 | + event: 'scroll', |
| 94 | + text: `Position: ${position} Offset: ${offset}`, |
| 95 | + timestamp: new Date(), |
| 96 | + }); |
| 97 | + setProgress({ |
| 98 | + position, |
| 99 | + offset, |
| 100 | + }); |
| 101 | + }, |
| 102 | + useNativeDriver: true, |
| 103 | + } |
| 104 | + ), |
| 105 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| 106 | + [] |
| 107 | + ); |
| 108 | + |
| 109 | + const onPageSelected = useMemo( |
| 110 | + () => |
| 111 | + Animated.event<PagerViewOnPageSelectedEventData>( |
| 112 | + [{ nativeEvent: { position: onPageSelectedPosition } }], |
| 113 | + { |
| 114 | + listener: ({ nativeEvent: { position } }) => { |
| 115 | + addLog({ |
| 116 | + event: 'select', |
| 117 | + text: `Page: ${position}`, |
| 118 | + timestamp: new Date(), |
| 119 | + }); |
| 120 | + setActivePage(position); |
| 121 | + onPageSelectedCallback(position); |
| 122 | + }, |
| 123 | + useNativeDriver: true, |
| 124 | + } |
| 125 | + ), |
| 126 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| 127 | + [] |
| 128 | + ); |
| 129 | + |
| 130 | + const onPageScrollStateChanged = useCallback( |
| 131 | + (e: PageScrollStateChangedNativeEvent) => { |
| 132 | + addLog({ |
| 133 | + event: 'statusChanged', |
| 134 | + text: `State: ${e.nativeEvent.pageScrollState}`, |
| 135 | + timestamp: new Date(), |
| 136 | + }); |
| 137 | + setScrollState(e.nativeEvent.pageScrollState); |
| 138 | + }, |
| 139 | + // eslint-disable-next-line react-hooks/exhaustive-deps |
| 140 | + [] |
| 141 | + ); |
| 142 | + |
| 143 | + return { |
| 144 | + ref, |
| 145 | + activePage, |
| 146 | + isAnimated, |
| 147 | + pages, |
| 148 | + scrollState, |
| 149 | + scrollEnabled, |
| 150 | + dotsEnabled, |
| 151 | + progress, |
| 152 | + overdragEnabled, |
| 153 | + setPage, |
| 154 | + addPage, |
| 155 | + removePage, |
| 156 | + toggleScroll, |
| 157 | + toggleDots, |
| 158 | + toggleAnimation, |
| 159 | + setProgress, |
| 160 | + onPageScroll, |
| 161 | + onPageSelected, |
| 162 | + onPageScrollStateChanged, |
| 163 | + toggleOverdrag, |
| 164 | + logs, |
| 165 | + }; |
| 166 | +} |
0 commit comments