diff --git a/src/CONST.js b/src/CONST.js
index 0a4f6cfe0475..32847df3ab37 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -808,6 +808,14 @@ const CONST = {
// When generating a random value to fit in 7 digits (for the `middle` or `right` parts above), this is the maximum value to multiply by Math.random().
MAX_INT_FOR_RANDOM_7_DIGIT_VALUE: 10000000,
IOS_KEYBOARD_SPACE_OFFSET: -30,
+
+ PDF_PASSWORD_FORM: {
+ // Constants for password-related error responses received from react-pdf.
+ REACT_PDF_PASSWORD_RESPONSES: {
+ NEED_PASSWORD: 1,
+ INCORRECT_PASSWORD: 2,
+ },
+ },
};
export default CONST;
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index ca0393926b9e..e5235244b719 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -1,6 +1,6 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {View} from 'react-native';
+import {View, Animated} from 'react-native';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
import lodashExtend from 'lodash/extend';
@@ -9,6 +9,7 @@ import CONST from '../CONST';
import Modal from './Modal';
import AttachmentView from './AttachmentView';
import styles from '../styles/styles';
+import * as StyleUtils from '../styles/StyleUtils';
import themeColors from '../styles/themes/default';
import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL';
import compose from '../libs/compose';
@@ -78,11 +79,14 @@ class AttachmentModal extends PureComponent {
file: null,
sourceURL: props.sourceURL,
modalType: CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE,
+ isConfirmButtonDisabled: false,
+ confirmButtonFadeAnimation: new Animated.Value(1),
};
this.submitAndClose = this.submitAndClose.bind(this);
this.closeConfirmModal = this.closeConfirmModal.bind(this);
this.validateAndDisplayFileToUpload = this.validateAndDisplayFileToUpload.bind(this);
+ this.updateConfirmButtonVisibility = this.updateConfirmButtonVisibility.bind(this);
}
/**
@@ -123,8 +127,9 @@ class AttachmentModal extends PureComponent {
* Execute the onConfirm callback and close the modal.
*/
submitAndClose() {
- // If the modal has already been closed, don't allow another submission
- if (!this.state.isModalOpen) {
+ // If the modal has already been closed or the confirm button is disabled
+ // do not submit.
+ if (!this.state.isModalOpen || this.state.isConfirmButtonDisabled) {
return;
}
@@ -205,14 +210,38 @@ class AttachmentModal extends PureComponent {
}
}
+ /**
+ * In order to gracefully hide/show the confirm button when the keyboard
+ * opens/closes, apply an animation to fade the confirm button out/in. And since
+ * we're only updating the opacity of the confirm button, we must also conditionally
+ * disable it.
+ *
+ * @param {Boolean} shouldFadeOut If true, fade out confirm button. Otherwise fade in.
+ */
+ updateConfirmButtonVisibility(shouldFadeOut) {
+ this.setState({isConfirmButtonDisabled: shouldFadeOut});
+ const toValue = shouldFadeOut ? 0 : 1;
+
+ Animated.timing(this.state.confirmButtonFadeAnimation, {
+ toValue,
+ duration: 100,
+ useNativeDriver: true,
+ }).start();
+ }
+
render() {
const sourceURL = this.props.isAuthTokenRequired
? addEncryptedAuthTokenToURL(this.state.sourceURL)
: this.state.sourceURL;
+ // When the confirm button is visible we don't need bottom padding on the attachment view.
+ const attachmentViewPaddingStyles = this.props.onConfirm
+ ? [styles.pl5, styles.pr5, styles.pt5]
+ : styles.p5;
+
const attachmentViewStyles = this.props.isSmallScreenWidth || this.props.isMediumScreenWidth
? [styles.imageModalImageCenterContainer]
- : [styles.imageModalImageCenterContainer, styles.p5];
+ : [styles.imageModalImageCenterContainer, attachmentViewPaddingStyles];
const {fileName, fileExtension} = this.splitExtensionFromFileName(this.props.originalFileName || lodashGet(this.state, 'file.name', ''));
@@ -246,20 +275,27 @@ class AttachmentModal extends PureComponent {
/>
{this.state.sourceURL && (
-
+
)}
{/* If we have an onConfirm method show a confirmation button */}
{this.props.onConfirm && (
-
+
+
+
)}
diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js
index d783de4e0401..3f6a6fefb61d 100755
--- a/src/components/AttachmentView.js
+++ b/src/components/AttachmentView.js
@@ -28,6 +28,9 @@ const propTypes = {
/** Flag to show the loading indicator */
shouldShowLoadingSpinnerIcon: PropTypes.bool,
+ /** Notify parent that the UI should be modified to accommodate keyboard */
+ onToggleKeyboard: PropTypes.func,
+
...withLocalizePropTypes,
};
@@ -37,6 +40,7 @@ const defaultProps = {
},
shouldShowDownloadIcon: false,
shouldShowLoadingSpinnerIcon: false,
+ onToggleKeyboard: () => {},
};
const AttachmentView = (props) => {
@@ -48,6 +52,7 @@ const AttachmentView = (props) => {
);
}
diff --git a/src/components/PDFView/PDFInfoMessage.js b/src/components/PDFView/PDFInfoMessage.js
new file mode 100644
index 000000000000..5d583db40ea5
--- /dev/null
+++ b/src/components/PDFView/PDFInfoMessage.js
@@ -0,0 +1,43 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {View} from 'react-native';
+import Text from '../Text';
+import TextLink from '../TextLink';
+import Icon from '../Icon';
+import * as Expensicons from '../Icon/Expensicons';
+import styles from '../../styles/styles';
+import variables from '../../styles/variables';
+import withLocalize, {withLocalizePropTypes} from '../withLocalize';
+
+const propTypes = {
+ /** Callback function to indicate that PDF password form should be shown */
+ onShowForm: PropTypes.func.isRequired,
+
+ ...withLocalizePropTypes,
+};
+
+const PDFInfoMessage = props => (
+
+
+
+ {props.translate('attachmentView.pdfPasswordForm.title')}
+
+ {props.translate('attachmentView.pdfPasswordForm.infoText')}
+
+ {props.translate('attachmentView.pdfPasswordForm.beforeLinkText')}
+
+ {` ${props.translate('attachmentView.pdfPasswordForm.linkText')} `}
+
+ {props.translate('attachmentView.pdfPasswordForm.afterLinkText')}
+
+
+);
+
+PDFInfoMessage.propTypes = propTypes;
+PDFInfoMessage.displayName = 'PDFInfoMessage';
+
+export default withLocalize(PDFInfoMessage);
diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js
new file mode 100644
index 000000000000..144de7de6497
--- /dev/null
+++ b/src/components/PDFView/PDFPasswordForm.js
@@ -0,0 +1,163 @@
+import _ from 'underscore';
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import {View, ScrollView} from 'react-native';
+import Button from '../Button';
+import Text from '../Text';
+import TextInput from '../TextInput';
+import Icon from '../Icon';
+import * as Expensicons from '../Icon/Expensicons';
+import styles from '../../styles/styles';
+import colors from '../../styles/colors';
+import PDFInfoMessage from './PDFInfoMessage';
+import compose from '../../libs/compose';
+import withLocalize, {withLocalizePropTypes} from '../withLocalize';
+import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
+
+const propTypes = {
+ /** If the submitted password is invalid (show an error message) */
+ isPasswordInvalid: PropTypes.bool,
+
+ /** If the password field should be auto-focused */
+ shouldAutofocusPasswordField: PropTypes.bool,
+
+ /** If loading indicator should be shown */
+ shouldShowLoadingIndicator: PropTypes.bool,
+
+ /** Notify parent that the password form has been submitted */
+ onSubmit: PropTypes.func,
+
+ /** Notify parent that the password has been updated/edited */
+ onPasswordUpdated: PropTypes.func,
+
+ /** Notify parent that a text field has been focused or blurred */
+ onPasswordFieldFocused: PropTypes.func,
+
+ ...withLocalizePropTypes,
+ ...windowDimensionsPropTypes,
+};
+
+const defaultProps = {
+ isPasswordInvalid: false,
+ shouldAutofocusPasswordField: false,
+ shouldShowLoadingIndicator: false,
+ onSubmit: () => {},
+ onPasswordUpdated: () => {},
+ onPasswordFieldFocused: () => {},
+};
+
+class PDFPasswordForm extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ password: '',
+ validationErrorText: '',
+ shouldShowForm: false,
+ };
+ this.submitPassword = this.submitPassword.bind(this);
+ this.updatePassword = this.updatePassword.bind(this);
+ this.showForm = this.showForm.bind(this);
+ this.validateAndNotifyPasswordBlur = this.validateAndNotifyPasswordBlur.bind(this);
+ }
+
+ submitPassword() {
+ if (!this.validate()) {
+ return;
+ }
+ this.props.onSubmit(this.state.password);
+ }
+
+ updatePassword(password) {
+ this.props.onPasswordUpdated(password);
+ if (!_.isEmpty(password) && this.state.validationErrorText) {
+ this.setState({validationErrorText: ''});
+ }
+ this.setState({password});
+ }
+
+ validate() {
+ if (!_.isEmpty(this.state.password)) {
+ return true;
+ }
+ this.setState({
+ validationErrorText: this.props.translate('attachmentView.passwordRequired'),
+ });
+ return false;
+ }
+
+ validateAndNotifyPasswordBlur() {
+ this.validate();
+ this.props.onPasswordFieldFocused(false);
+ }
+
+ showForm() {
+ this.setState({shouldShowForm: true});
+ }
+
+ render() {
+ const containerStyle = this.props.isSmallScreenWidth
+ ? [styles.flex1, styles.w100]
+ : styles.pdfPasswordForm.wideScreenWidth;
+
+ return (
+ <>
+ {this.state.shouldShowForm ? (
+
+
+
+ {this.props.translate('attachmentView.pdfPasswordForm.formLabel')}
+
+
+ this.props.onPasswordFieldFocused(true)}
+ onBlur={this.validateAndNotifyPasswordBlur}
+ autoFocus={this.props.shouldAutofocusPasswordField}
+ secureTextEntry
+ />
+ {this.props.isPasswordInvalid && (
+
+
+
+
+ {this.props.translate('attachmentView.passwordIncorrect')}
+
+
+
+ )}
+
+
+ ) : (
+
+
+
+ )}
+ >
+ );
+ }
+}
+
+PDFPasswordForm.propTypes = propTypes;
+PDFPasswordForm.defaultProps = defaultProps;
+
+export default compose(
+ withWindowDimensions,
+ withLocalize,
+)(PDFPasswordForm);
diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js
index 74b2ec33747d..b4c9079ca681 100644
--- a/src/components/PDFView/index.js
+++ b/src/components/PDFView/index.js
@@ -1,46 +1,101 @@
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
+import _ from 'underscore';
+import React, {Component} from 'react';
import {View, Dimensions} from 'react-native';
import {Document, Page} from 'react-pdf/dist/esm/entry.webpack';
+import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator';
import styles from '../../styles/styles';
-import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
import variables from '../../styles/variables';
-import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator';
-
-const propTypes = {
- /** URL to full-sized image */
- sourceURL: PropTypes.string,
-
- ...windowDimensionsPropTypes,
+import CONST from '../../CONST';
+import PDFPasswordForm from './PDFPasswordForm';
+import * as pdfViewPropTypes from './pdfViewPropTypes';
+import withWindowDimensions from '../withWindowDimensions';
- /** Any additional styles to apply */
- // eslint-disable-next-line react/forbid-prop-types
- style: PropTypes.any,
-};
-
-const defaultProps = {
- sourceURL: '',
- style: {},
-};
-
-class PDFView extends PureComponent {
+class PDFView extends Component {
constructor(props) {
super(props);
this.state = {
numPages: null,
windowWidth: Dimensions.get('window').width,
+ shouldRequestPassword: false,
+ isPasswordInvalid: false,
+ isKeyboardOpen: false,
};
this.onDocumentLoadSuccess = this.onDocumentLoadSuccess.bind(this);
+ this.initiatePasswordChallenge = this.initiatePasswordChallenge.bind(this);
+ this.attemptPdfLoad = this.attemptPdfLoad.bind(this);
+ this.toggleKeyboardOnSmallScreens = this.toggleKeyboardOnSmallScreens.bind(this);
+ }
+
+ componentDidUpdate(prevProps) {
+ // Use window height changes to toggle the keyboard. To maintain keyboard state
+ // on all platforms we also use focus/blur events. So we need to make sure here
+ // that we avoid redundant keyboard toggling.
+ if (!this.state.isKeyboardOpen && this.props.windowHeight < prevProps.windowHeight) {
+ this.toggleKeyboardOnSmallScreens(true);
+ } else if (this.state.isKeyboardOpen && this.props.windowHeight > prevProps.windowHeight) {
+ this.toggleKeyboardOnSmallScreens(false);
+ }
}
/**
- * Callback to be called to set the number of pages on PDF
+ * Upon successful document load, set the number of pages on PDF,
+ * hide/reset PDF password form, and notify parent component that
+ * user input is no longer required.
*
* @param {*} {numPages} No of pages in the rendered PDF
* @memberof PDFView
*/
onDocumentLoadSuccess({numPages}) {
- this.setState({numPages});
+ this.setState({
+ numPages,
+ shouldRequestPassword: false,
+ isPasswordInvalid: false,
+ });
+ }
+
+ /**
+ * Initiate password challenge process. The react-pdf/Document
+ * component calls this handler to indicate that a PDF requires a
+ * password, or to indicate that a previously provided password was
+ * invalid.
+ *
+ * The PasswordResponses constants used below were copied from react-pdf
+ * because they're not exported in entry.webpack.
+ *
+ * @param {Function} callback Callback used to send password to react-pdf
+ * @param {Number} reason Reason code for password request
+ */
+ initiatePasswordChallenge(callback, reason) {
+ this.onPasswordCallback = callback;
+
+ if (reason === CONST.PDF_PASSWORD_FORM.REACT_PDF_PASSWORD_RESPONSES.NEED_PASSWORD) {
+ this.setState({shouldRequestPassword: true});
+ } else if (reason === CONST.PDF_PASSWORD_FORM.REACT_PDF_PASSWORD_RESPONSES.INCORRECT_PASSWORD) {
+ this.setState({shouldRequestPassword: true, isPasswordInvalid: true});
+ }
+ }
+
+ /**
+ * Send password to react-pdf via its callback so that it can attempt to load
+ * the PDF.
+ *
+ * @param {String} password Password to send via callback to react-pdf
+ */
+ attemptPdfLoad(password) {
+ this.onPasswordCallback(password);
+ }
+
+ /**
+ * On small screens notify parent that the keyboard has opened or closed.
+ *
+ * @param {Boolean} isKeyboardOpen True if keyboard is open
+ */
+ toggleKeyboardOnSmallScreens(isKeyboardOpen) {
+ if (!this.props.isSmallScreenWidth) {
+ return;
+ }
+ this.setState({isKeyboardOpen});
+ this.props.onToggleKeyboard(isKeyboardOpen);
}
render() {
@@ -49,42 +104,60 @@ class PDFView extends PureComponent {
? pdfContainerWidth : variables.pdfPageMaxWidth;
const pageWidth = this.props.isSmallScreenWidth ? this.state.windowWidth - 30 : pageWidthOnLargeScreen;
- return (
- this.setState({windowWidth: event.nativeEvent.layout.width})}
- >
- }
- file={this.props.sourceURL}
- options={{
- cMapUrl: 'cmaps/',
- cMapPacked: true,
+ const outerContainerStyle = [
+ styles.w100,
+ styles.h100,
+ styles.justifyContentCenter,
+ styles.alignItemsCenter,
+ ];
+
+ // If we're requesting a password then we need to hide - but still render -
+ // the PDF component.
+ const pdfContainerStyle = this.state.shouldRequestPassword
+ ? [styles.PDFView, this.props.style, styles.invisible]
+ : [styles.PDFView, this.props.style];
- }}
- externalLinkTarget="_blank"
- onLoadSuccess={this.onDocumentLoadSuccess}
+ return (
+
+ this.setState({windowWidth: event.nativeEvent.layout.width})}
>
- {
- Array.from(
- new Array(this.state.numPages),
- (el, index) => (
-
- ),
- )
- }
-
+ }
+ file={this.props.sourceURL}
+ options={{
+ cMapUrl: 'cmaps/',
+ cMapPacked: true,
+ }}
+ externalLinkTarget="_blank"
+ onLoadSuccess={this.onDocumentLoadSuccess}
+ onPassword={this.initiatePasswordChallenge}
+ >
+ {_.map(_.range(this.state.numPages), (v, index) => (
+
+ ))}
+
+
+ {this.state.shouldRequestPassword && (
+ this.setState({isPasswordInvalid: false})}
+ isPasswordInvalid={this.state.isPasswordInvalid}
+ shouldAutofocusPasswordField={!this.props.isSmallScreenWidth}
+ onPasswordFieldFocused={this.toggleKeyboardOnSmallScreens}
+ />
+ )}
);
}
}
-PDFView.propTypes = propTypes;
-PDFView.defaultProps = defaultProps;
-PDFView.displayName = 'PDFView';
+PDFView.propTypes = pdfViewPropTypes.propTypes;
+PDFView.defaultProps = pdfViewPropTypes.defaultProps;
export default withWindowDimensions(PDFView);
diff --git a/src/components/PDFView/index.native.js b/src/components/PDFView/index.native.js
index 1b8d3f48b03d..95a7d235c67a 100644
--- a/src/components/PDFView/index.native.js
+++ b/src/components/PDFView/index.native.js
@@ -1,51 +1,166 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {TouchableWithoutFeedback} from 'react-native';
+import React, {Component} from 'react';
+import {TouchableWithoutFeedback, View} from 'react-native';
import PDF from 'react-native-pdf';
import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
-import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator';
+import KeyboardAvoidingView from '../KeyboardAvoidingView';
+import PDFPasswordForm from './PDFPasswordForm';
+import * as pdfViewPropTypes from './pdfViewPropTypes';
+import compose from '../../libs/compose';
+import withWindowDimensions from '../withWindowDimensions';
+import withKeyboardState from '../withKeyboardState';
-const propTypes = {
- /** URL to full-sized image */
- sourceURL: PropTypes.string,
+/**
+ * On the native layer, we use react-native-pdf/PDF to display PDFs. If a PDF is
+ * password-protected we render a PDFPasswordForm to request a password
+ * from the user.
+ *
+ * In order to render things nicely during a password challenge we need
+ * to keep track of additional state. In particular, the
+ * react-native-pdf/PDF component is both conditionally rendered and hidden
+ * depending upon the situation. It needs to be rerendered on each password
+ * submission because it doesn't dynamically handle updates to its
+ * password property. And we need to hide it during password challenges
+ * so that PDFPasswordForm doesn't bounce when react-native-pdf/PDF
+ * is (temporarily) rendered.
+ */
+class PDFView extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ shouldRequestPassword: false,
+ shouldAttemptPdfLoad: true,
+ shouldShowLoadingIndicator: true,
+ isPasswordInvalid: false,
+ password: '',
+ };
+ this.initiatePasswordChallenge = this.initiatePasswordChallenge.bind(this);
+ this.attemptPdfLoadWithPassword = this.attemptPdfLoadWithPassword.bind(this);
+ this.finishPdfLoad = this.finishPdfLoad.bind(this);
+ }
- /** Any additional styles to apply */
- // eslint-disable-next-line react/forbid-prop-types
- style: PropTypes.any,
+ componentDidUpdate() {
+ this.props.onToggleKeyboard(this.props.isShown);
+ }
- ...windowDimensionsPropTypes,
-};
+ /**
+ * Initiate password challenge if message received from react-native-pdf/PDF
+ * indicates that a password is required or invalid.
+ *
+ * For a password challenge the message is "Password required or incorrect password."
+ * Note that the message doesn't specify whether the password is simply empty or
+ * invalid.
+ *
+ * @param {String} message
+ */
+ initiatePasswordChallenge({message}) {
+ this.setState({shouldShowLoadingIndicator: false});
-const defaultProps = {
- sourceURL: '',
- style: {},
-};
+ if (!message.match(/password/i)) {
+ return;
+ }
-/**
- * On the native layer, we use a pdf library to display PDFs
- *
- * @param props
- * @returns {JSX.Element}
- */
+ // Render password form, and don't render PDF and loading indicator.
+ this.setState({
+ shouldRequestPassword: true,
+ shouldAttemptPdfLoad: false,
+ });
+
+ // The message provided by react-native-pdf doesn't indicate whether this
+ // is an initial password request or if the password is invalid. So we just assume
+ // that if a password was already entered then it's an invalid password error.
+ if (this.state.password) {
+ this.setState({isPasswordInvalid: true});
+ }
+ }
+
+ /**
+ * When the password is submitted via PDFPasswordForm, save the password
+ * in state and attempt to load the PDF. Also show the loading indicator
+ * since react-native-pdf/PDF will need to reload the PDF.
+ *
+ * @param {String} password Password submitted via PDFPasswordForm
+ */
+ attemptPdfLoadWithPassword(password) {
+ // Render react-native-pdf/PDF so that it can validate the password.
+ // Note that at this point in the password challenge, shouldRequestPassword is true.
+ // Thus react-native-pdf/PDF will be rendered - but not visible.
+ this.setState({
+ password,
+ shouldAttemptPdfLoad: true,
+ shouldShowLoadingIndicator: true,
+ });
+ }
+
+ /**
+ * After the PDF is successfully loaded hide PDFPasswordForm and the loading
+ * indicator.
+ */
+ finishPdfLoad() {
+ this.setState({
+ shouldRequestPassword: false,
+ shouldShowLoadingIndicator: false,
+ });
+ }
+
+ render() {
+ const pdfStyles = [
+ styles.imageModalPDF,
+ StyleUtils.getWidthAndHeightStyle(this.props.windowWidth, this.props.windowHeight),
+ ];
+ const touchableStyles = [
+ styles.flex1,
+ this.props.style,
+ styles.w100,
+ ];
+
+ // If we haven't yet successfully validated the password and loaded the PDF,
+ // then we need to hide the react-native-pdf/PDF component so that PDFPasswordForm
+ // is positioned nicely. We're specifically hiding it because we still need to render
+ // the PDF component so that it can validate the password.
+ if (this.state.shouldRequestPassword) {
+ pdfStyles.push(styles.invisible);
+ }
+
+ const containerStyles = this.state.shouldRequestPassword && this.props.isSmallScreenWidth
+ ? [styles.w100, styles.flex1]
+ : [styles.alignItemsCenter, styles.flex1];
+
+ return (
+
+ {this.state.shouldAttemptPdfLoad && (
+
+ }
+ source={{uri: this.props.sourceURL}}
+ style={pdfStyles}
+ onError={this.initiatePasswordChallenge}
+ password={this.state.password}
+ onLoadComplete={this.finishPdfLoad}
+ />
+
+ )}
+ {this.state.shouldRequestPassword && (
+
+ this.setState({isPasswordInvalid: false})}
+ isPasswordInvalid={this.state.isPasswordInvalid}
+ shouldShowLoadingIndicator={this.state.shouldShowLoadingIndicator}
+ />
+
+ )}
+
+ );
+ }
+}
+
+PDFView.propTypes = pdfViewPropTypes.propTypes;
+PDFView.defaultProps = pdfViewPropTypes.defaultProps;
-const PDFView = props => (
-
- }
- source={{uri: props.sourceURL}}
- style={[
- styles.imageModalPDF,
- StyleUtils.getWidthAndHeightStyle(props.windowWidth, props.windowHeight),
- ]}
- />
-
-);
-
-PDFView.propTypes = propTypes;
-PDFView.defaultProps = defaultProps;
-PDFView.displayName = 'PDFView';
-
-export default withWindowDimensions(PDFView);
+export default compose(
+ withWindowDimensions,
+ withKeyboardState,
+)(PDFView);
diff --git a/src/components/PDFView/pdfViewPropTypes.js b/src/components/PDFView/pdfViewPropTypes.js
new file mode 100644
index 000000000000..281c135fe438
--- /dev/null
+++ b/src/components/PDFView/pdfViewPropTypes.js
@@ -0,0 +1,24 @@
+import PropTypes from 'prop-types';
+import stylePropTypes from '../../styles/stylePropTypes';
+import {windowDimensionsPropTypes} from '../withWindowDimensions';
+
+const propTypes = {
+ /** URL to full-sized image */
+ sourceURL: PropTypes.string,
+
+ /** Additional style props */
+ style: stylePropTypes,
+
+ /** Notify parent that the keyboard has opened or closed */
+ onToggleKeyboard: PropTypes.func,
+
+ ...windowDimensionsPropTypes,
+};
+
+const defaultProps = {
+ sourceURL: '',
+ style: {},
+ onToggleKeyboard: () => {},
+};
+
+export {propTypes, defaultProps};
diff --git a/src/languages/en.js b/src/languages/en.js
index 12809ad90065..cfc3803a2c91 100755
--- a/src/languages/en.js
+++ b/src/languages/en.js
@@ -579,6 +579,16 @@ export default {
},
attachmentView: {
unknownFilename: 'Unknown filename',
+ passwordRequired: 'Please enter a password',
+ passwordIncorrect: 'Incorrect password. Please try again.',
+ pdfPasswordForm: {
+ title: 'Password protected PDF',
+ infoText: 'This PDF is password protected.',
+ beforeLinkText: 'Please',
+ linkText: 'enter the password',
+ afterLinkText: 'to view it.',
+ formLabel: 'View PDF',
+ },
},
pronouns: {
heHimHis: 'He/him',
diff --git a/src/languages/es.js b/src/languages/es.js
index 3810cb69a7e7..b30a1995ffcc 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -579,6 +579,16 @@ export default {
},
attachmentView: {
unknownFilename: 'Archivo desconocido',
+ passwordRequired: 'Por favor introduce tu contraseña',
+ passwordIncorrect: 'Contraseña incorrecta. Por favor intenta de nuevo.',
+ pdfPasswordForm: {
+ title: 'PDF protegido con contraseña',
+ infoText: 'Este PDF esta protegido con contraseña.',
+ beforeLinkText: 'Por favor',
+ linkText: 'introduce la contraseña',
+ afterLinkText: 'para verlo.',
+ formLabel: 'Ver PDF',
+ },
},
pronouns: {
heHimHis: 'Él',
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index 7bba357dd38e..bdfe34ce2d1c 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -174,7 +174,7 @@ class ReportActionsList extends React.Component {
const extraData = (!this.props.isDrawerOpen && this.props.isSmallScreenWidth) ? this.props.newMarkerSequenceNumber : undefined;
const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(this.props.personalDetails, this.props.report);
return (
-
+