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

Replace receipt #26508

Merged
merged 34 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d2f3e5f
create EditRequestReceiptPage
luacmartins Sep 1, 2023
6c96baf
rm EditRequestReceiptPage
luacmartins Sep 1, 2023
c1bd0b1
add three dot menu
luacmartins Sep 1, 2023
3c2594a
use correct anchor position
luacmartins Sep 1, 2023
3a69d70
add overlay
luacmartins Sep 1, 2023
8116a8e
adjust offset
luacmartins Sep 1, 2023
f5234ac
Merge branch 'main' into cmartins-replaceReceipt
luacmartins Sep 5, 2023
6ea483c
add replace callback
luacmartins Sep 5, 2023
eb50cd0
add comment
luacmartins Sep 5, 2023
356f371
use attachmentmodal in reportActionItemImage
luacmartins Sep 5, 2023
1c54e59
Revert "use attachmentmodal in reportActionItemImage"
luacmartins Sep 5, 2023
84ef3bd
create EditRequestReceiptPage
luacmartins Sep 5, 2023
2ac57b7
create route const, add onSubmit callback, navigate to correct route
luacmartins Sep 5, 2023
945d500
rm double onyx usage
luacmartins Sep 5, 2023
f4392a1
add onyx back, add dropui provider
luacmartins Sep 5, 2023
125117b
add props
luacmartins Sep 5, 2023
95fc8c2
pass replaceReceipt callback to ReceiptSelector
luacmartins Sep 5, 2023
23acf54
create IOU.replaceReceipt
luacmartins Sep 5, 2023
69ac668
dismiss modal
luacmartins Sep 5, 2023
24b38e6
resolve navigation issue
luacmartins Sep 11, 2023
43c08f5
use correct file path and name
luacmartins Sep 11, 2023
435bb61
replace in open state
luacmartins Sep 11, 2023
e735569
Merge branch 'main' into cmartins-replaceReceipt
luacmartins Sep 11, 2023
a1c8657
fix styles
luacmartins Sep 11, 2023
332a193
resolve conflicts
luacmartins Sep 12, 2023
fda0cee
update early return logic
luacmartins Sep 12, 2023
424ac34
rm promise
luacmartins Sep 13, 2023
1fc6c5a
resolve conflicts
luacmartins Sep 13, 2023
164b129
refactor usage to build filepath differently on native
luacmartins Sep 15, 2023
b81a159
refactor props
luacmartins Sep 15, 2023
1a8f8f5
resolve conflicts
luacmartins Sep 18, 2023
72eca6d
fix file select
luacmartins Sep 18, 2023
39236e3
read photo file
luacmartins Sep 18, 2023
35d51ab
Merge branch 'main' into cmartins-replaceReceipt
luacmartins Sep 19, 2023
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
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,7 @@ const CONST = {
DATE: 'date',
DESCRIPTION: 'description',
MERCHANT: 'merchant',
RECEIPT: 'receipt',
},
FOOTER: {
EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`,
Expand Down
33 changes: 31 additions & 2 deletions src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useState, useCallback} from 'react';
import React, {useState, useCallback, useRef} from 'react';
import PropTypes from 'prop-types';
import {View, Animated, Keyboard} from 'react-native';
import Str from 'expensify-common/lib/str';
Expand All @@ -25,6 +25,10 @@ import HeaderGap from './HeaderGap';
import SafeAreaConsumer from './SafeAreaConsumer';
import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL';
import reportPropTypes from '../pages/reportPropTypes';
import * as Expensicons from './Icon/Expensicons';
import useWindowDimensions from '../hooks/useWindowDimensions';
import Navigation from '../libs/Navigation/Navigation';
import ROUTES from '../ROUTES';
import useNativeDriver from '../libs/useNativeDriver';

/**
Expand Down Expand Up @@ -94,6 +98,7 @@ const defaultProps = {
};

function AttachmentModal(props) {
const onModalHideCallbackRef = useRef(null);
const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen);
const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false);
const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
Expand All @@ -106,6 +111,8 @@ function AttachmentModal(props) {
const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false);
const [confirmButtonFadeAnimation] = useState(new Animated.Value(1));
const [shouldShowDownloadButton, setShouldShowDownloadButton] = React.useState(true);
const {windowWidth} = useWindowDimensions();

const [file, setFile] = useState(
props.originalFileName
? {
Expand Down Expand Up @@ -331,6 +338,10 @@ function AttachmentModal(props) {
}}
onModalHide={(e) => {
props.onModalHide(e);
if (onModalHideCallbackRef.current) {
onModalHideCallbackRef.current();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

}

setShouldLoadAttachment(false);
}}
propagateSwipe
Expand All @@ -339,12 +350,30 @@ function AttachmentModal(props) {
<HeaderWithBackButton
title={props.headerTitle || translate(isAttachmentReceipt ? 'common.receipt' : 'common.attachment')}
shouldShowBorderBottom
shouldShowDownloadButton={props.allowDownload && shouldShowDownloadButton}
shouldShowDownloadButton={props.allowDownload && shouldShowDownloadButton && !isAttachmentReceipt}
onDownloadButtonPress={() => downloadAttachment(source)}
shouldShowCloseButton={!props.isSmallScreenWidth}
shouldShowBackButton={props.isSmallScreenWidth}
onBackButtonPress={closeModal}
onCloseButtonPress={closeModal}
shouldShowThreeDotsButton={isAttachmentReceipt}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)}
threeDotsMenuItems={[
{
icon: Expensicons.Camera,
text: props.translate('common.replace'),
onSelected: () => {
onModalHideCallbackRef.current = () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT));
closeModal();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused #27903 when navigating to the replace receipt screen not from within a tab navigator, i.e. whenever clicking from the three dots menu.

},
},
{
icon: Expensicons.Download,
text: props.translate('common.download'),
onSelected: () => downloadAttachment(source),
},
]}
shouldOverlay
/>
<View style={styles.imageModalImageCenterContainer}>
{!_.isEmpty(props.report) ? (
Expand Down
2 changes: 2 additions & 0 deletions src/components/HeaderWithBackButton/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function HeaderWithBackButton({
},
threeDotsMenuItems = [],
children = null,
shouldOverlay = false,
}) {
const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState();
const {translate} = useLocalize();
Expand Down Expand Up @@ -137,6 +138,7 @@ function HeaderWithBackButton({
menuItems={threeDotsMenuItems}
onIconPress={onThreeDotsButtonPress}
anchorPosition={threeDotsAnchorPosition}
shouldOverlay={shouldOverlay}
/>
)}
{shouldShowCloseButton && (
Expand Down
8 changes: 6 additions & 2 deletions src/components/ThreeDotsMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const propTypes = {
horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
}),

/** Whether the popover menu should overlay the current view */
shouldOverlay: PropTypes.bool,
};

const defaultProps = {
Expand All @@ -57,9 +60,10 @@ const defaultProps = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP
},
shouldOverlay: false,
};

function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment}) {
function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay}) {
const [isPopupMenuVisible, setPopupMenuVisible] = useState(false);
const buttonRef = useRef(null);
const {translate} = useLocalize();
Expand Down Expand Up @@ -106,7 +110,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me
anchorAlignment={anchorAlignment}
onItemSelected={hidePopoverMenu}
menuItems={menuItems}
withoutOverlay
withoutOverlay={!shouldOverlay}
anchorRef={buttonRef}
/>
</>
Expand Down
38 changes: 38 additions & 0 deletions src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,43 @@ function payMoneyRequest(paymentType, chatReport, iouReport) {
}
}

/**
* @param {String} transactionID
* @param {Object} receipt
* @param {String} filePath
*/
function replaceReceipt(transactionID, receipt, filePath) {
const transaction = lodashGet(allTransactions, 'transactionID', {});
const oldReceipt = lodashGet(transaction, 'receipt', {});

const optimisticData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: {
receipt: {
source: filePath,
state: CONST.IOU.RECEIPT_STATE.OPEN,
},
filename: receipt.name,
},
},
];

const failureData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
value: {
receipt: oldReceipt,
filename: transaction.filename,
},
},
];

API.write('ReplaceReceipt', {transactionID, receipt}, {optimisticData, failureData});
}

/**
* Initialize money request info and navigate to the MoneyRequest page
* @param {String} iouType
Expand Down Expand Up @@ -2051,4 +2088,5 @@ export {
setMoneyRequestReceipt,
createEmptyTransaction,
navigateToNextPage,
replaceReceipt,
};
10 changes: 10 additions & 0 deletions src/pages/EditRequestPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import EditRequestDescriptionPage from './EditRequestDescriptionPage';
import EditRequestMerchantPage from './EditRequestMerchantPage';
import EditRequestCreatedPage from './EditRequestCreatedPage';
import EditRequestAmountPage from './EditRequestAmountPage';
import EditRequestReceiptPage from './EditRequestReceiptPage';
import reportPropTypes from './reportPropTypes';
import * as IOU from '../libs/actions/IOU';
import * as CurrencyUtils from '../libs/CurrencyUtils';
Expand Down Expand Up @@ -171,6 +172,15 @@ function EditRequestPage({report, route, parentReport, policy, session}) {
);
}

if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) {
return (
<EditRequestReceiptPage
route={route}
transactionID={transaction.transactionID}
/>
);
}

return <FullPageNotFoundView shouldShow />;
}

Expand Down
52 changes: 52 additions & 0 deletions src/pages/EditRequestReceiptPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import ScreenWrapper from '../components/ScreenWrapper';
import HeaderWithBackButton from '../components/HeaderWithBackButton';
import Navigation from '../libs/Navigation/Navigation';
import useLocalize from '../hooks/useLocalize';
import ReceiptSelector from './iou/ReceiptSelector';
import DragAndDropProvider from '../components/DragAndDrop/Provider';

const propTypes = {
/** React Navigation route */
route: PropTypes.shape({
/** Params from the route */
params: PropTypes.shape({
/** The type of IOU report, i.e. bill, request, send */
iouType: PropTypes.string,

/** The report ID of the IOU */
reportID: PropTypes.string,
}),
}).isRequired,

/** The id of the transaction we're editing */
transactionID: PropTypes.string.isRequired,
};

function EditRequestReceiptPage({route, transactionID}) {
const {translate} = useLocalize();

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
>
<HeaderWithBackButton
title={translate('common.receipt')}
onBackButtonPress={Navigation.goBack}
/>
<DragAndDropProvider>
<ReceiptSelector
route={route}
transactionID={transactionID}
/>
</DragAndDropProvider>
Comment on lines +35 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes an inconsistency issue here where the Green drag and drop area does not show in full-screen #28014

</ScreenWrapper>
);
}

EditRequestReceiptPage.propTypes = propTypes;
EditRequestReceiptPage.displayName = 'EditRequestReceiptPage';

export default EditRequestReceiptPage;
12 changes: 12 additions & 0 deletions src/pages/iou/ReceiptSelector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import useLocalize from '../../../hooks/useLocalize';
import {DragAndDropContext} from '../../../components/DragAndDrop/Provider';
import * as ReceiptUtils from '../../../libs/ReceiptUtils';
import {iouPropTypes, iouDefaultProps} from '../propTypes';
import Navigation from '../../../libs/Navigation/Navigation';

const propTypes = {
/** Information shown to the user when a receipt is not valid */
Expand All @@ -47,6 +48,9 @@ const propTypes = {

/** Holds data related to Money Request view state, rather than the underlying Money Request data. */
iou: iouPropTypes,

/** The id of the transaction we're editing */
transactionID: PropTypes.string,
};

const defaultProps = {
Expand All @@ -57,6 +61,7 @@ const defaultProps = {
},
report: {},
iou: iouDefaultProps,
transactionID: '',
};

function ReceiptSelector(props) {
Expand All @@ -83,6 +88,13 @@ function ReceiptSelector(props) {

const filePath = URL.createObjectURL(file);
IOU.setMoneyRequestReceipt(filePath, file.name);

if (props.transactionID) {
IOU.replaceReceipt(props.transactionID, file, filePath);
Navigation.dismissModal();
return;
}

IOU.navigateToNextPage(iou, iouType, reportID, report);
};

Expand Down
45 changes: 36 additions & 9 deletions src/pages/iou/ReceiptSelector/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import Log from '../../../libs/Log';
import * as CameraPermission from './CameraPermission';
import {iouPropTypes, iouDefaultProps} from '../propTypes';
import NavigationAwareCamera from './NavigationAwareCamera';
import Navigation from '../../../libs/Navigation/Navigation';
import * as FileUtils from '../../../libs/fileDownload/FileUtils';

const propTypes = {
/** React Navigation route */
Expand All @@ -42,11 +44,15 @@ const propTypes = {

/** Holds data related to Money Request view state, rather than the underlying Money Request data. */
iou: iouPropTypes,

/** The id of the transaction we're editing */
transactionID: PropTypes.string,
};

const defaultProps = {
report: {},
iou: iouDefaultProps,
transactionID: '',
};

/**
Expand Down Expand Up @@ -74,7 +80,7 @@ function getImagePickerOptions(type) {
};
}

function ReceiptSelector(props) {
function ReceiptSelector({route, report, iou, transactionID}) {
const devices = useCameraDevices('wide-angle-camera');
const device = devices.back;

Expand All @@ -84,9 +90,9 @@ function ReceiptSelector(props) {
const isAndroidBlockedPermissionRef = useRef(false);
const appState = useRef(AppState.currentState);

const iouType = lodashGet(props.route, 'params.iouType', '');
const reportID = lodashGet(props.route, 'params.reportID', '');
const pageIndex = lodashGet(props.route, 'params.pageIndex', 1);
const iouType = lodashGet(route, 'params.iouType', '');
const reportID = lodashGet(route, 'params.reportID', '');
const pageIndex = lodashGet(route, 'params.pageIndex', 1);

const {translate} = useLocalize();

Expand Down Expand Up @@ -195,14 +201,25 @@ function ReceiptSelector(props) {
flash: flash ? 'on' : 'off',
})
.then((photo) => {
IOU.setMoneyRequestReceipt(`file://${photo.path}`, photo.path);
IOU.navigateToNextPage(props.iou, iouType, reportID, props.report);
const filePath = `file://${photo.path}`;
IOU.setMoneyRequestReceipt(filePath, photo.path);

if (transactionID) {
FileUtils.readFileAsync(filePath, photo.path).then((receipt) => {
IOU.replaceReceipt(transactionID, receipt, filePath);
});

Navigation.dismissModal();
return;
}

IOU.navigateToNextPage(iou, iouType, reportID, report);
})
.catch((error) => {
showCameraAlert();
Log.warn('Error taking photo', error);
});
}, [flash, iouType, props.iou, props.report, reportID, translate]);
}, [flash, iouType, iou, report, reportID, translate, transactionID]);

CameraPermission.getCameraPermissionStatus().then((permissionStatus) => {
setPermissions(permissionStatus);
Expand Down Expand Up @@ -260,8 +277,18 @@ function ReceiptSelector(props) {
onPress={() => {
showImagePicker(launchImageLibrary)
.then((receiptImage) => {
IOU.setMoneyRequestReceipt(receiptImage[0].uri, receiptImage[0].fileName);
IOU.navigateToNextPage(props.iou, iouType, reportID, props.report);
const filePath = receiptImage[0].uri;
IOU.setMoneyRequestReceipt(filePath, receiptImage[0].fileName);

if (transactionID) {
FileUtils.readFileAsync(filePath, receiptImage[0].fileName).then((receipt) => {
IOU.replaceReceipt(transactionID, receipt, filePath);
});
Navigation.dismissModal();
return;
}

IOU.navigateToNextPage(iou, iouType, reportID, report);
})
.catch(() => {
Log.info('User did not select an image from gallery');
Expand Down
Loading
Loading