From 55c8c7b9d1e1187d4470ec7278124513267e31f7 Mon Sep 17 00:00:00 2001 From: Isha Kakkad Date: Sat, 10 May 2025 14:56:23 +0530 Subject: [PATCH 1/8] 61084: fix Scan tooltip is pointing to the edge of Scan button --- src/components/TabSelector/TabSelector.tsx | 19 ++- .../TabSelector/TabSelectorItem.tsx | 84 ++++++---- src/libs/Navigation/OnyxTabNavigator.tsx | 12 +- src/pages/iou/request/IOURequestStartPage.tsx | 29 +++- .../step/IOURequestStepScan/index.native.tsx | 144 +++++++----------- .../request/step/IOURequestStepScan/index.tsx | 67 ++------ .../request/step/IOURequestStepScan/types.ts | 7 +- 7 files changed, 188 insertions(+), 174 deletions(-) diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index f0867bd82beb..55309c797eac 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -22,6 +22,12 @@ type TabSelectorProps = MaterialTopTabBarProps & { /** Whether to show the label when the tab is inactive */ shouldShowLabelWhenInactive?: boolean; + + /** Determines whether the product training tooltip should be displayed to the user. */ + shouldShowProductTrainingTooltip?: boolean; + + /** Function to render the content of the product training tooltip. */ + renderProductTrainingTooltip?: () => React.JSX.Element; }; type IconTitleAndTestID = { @@ -53,7 +59,16 @@ function getIconTitleAndTestID(route: string, translate: LocaleContextProps['tra } } -function TabSelector({state, navigation, onTabPress = () => {}, position, onFocusTrapContainerElementChanged, shouldShowLabelWhenInactive = true}: TabSelectorProps) { +function TabSelector({ + state, + navigation, + onTabPress = () => {}, + position, + onFocusTrapContainerElementChanged, + shouldShowLabelWhenInactive = true, + shouldShowProductTrainingTooltip = false, + renderProductTrainingTooltip, +}: TabSelectorProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -109,6 +124,8 @@ function TabSelector({state, navigation, onTabPress = () => {}, position, onFocu isActive={isActive} testID={testID} shouldShowLabelWhenInactive={shouldShowLabelWhenInactive} + shouldShowProductTrainingTooltip={shouldShowProductTrainingTooltip} + renderProductTrainingTooltip={renderProductTrainingTooltip} /> ); })} diff --git a/src/components/TabSelector/TabSelectorItem.tsx b/src/components/TabSelector/TabSelectorItem.tsx index 61e1f3d3e748..b376a8498a33 100644 --- a/src/components/TabSelector/TabSelectorItem.tsx +++ b/src/components/TabSelector/TabSelectorItem.tsx @@ -3,6 +3,7 @@ import React, {useState} from 'react'; import {Animated} from 'react-native'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Tooltip from '@components/Tooltip'; +import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -38,6 +39,12 @@ type TabSelectorItemProps = { /** Test identifier used to find elements in unit and e2e tests */ testID?: string; + + /** Determines whether the product training tooltip should be displayed to the user. */ + shouldShowProductTrainingTooltip?: boolean; + + /** Function to render the content of the product training tooltip. */ + renderProductTrainingTooltip?: () => React.JSX.Element; }; function TabSelectorItem({ @@ -50,40 +57,65 @@ function TabSelectorItem({ isActive = false, shouldShowLabelWhenInactive = true, testID, + shouldShowProductTrainingTooltip = false, + renderProductTrainingTooltip, }: TabSelectorItemProps) { const styles = useThemeStyles(); const [isHovered, setIsHovered] = useState(false); - return ( - setIsHovered(true)} + onHoverOut={() => setIsHovered(false)} + role={CONST.ROLE.BUTTON} + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} + testID={testID} > - setIsHovered(true)} - onHoverOut={() => setIsHovered(false)} - role={CONST.ROLE.BUTTON} - dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - testID={testID} - > - + {(shouldShowLabelWhenInactive || isActive) && ( + - {(shouldShowLabelWhenInactive || isActive) && ( - - )} - - + )} + + ); + + return ( + <> + {shouldShowEducationTooltip ? ( + + {children} + + ) : ( + + {children} + + )} + ); } diff --git a/src/libs/Navigation/OnyxTabNavigator.tsx b/src/libs/Navigation/OnyxTabNavigator.tsx index 683ed1ae2043..a5f1c411e3a1 100644 --- a/src/libs/Navigation/OnyxTabNavigator.tsx +++ b/src/libs/Navigation/OnyxTabNavigator.tsx @@ -47,6 +47,12 @@ type OnyxTabNavigatorProps = ChildrenProps & { /** Disable swipe between tabs */ disableSwipe?: boolean; + + /** Determines whether the product training tooltip should be displayed to the user. */ + shouldShowProductTrainingTooltip?: boolean; + + /** Function to render the content of the product training tooltip. */ + renderProductTrainingTooltip?: () => React.JSX.Element; }; // eslint-disable-next-line rulesdir/no-inline-named-export @@ -70,6 +76,8 @@ function OnyxTabNavigator({ screenListeners, shouldShowLabelWhenInactive = true, disableSwipe = false, + shouldShowProductTrainingTooltip, + renderProductTrainingTooltip, ...rest }: OnyxTabNavigatorProps) { // Mapping of tab name to focus trap container element @@ -99,12 +107,14 @@ function OnyxTabNavigator({ ); }, - [TabBar, onTabBarFocusTrapContainerElementChanged, shouldShowLabelWhenInactive], + [TabBar, onTabBarFocusTrapContainerElementChanged, shouldShowLabelWhenInactive, shouldShowProductTrainingTooltip, renderProductTrainingTooltip], ); // If the selected tab changes, we need to update the focus trap container element of the active tab diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index a1c94aac3007..d90413c9e743 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -1,21 +1,25 @@ import {useFocusEffect} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import FocusTrapContainerElement from '@components/FocusTrap/FocusTrapContainerElement'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; +import {dismissProductTraining} from '@libs/actions/Welcome'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator'; +import {getIsUserSubmittedExpenseOrScannedReceipt} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; -import {getPerDiemCustomUnit, getPerDiemCustomUnits} from '@libs/PolicyUtils'; +import Permissions from '@libs/Permissions'; +import {getPerDiemCustomUnit, getPerDiemCustomUnits, isUserInvitedToWorkspace} from '@libs/PolicyUtils'; import {getPayeeName} from '@libs/ReportUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {IOURequestType} from '@userActions/IOU'; @@ -119,6 +123,21 @@ function IOURequestStartPage({ const shouldShowPerDiemOption = iouType !== CONST.IOU.TYPE.SPLIT && iouType !== CONST.IOU.TYPE.TRACK && ((!isFromGlobalCreate && doesCurrentPolicyPerDiemExist) || (isFromGlobalCreate && doesPerDiemPolicyExist)); + const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: false}); + const setTestReceiptAndNavigateRef = useRef<() => void>(); + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP, + !getIsUserSubmittedExpenseOrScannedReceipt() && Permissions.canUseManagerMcTest(betas) && selectedTab === CONST.TAB_REQUEST.SCAN && !isUserInvitedToWorkspace(), + { + onConfirm: () => { + setTestReceiptAndNavigateRef?.current?.(); + }, + onDismiss: () => { + dismissProductTraining(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP, true); + }, + }, + ); + return ( {() => ( @@ -176,7 +197,9 @@ function IOURequestStartPage({ { + setTestReceiptAndNavigateRef.current = setTestReceiptAndNavigate; + }} /> )} diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 0afa82d90787..dacde4d52b75 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -1,4 +1,4 @@ -import {useFocusEffect, useIsFocused} from '@react-navigation/core'; +import {useFocusEffect} from '@react-navigation/core'; import {Str} from 'expensify-common'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import {ActivityIndicator, Alert, AppState, Image, InteractionManager, View} from 'react-native'; @@ -23,15 +23,12 @@ import ImageSVG from '@components/ImageSVG'; import LocationPermissionModal from '@components/LocationPermissionModal'; import PDFThumbnail from '@components/PDFThumbnail'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import {useProductTrainingContext} from '@components/ProductTrainingContext'; import Text from '@components/Text'; -import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {dismissProductTraining} from '@libs/actions/Welcome'; import {readFileAsync, resizeImageIfNeeded, showCameraPermissionsAlert, splitExtensionFromFileName} from '@libs/fileDownload/FileUtils'; import getPhotoSource from '@libs/fileDownload/getPhotoSource'; import getCurrentPosition from '@libs/getCurrentPosition'; @@ -40,9 +37,8 @@ import getReceiptsUploadFolderPath from '@libs/getReceiptsUploadFolderPath'; import {shouldStartLocationPermissionFlow} from '@libs/IOUUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; -import {getIsUserSubmittedExpenseOrScannedReceipt, getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; -import Permissions from '@libs/Permissions'; -import {isPaidGroupPolicy, isUserInvitedToWorkspace} from '@libs/PolicyUtils'; +import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; +import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {generateReportID, getPolicyExpenseChat, isArchivedReport, isPolicyExpenseChat} from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; @@ -50,7 +46,6 @@ import {getDefaultTaxCode} from '@libs/TransactionUtils'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; -import variables from '@styles/variables'; import { getMoneyRequestParticipantsFromReport, replaceReceipt, @@ -78,7 +73,7 @@ function IOURequestStepScan({ }, transaction, currentUserPersonalDetails, - isTooltipAllowed = false, + onLayout, }: IOURequestStepScanProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -87,7 +82,6 @@ function IOURequestStepScan({ physicalDevices: ['wide-angle-camera', 'ultra-wide-angle-camera'], }); - const [elementTop, setElementTop] = useState(0); const isEditing = action === CONST.IOU.ACTION.EDIT; const hasFlash = !!device?.hasFlash; const camera = useRef(null); @@ -101,13 +95,11 @@ function IOURequestStepScan({ const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); - const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: false}); const platform = getPlatform(true); const [mutedPlatforms = {}] = useOnyx(ONYXKEYS.NVP_MUTED_PLATFORMS, {canBeMissing: true}); const isPlatformMuted = mutedPlatforms[platform]; const [cameraPermissionStatus, setCameraPermissionStatus] = useState(null); const [didCapturePhoto, setDidCapturePhoto] = useState(false); - const isTabActive = useIsFocused(); const [pdfFile, setPdfFile] = useState(null); @@ -543,17 +535,6 @@ function IOURequestStepScan({ } }, [transactionID, isEditing, navigateToConfirmationStep]); - const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip} = useProductTrainingContext( - CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP, - isTooltipAllowed && !getIsUserSubmittedExpenseOrScannedReceipt() && Permissions.canUseManagerMcTest(betas) && isTabActive && !isUserInvitedToWorkspace(), - { - onConfirm: setTestReceiptAndNavigate, - onDismiss: () => { - dismissProductTraining(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP, true); - }, - }, - ); - /** * Sets the Receipt objects and navigates the user to the next page */ @@ -709,8 +690,11 @@ function IOURequestStepScan({ > { - setElementTop(e.nativeEvent.layout.height - (variables.tabSelectorButtonHeight + variables.tabSelectorButtonPadding) * 2); + onLayout={() => { + if (!onLayout) { + return; + } + onLayout(setTestReceiptAndNavigate); }} > {!!pdfFile && ( @@ -733,67 +717,55 @@ function IOURequestStepScan({ }} /> )} - - - {cameraPermissionStatus !== RESULTS.GRANTED && ( - - - - {translate('receipt.takePhoto')} - {translate('receipt.cameraAccess')} -