Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update users to focus mode automatically if they have the minimum report number #30664

Merged
merged 41 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
1e8e0ad
Add focus mode upgrade modal
marcaaron Oct 30, 2023
1e9e892
remove unused key fro Onyx
marcaaron Oct 30, 2023
e091586
Free up some reports when switching to focus mode
marcaaron Oct 31, 2023
3e12bb5
remove unneeded line
marcaaron Oct 31, 2023
5b6d499
Get rid of the underscore dangle
marcaaron Oct 31, 2023
8340d36
Use correct variable name
marcaaron Oct 31, 2023
71ca1b9
Simplify logic
marcaaron Oct 31, 2023
b37e92a
Update variable names
marcaaron Oct 31, 2023
2e57969
Adding this solution so there is a reference to it but breaking it ou…
marcaaron Nov 1, 2023
c219a3f
Remove logic to delete unused reports for now
marcaaron Nov 1, 2023
9d70253
Move call to set priority mode into modal
marcaaron Nov 1, 2023
33af895
Rename method. Remove unused logic
marcaaron Nov 1, 2023
901b1ba
Remove policy stuff
marcaaron Nov 1, 2023
3cbd44a
Add missing import
marcaaron Nov 1, 2023
63bf35e
Run prettier
marcaaron Nov 1, 2023
cf78f3e
Update Onyx key name
marcaaron Nov 1, 2023
9b46e8a
Fix up comments
marcaaron Nov 1, 2023
10f2783
Only set NVP if we auto switched
marcaaron Nov 1, 2023
19915b2
Add extra log params. Guard against modal showing on sign out
marcaaron Nov 1, 2023
6196119
Default to hiding the notification
marcaaron Nov 1, 2023
3d8d96d
Pass automatic param. Do not try to switch unless we have a currentUs…
marcaaron Nov 1, 2023
d7e24f1
Merge branch 'main' into marcaaron-setLastOfferedFocusMode
marcaaron Nov 1, 2023
aa4df37
Prettier
marcaaron Nov 1, 2023
c78324a
Need to add spanish translations
marcaaron Nov 1, 2023
b9ef6e3
Add workaround so that we are not waiting for serverDataIsReadyPromis…
marcaaron Nov 1, 2023
00e99ef
Add PriorityMode action
marcaaron Nov 1, 2023
53762c1
Move the things we actually need to wait for into a separate file to …
marcaaron Nov 1, 2023
8125cef
Prettier
marcaaron Nov 1, 2023
c228931
Cant use ?? because it breaks the logic
marcaaron Nov 1, 2023
03205bb
Clear reports on sign out
marcaaron Nov 1, 2023
b87d955
Run prettier
marcaaron Nov 1, 2023
c2f7fd0
prettier
marcaaron Nov 1, 2023
4e6aefb
Remove all logic from Report.js
marcaaron Nov 1, 2023
e52cb60
Add some documentation as the waiting for Onyx logic might be confusing
marcaaron Nov 1, 2023
f696795
Dont break links in comments
marcaaron Nov 1, 2023
d838394
use correct espanol translation
marcaaron Nov 3, 2023
7f25042
Update espanol copy
marcaaron Nov 3, 2023
195c4d6
Fix conflicts
marcaaron Nov 21, 2023
14caac3
Conflicts somehow got in
marcaaron Nov 21, 2023
8140821
Fix typescript issues
marcaaron Nov 21, 2023
eb99338
Run prettier
marcaaron Nov 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ const CONST = {
MAX_REPORT_PREVIEW_RECEIPTS: 3,
},
REPORT: {
MAX_COUNT_BEFORE_FOCUS_UPDATE: 30,
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
MAXIMUM_PARTICIPANTS: 8,
SPLIT_REPORTID: '-2',
ACTIONS: {
Expand Down
10 changes: 10 additions & 0 deletions src/Expensify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
};

Expand All @@ -88,6 +92,7 @@ const defaultProps = {
isSidebarLoaded: false,
screenShareRequest: null,
isCheckingPublicRoom: true,
focusModeNotification: false,
};

const SplashScreenHiddenContext = React.createContext({});
Expand Down Expand Up @@ -221,6 +226,7 @@ function Expensify(props) {
isVisible
/>
) : null}
{props.focusModeNotification ? <FocusModeNotification /> : null}
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
</>
)}

Expand Down Expand Up @@ -261,6 +267,10 @@ export default compose(
screenShareRequest: {
key: ONYXKEYS.SCREEN_SHARE_REQUEST,
},
focusModeNotification: {
key: ONYXKEYS.FOCUS_MODE_NOTIFICATION,
initWithStoredValues: false,
},
}),
)(Expensify);

Expand Down
8 changes: 8 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,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',

Expand Down Expand Up @@ -377,6 +383,8 @@ type OnyxValues = {
[ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf<typeof CONST.PRIORITY_MODE>;
[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<string, string>;
[ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[];
[ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean;
Expand Down
47 changes: 47 additions & 0 deletions src/components/FocusModeNotification.js
Original file line number Diff line number Diff line change
@@ -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 (
<ConfirmModal
title={translate('focusModeUpdateModal.title')}
confirmText={translate('common.buttonConfirm')}
onConfirm={User.clearFocusModeNotification}
shouldShowCancelButton={false}
prompt={
<Text>
{translate('focusModeUpdateModal.prompt')}
<TextLinkWithRef
href={href}
style={styles.link}
onPress={() => {
User.clearFocusModeNotification();
Link.openLink(href, environmentURL);
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
}}
>
{translate('common.here')}
</TextLinkWithRef>
.
</Text>
}
isVisible
/>
);
}

FocusModeNotification.displayName = 'FocusModeNotification';
export default FocusModeNotification;
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.
Expand All @@ -78,7 +31,7 @@ function AnchorRenderer(props) {
return (
<Text
style={styles.link}
onPress={navigateToLink}
onPress={() => Link.openLink(attrHref, environmentURL, isAttachment)}
suppressHighlighting
>
<TNodeChildrenRenderer tnode={props.tnode} />
Expand Down Expand Up @@ -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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App crashes when open internal link. Link.openLink requires 3 params as above.
Here's PR fixing this - #32553

>
<TNodeChildrenRenderer tnode={props.tnode} />
</AnchorForCommentsOnly>
Expand Down
4 changes: 3 additions & 1 deletion src/components/TextLink.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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];
Expand All @@ -51,7 +53,7 @@ function TextLink(props) {
return;
}

Link.openExternalLink(props.href);
Link.openLink(props.href, environmentURL);
};

/**
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,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',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,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í',
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -194,6 +195,8 @@ function AuthScreens({isUsingMemoryOnlyKeys, lastUpdateIDAppliedToClient, sessio
App.reconnectApp(lastUpdateIDAppliedToClient);
}

PriorityMode.autoSwitchToFocusMode();

App.setUpPoliciesAndNavigate(session);

App.redirectThirdPartyDesktopSignIn();
Expand Down
60 changes: 59 additions & 1 deletion src/libs/actions/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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};
Loading
Loading