11import { useIsFocused } from '@react-navigation/native' ;
2- import React , { forwardRef , useCallback , useEffect , useImperativeHandle , useRef , useState } from 'react' ;
2+ import { FlashList } from '@shopify/flash-list' ;
3+ import type { FlashListProps , ViewToken } from '@shopify/flash-list' ;
4+ import React , { forwardRef , useCallback , useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
35import type { ForwardedRef } from 'react' ;
46import { View } from 'react-native' ;
5- import type { FlatList , ListRenderItemInfo , NativeSyntheticEvent , StyleProp , ViewStyle , ViewToken } from 'react-native' ;
7+ import type { NativeSyntheticEvent , StyleProp , ViewStyle } from 'react-native' ;
68import Animated from 'react-native-reanimated' ;
7- import type { FlatListPropsWithLayout } from 'react-native-reanimated' ;
89import Checkbox from '@components/Checkbox' ;
910import * as Expensicons from '@components/Icon/Expensicons' ;
1011import MenuItem from '@components/MenuItem' ;
@@ -17,6 +18,7 @@ import type TransactionListItem from '@components/SelectionList/Search/Transacti
1718import type { ExtendedTargetedEvent , ReportActionListItemType , TaskListItemType , TransactionGroupListItemType , TransactionListItemType } from '@components/SelectionList/types' ;
1819import Text from '@components/Text' ;
1920import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager' ;
21+ import useInitialWindowDimensions from '@hooks/useInitialWindowDimensions' ;
2022import useKeyboardShortcut from '@hooks/useKeyboardShortcut' ;
2123import useKeyboardState from '@hooks/useKeyboardState' ;
2224import useLocalize from '@hooks/useLocalize' ;
@@ -30,8 +32,12 @@ import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/Keyboar
3032import variables from '@styles/variables' ;
3133import CONST from '@src/CONST' ;
3234import ONYXKEYS from '@src/ONYXKEYS' ;
35+ import { createItemHeightCalculator } from './itemHeightCalculator' ;
36+ import ITEM_HEIGHTS from './itemHeights' ;
3337import type { SearchQueryJSON } from './types' ;
3438
39+ const AnimatedFlashListComponent = Animated . createAnimatedComponent ( FlashList < SearchListItem > ) ;
40+
3541type SearchListItem = TransactionListItemType | TransactionGroupListItemType | ReportActionListItemType | TaskListItemType ;
3642type SearchListItemComponentType = typeof TransactionListItem | typeof ChatListItem | typeof TransactionGroupListItem | typeof TaskListItem ;
3743
@@ -40,7 +46,7 @@ type SearchListHandle = {
4046 scrollToIndex : ( index : number , animated ?: boolean ) => void ;
4147} ;
4248
43- type SearchListProps = Pick < FlatListPropsWithLayout < SearchListItem > , 'onScroll' | 'contentContainerStyle' | 'onEndReached' | 'onEndReachedThreshold' | 'ListFooterComponent' > & {
49+ type SearchListProps = Pick < FlashListProps < SearchListItem > , 'onScroll' | 'contentContainerStyle' | 'onEndReached' | 'onEndReachedThreshold' | 'ListFooterComponent' > & {
4450 data : SearchListItem [ ] ;
4551
4652 /** Default renderer for every item in the list */
@@ -78,15 +84,17 @@ type SearchListProps = Pick<FlatListPropsWithLayout<SearchListItem>, 'onScroll'
7884 /** Invoked on mount and layout changes */
7985 onLayout ?: ( ) => void ;
8086
87+ /** Styles to apply to the content container */
88+ contentContainerStyle ?: StyleProp < ViewStyle > ;
89+
90+ /** The estimated height of an item in the list */
91+ estimatedItemSize ?: number ;
92+
8193 /** Whether mobile selection mode is enabled */
8294 isMobileSelectionModeEnabled : boolean ;
8395} ;
8496
85- const keyExtractor = ( item : SearchListItem , index : number ) => {
86- return item . keyForList ?? `${ index } ` ;
87- } ;
88-
89- const onScrollToIndexFailed = ( ) => { } ;
97+ const keyExtractor = ( item : SearchListItem , index : number ) => item . keyForList ?? `${ index } ` ;
9098
9199function SearchList (
92100 {
@@ -96,7 +104,7 @@ function SearchList(
96104 onSelectRow,
97105 onCheckboxPress,
98106 canSelectMultiple,
99- onScroll,
107+ onScroll = ( ) => { } ,
100108 onAllCheckboxPress,
101109 contentContainerStyle,
102110 onEndReachedThreshold,
@@ -108,20 +116,33 @@ function SearchList(
108116 queryJSON,
109117 onViewableItemsChanged,
110118 onLayout,
119+ estimatedItemSize = ITEM_HEIGHTS . NARROW_WITHOUT_DRAWER . STANDARD ,
111120 isMobileSelectionModeEnabled,
112121 } : SearchListProps ,
113122 ref : ForwardedRef < SearchListHandle > ,
114123) {
115124 const styles = useThemeStyles ( ) ;
116- const { hash, groupBy} = queryJSON ;
125+
126+ const { initialHeight, initialWidth} = useInitialWindowDimensions ( ) ;
127+ const { hash, groupBy, type} = queryJSON ;
117128 const flattenedTransactions = groupBy ? ( data as TransactionGroupListItemType [ ] ) . flatMap ( ( item ) => item . transactions ) : data ;
118- const flattenedTransactionWithoutPendingDelete = flattenedTransactions . filter ( ( t ) => t . pendingAction !== CONST . RED_BRICK_ROAD_PENDING_ACTION . DELETE ) ;
119- const selectedItemsLength = flattenedTransactions . reduce ( ( acc , item ) => {
120- return item ?. isSelected ? acc + 1 : acc ;
121- } , 0 ) ;
129+
130+ const flattenedTransactionWithoutPendingDelete = useMemo (
131+ ( ) => flattenedTransactions . filter ( ( t ) => t ?. pendingAction !== CONST . RED_BRICK_ROAD_PENDING_ACTION . DELETE ) ,
132+ [ flattenedTransactions ] ,
133+ ) ;
134+
135+ const selectedItemsLength = useMemo (
136+ ( ) =>
137+ flattenedTransactions . reduce ( ( acc , item ) => {
138+ return item ?. isSelected ? acc + 1 : acc ;
139+ } , 0 ) ,
140+ [ flattenedTransactions ] ,
141+ ) ;
142+
122143 const { translate} = useLocalize ( ) ;
123144 const isFocused = useIsFocused ( ) ;
124- const listRef = useRef < FlatList < SearchListItem > > ( null ) ;
145+ const listRef = useRef < FlashList < SearchListItem > > ( null ) ;
125146 const hasKeyBeenPressed = useRef ( false ) ;
126147 const [ itemsToHighlight , setItemsToHighlight ] = useState < Set < string > | null > ( null ) ;
127148 const itemFocusTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
@@ -130,7 +151,7 @@ function SearchList(
130151 // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout here because there is a race condition that causes shouldUseNarrowLayout to change indefinitely in this component
131152 // See https://github.com/Expensify/App/issues/48675 for more details
132153 // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
133- const { isSmallScreenWidth} = useResponsiveLayout ( ) ;
154+ const { isSmallScreenWidth, isLargeScreenWidth , shouldUseNarrowLayout } = useResponsiveLayout ( ) ;
134155
135156 const [ isModalVisible , setIsModalVisible ] = useState ( false ) ;
136157 const [ longPressedItem , setLongPressedItem ] = useState < SearchListItem > ( ) ;
@@ -271,7 +292,8 @@ function SearchList(
271292 useImperativeHandle ( ref , ( ) => ( { scrollAndHighlightItem, scrollToIndex} ) , [ scrollAndHighlightItem , scrollToIndex ] ) ;
272293
273294 const renderItem = useCallback (
274- ( { item, index} : ListRenderItemInfo < SearchListItem > ) => {
295+ // eslint-disable-next-line react/no-unused-prop-types
296+ ( { item, index} : { item : SearchListItem ; index : number } ) => {
275297 const isItemFocused = focusedIndex === index ;
276298 const isItemHighlighted = ! ! itemsToHighlight ?. has ( item . keyForList ?? '' ) ;
277299 const isDisabled = item . pendingAction === CONST . RED_BRICK_ROAD_PENDING_ACTION . DELETE ;
@@ -332,6 +354,51 @@ function SearchList(
332354 const selectAllButtonVisible = canSelectMultiple && ! SearchTableHeader ;
333355 const isSelectAllChecked = selectedItemsLength > 0 && selectedItemsLength === flattenedTransactionWithoutPendingDelete . length ;
334356
357+ const getItemHeight = useMemo (
358+ ( ) =>
359+ createItemHeightCalculator ( {
360+ isLargeScreenWidth,
361+ shouldUseNarrowLayout,
362+ type,
363+ } ) ,
364+ [ isLargeScreenWidth , shouldUseNarrowLayout , type ] ,
365+ ) ;
366+
367+ const overrideItemLayout = useCallback (
368+ ( layout : { span ?: number ; size ?: number } , item : SearchListItem ) => {
369+ if ( ! layout ) {
370+ return ;
371+ }
372+ const height = getItemHeight ( item ) ;
373+ // eslint-disable-next-line no-param-reassign
374+ return ( layout . size = height > 0 ? height : estimatedItemSize ) ;
375+ } ,
376+ [ getItemHeight , estimatedItemSize ] ,
377+ ) ;
378+
379+ const calculatedListHeight = useMemo ( ( ) => {
380+ return initialHeight - variables . contentHeaderHeight ;
381+ } , [ initialHeight ] ) ;
382+
383+ const calculatedListWidth = useMemo ( ( ) => {
384+ if ( shouldUseNarrowLayout ) {
385+ return initialWidth ;
386+ }
387+
388+ if ( isLargeScreenWidth ) {
389+ return initialWidth - variables . navigationTabBarSize - variables . sideBarWithLHBWidth ;
390+ }
391+
392+ return initialWidth ;
393+ } , [ initialWidth , shouldUseNarrowLayout , isLargeScreenWidth ] ) ;
394+
395+ const estimatedListSize = useMemo ( ( ) => {
396+ return {
397+ height : calculatedListHeight ,
398+ width : calculatedListWidth ,
399+ } ;
400+ } , [ calculatedListHeight , calculatedListWidth ] ) ;
401+
335402 return (
336403 < View style = { [ styles . flex1 , ! isKeyboardShown && safeAreaPaddingBottomStyle , containerStyle ] } >
337404 { tableHeaderVisible && (
@@ -365,22 +432,26 @@ function SearchList(
365432 </ View >
366433 ) }
367434
368- < Animated . FlatList
435+ < AnimatedFlashListComponent
369436 data = { data }
370437 renderItem = { renderItem }
371438 keyExtractor = { keyExtractor }
372439 onScroll = { onScroll }
373- contentContainerStyle = { contentContainerStyle }
374440 showsVerticalScrollIndicator = { false }
375441 ref = { listRef }
376442 extraData = { focusedIndex }
377443 onEndReached = { onEndReached }
378444 onEndReachedThreshold = { onEndReachedThreshold }
379445 ListFooterComponent = { ListFooterComponent }
380- removeClippedSubviews
381446 onViewableItemsChanged = { onViewableItemsChanged }
382- onScrollToIndexFailed = { onScrollToIndexFailed }
383447 onLayout = { onLayout }
448+ removeClippedSubviews
449+ drawDistance = { 1000 }
450+ estimatedItemSize = { estimatedItemSize }
451+ overrideItemLayout = { overrideItemLayout }
452+ estimatedListSize = { estimatedListSize }
453+ contentContainerStyle = { contentContainerStyle }
454+ overrideProps = { { estimatedHeightSize : calculatedListHeight } }
384455 />
385456 < Modal
386457 isVisible = { isModalVisible }
0 commit comments