From 63f573d70473e32fab548a2091a1bbd50c5b36c3 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Wed, 25 Feb 2026 01:44:47 +0500 Subject: [PATCH 01/22] Add useDragAndDropSupport hook for native and web environment --- .../useDragAndDropSupport/index.native.ts | 9 +++++++++ .../IOURequestStepScan/useDragAndDropSupport/index.ts | 11 +++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts create mode 100644 src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts diff --git a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts new file mode 100644 index 0000000000000..d8eb24d1063ae --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts @@ -0,0 +1,9 @@ +/** + * Drag-and-drop for file upload is not supported in native apps. + */ + +function useDragAndDropSupport(): boolean { + return false; +} + +export default useDragAndDropSupport; diff --git a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts new file mode 100644 index 0000000000000..6f24800f51403 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts @@ -0,0 +1,11 @@ +/** + * Drag-and-drop is supported on desktop web only. Returns false on mobile web. + */ + +import {isMobile} from '@libs/Browser'; + +function useDragAndDropSupport(): boolean { + return !isMobile(); +} + +export default useDragAndDropSupport; From 6b6563c67498f7bbbedd45771c28e5397a1dd539 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Wed, 25 Feb 2026 01:46:46 +0500 Subject: [PATCH 02/22] Refactor IOURequestStepScan to use isMobileWeb and canUseDragAndDrop --- .../request/step/IOURequestStepScan/index.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 1b93f9a451f15..4ebb1f7e6ef03 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -23,7 +23,6 @@ import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hook import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {clearUserLocation, setUserLocation} from '@libs/actions/UserLocation'; @@ -46,6 +45,7 @@ import {getLocationPermission} from './LocationPermission'; import NavigationAwareCamera from './NavigationAwareCamera/WebCamera'; import ReceiptPreviews from './ReceiptPreviews'; import type IOURequestStepScanProps from './types'; +import useDragAndDropSupport from './useDragAndDropSupport'; import useReceiptScan from './useReceiptScan'; function IOURequestStepScan({ @@ -62,9 +62,8 @@ function IOURequestStepScan({ }: Omit) { const theme = useTheme(); const styles = useThemeStyles(); - // we need to use isSmallScreenWidth instead of shouldUseNarrowLayout because drag and drop is not supported on mobile - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); + const isMobileWeb = isMobile(); + const canUseDragAndDrop = useDragAndDropSupport(); const {translate} = useLocalize(); const {isDraggingOver} = useDragAndDropState(); const [cameraPermissionState, setCameraPermissionState] = useState('prompt'); @@ -140,7 +139,7 @@ function IOURequestStepScan({ * The last deviceId is of regular len camera. */ const requestCameraPermission = useCallback(() => { - if (!isMobile()) { + if (!isMobileWeb) { return; } @@ -227,7 +226,7 @@ function IOURequestStepScan({ }, []); useEffect(() => { - if (!isMobile() || !isTabActive) { + if (!isMobileWeb || !isTabActive) { setVideoConstraints(undefined); return; } @@ -468,7 +467,7 @@ function IOURequestStepScan({ mirrored={false} screenshotQuality={0} /> - {canUseMultiScan && isMobile() ? ( + {canUseMultiScan && isMobileWeb ? ( - {canUseMultiScan && isMobile() ? ( + {canUseMultiScan && isMobileWeb ? ( )} - {canUseMultiScan && isMobile() && shouldShowMultiScanEducationalPopup && ( + {canUseMultiScan && isMobileWeb && shouldShowMultiScanEducationalPopup && ( containerHeight; + // We use isMobileWeb here to explicitly hide the alternative methods component on both mobile web and native apps + const chooseFilesPaddingVertical = Number(styles.chooseFilesView(!canUseDragAndDrop).paddingVertical); + const shouldHideAlternativeMethods = isMobileWeb || alternativeMethodsHeight + desktopUploadViewHeight + chooseFilesPaddingVertical * 2 > containerHeight; const desktopUploadView = () => ( - - {!(isDraggingOver ?? isDraggingOverWrapper) && (isMobile() ? mobileCameraView() : desktopUploadView())} + + {!(isDraggingOver ?? isDraggingOverWrapper) && (isMobileWeb ? mobileCameraView() : desktopUploadView())} Date: Wed, 25 Feb 2026 02:00:29 +0500 Subject: [PATCH 03/22] fix prettier --- .../IOURequestStepScan/useDragAndDropSupport/index.native.ts | 1 - .../step/IOURequestStepScan/useDragAndDropSupport/index.ts | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts index d8eb24d1063ae..8c97b78dd14c2 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts +++ b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts @@ -1,7 +1,6 @@ /** * Drag-and-drop for file upload is not supported in native apps. */ - function useDragAndDropSupport(): boolean { return false; } diff --git a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts index 6f24800f51403..1f0399159f4ad 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts +++ b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts @@ -1,9 +1,8 @@ +import {isMobile} from '@libs/Browser'; + /** * Drag-and-drop is supported on desktop web only. Returns false on mobile web. */ - -import {isMobile} from '@libs/Browser'; - function useDragAndDropSupport(): boolean { return !isMobile(); } From ffcdcf573a7ed509d3062c9ddc47fee2ea01bc0b Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 26 Feb 2026 21:54:28 +0500 Subject: [PATCH 04/22] remove useDragAndDropSupport hook --- .../iou/request/step/IOURequestStepScan/index.tsx | 3 +-- .../useDragAndDropSupport/index.native.ts | 8 -------- .../IOURequestStepScan/useDragAndDropSupport/index.ts | 10 ---------- 3 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts delete mode 100644 src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 4ebb1f7e6ef03..edbc105404bf2 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -45,7 +45,6 @@ import {getLocationPermission} from './LocationPermission'; import NavigationAwareCamera from './NavigationAwareCamera/WebCamera'; import ReceiptPreviews from './ReceiptPreviews'; import type IOURequestStepScanProps from './types'; -import useDragAndDropSupport from './useDragAndDropSupport'; import useReceiptScan from './useReceiptScan'; function IOURequestStepScan({ @@ -63,7 +62,7 @@ function IOURequestStepScan({ const theme = useTheme(); const styles = useThemeStyles(); const isMobileWeb = isMobile(); - const canUseDragAndDrop = useDragAndDropSupport(); + const canUseDragAndDrop = !isMobileWeb; const {translate} = useLocalize(); const {isDraggingOver} = useDragAndDropState(); const [cameraPermissionState, setCameraPermissionState] = useState('prompt'); diff --git a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts deleted file mode 100644 index 8c97b78dd14c2..0000000000000 --- a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.native.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Drag-and-drop for file upload is not supported in native apps. - */ -function useDragAndDropSupport(): boolean { - return false; -} - -export default useDragAndDropSupport; diff --git a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts b/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts deleted file mode 100644 index 1f0399159f4ad..0000000000000 --- a/src/pages/iou/request/step/IOURequestStepScan/useDragAndDropSupport/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {isMobile} from '@libs/Browser'; - -/** - * Drag-and-drop is supported on desktop web only. Returns false on mobile web. - */ -function useDragAndDropSupport(): boolean { - return !isMobile(); -} - -export default useDragAndDropSupport; From 711c503a4f1941d78ecd3356a1e62524a4259257 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 26 Feb 2026 21:55:15 +0500 Subject: [PATCH 05/22] remove isMobileWeb from mobileCameraView since we only show this if isMobileWeb true --- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index edbc105404bf2..eee50a93dff8f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -466,7 +466,7 @@ function IOURequestStepScan({ mirrored={false} screenshotQuality={0} /> - {canUseMultiScan && isMobileWeb ? ( + {canUseMultiScan ? ( - {canUseMultiScan && isMobileWeb ? ( + {canUseMultiScan ? ( )} - {canUseMultiScan && isMobileWeb && shouldShowMultiScanEducationalPopup && ( + {canUseMultiScan && shouldShowMultiScanEducationalPopup && ( Date: Mon, 2 Mar 2026 04:59:17 +0500 Subject: [PATCH 06/22] Add DesktopWebUploadView and MobileWebCameraView components --- .../DesktopWebUploadView.tsx | 75 ++++ .../MobileWebCameraView.tsx | 419 ++++++++++++++++++ 2 files changed, 494 insertions(+) create mode 100644 src/pages/iou/request/step/IOURequestStepScan/DesktopWebUploadView.tsx create mode 100644 src/pages/iou/request/step/IOURequestStepScan/MobileWebCameraView.tsx diff --git a/src/pages/iou/request/step/IOURequestStepScan/DesktopWebUploadView.tsx b/src/pages/iou/request/step/IOURequestStepScan/DesktopWebUploadView.tsx new file mode 100644 index 0000000000000..50413f7685df9 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepScan/DesktopWebUploadView.tsx @@ -0,0 +1,75 @@ +import React, {useRef} from 'react'; +import {PanResponder, View} from 'react-native'; +import AttachmentPicker from '@components/AttachmentPicker'; +import Button from '@components/Button'; +import Icon from '@components/Icon'; +import Text from '@components/Text'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import type {FileObject} from '@src/types/utils/Attachment'; + +type DesktopWebUploadViewProps = { + PDFValidationComponent: React.ReactNode; + shouldAcceptMultipleFiles: boolean; + validateFiles: (files: FileObject[], items?: DataTransferItem[]) => void; + setDesktopUploadViewHeight: (height: number) => void; +}; + +function DesktopWebUploadView({PDFValidationComponent, shouldAcceptMultipleFiles, validateFiles, setDesktopUploadViewHeight}: DesktopWebUploadViewProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptStack']); + const panResponder = useRef( + PanResponder.create({ + onPanResponderTerminationRequest: () => false, + }), + ).current; + + return ( + { + setDesktopUploadViewHeight(e.nativeEvent.layout.height); + }} + > + {PDFValidationComponent} + + + {translate(shouldAcceptMultipleFiles ? 'receipt.uploadMultiple' : 'receipt.upload')} + + {translate(shouldAcceptMultipleFiles ? 'receipt.desktopSubtitleMultiple' : 'receipt.desktopSubtitleSingle')} + + + + + {({openPicker}) => ( +