Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,6 @@ const CONST = {

// Allowed extensions for receipts
ALLOWED_RECEIPT_EXTENSIONS: ['heif', 'heic', 'jpg', 'jpeg', 'gif', 'png', 'pdf', 'htm', 'html', 'text', 'rtf', 'doc', 'tif', 'tiff', 'msword', 'zip', 'xml', 'message'],

MAX_FILE_LIMIT: 30,
},

// Allowed extensions for spreadsheets import
Expand Down Expand Up @@ -1881,19 +1879,6 @@ const CONST = {
// Video MimeTypes allowed by iOS photos app.
VIDEO: /\.(mov|mp4)$/,
},

FILE_VALIDATION_ERRORS: {
WRONG_FILE_TYPE: 'wrongFileType',
WRONG_FILE_TYPE_MULTIPLE: 'wrongFileTypeMultiple',
FILE_TOO_LARGE: 'fileTooLarge',
FILE_TOO_LARGE_MULTIPLE: 'fileTooLargeMultiple',
FILE_TOO_SMALL: 'fileTooSmall',
FILE_CORRUPTED: 'fileCorrupted',
FOLDER_NOT_ALLOWED: 'folderNotAllowed',
MAX_FILE_LIMIT_EXCEEDED: 'fileLimitExceeded',
PROTECTED_FILE: 'protectedFile',
},

IOS_CAMERA_ROLL_ACCESS_ERROR: 'Access to photo library was denied',
ADD_PAYMENT_MENU_POSITION_Y: 226,
ADD_PAYMENT_MENU_POSITION_X: 356,
Expand Down
162 changes: 49 additions & 113 deletions src/components/AttachmentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import attachmentModalHandler from '@libs/AttachmentModalHandler';
import fileDownload from '@libs/fileDownload';
import {cleanFileName, getFileName, getFileValidationErrorText, validateAttachment, validateImageForCorruption} from '@libs/fileDownload/FileUtils';
import {cleanFileName, getFileName, validateImageForCorruption} from '@libs/fileDownload/FileUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {hasEReceipt, hasMissingSmartscanFields, hasReceipt, hasReceiptSource, isReceiptBeingScanned} from '@libs/TransactionUtils';
Expand All @@ -23,6 +23,7 @@ import variables from '@styles/variables';
import {detachReceipt} from '@userActions/IOU';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
Expand Down Expand Up @@ -63,7 +64,6 @@ type FileObject = Partial<File | ImagePickerResponse>;

type ChildrenProps = {
displayFileInModal: (data: FileObject) => void;
displayMultipleFilesInModal: (data: FileObject[]) => void;
show: () => void;
};

Expand All @@ -75,7 +75,7 @@ type AttachmentModalProps = {
attachmentID?: string;

/** Optional callback to fire when we want to preview an image and approve it for use. */
onConfirm?: ((file: FileObject | FileObject[]) => void) | null;
onConfirm?: ((file: FileObject) => void) | null;

/** Whether the modal should be open by default */
defaultOpen?: boolean;
Expand Down Expand Up @@ -196,10 +196,11 @@ function AttachmentModal({
const styles = useThemeStyles();
const [isModalOpen, setIsModalOpen] = useState(defaultOpen);
const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false);
const [fileError, setFileError] = useState<ValueOf<typeof CONST.FILE_VALIDATION_ERRORS> | null>(null);
const [isFileErrorModalVisible, setIsFileErrorModalVisible] = useState(false);
const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false);
const [isAuthTokenRequiredState, setIsAuthTokenRequiredState] = useState(isAuthTokenRequired);
const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths | null>(null);
const [attachmentInvalidReason, setAttachmentInvalidReason] = useState<TranslationPaths | null>(null);
const [sourceState, setSourceState] = useState<AvatarSource>(() => source);
const [modalType, setModalType] = useState<ModalType>(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE);
const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false);
Expand All @@ -209,14 +210,13 @@ function AttachmentModal({
const {windowWidth} = useWindowDimensions();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const nope = useSharedValue(false);
const isOverlayModalVisible = (isReceiptAttachment && isDeleteReceiptConfirmModalVisible) || (!isReceiptAttachment && fileError);
const isOverlayModalVisible = (isReceiptAttachment && isDeleteReceiptConfirmModalVisible) || (!isReceiptAttachment && isAttachmentInvalid);
const iouType = useMemo(() => iouTypeProp ?? (isTrackExpenseAction ? CONST.IOU.TYPE.TRACK : CONST.IOU.TYPE.SUBMIT), [isTrackExpenseAction, iouTypeProp]);
const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const transactionID = (isMoneyRequestAction(parentReportAction) && getOriginalMessage(parentReportAction)?.IOUTransactionID) || CONST.DEFAULT_NUMBER_ID;
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true});
const [currentAttachmentLink, setCurrentAttachmentLink] = useState(attachmentLink);
const [validFilesToUpload, setValidFilesToUpload] = useState<FileObject[]>([]);
const {setAttachmentError, isErrorInAttachment, clearAttachmentErrors} = useAttachmentErrors();

const [file, setFile] = useState<FileObject | undefined>(
Expand Down Expand Up @@ -300,11 +300,7 @@ function AttachmentModal({
}

if (onConfirm) {
if (validFilesToUpload.length) {
onConfirm(validFilesToUpload);
} else {
onConfirm(Object.assign(file ?? {}, {source: sourceState} as FileObject));
}
onConfirm(Object.assign(file ?? {}, {source: sourceState} as FileObject));
}

setIsModalOpen(false);
Expand All @@ -315,7 +311,7 @@ function AttachmentModal({
* Close the confirm modals.
*/
const closeConfirmModal = useCallback(() => {
setIsFileErrorModalVisible(false);
setIsAttachmentInvalid(false);
setIsDeleteReceiptConfirmModalVisible(false);
}, []);

Expand All @@ -329,101 +325,44 @@ function AttachmentModal({
}, [transaction]);

const isValidFile = useCallback(
(fileObject: FileObject, isCheckingMultipleFiles?: boolean) =>
(fileObject: FileObject) =>
validateImageForCorruption(fileObject)
.then(() => {
const error = validateAttachment(fileObject, isCheckingMultipleFiles);
if (error) {
setFileError(error);
setIsFileErrorModalVisible(true);
if (fileObject.size && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooLarge');
setAttachmentInvalidReason('attachmentPicker.sizeExceeded');
return false;
}

if (fileObject.size && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall');
setAttachmentInvalidReason('attachmentPicker.sizeNotMet');
return false;
}

return true;
})
.catch(() => {
setFileError(CONST.FILE_VALIDATION_ERRORS.FILE_CORRUPTED);
setIsFileErrorModalVisible(true);
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentError');
setAttachmentInvalidReason('attachmentPicker.errorWhileSelectingCorruptedAttachment');
return false;
}),
[],
);

const isDirectoryCheck = useCallback((data: FileObject) => {
if ('webkitGetAsEntry' in data && (data as DataTransferItem).webkitGetAsEntry()?.isDirectory) {
setFileError(CONST.FILE_VALIDATION_ERRORS.FOLDER_NOT_ALLOWED);
setIsFileErrorModalVisible(true);
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentError');
setAttachmentInvalidReason('attachmentPicker.folderNotAllowedMessage');
return false;
}
return true;
}, []);

const handleOpenModal = useCallback(
(inputSource: string, fileObject: FileObject) => {
const inputModalType = getModalType(inputSource, fileObject);
setIsModalOpen(true);
setSourceState(inputSource);
setFile(fileObject);
setModalType(inputModalType);
},
[getModalType, setSourceState, setFile, setModalType],
);

useEffect(() => {
if (!validFilesToUpload.length) {
return;
}

if (validFilesToUpload.length > 0) {
if (fileError) {
return;
}
const fileToDisplay = validFilesToUpload.at(0);
if (fileToDisplay) {
const inputSource = fileToDisplay.uri ?? '';
handleOpenModal(inputSource, fileToDisplay);
}
}
}, [fileError, handleOpenModal, validFilesToUpload]);

const validateFiles = useCallback(
(data: FileObject[]) => {
let validFiles: FileObject[] = [];

Promise.all(data.map((fileToUpload) => isValidFile(fileToUpload, true).then((isValid) => (isValid ? fileToUpload : null)))).then((results) => {
validFiles = results.filter((validFile): validFile is FileObject => validFile !== null);
setValidFilesToUpload(validFiles);
});
},
[isValidFile],
);

const confirmAndContinue = () => {
if (fileError === CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED) {
validateFiles(validFilesToUpload);
}
setIsFileErrorModalVisible(false);
InteractionManager.runAfterInteractions(() => {
setFileError(null);
});
};

const validateAndDisplayMultipleFilesToUpload = useCallback(
(data: FileObject[]) => {
if (!data?.length || data.some((fileObject) => !isDirectoryCheck(fileObject))) {
return;
}
if (data.length > CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT) {
const validFiles = data.slice(0, CONST.API_ATTACHMENT_VALIDATIONS.MAX_FILE_LIMIT);
setValidFilesToUpload(validFiles);
setFileError(CONST.FILE_VALIDATION_ERRORS.MAX_FILE_LIMIT_EXCEEDED);
setIsFileErrorModalVisible(true);
return;
}
validateFiles(data);
},
[isDirectoryCheck, validateFiles],
);

const validateAndDisplayFileToUpload = useCallback(
(data: FileObject) => {
if (!data || !isDirectoryCheck(data)) {
Expand Down Expand Up @@ -453,13 +392,21 @@ function AttachmentModal({
}
const inputSource = URL.createObjectURL(updatedFile);
updatedFile.uri = inputSource;
handleOpenModal(inputSource, updatedFile);
const inputModalType = getModalType(inputSource, updatedFile);
setIsModalOpen(true);
setSourceState(inputSource);
setFile(updatedFile);
setModalType(inputModalType);
} else if (fileObject.uri) {
handleOpenModal(fileObject.uri, fileObject);
const inputModalType = getModalType(fileObject.uri, fileObject);
setIsModalOpen(true);
setSourceState(fileObject.uri);
setFile(fileObject);
setModalType(inputModalType);
}
});
},
[isDirectoryCheck, isValidFile, handleOpenModal],
[isValidFile, getModalType, isDirectoryCheck],
);

/**
Expand All @@ -486,15 +433,6 @@ function AttachmentModal({
[onModalClose],
);

const closeAndResetModal = useCallback(() => {
closeConfirmModal();
closeModal();
InteractionManager.runAfterInteractions(() => {
setFileError(null);
setValidFilesToUpload([]);
});
}, [closeConfirmModal, closeModal]);

/**
* open the modal
*/
Expand Down Expand Up @@ -593,10 +531,10 @@ function AttachmentModal({
}
setShouldLoadAttachment(false);
clearAttachmentErrors();
setValidFilesToUpload([]);
if (isPDFLoadError.current) {
setFileError(CONST.FILE_VALIDATION_ERRORS.FILE_CORRUPTED);
setIsFileErrorModalVisible(true);
setIsAttachmentInvalid(true);
setAttachmentInvalidReasonTitle('attachmentPicker.attachmentError');
setAttachmentInvalidReason('attachmentPicker.errorWhileSelectingCorruptedAttachment');
return;
}

Expand Down Expand Up @@ -744,14 +682,13 @@ function AttachmentModal({
</Modal>
{!isReceiptAttachment && (
<ConfirmModal
title={getFileValidationErrorText(fileError).title}
onConfirm={confirmAndContinue}
onCancel={closeAndResetModal}
isVisible={isFileErrorModalVisible}
prompt={getFileValidationErrorText(fileError).reason}
confirmText={translate(validFilesToUpload.length ? 'common.continue' : 'common.close')}
shouldShowCancelButton={!!validFilesToUpload.length}
cancelText={translate('common.cancel')}
title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
onConfirm={closeConfirmModal}
onCancel={closeConfirmModal}
isVisible={isAttachmentInvalid}
prompt={attachmentInvalidReason ? translate(attachmentInvalidReason) : ''}
confirmText={translate('common.close')}
shouldShowCancelButton={false}
onModalHide={() => {
if (!isPDFLoadError.current) {
return;
Expand All @@ -764,7 +701,6 @@ function AttachmentModal({

{children?.({
displayFileInModal: validateAndDisplayFileToUpload,
displayMultipleFilesInModal: validateAndDisplayMultipleFilesToUpload,
show: openModal,
})}
</>
Expand Down
Loading
Loading