From b6566e688b8e7ebb897e19e9b38c983f528a6631 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 31 Mar 2026 18:54:24 +0200 Subject: [PATCH 1/6] Add rotation for PDF attachments --- .../AttachmentView/AttachmentViewPdf/index.tsx | 3 ++- .../AttachmentView/AttachmentViewPdf/types.ts | 4 ++++ .../Attachments/AttachmentView/index.tsx | 6 ++++++ src/components/PDFView/index.tsx | 3 ++- src/components/PDFView/types.ts | 4 ++++ .../AttachmentModalBaseContent/index.tsx | 3 +++ .../AttachmentModalBaseContent/types.ts | 4 ++++ .../routes/TransactionReceiptModalContent.tsx | 16 +++++++++++++++- 8 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx index e8f6568f98c0b..5196a3a99c660 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx @@ -2,7 +2,7 @@ import React, {memo} from 'react'; import PDFView from '@components/PDFView'; import type AttachmentViewPdfProps from './types'; -function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError}: AttachmentViewPdfProps) { +function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError, rotation}: AttachmentViewPdfProps) { return ( ); } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts index dd5bae6e9e7b7..d10e9561735f2 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts @@ -1,3 +1,4 @@ +import type {RotationDegrees} from 'react-fast-pdf'; import type {StyleProp, ViewStyle} from 'react-native'; import type {AttachmentViewProps} from '..'; @@ -16,6 +17,9 @@ type AttachmentViewPdfProps = Pick; + + /** Controlled rotation angle for the PDF */ + rotation?: RotationDegrees; }; function checkIsFileImage(source: string | number | ImageURISource | ImageURISource[], fileName: string | undefined) { @@ -131,6 +135,7 @@ function AttachmentView({ isUploading = false, reportID, transaction: transactionProp, + rotation, }: AttachmentViewProps) { const icons = useMemoizedLazyExpensifyIcons(['ArrowCircleClockwise', 'Gallery']); const [transactionFromOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`); @@ -255,6 +260,7 @@ function AttachmentView({ style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} isUsedAsChatAttachment={isUsedAsChatAttachment} onLoadError={onPDFLoadError} + rotation={rotation} /> ); diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 9242f2a1bcadf..195ee25e514ef 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -22,7 +22,7 @@ import type {PDFViewProps} from './types'; const LOADING_THUMBNAIL_HEIGHT = 250; const LOADING_THUMBNAIL_WIDTH = 250; -function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, style, isUsedAsChatAttachment, onLoadError}: PDFViewProps) { +function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, style, isUsedAsChatAttachment, onLoadError, rotation}: PDFViewProps) { const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -117,6 +117,7 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, sty } shouldShowErrorComponent={false} onLoadError={onLoadError} + rotation={rotation} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( @@ -291,6 +293,7 @@ function AttachmentModalBaseContent({ isWorkspaceAvatar, maybeIcon, onNavigate, + pdfRotation, report, reportID, setAttachmentError, diff --git a/src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/types.ts b/src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/types.ts index 94d737d12ce92..d15d17f528093 100644 --- a/src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/types.ts +++ b/src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/types.ts @@ -1,4 +1,5 @@ import type {RefObject} from 'react'; +import type {RotationDegrees} from 'react-fast-pdf'; import type {StyleProp, View, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -142,6 +143,9 @@ type AttachmentModalBaseContentProps = { /** Extra styles to pass for the attachment view container */ attachmentViewContainerStyles?: StyleProp; + + /** Controlled rotation angle for the PDF */ + pdfRotation?: RotationDegrees; }; export type { diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index 94e73084d48c7..6181d8118d2db 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -1,5 +1,6 @@ import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import type {RotationDegrees} from 'react-fast-pdf'; import {View} from 'react-native'; import Button from '@components/Button'; import ConfirmModal from '@components/ConfirmModal'; @@ -158,11 +159,13 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre const isEReceipt = transaction && !hasReceiptSource(transaction) && hasEReceipt(transaction); const fileName = (isOdometerImage ? odometerFilename : receiptFilename) ?? ''; const isImage = !!fileName && Str.isImage(fileName); + const isPDF = !!fileName && Str.isPDF(fileName); const fileType = isOdometerImage ? odometerFileType : (transaction?.receipt?.type ?? CONST.IMAGE_FILE_FORMAT.JPEG); const isTrackExpenseActionValue = isTrackExpenseAction(parentReportAction); const iouType = useMemo(() => iouTypeParam ?? (isTrackExpenseActionValue ? CONST.IOU.TYPE.TRACK : CONST.IOU.TYPE.SUBMIT), [isTrackExpenseActionValue, iouTypeParam]); const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false); + const [pdfRotation, setPdfRotation] = useState(0); const [isRotating, setIsRotating] = useState(false); const [isCropping, setIsCropping] = useState(false); const [isCropSaving, setIsCropSaving] = useState(false); @@ -546,7 +549,15 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre style={styles.transactionReceiptButton} /> )} - {(shouldShowReplaceReceiptButton || isOdometerImage) && ( + {isPDF && ( +