From ce1b04b9250d25ec781c7e1ee046ea6358a81a83 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 26 Nov 2024 15:55:27 +0000 Subject: Fix `` types (#6756) * fix List and ScrollView types * add comment * rm omitting ref --- src/screens/Messages/components/MessagesList.tsx | 6 +- src/screens/Onboarding/Layout.tsx | 3 +- src/view/com/pager/PagerWithHeader.web.tsx | 4 +- src/view/com/util/List.tsx | 240 ++++++++++++----------- src/view/com/util/ViewSelector.tsx | 1 + src/view/com/util/Views.d.ts | 19 -- src/view/com/util/Views.jsx | 7 - src/view/com/util/Views.tsx | 28 +++ src/view/screens/Feeds.tsx | 6 +- src/view/screens/Storybook/ListContained.tsx | 6 +- 10 files changed, 168 insertions(+), 152 deletions(-) delete mode 100644 src/view/com/util/Views.d.ts delete mode 100644 src/view/com/util/Views.jsx create mode 100644 src/view/com/util/Views.tsx (limited to 'src') diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 067abb27e..5edea2411 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useRef} from 'react' -import {FlatList, LayoutChangeEvent, View} from 'react-native' +import {LayoutChangeEvent, View} from 'react-native' import { KeyboardStickyView, useKeyboardHandler, @@ -33,7 +33,7 @@ import { EmojiPicker, EmojiPickerState, } from '#/view/com/composer/text-input/web/EmojiPicker.web' -import {List} from '#/view/com/util/List' +import {List, ListMethods} from '#/view/com/util/List' import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled' import {MessageInput} from '#/screens/Messages/components/MessageInput' import {MessageListError} from '#/screens/Messages/components/MessageListError' @@ -94,7 +94,7 @@ export function MessagesList({ const getPost = useGetPost() const {embedUri, setEmbed} = useMessageEmbed() - const flatListRef = useAnimatedRef() + const flatListRef = useAnimatedRef() const [newMessagesPill, setNewMessagesPill] = React.useState({ show: false, diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index 4a07ebd83..54821532c 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -1,5 +1,6 @@ import React from 'react' import {View} from 'react-native' +import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -35,7 +36,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) { const {gtMobile} = useBreakpoints() const onboardDispatch = useOnboardingDispatch() const {state, dispatch} = React.useContext(Context) - const scrollview = React.useRef(null) + const scrollview = React.useRef(null) const prevActiveStep = React.useRef(state.activeStep) React.useEffect(() => { diff --git a/src/view/com/pager/PagerWithHeader.web.tsx b/src/view/com/pager/PagerWithHeader.web.tsx index acf4f1784..e72c1f3cc 100644 --- a/src/view/com/pager/PagerWithHeader.web.tsx +++ b/src/view/com/pager/PagerWithHeader.web.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import {FlatList, ScrollView, StyleSheet, View} from 'react-native' +import {ScrollView, StyleSheet, View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' import {usePalette} from '#/lib/hooks/usePalette' @@ -11,7 +11,7 @@ import {TabBar} from './TabBar' export interface PagerWithHeaderChildParams { headerHeight: number isFocused: boolean - scrollElRef: React.MutableRefObject | ScrollView | null> + scrollElRef: React.MutableRefObject } export interface PagerWithHeaderProps { 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 = Omit< - FlatListProps, +export type ListProps = Omit< + FlatListPropsWithLayout, | 'onMomentumScrollBegin' // Use ScrollContext instead. | 'onMomentumScrollEnd' // Use ScrollContext instead. | 'onScroll' // Use ScrollContext instead. @@ -22,6 +26,7 @@ export type ListProps = 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 = 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 const SCROLLED_DOWN_LIMIT = 200 -function ListImpl( - { - onScrolledDownChange, - refreshing, - onRefresh, - onItemSeen, - headerOffset, - style, - progressViewOffset, - ...props - }: ListProps, - ref: React.Ref, -) { - 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( + ( + { + 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; changed: Array}) => { - 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 = ( - - ) - } + const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => { + if (!onItemSeen) { + return [undefined, undefined] + } + return [ + (info: { + viewableItems: Array + changed: Array + }) => { + 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 = ( + + ) + } - return ( - - ) -} + let contentOffset + if (headerOffset != null) { + style = addStyle(style, { + paddingTop: headerOffset, + }) + contentOffset = {x: 0, y: headerOffset * -1} + } + + return ( + + ) + }, +) +List.displayName = 'List' -export const List = memo(React.forwardRef(ListImpl)) as ( - props: ListProps & {ref?: React.Ref}, -) => React.ReactElement +List = memo(List) +export {List} diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx index f8ba8f561..b5075707a 100644 --- a/src/view/com/util/ViewSelector.tsx +++ b/src/view/com/util/ViewSelector.tsx @@ -113,6 +113,7 @@ export const ViewSelector = React.forwardRef< ) return ( ) diff --git a/src/view/com/util/Views.jsx b/src/view/com/util/Views.jsx deleted file mode 100644 index 02a2b407e..000000000 --- a/src/view/com/util/Views.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import {View} from 'react-native' -import Animated from 'react-native-reanimated' - -// If you explode these into functions, don't forget to forwardRef! -export const FlatList_INTERNAL = Animated.FlatList -export const CenteredView = View -export const ScrollView = Animated.ScrollView diff --git a/src/view/com/util/Views.tsx b/src/view/com/util/Views.tsx new file mode 100644 index 000000000..0d3f63794 --- /dev/null +++ b/src/view/com/util/Views.tsx @@ -0,0 +1,28 @@ +import {forwardRef} from 'react' +import {FlatListComponent} from 'react-native' +import {View, ViewProps} from 'react-native' +import Animated from 'react-native-reanimated' +import {FlatListPropsWithLayout} from 'react-native-reanimated' + +// If you explode these into functions, don't forget to forwardRef! + +/** + * Avoid using `FlatList_INTERNAL` and use `List` where possible. + * The types are a bit wrong on `FlatList_INTERNAL` + */ +export const FlatList_INTERNAL = Animated.FlatList +export type FlatList_INTERNAL = Omit< + FlatListComponent>, + 'CellRendererComponent' +> +export const ScrollView = Animated.ScrollView +export type ScrollView = typeof Animated.ScrollView + +export const CenteredView = forwardRef< + View, + React.PropsWithChildren< + ViewProps & {sideBorders?: boolean; topBorder?: boolean} + > +>(function CenteredView(props, ref) { + return +}) diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index 404145714..406f11792 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {ActivityIndicator, type FlatList, StyleSheet, View} from 'react-native' +import {ActivityIndicator, StyleSheet, View} from 'react-native' import {AppBskyFeedDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -25,7 +25,7 @@ import {useComposerControls} from '#/state/shell/composer' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {FAB} from '#/view/com/util/fab/FAB' import {TextLink} from '#/view/com/util/Link' -import {List} from '#/view/com/util/List' +import {List, ListMethods} from '#/view/com/util/List' import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {Text} from '#/view/com/util/text/Text' import {ViewHeader} from '#/view/com/util/ViewHeader' @@ -130,7 +130,7 @@ export function FeedsScreen(_props: Props) { error: searchError, } = useSearchPopularFeedsMutation() const {hasSession} = useSession() - const listRef = React.useRef(null) + const listRef = React.useRef(null) /** * A search query is present. We may not have search results yet. diff --git a/src/view/screens/Storybook/ListContained.tsx b/src/view/screens/Storybook/ListContained.tsx index 833320148..e673743eb 100644 --- a/src/view/screens/Storybook/ListContained.tsx +++ b/src/view/screens/Storybook/ListContained.tsx @@ -1,15 +1,15 @@ import React from 'react' -import {FlatList, View} from 'react-native' +import {View} from 'react-native' import {ScrollProvider} from '#/lib/ScrollContext' -import {List} from '#/view/com/util/List' +import {List, ListMethods} from '#/view/com/util/List' import {Button, ButtonText} from '#/components/Button' import * as Toggle from '#/components/forms/Toggle' import {Text} from '#/components/Typography' export function ListContained() { const [animated, setAnimated] = React.useState(false) - const ref = React.useRef(null) + const ref = React.useRef(null) const data = React.useMemo(() => { return Array.from({length: 100}, (_, i) => ({ -- cgit 1.4.1