diff --git a/src/components/AttachmentPreview.tsx b/src/components/AttachmentPreview.tsx
index 19e0430f444b..3fee12304160 100644
--- a/src/components/AttachmentPreview.tsx
+++ b/src/components/AttachmentPreview.tsx
@@ -8,7 +8,7 @@ import {checkIsFileImage} from './Attachments/AttachmentView';
import DefaultAttachmentView from './Attachments/AttachmentView/DefaultAttachmentView';
import Icon from './Icon';
import {Play} from './Icon/Expensicons';
-import ImageView from './ImageView';
+import Image from './Image';
import PDFThumbnail from './PDFThumbnail';
import {PressableWithFeedback} from './Pressable';
@@ -78,9 +78,9 @@ function AttachmentPreview({source, aspectRatio = 1, onPress, onLoadError}: Atta
accessibilityLabel="Image Thumbnail"
>
-
@@ -89,11 +89,21 @@ function AttachmentPreview({source, aspectRatio = 1, onPress, onLoadError}: Atta
if (typeof source === 'string' && Str.isPDF(source) && !isEncryptedPDF) {
return (
- setIsEncryptedPDF(true)}
- />
+
+ setIsEncryptedPDF(true)}
+ />
+
);
}
diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx
index 66a42f6f0830..f7cb422ddb18 100644
--- a/src/components/PDFThumbnail/index.native.tsx
+++ b/src/components/PDFThumbnail/index.native.tsx
@@ -7,7 +7,7 @@ import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import PDFThumbnailError from './PDFThumbnailError';
import type PDFThumbnailProps from './types';
-function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError, onLoadSuccess}: PDFThumbnailProps) {
+function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, fitPolicy = 0, onPassword, onLoadError, onLoadSuccess}: PDFThumbnailProps) {
const styles = useThemeStyles();
const sizeStyles = [styles.w100, styles.h100];
const [failedToLoad, setFailedToLoad] = useState(false);
@@ -17,7 +17,7 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena
{enabled && !failedToLoad && (
}
source={{uri: isAuthTokenRequired ? addEncryptedAuthTokenToURL(previewSourceURL) : previewSourceURL}}
diff --git a/src/components/PDFThumbnail/types.ts b/src/components/PDFThumbnail/types.ts
index 3d79e7c026d2..ca95bdc55bc7 100644
--- a/src/components/PDFThumbnail/types.ts
+++ b/src/components/PDFThumbnail/types.ts
@@ -13,6 +13,9 @@ type PDFThumbnailProps = {
/** Whether the PDF thumbnail can be loaded */
enabled?: boolean;
+ /** Fit policy for the PDF thumbnail */
+ fitPolicy?: number;
+
/** Callback to call if PDF is password protected */
onPassword?: () => void;
diff --git a/src/pages/Share/ShareDetailsPage.tsx b/src/pages/Share/ShareDetailsPage.tsx
index 620b98bab927..8847884581f1 100644
--- a/src/pages/Share/ShareDetailsPage.tsx
+++ b/src/pages/Share/ShareDetailsPage.tsx
@@ -1,5 +1,5 @@
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useMemo, useState} from 'react';
+import React, {useEffect, useMemo, useState} from 'react';
import {SafeAreaView, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
@@ -11,7 +11,6 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {FallbackAvatar} from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
import ScreenWrapper from '@components/ScreenWrapper';
-import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import useLocalize from '@hooks/useLocalize';
@@ -24,13 +23,16 @@ import type {ShareNavigatorParamList} from '@libs/Navigation/types';
import {getReportDisplayOption} from '@libs/OptionsListUtils';
import {getReportOrDraftReport, isDraftReport} from '@libs/ReportUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
+import variables from '@styles/variables';
import UserListItem from '@src/components/SelectionList/UserListItem';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Report as ReportType} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import KeyboardUtils from '@src/utils/keyboard';
+import getFileSize from './getFileSize';
import {showErrorAlert} from './ShareRootPage';
type ShareDetailsPageProps = StackScreenProps;
@@ -46,10 +48,37 @@ function ShareDetailsPage({
const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE);
const isTextShared = currentAttachment?.mimeType === 'txt';
const [message, setMessage] = useState(isTextShared ? currentAttachment?.content ?? '' : '');
+ const [errorTitle, setErrorTitle] = useState(undefined);
+ const [errorMessage, setErrorMessage] = useState(undefined);
const report: OnyxEntry = getReportOrDraftReport(reportOrAccountID);
const displayReport = useMemo(() => getReportDisplayOption(report, unknownUserDetails), [report, unknownUserDetails]);
+ useEffect(() => {
+ if (!currentAttachment?.content || errorTitle) {
+ return;
+ }
+ getFileSize(currentAttachment?.content).then((size) => {
+ if (size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
+ setErrorTitle(translate('attachmentPicker.attachmentTooLarge'));
+ setErrorMessage(translate('attachmentPicker.sizeExceeded'));
+ }
+
+ if (size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
+ setErrorTitle(translate('attachmentPicker.attachmentTooSmall'));
+ setErrorMessage(translate('attachmentPicker.sizeNotMet'));
+ }
+ });
+ }, [currentAttachment, errorTitle, translate]);
+
+ useEffect(() => {
+ if (!errorTitle || !errorMessage) {
+ return;
+ }
+
+ showErrorAlert(errorTitle, errorMessage);
+ }, [errorTitle, errorMessage]);
+
if (isEmptyObject(report)) {
return ;
}
@@ -68,7 +97,7 @@ function ShareDetailsPage({
if (isTextShared) {
addComment(report.reportID, message);
const routeToNavigate = ROUTES.REPORT_WITH_ID.getRoute(reportOrAccountID);
- Navigation.navigate(routeToNavigate);
+ Navigation.navigate(routeToNavigate, {forceReplace: true});
return;
}
@@ -100,20 +129,20 @@ function ShareDetailsPage({
};
return (
- {
- KeyboardUtils.dismiss();
- }}
- accessible={false}
+
-
-
+
+ {
+ KeyboardUtils.dismiss();
+ }}
+ accessible={false}
+ >
)}
-
-
-
-
-
-
-
+
+
+
+
+
+ {
+ KeyboardUtils.dismiss();
+ }}
+ accessible={false}
+ >
{shouldShowAttachment && (
<>
@@ -176,19 +212,19 @@ function ShareDetailsPage({
>
)}
-
+
-
+
-
-
+
+
);
}
diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx
index de8da74f2fa3..c28a9606e183 100644
--- a/src/pages/Share/ShareRootPage.tsx
+++ b/src/pages/Share/ShareRootPage.tsx
@@ -16,6 +16,7 @@ import ShareActionHandler from '@libs/ShareActionHandlerModule';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {ShareTempFile} from '@src/types/onyx';
+import getFileSize from './getFileSize';
import ShareTab from './ShareTab';
import SubmitTab from './SubmitTab';
@@ -39,33 +40,50 @@ function ShareRootPage() {
const [isFileScannable, setIsFileScannable] = useState(false);
const receiptFileFormats = Object.values(CONST.RECEIPT_ALLOWED_FILE_TYPES) as string[];
const shareFileMimetypes = Object.values(CONST.SHARE_FILE_MIMETYPE) as string[];
+ const [errorTitle, setErrorTitle] = useState(undefined);
+ const [errorMessage, setErrorMessage] = useState(undefined);
+
+ useEffect(() => {
+ if (!errorTitle || !errorMessage) {
+ return;
+ }
+
+ showErrorAlert(errorTitle, errorMessage);
+ }, [errorTitle, errorMessage]);
const handleProcessFiles = useCallback(() => {
ShareActionHandler.processFiles((processedFiles) => {
const tempFile = Array.isArray(processedFiles) ? processedFiles.at(0) : (JSON.parse(processedFiles) as ShareTempFile);
+ if (errorTitle) {
+ return;
+ }
if (!tempFile?.mimeType || !shareFileMimetypes.includes(tempFile?.mimeType)) {
- showErrorAlert(translate('attachmentPicker.wrongFileType'), translate('attachmentPicker.notAllowedExtension'));
+ setErrorTitle(translate('attachmentPicker.wrongFileType'));
+ setErrorMessage(translate('attachmentPicker.notAllowedExtension'));
return;
}
- const fileRegexp = /image\/.*/;
- if (fileRegexp.test(tempFile?.mimeType)) {
+ const isImage = /image\/.*/.test(tempFile?.mimeType);
+ if (tempFile?.mimeType && tempFile?.mimeType !== 'txt' && !isImage) {
+ getFileSize(tempFile?.content).then((size) => {
+ if (size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
+ setErrorTitle(translate('attachmentPicker.attachmentTooLarge'));
+ setErrorMessage(translate('attachmentPicker.sizeExceeded'));
+ }
+
+ if (size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
+ setErrorTitle(translate('attachmentPicker.attachmentTooSmall'));
+ setErrorMessage(translate('attachmentPicker.sizeNotMet'));
+ }
+ });
+ }
+
+ if (isImage) {
const fileObject: FileObject = {name: tempFile.id, uri: tempFile?.content, type: tempFile?.mimeType};
- validateImageForCorruption(fileObject)
- .then(() => {
- if (fileObject.size && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
- showErrorAlert(translate('attachmentPicker.attachmentTooLarge'), translate('attachmentPicker.sizeExceeded'));
- }
-
- if (fileObject.size && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
- showErrorAlert(translate('attachmentPicker.attachmentTooSmall'), translate('attachmentPicker.sizeNotMet'));
- }
-
- return true;
- })
- .catch(() => {
- showErrorAlert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedAttachment'));
- });
+ validateImageForCorruption(fileObject).catch(() => {
+ setErrorTitle(translate('attachmentPicker.attachmentError'));
+ setErrorMessage(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment'));
+ });
}
const {fileExtension} = splitExtensionFromFileName(tempFile?.content);
@@ -82,7 +100,7 @@ function ShareRootPage() {
addTempShareFile(tempFile);
}
});
- }, [receiptFileFormats, shareFileMimetypes, translate]);
+ }, [receiptFileFormats, shareFileMimetypes, translate, errorTitle]);
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx
index f29b7fea6c52..295ecf9b832a 100644
--- a/src/pages/Share/SubmitDetailsPage.tsx
+++ b/src/pages/Share/SubmitDetailsPage.tsx
@@ -50,6 +50,17 @@ function SubmitDetailsPage({
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
+ const [errorTitle, setErrorTitle] = useState(undefined);
+ const [errorMessage, setErrorMessage] = useState(undefined);
+
+ useEffect(() => {
+ if (!errorTitle || !errorMessage) {
+ return;
+ }
+
+ showErrorAlert(errorTitle, errorMessage);
+ }, [errorTitle, errorMessage]);
+
useEffect(() => {
initMoneyRequest(reportOrAccountID, policy, false, CONST.IOU.REQUEST_TYPE.SCAN, CONST.IOU.REQUEST_TYPE.SCAN);
}, [reportOrAccountID, policy]);
@@ -201,10 +212,18 @@ function SubmitDetailsPage({
shouldShowSmartScanFields={false}
isDistanceRequest={false}
onPDFLoadError={() => {
- showErrorAlert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedAttachment'));
+ if (errorTitle) {
+ return;
+ }
+ setErrorTitle(translate('attachmentPicker.attachmentError'));
+ setErrorMessage(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment'));
}}
onPDFPassword={() => {
- showErrorAlert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.protectedPDFNotSupported'));
+ if (errorTitle) {
+ return;
+ }
+ setErrorTitle(translate('attachmentPicker.attachmentError'));
+ setErrorMessage(translate('attachmentPicker.protectedPDFNotSupported'));
}}
/>
diff --git a/src/pages/Share/getFileSize/index.native.ts b/src/pages/Share/getFileSize/index.native.ts
new file mode 100644
index 000000000000..c008f37b2fab
--- /dev/null
+++ b/src/pages/Share/getFileSize/index.native.ts
@@ -0,0 +1,10 @@
+import RNFS from 'react-native-fs';
+import type GetFileSizeType from './types';
+
+const getFileSize: GetFileSizeType = (uri: string) => {
+ return RNFS.stat(uri).then((fileStat) => {
+ return fileStat.size;
+ });
+};
+
+export default getFileSize;
diff --git a/src/pages/Share/getFileSize/index.ts b/src/pages/Share/getFileSize/index.ts
new file mode 100644
index 000000000000..41f9be06b5d1
--- /dev/null
+++ b/src/pages/Share/getFileSize/index.ts
@@ -0,0 +1,7 @@
+import type GetFileSizeType from './types';
+
+const getFileSize: GetFileSizeType = () => {
+ return Promise.resolve(0);
+};
+
+export default getFileSize;
diff --git a/src/pages/Share/getFileSize/types.ts b/src/pages/Share/getFileSize/types.ts
new file mode 100644
index 000000000000..e8739f5294f4
--- /dev/null
+++ b/src/pages/Share/getFileSize/types.ts
@@ -0,0 +1,3 @@
+type GetFileSizeType = (uri: string) => Promise;
+
+export default GetFileSizeType;