Skip to content

Commit 4fa6854

Browse files
Merge branch 'main' into jakubkalinski0/Odometer_blob_url_loss_detection
2 parents 4c80f6a + 80a6541 commit 4fa6854

82 files changed

Lines changed: 1362 additions & 1862 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

contributingGuides/SETUP_ANDROID.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ If you haven't done any intentional edits outside of `src/` (like adding new dep
7373
- Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile
7474

7575
### Expensify Employees
76-
If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699)
76+
- To point the **Development Emulator** at your local VM, follow [these steps](https://stackoverflow.com/c/expensify/questions/7699).
77+
- You must configure the local dev email account; follow [these instructions](https://stackoverflowteams.com/c/expensify/questions/23537/23538#23538).
7778

7879
## Enabling Prebuilt React Native Artifacts
7980

contributingGuides/SETUP_IOS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ For an M1 Mac, read this [Stack Overflow post](https://stackoverflow.com/questio
8080
If you want to run the app on an actual physical iOS device, please follow the instructions [here](https://github.com/Expensify/App/blob/main/contributingGuides/HOW_TO_BUILD_APP_ON_PHYSICAL_IOS_DEVICE.md).
8181

8282
### Expensify Employees
83-
If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699)
83+
- To point the **Development Simulator** at your local VM, follow [these steps](https://stackoverflow.com/c/expensify/questions/7699).
84+
- You must configure the local dev email account; follow [these instructions](https://stackoverflowteams.com/c/expensify/questions/23537/23538#23538).
8485

8586
## Push Notifications Setup
8687

src/components/Hoverable/ActiveHoverable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {cloneElement, useCallback, useEffect, useMemo, useRef, useState} from 'react';
22
import {DeviceEventEmitter} from 'react-native';
3+
import getReturnValue from '@libs/getReturnValue';
34
import mergeRefs from '@libs/mergeRefs';
4-
import {getReturnValue} from '@libs/ValueUtils';
55
import CONST from '@src/CONST';
66
import type HoverableProps from './types';
77

src/components/Hoverable/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {cloneElement} from 'react';
22
import {hasHoverSupport} from '@libs/DeviceCapabilities';
3+
import getReturnValue from '@libs/getReturnValue';
34
import mergeRefs from '@libs/mergeRefs';
4-
import {getReturnValue} from '@libs/ValueUtils';
55
import ActiveHoverable from './ActiveHoverable';
66
import type HoverableProps from './types';
77

src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -810,12 +810,14 @@ function MoneyRequestReportTransactionList({
810810
</View>
811811
)}
812812

813-
<MoneyRequestReportTotalSpend
814-
isEmptyTransactions={isEmptyTransactions}
815-
totalDisplaySpend={totalDisplaySpend}
816-
report={report}
817-
hasPendingAction={hasPendingAction}
818-
/>
813+
<OfflineWithFeedback pendingAction={report?.pendingFields?.total}>
814+
<MoneyRequestReportTotalSpend
815+
isEmptyTransactions={isEmptyTransactions}
816+
totalDisplaySpend={totalDisplaySpend}
817+
report={report}
818+
hasPendingAction={hasPendingAction}
819+
/>
820+
</OfflineWithFeedback>
819821
</View>
820822
</View>
821823
<Modal

src/components/ReportActionItem/TripRoomPreview.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ function TripRoomPreview({
206206
{reservationsData.length > 0 && (
207207
<FlatList
208208
data={reservationsData}
209-
style={[styles.gap4, styles.border, styles.borderRadiusComponentLarge, styles.p4]}
209+
style={[styles.border, styles.borderRadiusComponentLarge, styles.p4, styles.flexGrow0]}
210+
contentContainerStyle={styles.gap4}
210211
renderItem={renderItem}
211212
/>
212213
)}

src/hooks/useBeforeRemove.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@ import type {EventListenerCallback, EventMapCore, NavigationState} from '@react-
33
import {useEffect} from 'react';
44

55
// beforeRemove have some limitations. When the react-navigation is upgraded to 7.x, update this to use usePreventRemove hook.
6-
const useBeforeRemove = (onBeforeRemove: EventListenerCallback<EventMapCore<NavigationState>, 'beforeRemove'>, isEnabled = true) => {
6+
const useBeforeRemove = (onBeforeRemove: EventListenerCallback<EventMapCore<NavigationState>, 'beforeRemove'>) => {
77
const navigation = useNavigation();
88

99
useEffect(() => {
10-
if (!isEnabled) {
11-
return undefined;
12-
}
1310
const unsubscribe = navigation.addListener('beforeRemove', onBeforeRemove);
1411
return unsubscribe;
15-
}, [navigation, onBeforeRemove, isEnabled]);
12+
}, [navigation, onBeforeRemove]);
1613
};
1714

1815
export default useBeforeRemove;

src/hooks/useDiscardChangesConfirmation/index.native.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import type {NavigationAction} from '@react-navigation/native';
2-
import {useIsFocused, usePreventRemove} from '@react-navigation/native';
2+
import {usePreventRemove} from '@react-navigation/native';
33
import {useCallback, useRef, useState} from 'react';
44
import {ModalActions} from '@components/Modal/Global/ModalContext';
55
import useConfirmModal from '@hooks/useConfirmModal';
66
import useLocalize from '@hooks/useLocalize';
7+
import Log from '@libs/Log';
78
import navigationRef from '@libs/Navigation/navigationRef';
89
import type UseDiscardChangesConfirmationOptions from './types';
910

10-
function useDiscardChangesConfirmation({getHasUnsavedChanges, onVisibilityChange, isEnabled = true}: UseDiscardChangesConfirmationOptions) {
11+
function useDiscardChangesConfirmation({getHasUnsavedChanges, onCancel, onVisibilityChange, onConfirm}: UseDiscardChangesConfirmationOptions) {
1112
const {translate} = useLocalize();
12-
const isFocused = useIsFocused();
1313
const {showConfirmModal} = useConfirmModal();
1414
const [shouldAllowNavigation, setShouldAllowNavigation] = useState(false);
1515
const blockedNavigationAction = useRef<NavigationAction | undefined>(undefined);
1616

17-
const shouldPrevent = isEnabled && isFocused && !shouldAllowNavigation;
17+
const shouldPrevent = !shouldAllowNavigation;
1818

1919
usePreventRemove(
2020
shouldPrevent,
@@ -36,18 +36,28 @@ function useDiscardChangesConfirmation({getHasUnsavedChanges, onVisibilityChange
3636
}).then((result) => {
3737
onVisibilityChange?.(false);
3838
if (result.action !== ModalActions.CONFIRM) {
39+
onCancel?.();
3940
return;
4041
}
41-
setShouldAllowNavigation(true);
42-
if (blockedNavigationAction.current) {
43-
navigationRef.current?.dispatch(blockedNavigationAction.current);
44-
blockedNavigationAction.current = undefined;
45-
} else {
46-
navigationRef.current?.goBack();
47-
}
42+
const confirmNavigation = () => {
43+
setShouldAllowNavigation(true);
44+
if (blockedNavigationAction.current) {
45+
navigationRef.current?.dispatch(blockedNavigationAction.current);
46+
blockedNavigationAction.current = undefined;
47+
} else {
48+
navigationRef.current?.goBack();
49+
}
50+
};
51+
Promise.resolve()
52+
.then(() => onConfirm?.())
53+
.then(confirmNavigation)
54+
.catch((error: unknown) => {
55+
Log.warn('[useDiscardChangesConfirmation] Failed to run onConfirm callback', {error});
56+
blockedNavigationAction.current = undefined;
57+
});
4858
});
4959
},
50-
[getHasUnsavedChanges, onVisibilityChange, showConfirmModal, translate],
60+
[getHasUnsavedChanges, onCancel, onVisibilityChange, onConfirm, showConfirmModal, translate],
5161
),
5262
);
5363
}
Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import type {NavigationAction} from '@react-navigation/native';
2-
import {useIsFocused, useNavigation} from '@react-navigation/native';
2+
import {useNavigation} from '@react-navigation/native';
33
import {useCallback, useEffect, useRef} from 'react';
44
import {ModalActions} from '@components/Modal/Global/ModalContext';
55
import useBeforeRemove from '@hooks/useBeforeRemove';
66
import useConfirmModal from '@hooks/useConfirmModal';
77
import useLocalize from '@hooks/useLocalize';
8+
import Log from '@libs/Log';
89
import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNavigationActionToMicrotaskQueue';
910
import navigateAfterInteraction from '@libs/Navigation/navigateAfterInteraction';
1011
import navigationRef from '@libs/Navigation/navigationRef';
1112
import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';
1213
import type {RootNavigatorParamList} from '@libs/Navigation/types';
1314
import type UseDiscardChangesConfirmationOptions from './types';
1415

15-
function useDiscardChangesConfirmation({getHasUnsavedChanges, onCancel, onVisibilityChange, isEnabled = true}: UseDiscardChangesConfirmationOptions) {
16+
function useDiscardChangesConfirmation({getHasUnsavedChanges, onCancel, onVisibilityChange, onConfirm}: UseDiscardChangesConfirmationOptions) {
1617
const navigation = useNavigation<PlatformStackNavigationProp<RootNavigatorParamList>>();
17-
const isFocused = useIsFocused();
1818
const {translate} = useLocalize();
19-
const {showConfirmModal, closeModal} = useConfirmModal();
19+
const {showConfirmModal} = useConfirmModal();
2020
const blockedNavigationAction = useRef<NavigationAction>(undefined);
2121
const shouldNavigateBack = useRef(false);
22-
const isDiscardModalOpenRef = useRef(false);
2322

2423
const navigateBack = useCallback(() => {
2524
if (blockedNavigationAction.current) {
@@ -34,7 +33,6 @@ function useDiscardChangesConfirmation({getHasUnsavedChanges, onCancel, onVisibi
3433

3534
const showDiscardModal = useCallback(() => {
3635
onVisibilityChange?.(true);
37-
isDiscardModalOpenRef.current = true;
3836
showConfirmModal({
3937
title: translate('discardChangesConfirmation.title'),
4038
prompt: translate('discardChangesConfirmation.body'),
@@ -43,43 +41,41 @@ function useDiscardChangesConfirmation({getHasUnsavedChanges, onCancel, onVisibi
4341
cancelText: translate('common.cancel'),
4442
shouldIgnoreBackHandlerDuringTransition: true,
4543
}).then((result) => {
46-
isDiscardModalOpenRef.current = false;
4744
onVisibilityChange?.(false);
4845
if (result.action === ModalActions.CONFIRM) {
49-
setNavigationActionToMicrotaskQueue(navigateBack);
46+
Promise.resolve()
47+
.then(() => onConfirm?.())
48+
.then(() => {
49+
setNavigationActionToMicrotaskQueue(navigateBack);
50+
})
51+
.catch((error: unknown) => {
52+
Log.warn('[useDiscardChangesConfirmation] Failed to run onConfirm callback', {error});
53+
blockedNavigationAction.current = undefined;
54+
shouldNavigateBack.current = false;
55+
});
5056
} else {
5157
blockedNavigationAction.current = undefined;
5258
shouldNavigateBack.current = false;
5359
onCancel?.();
5460
}
5561
});
56-
}, [showConfirmModal, translate, navigateBack, onCancel, onVisibilityChange]);
62+
}, [showConfirmModal, translate, navigateBack, onCancel, onConfirm, onVisibilityChange]);
5763

58-
useBeforeRemove(
59-
useCallback(
60-
(e) => {
61-
if (!isEnabled || !isFocused || !getHasUnsavedChanges() || shouldNavigateBack.current) {
62-
return;
63-
}
64-
65-
e.preventDefault();
66-
blockedNavigationAction.current = e.data.action;
67-
navigateAfterInteraction(showDiscardModal);
68-
},
69-
[getHasUnsavedChanges, isFocused, isEnabled, showDiscardModal],
70-
),
71-
isEnabled && isFocused,
72-
);
64+
useBeforeRemove((e) => {
65+
if (!getHasUnsavedChanges() || shouldNavigateBack.current) {
66+
return;
67+
}
68+
e.preventDefault();
69+
blockedNavigationAction.current = e.data.action;
70+
navigateAfterInteraction(showDiscardModal);
71+
});
7372

7473
/**
7574
* We cannot programmatically stop the browser's back navigation like react-navigation's beforeRemove.
7675
* Events like popstate and transitionStart are triggered AFTER the back navigation has already completed.
7776
* So we need to go forward to get back to the current page.
7877
*/
7978
useEffect(() => {
80-
if (!isEnabled || !isFocused) {
81-
return undefined;
82-
}
8379
const unsubscribe = navigation.addListener('transitionStart', ({data: {closing}}) => {
8480
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
8581
if (!getHasUnsavedChanges()) {
@@ -95,20 +91,7 @@ function useDiscardChangesConfirmation({getHasUnsavedChanges, onCancel, onVisibi
9591
});
9692

9793
return unsubscribe;
98-
}, [navigation, getHasUnsavedChanges, isFocused, isEnabled, showDiscardModal]);
99-
100-
/**
101-
* When the screen loses focus (or is disabled) while the discard modal is open,
102-
* close the modal and reset refs so we don't leave the modal visible or stale state.
103-
*/
104-
useEffect(() => {
105-
if ((isFocused && isEnabled) || !isDiscardModalOpenRef.current) {
106-
return;
107-
}
108-
closeModal();
109-
blockedNavigationAction.current = undefined;
110-
shouldNavigateBack.current = false;
111-
}, [isFocused, isEnabled, closeModal]);
94+
}, [navigation, getHasUnsavedChanges, showDiscardModal]);
11295
}
11396

11497
export default useDiscardChangesConfirmation;

src/hooks/useDiscardChangesConfirmation/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ type UseDiscardChangesConfirmationOptions = {
22
getHasUnsavedChanges: () => boolean;
33
onCancel?: () => void;
44
onVisibilityChange?: (visible: boolean) => void;
5-
isEnabled?: boolean;
5+
onConfirm?: () => void | Promise<void>;
66
};
77

88
export default UseDiscardChangesConfirmationOptions;

0 commit comments

Comments
 (0)