Skip to content
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
29 changes: 18 additions & 11 deletions src/components/AttachmentPicker/index.native.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import _ from 'underscore';
import React, {useState, useRef, useCallback, useMemo} from 'react';
import PropTypes from 'prop-types';
import {View, Alert, Linking} from 'react-native';
import RNDocumentPicker from 'react-native-document-picker';
import RNFetchBlob from 'react-native-blob-util';
import lodashCompact from 'lodash/compact';
import {launchImageLibrary} from 'react-native-image-picker';
import {propTypes as basePropTypes, defaultProps} from './attachmentPickerPropTypes';
import {propTypes as basePropTypes, defaultProps as baseDefaultProps} from './attachmentPickerPropTypes';
import CONST from '../../CONST';
import * as FileUtils from '../../libs/fileDownload/FileUtils';
import * as Expensicons from '../Icon/Expensicons';
Expand All @@ -19,6 +21,14 @@ import useArrowKeyFocusManager from '../../hooks/useArrowKeyFocusManager';

const propTypes = {
...basePropTypes,

/** If this value is true, then we exclude Camera option. */
shouldHideCameraOption: PropTypes.bool,
};

const defaultProps = {
...baseDefaultProps,
shouldHideCameraOption: false,
};

/**
Expand Down Expand Up @@ -90,7 +100,7 @@ const getDataForUpload = (fileData) => {
* @param {propTypes} props
* @returns {JSX.Element}
*/
function AttachmentPicker({type, children}) {
function AttachmentPicker({type, children, shouldHideCameraOption}) {
const [isVisible, setIsVisible] = useState(false);

const completeAttachmentSelection = useRef();
Expand Down Expand Up @@ -180,8 +190,8 @@ function AttachmentPicker({type, children}) {
);

const menuItemData = useMemo(() => {
const data = [
{
const data = lodashCompact([
!shouldHideCameraOption && {
icon: Expensicons.Camera,
textTranslationKey: 'attachmentPicker.takePhoto',
pickAttachment: () => showImagePicker(launchCamera),
Expand All @@ -191,18 +201,15 @@ function AttachmentPicker({type, children}) {
textTranslationKey: 'attachmentPicker.chooseFromGallery',
pickAttachment: () => showImagePicker(launchImageLibrary),
},
];

if (type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE) {
data.push({
type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE && {
icon: Expensicons.Paperclip,
textTranslationKey: 'attachmentPicker.chooseDocument',
pickAttachment: showDocumentPicker,
});
}
},
]);

return data;
}, [showDocumentPicker, showImagePicker, type]);
}, [showDocumentPicker, showImagePicker, type, shouldHideCameraOption]);

const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: menuItemData.length - 1, isActive: isVisible});

Expand Down
150 changes: 33 additions & 117 deletions src/pages/iou/ReceiptSelector/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
import {useCameraDevices} from 'react-native-vision-camera';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import {launchImageLibrary} from 'react-native-image-picker';
import {withOnyx} from 'react-native-onyx';
import {RESULTS} from 'react-native-permissions';
import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback';
import Icon from '../../../components/Icon';
import * as Expensicons from '../../../components/Icon/Expensicons';
import AttachmentPicker from '../../../components/AttachmentPicker';
import styles from '../../../styles/styles';
import Shutter from '../../../../assets/images/shutter.svg';
import Hand from '../../../../assets/images/hand.svg';
Expand Down Expand Up @@ -63,31 +63,6 @@ const defaultProps = {
isInTabNavigator: true,
};

/**
* See https://github.com/react-native-image-picker/react-native-image-picker/#options
* for ImagePicker configuration options
*/
const imagePickerOptions = {
includeBase64: false,
saveToPhotos: false,
selectionLimit: 1,
includeExtra: false,
};

/**
* Return imagePickerOptions based on the type
* @param {String} type
* @returns {Object}
*/
function getImagePickerOptions(type) {
// mediaType property is one of the ImagePicker configuration to restrict types'
const mediaType = type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE ? 'photo' : 'mixed';
return {
mediaType,
...imagePickerOptions,
};
}

function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator}) {
const devices = useCameraDevices('wide-angle-camera');
const device = devices.back;
Expand Down Expand Up @@ -122,35 +97,6 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator})
};
}, []);

/**
* Inform the users when they need to grant camera access and guide them to settings
*/
const showPermissionsAlert = () => {
Alert.alert(
translate('attachmentPicker.cameraPermissionRequired'),
translate('attachmentPicker.expensifyDoesntHaveAccessToCamera'),
[
{
text: translate('common.cancel'),
style: 'cancel',
},
{
text: translate('common.settings'),
onPress: () => Linking.openSettings(),
},
],
{cancelable: false},
);
};

/**
* A generic handling when we don't know the exact reason for an error
*
*/
const showGeneralAlert = () => {
Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingAttachment'));
};

const askForPermissions = () => {
// There's no way we can check for the BLOCKED status without requesting the permission first
// https://github.com/zoontek/react-native-permissions/blob/a836e114ce3a180b2b23916292c79841a267d828/README.md?plain=1#L670
Expand All @@ -164,36 +110,6 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator})
}
};

/**
* Common image picker handling
*
* @param {function} imagePickerFunc - RNImagePicker.launchCamera or RNImagePicker.launchImageLibrary
* @returns {Promise}
*/
const showImagePicker = (imagePickerFunc) =>
new Promise((resolve, reject) => {
imagePickerFunc(getImagePickerOptions(CONST.ATTACHMENT_PICKER_TYPE.IMAGE), (response) => {
if (response.didCancel) {
// When the user cancelled resolve with no attachment
return resolve();
}
if (response.errorCode) {
switch (response.errorCode) {
case 'permission':
showPermissionsAlert();
return resolve();
default:
showGeneralAlert();
break;
}

return reject(new Error(`Error during attachment selection: ${response.errorMessage}`));
}

return resolve(response.assets);
});
});

const takePhoto = useCallback(() => {
const showCameraAlert = () => {
Alert.alert(translate('receipt.cameraErrorTitle'), translate('receipt.cameraErrorMessage'));
Expand Down Expand Up @@ -275,38 +191,38 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator})
/>
)}
<View style={[styles.flexRow, styles.justifyContentAround, styles.alignItemsCenter, styles.pv3]}>
<PressableWithFeedback
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={translate('receipt.gallery')}
style={[styles.alignItemsStart]}
onPress={() => {
showImagePicker(launchImageLibrary)
.then((receiptImage) => {
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, report, route.path);
})
.catch(() => {
Log.info('User did not select an image from gallery');
});
}}
>
<Icon
height={32}
width={32}
src={Expensicons.Gallery}
fill={themeColors.textSupporting}
/>
</PressableWithFeedback>
<AttachmentPicker shouldHideCameraOption>
{({openPicker}) => (
<PressableWithFeedback
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={translate('receipt.gallery')}
style={[styles.alignItemsStart]}
onPress={() => {
openPicker({
onPicked: (file) => {
const filePath = file.uri;
IOU.setMoneyRequestReceipt(filePath, file.name);

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

IOU.navigateToNextPage(iou, iouType, report, route.path);
},
});
}}
>
<Icon
height={32}
width={32}
src={Expensicons.Gallery}
fill={themeColors.textSupporting}
/>
</PressableWithFeedback>
)}
</AttachmentPicker>
<PressableWithFeedback
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={translate('receipt.shutter')}
Expand Down