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',