Skip to content
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: PDF is not cached #32678

Merged
merged 18 commits into from
Feb 28, 2024
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
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ const ONYXKEYS = {
/** Indicates whether we should store logs or not */
SHOULD_STORE_LOGS: 'shouldStoreLogs',

// Paths of PDF file that has been cached during one session
CACHED_PDF_PATHS: 'cachedPDFPaths',

/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
Expand Down Expand Up @@ -566,6 +569,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.PLAID_CURRENT_EVENT]: string;
[ONYXKEYS.LOGS]: Record<number, OnyxTypes.Log>;
[ONYXKEYS.SHOULD_STORE_LOGS]: boolean;
[ONYXKEYS.CACHED_PDF_PATHS]: Record<string, string>;
};

type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}) {
isAuthTokenRequired={item.isAuthTokenRequired}
onPress={onPress}
transactionID={item.transactionID}
reportActionID={item.reportActionID}
isHovered={isModalHovered}
isFocused={isFocused}
optionalVideoDuration={item.duration}
Expand Down
17 changes: 16 additions & 1 deletion src/components/Attachments/AttachmentView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CachedPDFPaths from '@libs/actions/CachedPDFPaths';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import compose from '@libs/compose';
import * as TransactionUtils from '@libs/TransactionUtils';
Expand Down Expand Up @@ -57,6 +58,9 @@ const propTypes = {
// eslint-disable-next-line react/no-unused-prop-types
transactionID: PropTypes.string,

/** The id of the report action related to the attachment */
reportActionID: PropTypes.string,

isHovered: PropTypes.bool,

optionalVideoDuration: PropTypes.number,
Expand All @@ -71,6 +75,7 @@ const defaultProps = {
isWorkspaceAvatar: false,
maybeIcon: false,
transactionID: '',
reportActionID: '',
isHovered: false,
optionalVideoDuration: 0,
};
Expand All @@ -92,6 +97,7 @@ function AttachmentView({
maybeIcon,
fallbackSource,
transaction,
reportActionID,
isHovered,
optionalVideoDuration,
}) {
Expand Down Expand Up @@ -153,6 +159,15 @@ function AttachmentView({
if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) {
const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source;

const onPDFLoadComplete = (path) => {
if (path && (transaction.transactionID || reportActionID)) {
CachedPDFPaths.add(transaction.transactionID || reportActionID, path);
}
if (!loadComplete) {
setLoadComplete(true);
}
};

// We need the following View component on android native
// So that the event will propagate properly and
// the Password protected preview will be shown for pdf attachement we are about to send.
Expand All @@ -166,7 +181,7 @@ function AttachmentView({
encryptedSourceUrl={encryptedSourceUrl}
onPress={onPress}
onToggleKeyboard={onToggleKeyboard}
onLoadComplete={() => !loadComplete && setLoadComplete(true)}
onLoadComplete={onPDFLoadComplete}
errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]}
style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1}
isUsedInCarousel={isUsedInCarousel}
Expand Down
8 changes: 5 additions & 3 deletions src/components/PDFView/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,14 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused
/**
* After the PDF is successfully loaded hide PDFPasswordForm and the loading
* indicator.
* @param {Number} numberOfPages
* @param {Number} path - Path to cache location
*/
const finishPDFLoad = () => {
const finishPDFLoad = (numberOfPages, path) => {
setShouldRequestPassword(false);
setShouldShowLoadingIndicator(false);
setSuccessToLoadPDF(true);
onLoadComplete();
onLoadComplete(path);
};

function renderPDFView() {
Expand Down Expand Up @@ -137,7 +139,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused
fitPolicy={0}
trustAllCerts={false}
renderActivityIndicator={() => <FullScreenLoadingIndicator />}
source={{uri: sourceURL}}
source={{uri: sourceURL, cache: true, expiration: 864000}}
style={pdfStyles}
onError={handleFailureToLoadPDF}
password={password}
Expand Down
47 changes: 47 additions & 0 deletions src/libs/actions/CachedPDFPaths/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {exists, unlink} from 'react-native-fs';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Add, Clear, ClearAll, ClearByKey} from './types';

/*
* We need to save the paths of PDF files so we can delete them later.
* This is to remove the cached PDFs when an attachment is deleted or the user logs out.
*/
let pdfPaths: Record<string, string> = {};
Onyx.connect({
key: ONYXKEYS.CACHED_PDF_PATHS,
callback: (val) => {
pdfPaths = val ?? {};
},
});

const add: Add = (id: string, path: string) => {
if (pdfPaths[id]) {
return Promise.resolve();
}
return Onyx.merge(ONYXKEYS.CACHED_PDF_PATHS, {[id]: path});
};

const clear: Clear = (path: string) => {
if (!path) {
return Promise.resolve();
}
return new Promise((resolve) => {
exists(path).then((exist) => {
if (!exist) {
resolve();
}
return unlink(path);
});
});
};

const clearByKey: ClearByKey = (id: string) => {
clear(pdfPaths[id] ?? '').then(() => Onyx.merge(ONYXKEYS.CACHED_PDF_PATHS, {[id]: null}));
};

const clearAll: ClearAll = () => {
Promise.all(Object.values(pdfPaths).map(clear)).then(() => Onyx.merge(ONYXKEYS.CACHED_PDF_PATHS, {}));
};

export {add, clearByKey, clearAll};
9 changes: 9 additions & 0 deletions src/libs/actions/CachedPDFPaths/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {Add, ClearAll, ClearByKey} from './types';

const add: Add = () => Promise.resolve();

const clearByKey: ClearByKey = () => {};

const clearAll: ClearAll = () => {};

export {add, clearByKey, clearAll};
6 changes: 6 additions & 0 deletions src/libs/actions/CachedPDFPaths/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type Add = (id: string, path: string) => Promise<void>;
type Clear = (path: string) => Promise<void>;
type ClearAll = () => void;
type ClearByKey = (id: string) => void;

export type {Add, Clear, ClearAll, ClearByKey};
2 changes: 2 additions & 0 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import type {OnyxData} from '@src/types/onyx/Request';
import type {Comment, Receipt, ReceiptSource, TaxRate, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import * as CachedPDFPaths from './CachedPDFPaths';
import * as Policy from './Policy';
import * as Report from './Report';

Expand Down Expand Up @@ -3139,6 +3140,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor

// STEP 6: Make the API request
API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData});
CachedPDFPaths.clearByKey(transactionID);

// STEP 7: Navigate the user depending on which page they are on and which resources were deleted
if (iouReport && isSingleTransactionView && shouldDeleteTransactionThread && !shouldDeleteIOUReport) {
Expand Down
2 changes: 2 additions & 0 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/Rep
import type ReportAction from '@src/types/onyx/ReportAction';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import * as CachedPDFPaths from './CachedPDFPaths';
import * as Modal from './Modal';
import * as Session from './Session';
import * as Welcome from './Welcome';
Expand Down Expand Up @@ -1223,6 +1224,7 @@ function deleteReportComment(reportID: string, reportAction: ReportAction) {
reportActionID,
};

CachedPDFPaths.clearByKey(reportActionID);
API.write(WRITE_COMMANDS.DELETE_COMMENT, parameters, {optimisticData, successData, failureData});
}

Expand Down
Loading