diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-11-01 16:15:40 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-01 16:15:40 -0700 |
commit | f57a8cf8ba0cd10a54abf35d960d8fb90266fa6b (patch) | |
tree | a9da6032bcbd587d92fd1030e698aea2dbef9f72 /src/view/com/pager/PagerWithHeader.tsx | |
parent | f9944b55e26fe6109bc2e7a25b88979111470ed9 (diff) | |
download | voidsky-f57a8cf8ba0cd10a54abf35d960d8fb90266fa6b.tar.zst |
Lists updates: curate lists and blocklists (#1689)
* Add lists screen * Update Lists screen and List create/edit modal to support curate lists * Rework the ProfileList screen and add curatelist support * More ProfileList progress * Update list modals * Rename mutelists to modlists * Layout updates/fixes * More layout fixes * Modal fixes * List list screen updates * Update feed page to give more info * Layout fixes to ListAddUser modal * Layout fixes to FlatList and Feed on desktop * Layout fix to LoadLatestBtn on Web * Handle did resolution before showing the ProfileList screen * Rename the CustomFeed routes to ProfileFeed for consistency * Fix layout issues with the pager and feeds * Factor out some common code * Fix UIs for mobile * Fix user list rendering * Fix: dont bubble custom feed errors in the merge feed * Refactor feed models to reduce usage of the SavedFeeds model * Replace CustomFeedModel with FeedSourceModel which abstracts feed-generators and lists * Add the ability to pin lists * Add pinned lists to mobile * Remove dead code * Rework the ProfileScreenHeader to create more real-estate for action buttons * Improve layout behavior on web mobile breakpoints * Refactor feed & list pages to use new Tabs layout component * Refactor to ProfileSubpageHeader * Implement modlist block and mute * Switch to new api and just modify state on modlist actions * Fix some UI overflows * Fix: dont show edit buttons on lists you dont own * Fix alignment issue on long titles * Improve loading and error states for feeds & lists * Update list dropdown icons for ios * Fetch feed display names in the mergefeed * Improve rendering off offline feeds in the feed-listing page * Update Feeds listing UI to react to changes in saved/pinned state * Refresh list and feed on posts tab press * Fix pinned feed ordering UI * Fixes to list pinning * Remove view=simple qp * Add list to feed tuners * Render richtext * Add list href * Add 'view avatar' * Remove unused import * Fix missing import * Correctly reflect block by list state * Replace the <Tabs> component with the more effective <PagerWithHeader> component * Improve the responsiveness of the PagerWithHeader * Fix visual jank in the feed loading state * Improve performance of the PagerWithHeader * Fix a case that would cause the header to animate too aggressively * Add the ability to scroll to top by tapping the selected tab * Fix unit test runner * Update modlists test * Add curatelist tests * Fix: remove link behavior in ListAddUser modal * Fix some layout jank in the PagerWithHeader on iOS * Simplify ListItems header rendering * Wait for the appview to recognize the list before proceeding with list creation * Fix glitch in the onPageSelecting index of the Pager * Fix until() * Copy fix Co-authored-by: Eric Bailey <git@esb.lol> --------- Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/view/com/pager/PagerWithHeader.tsx')
-rw-r--r-- | src/view/com/pager/PagerWithHeader.tsx | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx new file mode 100644 index 000000000..3cdd3ab2e --- /dev/null +++ b/src/view/com/pager/PagerWithHeader.tsx @@ -0,0 +1,212 @@ +import * as React from 'react' +import {LayoutChangeEvent, StyleSheet} from 'react-native' +import Animated, { + Easing, + useAnimatedReaction, + useAnimatedScrollHandler, + useAnimatedStyle, + useSharedValue, + withTiming, + runOnJS, +} from 'react-native-reanimated' +import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' +import {TabBar} from './TabBar' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {OnScrollCb} from 'lib/hooks/useOnMainScroll' + +const SCROLLED_DOWN_LIMIT = 200 + +interface PagerWithHeaderChildParams { + headerHeight: number + onScroll: OnScrollCb + isScrolledDown: boolean +} + +export interface PagerWithHeaderProps { + testID?: string + children: + | (((props: PagerWithHeaderChildParams) => JSX.Element) | null)[] + | ((props: PagerWithHeaderChildParams) => JSX.Element) + items: string[] + renderHeader?: () => JSX.Element + initialPage?: number + onPageSelected?: (index: number) => void + onCurrentPageSelected?: (index: number) => void +} +export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>( + function PageWithHeaderImpl( + { + children, + testID, + items, + renderHeader, + initialPage, + onPageSelected, + onCurrentPageSelected, + }: PagerWithHeaderProps, + ref, + ) { + const {isMobile} = useWebMediaQueries() + const [currentPage, setCurrentPage] = React.useState(0) + const scrollYs = React.useRef<Record<number, number>>({}) + const scrollY = useSharedValue(scrollYs.current[currentPage] || 0) + const [tabBarHeight, setTabBarHeight] = React.useState(0) + const [headerHeight, setHeaderHeight] = React.useState(0) + const [isScrolledDown, setIsScrolledDown] = React.useState( + scrollYs.current[currentPage] > SCROLLED_DOWN_LIMIT, + ) + + // react to scroll updates + function onScrollUpdate(v: number) { + // track each page's current scroll position + scrollYs.current[currentPage] = Math.min(v, headerHeight - tabBarHeight) + // update the 'is scrolled down' value + setIsScrolledDown(v > SCROLLED_DOWN_LIMIT) + } + useAnimatedReaction( + () => scrollY.value, + v => runOnJS(onScrollUpdate)(v), + ) + + // capture the header bar sizing + const onTabBarLayout = React.useCallback( + (evt: LayoutChangeEvent) => { + setTabBarHeight(evt.nativeEvent.layout.height) + }, + [setTabBarHeight], + ) + const onHeaderLayout = React.useCallback( + (evt: LayoutChangeEvent) => { + setHeaderHeight(evt.nativeEvent.layout.height) + }, + [setHeaderHeight], + ) + + // render the the header and tab bar + const headerTransform = useAnimatedStyle( + () => ({ + transform: [ + { + translateY: Math.min( + Math.min(scrollY.value, headerHeight - tabBarHeight) * -1, + 0, + ), + }, + ], + }), + [scrollY, headerHeight, tabBarHeight], + ) + const renderTabBar = React.useCallback( + (props: RenderTabBarFnProps) => { + return ( + <Animated.View + onLayout={onHeaderLayout} + style={[ + isMobile ? styles.tabBarMobile : styles.tabBarDesktop, + headerTransform, + ]}> + {renderHeader?.()} + <TabBar + items={items} + selectedPage={currentPage} + onSelect={props.onSelect} + onPressSelected={onCurrentPageSelected} + onLayout={onTabBarLayout} + /> + </Animated.View> + ) + }, + [ + items, + renderHeader, + headerTransform, + currentPage, + onCurrentPageSelected, + isMobile, + onTabBarLayout, + onHeaderLayout, + ], + ) + + // props to pass into children render functions + const onScroll = useAnimatedScrollHandler({ + onScroll(e) { + scrollY.value = e.contentOffset.y + }, + }) + const childProps = React.useMemo<PagerWithHeaderChildParams>(() => { + return { + headerHeight, + onScroll, + isScrolledDown, + } + }, [headerHeight, onScroll, isScrolledDown]) + + const onPageSelectedInner = React.useCallback( + (index: number) => { + setCurrentPage(index) + onPageSelected?.(index) + }, + [onPageSelected, setCurrentPage], + ) + + const onPageSelecting = React.useCallback( + (index: number) => { + setCurrentPage(index) + if (scrollY.value > headerHeight) { + scrollY.value = headerHeight + } + scrollY.value = withTiming(scrollYs.current[index] || 0, { + duration: 170, + easing: Easing.inOut(Easing.quad), + }) + }, + [scrollY, setCurrentPage, scrollYs, headerHeight], + ) + + return ( + <Pager + ref={ref} + testID={testID} + initialPage={initialPage} + onPageSelected={onPageSelectedInner} + onPageSelecting={onPageSelecting} + renderTabBar={renderTabBar} + tabBarPosition="top"> + {toArray(children) + .filter(Boolean) + .map(child => { + if (child) { + return child(childProps) + } + return null + })} + </Pager> + ) + }, +) + +const styles = StyleSheet.create({ + tabBarMobile: { + position: 'absolute', + zIndex: 1, + top: 0, + left: 0, + width: '100%', + }, + tabBarDesktop: { + position: 'absolute', + zIndex: 1, + top: 0, + // @ts-ignore Web only -prf + left: 'calc(50% - 299px)', + width: 598, + }, +}) + +function toArray<T>(v: T | T[]): T[] { + if (Array.isArray(v)) { + return v + } + return [v] +} |