-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Scan - No error pop-up and scan expense with corrupted PDF can be submitted via QAB #44778
Changes from 14 commits
0caf904
2a76f6d
4cbf8e2
a26c5b6
80fa7bb
65c1537
3f1fafa
2a7cbe9
458c75d
85e166e
e4eb9a2
29ad495
881dba7
33b3912
1a5c19b
f36966d
1a71c50
24b07d5
ace0862
105356b
8d553b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {useFocusEffect} from '@react-navigation/core'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {Str} from 'expensify-common'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import React, {useCallback, useMemo, useRef, useState} from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {ActivityIndicator, Alert, AppState, InteractionManager, View} from 'react-native'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {Gesture, GestureDetector} from 'react-native-gesture-handler'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -16,6 +17,7 @@ import Button from '@components/Button'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import Icon from '@components/Icon'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import * as Expensicons from '@components/Icon/Expensicons'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import ImageSVG from '@components/ImageSVG'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import PDFThumbnail from '@components/PDFThumbnail'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import Text from '@components/Text'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -65,6 +67,8 @@ function IOURequestStepScan({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [cameraPermissionStatus, setCameraPermissionStatus] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [didCapturePhoto, setDidCapturePhoto] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [pdfFile, setPdfFile] = useState<null | FileObject>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, transaction); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const transactionTaxCode = (transaction?.taxCode ? transaction?.taxCode : defaultTaxCode) ?? ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const transactionTaxAmount = transaction?.taxAmount ?? 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -379,11 +383,16 @@ function IOURequestStepScan({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Sets the Receipt objects and navigates the user to the next page | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const setReceiptAndNavigate = (file: FileObject) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const setReceiptAndNavigate = (file: FileObject, isPdfValidated?: boolean) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!validateReceipt(file)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (Str.isPDF(file.name ?? '') && !isPdfValidated) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setPdfFile(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Store the receipt on the transaction object in Onyx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// On Android devices, fetching blob for a file with name containing spaces fails to retrieve the type of file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// So, let us also save the file type in receipt for later use during blob fetch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -454,6 +463,30 @@ function IOURequestStepScan({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
shouldShowWrapper={!!backTo} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
testID={IOURequestStepScan.displayName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<View style={{height: 0, overflow: 'hidden', width: 0, opacity: 0}}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{pdfFile && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<View style={{height: 1, width: 1}}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<PDFThumbnail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
previewSourceURL={pdfFile?.uri ?? ''} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don’t need to disable eslint for that case because an empty previewSourceURL is invalid. Instead, I suggest checking the condition before rendering ST like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated. we don't need to check for |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onLoadSuccess={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setPdfFile(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (pdfFile) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setReceiptAndNavigate(pdfFile, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onPassword={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setPdfFile(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.protectedPDFNotSupported')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At MoneyRequestConfirmationListFooter.tsx we show error by confirmation modal App/src/components/MoneyRequestConfirmationListFooter.tsx Lines 657 to 665 in c5eafd3
So we have to update There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @suneox, for native we use alert to show all file related issues. App/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx Lines 165 to 186 in 29e8df5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, just make it consistent with other places on IOURequestStepScan/native There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @suneox, you mean to make it consistent with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we can using Alert for native in this case |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onLoadError={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
setPdfFile(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have to update |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</View> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</View> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{cameraPermissionStatus !== RESULTS.GRANTED && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<View style={[styles.cameraView, styles.permissionView, styles.userSelectNone]}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<ImageSVG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import {Str} from 'expensify-common'; | ||
import React, {useCallback, useContext, useEffect, useMemo, useReducer, useRef, useState} from 'react'; | ||
import {ActivityIndicator, PanResponder, PixelRatio, View} from 'react-native'; | ||
import {withOnyx} from 'react-native-onyx'; | ||
|
@@ -14,6 +15,7 @@ import CopyTextToClipboard from '@components/CopyTextToClipboard'; | |
import {DragAndDropContext} from '@components/DragAndDrop/Provider'; | ||
import Icon from '@components/Icon'; | ||
import * as Expensicons from '@components/Icon/Expensicons'; | ||
import PDFThumbnail from '@components/PDFThumbnail'; | ||
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; | ||
import Text from '@components/Text'; | ||
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; | ||
|
@@ -25,7 +27,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; | |
import * as Browser from '@libs/Browser'; | ||
import * as FileUtils from '@libs/fileDownload/FileUtils'; | ||
import getCurrentPosition from '@libs/getCurrentPosition'; | ||
import isPdfFilePasswordProtected from '@libs/isPdfFilePasswordProtected'; | ||
import Log from '@libs/Log'; | ||
import Navigation from '@libs/Navigation/Navigation'; | ||
import * as OptionsListUtils from '@libs/OptionsListUtils'; | ||
|
@@ -63,7 +64,7 @@ function IOURequestStepScan({ | |
const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); | ||
const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths>(); | ||
const [attachmentInvalidReason, setAttachmentValidReason] = useState<TranslationPaths>(); | ||
|
||
const [pdfFile, setPdfFile] = useState<null | FileObject>(null); | ||
const [receiptImageTopPosition, setReceiptImageTopPosition] = useState(0); | ||
const {isSmallScreenWidth} = useWindowDimensions(); | ||
const {translate} = useLocalize(); | ||
|
@@ -188,6 +189,7 @@ function IOURequestStepScan({ | |
setIsAttachmentInvalid(isInvalid); | ||
setAttachmentInvalidReasonTitle(title); | ||
setAttachmentValidReason(reason); | ||
setPdfFile(null); | ||
}; | ||
|
||
function validateReceipt(file: FileObject) { | ||
|
@@ -212,16 +214,6 @@ function IOURequestStepScan({ | |
setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); | ||
return false; | ||
} | ||
|
||
if (fileExtension === 'pdf') { | ||
return isPdfFilePasswordProtected(file).then((isProtected: boolean) => { | ||
if (isProtected) { | ||
setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.protectedPDFNotSupported'); | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
return true; | ||
}) | ||
.catch(() => { | ||
|
@@ -425,11 +417,15 @@ function IOURequestStepScan({ | |
/** | ||
* Sets the Receipt objects and navigates the user to the next page | ||
*/ | ||
const setReceiptAndNavigate = (file: FileObject) => { | ||
const setReceiptAndNavigate = (file: FileObject, isPdfValidated?: boolean) => { | ||
validateReceipt(file).then((isFileValid) => { | ||
if (!isFileValid) { | ||
return; | ||
} | ||
if (Str.isPDF(file.name ?? '') && !isPdfValidated) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you explain the check for a not validated pdf here? |
||
setPdfFile(file); | ||
return; | ||
} | ||
// Store the receipt on the transaction object in Onyx | ||
const source = URL.createObjectURL(file as Blob); | ||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | ||
|
@@ -519,9 +515,29 @@ function IOURequestStepScan({ | |
[], | ||
); | ||
|
||
const PDFThumbnailView = pdfFile ? ( | ||
<View style={{position: 'absolute', opacity: 0, width: 1, height: 1}}> | ||
<PDFThumbnail | ||
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style | ||
previewSourceURL={pdfFile.uri ?? ''} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated. |
||
onLoadSuccess={() => { | ||
setPdfFile(null); | ||
setReceiptAndNavigate(pdfFile, true); | ||
}} | ||
onPassword={() => { | ||
setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.protectedPDFNotSupported'); | ||
}} | ||
onLoadError={() => { | ||
setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment'); | ||
}} | ||
/> | ||
</View> | ||
) : null; | ||
|
||
const mobileCameraView = () => ( | ||
<> | ||
<View style={[styles.cameraView]}> | ||
{PDFThumbnailView} | ||
{((cameraPermissionState === 'prompt' && !isQueriedPermissionState) || (cameraPermissionState === 'granted' && isEmptyObject(videoConstraints))) && ( | ||
<ActivityIndicator | ||
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE} | ||
|
@@ -620,6 +636,7 @@ function IOURequestStepScan({ | |
|
||
const desktopUploadView = () => ( | ||
<> | ||
{PDFThumbnailView} | ||
<View onLayout={({nativeEvent}) => setReceiptImageTopPosition(PixelRatio.roundToNearestPixel((nativeEvent.layout as DOMRect).top))}> | ||
<ReceiptUpload | ||
width={CONST.RECEIPT.ICON_SIZE} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we create a new prop, such as invisible, and handle the style inside PDFThumbnail instead of handling it at the screen level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.