diff options
Diffstat (limited to 'src/view/com/util/List.tsx')
-rw-r--r-- | src/view/com/util/List.tsx | 240 |
1 files changed, 126 insertions, 114 deletions
diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx index fa93ec5e6..8176d0b43 100644 --- a/src/view/com/util/List.tsx +++ b/src/view/com/util/List.tsx @@ -1,6 +1,10 @@ import React, {memo} from 'react' -import {FlatListProps, RefreshControl, ViewToken} from 'react-native' -import {runOnJS, useSharedValue} from 'react-native-reanimated' +import {RefreshControl, ViewToken} from 'react-native' +import { + FlatListPropsWithLayout, + runOnJS, + useSharedValue, +} from 'react-native-reanimated' import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' @@ -13,8 +17,8 @@ import {useTheme} from '#/alf' import {FlatList_INTERNAL} from './Views' export type ListMethods = FlatList_INTERNAL -export type ListProps<ItemT> = Omit< - FlatListProps<ItemT>, +export type ListProps<ItemT = any> = Omit< + FlatListPropsWithLayout<ItemT>, | 'onMomentumScrollBegin' // Use ScrollContext instead. | 'onMomentumScrollEnd' // Use ScrollContext instead. | 'onScroll' // Use ScrollContext instead. @@ -22,6 +26,7 @@ export type ListProps<ItemT> = Omit< | 'onScrollEndDrag' // Use ScrollContext instead. | 'refreshControl' // Pass refreshing and/or onRefresh instead. | 'contentOffset' // Pass headerOffset instead. + | 'progressViewOffset' // Can't be an animated value > & { onScrolledDownChange?: (isScrolledDown: boolean) => void headerOffset?: number @@ -32,130 +37,137 @@ export type ListProps<ItemT> = Omit< // Web only prop to contain the scroll to the container rather than the window disableFullWindowScroll?: boolean sideBorders?: boolean + progressViewOffset?: number } export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null> const SCROLLED_DOWN_LIMIT = 200 -function ListImpl<ItemT>( - { - onScrolledDownChange, - refreshing, - onRefresh, - onItemSeen, - headerOffset, - style, - progressViewOffset, - ...props - }: ListProps<ItemT>, - ref: React.Ref<ListMethods>, -) { - const isScrolledDown = useSharedValue(false) - const t = useTheme() - const dedupe = useDedupe(400) - const {activeLightbox} = useLightbox() - - function handleScrolledDownChange(didScrollDown: boolean) { - onScrolledDownChange?.(didScrollDown) - } - - // Intentionally destructured outside the main thread closure. - // See https://github.com/bluesky-social/social-app/pull/4108. - const { - onBeginDrag: onBeginDragFromContext, - onEndDrag: onEndDragFromContext, - onScroll: onScrollFromContext, - onMomentumEnd: onMomentumEndFromContext, - } = useScrollHandlers() - const scrollHandler = useAnimatedScrollHandler({ - onBeginDrag(e, ctx) { - onBeginDragFromContext?.(e, ctx) +let List = React.forwardRef<ListMethods, ListProps>( + ( + { + onScrolledDownChange, + refreshing, + onRefresh, + onItemSeen, + headerOffset, + style, + progressViewOffset, + ...props }, - onEndDrag(e, ctx) { - runOnJS(updateActiveVideoViewAsync)() - onEndDragFromContext?.(e, ctx) - }, - onScroll(e, ctx) { - onScrollFromContext?.(e, ctx) + ref, + ): React.ReactElement => { + const isScrolledDown = useSharedValue(false) + const t = useTheme() + const dedupe = useDedupe(400) + const {activeLightbox} = useLightbox() - const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT - if (isScrolledDown.get() !== didScrollDown) { - isScrolledDown.set(didScrollDown) - if (onScrolledDownChange != null) { - runOnJS(handleScrolledDownChange)(didScrollDown) - } - } + function handleScrolledDownChange(didScrollDown: boolean) { + onScrolledDownChange?.(didScrollDown) + } - if (isIOS) { - runOnJS(dedupe)(updateActiveVideoViewAsync) - } - }, - // Note: adding onMomentumBegin here makes simulator scroll - // lag on Android. So either don't add it, or figure out why. - onMomentumEnd(e, ctx) { - runOnJS(updateActiveVideoViewAsync)() - onMomentumEndFromContext?.(e, ctx) - }, - }) + // Intentionally destructured outside the main thread closure. + // See https://github.com/bluesky-social/social-app/pull/4108. + const { + onBeginDrag: onBeginDragFromContext, + onEndDrag: onEndDragFromContext, + onScroll: onScrollFromContext, + onMomentumEnd: onMomentumEndFromContext, + } = useScrollHandlers() + const scrollHandler = useAnimatedScrollHandler({ + onBeginDrag(e, ctx) { + onBeginDragFromContext?.(e, ctx) + }, + onEndDrag(e, ctx) { + runOnJS(updateActiveVideoViewAsync)() + onEndDragFromContext?.(e, ctx) + }, + onScroll(e, ctx) { + onScrollFromContext?.(e, ctx) - const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => { - if (!onItemSeen) { - return [undefined, undefined] - } - return [ - (info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => { - for (const item of info.changed) { - if (item.isViewable) { - onItemSeen(item.item) + const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT + if (isScrolledDown.get() !== didScrollDown) { + isScrolledDown.set(didScrollDown) + if (onScrolledDownChange != null) { + runOnJS(handleScrolledDownChange)(didScrollDown) } } + + if (isIOS) { + runOnJS(dedupe)(updateActiveVideoViewAsync) + } }, - { - itemVisiblePercentThreshold: 40, - minimumViewTime: 0.5e3, + // Note: adding onMomentumBegin here makes simulator scroll + // lag on Android. So either don't add it, or figure out why. + onMomentumEnd(e, ctx) { + runOnJS(updateActiveVideoViewAsync)() + onMomentumEndFromContext?.(e, ctx) }, - ] - }, [onItemSeen]) + }) - let refreshControl - if (refreshing !== undefined || onRefresh !== undefined) { - refreshControl = ( - <RefreshControl - refreshing={refreshing ?? false} - onRefresh={onRefresh} - tintColor={t.atoms.text.color} - titleColor={t.atoms.text.color} - progressViewOffset={progressViewOffset ?? headerOffset} - /> - ) - } + const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => { + if (!onItemSeen) { + return [undefined, undefined] + } + return [ + (info: { + viewableItems: Array<ViewToken> + changed: Array<ViewToken> + }) => { + for (const item of info.changed) { + if (item.isViewable) { + onItemSeen(item.item) + } + } + }, + { + itemVisiblePercentThreshold: 40, + minimumViewTime: 0.5e3, + }, + ] + }, [onItemSeen]) - let contentOffset - if (headerOffset != null) { - style = addStyle(style, { - paddingTop: headerOffset, - }) - contentOffset = {x: 0, y: headerOffset * -1} - } + let refreshControl + if (refreshing !== undefined || onRefresh !== undefined) { + refreshControl = ( + <RefreshControl + refreshing={refreshing ?? false} + onRefresh={onRefresh} + tintColor={t.atoms.text.color} + titleColor={t.atoms.text.color} + progressViewOffset={progressViewOffset ?? headerOffset} + /> + ) + } - return ( - <FlatList_INTERNAL - {...props} - scrollIndicatorInsets={{right: 1}} - contentOffset={contentOffset} - refreshControl={refreshControl} - onScroll={scrollHandler} - scrollsToTop={!activeLightbox} - scrollEventThrottle={1} - onViewableItemsChanged={onViewableItemsChanged} - viewabilityConfig={viewabilityConfig} - showsVerticalScrollIndicator={!isAndroid} - style={style} - ref={ref} - /> - ) -} + let contentOffset + if (headerOffset != null) { + style = addStyle(style, { + paddingTop: headerOffset, + }) + contentOffset = {x: 0, y: headerOffset * -1} + } + + return ( + <FlatList_INTERNAL + {...props} + scrollIndicatorInsets={{right: 1}} + contentOffset={contentOffset} + refreshControl={refreshControl} + onScroll={scrollHandler} + scrollsToTop={!activeLightbox} + scrollEventThrottle={1} + onViewableItemsChanged={onViewableItemsChanged} + viewabilityConfig={viewabilityConfig} + showsVerticalScrollIndicator={!isAndroid} + style={style} + // @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn + ref={ref} + /> + ) + }, +) +List.displayName = 'List' -export const List = memo(React.forwardRef(ListImpl)) as <ItemT>( - props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>}, -) => React.ReactElement +List = memo(List) +export {List} |