diff --git a/android/app/build.gradle b/android/app/build.gradle
index ed6b297f493b..eff00b9238c9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -148,8 +148,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001005803
- versionName "1.0.58-3"
+ versionCode 1001005804
+ versionName "1.0.58-4"
}
splits {
abi {
diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist
index a8cd5c17bd37..a06c83ca6ced 100644
--- a/ios/ExpensifyCash/Info.plist
+++ b/ios/ExpensifyCash/Info.plist
@@ -30,7 +30,7 @@
CFBundleVersion
- 1.0.58.3
+ 1.0.58.4
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist
index f90b12caa93f..e2c75f224f37 100644
--- a/ios/ExpensifyCashTests/Info.plist
+++ b/ios/ExpensifyCashTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.0.58.3
+ 1.0.58.4
diff --git a/package-lock.json b/package-lock.json
index 71d9532d60a2..eb849b66b455 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "expensify.cash",
- "version": "1.0.58-3",
+ "version": "1.0.58-4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 5c5cccd9cd81..2e78607c643a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "expensify.cash",
- "version": "1.0.58-3",
+ "version": "1.0.58-4",
"author": "Expensify, Inc.",
"homepage": "https://expensify.cash",
"description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/libs/reportUtils.js b/src/libs/reportUtils.js
index 237b9519cc34..f553f794197c 100644
--- a/src/libs/reportUtils.js
+++ b/src/libs/reportUtils.js
@@ -1,5 +1,15 @@
import _ from 'underscore';
import Str from 'expensify-common/lib/str';
+import lodashGet from 'lodash/get';
+import Onyx from 'react-native-onyx';
+import ONYXKEYS from '../ONYXKEYS';
+import CONST from '../CONST';
+
+let sessionEmail;
+Onyx.connect({
+ key: ONYXKEYS.SESSION,
+ callback: val => sessionEmail = val ? val.email : null,
+});
/**
* Returns the concatenated title for the PrimaryLogins of a report
@@ -35,6 +45,22 @@ function sortReportsByLastVisited(reports) {
.value();
}
+/**
+ * Can only edit if it's a ADDCOMMENT, the author is this user and it's not a optimistic response.
+ * If it's an optimistic response comment it will not have a reportActionID,
+ * and we should wait until it does before we show the actions
+ *
+ * @param {Object} reportAction
+ * @param {String} sessionEmail
+ * @returns {Boolean}
+ */
+function canEditReportAction(reportAction) {
+ return reportAction.actorEmail === sessionEmail
+ && reportAction.reportActionID
+ && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT
+ && !isReportMessageAttachment(lodashGet(reportAction, ['message', 0, 'text'], ''));
+}
+
/**
* Given a collection of reports returns the most recently accessed one
*
@@ -49,5 +75,6 @@ export {
getReportParticipantsTitle,
isReportMessageAttachment,
findLastAccessedReport,
+ canEditReportAction,
sortReportsByLastVisited,
};
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index b6b2c8ac273d..e1f0fecebbf8 100755
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -27,7 +27,12 @@ import {
Receipt,
} from '../../../components/Icon/Expensicons';
import AttachmentPicker from '../../../components/AttachmentPicker';
-import {addAction, saveReportComment, broadcastUserIsTyping} from '../../../libs/actions/Report';
+import {
+ addAction,
+ saveReportComment,
+ saveReportActionDraft,
+ broadcastUserIsTyping,
+} from '../../../libs/actions/Report';
import ReportTypingIndicator from './ReportTypingIndicator';
import AttachmentModal from '../../../components/AttachmentModal';
import compose from '../../../libs/compose';
@@ -44,6 +49,8 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal
import Permissions from '../../../libs/Permissions';
import Navigation from '../../../libs/Navigation/Navigation';
import ROUTES from '../../../ROUTES';
+import ReportActionPropTypes from './ReportActionPropTypes';
+import {canEditReportAction} from '../../../libs/reportUtils';
const propTypes = {
/** A method to call when the form is submitted */
@@ -68,6 +75,9 @@ const propTypes = {
participants: PropTypes.arrayOf(PropTypes.string),
}),
+ /** Array of report actions for this report */
+ reportActions: PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)),
+
/** Is the report view covered by the drawer */
isDrawerOpen: PropTypes.bool.isRequired,
@@ -91,6 +101,7 @@ const defaultProps = {
comment: '',
modal: {},
report: {},
+ reportActions: {},
network: {isOffline: false},
};
@@ -101,7 +112,7 @@ class ReportActionCompose extends React.Component {
this.updateComment = this.updateComment.bind(this);
this.debouncedSaveReportComment = _.debounce(this.debouncedSaveReportComment.bind(this), 1000, false);
this.debouncedBroadcastUserIsTyping = _.debounce(this.debouncedBroadcastUserIsTyping.bind(this), 100, true);
- this.triggerSubmitShortcut = this.triggerSubmitShortcut.bind(this);
+ this.triggerHotkeyActions = this.triggerHotkeyActions.bind(this);
this.submitForm = this.submitForm.bind(this);
this.setIsFocused = this.setIsFocused.bind(this);
this.showEmojiPicker = this.showEmojiPicker.bind(this);
@@ -231,15 +242,32 @@ class ReportActionCompose extends React.Component {
}
/**
- * Listens for the keyboard shortcut and submits
- * the form when we have enter
+ * Listens for keyboard shortcuts and applies the action
*
* @param {Object} e
*/
- triggerSubmitShortcut(e) {
- if (e && e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- this.submitForm();
+ triggerHotkeyActions(e) {
+ if (e) {
+ // Submit the form when Enter is pressed
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ this.submitForm();
+ }
+
+ // Trigger the edit box for last sent message if ArrowUp is pressed
+ if (e.key === 'ArrowUp' && this.state.isCommentEmpty) {
+ e.preventDefault();
+
+ const reportActionKey = _.find(
+ Object.keys(this.props.reportActions).reverse(),
+ key => canEditReportAction(this.props.reportActions[key]),
+ );
+
+ if (reportActionKey !== -1 && this.props.reportActions[reportActionKey]) {
+ const {reportActionID, message} = this.props.reportActions[reportActionKey];
+ saveReportActionDraft(this.props.reportID, reportActionID, _.last(message).text);
+ }
+ }
}
}
@@ -414,7 +442,7 @@ class ReportActionCompose extends React.Component {
placeholder={this.props.translate('reportActionCompose.writeSomething')}
placeholderTextColor={themeColors.placeholderText}
onChangeText={this.updateComment}
- onKeyPress={this.triggerSubmitShortcut}
+ onKeyPress={this.triggerHotkeyActions}
onDragEnter={() => this.setState({isDraggingOver: true})}
onDragLeave={() => this.setState({isDraggingOver: false})}
onDrop={(e) => {
@@ -534,6 +562,10 @@ export default compose(
network: {
key: ONYXKEYS.NETWORK,
},
+ reportActions: {
+ key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
+ canEvict: false,
+ },
report: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
},
diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js
index 32f9b10a3948..8f4079dc0d79 100755
--- a/src/pages/home/report/ReportActionContextMenu.js
+++ b/src/pages/home/report/ReportActionContextMenu.js
@@ -3,7 +3,6 @@ import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
-import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import {
Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark,
@@ -16,11 +15,9 @@ import ReportActionContextMenuItem from './ReportActionContextMenuItem';
import ReportActionPropTypes from './ReportActionPropTypes';
import Clipboard from '../../../libs/Clipboard';
import compose from '../../../libs/compose';
-import {isReportMessageAttachment} from '../../../libs/reportUtils';
-import ONYXKEYS from '../../../ONYXKEYS';
+import {isReportMessageAttachment, canEditReportAction} from '../../../libs/reportUtils';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import ConfirmModal from '../../../components/ConfirmModal';
-import CONST from '../../../CONST';
const propTypes = {
/** The ID of the report this report action is attached to. */
@@ -46,13 +43,6 @@ const propTypes = {
/** Function to dismiss the popover containing this menu */
hidePopover: PropTypes.func.isRequired,
- /* Onyx Props */
-
- /** The session of the logged in person */
- session: PropTypes.shape({
- /** Email of the logged in person */
- email: PropTypes.string,
- }),
...withLocalizePropTypes,
};
@@ -60,7 +50,6 @@ const defaultProps = {
isMini: false,
isVisible: false,
selection: '',
- session: {},
draftMessage: '',
};
@@ -71,7 +60,6 @@ class ReportActionContextMenu extends React.Component {
this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this);
this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this);
this.getActionText = this.getActionText.bind(this);
- this.canEdit = this.canEdit.bind(this);
// A list of all the context actions in this menu.
this.contextActions = [
@@ -122,10 +110,7 @@ class ReportActionContextMenu extends React.Component {
{
text: this.props.translate('reportActionContextMenu.editComment'),
icon: Pencil,
- shouldShow: () => (
- this.canEdit()
- && !isReportMessageAttachment(this.getActionText())
- ),
+ shouldShow: () => canEditReportAction(this.props.reportAction),
onPress: () => {
this.props.hidePopover();
saveReportActionDraft(
@@ -138,7 +123,7 @@ class ReportActionContextMenu extends React.Component {
{
text: this.props.translate('reportActionContextMenu.deleteComment'),
icon: Trashcan,
- shouldShow: this.canEdit,
+ shouldShow: () => canEditReportAction(this.props.reportAction),
onPress: () => this.setState({isDeleteCommentConfirmModalVisible: true}),
},
];
@@ -160,20 +145,6 @@ class ReportActionContextMenu extends React.Component {
return lodashGet(message, 'text', '');
}
- /**
- * Can the current user edit this report action?
- *
- * @return {Boolean}
- */
- canEdit() {
- // Can only edit if it's a ADDCOMMENT, the author is this user and it's not a optimistic response.
- // If it's an optimistic response comment it will not have a reportActionID,
- // and we should wait until it does before we show the actions
- return this.props.reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT
- && this.props.reportAction.actorEmail === this.props.session.email
- && this.props.reportAction.reportActionID;
- }
-
confirmDeleteAndHideModal() {
deleteReportComment(this.props.reportID, this.props.reportAction);
this.setState({isDeleteCommentConfirmModalVisible: false});
@@ -216,9 +187,4 @@ ReportActionContextMenu.defaultProps = defaultProps;
export default compose(
withLocalize,
- withOnyx({
- session: {
- key: ONYXKEYS.SESSION,
- },
- }),
)(ReportActionContextMenu);