Skip to content

Commit 36fb3bf

Browse files
authored
Merge pull request Expensify#86760 from callstack-internal/feature/pdf-rotation-in-modal
Add rotation for PDF attachments
2 parents bcdb515 + b652fb4 commit 36fb3bf

10 files changed

Lines changed: 41 additions & 4 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@
153153
"react-content-loader": "^7.0.0",
154154
"react-dom": "19.2.0",
155155
"react-error-boundary": "^4.0.11",
156-
"react-fast-pdf": "^1.0.32",
156+
"react-fast-pdf": "^1.0.33",
157157
"react-is": "^18.3.1",
158158
"react-map-gl": "^7.1.3",
159159
"react-native": "0.83.1",

src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, {memo} from 'react';
22
import PDFView from '@components/PDFView';
33
import type AttachmentViewPdfProps from './types';
44

5-
function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError}: AttachmentViewPdfProps) {
5+
function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onToggleKeyboard, onLoadComplete, style, isUsedAsChatAttachment, onLoadError, rotation}: AttachmentViewPdfProps) {
66
return (
77
<PDFView
88
onPress={onPress}
@@ -14,6 +14,7 @@ function AttachmentViewPdf({file, encryptedSourceUrl, isFocused, onPress, onTogg
1414
onLoadComplete={onLoadComplete}
1515
isUsedAsChatAttachment={isUsedAsChatAttachment}
1616
onLoadError={onLoadError}
17+
rotation={rotation}
1718
/>
1819
);
1920
}

src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {RotationDegrees} from 'react-fast-pdf';
12
import type {StyleProp, ViewStyle} from 'react-native';
23
import type {AttachmentViewProps} from '..';
34

@@ -16,6 +17,9 @@ type AttachmentViewPdfProps = Pick<AttachmentViewProps, 'file' | 'onPress' | 'is
1617

1718
/** Whether the PDF is used as a chat attachment */
1819
isUsedAsChatAttachment?: boolean;
20+
21+
/** Controlled rotation angle for the PDF */
22+
rotation?: RotationDegrees;
1923
};
2024

2125
export default AttachmentViewPdfProps;

src/components/Attachments/AttachmentView/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Str} from 'expensify-common';
22
import React, {memo, useEffect, useState} from 'react';
3+
import type {RotationDegrees} from 'react-fast-pdf';
34
import type {GestureResponderEvent, ImageURISource, StyleProp, ViewStyle} from 'react-native';
45
import {View} from 'react-native';
56
import type {OnyxEntry} from 'react-native-onyx';
@@ -94,6 +95,9 @@ type AttachmentViewProps = Attachment & {
9495

9596
/** Transaction object. When provided, will be used instead of fetching from Onyx. */
9697
transaction?: OnyxEntry<OnyxTypes.Transaction>;
98+
99+
/** Controlled rotation angle for the PDF */
100+
rotation?: RotationDegrees;
97101
};
98102

99103
function checkIsFileImage(source: string | number | ImageURISource | ImageURISource[], fileName: string | undefined) {
@@ -131,6 +135,7 @@ function AttachmentView({
131135
isUploading = false,
132136
reportID,
133137
transaction: transactionProp,
138+
rotation,
134139
}: AttachmentViewProps) {
135140
const icons = useMemoizedLazyExpensifyIcons(['ArrowCircleClockwise', 'Gallery']);
136141
const [transactionFromOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`);
@@ -255,6 +260,7 @@ function AttachmentView({
255260
style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1}
256261
isUsedAsChatAttachment={isUsedAsChatAttachment}
257262
onLoadError={onPDFLoadError}
263+
rotation={rotation}
258264
/>
259265
</View>
260266
);

src/components/PDFView/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type {PDFViewProps} from './types';
2222
const LOADING_THUMBNAIL_HEIGHT = 250;
2323
const LOADING_THUMBNAIL_WIDTH = 250;
2424

25-
function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, style, isUsedAsChatAttachment, onLoadError}: PDFViewProps) {
25+
function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, style, isUsedAsChatAttachment, onLoadError, rotation}: PDFViewProps) {
2626
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
2727
const styles = useThemeStyles();
2828
const StyleUtils = useStyleUtils();
@@ -117,6 +117,7 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, sty
117117
}
118118
shouldShowErrorComponent={false}
119119
onLoadError={onLoadError}
120+
rotation={rotation}
120121
renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => (
121122
<PDFPasswordForm
122123
isFocused={!!isFocused}

src/components/PDFView/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {RotationDegrees} from 'react-fast-pdf';
12
import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
23

34
type PDFViewProps = {
@@ -30,6 +31,9 @@ type PDFViewProps = {
3031

3132
/** Whether the PDF is used as a chat attachment */
3233
isUsedAsChatAttachment?: boolean;
34+
35+
/** Controlled rotation angle for the PDF */
36+
rotation?: RotationDegrees;
3337
};
3438

3539
type PDFViewNativeProps = PDFViewProps & {

src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ function AttachmentModalBaseContent({
7070
footerActionButtons,
7171
customAttachmentContent,
7272
attachmentViewContainerStyles,
73+
pdfRotation,
7374
}: AttachmentModalBaseContentProps) {
7475
const styles = useThemeStyles();
7576
const {shouldUseNarrowLayout} = useResponsiveLayout();
@@ -259,6 +260,7 @@ function AttachmentModalBaseContent({
259260
transaction={transaction}
260261
isUploaded={!isEmptyObject(report)}
261262
reportID={reportID ?? (!isEmptyObject(report) ? report.reportID : undefined)}
263+
rotation={pdfRotation}
262264
/>
263265
</AttachmentCarouselPagerActionsContext.Provider>
264266
</AttachmentCarouselPagerStateContext.Provider>
@@ -278,6 +280,7 @@ function AttachmentModalBaseContent({
278280
isWorkspaceAvatar,
279281
maybeIcon,
280282
onNavigate,
283+
pdfRotation,
281284
report,
282285
reportID,
283286
setAttachmentError,

src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {RefObject} from 'react';
2+
import type {RotationDegrees} from 'react-fast-pdf';
23
import type {StyleProp, View, ViewStyle} from 'react-native';
34
import type {OnyxEntry} from 'react-native-onyx';
45
import type {ValueOf} from 'type-fest';
@@ -142,6 +143,9 @@ type AttachmentModalBaseContentProps = {
142143

143144
/** Extra styles to pass for the attachment view container */
144145
attachmentViewContainerStyles?: StyleProp<ViewStyle>;
146+
147+
/** Controlled rotation angle for the PDF */
148+
pdfRotation?: RotationDegrees;
145149
};
146150

147151
export type {

src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Str} from 'expensify-common';
22
import React, {useCallback, useEffect, useMemo, useState} from 'react';
3+
import type {RotationDegrees} from 'react-fast-pdf';
34
import {View} from 'react-native';
45
import Button from '@components/Button';
56
import ConfirmModal from '@components/ConfirmModal';
@@ -155,11 +156,13 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
155156
const isEReceipt = transaction && !hasReceiptSource(transaction) && hasEReceipt(transaction);
156157
const fileName = (isOdometerImage ? odometerFilename : receiptFilename) ?? '';
157158
const isImage = !!fileName && Str.isImage(fileName);
159+
const isPDF = !!fileName && Str.isPDF(fileName);
158160
const fileType = isOdometerImage ? odometerFileType : (transaction?.receipt?.type ?? CONST.IMAGE_FILE_FORMAT.JPEG);
159161
const isTrackExpenseActionValue = isTrackExpenseAction(parentReportAction);
160162
const iouType = useMemo(() => iouTypeParam ?? (isTrackExpenseActionValue ? CONST.IOU.TYPE.TRACK : CONST.IOU.TYPE.SUBMIT), [isTrackExpenseActionValue, iouTypeParam]);
161163

162164
const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false);
165+
const [pdfRotation, setPdfRotation] = useState<RotationDegrees>(0);
163166
const [isRotating, setIsRotating] = useState(false);
164167
const [isCropping, setIsCropping] = useState(false);
165168
const [isCropSaving, setIsCropSaving] = useState(false);
@@ -531,6 +534,14 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
531534
style={styles.transactionReceiptButton}
532535
/>
533536
)}
537+
{isPDF && !isNative && (
538+
<Button
539+
icon={expensifyIcons.Rotate}
540+
onPress={() => setPdfRotation((prev) => ((prev + 270) % 360) as RotationDegrees)}
541+
text={translate('common.rotate')}
542+
style={styles.transactionReceiptButton}
543+
/>
544+
)}
534545
{(shouldShowReplaceReceiptButton || isOdometerImage) && (
535546
<Button
536547
icon={expensifyIcons.Camera}
@@ -569,6 +580,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
569580
shouldShowRotateAndCropReceiptButton,
570581
shouldShowReplaceReceiptButton,
571582
isOdometerImage,
583+
isPDF,
572584
styles.flexRow,
573585
styles.gap2,
574586
styles.ph5,
@@ -628,6 +640,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
628640
shouldShowNotFoundPage,
629641
shouldShowCarousel: false,
630642
shouldShowRotateButton: false,
643+
pdfRotation,
631644
onDownloadAttachment: allowDownload ? undefined : onDownloadAttachment,
632645
transaction,
633646
shouldMinimizeMenuButton: false,
@@ -648,6 +661,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre
648661
shouldShowNotFoundPage,
649662
allowDownload,
650663
onDownloadAttachment,
664+
pdfRotation,
651665
footerActionButtons,
652666
customAttachmentContent,
653667
styles.pv5,

0 commit comments

Comments
 (0)