diff --git a/assets/images/bell.svg b/assets/images/bell.svg
new file mode 100644
index 000000000000..6ba600dc695b
--- /dev/null
+++ b/assets/images/bell.svg
@@ -0,0 +1,6 @@
+
+
+
diff --git a/assets/images/bellSlash.svg b/assets/images/bellSlash.svg
new file mode 100644
index 000000000000..488acc4de05e
--- /dev/null
+++ b/assets/images/bellSlash.svg
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/components/Icon/Expensicons.js b/src/components/Icon/Expensicons.js
index dd106c6b3c20..e15db475597d 100644
--- a/src/components/Icon/Expensicons.js
+++ b/src/components/Icon/Expensicons.js
@@ -9,6 +9,8 @@ import ArrowRightLong from '../../../assets/images/arrow-right-long.svg';
import ArrowsUpDown from '../../../assets/images/arrows-updown.svg';
import BackArrow from '../../../assets/images/back-left.svg';
import Bank from '../../../assets/images/bank.svg';
+import Bell from '../../../assets/images/bell.svg';
+import BellSlash from '../../../assets/images/bellSlash.svg';
import Bill from '../../../assets/images/bill.svg';
import Bolt from '../../../assets/images/bolt.svg';
import Briefcase from '../../../assets/images/briefcase.svg';
@@ -140,6 +142,8 @@ export {
BackArrow,
Bank,
Bill,
+ Bell,
+ BellSlash,
Bolt,
Briefcase,
Bug,
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 0d59e6321bbf..1251e44cfac5 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -423,6 +423,8 @@ export default {
deleteConfirmation: ({action}: DeleteConfirmationParams) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`,
onlyVisible: 'Only visible to',
replyInThread: 'Reply in thread',
+ subscribeToThread: 'Subscribe to thread',
+ unsubscribeFromThread: 'Unsubscribe from thread',
flagAsOffensive: 'Flag as offensive',
},
emojiReactions: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 6a524a4a760f..537d135f41de 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -414,6 +414,8 @@ export default {
deleteConfirmation: ({action}: DeleteConfirmationParams) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`,
onlyVisible: 'Visible sólo para',
replyInThread: 'Responder en el hilo',
+ subscribeToThread: 'Suscribirse al hilo',
+ unsubscribeFromThread: 'Darse de baja del hilo',
flagAsOffensive: 'Marcar como ofensivo',
},
emojiReactions: {
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index eb558e7ac066..2dccc68b74e0 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -783,6 +783,16 @@ function getReport(reportID) {
return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {}) || {};
}
+/**
+ * Returns whether or not the author of the action is this user
+ *
+ * @param {Object} reportAction
+ * @returns {Boolean}
+ */
+function isActionCreator(reportAction) {
+ return reportAction.actorAccountID === currentUserAccountID;
+}
+
/**
* Can only delete if the author is this user and the action is an ADDCOMMENT action or an IOU action in an unsettled report, or if the user is a
* policy admin
@@ -4121,6 +4131,7 @@ export {
canEditReportAction,
canFlagReportAction,
shouldShowFlagComment,
+ isActionCreator,
canDeleteReportAction,
canLeaveRoom,
sortReportsByLastRead,
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index fcef11132283..46994079d605 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -1360,8 +1360,10 @@ function saveReportActionDraftNumberOfLines(reportID, reportActionID, numberOfLi
* @param {String} previousValue
* @param {String} newValue
* @param {boolean} navigate
+ * @param {String} parentReportID
+ * @param {String} parentReportActionID
*/
-function updateNotificationPreference(reportID, previousValue, newValue, navigate) {
+function updateNotificationPreference(reportID, previousValue, newValue, navigate, parentReportID = 0, parentReportActionID = 0) {
if (previousValue === newValue) {
if (navigate) {
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID));
@@ -1382,12 +1384,68 @@ function updateNotificationPreference(reportID, previousValue, newValue, navigat
value: {notificationPreference: previousValue},
},
];
+ if (parentReportID && parentReportActionID) {
+ optimisticData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
+ value: {[parentReportActionID]: {childReportNotificationPreference: newValue}},
+ });
+ failureData.push({
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
+ value: {[parentReportActionID]: {childReportNotificationPreference: previousValue}},
+ });
+ }
API.write('UpdateReportNotificationPreference', {reportID, notificationPreference: newValue}, {optimisticData, failureData});
if (navigate) {
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(reportID));
}
}
+/**
+ * This will subscribe to an existing thread, or create a new one and then subsribe to it if necessary
+ *
+ * @param {String} childReportID The reportID we are trying to open
+ * @param {Object} parentReportAction the parent comment of a thread
+ * @param {String} parentReportID The reportID of the parent
+ * @param {String} prevNotificationPreference The previous notification preference for the child report
+ *
+ */
+function toggleSubscribeToChildReport(childReportID = '0', parentReportAction = {}, parentReportID = '0', prevNotificationPreference) {
+ if (childReportID !== '0') {
+ openReport(childReportID);
+ const parentReportActionID = lodashGet(parentReportAction, 'reportActionID', '0');
+ if (!prevNotificationPreference || prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) {
+ updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, parentReportID, parentReportActionID);
+ } else {
+ updateNotificationPreference(childReportID, prevNotificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, false, parentReportID, parentReportActionID);
+ }
+ } else {
+ const participantAccountIDs = _.uniq([currentUserAccountID, Number(parentReportAction.actorAccountID)]);
+ const parentReport = allReports[parentReportID];
+ const newChat = ReportUtils.buildOptimisticChatReport(
+ participantAccountIDs,
+ lodashGet(parentReportAction, ['message', 0, 'text']),
+ lodashGet(parentReport, 'chatType', ''),
+ lodashGet(parentReport, 'policyID', CONST.POLICY.OWNER_EMAIL_FAKE),
+ CONST.POLICY.OWNER_ACCOUNT_ID_FAKE,
+ false,
+ '',
+ undefined,
+ undefined,
+ CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
+ parentReportAction.reportActionID,
+ parentReportID,
+ );
+
+ const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(newChat.participantAccountIDs);
+ openReport(newChat.reportID, participantLogins, newChat, parentReportAction.reportActionID);
+ const notificationPreference =
+ prevNotificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ updateNotificationPreference(newChat.reportID, prevNotificationPreference, notificationPreference, false, parentReportID, parentReportAction.reportActionID);
+ }
+}
+
/**
* @param {String} reportID
* @param {String} previousValue
@@ -2467,6 +2525,7 @@ export {
navigateToAndOpenReport,
navigateToAndOpenReportWithAccountIDs,
navigateToAndOpenChildReport,
+ toggleSubscribeToChildReport,
updatePolicyRoomNameAndNavigate,
clearPolicyRoomNameErrors,
clearIOUError,
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js
index ec2f08df502d..4e7118212b49 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.js
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js
@@ -145,6 +145,83 @@ export default [
},
getDescription: () => {},
},
+ {
+ isAnonymousAction: false,
+ textTranslateKey: 'reportActionContextMenu.subscribeToThread',
+ icon: Expensicons.Bell,
+ successTextTranslateKey: '',
+ successIcon: null,
+ shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => {
+ let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
+ if (!childReportNotificationPreference) {
+ const isActionCreator = ReportUtils.isActionCreator(reportAction);
+ childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ }
+ const subscribed = childReportNotificationPreference !== 'hidden';
+ const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID);
+ const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW;
+ const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction);
+ return !subscribed && (isCommentAction || isReportPreviewAction || isIOUAction);
+ },
+ onPress: (closePopover, {reportAction, reportID}) => {
+ let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
+ if (!childReportNotificationPreference) {
+ const isActionCreator = ReportUtils.isActionCreator(reportAction);
+ childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ }
+ if (closePopover) {
+ hideContextMenu(false, () => {
+ ReportActionComposeFocusManager.focus();
+ Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference);
+ });
+ return;
+ }
+
+ ReportActionComposeFocusManager.focus();
+ Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference);
+ },
+ getDescription: () => {},
+ },
+ {
+ isAnonymousAction: false,
+ textTranslateKey: 'reportActionContextMenu.unsubscribeFromThread',
+ icon: Expensicons.BellSlash,
+ successTextTranslateKey: '',
+ successIcon: null,
+ shouldShow: (type, reportAction, isArchivedRoom, betas, anchor, isChronosReport, reportID) => {
+ let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
+ if (!childReportNotificationPreference) {
+ const isActionCreator = ReportUtils.isActionCreator(reportAction);
+ childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ }
+ const subscribed = childReportNotificationPreference !== 'hidden';
+ if (type !== CONTEXT_MENU_TYPES.REPORT_ACTION) {
+ return false;
+ }
+ const isCommentAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !ReportUtils.isThreadFirstChat(reportAction, reportID);
+ const isReportPreviewAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW;
+ const isIOUAction = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && !ReportActionsUtils.isSplitBillAction(reportAction);
+ return subscribed && (isCommentAction || isReportPreviewAction || isIOUAction);
+ },
+ onPress: (closePopover, {reportAction, reportID}) => {
+ let childReportNotificationPreference = lodashGet(reportAction, 'childReportNotificationPreference', '');
+ if (!childReportNotificationPreference) {
+ const isActionCreator = ReportUtils.isActionCreator(reportAction);
+ childReportNotificationPreference = isActionCreator ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ }
+ if (closePopover) {
+ hideContextMenu(false, () => {
+ ReportActionComposeFocusManager.focus();
+ Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference);
+ });
+ return;
+ }
+
+ ReportActionComposeFocusManager.focus();
+ Report.toggleSubscribeToChildReport(lodashGet(reportAction, 'childReportID', '0'), reportAction, reportID, childReportNotificationPreference);
+ },
+ getDescription: () => {},
+ },
{
isAnonymousAction: true,
textTranslateKey: 'reportActionContextMenu.copyURLToClipboard',