Skip to content

Commit 7388329

Browse files
authored
Merge pull request Expensify#61913 from Expensify/arosiclair-auto-priority-mode-fix
Fix flakey #focus mode auto switching
2 parents fd5a995 + 2204ebe commit 7388329

9 files changed

Lines changed: 110 additions & 197 deletions

File tree

src/Expensify.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Onyx, {useOnyx} from 'react-native-onyx';
77
import ConfirmModal from './components/ConfirmModal';
88
import DeeplinkWrapper from './components/DeeplinkWrapper';
99
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
10-
import FocusModeNotification from './components/FocusModeNotification';
1110
import GrowlNotification from './components/GrowlNotification';
1211
import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper';
1312
import SplashScreenHider from './components/SplashScreenHider';
@@ -81,9 +80,6 @@ type ExpensifyProps = {
8180
/** True when the user must update to the latest minimum version of the app */
8281
updateRequired: OnyxEntry<boolean>;
8382

84-
/** Whether we should display the notification alerting the user that focus mode has been auto-enabled */
85-
focusModeNotification: OnyxEntry<boolean>;
86-
8783
/** Last visited path in the app */
8884
lastVisitedPath: OnyxEntry<string | undefined>;
8985
};
@@ -103,7 +99,6 @@ function Expensify() {
10399
const [updateRequired] = useOnyx(ONYXKEYS.UPDATE_REQUIRED, {initWithStoredValues: false, canBeMissing: true});
104100
const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {canBeMissing: true});
105101
const [screenShareRequest] = useOnyx(ONYXKEYS.SCREEN_SHARE_REQUEST, {canBeMissing: true});
106-
const [focusModeNotification] = useOnyx(ONYXKEYS.FOCUS_MODE_NOTIFICATION, {initWithStoredValues: false, canBeMissing: true});
107102
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true});
108103

109104
useDebugShortcut();
@@ -183,7 +178,6 @@ function Expensify() {
183178
updateAvailable,
184179
isSidebarLoaded,
185180
screenShareRequest,
186-
focusModeNotification,
187181
isAuthenticated,
188182
lastVisitedPath,
189183
};
@@ -292,7 +286,6 @@ function Expensify() {
292286
isVisible
293287
/>
294288
) : null}
295-
{focusModeNotification ? <FocusModeNotification /> : null}
296289
</>
297290
)}
298291

src/ONYXKEYS.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,6 @@ const ONYXKEYS = {
275275
/** The user's cash card and imported cards (including the Expensify Card) */
276276
CARD_LIST: 'cardList',
277277

278-
/** Boolean flag used to display the focus mode notification */
279-
FOCUS_MODE_NOTIFICATION: 'focusModeNotification',
280-
281278
/** Stores information about the user's saved statements */
282279
WALLET_STATEMENT: 'walletStatement',
283280

@@ -1053,7 +1050,6 @@ type OnyxValuesMapping = {
10531050
[ONYXKEYS.NVP_RECENT_ATTENDEES]: Attendee[];
10541051
[ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean;
10551052
[ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean;
1056-
[ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean;
10571053
[ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: OnyxTypes.LastPaymentMethod;
10581054
[ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT]: string;
10591055
[ONYXKEYS.LAST_EXPORT_METHOD]: OnyxTypes.LastExportMethod;

src/components/FocusModeNotification.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,41 @@
1-
import React, {useEffect} from 'react';
1+
import React from 'react';
22
import useEnvironment from '@hooks/useEnvironment';
33
import useLocalize from '@hooks/useLocalize';
44
import useStyleUtils from '@hooks/useStyleUtils';
55
import useThemeStyles from '@hooks/useThemeStyles';
66
import {openLink} from '@libs/actions/Link';
7-
import {clearFocusModeNotification, updateChatPriorityMode} from '@libs/actions/User';
87
import colors from '@styles/theme/colors';
9-
import CONST from '@src/CONST';
108
import ConfirmModal from './ConfirmModal';
119
import {ThreeLeggedLaptopWoman} from './Icon/Illustrations';
1210
import Text from './Text';
1311
import TextLinkWithRef from './TextLink';
1412

15-
function FocusModeNotification() {
13+
type FocusModeNotificationProps = {
14+
onClose: () => void;
15+
};
16+
17+
function FocusModeNotification({onClose}: FocusModeNotificationProps) {
1618
const styles = useThemeStyles();
1719
const StyleUtils = useStyleUtils();
1820
const {environmentURL} = useEnvironment();
1921
const {translate} = useLocalize();
20-
useEffect(() => {
21-
updateChatPriorityMode(CONST.PRIORITY_MODE.GSD, true);
22-
}, []);
2322
const href = `${environmentURL}/settings/preferences/priority-mode`;
23+
2424
return (
2525
<ConfirmModal
2626
title={translate('focusModeUpdateModal.title')}
2727
confirmText={translate('common.buttonConfirm')}
28-
onConfirm={clearFocusModeNotification}
28+
onConfirm={onClose}
2929
shouldShowCancelButton={false}
30-
onBackdropPress={clearFocusModeNotification}
31-
onCancel={clearFocusModeNotification}
30+
onBackdropPress={onClose}
31+
onCancel={onClose}
3232
prompt={
3333
<Text>
3434
{translate('focusModeUpdateModal.prompt')}
3535
<TextLinkWithRef
3636
style={styles.link}
3737
onPress={() => {
38-
clearFocusModeNotification();
38+
onClose();
3939
openLink(href, environmentURL);
4040
}}
4141
>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {useNavigation} from '@react-navigation/native';
2+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
3+
import {useOnyx} from 'react-native-onyx';
4+
import {updateChatPriorityMode} from '@libs/actions/User';
5+
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
6+
import Log from '@libs/Log';
7+
import navigationRef from '@libs/Navigation/navigationRef';
8+
import {isReportParticipant, isValidReport} from '@libs/ReportUtils';
9+
import CONST from '@src/CONST';
10+
import ONYXKEYS from '@src/ONYXKEYS';
11+
import SCREENS from '@src/SCREENS';
12+
import FocusModeNotification from './FocusModeNotification';
13+
14+
/**
15+
* This component is used to automatically switch a user into #focus mode when they exceed a certain number of reports.
16+
* We do this primarily for performance reasons. Similar to the "Welcome action" we must wait for a number of things to
17+
* happen when the user signs in or refreshes the page:
18+
*
19+
* - NVP that tracks whether they have already been switched over. We only do this once.
20+
* - Priority mode NVP (that dictates the ordering/filtering logic of the LHN)
21+
* - Reports to load (in ReconnectApp or OpenApp). As we check the count of the reports to determine whether the
22+
* user is eligible to be automatically switched.
23+
*
24+
*/
25+
export default function PriorityModeController() {
26+
const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID, canBeMissing: true});
27+
const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {canBeMissing: true});
28+
const [isInFocusMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {selector: (priorityMode) => priorityMode === CONST.PRIORITY_MODE.GSD, canBeMissing: true});
29+
const [hasTriedFocusMode] = useOnyx(ONYXKEYS.NVP_TRY_FOCUS_MODE, {canBeMissing: true});
30+
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
31+
const currentRouteName = useCurrentRouteName();
32+
const [shouldShowModal, setShouldShowModal] = useState(false);
33+
const closeModal = useCallback(() => setShouldShowModal(false), []);
34+
35+
const validReportCount = useMemo(() => {
36+
let count = 0;
37+
Object.values(allReports ?? {}).forEach((report) => {
38+
if (!isValidReport(report) || !isReportParticipant(accountID ?? CONST.DEFAULT_NUMBER_ID, report)) {
39+
return;
40+
}
41+
42+
count++;
43+
});
44+
return count;
45+
}, [accountID, allReports]);
46+
47+
// We set this when we have finally auto-switched the user of #focus mode to prevent duplication.
48+
const hasSwitched = useRef(false);
49+
50+
// Listen for state changes and trigger the #focus mode when appropriate
51+
useEffect(() => {
52+
// Wait for Onyx state to fully load
53+
if (isLoadingReportData !== false || isInFocusMode === undefined || hasTriedFocusMode === undefined || !accountID) {
54+
return;
55+
}
56+
57+
if (hasSwitched.current || isInFocusMode || hasTriedFocusMode) {
58+
return;
59+
}
60+
61+
if (validReportCount < CONST.REPORT.MAX_COUNT_BEFORE_FOCUS_UPDATE) {
62+
Log.info('[PriorityModeController] Not switching user to focus mode as they do not have enough reports', false, {validReportCount});
63+
return;
64+
}
65+
66+
// We wait for the user to navigate back to the home screen before triggering this switch
67+
const isNarrowLayout = getIsNarrowLayout();
68+
if ((isNarrowLayout && currentRouteName !== SCREENS.HOME) || (!isNarrowLayout && currentRouteName !== SCREENS.REPORT)) {
69+
Log.info("[PriorityModeController] Not switching user to focus mode as they aren't on the home screen", false, {validReportCount, currentRouteName});
70+
return;
71+
}
72+
73+
Log.info('[PriorityModeController] Switching user to focus mode', false, {validReportCount, hasTriedFocusMode, isInFocusMode, currentRouteName});
74+
updateChatPriorityMode(CONST.PRIORITY_MODE.GSD, true);
75+
setShouldShowModal(true);
76+
hasSwitched.current = true;
77+
}, [accountID, currentRouteName, hasTriedFocusMode, isInFocusMode, isLoadingReportData, validReportCount]);
78+
79+
return shouldShowModal ? <FocusModeNotification onClose={closeModal} /> : null;
80+
}
81+
82+
/**
83+
* A funky but reliable way to subscribe to screen changes.
84+
*/
85+
function useCurrentRouteName() {
86+
const navigation = useNavigation();
87+
const [currentRouteName, setCurrentRouteName] = useState<string | undefined>('');
88+
89+
useEffect(() => {
90+
const unsubscribe = navigation.addListener('state', () => {
91+
setCurrentRouteName(navigationRef.getCurrentRoute()?.name);
92+
});
93+
return () => unsubscribe();
94+
}, [navigation]);
95+
96+
return currentRouteName;
97+
}

src/libs/Navigation/AppNavigator/AuthScreens.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Onyx, {useOnyx, withOnyx} from 'react-native-onyx';
66
import ComposeProviders from '@components/ComposeProviders';
77
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
88
import OptionsListContextProvider from '@components/OptionListContextProvider';
9+
import PriorityModeController from '@components/PriorityModeController';
910
import {SearchContextProvider} from '@components/Search/SearchContext';
1011
import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext';
1112
import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal';
@@ -44,7 +45,6 @@ import * as App from '@userActions/App';
4445
import * as Download from '@userActions/Download';
4546
import * as Modal from '@userActions/Modal';
4647
import * as PersonalDetails from '@userActions/PersonalDetails';
47-
import * as PriorityMode from '@userActions/PriorityMode';
4848
import * as Report from '@userActions/Report';
4949
import * as Session from '@userActions/Session';
5050
import * as User from '@userActions/User';
@@ -239,13 +239,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
239239
// State to track whether the delegator's authentication is completed before displaying data
240240
const [isDelegatorFromOldDotIsReady, setIsDelegatorFromOldDotIsReady] = useState(false);
241241

242-
useEffect(() => {
243-
const unsubscribe = navigation.addListener('state', () => {
244-
PriorityMode.autoSwitchToFocusMode();
245-
});
246-
return () => unsubscribe();
247-
}, [navigation]);
248-
249242
// On HybridApp we need to prevent flickering during transition to OldDot
250243
const shouldRenderOnboardingExclusivelyOnHybridApp = useMemo(() => {
251244
return CONFIG.IS_HYBRID_APP && Navigation.getActiveRoute().includes(ROUTES.ONBOARDING_ACCOUNTING.route) && isOnboardingCompleted === true;
@@ -340,10 +333,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
340333
}
341334
Download.clearDownloads();
342335

343-
Navigation.isNavigationReady().then(() => {
344-
PriorityMode.autoSwitchToFocusMode();
345-
});
346-
347336
const unsubscribeOnyxModal = onyxSubscribe({
348337
key: ONYXKEYS.MODAL,
349338
callback: (modalArg) => {
@@ -755,6 +744,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
755744
/>
756745
</RootStack.Navigator>
757746
<SearchRouterModal />
747+
<PriorityModeController />
758748
</ComposeProviders>
759749
);
760750
}

src/libs/ReportUtils.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
7272
import type IconAsset from '@src/types/utils/IconAsset';
7373
import {createDraftTransaction, getIOUReportActionToApproveOrPay, setMoneyRequestParticipants, unholdRequest} from './actions/IOU';
7474
import {createDraftWorkspace} from './actions/Policy/Policy';
75-
import {autoSwitchToFocusMode} from './actions/PriorityMode';
7675
import {hasCreditBankAccount} from './actions/ReimbursementAccount/store';
7776
import {handleReportChanged} from './actions/Report';
7877
import type {GuidedSetupData, TaskForParameters} from './actions/Report';
@@ -930,9 +929,6 @@ Onyx.connect({
930929
module.triggerUnreadUpdate();
931930
});
932931

933-
// Each time a new report is added we will check to see if the user should be switched
934-
autoSwitchToFocusMode();
935-
936932
if (!value) {
937933
return;
938934
}

0 commit comments

Comments
 (0)