From 307929621c8645c274d1cd689f6db99714268b73 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 20 Sep 2024 11:36:21 +0700 Subject: [PATCH 1/6] fix: search header flickering on selection mode --- src/components/Search/index.tsx | 3 ++- src/components/SelectionListWithModal/index.tsx | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 4e23c0883f84..90cc443029c7 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -86,7 +86,7 @@ function Search({queryJSON}: SearchProps) { const navigation = useNavigation>(); const lastSearchResultsRef = useRef>(); const {setCurrentSearchHash, setSelectedTransactions, selectedTransactions, clearSelectedTransactions} = useSearchContext(); - const {selectionMode} = useMobileSelectionMode(); + const {selectionMode} = useMobileSelectionMode(false); const [offset, setOffset] = useState(0); const [offlineModalVisible, setOfflineModalVisible] = useState(false); @@ -371,6 +371,7 @@ function Search({queryJSON}: SearchProps) { /> ) } + shouldAutoTurnOff={false} canSelectMultiple={type !== CONST.SEARCH.DATA_TYPES.CHAT && canSelectMultiple} customListHeaderHeight={searchHeaderHeight} // To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling, diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index 2d218bc815fe..d89fffd53e5c 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {forwardRef, useEffect, useState} from 'react'; +import React, {forwardRef, useEffect, useRef, useState} from 'react'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; @@ -14,10 +14,11 @@ import CONST from '@src/CONST'; type SelectionListWithModalProps = BaseSelectionListProps & { turnOnSelectionModeOnLongPress?: boolean; onTurnOnSelectionMode?: (item: TItem | null) => void; + shouldAutoTurnOff?: boolean; }; function SelectionListWithModal( - {turnOnSelectionModeOnLongPress, onTurnOnSelectionMode, onLongPressRow, sections, ...rest}: SelectionListWithModalProps, + {turnOnSelectionModeOnLongPress, onTurnOnSelectionMode, onLongPressRow, sections, shouldAutoTurnOff, ...rest}: SelectionListWithModalProps, ref: ForwardedRef, ) { const [isModalVisible, setIsModalVisible] = useState(false); @@ -26,7 +27,8 @@ function SelectionListWithModal( // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout here because there is a race condition that causes shouldUseNarrowLayout to change indefinitely in this component // See https://github.com/Expensify/App/issues/48675 for more details const {isSmallScreenWidth} = useResponsiveLayout(); - const {selectionMode} = useMobileSelectionMode(true); + const {selectionMode} = useMobileSelectionMode(shouldAutoTurnOff); + const wasSelectionOnRef = useRef(false); useEffect(() => { // We can access 0 index safely as we are not displaying multiple sections in table view @@ -38,7 +40,10 @@ function SelectionListWithModal( return; } if (selectedItems.length > 0 && !selectionMode?.isEnabled) { + wasSelectionOnRef.current = true; turnOnMobileSelectionMode(); + } else if (selectedItems.length === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) { + turnOffMobileSelectionMode(); } }, [sections, selectionMode, isSmallScreenWidth]); From 3830db3df6f6c2c0f8931d76ab583eb2811a6e66 Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 23 Sep 2024 21:50:10 +0700 Subject: [PATCH 2/6] fix: transactions selection flickering --- src/components/SelectionListWithModal/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index d89fffd53e5c..0627f1422365 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -4,7 +4,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import SelectionList from '@components/SelectionList'; -import type {BaseSelectionListProps, ListItem, SelectionListHandle} from '@components/SelectionList/types'; +import type {BaseSelectionListProps, ListItem, ReportListItemType, SelectionListHandle} from '@components/SelectionList/types'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -32,15 +32,17 @@ function SelectionListWithModal( useEffect(() => { // We can access 0 index safely as we are not displaying multiple sections in table view - const selectedItems = sections[0].data.filter((item) => item.isSelected); + const selectedItems = sections[0].data.filter((item) => !!item.isSelected || (item as unknown as ReportListItemType)?.transactions?.some((transaction) => transaction.isSelected)); if (!isSmallScreenWidth) { if (selectedItems.length === 0) { turnOffMobileSelectionMode(); } return; } - if (selectedItems.length > 0 && !selectionMode?.isEnabled) { + if (!wasSelectionOnRef.current && selectedItems.length > 0) { wasSelectionOnRef.current = true; + } + if (selectedItems.length > 0 && !selectionMode?.isEnabled) { turnOnMobileSelectionMode(); } else if (selectedItems.length === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) { turnOffMobileSelectionMode(); From 2cc1288c8214a9d7ce5857d6c61e48d7ebc59a12 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 26 Sep 2024 15:56:18 +0700 Subject: [PATCH 3/6] fix: flickering after deselect items and navigate --- src/components/SelectionListWithModal/index.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/SelectionListWithModal/index.tsx b/src/components/SelectionListWithModal/index.tsx index 0627f1422365..eee9a2541100 100644 --- a/src/components/SelectionListWithModal/index.tsx +++ b/src/components/SelectionListWithModal/index.tsx @@ -29,10 +29,13 @@ function SelectionListWithModal( const {isSmallScreenWidth} = useResponsiveLayout(); const {selectionMode} = useMobileSelectionMode(shouldAutoTurnOff); const wasSelectionOnRef = useRef(false); + const selectionRef = useRef(0); useEffect(() => { // We can access 0 index safely as we are not displaying multiple sections in table view const selectedItems = sections[0].data.filter((item) => !!item.isSelected || (item as unknown as ReportListItemType)?.transactions?.some((transaction) => transaction.isSelected)); + selectionRef.current = selectedItems.length; + if (!isSmallScreenWidth) { if (selectedItems.length === 0) { turnOffMobileSelectionMode(); @@ -49,6 +52,16 @@ function SelectionListWithModal( } }, [sections, selectionMode, isSmallScreenWidth]); + useEffect( + () => () => { + if (selectionRef.current !== 0) { + return; + } + turnOffMobileSelectionMode(); + }, + [], + ); + const handleLongPressRow = (item: TItem) => { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if (!turnOnSelectionModeOnLongPress || !isSmallScreenWidth || item?.isDisabled || item?.isDisabledCheckbox) { From c7ae7814a4e124e198ad506912a6f85e876164eb Mon Sep 17 00:00:00 2001 From: daledah Date: Tue, 1 Oct 2024 10:22:46 +0700 Subject: [PATCH 4/6] fix: resolve conflict --- .../invoices/WorkspaceInvoiceVBASection.tsx | 118 ------------------ 1 file changed, 118 deletions(-) diff --git a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx index 9ba2fa43c904..1e876a9867c3 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx @@ -16,44 +16,16 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; -<<<<<<< HEAD import * as PaymentUtils from '@libs/PaymentUtils'; import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList'; -======= -import Navigation from '@libs/Navigation/Navigation'; -import * as PaymentUtils from '@libs/PaymentUtils'; -import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList'; -import type {FormattedSelectedPaymentMethodIcon} from '@pages/settings/Wallet/WalletPage/types'; ->>>>>>> be90481835 (integrate bank accounts logic) import variables from '@styles/variables'; import * as BankAccounts from '@userActions/BankAccounts'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -<<<<<<< HEAD -import type {AccountData} from '@src/types/onyx'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; -======= -import ROUTES from '@src/ROUTES'; import type {AccountData} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -type FormattedSelectedPaymentMethod = { - title: string; - icon?: FormattedSelectedPaymentMethodIcon; - description?: string; - type?: string; -}; - -type PaymentMethodState = { - isSelectedPaymentMethodDefault: boolean; - selectedPaymentMethod: AccountData; - formattedSelectedPaymentMethod: FormattedSelectedPaymentMethod; - methodID: string | number; - selectedPaymentMethodType: string; -}; ->>>>>>> be90481835 (integrate bank accounts logic) - type WorkspaceInvoiceVBASectionProps = { /** The policy ID currently being configured */ policyID: string; @@ -66,54 +38,24 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) const {translate} = useLocalize(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); -<<<<<<< HEAD const {paymentMethod, setPaymentMethod, resetSelectedPaymentMethodData} = usePaymentMethodState(); -======= - const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); - const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); ->>>>>>> be90481835 (integrate bank accounts logic) const addPaymentMethodAnchorRef = useRef(null); const paymentMethodButtonRef = useRef(null); const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false); const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false); const [shouldShowDefaultDeleteMenu, setShouldShowDefaultDeleteMenu] = useState(false); -<<<<<<< HEAD -======= - const [paymentMethod, setPaymentMethod] = useState({ - isSelectedPaymentMethodDefault: false, - selectedPaymentMethod: {}, - formattedSelectedPaymentMethod: { - title: '', - }, - methodID: '', - selectedPaymentMethodType: '', - }); ->>>>>>> be90481835 (integrate bank accounts logic) const [anchorPosition, setAnchorPosition] = useState({ anchorPositionHorizontal: 0, anchorPositionVertical: 0, anchorPositionTop: 0, anchorPositionRight: 0, }); -<<<<<<< HEAD const hasBankAccount = !isEmptyObject(bankAccountList); const shouldShowEmptyState = !hasBankAccount; // Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 || shouldUseNarrowLayout; const shouldShowMakeDefaultButton = !paymentMethod.isSelectedPaymentMethodDefault; const transferBankAccountID = policy?.invoice?.bankAccount?.transferBankAccountID; -======= - const hasBankAccount = !isEmptyObject(bankAccountList) || !isEmptyObject(fundList); - const hasWallet = !isEmptyObject(userWallet); - const hasAssignedCard = !isEmptyObject(cardList); - const shouldShowEmptyState = !hasBankAccount && !hasWallet && !hasAssignedCard; - // Determines whether or not the modal popup is mounted from the bottom of the screen instead of the side mount on Web or Desktop screens - const isPopoverBottomMount = anchorPosition.anchorPositionTop === 0 || shouldUseNarrowLayout; - const shouldShowMakeDefaultButton = - !paymentMethod.isSelectedPaymentMethodDefault && - !(paymentMethod.formattedSelectedPaymentMethod.type === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && paymentMethod.selectedPaymentMethod.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS); ->>>>>>> be90481835 (integrate bank accounts logic) /** * Set position of the payment menu @@ -170,11 +112,7 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) }; } setPaymentMethod({ -<<<<<<< HEAD isSelectedPaymentMethodDefault: transferBankAccountID === methodID, -======= - isSelectedPaymentMethodDefault: !!isDefault, ->>>>>>> be90481835 (integrate bank accounts logic) selectedPaymentMethod: account ?? {}, selectedPaymentMethodType: accountType, formattedSelectedPaymentMethod, @@ -211,7 +149,6 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethodType]); const makeDefaultPaymentMethod = useCallback(() => { -<<<<<<< HEAD // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList ?? {}, {}, styles); const previousPaymentMethod = paymentMethods.find((method) => !!method.isDefault); @@ -220,55 +157,12 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) PaymentMethods.setInvoicingTransferBankAccount(currentPaymentMethod?.methodID ?? -1, policyID, previousPaymentMethod?.methodID ?? -1); } }, [bankAccountList, styles, paymentMethod.selectedPaymentMethodType, paymentMethod.methodID, policyID]); -======= - const paymentCardList = fundList ?? {}; - // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors - const paymentMethods = PaymentUtils.formatPaymentMethods(bankAccountList ?? {}, paymentCardList, styles); - - const previousPaymentMethod = paymentMethods.find((method) => !!method.isDefault); - const currentPaymentMethod = paymentMethods.find((method) => method.methodID === paymentMethod.methodID); - if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT) { - PaymentMethods.makeDefaultPaymentMethod(paymentMethod.selectedPaymentMethod.bankAccountID ?? -1, 0, previousPaymentMethod, currentPaymentMethod); - } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - PaymentMethods.makeDefaultPaymentMethod(0, paymentMethod.selectedPaymentMethod.fundID ?? -1, previousPaymentMethod, currentPaymentMethod); - } - }, [ - paymentMethod.methodID, - paymentMethod.selectedPaymentMethod.bankAccountID, - paymentMethod.selectedPaymentMethod.fundID, - paymentMethod.selectedPaymentMethodType, - bankAccountList, - fundList, - styles, - ]); - - const resetSelectedPaymentMethodData = useCallback(() => { - // Reset to same values as in the constructor - setPaymentMethod({ - isSelectedPaymentMethodDefault: false, - selectedPaymentMethod: {}, - formattedSelectedPaymentMethod: { - title: '', - }, - methodID: '', - selectedPaymentMethodType: '', - }); - }, [setPaymentMethod]); ->>>>>>> be90481835 (integrate bank accounts logic) /** * Navigate to the appropriate payment type addition screen */ const addPaymentMethodTypePressed = (paymentType: string) => { hideAddPaymentMenu(); -<<<<<<< HEAD -======= - - if (paymentType === CONST.PAYMENT_METHODS.DEBIT_CARD) { - Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD); - return; - } ->>>>>>> be90481835 (integrate bank accounts logic) if (paymentType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT || paymentType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT) { BankAccounts.openPersonalBankAccountSetupView(); return; @@ -289,22 +183,14 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) shouldShowAddPaymentMethodButton={false} shouldShowEmptyListMessage={false} onPress={paymentMethodPressed} -<<<<<<< HEAD invoiceTransferBankAccountID={transferBankAccountID} activePaymentMethodID={transferBankAccountID} -======= - activePaymentMethodID={policy?.invoice?.bankAccount?.transferBankAccountID ?? ''} ->>>>>>> be90481835 (integrate bank accounts logic) actionPaymentMethodType={shouldShowDefaultDeleteMenu ? paymentMethod.selectedPaymentMethodType : ''} buttonRef={addPaymentMethodAnchorRef} shouldEnableScroll={false} style={[styles.mt5, hasBankAccount && [shouldUseNarrowLayout ? styles.mhn5 : styles.mhn8]]} listItemStyle={shouldUseNarrowLayout ? styles.ph5 : styles.ph8} /> -<<<<<<< HEAD -======= - ->>>>>>> be90481835 (integrate bank accounts logic) -<<<<<<< HEAD -======= - ->>>>>>> be90481835 (integrate bank accounts logic) Date: Thu, 3 Oct 2024 16:31:15 +0700 Subject: [PATCH 5/6] fix: update logic to preven castings --- src/components/Search/index.tsx | 5 +++++ src/components/SelectionListWithModal/index.tsx | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 8130f25bb4a5..fca81c6ef50b 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -335,6 +335,11 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr onTurnOnSelectionMode={(item) => item && toggleTransaction(item)} onCheckboxPress={toggleTransaction} onSelectAll={toggleAllTransactions} + isSelected={(item) => + status !== CONST.SEARCH.STATUS.EXPENSE.ALL + ? (item as ReportListItemType).transactions.some((transaction) => selectedTransactions[transaction.keyForList]?.isSelected) + : !!item.isSelected + } customListHeader={ !isLargeScreenWidth ? null : ( = BaseSelectionListProp turnOnSelectionModeOnLongPress?: boolean; onTurnOnSelectionMode?: (item: TItem | null) => void; shouldAutoTurnOff?: boolean; + isSelected?: (item: TItem) => boolean; }; function SelectionListWithModal( - {turnOnSelectionModeOnLongPress, onTurnOnSelectionMode, onLongPressRow, sections, shouldAutoTurnOff, ...rest}: SelectionListWithModalProps, + {turnOnSelectionModeOnLongPress, onTurnOnSelectionMode, onLongPressRow, sections, shouldAutoTurnOff, isSelected, ...rest}: SelectionListWithModalProps, ref: ForwardedRef, ) { const [isModalVisible, setIsModalVisible] = useState(false); @@ -28,12 +29,19 @@ function SelectionListWithModal( // See https://github.com/Expensify/App/issues/48675 for more details const {isSmallScreenWidth} = useResponsiveLayout(); const {selectionMode} = useMobileSelectionMode(shouldAutoTurnOff); + // 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); useEffect(() => { // We can access 0 index safely as we are not displaying multiple sections in table view - const selectedItems = sections[0].data.filter((item) => !!item.isSelected || (item as unknown as ReportListItemType)?.transactions?.some((transaction) => transaction.isSelected)); + const selectedItems = sections[0].data.filter((item) => { + if (isSelected) { + return isSelected(item); + } + return !!item.isSelected; + }); selectionRef.current = selectedItems.length; if (!isSmallScreenWidth) { @@ -50,7 +58,7 @@ function SelectionListWithModal( } else if (selectedItems.length === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) { turnOffMobileSelectionMode(); } - }, [sections, selectionMode, isSmallScreenWidth]); + }, [sections, selectionMode, isSmallScreenWidth, isSelected]); useEffect( () => () => { From 9e80633507da84c96c0dfd4c492f3a2c927fda66 Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 7 Oct 2024 11:17:05 +0700 Subject: [PATCH 6/6] fix: avoid typecastings --- src/components/Search/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index fca81c6ef50b..b31e70c0676f 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -336,8 +336,8 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr onCheckboxPress={toggleTransaction} onSelectAll={toggleAllTransactions} isSelected={(item) => - status !== CONST.SEARCH.STATUS.EXPENSE.ALL - ? (item as ReportListItemType).transactions.some((transaction) => selectedTransactions[transaction.keyForList]?.isSelected) + status !== CONST.SEARCH.STATUS.EXPENSE.ALL && SearchUtils.isReportListItemType(item) + ? item.transactions.some((transaction) => selectedTransactions[transaction.keyForList]?.isSelected) : !!item.isSelected } customListHeader={