diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index cfb6a38be0a11..45b47382b5f1c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1156,7 +1156,7 @@ type OnyxValuesMapping = { [ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates; [ONYXKEYS.ADD_NEW_COMPANY_CARD]: OnyxTypes.AddNewCompanyCardFeed; [ONYXKEYS.ASSIGN_CARD]: OnyxTypes.AssignCard; - [ONYXKEYS.MOBILE_SELECTION_MODE]: OnyxTypes.MobileSelectionMode; + [ONYXKEYS.MOBILE_SELECTION_MODE]: boolean; [ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string; [ONYXKEYS.NVP_BILLING_FUND_ID]: number; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 35f1fc35434bf..80c0e70b27396 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -880,9 +880,9 @@ function MoneyReportHeader({ [connectedIntegrationName, styles.noWrap, styles.textStrong, translate], ); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { return ( - {shouldUseNarrowLayout && !!selectionMode?.isEnabled && ( + {shouldUseNarrowLayout && isMobileSelectionModeEnabled && ( <> null} diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 9c79839e48b74..d0ca1716cf724 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -136,7 +136,7 @@ function MoneyRequestReportTransactionList({ const {isMouseDownOnInput, setMouseUp} = useMouseContext(); const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); const toggleTransaction = useCallback( (transactionID: string) => { @@ -273,7 +273,7 @@ function MoneyRequestReportTransactionList({ return; } - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { toggleTransaction(transaction.transactionID); return; } @@ -295,7 +295,7 @@ function MoneyRequestReportTransactionList({ if (!isSmallScreenWidth) { return; } - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { toggleTransaction(transaction.transactionID); return; } @@ -312,7 +312,7 @@ function MoneyRequestReportTransactionList({ taxAmountColumnSize={taxAmountColumnSize} shouldShowTooltip shouldUseNarrowLayout={shouldUseNarrowLayout || isMediumScreenWidth} - shouldShowCheckbox={!!selectionMode?.isEnabled || !isSmallScreenWidth} + shouldShowCheckbox={isMobileSelectionModeEnabled || !isSmallScreenWidth} onCheckboxPress={toggleTransaction} columns={allReportColumns} scrollToNewTransaction={transaction.transactionID === newTransactions?.at(0)?.transactionID ? scrollToNewTransaction : undefined} @@ -355,7 +355,7 @@ function MoneyRequestReportTransactionList({ title={translate('common.select')} icon={Expensicons.CheckSquare} onPress={() => { - if (!selectionMode?.isEnabled) { + if (!isMobileSelectionModeEnabled) { turnOnMobileSelectionMode(); } toggleTransaction(selectedTransactionID); diff --git a/src/components/Navigation/SearchSidebar.tsx b/src/components/Navigation/SearchSidebar.tsx index 74793bb5515a3..6cea273c6cba7 100644 --- a/src/components/Navigation/SearchSidebar.tsx +++ b/src/components/Navigation/SearchSidebar.tsx @@ -57,7 +57,7 @@ function SearchSidebar({state}: SearchSidebarProps) { } }, [lastSearchType, queryJSON, setLastSearchType, currentSearchResults]); - const isDataLoaded = isSearchDataLoaded(currentSearchResults, lastNonEmptySearchResults, queryJSON); + const isDataLoaded = isSearchDataLoaded(currentSearchResults?.data ? currentSearchResults : lastNonEmptySearchResults, queryJSON); const shouldShowLoadingState = route?.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT ? false : !isOffline && !isDataLoaded; if (shouldUseNarrowLayout) { diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 940891a69f8c4..6e0d67e39daa4 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -3,6 +3,7 @@ import {isMoneyRequestReport} from '@libs/ReportUtils'; import {isTransactionCardGroupListItemType, isTransactionListItemType, isTransactionMemberGroupListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {SearchContext, SearchContextData} from './types'; const defaultSearchContextData: SearchContextData = { @@ -41,10 +42,15 @@ function SearchContextProvider({children}: ChildrenProps) { const areTransactionsEmpty = useRef(true); const setCurrentSearchHash = useCallback((searchHash: number) => { - setSearchContextData((prevState) => ({ - ...prevState, - currentSearchHash: searchHash, - })); + setSearchContextData((prevState) => { + if (searchHash === prevState.currentSearchHash) { + return prevState; + } + return { + ...prevState, + currentSearchHash: searchHash, + }; + }); }, []); const setSelectedTransactions: SearchContext['setSelectedTransactions'] = useCallback((selectedTransactions, data = []) => { @@ -107,6 +113,10 @@ function SearchContextProvider({children}: ChildrenProps) { if (searchHashOrClearIDsFlag === searchContextData.currentSearchHash) { return; } + + if (searchContextData.selectedReports.length === 0 && isEmptyObject(searchContextData.selectedTransactions) && !searchContextData.shouldTurnOffSelectionMode) { + return; + } setSearchContextData((prevState) => ({ ...prevState, shouldTurnOffSelectionMode, @@ -116,7 +126,13 @@ function SearchContextProvider({children}: ChildrenProps) { setShouldShowExportModeOption(false); setExportMode(false); }, - [searchContextData.currentSearchHash, setSelectedTransactions], + [ + searchContextData.currentSearchHash, + searchContextData.selectedReports.length, + searchContextData.selectedTransactions, + searchContextData.shouldTurnOffSelectionMode, + setSelectedTransactions, + ], ); const removeTransaction: SearchContext['removeTransaction'] = useCallback( diff --git a/src/components/Search/SearchList.tsx b/src/components/Search/SearchList.tsx index a6f6bf436092e..a7ecf385af49b 100644 --- a/src/components/Search/SearchList.tsx +++ b/src/components/Search/SearchList.tsx @@ -20,12 +20,11 @@ import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useKeyboardState from '@hooks/useKeyboardState'; import useLocalize from '@hooks/useLocalize'; -import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings'; import useThemeStyles from '@hooks/useThemeStyles'; -import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; +import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import {isMobileChrome} from '@libs/Browser'; import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener'; import variables from '@styles/variables'; @@ -78,6 +77,13 @@ type SearchListProps = Pick, 'onScroll' /** Invoked on mount and layout changes */ onLayout?: () => void; + + /** Whether mobile selection mode is enabled */ + isMobileSelectionModeEnabled: boolean; +}; + +const keyExtractor = (item: SearchListItem, index: number) => { + return item.keyForList ?? `${index}`; }; const onScrollToIndexFailed = () => {}; @@ -102,6 +108,7 @@ function SearchList( queryJSON, onViewableItemsChanged, onLayout, + isMobileSelectionModeEnabled, }: SearchListProps, ref: ForwardedRef, ) { @@ -126,12 +133,7 @@ function SearchList( const {isSmallScreenWidth} = useResponsiveLayout(); const [isModalVisible, setIsModalVisible] = useState(false); - const {selectionMode} = useMobileSelectionMode(); const [longPressedItem, setLongPressedItem] = useState(); - // Check if selection should be on when the modal is opened - const wasSelectionOnRef = useRef(false); - // Keep track of the number of selected items to determine if we should turn off selection mode - const selectionRef = useRef(0); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { canBeMissing: true, @@ -139,38 +141,6 @@ function SearchList( const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); - useEffect(() => { - selectionRef.current = selectedItemsLength; - - if (!isSmallScreenWidth) { - if (selectedItemsLength === 0) { - turnOffMobileSelectionMode(); - } - return; - } - if (!isFocused) { - return; - } - if (!wasSelectionOnRef.current && selectedItemsLength > 0) { - wasSelectionOnRef.current = true; - } - if (selectedItemsLength > 0 && !selectionMode?.isEnabled) { - turnOnMobileSelectionMode(); - } else if (selectedItemsLength === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) { - turnOffMobileSelectionMode(); - } - }, [selectionMode, isSmallScreenWidth, isFocused, selectedItemsLength]); - - useEffect( - () => () => { - if (selectionRef.current !== 0) { - return; - } - turnOffMobileSelectionMode(); - }, - [], - ); - const handleLongPressRow = useCallback( (item: SearchListItem) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -181,14 +151,14 @@ function SearchList( if ('transactions' in item && item.transactions.length === 0) { return; } - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { onCheckboxPress(item); return; } setLongPressedItem(item); setIsModalVisible(true); }, - [isFocused, isSmallScreenWidth, onCheckboxPress, selectionMode?.isEnabled, shouldPreventLongPressRow], + [isFocused, isSmallScreenWidth, onCheckboxPress, isMobileSelectionModeEnabled, shouldPreventLongPressRow], ); const turnOnSelectionMode = useCallback(() => { @@ -398,7 +368,7 @@ function SearchList( item.keyForList ?? `${index}`} + keyExtractor={keyExtractor} onScroll={onScroll} contentContainerStyle={contentContainerStyle} showsVerticalScrollIndicator={false} diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index 2438946ebdec3..70e0d749fdd05 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -44,9 +44,10 @@ import type {SearchHeaderOptionValue} from './SearchPageHeader'; type SearchFiltersBarProps = { queryJSON: SearchQueryJSON; headerButtonsOptions: Array>; + isMobileSelectionModeEnabled: boolean; }; -function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarProps) { +function SearchFiltersBar({queryJSON, headerButtonsOptions, isMobileSelectionModeEnabled}: SearchFiltersBarProps) { const scrollRef = useRef(null); // type, groupBy and status values are not guaranteed to respect the ts type as they come from user input @@ -70,7 +71,6 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: true}); const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: true}); const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true}); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true}); const [searchResultsErrors] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true, selector: (data) => data?.errors}); @@ -79,7 +79,7 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions ?? {}), [selectedTransactions]); const hasErrors = Object.keys(searchResultsErrors ?? {}).length > 0 && !isOffline; - const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || (!!selectionMode && selectionMode.isEnabled)); + const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || isMobileSelectionModeEnabled); const [typeOptions, type] = useMemo(() => { const options = getTypeOptions(allPolicies, email); diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index f0f8ae1295fc6..f14f52478edc0 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -3,11 +3,9 @@ import {View} from 'react-native'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import {useSearchContext} from '@components/Search/SearchContext'; import type {SearchQueryJSON} from '@components/Search/types'; -import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow'; import type CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import SearchPageHeaderInput from './SearchPageHeaderInput'; @@ -18,18 +16,26 @@ type SearchPageHeaderProps = { onSearchRouterFocus?: () => void; headerButtonsOptions: Array>; handleSearch: (value: string) => void; + isMobileSelectionModeEnabled: boolean; }; type SearchHeaderOptionValue = DeepValueOf | undefined; -function SearchPageHeader({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, headerButtonsOptions, handleSearch}: SearchPageHeaderProps) { +function SearchPageHeader({ + queryJSON, + searchRouterListVisible, + hideSearchRouterList, + onSearchRouterFocus, + headerButtonsOptions, + handleSearch, + isMobileSelectionModeEnabled, +}: SearchPageHeaderProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const {selectedTransactions} = useSearchContext(); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); - if (shouldUseNarrowLayout && selectionMode?.isEnabled) { + if (shouldUseNarrowLayout && isMobileSelectionModeEnabled) { return ( ) => void; contentContainerStyle?: StyleProp; - currentSearchResults?: SearchResults; - lastNonEmptySearchResults?: SearchResults; + searchResults?: SearchResults; handleSearch: (value: SearchParams) => void; + isMobileSelectionModeEnabled: boolean; }; function mapTransactionItemToSelectedEntry(item: TransactionListItemType, reportActions: ReportAction[]): [string, SelectedTransactionInfo] { @@ -136,7 +136,7 @@ function prepareTransactionsList(item: TransactionListItemType, selectedTransact }; } -function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onSearchListScroll, contentContainerStyle, handleSearch}: SearchProps) { +function Search({queryJSON, searchResults, onSearchListScroll, contentContainerStyle, handleSearch, isMobileSelectionModeEnabled}: SearchProps) { const {isOffline} = useNetwork(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); @@ -157,7 +157,6 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS isExportMode, setExportMode, } = useSearchContext(); - const {selectionMode} = useMobileSelectionMode(); const [offset, setOffset] = useState(0); const {type, status, sortBy, sortOrder, hash, groupBy} = queryJSON; @@ -183,7 +182,6 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS }, [hash, clearSelectedTransactions, setCurrentSearchHash]), ); - const searchResults = currentSearchResults?.data ? currentSearchResults : lastNonEmptySearchResults; const isSearchResultsEmpty = !searchResults?.data || isSearchResultsEmptyUtil(searchResults); useEffect(() => { @@ -192,31 +190,29 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS } const selectedKeys = Object.keys(selectedTransactions).filter((key) => selectedTransactions[key]); - if (selectedKeys.length === 0 && selectionMode?.isEnabled && shouldTurnOffSelectionMode) { + if (selectedKeys.length === 0 && isMobileSelectionModeEnabled && shouldTurnOffSelectionMode) { turnOffMobileSelectionMode(); } // We don't want to run the effect on isFocused change as we only need it to early return when it is false. // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTransactions, selectionMode?.isEnabled, shouldTurnOffSelectionMode]); + }, [selectedTransactions, isMobileSelectionModeEnabled, shouldTurnOffSelectionMode]); useEffect(() => { const selectedKeys = Object.keys(selectedTransactions).filter((key) => selectedTransactions[key]); if (!isSmallScreenWidth) { - if (selectedKeys.length === 0) { + if (selectedKeys.length === 0 && isMobileSelectionModeEnabled) { turnOffMobileSelectionMode(); } return; } - if (selectedKeys.length > 0 && !selectionMode?.isEnabled && !isSearchResultsEmpty) { + if (selectedKeys.length > 0 && !isMobileSelectionModeEnabled && !isSearchResultsEmpty) { turnOnMobileSelectionMode(); } - - // We don't need to run the effect on change of isSearchResultsEmpty. // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSmallScreenWidth, selectedTransactions, selectionMode?.isEnabled]); + }, [isSmallScreenWidth, selectedTransactions, isMobileSelectionModeEnabled]); useEffect(() => { if (isOffline) { @@ -242,7 +238,7 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS // There's a race condition in Onyx which makes it return data from the previous Search, so in addition to checking that the data is loaded // we also need to check that the searchResults matches the type and status of the current search - const isDataLoaded = isSearchDataLoaded(currentSearchResults, lastNonEmptySearchResults, queryJSON); + const isDataLoaded = isSearchDataLoaded(searchResults, queryJSON); const shouldShowLoadingState = !isOffline && (!isDataLoaded || (!!searchResults?.search.isLoading && Array.isArray(searchResults?.data) && searchResults?.data.length === 0)); const shouldShowLoadingMoreItems = !shouldShowLoadingState && searchResults?.search?.isLoading && searchResults?.search?.offset > 0; @@ -315,6 +311,10 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS }; }); } + if (isEmptyObject(newTransactionList)) { + return; + } + setSelectedTransactions(newTransactionList, data); isRefreshingSelection.current = true; @@ -403,7 +403,7 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS const openReport = useCallback( (item: SearchListItem) => { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { toggleTransaction(item); return; } @@ -453,7 +453,7 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})); }, - [hash, selectionMode?.isEnabled, toggleTransaction], + [hash, isMobileSelectionModeEnabled, toggleTransaction], ); const onViewableItemsChanged = useCallback( @@ -473,6 +473,76 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS [shouldShowLoadingState], ); + const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; + const isTask = type === CONST.SEARCH.DATA_TYPES.TASK; + const canSelectMultiple = !isChat && !isTask && (!isSmallScreenWidth || isMobileSelectionModeEnabled); + const ListItem = getListItem(type, status, groupBy); + const sortedSelectedData = useMemo( + () => + getSortedSections(type, status, data, sortBy, sortOrder, groupBy).map((item) => { + const baseKey = isChat + ? `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}` + : `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; + // Check if the base key matches the newSearchResultKey (TransactionListItemType) + const isBaseKeyMatch = baseKey === newSearchResultKey; + // Check if any transaction within the transactions array (TransactionGroupListItemType) matches the newSearchResultKey + const isAnyTransactionMatch = + !isChat && + (item as TransactionGroupListItemType)?.transactions?.some((transaction) => { + const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; + return transactionKey === newSearchResultKey; + }); + // Determine if either the base key or any transaction key matches + const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch; + + return mapToItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight); + }), + [type, status, data, sortBy, sortOrder, groupBy, isChat, newSearchResultKey, selectedTransactions, canSelectMultiple], + ); + + const hasErrors = Object.keys(searchResults?.errors ?? {}).length > 0 && !isOffline; + + const fetchMoreResults = useCallback(() => { + if (!searchResults?.search?.hasMoreResults || shouldShowLoadingState || shouldShowLoadingMoreItems) { + return; + } + setOffset(offset + CONST.SEARCH.RESULTS_PAGE_SIZE); + }, [offset, searchResults?.search?.hasMoreResults, shouldShowLoadingMoreItems, shouldShowLoadingState]); + + const toggleAllTransactions = useCallback(() => { + const areItemsGrouped = !!groupBy; + const totalSelected = Object.keys(selectedTransactions).length; + + if (totalSelected > 0) { + clearSelectedTransactions(); + return; + } + + if (areItemsGrouped) { + setSelectedTransactions( + Object.fromEntries( + (data as TransactionGroupListItemType[]).flatMap((item) => + item.transactions.filter((t) => !isTransactionPendingDelete(t)).map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray)), + ), + ), + data, + ); + + return; + } + + setSelectedTransactions( + Object.fromEntries( + (data as TransactionListItemType[]) + .filter((t) => !isTransactionPendingDelete(t)) + .map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray)), + ), + data, + ); + }, [clearSelectedTransactions, data, groupBy, reportActionsArray, selectedTransactions, setSelectedTransactions]); + + const onLayout = useCallback(() => handleSelectionListScroll(sortedSelectedData, searchListRef.current), [handleSelectionListScroll, sortedSelectedData]); + if (shouldShowLoadingState) { return ( {null}; } - const ListItem = getListItem(type, status, groupBy); - const sortedData = getSortedSections(type, status, data, sortBy, sortOrder, groupBy); - - const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; - const isTask = type === CONST.SEARCH.DATA_TYPES.TASK; - const canSelectMultiple = !isChat && !isTask && (!isSmallScreenWidth || selectionMode?.isEnabled === true); - - const sortedSelectedData = sortedData.map((item) => { - const baseKey = isChat - ? `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(item as ReportActionListItemType).reportActionID}` - : `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; - // Check if the base key matches the newSearchResultKey (TransactionListItemType) - const isBaseKeyMatch = baseKey === newSearchResultKey; - // Check if any transaction within the transactions array (TransactionGroupListItemType) matches the newSearchResultKey - const isAnyTransactionMatch = - !isChat && - (item as TransactionGroupListItemType)?.transactions?.some((transaction) => { - const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`; - return transactionKey === newSearchResultKey; - }); - // Determine if either the base key or any transaction key matches - const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch; - - return mapToItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight); - }); - - const hasErrors = Object.keys(searchResults?.errors ?? {}).length > 0 && !isOffline; - if (hasErrors) { return ( @@ -542,45 +584,6 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS ); } - const fetchMoreResults = () => { - if (!searchResults?.search?.hasMoreResults || shouldShowLoadingState || shouldShowLoadingMoreItems) { - return; - } - setOffset(offset + CONST.SEARCH.RESULTS_PAGE_SIZE); - }; - - const toggleAllTransactions = () => { - const areItemsGrouped = !!groupBy; - const totalSelected = Object.keys(selectedTransactions).length; - - if (totalSelected > 0) { - clearSelectedTransactions(); - return; - } - - if (areItemsGrouped) { - setSelectedTransactions( - Object.fromEntries( - (data as TransactionGroupListItemType[]).flatMap((item) => - item.transactions.filter((t) => !isTransactionPendingDelete(t)).map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray)), - ), - ), - data, - ); - - return; - } - - setSelectedTransactions( - Object.fromEntries( - (data as TransactionListItemType[]) - .filter((t) => !isTransactionPendingDelete(t)) - .map((transactionItem) => mapTransactionItemToSelectedEntry(transactionItem, reportActionsArray)), - ), - data, - ); - }; - const onSortPress = (column: SearchColumnType, order: SortOrder) => { const newQuery = buildSearchQueryString({...queryJSON, sortBy: column, sortOrder: order}); navigation.setParams({q: newQuery}); @@ -634,7 +637,8 @@ function Search({queryJSON, currentSearchResults, lastNonEmptySearchResults, onS } queryJSON={queryJSON} onViewableItemsChanged={onViewableItemsChanged} - onLayout={() => handleSelectionListScroll(sortedSelectedData, searchListRef.current)} + onLayout={onLayout} + isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} /> ); diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index ccc1d98894c1a..b433330884549 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import type {ValueOf} from 'type-fest'; import {useSearchContext} from '@components/Search/SearchContext'; import BaseListItem from '@components/SelectionList/BaseListItem'; @@ -8,7 +8,7 @@ import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {handleActionButtonPress} from '@libs/actions/Search'; +import {handleActionButtonPress as handleActionButtonPressUtil} from '@libs/actions/Search'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import UserInfoAndActionButtonRow from './UserInfoAndActionButtonRow'; @@ -83,6 +83,14 @@ function TransactionListItem({ [transactionItem?.shouldShowCategory, transactionItem?.shouldShowTag, transactionItem?.shouldShowTax], ); + const handleActionButtonPress = useCallback(() => { + handleActionButtonPressUtil(currentSearchHash, transactionItem, () => onSelectRow(item), shouldUseNarrowLayout && !!canSelectMultiple); + }, [canSelectMultiple, currentSearchHash, item, onSelectRow, shouldUseNarrowLayout, transactionItem]); + + const handleCheckboxPress = useCallback(() => { + onCheckboxPress?.(item); + }, [item, onCheckboxPress]); + return ( ({ {!isLargeScreenWidth && ( { - handleActionButtonPress(currentSearchHash, transactionItem, () => onSelectRow(item), shouldUseNarrowLayout && !!canSelectMultiple); - }} + handleActionButtonPress={handleActionButtonPress} shouldShowUserInfo={!!transactionItem?.from} /> )} { - handleActionButtonPress(currentSearchHash, transactionItem, () => onSelectRow(item), shouldUseNarrowLayout && !!canSelectMultiple); - }} - onCheckboxPress={() => onCheckboxPress?.(item)} + onButtonPress={handleActionButtonPress} + onCheckboxPress={handleCheckboxPress} shouldUseNarrowLayout={!isLargeScreenWidth} columns={columns} isParentHovered={hovered} diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index 2c2a9cd58fdfd..a155c7f9cbd26 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -41,7 +41,7 @@ function SelectionListWithModal( const {isSmallScreenWidth} = useResponsiveLayout(); const isFocused = useIsFocused(); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); // Check if selection should be on when the modal is opened const wasSelectionOnRef = useRef(false); // Keep track of the number of selected items to determine if we should turn off selection mode @@ -60,7 +60,7 @@ function SelectionListWithModal( selectionRef.current = selectedItems.length; if (!isSmallScreenWidth) { - if (selectedItems.length === 0) { + if (selectedItems.length === 0 && isMobileSelectionModeEnabled) { turnOffMobileSelectionMode(); } return; @@ -71,13 +71,13 @@ function SelectionListWithModal( if (!wasSelectionOnRef.current && selectedItems.length > 0) { wasSelectionOnRef.current = true; } - if (selectedItems.length > 0 && !selectionMode?.isEnabled) { + if (selectedItems.length > 0 && !isMobileSelectionModeEnabled) { turnOnMobileSelectionMode(); - } else if (selectedItems.length === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) { + } else if (selectedItems.length === 0 && isMobileSelectionModeEnabled && !wasSelectionOnRef.current) { turnOffMobileSelectionMode(); } // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [sections, selectedItemsProp, selectionMode, isSmallScreenWidth, isSelected, isFocused]); + }, [sections, selectedItemsProp, isMobileSelectionModeEnabled, isSmallScreenWidth, isSelected, isFocused]); useEffect( () => () => { @@ -94,7 +94,7 @@ function SelectionListWithModal( if (!turnOnSelectionModeOnLongPress || !isSmallScreenWidth || item?.isDisabled || item?.isDisabledCheckbox || (!isFocused && !isScreenFocused)) { return; } - if (isSmallScreenWidth && selectionMode?.isEnabled) { + if (isSmallScreenWidth && isMobileSelectionModeEnabled) { rest?.onCheckboxPress?.(item); return; } diff --git a/src/hooks/useMobileSelectionMode.ts b/src/hooks/useMobileSelectionMode.ts index 43ad7e65eb8b0..7d53ad4ee7674 100644 --- a/src/hooks/useMobileSelectionMode.ts +++ b/src/hooks/useMobileSelectionMode.ts @@ -1,14 +1,19 @@ -import {useEffect} from 'react'; +import {useEffect, useRef} from 'react'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import ONYXKEYS from '@src/ONYXKEYS'; import useOnyx from './useOnyx'; export default function useMobileSelectionMode() { - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); + const [isSelectionModeEnabled] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {initialValue: false, initWithStoredValues: false, canBeMissing: true}); + const initialSelectionModeValueRef = useRef(isSelectionModeEnabled); useEffect(() => { + // in case the selection mode is already off at the start, we don't need to turn it off again + if (!initialSelectionModeValueRef.current) { + return; + } turnOffMobileSelectionMode(); }, []); - return {selectionMode}; + return !!isSelectionModeEnabled; } diff --git a/src/hooks/useSearchBackPress/index.android.ts b/src/hooks/useSearchBackPress/index.android.ts index 92e637f0b931f..dc2d4626bbe3c 100644 --- a/src/hooks/useSearchBackPress/index.android.ts +++ b/src/hooks/useSearchBackPress/index.android.ts @@ -7,11 +7,11 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type UseSearchBackPress from './types'; const useSearchBackPress: UseSearchBackPress = ({onClearSelection, onNavigationCallBack}) => { - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); + const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); useFocusEffect( useCallback(() => { const onBackPress = () => { - if (selectionMode?.isEnabled) { + if (selectionMode) { onClearSelection(); turnOffMobileSelectionMode(); return true; @@ -21,7 +21,7 @@ const useSearchBackPress: UseSearchBackPress = ({onClearSelection, onNavigationC }; const backHandler = BackHandler.addEventListener('hardwareBackPress', onBackPress); return () => backHandler.remove(); - }, [selectionMode?.isEnabled, onClearSelection, onNavigationCallBack]), + }, [selectionMode, onClearSelection, onNavigationCallBack]), ); }; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 2e46931454a0e..d2f672fecc955 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1641,8 +1641,7 @@ function shouldShowEmptyState(isDataLoaded: boolean, dataLength: number, type: S return !isDataLoaded || dataLength === 0 || !Object.values(CONST.SEARCH.DATA_TYPES).includes(type); } -function isSearchDataLoaded(currentSearchResults: SearchResults | undefined, lastNonEmptySearchResults: SearchResults | undefined, queryJSON: SearchQueryJSON | undefined) { - const searchResults = currentSearchResults?.data ? currentSearchResults : lastNonEmptySearchResults; +function isSearchDataLoaded(searchResults: SearchResults | undefined, queryJSON: SearchQueryJSON | undefined) { const {status} = queryJSON ?? {}; const isDataLoaded = diff --git a/src/libs/actions/MobileSelectionMode.ts b/src/libs/actions/MobileSelectionMode.ts index 65a51b8349016..65c6e463461be 100644 --- a/src/libs/actions/MobileSelectionMode.ts +++ b/src/libs/actions/MobileSelectionMode.ts @@ -2,11 +2,11 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; const turnOnMobileSelectionMode = () => { - Onyx.merge(ONYXKEYS.MOBILE_SELECTION_MODE, {isEnabled: true}); + Onyx.merge(ONYXKEYS.MOBILE_SELECTION_MODE, true); }; const turnOffMobileSelectionMode = () => { - Onyx.merge(ONYXKEYS.MOBILE_SELECTION_MODE, {isEnabled: false}); + Onyx.merge(ONYXKEYS.MOBILE_SELECTION_MODE, false); }; export {turnOnMobileSelectionMode, turnOffMobileSelectionMode}; diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx index e02fa651496de..b684262789b4c 100755 --- a/src/pages/ReportParticipantsPage.tsx +++ b/src/pages/ReportParticipantsPage.tsx @@ -75,7 +75,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { const isReportArchived = useReportIsArchived(report?.reportID); const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {canBeMissing: false}); const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (attributes) => attributes?.reports, canBeMissing: false}); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); const currentUserAccountID = Number(session?.accountID); @@ -83,7 +83,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { const isGroupChat = useMemo(() => isGroupChatUtils(report), [report]); const isFocused = useIsFocused(); const {isOffline} = useNetwork(); - const canSelectMultiple = isGroupChat && isCurrentUserAdmin && (isSmallScreenWidth ? selectionMode?.isEnabled : true); + const canSelectMultiple = isGroupChat && isCurrentUserAdmin && (isSmallScreenWidth ? isMobileSelectionModeEnabled : true); const [searchValue, setSearchValue] = useState(''); const {chatParticipants, personalDetailsParticipants} = useMemo( @@ -383,7 +383,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { return translate('common.details'); }, [report, translate, isGroupChat]); - const selectionModeHeader = selectionMode?.isEnabled && isSmallScreenWidth; + const selectionModeHeader = isMobileSelectionModeEnabled && isSmallScreenWidth; // eslint-disable-next-line rulesdir/no-negated-variables const memberNotFoundMessage = isGroupChat @@ -401,7 +401,7 @@ function ReportParticipantsPage({report, route}: ReportParticipantsPageProps) { { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { setSelectedMembers([]); turnOffMobileSelectionMode(); return; diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 70a2bb7a89685..c17f854f2a63d 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -18,6 +18,7 @@ import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentU import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useFilteredSelection from '@hooks/useFilteredSelection'; import useLocalize from '@hooks/useLocalize'; +import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -91,8 +92,8 @@ function RoomMembersPage({report, policy}: RoomMembersPageProps) { // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the selection mode only on small screens // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); - const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true; + const isMobileSelectionModeEnabled = useMobileSelectionMode(); + const canSelectMultiple = isSmallScreenWidth ? isMobileSelectionModeEnabled : true; /** * Get members for the current room @@ -357,7 +358,7 @@ function RoomMembersPage({report, policy}: RoomMembersPageProps) { }, [report, backTo], ); - const selectionModeHeader = selectionMode?.isEnabled && isSmallScreenWidth; + const selectionModeHeader = isMobileSelectionModeEnabled && isSmallScreenWidth; const customListHeader = useMemo(() => { const header = ( @@ -392,7 +393,7 @@ function RoomMembersPage({report, policy}: RoomMembersPageProps) { title={selectionModeHeader ? translate('common.selectMultiple') : translate('workspace.common.members')} subtitle={StringUtils.lineBreaksToSpaces(getReportName(report))} onBackButtonPress={() => { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { setSelectedMembers([]); turnOffMobileSelectionMode(); return; diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 82e5dfe08b3c0..8e9e42e7d38f0 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; @@ -19,6 +19,7 @@ import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContex import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useFilesValidation from '@hooks/useFilesValidation'; import useLocalize from '@hooks/useLocalize'; +import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; @@ -69,8 +70,8 @@ function SearchPage({route}: SearchPageProps) { const {isOffline} = useNetwork(); const {selectedTransactions, clearSelectedTransactions, selectedReports, lastSearchType, setLastSearchType, isExportMode, setExportMode} = useSearchContext(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); - const [lastPaymentMethods = {}] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); + const [lastPaymentMethods] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); @@ -88,7 +89,7 @@ function SearchPage({route}: SearchPageProps) { // eslint-disable-next-line rulesdir/no-default-id-values const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${queryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID}`, {canBeMissing: true}); - const [lastNonEmptySearchResults, setLastNonEmptySearchResults] = useState(undefined); + const lastNonEmptySearchResults = useRef(undefined); useEffect(() => { confirmReadyToOpenApp(); @@ -101,7 +102,7 @@ function SearchPage({route}: SearchPageProps) { setLastSearchType(currentSearchResults.search.type); if (currentSearchResults.data) { - setLastNonEmptySearchResults(currentSearchResults); + lastNonEmptySearchResults.current = currentSearchResults; } }, [lastSearchType, queryJSON, setLastSearchType, currentSearchResults]); @@ -467,7 +468,7 @@ function SearchPage({route}: SearchPageProps) { const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()})); const {resetVideoPlayerData} = usePlaybackContext(); - const shouldShowOfflineIndicator = currentSearchResults?.data ?? lastNonEmptySearchResults; + const shouldShowOfflineIndicator = currentSearchResults?.data ?? lastNonEmptySearchResults.current; // Handles video player cleanup: // 1. On mount: Resets player if navigating from report screen @@ -504,8 +505,8 @@ function SearchPage({route}: SearchPageProps) { {ErrorModal} - {!!selectionMode && selectionMode?.isEnabled && ( + {isMobileSelectionModeEnabled && ( >; - currentSearchResults?: SearchResults; - lastNonEmptySearchResults?: SearchResults; + searchResults?: SearchResults; + isMobileSelectionModeEnabled: boolean; }; -function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults, lastNonEmptySearchResults}: SearchPageNarrowProps) { +function SearchPageNarrow({queryJSON, headerButtonsOptions, searchResults, isMobileSelectionModeEnabled}: SearchPageNarrowProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {windowHeight} = useWindowDimensions(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); const {clearSelectedTransactions} = useSearchContext(); const [searchRouterListVisible, setSearchRouterListVisible] = useState(false); - const searchResults = currentSearchResults?.data ? currentSearchResults : lastNonEmptySearchResults; const {isOffline} = useNetwork(); // Controls the visibility of the educational tooltip based on user scrolling. @@ -62,12 +58,12 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults const triggerScrollEvent = useScrollEventEmitter(); const handleBackButtonPress = useCallback(() => { - if (!selectionMode?.isEnabled) { + if (!isMobileSelectionModeEnabled) { return false; } clearSelectedTransactions(undefined, true); return true; - }, [selectionMode, clearSelectedTransactions]); + }, [isMobileSelectionModeEnabled, clearSelectedTransactions]); useHandleBackButton(handleBackButtonPress); @@ -77,25 +73,28 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults top: topBarOffset.get(), })); - const scrollHandler = useAnimatedScrollHandler({ - onScroll: (event) => { - runOnJS(triggerScrollEvent)(); - const {contentOffset, layoutMeasurement, contentSize} = event; - if (windowHeight > contentSize.height) { - return; - } - const currentOffset = contentOffset.y; - const isScrollingDown = currentOffset > scrollOffset.get(); - const distanceScrolled = currentOffset - scrollOffset.get(); - - if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) { - topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, StyleUtils.searchHeaderDefaultOffset)); - } else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) { - topBarOffset.set(withTiming(StyleUtils.searchHeaderDefaultOffset, {duration: ANIMATION_DURATION_IN_MS})); - } - scrollOffset.set(currentOffset); + const scrollHandler = useAnimatedScrollHandler( + { + onScroll: (event) => { + runOnJS(triggerScrollEvent)(); + const {contentOffset, layoutMeasurement, contentSize} = event; + if (windowHeight > contentSize.height) { + return; + } + const currentOffset = contentOffset.y; + const isScrollingDown = currentOffset > scrollOffset.get(); + const distanceScrolled = currentOffset - scrollOffset.get(); + + if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) { + topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, StyleUtils.searchHeaderDefaultOffset)); + } else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) { + topBarOffset.set(withTiming(StyleUtils.searchHeaderDefaultOffset, {duration: ANIMATION_DURATION_IN_MS})); + } + scrollOffset.set(currentOffset); + }, }, - }); + [], + ); const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_ROOT.getRoute({query: buildCannedSearchQuery()})); @@ -133,7 +132,7 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults ); } - const isDataLoaded = isSearchDataLoaded(currentSearchResults, lastNonEmptySearchResults, queryJSON); + const isDataLoaded = isSearchDataLoaded(searchResults, queryJSON); const shouldShowLoadingState = !isOffline && !isDataLoaded; return ( @@ -146,7 +145,7 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults shouldShowOfflineIndicator={!!searchResults} > - {!selectionMode?.isEnabled ? ( + {!isMobileSelectionModeEnabled ? ( @@ -178,6 +178,7 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults )} @@ -198,19 +199,20 @@ function SearchPageNarrow({queryJSON, headerButtonsOptions, currentSearchResults queryJSON={queryJSON} headerButtonsOptions={headerButtonsOptions} handleSearch={handleSearchAction} + isMobileSelectionModeEnabled={isMobileSelectionModeEnabled} /> )} {!searchRouterListVisible && ( )} diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 78eebef028ea2..3c702ec0542c9 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -139,7 +139,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers ); const [invitedEmailsToAccountIDsDraft] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, {canBeMissing: true}); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const currentUserAccountID = Number(session?.accountID); const selectionListRef = useRef(null); @@ -158,7 +158,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers [personalDetails, policy?.employeeList, policy?.owner, policyApproverEmail], ); - const canSelectMultiple = isPolicyAdmin && (shouldUseNarrowLayout ? selectionMode?.isEnabled : true); + const canSelectMultiple = isPolicyAdmin && (shouldUseNarrowLayout ? isMobileSelectionModeEnabled : true); const confirmModalPrompt = useMemo(() => { const approverAccountID = selectedEmployees.find((selectedEmployee) => isApprover(policy, selectedEmployee)); @@ -523,12 +523,12 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers ); useEffect(() => { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { return; } setSelectedEmployees([]); - }, [setSelectedEmployees, selectionMode?.isEnabled]); + }, [setSelectedEmployees, isMobileSelectionModeEnabled]); useSearchBackPress({ onClearSelection: () => setSelectedEmployees([]), @@ -702,7 +702,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers ); }; - const selectionModeHeader = selectionMode?.isEnabled && shouldUseNarrowLayout; + const selectionModeHeader = isMobileSelectionModeEnabled && shouldUseNarrowLayout; const headerContent = ( <> @@ -740,7 +740,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers shouldShowOfflineIndicatorInWideScreen shouldShowNonAdmin onBackButtonPress={() => { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { setSelectedEmployees([]); turnOffMobileSelectionMode(); return; diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 9cbd79292ff96..53accfba95bed 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -79,7 +79,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const policyId = route.params.policyID; const backTo = route.params?.backTo; const policy = usePolicy(policyId); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const [policyTagLists] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyId}`, {canBeMissing: true}); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`, {canBeMissing: true}); @@ -93,7 +93,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const filterCategories = useCallback((category: PolicyCategory | undefined) => !!category && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, []); const [selectedCategories, setSelectedCategories] = useFilteredSelection(policyCategories, filterCategories); - const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true; + const canSelectMultiple = isSmallScreenWidth ? isMobileSelectionModeEnabled : true; const fetchCategories = useCallback(() => { openPolicyCategoriesPage(policyId); @@ -203,7 +203,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { }; const navigateToCategorySettings = (category: PolicyOption) => { - if (isSmallScreenWidth && selectionMode?.isEnabled) { + if (isSmallScreenWidth && isMobileSelectionModeEnabled) { toggleCategory(category); return; } @@ -400,14 +400,14 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const isLoading = !isOffline && policyCategories === undefined; useEffect(() => { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { return; } setSelectedCategories([]); - }, [setSelectedCategories, selectionMode?.isEnabled]); + }, [setSelectedCategories, isMobileSelectionModeEnabled]); - const selectionModeHeader = selectionMode?.isEnabled && shouldUseNarrowLayout; + const selectionModeHeader = isMobileSelectionModeEnabled && shouldUseNarrowLayout; const headerContent = ( <> @@ -475,7 +475,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { icon={!selectionModeHeader ? Illustrations.FolderOpen : undefined} shouldUseHeadlineHeader={!selectionModeHeader} onBackButtonPress={() => { - if (selectionMode?.isEnabled) { + if (isMobileSelectionModeEnabled) { setSelectedCategories([]); turnOffMobileSelectionMode(); return; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index a1ef02cb5a9cb..54c81194052b1 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -62,9 +62,9 @@ function PolicyDistanceRatesPage({ const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const policy = usePolicy(policyID); - const {selectionMode} = useMobileSelectionMode(); + const isMobileSelectionModeEnabled = useMobileSelectionMode(); - const canSelectMultiple = shouldUseNarrowLayout ? selectionMode?.isEnabled : true; + const canSelectMultiple = shouldUseNarrowLayout ? isMobileSelectionModeEnabled : true; const customUnit = getDistanceRateCustomUnit(policy); const customUnitRates: Record = useMemo(() => customUnit?.rates ?? {}, [customUnit]); @@ -325,7 +325,7 @@ function PolicyDistanceRatesPage({ const headerButtons = ( - {(shouldUseNarrowLayout ? !selectionMode?.isEnabled : selectedDistanceRates.length === 0) ? ( + {(shouldUseNarrowLayout ? !isMobileSelectionModeEnabled : selectedDistanceRates.length === 0) ? ( <>