diff --git a/src/CONST.ts b/src/CONST.ts index 3d69c83c5c22..283195562e49 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -483,6 +483,7 @@ const CONST = { MAX_REPORT_PREVIEW_RECEIPTS: 3, }, REPORT: { + MAX_COUNT_BEFORE_FOCUS_UPDATE: 30, MAXIMUM_PARTICIPANTS: 8, SPLIT_REPORTID: '-2', ACTIONS: { diff --git a/src/Expensify.js b/src/Expensify.js index 1b692f86a197..aece93c0ff4d 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -7,6 +7,7 @@ import _ from 'underscore'; import ConfirmModal from './components/ConfirmModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; +import FocusModeNotification from './components/FocusModeNotification'; import GrowlNotification from './components/GrowlNotification'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import SplashScreenHider from './components/SplashScreenHider'; @@ -76,6 +77,9 @@ const propTypes = { /** Whether the app is waiting for the server's response to determine if a room is public */ isCheckingPublicRoom: PropTypes.bool, + /** Whether we should display the notification alerting the user that focus mode has been auto-enabled */ + focusModeNotification: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -88,6 +92,7 @@ const defaultProps = { isSidebarLoaded: false, screenShareRequest: null, isCheckingPublicRoom: true, + focusModeNotification: false, }; const SplashScreenHiddenContext = React.createContext({}); @@ -221,6 +226,7 @@ function Expensify(props) { isVisible /> ) : null} + {props.focusModeNotification ? : null} )} @@ -261,6 +267,10 @@ export default compose( screenShareRequest: { key: ONYXKEYS.SCREEN_SHARE_REQUEST, }, + focusModeNotification: { + key: ONYXKEYS.FOCUS_MODE_NOTIFICATION, + initWithStoredValues: false, + }, }), )(Expensify); diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a983ec5acba5..97f24a2710aa 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -152,6 +152,12 @@ const ONYXKEYS = { /** The user's cash card and imported cards (including the Expensify Card) */ CARD_LIST: 'cardList', + /** Whether the user has tried focus mode yet */ + NVP_TRY_FOCUS_MODE: 'tryFocusMode', + + /** Boolean flag used to display the focus mode notification */ + FOCUS_MODE_NOTIFICATION: 'focusModeNotification', + /** Stores information about the user's saved statements */ WALLET_STATEMENT: 'walletStatement', @@ -383,6 +389,8 @@ type OnyxValues = { [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string; + [ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean; + [ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record; [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean; diff --git a/src/components/FocusModeNotification.js b/src/components/FocusModeNotification.js new file mode 100644 index 000000000000..37d8e4848b98 --- /dev/null +++ b/src/components/FocusModeNotification.js @@ -0,0 +1,47 @@ +import React, {useEffect} from 'react'; +import useEnvironment from '@hooks/useEnvironment'; +import useLocalize from '@hooks/useLocalize'; +import styles from '@styles/styles'; +import * as Link from '@userActions/Link'; +import * as User from '@userActions/User'; +import CONST from '@src/CONST'; +import ConfirmModal from './ConfirmModal'; +import Text from './Text'; +import TextLinkWithRef from './TextLink'; + +function FocusModeNotification() { + const {environmentURL} = useEnvironment(); + const {translate} = useLocalize(); + useEffect(() => { + User.updateChatPriorityMode(CONST.PRIORITY_MODE.GSD, true); + }, []); + const href = `${environmentURL}/settings/preferences/priority-mode`; + return ( + + {translate('focusModeUpdateModal.prompt')} + { + User.clearFocusModeNotification(); + Link.openLink(href, environmentURL); + }} + > + {translate('common.here')} + + . + + } + isVisible + /> + ); +} + +FocusModeNotification.displayName = 'FocusModeNotification'; +export default FocusModeNotification; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index 63973ea43e19..8f1406439be9 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -6,15 +6,10 @@ import AnchorForCommentsOnly from '@components/AnchorForCommentsOnly'; import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils'; import Text from '@components/Text'; import useEnvironment from '@hooks/useEnvironment'; -import Navigation from '@libs/Navigation/Navigation'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; -import * as Url from '@libs/Url'; import useThemeStyles from '@styles/useThemeStyles'; import * as Link from '@userActions/Link'; -import * as Session from '@userActions/Session'; -import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import htmlRendererPropTypes from './htmlRendererPropTypes'; function AnchorRenderer(props) { @@ -26,50 +21,8 @@ function AnchorRenderer(props) { const displayName = lodashGet(props.tnode, 'domNode.children[0].data', ''); const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {}); const attrHref = htmlAttribs.href || ''; - const attrPath = Url.getPathFromURL(attrHref); - const hasSameOrigin = Url.hasSameExpensifyOrigin(attrHref, environmentURL); - const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.STAGING_API_ROOT); - const internalNewExpensifyPath = - (Url.hasSameExpensifyOrigin(attrHref, CONST.NEW_EXPENSIFY_URL) || - Url.hasSameExpensifyOrigin(attrHref, CONST.STAGING_NEW_EXPENSIFY_URL) || - attrHref.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) && - !CONST.PATHS_TO_TREAT_AS_EXTERNAL.includes(attrPath) - ? attrPath - : ''; - const internalExpensifyPath = - hasExpensifyOrigin && !attrPath.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL_PATHNAME) && !attrPath.startsWith(CONFIG.EXPENSIFY.DEVPORTAL_URL_PATHNAME) && attrPath; - const navigateToLink = () => { - // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: - // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, - // clicking on the link will open the chat in NewDot. However, when a user is in NewDot and clicks on the concierge link, the link needs to be handled differently. - // Normally, the link would be sent to Link.openOldDotLink() and opened in a new tab, and that's jarring to the user. Since the intention is to link to a specific NewDot chat, - // the reportID is extracted from the URL and then opened as an internal link, taking the user straight to the chat in the same tab. - if (hasExpensifyOrigin && attrHref.indexOf('newdotreport?reportID=') > -1) { - const reportID = attrHref.split('newdotreport?reportID=').pop(); - const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID); - Navigation.navigate(reportRoute); - return; - } - - // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation - // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) - if (internalNewExpensifyPath && hasSameOrigin) { - if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(internalNewExpensifyPath)) { - Session.signOutAndRedirectToSignIn(); - return; - } - Navigation.navigate(internalNewExpensifyPath); - return; - } - - // If we are handling an old dot Expensify link we need to open it with openOldDotLink() so we can navigate to it with the user already logged in. - // As attachments also use expensify.com we don't want it working the same as links. - if (internalExpensifyPath && !isAttachment) { - Link.openOldDotLink(internalExpensifyPath); - return; - } - Link.openExternalLink(attrHref); - }; + const internalNewExpensifyPath = Link.getInternalNewExpensifyPath(attrHref); + const internalExpensifyPath = Link.getInternalExpensifyPath(attrHref); if (!HTMLEngineUtils.isChildOfComment(props.tnode)) { // This is not a comment from a chat, the AnchorForCommentsOnly uses a Pressable to create a context menu on right click. @@ -78,7 +31,7 @@ function AnchorRenderer(props) { return ( Link.openLink(attrHref, environmentURL, isAttachment)} suppressHighlighting > @@ -109,7 +62,7 @@ function AnchorRenderer(props) { key={props.key} displayName={displayName} // Only pass the press handler for internal links. For public links or whitelisted internal links fallback to default link handling - onPress={internalNewExpensifyPath || internalExpensifyPath ? navigateToLink : undefined} + onPress={internalNewExpensifyPath || internalExpensifyPath ? Link.openLink : undefined} > diff --git a/src/components/TextLink.js b/src/components/TextLink.js index ab59f6f8b121..46c074eb79e6 100644 --- a/src/components/TextLink.js +++ b/src/components/TextLink.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import _ from 'underscore'; +import useEnvironment from '@hooks/useEnvironment'; import stylePropTypes from '@styles/stylePropTypes'; import useThemeStyles from '@styles/useThemeStyles'; import * as Link from '@userActions/Link'; @@ -37,6 +38,7 @@ const defaultProps = { }; function TextLink(props) { + const {environmentURL} = useEnvironment(); const styles = useThemeStyles(); const rest = _.omit(props, _.keys(propTypes)); const additionalStyles = _.isArray(props.style) ? props.style : [props.style]; @@ -51,7 +53,7 @@ function TextLink(props) { return; } - Link.openExternalLink(props.href); + Link.openLink(props.href, environmentURL); }; /** diff --git a/src/languages/en.ts b/src/languages/en.ts index b03e5d228a55..817f06f6b344 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1133,6 +1133,10 @@ export default { year: 'Year', selectYear: 'Please select a year', }, + focusModeUpdateModal: { + title: 'Welcome to #focus mode!', + prompt: "Read chats will be hidden, unless they have a green dot, which means there's an action you need to take on them. You can change this in your account settings ", + }, notFound: { chatYouLookingForCannotBeFound: 'The chat you are looking for cannot be found.', getMeOutOfHere: 'Get me out of here', diff --git a/src/languages/es.ts b/src/languages/es.ts index c6bc77681b21..b219021daa0f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1133,6 +1133,10 @@ export default { year: 'Año', selectYear: 'Por favor, selecciona un año', }, + focusModeUpdateModal: { + title: '¡Bienvenido al modo #concentración!', + prompt: 'Los mensajes leídos se ocultarán, a menos que tengan un punto verde, lo que significa que tienes que tomar una acción en ellos. Puedes cambiar esto en la configuración de tu cuenta ', + }, notFound: { chatYouLookingForCannotBeFound: 'El chat que estás buscando no se pudo encontrar.', getMeOutOfHere: 'Sácame de aquí', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 4c610bc12099..de334635da54 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -20,6 +20,7 @@ import * as App from '@userActions/App'; import * as Download from '@userActions/Download'; import * as Modal from '@userActions/Modal'; import * as PersonalDetails from '@userActions/PersonalDetails'; +import * as PriorityMode from '@userActions/PriorityMode'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import Timing from '@userActions/Timing'; @@ -194,6 +195,8 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio App.reconnectApp(lastUpdateIDAppliedToClient); } + PriorityMode.autoSwitchToFocusMode(); + App.setUpPoliciesAndNavigate(session); App.redirectThirdPartyDesktopSignIn(); diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index d741ced6dc08..5ee15c1cd1d0 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -2,8 +2,12 @@ import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import asyncOpenURL from '@libs/asyncOpenURL'; import * as Environment from '@libs/Environment/Environment'; +import Navigation from '@libs/Navigation/Navigation'; import * as Url from '@libs/Url'; +import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; let isNetworkOffline = false; Onyx.connect({ @@ -56,4 +60,58 @@ function openOldDotLink(url: string) { (oldDotURL) => oldDotURL, ); } -export {buildOldDotURL, openOldDotLink, openExternalLink}; + +function getInternalNewExpensifyPath(href: string) { + const attrPath = Url.getPathFromURL(href); + return (Url.hasSameExpensifyOrigin(href, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONST.STAGING_NEW_EXPENSIFY_URL) || href.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) && + !CONST.PATHS_TO_TREAT_AS_EXTERNAL.find((path) => path === attrPath) + ? attrPath + : ''; +} + +function getInternalExpensifyPath(href: string) { + const attrPath = Url.getPathFromURL(href); + const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.STAGING_API_ROOT); + if (!hasExpensifyOrigin || attrPath.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL_PATHNAME) || attrPath.startsWith(CONFIG.EXPENSIFY.DEVPORTAL_URL_PATHNAME)) { + return ''; + } + + return attrPath; +} + +function openLink(href: string, environmentURL: string, isAttachment = false) { + const hasSameOrigin = Url.hasSameExpensifyOrigin(href, environmentURL); + const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.STAGING_API_ROOT); + const internalNewExpensifyPath = getInternalNewExpensifyPath(href); + const internalExpensifyPath = getInternalExpensifyPath(href); + + // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: + // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, + // clicking on the link will open the chat in NewDot. However, when a user is in NewDot and clicks on the concierge link, the link needs to be handled differently. + // Normally, the link would be sent to Link.openOldDotLink() and opened in a new tab, and that's jarring to the user. Since the intention is to link to a specific NewDot chat, + // the reportID is extracted from the URL and then opened as an internal link, taking the user straight to the chat in the same tab. + if (hasExpensifyOrigin && href.indexOf('newdotreport?reportID=') > -1) { + const reportID = href.split('newdotreport?reportID=').pop(); + const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID ?? ''); + Navigation.navigate(reportRoute); + return; + } + + // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation + // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) + if (internalNewExpensifyPath && hasSameOrigin) { + Navigation.navigate(internalNewExpensifyPath); + return; + } + + // If we are handling an old dot Expensify link we need to open it with openOldDotLink() so we can navigate to it with the user already logged in. + // As attachments also use expensify.com we don't want it working the same as links. + if (internalExpensifyPath && !isAttachment) { + openOldDotLink(internalExpensifyPath); + return; + } + + openExternalLink(href); +} + +export {buildOldDotURL, openOldDotLink, openExternalLink, openLink, getInternalNewExpensifyPath, getInternalExpensifyPath}; diff --git a/src/libs/actions/PriorityMode.ts b/src/libs/actions/PriorityMode.ts new file mode 100644 index 000000000000..d528c31d7453 --- /dev/null +++ b/src/libs/actions/PriorityMode.ts @@ -0,0 +1,138 @@ +import debounce from 'lodash/debounce'; +import Onyx, {OnyxCollection} from 'react-native-onyx'; +import * as CollectionUtils from '@libs/CollectionUtils'; +import Log from '@libs/Log'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {Report} from '@src/types/onyx'; + +/** + * This actions file is used to automatically switch a user into #focus mode when they exceed a certain number of reports. We do this primarily for performance reasons. + * Similar to the "Welcome action" we must wait for a number of things to happen when the user signs in or refreshes the page: + * + * - NVP that tracks whether they have already been switched over. We only do this once. + * - Priority mode NVP (that dictates the ordering/filtering logic of the LHN) + * - Reports to load (in ReconnectApp or OpenApp). As we check the count of the reports to determine whether the user is eligible to be automatically switched. + */ + +let resolveIsReadyPromise: (args?: unknown[]) => void; +let isReadyPromise = new Promise((resolve) => { + resolveIsReadyPromise = resolve; +}); + +let currentUserAccountID: number | undefined | null; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (val) => { + currentUserAccountID = val?.accountID; + }, +}); + +/** + * Debounce the prompt to promote focus mode as many reports updates could happen in a short burst + */ +// eslint-disable-next-line @typescript-eslint/no-use-before-define +const autoSwitchToFocusMode = debounce(tryFocusModeUpdate, 300, {leading: true}); + +let allReports: OnyxCollection | undefined; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + callback: (report, key) => { + if (!key || !report) { + return; + } + + if (!allReports) { + allReports = {}; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + + allReports[reportID] = report; + + // Each time a new report is added we will check to see if the user should be switched + autoSwitchToFocusMode(); + }, +}); + +let isLoadingReportData = true; +Onyx.connect({ + key: ONYXKEYS.IS_LOADING_REPORT_DATA, + initWithStoredValues: false, + callback: (value) => { + isLoadingReportData = value ?? false; + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + checkRequiredData(); + }, +}); + +let isInFocusMode: boolean | undefined; +Onyx.connect({ + key: ONYXKEYS.NVP_PRIORITY_MODE, + callback: (priorityMode) => { + isInFocusMode = priorityMode === CONST.PRIORITY_MODE.GSD; + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + checkRequiredData(); + }, +}); + +let hasTriedFocusMode: boolean | undefined | null; +Onyx.connect({ + key: ONYXKEYS.NVP_TRY_FOCUS_MODE, + callback: (val) => { + hasTriedFocusMode = val; + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + checkRequiredData(); + }, +}); + +function resetHasReadRequiredDataFromStorage() { + // Create a new promise and a new resolve function + isReadyPromise = new Promise((resolve) => { + resolveIsReadyPromise = resolve; + }); + isLoadingReportData = true; + allReports = {}; +} + +function checkRequiredData() { + if (allReports === undefined || hasTriedFocusMode === undefined || isInFocusMode === undefined || isLoadingReportData) { + return; + } + + resolveIsReadyPromise(); +} + +function tryFocusModeUpdate() { + isReadyPromise.then(() => { + // User is signed out so do not try to switch them + if (!currentUserAccountID) { + return; + } + + // Check to see if the user is using #focus mode, has tried it before, or we have already switched them over automatically. + if ((isInFocusMode ?? false) || hasTriedFocusMode) { + Log.info('Not switching user to optimized focus mode.', false, {isInFocusMode, hasTriedFocusMode}); + return; + } + + const reportCount = Object.keys(allReports ?? {}).length; + if (reportCount < CONST.REPORT.MAX_COUNT_BEFORE_FOCUS_UPDATE) { + Log.info('Not switching user to optimized focus mode as they do not have enough reports', false, {reportCount}); + return; + } + + Log.info('Switching user to optimized focus mode', false, {reportCount, hasTriedFocusMode, isInFocusMode}); + + // Record that we automatically switched them so we don't ask again. + hasTriedFocusMode = true; + + // Setting this triggers a modal to open and notify the user. + Onyx.set(ONYXKEYS.FOCUS_MODE_NOTIFICATION, true); + }); +} + +export {resetHasReadRequiredDataFromStorage, autoSwitchToFocusMode}; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 559f60a21c98..82b51651cacc 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -15,6 +15,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import Timers from '@libs/Timers'; import {hideContextMenu} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import * as Device from '@userActions/Device'; +import * as PriorityMode from '@userActions/PriorityMode'; import redirectToSignIn from '@userActions/SignInRedirect'; import Timing from '@userActions/Timing'; import * as Welcome from '@userActions/Welcome'; @@ -589,6 +590,7 @@ function cleanupSession() { Pusher.disconnect(); Timers.clearAll(); Welcome.resetReadyCheck(); + PriorityMode.resetHasReadRequiredDataFromStorage(); } function clearAccountMessages() { diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index ad6fd7be10dd..1bd1cb0b763b 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -583,8 +583,10 @@ function updateFrequentlyUsedEmojis(frequentlyUsedEmojis) { /** * Sync user chat priority mode with Onyx and Server * @param {String} mode + * @param {boolean} [automatic] if we changed the mode automatically */ -function updateChatPriorityMode(mode) { +function updateChatPriorityMode(mode, automatic = false) { + const autoSwitchedToFocusMode = mode === CONST.PRIORITY_MODE.GSD && automatic; const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -592,14 +594,31 @@ function updateChatPriorityMode(mode) { value: mode, }, ]; + + if (autoSwitchedToFocusMode) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_TRY_FOCUS_MODE, + value: true, + }); + } + API.write( 'UpdateChatPriorityMode', { value: mode, + automatic, }, {optimisticData}, ); - Navigation.goBack(ROUTES.SETTINGS_PREFERENCES); + + if (!autoSwitchedToFocusMode) { + Navigation.goBack(ROUTES.SETTINGS_PREFERENCES); + } +} + +function clearFocusModeNotification() { + Onyx.set(ONYXKEYS.FOCUS_MODE_NOTIFICATION, false); } /** @@ -843,6 +862,7 @@ function clearDraftCustomStatus() { } export { + clearFocusModeNotification, closeAccount, resendValidateCode, requestContactMethodValidateCode,