From 37cf997589f78ba2de838a5252bf227826e13fe0 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 15:07:54 +0700 Subject: [PATCH 001/686] display backend unreachability message --- src/CONST.ts | 3 +- src/Expensify.tsx | 4 +- src/components/OfflineIndicator.tsx | 27 ++++++++++++-- src/hooks/useNetwork.ts | 6 +-- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/NetworkConnection.ts | 57 +++++++++++++++++------------ src/libs/actions/Network.ts | 6 ++- src/types/onyx/Network.ts | 2 + 9 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index fa44cda20720..472c980ebaca 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -507,6 +507,7 @@ const CONST = { EMPTY_ARRAY, EMPTY_OBJECT, USE_EXPENSIFY_URL, + STATUS_EXPENSIFY_URL: 'https://status.expensify.com/', GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com', IMAGE_BASE64_MATCH: 'base64', @@ -917,7 +918,7 @@ const CONST = { DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, DEFAULT_CLOSE_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, - DEFAULT_NETWORK_DATA: {isOffline: false}, + DEFAULT_NETWORK_DATA: {isOffline: false, isBackendReachable: true}, FORMS: { LOGIN_FORM: 'LoginForm', VALIDATE_CODE_FORM: 'ValidateCodeForm', diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 5681be838ca8..7a2a2189b0fa 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -139,7 +139,9 @@ function Expensify({ ActiveClientManager.init(); // Used for the offline indicator appearing when someone is offline - NetworkConnection.subscribeToNetInfo(); + const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); + + return () => unsubscribeNetInfo(); }, []); useEffect(() => { diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index 1a61b6622783..a550a2a6a563 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -7,9 +7,11 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; +import TextLink from './TextLink'; type OfflineIndicatorProps = { /** Optional styles for container element that will override the default styling for the offline indicator */ @@ -23,7 +25,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {isOffline} = useNetwork(); + const {isOffline, isBackendReachable} = useNetwork(); const {isSmallScreenWidth} = useWindowDimensions(); const computedStyles = useMemo((): StyleProp => { @@ -34,7 +36,8 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); - if (!isOffline) { + // Truthy isBackendReachable implies both online and normal backend reachability + if (isBackendReachable) { return null; } @@ -46,7 +49,25 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { width={variables.iconSizeSmall} height={variables.iconSizeSmall} /> - {translate('common.youAppearToBeOffline')} + + { + // Do not reorder the condition, we only show unreachability message when online and backend is unreachable + isOffline ? ( + translate('common.youAppearToBeOffline') + ) : ( + <> + {translate('common.weMightHaveProblem')} + + {CONST.STATUS_EXPENSIFY_URL} + + . + + ) + } + ); } diff --git a/src/hooks/useNetwork.ts b/src/hooks/useNetwork.ts index 9d5e1e75d7c8..4dfa3a322df8 100644 --- a/src/hooks/useNetwork.ts +++ b/src/hooks/useNetwork.ts @@ -6,13 +6,13 @@ type UseNetworkProps = { onReconnect?: () => void; }; -type UseNetwork = {isOffline: boolean}; +type UseNetwork = {isOffline: boolean; isBackendReachable: boolean}; export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = {}): UseNetwork { const callback = useRef(onReconnect); callback.current = onReconnect; - const {isOffline} = useContext(NetworkContext) ?? CONST.DEFAULT_NETWORK_DATA; + const {isOffline, isBackendReachable} = useContext(NetworkContext) ?? CONST.DEFAULT_NETWORK_DATA; const prevOfflineStatusRef = useRef(isOffline); useEffect(() => { // If we were offline before and now we are not offline then we just reconnected @@ -29,5 +29,5 @@ export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = { prevOfflineStatusRef.current = isOffline; }, [isOffline]); - return {isOffline: isOffline ?? false}; + return {isOffline: isOffline ?? false, isBackendReachable: isBackendReachable ?? true}; } diff --git a/src/languages/en.ts b/src/languages/en.ts index eecd81c54123..da67933011a5 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -266,6 +266,7 @@ export default { conciergeHelp: 'Please reach out to Concierge for help.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `You've selected the maximum number (${count}) of participants.`, youAppearToBeOffline: 'You appear to be offline.', + weMightHaveProblem: 'We might have a problem. Check out ', thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.', areYouSure: 'Are you sure?', verify: 'Verify', diff --git a/src/languages/es.ts b/src/languages/es.ts index cd36f9071de6..3e3b6be636eb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,6 +256,7 @@ export default { conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', + weMightHaveProblem: 'We might have a problem. Check out', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index f5c391aad09c..0a3987e97152 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -2,7 +2,6 @@ import NetInfo from '@react-native-community/netinfo'; import throttle from 'lodash/throttle'; import Onyx from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; @@ -39,6 +38,7 @@ function setOfflineStatus(isCurrentlyOffline: boolean): void { // When reconnecting, ie, going from offline to online, all the reconnection callbacks // are triggered (this is usually Actions that need to re-download data from the server) if (isOffline && !isCurrentlyOffline) { + NetworkActions.setIsBackendReachable(true); triggerReconnectionCallbacks('offline status changed'); } @@ -72,35 +72,41 @@ Onyx.connect({ * internet connectivity or not. This is more reliable than the Pusher * `disconnected` event which takes about 10-15 seconds to emit. */ -function subscribeToNetInfo(): void { - // Note: We are disabling the configuration for NetInfo when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". +function subscribeToNetInfo() { + let backendReachabilityCheckInterval: NodeJS.Timeout; + // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. if (!CONFIG.IS_USING_LOCAL_WEB) { - // Calling NetInfo.configure (re)checks current state. We use it to force a recheck whenever we (re)subscribe - NetInfo.configure({ - // By default, NetInfo uses `/` for `reachabilityUrl` - // When App is served locally (or from Electron) this address is always reachable - even offline + // Set interval to (re)checks current state every 15 seconds + const BACKEND_REACHABILITY_CHECK_INTERVAL = 15000; + + backendReachabilityCheckInterval = setInterval(() => { + // Offline status also implies backend unreachability + if (isOffline) { + return; + } + // When App is served locally (or from Electron) is always reachable - even offline // Using the API url ensures reachability is tested over internet - reachabilityUrl: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, - reachabilityMethod: 'GET', - reachabilityTest: (response) => { - if (!response.ok) { - return Promise.resolve(false); - } - return response - .json() - .then((json) => Promise.resolve(json.jsonCode === 200)) - .catch(() => Promise.resolve(false)); - }, - - // If a check is taking longer than this time we're considered offline - reachabilityRequestTimeout: CONST.NETWORK.MAX_PENDING_TIME_MS, - }); + fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, { + method: 'GET', + cache: 'no-cache', + }) + .then((response) => { + if (!response.ok) { + return Promise.resolve(false); + } + return response + .json() + .then((json) => Promise.resolve(json.jsonCode === 200)) + .catch(() => Promise.resolve(false)); + }) + .then(NetworkActions.setIsBackendReachable); + }, BACKEND_REACHABILITY_CHECK_INTERVAL); } // Subscribe to the state change event via NetInfo so we can update // whether a user has internet connectivity or not. - NetInfo.addEventListener((state) => { + const unsubscribeNetInfo = NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, {...state}); if (shouldForceOffline) { Log.info('[NetworkConnection] Not setting offline status because shouldForceOffline = true'); @@ -108,6 +114,11 @@ function subscribeToNetInfo(): void { } setOfflineStatus(state.isInternetReachable === false); }); + + return () => { + clearInterval(backendReachabilityCheckInterval); + unsubscribeNetInfo(); + }; } function listenForReconnect() { diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index e71094eded05..72a6bfcb3beb 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,6 +1,10 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; +function setIsBackendReachable(isBackendReachable: boolean) { + Onyx.merge(ONYXKEYS.NETWORK, {isBackendReachable}); +} + function setIsOffline(isOffline: boolean) { Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } @@ -20,4 +24,4 @@ function setShouldFailAllRequests(shouldFailAllRequests: boolean) { Onyx.merge(ONYXKEYS.NETWORK, {shouldFailAllRequests}); } -export {setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew}; +export {setIsBackendReachable, setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew}; diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index 173ca486b53c..d4162d1ffdb7 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -2,6 +2,8 @@ type Network = { /** Is the network currently offline or not */ isOffline: boolean; + isBackendReachable: boolean; + /** Should the network be forced offline */ shouldForceOffline?: boolean; From 4b24bd98d2e9fbefb68b2615ea0d86b84074a3aa Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 15:20:31 +0700 Subject: [PATCH 002/686] show status page hostname only --- src/CONST.ts | 2 +- src/components/OfflineIndicator.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 472c980ebaca..515054f3132b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -507,7 +507,7 @@ const CONST = { EMPTY_ARRAY, EMPTY_OBJECT, USE_EXPENSIFY_URL, - STATUS_EXPENSIFY_URL: 'https://status.expensify.com/', + STATUS_EXPENSIFY_URL: 'https://status.expensify.com', GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com', IMAGE_BASE64_MATCH: 'base64', diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index a550a2a6a563..bd57c176db65 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -61,7 +61,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { href={CONST.STATUS_EXPENSIFY_URL} style={styles.link} > - {CONST.STATUS_EXPENSIFY_URL} + {new URL(CONST.STATUS_EXPENSIFY_URL).host} . From 7e8cf7fce86c9a782f164e99a2157beb0b79535a Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 18:27:44 +0700 Subject: [PATCH 003/686] adjust polling timeout and status page link style --- src/CONST.ts | 1 + src/components/OfflineIndicator.tsx | 5 ++--- src/libs/NetworkConnection.ts | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 515054f3132b..9ab45c6537c2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -912,6 +912,7 @@ const CONST = { MAX_RETRY_WAIT_TIME_MS: 10 * 1000, PROCESS_REQUEST_DELAY_MS: 1000, MAX_PENDING_TIME_MS: 10 * 1000, + REACHABILITY_TIMEOUT_MS: 60 * 1000, MAX_REQUEST_RETRIES: 10, }, WEEK_STARTS_ON: 1, // Monday diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index bd57c176db65..4ed01a531cd2 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -36,8 +36,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); - // Truthy isBackendReachable implies both online and normal backend reachability - if (isBackendReachable) { + if (!isOffline && isBackendReachable) { return null; } @@ -59,7 +58,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { {translate('common.weMightHaveProblem')} {new URL(CONST.STATUS_EXPENSIFY_URL).host} diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 0a3987e97152..56f6f7e3c786 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -2,6 +2,7 @@ import NetInfo from '@react-native-community/netinfo'; import throttle from 'lodash/throttle'; import Onyx from 'react-native-onyx'; import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; @@ -77,15 +78,12 @@ function subscribeToNetInfo() { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. if (!CONFIG.IS_USING_LOCAL_WEB) { - // Set interval to (re)checks current state every 15 seconds - const BACKEND_REACHABILITY_CHECK_INTERVAL = 15000; - + // Set interval to (re)check current state every 60 seconds backendReachabilityCheckInterval = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { return; } - // When App is served locally (or from Electron) is always reachable - even offline // Using the API url ensures reachability is tested over internet fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=Ping`, { method: 'GET', @@ -100,8 +98,9 @@ function subscribeToNetInfo() { .then((json) => Promise.resolve(json.jsonCode === 200)) .catch(() => Promise.resolve(false)); }) - .then(NetworkActions.setIsBackendReachable); - }, BACKEND_REACHABILITY_CHECK_INTERVAL); + .then(NetworkActions.setIsBackendReachable) + .catch(() => NetworkActions.setIsBackendReachable(false)); + }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); } // Subscribe to the state change event via NetInfo so we can update From c7b627d62df4769eb6eb3bed1bc4076b3023b05f Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 15 Mar 2024 18:46:37 +0700 Subject: [PATCH 004/686] update comment --- src/Expensify.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 7a2a2189b0fa..08fb4f269501 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -138,7 +138,7 @@ function Expensify({ // Initialize this client as being an active client ActiveClientManager.init(); - // Used for the offline indicator appearing when someone is offline + // Used for the offline indicator appearing when someone is offline or backend is unreachable const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); return () => unsubscribeNetInfo(); From f3be10027c4d786662253794c2cc7831170baf2a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 18 Mar 2024 18:19:33 +0700 Subject: [PATCH 005/686] update spanish copy --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 9ac6e22648c0..9e5e4e0b6064 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,7 +256,7 @@ export default { conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', - weMightHaveProblem: 'We might have a problem. Check out', + weMightHaveProblem: 'We might have a problem. Check out ', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', From d3faf922c7f9784c5b51edbdb208fa3126d6768d Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 18 Mar 2024 18:51:52 +0700 Subject: [PATCH 006/686] Update Espanol copy --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 9e5e4e0b6064..3ccf191221fb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -256,7 +256,7 @@ export default { conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', - weMightHaveProblem: 'We might have a problem. Check out ', + weMightHaveProblem: 'Peude que te tengamos un problema. Echa un vistazo a ', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', From aae3f37b09dbb9ee0dd95d7d69464f7ff9a1de7c Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 01:07:35 +0700 Subject: [PATCH 007/686] update comments --- src/components/OfflineIndicator.tsx | 2 +- src/libs/NetworkConnection.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index 4ed01a531cd2..bb497fad5b4e 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -50,7 +50,7 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { /> { - // Do not reorder the condition, we only show unreachability message when online and backend is unreachable + // If we reversed the ternary, unreachability message would always show even when offline isOffline ? ( translate('common.youAppearToBeOffline') ) : ( diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index cbfb4f9bfba3..96acc4c7d336 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -69,16 +69,14 @@ Onyx.connect({ }); /** - * Set up the event listener for NetInfo to tell whether the user has - * internet connectivity or not. This is more reliable than the Pusher - * `disconnected` event which takes about 10-15 seconds to emit. + * Monitor internet connectivity and perform periodic backend reachability checks */ function subscribeToNetInfo() { let backendReachabilityCheckInterval: NodeJS.Timeout; // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. if (!CONFIG.IS_USING_LOCAL_WEB) { - // Set interval to (re)check current state every 60 seconds + // Set interval to periodically (re)check backend status backendReachabilityCheckInterval = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { @@ -103,8 +101,11 @@ function subscribeToNetInfo() { }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); } - // Subscribe to the state change event via NetInfo so we can update - // whether a user has internet connectivity or not. + /** + * Set up the event listener for NetInfo to tell whether the user has + * internet connectivity or not. This is more reliable than the Pusher + * `disconnected` event which takes about 10-15 seconds to emit. + */ const unsubscribeNetInfo = NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, {...state}); if (shouldForceOffline) { From 4c84f89b10cda8b663c5dd7168ba6447cac0d9cb Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 17:45:51 +0700 Subject: [PATCH 008/686] refactor: extract subscribeToBackendReachability and modify minor comments --- src/Expensify.tsx | 4 +-- src/libs/NetworkConnection.ts | 68 ++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 08fb4f269501..4f4a9e53bf5a 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -139,9 +139,9 @@ function Expensify({ ActiveClientManager.init(); // Used for the offline indicator appearing when someone is offline or backend is unreachable - const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo(); + const unsubscribeNetworkStatus = NetworkConnection.subscribeToNetworkStatus(); - return () => unsubscribeNetInfo(); + return () => unsubscribeNetworkStatus(); }, []); useEffect(() => { diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 10703eade109..511ecdd5614a 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -68,44 +68,46 @@ Onyx.connect({ }, }); +/** + * Set interval to periodically (re)check backend status + * @returns for use with clearInterval + */ +function subscribeToBackendReachability() { + return setInterval(() => { + // Offline status also implies backend unreachability + if (isOffline) { + return; + } + // Using the API url ensures reachability is tested over internet + fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping`, { + method: 'GET', + cache: 'no-cache', + }) + .then((response) => { + if (!response.ok) { + return Promise.resolve(false); + } + return response + .json() + .then((json) => Promise.resolve(json.jsonCode === 200)) + .catch(() => Promise.resolve(false)); + }) + .then(NetworkActions.setIsBackendReachable) + .catch(() => NetworkActions.setIsBackendReachable(false)); + }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); +} + /** * Monitor internet connectivity and perform periodic backend reachability checks */ -function subscribeToNetInfo() { - let backendReachabilityCheckInterval: NodeJS.Timeout; +function subscribeToNetworkStatus() { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. - if (!CONFIG.IS_USING_LOCAL_WEB) { - // Set interval to periodically (re)check backend status - backendReachabilityCheckInterval = setInterval(() => { - // Offline status also implies backend unreachability - if (isOffline) { - return; - } - // Using the API url ensures reachability is tested over internet - fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping`, { - method: 'GET', - cache: 'no-cache', - }) - .then((response) => { - if (!response.ok) { - return Promise.resolve(false); - } - return response - .json() - .then((json) => Promise.resolve(json.jsonCode === 200)) - .catch(() => Promise.resolve(false)); - }) - .then(NetworkActions.setIsBackendReachable) - .catch(() => NetworkActions.setIsBackendReachable(false)); - }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); - } + const backendReachabilityCheckInterval = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; - /** - * Set up the event listener for NetInfo to tell whether the user has - * internet connectivity or not. This is more reliable than the Pusher - * `disconnected` event which takes about 10-15 seconds to emit. - */ + // Set up the event listener for NetInfo to tell whether the user has + // internet connectivity or not. This is more reliable than the Pusher + // `disconnected` event which takes about 10-15 seconds to emit. const unsubscribeNetInfo = NetInfo.addEventListener((state) => { Log.info('[NetworkConnection] NetInfo state change', false, {...state}); if (shouldForceOffline) { @@ -167,5 +169,5 @@ export default { onReconnect, triggerReconnectionCallbacks, recheckNetworkConnection, - subscribeToNetInfo, + subscribeToNetworkStatus, }; From 0b5dec8bc4cf9b28cf7c4b05be55189b23f5474a Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 17:49:38 +0700 Subject: [PATCH 009/686] refactor: add returns doc --- src/libs/NetworkConnection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 511ecdd5614a..eb87da2b0684 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -72,7 +72,7 @@ Onyx.connect({ * Set interval to periodically (re)check backend status * @returns for use with clearInterval */ -function subscribeToBackendReachability() { +function subscribeToBackendReachability(): NodeJS.Timeout { return setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { @@ -99,8 +99,9 @@ function subscribeToBackendReachability() { /** * Monitor internet connectivity and perform periodic backend reachability checks + * @returns unsubscribe method */ -function subscribeToNetworkStatus() { +function subscribeToNetworkStatus(): () => void { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. const backendReachabilityCheckInterval = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; From b7be2f2acff75a13bd15543275c8dddca3348ed2 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Mar 2024 18:19:16 +0700 Subject: [PATCH 010/686] refactor: return cleanup interval function --- src/libs/NetworkConnection.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index eb87da2b0684..57ae5be3f9fa 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -70,10 +70,10 @@ Onyx.connect({ /** * Set interval to periodically (re)check backend status - * @returns for use with clearInterval + * @returns clearInterval cleanup */ -function subscribeToBackendReachability(): NodeJS.Timeout { - return setInterval(() => { +function subscribeToBackendReachability(): () => void { + const intervalID = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { return; @@ -95,6 +95,10 @@ function subscribeToBackendReachability(): NodeJS.Timeout { .then(NetworkActions.setIsBackendReachable) .catch(() => NetworkActions.setIsBackendReachable(false)); }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); + + return () => { + clearInterval(intervalID); + }; } /** @@ -104,7 +108,7 @@ function subscribeToBackendReachability(): NodeJS.Timeout { function subscribeToNetworkStatus(): () => void { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". // If you need to test the "recheck" feature then switch to the production API proxy server. - const backendReachabilityCheckInterval = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; + const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; // Set up the event listener for NetInfo to tell whether the user has // internet connectivity or not. This is more reliable than the Pusher @@ -119,7 +123,7 @@ function subscribeToNetworkStatus(): () => void { }); return () => { - clearInterval(backendReachabilityCheckInterval); + unsubscribeFromBackendReachability?.(); unsubscribeNetInfo(); }; } From 91daa59c3cdc13d35038ae40db35a013e12f9438 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 29 Mar 2024 22:50:15 +0300 Subject: [PATCH 011/686] implemented offline indicator for video --- src/components/VideoPlayer/BaseVideoPlayer.js | 20 ++++++++++++++++--- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index 91737ad3938a..9b247905db37 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -3,13 +3,17 @@ import {Video, VideoFullscreenUpdate} from 'expo-av'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; +import BlockingView from '@components/BlockingViews/BlockingView'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; +import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -73,6 +77,8 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); + const {translate} = useLocalize(); + const theme = useTheme(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -275,9 +281,17 @@ function BaseVideoPlayer({ )} - - {(isLoading || isBuffering) && } - + {((isLoading && !isOffline) || isBuffering) && } + {isLoading && isOffline && ( + + + + )} {shouldShowVideoControls && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( `You've selected the maximum number (${count}) of participants.`, youAppearToBeOffline: 'You appear to be offline.', thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.', + attachementWillBeAvailableOnceBackOnline: 'Attachment will become available once back online.', areYouSure: 'Are you sure?', verify: 'Verify', yesContinue: 'Yes, continue', diff --git a/src/languages/es.ts b/src/languages/es.ts index da4a17e76fdc..fb417cf5364f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -257,6 +257,7 @@ export default { maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', + attachementWillBeAvailableOnceBackOnline: 'El archivo adjunto estará disponible cuando vuelvas a estar en línea.', areYouSure: '¿Estás seguro?', verify: 'Verifique', yesContinue: 'Sí, continuar', From 496791196798dce7ea14db3a6c7a6acf4f4a4bc8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 1 Apr 2024 17:10:21 +0700 Subject: [PATCH 012/686] remove redundant comment --- src/components/OfflineIndicator.tsx | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/components/OfflineIndicator.tsx b/src/components/OfflineIndicator.tsx index bb497fad5b4e..26a9dd740e24 100644 --- a/src/components/OfflineIndicator.tsx +++ b/src/components/OfflineIndicator.tsx @@ -49,23 +49,20 @@ function OfflineIndicator({style, containerStyles}: OfflineIndicatorProps) { height={variables.iconSizeSmall} /> - { - // If we reversed the ternary, unreachability message would always show even when offline - isOffline ? ( - translate('common.youAppearToBeOffline') - ) : ( - <> - {translate('common.weMightHaveProblem')} - - {new URL(CONST.STATUS_EXPENSIFY_URL).host} - - . - - ) - } + {isOffline ? ( + translate('common.youAppearToBeOffline') + ) : ( + <> + {translate('common.weMightHaveProblem')} + + {new URL(CONST.STATUS_EXPENSIFY_URL).host} + + . + + )} ); From 4213ccf99135100c4963a4e127dd7689efa3797b Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 1 Apr 2024 17:10:30 +0700 Subject: [PATCH 013/686] fix typecheck --- .storybook/preview.tsx | 2 +- src/types/onyx/Network.ts | 1 + tests/unit/APITest.ts | 38 +++++++++++++++++++------------------- tests/unit/NetworkTest.ts | 10 +++++----- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 4767c7d81343..9c6738704c45 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -16,7 +16,7 @@ import './fonts.css'; Onyx.init({ keys: ONYXKEYS, initialKeyStates: { - [ONYXKEYS.NETWORK]: {isOffline: false}, + [ONYXKEYS.NETWORK]: {isOffline: false, isBackendReachable: true}, }, }); diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index d4162d1ffdb7..e9a56ba013a8 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -2,6 +2,7 @@ type Network = { /** Is the network currently offline or not */ isOffline: boolean; + /** Is the backend reachable when online */ isBackendReachable: boolean; /** Should the network be forced offline */ diff --git a/tests/unit/APITest.ts b/tests/unit/APITest.ts index 359288b2a1ef..b9404973d418 100644 --- a/tests/unit/APITest.ts +++ b/tests/unit/APITest.ts @@ -68,7 +68,7 @@ describe('APITests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr').mockRejectedValue(new Error('Unexpected xhr call')); // Given we're offline - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}) .then(() => { // When API Writes and Reads are called // @ts-expect-error - mocking the parameter @@ -104,7 +104,7 @@ describe('APITests', () => { // Given we have some requests made while we're offline return ( Onyx.multiSet({ - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}, [ONYXKEYS.SESSION]: {authToken: 'testToken'}, }) @@ -122,7 +122,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { expect(NetworkStore.isOffline()).toBe(false); @@ -158,7 +158,7 @@ describe('APITests', () => { // Given we have some requests made while we're offline return ( - Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}) .then(() => { // When API Write commands are made // @ts-expect-error - mocking the parameter @@ -169,7 +169,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then requests should remain persisted until the xhr call is resolved @@ -222,7 +222,7 @@ describe('APITests', () => { // Given we have a request made while we're offline return ( - Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}) + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}) .then(() => { // When API Write commands are made // @ts-expect-error - mocking the parameter @@ -231,7 +231,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then there has only been one request so far @@ -310,7 +310,7 @@ describe('APITests', () => { Onyx.merge(ONYXKEYS.CREDENTIALS, {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}); return ( waitForBatchedUpdates() - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false})) .then(() => { // @ts-expect-error - mocking the parameter API.write('Mock', {param1: 'value1'}); @@ -318,7 +318,7 @@ describe('APITests', () => { }) // When we resume connectivity - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { const nonLogCalls = xhr.mock.calls.filter(([commandName]) => commandName !== 'Log'); @@ -341,7 +341,7 @@ describe('APITests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr').mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS}); return Onyx.multiSet({ [ONYXKEYS.SESSION]: {authToken: 'anyToken'}, - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test_user', autoGeneratedPassword: 'psswd'}, }) .then(() => { @@ -363,7 +363,7 @@ describe('APITests', () => { return waitForBatchedUpdates(); }) - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then expect all 7 calls to have been made and for the Writes to be made in the order that we made them @@ -384,7 +384,7 @@ describe('APITests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr').mockResolvedValueOnce({jsonCode: CONST.JSON_CODE.NOT_AUTHENTICATED}).mockResolvedValue({jsonCode: CONST.JSON_CODE.SUCCESS}); return Onyx.multiSet({ - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.SESSION]: {authToken: 'test'}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test', autoGeneratedPassword: 'passwd'}, }) @@ -404,7 +404,7 @@ describe('APITests', () => { API.write('MockCommand', {content: 'value6'}); return waitForBatchedUpdates(); }) - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(waitForBatchedUpdates) .then(() => { // Then expect only 8 calls to have been made total and for them to be made in the order that we made them despite requiring reauthentication @@ -434,7 +434,7 @@ describe('APITests', () => { return Onyx.multiSet({ [ONYXKEYS.SESSION]: {authToken: 'oldToken'}, - [ONYXKEYS.NETWORK]: {isOffline: false}, + [ONYXKEYS.NETWORK]: {isOffline: false, isBackendReachable: true}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin: 'test_user', autoGeneratedPassword: 'psswd'}, }) .then(() => { @@ -448,7 +448,7 @@ describe('APITests', () => { forceNetworkRequest: false, }); - Onyx.set(ONYXKEYS.NETWORK, {isOffline: true}); + Onyx.set(ONYXKEYS.NETWORK, {isOffline: true, isBackendReachable: false}); expect(NetworkStore.isOffline()).toBe(false); expect(NetworkStore.isAuthenticating()).toBe(false); return waitForBatchedUpdates(); @@ -467,7 +467,7 @@ describe('APITests', () => { waitForBatchedUpdates(); // Come back from offline to trigger the sequential queue flush - Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}); }) .then(() => { // When we wait for the sequential queue to finish @@ -514,7 +514,7 @@ describe('APITests', () => { // Given a simulated a condition where the credentials have not yet been read from storage and we are offline return Onyx.multiSet({ - [ONYXKEYS.NETWORK]: {isOffline: true}, + [ONYXKEYS.NETWORK]: {isOffline: true, isBackendReachable: false}, [ONYXKEYS.CREDENTIALS]: {}, [ONYXKEYS.SESSION]: null, }) @@ -531,7 +531,7 @@ describe('APITests', () => { expect(PersistedRequests.getAll().length).toBe(1); // When we go online and wait for promises to resolve - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}); + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}); }) .then(waitForBatchedUpdates) .then(() => { @@ -555,7 +555,7 @@ describe('APITests', () => { test('Write request will move directly to the SequentialQueue when we are online and block non-Write requests', () => { const xhr = jest.spyOn(HttpUtils, 'xhr'); - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { // GIVEN that we are online expect(NetworkStore.isOffline()).toBe(false); diff --git a/tests/unit/NetworkTest.ts b/tests/unit/NetworkTest.ts index 63b275a1a6b6..eef607f8036b 100644 --- a/tests/unit/NetworkTest.ts +++ b/tests/unit/NetworkTest.ts @@ -110,7 +110,7 @@ describe('NetworkTests', () => { // This should first trigger re-authentication and then a Failed to fetch PersonalDetails.openPersonalDetails(); return waitForBatchedUpdates() - .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false})) + .then(() => Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true})) .then(() => { expect(isOffline).toBe(false); @@ -261,7 +261,7 @@ describe('NetworkTests', () => { const logHmmmSpy = jest.spyOn(Log, 'hmmm'); // Given we have a request made while online - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { Network.post('MockBadNetworkResponse', {param1: 'value1'}); return waitForBatchedUpdates(); @@ -277,7 +277,7 @@ describe('NetworkTests', () => { const logAlertSpy = jest.spyOn(Log, 'alert'); // Given we have a request made while online - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { Network.post('MockBadNetworkResponse', {param1: 'value1'}); return waitForBatchedUpdates(); @@ -293,7 +293,7 @@ describe('NetworkTests', () => { const onResolved = jest.fn(); // Given we have a request made while online - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { expect(NetworkStore.isOffline()).toBe(false); @@ -314,7 +314,7 @@ describe('NetworkTests', () => { // GIVEN a mock that will return a "cancelled" request error global.fetch = jest.fn().mockRejectedValue(new DOMException('Aborted', CONST.ERROR.REQUEST_CANCELLED)); - return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false}) + return Onyx.set(ONYXKEYS.NETWORK, {isOffline: false, isBackendReachable: true}) .then(() => { // WHEN we make a few requests and then cancel them Network.post('MockCommandOne'); From 5c860463ab31c2772a937e9570aa82586a8241c7 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 1 Apr 2024 17:23:54 +0700 Subject: [PATCH 014/686] rename constant --- src/CONST.ts | 2 +- src/libs/NetworkConnection.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 74938324357a..4b1786fdf643 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -976,7 +976,7 @@ const CONST = { MAX_RETRY_WAIT_TIME_MS: 10 * 1000, PROCESS_REQUEST_DELAY_MS: 1000, MAX_PENDING_TIME_MS: 10 * 1000, - REACHABILITY_TIMEOUT_MS: 60 * 1000, + BACKEND_CHECK_INTERVAL_MS: 60 * 1000, MAX_REQUEST_RETRIES: 10, }, WEEK_STARTS_ON: 1, // Monday diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 57ae5be3f9fa..4048cc209c59 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -94,7 +94,7 @@ function subscribeToBackendReachability(): () => void { }) .then(NetworkActions.setIsBackendReachable) .catch(() => NetworkActions.setIsBackendReachable(false)); - }, CONST.NETWORK.REACHABILITY_TIMEOUT_MS); + }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); return () => { clearInterval(intervalID); From 5de949bb681aa01fa9fee121d78878dcb9c67023 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 2 Apr 2024 15:48:30 +0700 Subject: [PATCH 015/686] Modify comment --- src/libs/NetworkConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 4048cc209c59..4383a921662f 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -106,7 +106,7 @@ function subscribeToBackendReachability(): () => void { * @returns unsubscribe method */ function subscribeToNetworkStatus(): () => void { - // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for "offline". + // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for reachability. // If you need to test the "recheck" feature then switch to the production API proxy server. const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; From 7b007e769a31f71f8c14638c822437e2d74d53a6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 4 Apr 2024 23:39:14 +0300 Subject: [PATCH 016/686] Created attachment offline indicator component --- src/components/AttachmentOfflineIndicator.tsx | 59 +++++++++++++++++++ src/components/VideoPlayer/BaseVideoPlayer.js | 11 ++-- src/components/VideoPlayerPreview/index.tsx | 1 + 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/components/AttachmentOfflineIndicator.tsx diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx new file mode 100644 index 000000000000..476af1dcd31c --- /dev/null +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -0,0 +1,59 @@ +import React, {useMemo} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; +import Icon from './Icon'; +import * as Expensicons from './Icon/Expensicons'; +import Text from './Text'; + +type AttachmentOfflineIndicatorProps = { + /** Optional styles for container element that will override the default styling for the offline indicator */ + containerStyles?: StyleProp; + + /** Optional styles for the container */ + style?: StyleProp; +}; + +function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {isOffline} = useNetwork(); + const {isSmallScreenWidth} = useWindowDimensions(); + + const computedStyles = useMemo((): StyleProp => { + if (containerStyles) { + return containerStyles; + } + + return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; + }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); + + if (!isOffline) { + return null; + } + + return ( + + + {!isPreview && ( + + {title} + {subtitle} + + )} + + ); +} + +AttachmentOfflineIndicator.displayName = 'OfflineIndicator'; + +export default AttachmentOfflineIndicator; diff --git a/src/components/VideoPlayer/BaseVideoPlayer.js b/src/components/VideoPlayer/BaseVideoPlayer.js index d1533b2bf487..2cbd22e173d5 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.js +++ b/src/components/VideoPlayer/BaseVideoPlayer.js @@ -3,17 +3,15 @@ import {Video, VideoFullscreenUpdate} from 'expo-av'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import BlockingView from '@components/BlockingViews/BlockingView'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; -import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -44,6 +42,7 @@ function BaseVideoPlayer({ // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. // eslint-disable-next-line no-unused-vars isVideoHovered, + isPreview, }) { const styles = useThemeStyles(); const { @@ -78,7 +77,6 @@ function BaseVideoPlayer({ const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); const {translate} = useLocalize(); - const theme = useTheme(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; @@ -285,11 +283,10 @@ function BaseVideoPlayer({ {((isLoading && !isOffline) || isBuffering) && } {isLoading && isOffline && ( - )} diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index 37ddacb1f0db..dcbbbc900074 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -83,6 +83,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions, videoDuration={videoDuration} shouldUseSmallVideoControls style={[styles.w100, styles.h100]} + isPreview /> Date: Fri, 5 Apr 2024 20:51:27 +0300 Subject: [PATCH 017/686] did code cleanup --- src/components/AttachmentOfflineIndicator.tsx | 30 +++++++------------ src/components/VideoPlayer/types.ts | 1 + 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 476af1dcd31c..76830abb42e8 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,36 +1,28 @@ -import React, {useMemo} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; +import React from 'react'; import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; type AttachmentOfflineIndicatorProps = { - /** Optional styles for container element that will override the default styling for the offline indicator */ - containerStyles?: StyleProp; + /** Whether the offline indicator is displayed for the attachment preview. */ + isPreview?: boolean; - /** Optional styles for the container */ - style?: StyleProp; + /** Title text to be displayed. */ + title: string; + + /** Subtitle text to be displayed. */ + subtitle: string; }; -function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { +function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); - const {isSmallScreenWidth} = useWindowDimensions(); - - const computedStyles = useMemo((): StyleProp => { - if (containerStyles) { - return containerStyles; - } - - return isSmallScreenWidth ? styles.offlineIndicatorMobile : styles.offlineIndicator; - }, [containerStyles, isSmallScreenWidth, styles.offlineIndicatorMobile, styles.offlineIndicator]); if (!isOffline) { return null; @@ -39,7 +31,7 @@ function AttachmentOfflineIndicator({containerStyles, title, subtitle, isPreview return ( ; shouldPlay?: boolean; + isPreview?: boolean; }; export type {VideoPlayerProps, VideoWithOnFullScreenUpdate}; From e6153f2fb460b2e7fc6843cea89e158d15c3c07c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 20:59:44 +0300 Subject: [PATCH 018/686] fix lint and type --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 0d6635db1e77..e01f2c7a2c8b 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -19,7 +19,7 @@ import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import CONST from '@src/CONST'; import shouldReplayVideo from './shouldReplayVideo'; -import type {VideoWithOnFullScreenUpdate} from './types'; +import type {VideoPlayerProps, VideoWithOnFullScreenUpdate} from './types'; import * as VideoUtils from './utils'; import VideoPlayerControls from './VideoPlayerControls'; @@ -44,9 +44,8 @@ function BaseVideoPlayer({ // but current workaround is just not to use it here for now. This causes not displaying the video controls when // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. // eslint-disable-next-line no-unused-vars - isVideoHovered = false, isPreview, -}) { +}: VideoPlayerProps) { const styles = useThemeStyles(); const {pauseVideo, playVideo, currentlyPlayingURL, sharedElement, originalParent, shareVideoPlayerElements, currentVideoPlayerRef, updateCurrentlyPlayingURL, videoResumeTryNumber} = usePlaybackContext(); From cbf52fe51b31d4d29b88e21095e97d4df865a077 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 21:01:30 +0300 Subject: [PATCH 019/686] revert unnecessary change --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index e01f2c7a2c8b..b771e3fc61be 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -43,7 +43,8 @@ function BaseVideoPlayer({ // isVideoHovered caused a bug with unexpected video switching. We are investigating the root cause of the issue, // but current workaround is just not to use it here for now. This causes not displaying the video controls when // user hovers the mouse over the carousel arrows, but this UI bug feels much less troublesome for now. - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isVideoHovered = false, isPreview, }: VideoPlayerProps) { const styles = useThemeStyles(); From b3f14d666b65e008f0d207a9ab0f28d5b11d45dd Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 21:04:52 +0300 Subject: [PATCH 020/686] removed unnecessary view --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index b771e3fc61be..e7eb78b12ae0 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -308,13 +308,11 @@ function BaseVideoPlayer({ {((isLoading && !isOffline) || isBuffering) && } {isLoading && isOffline && ( - - - + )} {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Fri, 5 Apr 2024 22:06:54 +0300 Subject: [PATCH 021/686] implemented for image view --- src/components/AttachmentOfflineIndicator.tsx | 14 +++++--------- src/components/ImageView/index.tsx | 6 +++++- src/components/VideoPlayer/BaseVideoPlayer.tsx | 8 +------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index 76830abb42e8..b22478330bff 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -11,18 +12,13 @@ import Text from './Text'; type AttachmentOfflineIndicatorProps = { /** Whether the offline indicator is displayed for the attachment preview. */ isPreview?: boolean; - - /** Title text to be displayed. */ - title: string; - - /** Subtitle text to be displayed. */ - subtitle: string; }; -function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: AttachmentOfflineIndicatorProps) { +function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndicatorProps) { const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); + const {translate} = useLocalize(); if (!isOffline) { return null; @@ -38,8 +34,8 @@ function AttachmentOfflineIndicator({title, subtitle, isPreview = false}: Attach /> {!isPreview && ( - {title} - {subtitle} + {translate('common.youAppearToBeOffline')} + {translate('common.attachementWillBeAvailableOnceBackOnline')} )} diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 5d09e7abf41d..2316577158f8 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -2,11 +2,13 @@ import type {SyntheticEvent} from 'react'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import type {GestureResponderEvent, LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Image from '@components/Image'; import RESIZE_MODES from '@components/Image/resizeModes'; import type {ImageOnLoadEvent} from '@components/Image/types'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -33,6 +35,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV const [imgHeight, setImgHeight] = useState(0); const [zoomScale, setZoomScale] = useState(0); const [zoomDelta, setZoomDelta] = useState(); + const {isOffline} = useNetwork(); const scrollableRef = useRef(null); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); @@ -243,7 +246,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV /> - {isLoading && } + {isLoading && !isOffline && } + {isLoading && } ); } diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index e7eb78b12ae0..10d5051c5234 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -307,13 +307,7 @@ function BaseVideoPlayer({ )} {((isLoading && !isOffline) || isBuffering) && } - {isLoading && isOffline && ( - - )} + {isLoading && } {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Fri, 5 Apr 2024 22:42:34 +0300 Subject: [PATCH 022/686] implemented for thumbnail preview --- src/components/AttachmentOfflineIndicator.tsx | 2 +- src/components/ImageWithSizeCalculation.tsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentOfflineIndicator.tsx b/src/components/AttachmentOfflineIndicator.tsx index b22478330bff..8a831b02b407 100644 --- a/src/components/AttachmentOfflineIndicator.tsx +++ b/src/components/AttachmentOfflineIndicator.tsx @@ -25,7 +25,7 @@ function AttachmentOfflineIndicator({isPreview = false}: AttachmentOfflineIndica } return ( - + - {isLoading && !isImageCached && } + {isLoading && !isImageCached && !isOffline && } + {isLoading && !isImageCached && } ); } From da534453d52c1fb2993b2e31451503bfedd83bd3 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 5 Apr 2024 23:01:48 +0300 Subject: [PATCH 023/686] minor fix --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 10d5051c5234..08036890d35c 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -70,7 +70,6 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); - const {translate} = useLocalize(); const togglePlayCurrentVideo = useCallback(() => { videoResumeTryNumber.current = 0; From 2a69717b6ce7afd5350777351018c84c588bbe64 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 20:27:35 +0300 Subject: [PATCH 024/686] implemented offline indicator for native --- src/components/Lightbox/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 86a52c2baf6c..e5261771c5a4 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -40,6 +42,7 @@ type LightboxProps = { function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChangedProp, onError, style, zoomRange = DEFAULT_ZOOM_RANGE}: LightboxProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); + const {isOffline} = useNetwork(); /** * React hooks must be used in the render function of the component at top-level and unconditionally. @@ -243,12 +246,13 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( + {isLoading && !isOffline && ( )} + {isLoading && } )} From bf1786e5a387a9663f3f11cfee6290bb7bc58ac6 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 20:32:31 +0300 Subject: [PATCH 025/686] minor fix --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 08036890d35c..2ddb19716c8e 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -12,7 +12,6 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; import VideoPopoverMenu from '@components/VideoPopoverMenu'; -import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; From 395468dc259e680429e64971479f72a40f2431a3 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 9 Apr 2024 11:39:38 +0200 Subject: [PATCH 026/686] receipt scan ui change wip --- assets/images/receipt-scan.svg | 14 ++++++++++++++ src/components/Icon/Expensicons.ts | 2 ++ .../MoneyRequestPreviewContent.tsx | 5 +++++ src/languages/en.ts | 3 ++- src/languages/es.ts | 1 + src/pages/home/report/ReportActionsList.tsx | 2 ++ 6 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 assets/images/receipt-scan.svg diff --git a/assets/images/receipt-scan.svg b/assets/images/receipt-scan.svg new file mode 100644 index 000000000000..c93986de3c9b --- /dev/null +++ b/assets/images/receipt-scan.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 1fcf0d07276c..ba00ad684473 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -120,6 +120,7 @@ import Printer from '@assets/images/printer.svg'; import Profile from '@assets/images/profile.svg'; import QrCode from '@assets/images/qrcode.svg'; import QuestionMark from '@assets/images/question-mark-circle.svg'; +import ReceiptScan from '@assets/images/receipt-scan.svg'; import ReceiptSearch from '@assets/images/receipt-search.svg'; import Receipt from '@assets/images/receipt.svg'; import RemoveMembers from '@assets/images/remove-members.svg'; @@ -283,6 +284,7 @@ export { QrCode, QuestionMark, Receipt, + ReceiptScan, RemoveMembers, ReceiptSearch, Rotate, diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 2c6f14cec4c2..8d1b7880726b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -312,6 +312,11 @@ function MoneyRequestPreviewContent({ )} + + {true && ( + {translate('iou.receiptScanInProgress')} + )} + diff --git a/src/languages/en.ts b/src/languages/en.ts index 55a4c586716a..ad5377d8349b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -632,7 +632,8 @@ export default { posted: 'Posted', deleteReceipt: 'Delete receipt', routePending: 'Route pending...', - receiptScanning: 'Scan in progress…', + receiptScanning: 'Receipt scanning…', + receiptScanInProgress: 'Receipt scan in progress.', receiptMissingDetails: 'Receipt missing details', missingAmount: 'Missing amount', missingMerchant: 'Missing merchant', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5956f1457005..61c6c82bbc1c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -629,6 +629,7 @@ export default { deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', + receiptScanInProgress: 'Escaneo en curso…', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d1b9c420b0af..e261f1f3c38e 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,6 +6,7 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; @@ -184,6 +185,7 @@ function ReportActionsList({ const hasFooterRendered = useRef(false); const lastVisibleActionCreatedRef = useRef(report.lastVisibleActionCreated); const lastReadTimeRef = useRef(report.lastReadTime); + Onyx.merge('transactions_8811441407757684730', {cardID: 1, merchant: 'Google', hasEReceipt: true, status: 'Pending'}); const sortedVisibleReportActions = useMemo( () => From 2e1f4ce29cca3c94151a02cc2f58a244517c25d2 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 9 Apr 2024 22:20:26 +0200 Subject: [PATCH 027/686] scanning receipt wip --- src/components/MoneyRequestHeader.tsx | 17 +++++++++++++++++ src/components/MoneyRequestHeaderStatusBar.tsx | 3 ++- .../MoneyRequestPreviewContent.tsx | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..ac6cfd911a6a 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -19,9 +19,13 @@ import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; +import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; +import {ReceiptScan} from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; +import variables from '@styles/variables'; +import theme from '@styles/theme'; type MoneyRequestHeaderOnyxProps = { /** Session info for the currently logged in user. */ @@ -216,6 +220,19 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ); } +function ScanningReceiptHeaderTitle() { + return ( + + + + ); +} + MoneyRequestHeader.displayName = 'MoneyRequestHeader'; const MoneyRequestHeaderWithTransaction = withOnyx>({ diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 59ef4ee0bd26..828650fa8fba 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,3 +1,4 @@ +import type {ReactElement} from 'react'; import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -5,7 +6,7 @@ import Text from './Text'; type MoneyRequestHeaderStatusBarProps = { /** Title displayed in badge */ - title: string; + title: string | ReactElement; /** Banner Description */ description: string; diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 8d1b7880726b..745c13ce9d0d 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -7,6 +7,7 @@ import type {GestureResponderEvent} from 'react-native'; import ConfirmedRoute from '@components/ConfirmedRoute'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import {ReceiptScan} from '@components/Icon/Expensicons'; import MoneyRequestSkeletonView from '@components/MoneyRequestSkeletonView'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -30,6 +31,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; +import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; @@ -312,11 +314,17 @@ function MoneyRequestPreviewContent({ )} - - {true && ( + {isScanning && ( + + {translate('iou.receiptScanInProgress')} - )} - + + )} From 9ee70da12544f7017ed8d70e1c04b833600edfa4 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 08:15:15 +0200 Subject: [PATCH 028/686] finalize scanning receipt --- src/components/MoneyRequestHeader.tsx | 29 ++++++++----------- .../MoneyRequestHeaderStatusBar.tsx | 14 +++++---- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index ac6cfd911a6a..2f80cf3c6e59 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as HeaderUtils from '@libs/HeaderUtils'; @@ -10,6 +11,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -24,8 +26,6 @@ import * as Expensicons from './Icon/Expensicons'; import {ReceiptScan} from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; -import variables from '@styles/variables'; -import theme from '@styles/theme'; type MoneyRequestHeaderOnyxProps = { /** Session info for the currently logged in user. */ @@ -58,6 +58,7 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & { function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) { const styles = useThemeStyles(); + const theme = useTheme(); const {translate} = useLocalize(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); @@ -192,8 +193,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, )} {isScanning && ( + } + description={translate('iou.receiptScanInProgressDescription')} shouldShowBorderBottom /> )} @@ -220,19 +228,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, ); } -function ScanningReceiptHeaderTitle() { - return ( - - - - ); -} - MoneyRequestHeader.displayName = 'MoneyRequestHeader'; const MoneyRequestHeaderWithTransaction = withOnyx>({ diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index 828650fa8fba..d21e66ba39eb 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,4 +1,4 @@ -import type {ReactElement} from 'react'; +import type {ReactNode} from 'react'; import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -6,7 +6,7 @@ import Text from './Text'; type MoneyRequestHeaderStatusBarProps = { /** Title displayed in badge */ - title: string | ReactElement; + title: string | ReactNode; /** Banner Description */ description: string; @@ -20,9 +20,13 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom const borderBottomStyle = shouldShowBorderBottom ? styles.borderBottom : {}; return ( - - {title} - + {typeof title === 'string' ? ( + + {title} + + ) : ( + {title} + )} {description} diff --git a/src/languages/en.ts b/src/languages/en.ts index ad5377d8349b..c7ea86ff8064 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -634,6 +634,7 @@ export default { routePending: 'Route pending...', receiptScanning: 'Receipt scanning…', receiptScanInProgress: 'Receipt scan in progress.', + receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.', receiptMissingDetails: 'Receipt missing details', missingAmount: 'Missing amount', missingMerchant: 'Missing merchant', diff --git a/src/languages/es.ts b/src/languages/es.ts index 61c6c82bbc1c..c9aabe62087a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -630,6 +630,7 @@ export default { routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', receiptScanInProgress: 'Escaneo en curso…', + receiptScanInProgressDescription: 'Escaneo en curso.', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', From 2a7fac59e5fde23d38751b0732de483d13c4da43 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 10 Apr 2024 14:32:00 +0300 Subject: [PATCH 029/686] revert light box change --- src/components/Lightbox/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index e5261771c5a4..86a52c2baf6c 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,14 +2,12 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; -import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; -import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -42,7 +40,6 @@ type LightboxProps = { function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChangedProp, onError, style, zoomRange = DEFAULT_ZOOM_RANGE}: LightboxProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); - const {isOffline} = useNetwork(); /** * React hooks must be used in the render function of the component at top-level and unconditionally. @@ -246,13 +243,12 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan )} {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( + {isLoading && ( )} - {isLoading && } )} From bbfda4d6a9292afc983458922a22a40a543fbd70 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 10 Apr 2024 14:53:25 +0300 Subject: [PATCH 030/686] fix buffering case --- src/components/VideoPlayer/BaseVideoPlayer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 2ddb19716c8e..26a44364f807 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -305,7 +305,7 @@ function BaseVideoPlayer({ )} {((isLoading && !isOffline) || isBuffering) && } - {isLoading && } + {isLoading && !isBuffering && } {controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && ( Date: Wed, 10 Apr 2024 17:06:49 +0200 Subject: [PATCH 031/686] Add useActiveWorkspaceFromNavigationState --- .../useActiveWorkspaceFromNavigationState.ts | 13 ++++ src/libs/Navigation/types.ts | 4 +- src/libs/PolicyUtils.ts | 68 ++++++++----------- src/pages/home/sidebar/SidebarLinksData.tsx | 4 +- .../SidebarScreen/BaseSidebarScreen.tsx | 4 +- 5 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 src/hooks/useActiveWorkspaceFromNavigationState.ts diff --git a/src/hooks/useActiveWorkspaceFromNavigationState.ts b/src/hooks/useActiveWorkspaceFromNavigationState.ts new file mode 100644 index 000000000000..8f9da3d9df37 --- /dev/null +++ b/src/hooks/useActiveWorkspaceFromNavigationState.ts @@ -0,0 +1,13 @@ +import {useNavigationState} from '@react-navigation/native'; +import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; + +/** + * Get the currently selected policy ID stored in the navigation state. This hook should only be called only from screens in BottomTab. + */ +function useActiveWorkspaceFromNavigationState() { + const activeWorkpsaceID = useNavigationState((state) => state.routes.at(-1)?.params?.policyID); + + return activeWorkpsaceID; +} + +export default useActiveWorkspaceFromNavigationState; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1f1e7ec9a459..04bb7797804e 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -662,8 +662,8 @@ type WelcomeVideoModalNavigatorParamList = { }; type BottomTabNavigatorParamList = { - [SCREENS.HOME]: undefined; - [SCREENS.SETTINGS.ROOT]: undefined; + [SCREENS.HOME]: {policyID?: string}; + [SCREENS.SETTINGS.ROOT]: {policyID?: string}; }; type SharedScreensParamList = { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 665830ca7167..6c5e4ea695c2 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -8,9 +8,7 @@ import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, Polic import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; -import Navigation, {navigationRef} from './Navigation/Navigation'; -import type {RootStackParamList, State} from './Navigation/types'; +import Navigation from './Navigation/Navigation'; type MemberEmailsToAccountIDs = Record; @@ -310,51 +308,43 @@ function isPolicyFeatureEnabled(policy: OnyxEntry | EmptyObject, feature return Boolean(policy?.[featureName]); } -/** - * Get the currently selected policy ID stored in the navigation state. - */ -function getPolicyIDFromNavigationState() { - return getPolicyIDFromState(navigationRef.getRootState() as State); -} - export { + canEditTaxRate, + extractPolicyIDFromPath, getActivePolicies, + getCleanedTagName, + getCountOfEnabledTagsOfList, + getIneligibleInvitees, + getMemberAccountIDsForWorkspace, + getNumericValue, + getPathWithoutPolicyID, + getPolicyBrickRoadIndicatorStatus, + getPolicyMembersByIdWithoutCurrentUser, + getSortedTagKeys, + getTagList, + getTagListName, + getTagLists, + getTaxByID, + getUnitRateValue, + goBackFromInvalidPolicy, hasAccountingConnections, - hasPolicyMemberError, + hasCustomUnitsError, + hasPolicyCategoriesError, hasPolicyError, hasPolicyErrorFields, - hasCustomUnitsError, - getNumericValue, - getUnitRateValue, - getPolicyBrickRoadIndicatorStatus, - shouldShowPolicy, + hasPolicyMemberError, + hasTaxRateError, isExpensifyTeam, - isInstantSubmitEnabled, isFreeGroupPolicy, - isPolicyAdmin, - isTaxTrackingEnabled, - isSubmitAndClose, - getMemberAccountIDsForWorkspace, - getIneligibleInvitees, - getTagLists, - getTagListName, - getSortedTagKeys, - canEditTaxRate, - getTagList, - getCleanedTagName, - getCountOfEnabledTagsOfList, - isPendingDeletePolicy, - isPolicyMember, + isInstantSubmitEnabled, isPaidGroupPolicy, - extractPolicyIDFromPath, - getPathWithoutPolicyID, - getPolicyMembersByIdWithoutCurrentUser, - goBackFromInvalidPolicy, + isPendingDeletePolicy, + isPolicyAdmin, isPolicyFeatureEnabled, - hasTaxRateError, - getTaxByID, - hasPolicyCategoriesError, - getPolicyIDFromNavigationState, + isPolicyMember, + isSubmitAndClose, + isTaxTrackingEnabled, + shouldShowPolicy, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index e56962a331a2..4a90d6e96239 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -9,7 +9,7 @@ import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import withCurrentReportID from '@components/withCurrentReportID'; -import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -85,7 +85,7 @@ function SidebarLinksData({ const network = useNetwork(); const isFocused = useIsFocused(); const styles = useThemeStyles(); - const {activeWorkspaceID} = useActiveWorkspace(); + const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); const {translate} = useLocalize(); const prevPriorityMode = usePrevious(priorityMode); const policyMemberAccountIDs = getPolicyMembersByIdWithoutCurrentUser(policyMembers, activeWorkspaceID, accountID); diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index fe61af021d7f..a8df74cfc4e9 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -1,11 +1,11 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import ScreenWrapper from '@components/ScreenWrapper'; +import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import Performance from '@libs/Performance'; -import {getPolicyIDFromNavigationState} from '@libs/PolicyUtils'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; @@ -20,7 +20,7 @@ const startTimer = () => { function BaseSidebarScreen() { const styles = useThemeStyles(); - const activeWorkspaceID = getPolicyIDFromNavigationState(); + const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); Timing.start(CONST.TIMING.SIDEBAR_LOADED, true); From 8f22291f1a8ea5b6b2953cf13e7a17202502f6ce Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:18:46 +0200 Subject: [PATCH 032/686] finalize pending transaction, cleanup wip --- assets/images/credit-card-hourglass.svg | 19 +++++++++++++++ src/components/Icon/Expensicons.ts | 2 ++ src/components/MoneyRequestHeader.tsx | 14 +++++++---- .../MoneyRequestPreviewContent.tsx | 10 -------- .../ReportActionItem/MoneyRequestView.tsx | 5 ---- .../ReportActionItem/ReportPreview.tsx | 14 +++++++++++ src/languages/en.ts | 3 ++- src/libs/CardUtils.ts | 2 +- src/pages/home/ReportScreen.tsx | 23 ++++++++++--------- src/pages/home/report/ReportActionItem.tsx | 1 + src/pages/home/report/ReportActionsList.tsx | 2 +- 11 files changed, 62 insertions(+), 33 deletions(-) create mode 100644 assets/images/credit-card-hourglass.svg diff --git a/assets/images/credit-card-hourglass.svg b/assets/images/credit-card-hourglass.svg new file mode 100644 index 000000000000..2acd013fbe59 --- /dev/null +++ b/assets/images/credit-card-hourglass.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index ba00ad684473..78583f3af4d4 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -41,6 +41,7 @@ import Collapse from '@assets/images/collapse.svg'; import Concierge from '@assets/images/concierge.svg'; import Connect from '@assets/images/connect.svg'; import Copy from '@assets/images/copy.svg'; +import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg'; import CreditCard from '@assets/images/creditcard.svg'; import DocumentPlus from '@assets/images/document-plus.svg'; import DocumentSlash from '@assets/images/document-slash.svg'; @@ -198,6 +199,7 @@ export { Connect, Copy, CreditCard, + CreditCardHourglass, DeletedRoomAvatar, Document, DocumentSlash, diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 2f80cf3c6e59..5ea46f339d46 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -23,7 +23,6 @@ import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import {ReceiptScan} from './Icon/Expensicons'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; @@ -186,8 +185,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, /> {isPending && ( + } + description={translate('iou.transactionPendingDescription')} shouldShowBorderBottom={!isScanning} /> )} @@ -195,7 +201,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, 0; const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const hasErrors = hasMissingSmartscanFields || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations)) || ReportUtils.hasActionsWithErrors(iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); @@ -226,6 +228,7 @@ function ReportPreview({ const shouldShowSingleRequestMerchantOrDescription = numberOfRequests === 1 && (!!formattedMerchant || !!formattedDescription) && !(hasOnlyTransactionsWithPendingRoutes && !totalDisplaySpend); const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchantOrDescription || numberOfRequests > 1); + const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && transactionsWithReceipts.length === 1; const {isSupportTextHtml, supportText} = useMemo(() => { if (formattedMerchant) { @@ -318,6 +321,17 @@ function ReportPreview({ )} + {shouldShowPendingSubtitle && ( + + + {translate('iou.transactionPending')} + + )} {shouldShowSettlementButton && ( diff --git a/src/languages/en.ts b/src/languages/en.ts index c7ea86ff8064..27637aeb9602 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -641,7 +641,7 @@ export default { receiptStatusTitle: 'Scanning…', receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", receiptScanningFailed: 'Receipt scanning failed. Enter the details manually.', - transactionPendingText: 'It takes a few days from the date the card was used for the transaction to post.', + transactionPendingDescription: 'Transaction pending. It can take a few days from the date the card was used for the transaction to post.', requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pending` : '' @@ -737,6 +737,7 @@ export default { set: 'set', changed: 'changed', removed: 'removed', + transactionPending: 'Transaction pending.', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index c4d67adcd54a..0d8de86f63bc 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -46,7 +46,7 @@ function isExpensifyCard(cardID?: number) { * @returns boolean if the cardID is in the cardList from ONYX. Includes Expensify Cards. */ function isCorporateCard(cardID: number) { - return !!allCards[cardID]; + return true; } /** diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 940cba181db7..e8c00f807e06 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,7 +323,7 @@ function ReportScreen({ /> ); - if (isSingleTransactionView) { + if (true) { headerView = ( ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? []), [reportActions]); - if (ReportUtils.isMoneyRequestReport(report)) { - headerView = ( - - ); - } + // if (ReportUtils.isMoneyRequestReport(report)) { + // headerView = ( + // + // ); + // } /** * When false the ReportActionsView will completely unmount and we will show a loader until it returns true. @@ -603,6 +603,7 @@ function ReportScreen({ ); } + console.log('REPORT SCREEN'); return ( diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index eeeb5b95273c..02d139780ed0 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -832,6 +832,7 @@ function ReportActionItem({ ? (Object.values(personalDetails ?? {}).filter((details) => whisperedToAccountIDs.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) : []; const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + return ( @@ -196,6 +195,7 @@ function ReportActionsList({ ), [sortedReportActions, isOffline], ); + const lastActionIndex = sortedVisibleReportActions[0]?.reportActionID; const reportActionSize = useRef(sortedVisibleReportActions.length); const hasNewestReportAction = sortedReportActions?.[0].created === report.lastVisibleActionCreated; From efe88ce911f70c4d74fe90d08c42cb17c7b9b0c9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:31:16 +0200 Subject: [PATCH 033/686] cleanup --- src/languages/es.ts | 5 +++-- src/pages/home/report/ReportActionsList.tsx | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index c9aabe62087a..a6a19d2bf35c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -630,14 +630,14 @@ export default { routePending: 'Ruta pendiente...', receiptScanning: 'Escaneo en curso…', receiptScanInProgress: 'Escaneo en curso…', - receiptScanInProgressDescription: 'Escaneo en curso.', + receiptScanInProgressDescription: ' Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', receiptStatusTitle: 'Escaneando…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', - transactionPendingText: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', + transactionPendingDescription: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' @@ -735,6 +735,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', + transactionPending: 'Transaction pending.', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 0068ed875b82..60a620f186cc 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,7 +6,6 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; From c3cd931cfaccaece3414103983483b7683c8f04f Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 10 Apr 2024 17:39:17 +0200 Subject: [PATCH 034/686] Remove redundant SCREENS.SETTINGS.ROOT route --- src/libs/Navigation/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 04bb7797804e..bffe333900e5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -71,7 +71,6 @@ type BackToParams = { }; type SettingsNavigatorParamList = { - [SCREENS.SETTINGS.ROOT]: undefined; [SCREENS.SETTINGS.SHARE_CODE]: undefined; [SCREENS.SETTINGS.PROFILE.ROOT]: undefined; [SCREENS.SETTINGS.PROFILE.PRONOUNS]: undefined; From 9a664e5d4db4201533fe1d9c9180b6fe4bdf81a9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:43:25 +0200 Subject: [PATCH 035/686] cleanup --- src/pages/home/ReportScreen.tsx | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e8c00f807e06..940cba181db7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,7 +323,7 @@ function ReportScreen({ /> ); - if (true) { + if (isSingleTransactionView) { headerView = ( ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? []), [reportActions]); - // if (ReportUtils.isMoneyRequestReport(report)) { - // headerView = ( - // - // ); - // } + if (ReportUtils.isMoneyRequestReport(report)) { + headerView = ( + + ); + } /** * When false the ReportActionsView will completely unmount and we will show a loader until it returns true. @@ -603,7 +603,6 @@ function ReportScreen({ ); } - console.log('REPORT SCREEN'); return ( From 945872ab306ed01b352caf10a3a3aea01d6cc087 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 10 Apr 2024 17:47:50 +0200 Subject: [PATCH 036/686] Handle mocking useActiveWorkspaceFromNavigationState in tests --- tests/unit/SidebarOrderTest.ts | 1 + tests/unit/SidebarTest.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 2758d43fb81e..ac74fb50245b 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -14,6 +14,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch jest.mock('@libs/Permissions'); jest.mock('@hooks/usePermissions.ts'); jest.mock('@components/Icon/Expensicons'); +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index 23ea0d377634..ba2d950232b5 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -12,6 +12,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked Permissions and Expensicons libraries as well as the usePermissions hook or else the beta tests won't work jest.mock('@src/libs/Permissions'); jest.mock('@src/hooks/usePermissions.ts'); +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') jest.mock('@src/components/Icon/Expensicons'); describe('Sidebar', () => { From 3be474286b2de7510841a1e2d80baf45f203ba4f Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 10 Apr 2024 17:55:50 +0200 Subject: [PATCH 037/686] Run prettier --- tests/unit/SidebarOrderTest.ts | 2 +- tests/unit/SidebarTest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index ac74fb50245b..e0858c4e78b9 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -14,7 +14,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch jest.mock('@libs/Permissions'); jest.mock('@hooks/usePermissions.ts'); jest.mock('@components/Icon/Expensicons'); -jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState'); const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index ba2d950232b5..e35a479c1add 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -12,7 +12,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked Permissions and Expensicons libraries as well as the usePermissions hook or else the beta tests won't work jest.mock('@src/libs/Permissions'); jest.mock('@src/hooks/usePermissions.ts'); -jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState') +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState'); jest.mock('@src/components/Icon/Expensicons'); describe('Sidebar', () => { From d315efb2d08469d717e820189c2d4a388976cfee Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 10 Apr 2024 17:57:36 +0200 Subject: [PATCH 038/686] cleanup --- src/libs/CardUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 0d8de86f63bc..c4d67adcd54a 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -46,7 +46,7 @@ function isExpensifyCard(cardID?: number) { * @returns boolean if the cardID is in the cardList from ONYX. Includes Expensify Cards. */ function isCorporateCard(cardID: number) { - return true; + return !!allCards[cardID]; } /** From 4e08e070c709b6e8350d3cbe05221a8b56098ac0 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 00:26:01 +0200 Subject: [PATCH 039/686] cleanup translations --- src/languages/es.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index a6a19d2bf35c..877d745fa59c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -628,16 +628,16 @@ export default { posted: 'Contabilizado', deleteReceipt: 'Eliminar recibo', routePending: 'Ruta pendiente...', - receiptScanning: 'Escaneo en curso…', - receiptScanInProgress: 'Escaneo en curso…', - receiptScanInProgressDescription: ' Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', + receiptScanning: 'Escaneando recibo…', + receiptScanInProgress: 'Escaneo en curso.', + receiptScanInProgressDescription: 'Escaneando recibo. Vuelva a comprobarlo más tarde o introduzca los detalles ahora.', receiptMissingDetails: 'Recibo con campos vacíos', missingAmount: 'Falta importe', missingMerchant: 'Falta comerciante', receiptStatusTitle: 'Escaneando…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', - transactionPendingDescription: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', + transactionPendingDescription: 'Transacción pendiente. Esto puede tardar algunos días en registrarse a partir de la fecha en que se utilizó la tarjeta.', requestCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${ pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : '' @@ -735,7 +735,7 @@ export default { set: 'estableció', changed: 'cambió', removed: 'eliminó', - transactionPending: 'Transaction pending.', + transactionPending: 'Transacción pendiente.', }, notificationPreferencesPage: { header: 'Preferencias de avisos', From 648e333f6178eb2b4699d024936d4ef281aacd7d Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 00:41:33 +0200 Subject: [PATCH 040/686] fix preview --- .../MoneyRequestPreviewContent.tsx | 12 ++++++++++++ src/pages/home/report/ReportActionsList.tsx | 1 + 2 files changed, 13 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b0363135e273..e149891b0365 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -87,6 +87,7 @@ function MoneyRequestPreviewContent({ const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); + const isPending = TransactionUtils.isPending(transaction); const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial); const isPartialHold = isSettlementOrApprovalPartial && isOnHold; @@ -315,6 +316,17 @@ function MoneyRequestPreviewContent({ {translate('iou.receiptScanInProgress')} )} + {isPending && ( + + + {translate('iou.transactionPending')} + + )} diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 60a620f186cc..0068ed875b82 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -6,6 +6,7 @@ import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 're import {DeviceEventEmitter, InteractionManager} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import InvertedFlatList from '@components/InvertedFlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; From 6cb699dd7a02320971348b2c636e96852f2c5bb8 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 14:05:43 +0300 Subject: [PATCH 041/686] fix touch screen case for image view --- src/components/ImageView/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 2316577158f8..9865adb04d3d 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -213,7 +213,8 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV onLoad={imageLoad} onError={onError} /> - {(isLoading || zoomScale === 0) && } + {((isLoading && !isOffline) || zoomScale === 0) && } + {isLoading && } ); } From cbea4acce2f432b71940929f52b6d99c2daa1b22 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 14:17:38 +0300 Subject: [PATCH 042/686] fix logic for zoom scale --- src/components/ImageView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ImageView/index.tsx b/src/components/ImageView/index.tsx index 9865adb04d3d..f08941ef7d77 100644 --- a/src/components/ImageView/index.tsx +++ b/src/components/ImageView/index.tsx @@ -213,7 +213,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV onLoad={imageLoad} onError={onError} /> - {((isLoading && !isOffline) || zoomScale === 0) && } + {((isLoading && !isOffline) || (!isLoading && zoomScale === 0)) && } {isLoading && } ); From a3c565ae84371cb37342c62946603356a138551c Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 11 Apr 2024 14:03:48 +0200 Subject: [PATCH 043/686] Fix sidebar links perf tests --- tests/perf-test/SidebarLinks.perf-test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/perf-test/SidebarLinks.perf-test.tsx b/tests/perf-test/SidebarLinks.perf-test.tsx index 2848015d5c63..036d4ea84ff9 100644 --- a/tests/perf-test/SidebarLinks.perf-test.tsx +++ b/tests/perf-test/SidebarLinks.perf-test.tsx @@ -10,6 +10,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch jest.mock('@libs/Permissions'); jest.mock('@hooks/usePermissions.ts'); +jest.mock('@src/hooks/useActiveWorkspaceFromNavigationState'); jest.mock('@libs/Navigation/Navigation'); jest.mock('@components/Icon/Expensicons'); From f2bcf10e8e12bef7242a95adbab46f0a3352e04c Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 15:57:59 +0200 Subject: [PATCH 044/686] wip --- ios/Podfile.lock | 2 +- src/components/MoneyRequestHeader.tsx | 2 ++ .../MoneyRequestPreviewContent.tsx | 1 + .../ReportActionItem/ReportPreview.tsx | 14 +++++++++++++- src/libs/ReportUtils.ts | 1 + src/pages/home/ReportScreen.tsx | 2 ++ src/pages/home/report/ReportActionsList.tsx | 19 +++++++++++++++++++ 7 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 32a8bca75bcd..94bd6e35f31d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1921,7 +1921,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 9f26224fce1233ffdad9fa4e56863e3de2190dc0 - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: a431c146e1501391834a2f299a74093bac53b530 diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5ea46f339d46..909b6e6f71f6 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -165,6 +165,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, }); } + console.log('MONEY REQUEST HEADER'); + return ( <> diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index e149891b0365..2a56e2f2405d 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -67,6 +67,7 @@ function MoneyRequestPreviewContent({ const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); const parser = new ExpensiMark(); + console.warn('TRANSACTION ', transaction); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; const ownerAccountID = iouReport?.ownerAccountID ?? -1; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 40c9f1afcc21..5b9c2269028c 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -228,7 +228,8 @@ function ReportPreview({ const shouldShowSingleRequestMerchantOrDescription = numberOfRequests === 1 && (!!formattedMerchant || !!formattedDescription) && !(hasOnlyTransactionsWithPendingRoutes && !totalDisplaySpend); const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchantOrDescription || numberOfRequests > 1); - const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && transactionsWithReceipts.length === 1; + const shouldShowScanningSubtitle = numberOfScanningReceipts === 1 && allTransactions.length === 1; + const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && allTransactions.length === 1; const {isSupportTextHtml, supportText} = useMemo(() => { if (formattedMerchant) { @@ -321,6 +322,17 @@ function ReportPreview({ )} + {shouldShowScanningSubtitle && ( + + + {translate('iou.receiptScanInProgress')} + + )} {shouldShowPendingSubtitle && ( ): boolean { */ function isMoneyRequest(reportOrID: OnyxEntry | string): boolean { const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID; + console.log('REPORT ', report); return isIOURequest(report) || isExpenseRequest(report); } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 940cba181db7..de02342ff414 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,6 +323,8 @@ function ReportScreen({ /> ); + console.warn('ReportUtils.isMoneyRequest(report) ', ReportUtils.isMoneyRequest(report)); + if (isSingleTransactionView) { headerView = ( { const unsubscriber = Visibility.onVisibilityChange(() => { setIsVisible(Visibility.isVisible()); From c3285b8e4baf6fc2cf64dd0cc17dd3c3fb10c6ec Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Thu, 11 Apr 2024 16:18:56 +0200 Subject: [PATCH 045/686] cleanup --- src/components/MoneyRequestHeader.tsx | 2 -- .../MoneyRequestPreviewContent.tsx | 1 - src/libs/ReportUtils.ts | 1 - src/pages/home/ReportScreen.tsx | 2 -- src/pages/home/report/ReportActionsList.tsx | 20 ------------------- 5 files changed, 26 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 909b6e6f71f6..5ea46f339d46 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -165,8 +165,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, }); } - console.log('MONEY REQUEST HEADER'); - return ( <> diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 2a56e2f2405d..e149891b0365 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -67,7 +67,6 @@ function MoneyRequestPreviewContent({ const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); const parser = new ExpensiMark(); - console.warn('TRANSACTION ', transaction); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; const ownerAccountID = iouReport?.ownerAccountID ?? -1; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f03a5051d634..fec64efaac7f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1282,7 +1282,6 @@ function isTrackExpenseReport(report: OnyxEntry): boolean { */ function isMoneyRequest(reportOrID: OnyxEntry | string): boolean { const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID; - console.log('REPORT ', report); return isIOURequest(report) || isExpenseRequest(report); } diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index de02342ff414..940cba181db7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -323,8 +323,6 @@ function ReportScreen({ /> ); - console.warn('ReportUtils.isMoneyRequest(report) ', ReportUtils.isMoneyRequest(report)); - if (isSingleTransactionView) { headerView = ( { const unsubscriber = Visibility.onVisibilityChange(() => { setIsVisible(Visibility.isVisible()); From bffd5f03956f13c18f4d49cb25fc5ecc8d64b241 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Apr 2024 11:24:10 +0700 Subject: [PATCH 046/686] implement internet reachability for android --- src/CONST.ts | 1 + src/libs/NetworkConnection.ts | 17 ++++++++++--- .../index.android.ts | 24 +++++++++++++++++++ src/libs/checkInternetReachability/index.ts | 5 ++++ src/libs/checkInternetReachability/types.ts | 3 +++ 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/libs/checkInternetReachability/index.android.ts create mode 100644 src/libs/checkInternetReachability/index.ts create mode 100644 src/libs/checkInternetReachability/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index 0ce5458e0849..9e4e41a64c38 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -543,6 +543,7 @@ const CONST = { STATUS_EXPENSIFY_URL: 'https://status.expensify.com', GOOGLE_MEET_URL_ANDROID: 'https://meet.google.com', GOOGLE_DOC_IMAGE_LINK_MATCH: 'googleusercontent.com', + GOOGLE_CLOUD_URL: 'https://clients3.google.com/generate_204', IMAGE_BASE64_MATCH: 'base64', DEEPLINK_BASE_URL: 'new-expensify://', PDF_VIEWER_URL: '/pdf/web/viewer.html', diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 4383a921662f..c2b348cc0f6f 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -6,6 +6,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; +import checkInternetReachability from './checkInternetReachability'; import Log from './Log'; let isOffline = false; @@ -69,10 +70,11 @@ Onyx.connect({ }); /** - * Set interval to periodically (re)check backend status + * Set interval to periodically (re)check backend status. + * Because backend unreachability might imply lost internet connection, we need to check internet reachability. * @returns clearInterval cleanup */ -function subscribeToBackendReachability(): () => void { +function subscribeToBackendAndInternetReachability(): () => void { const intervalID = setInterval(() => { // Offline status also implies backend unreachability if (isOffline) { @@ -92,6 +94,15 @@ function subscribeToBackendReachability(): () => void { .then((json) => Promise.resolve(json.jsonCode === 200)) .catch(() => Promise.resolve(false)); }) + .then((isBackendReachable: boolean) => { + if (isBackendReachable) { + return Promise.resolve(true); + } + return checkInternetReachability().then((isInternetReachable: boolean) => { + NetworkActions.setIsOffline(!isInternetReachable); + return Promise.resolve(false); + }); + }) .then(NetworkActions.setIsBackendReachable) .catch(() => NetworkActions.setIsBackendReachable(false)); }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); @@ -108,7 +119,7 @@ function subscribeToBackendReachability(): () => void { function subscribeToNetworkStatus(): () => void { // Note: We are disabling the reachability check when using the local web API since requests can get stuck in a 'Pending' state and are not reliable indicators for reachability. // If you need to test the "recheck" feature then switch to the production API proxy server. - const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendReachability() : undefined; + const unsubscribeFromBackendReachability = !CONFIG.IS_USING_LOCAL_WEB ? subscribeToBackendAndInternetReachability() : undefined; // Set up the event listener for NetInfo to tell whether the user has // internet connectivity or not. This is more reliable than the Pusher diff --git a/src/libs/checkInternetReachability/index.android.ts b/src/libs/checkInternetReachability/index.android.ts new file mode 100644 index 000000000000..2dfb015dc9ed --- /dev/null +++ b/src/libs/checkInternetReachability/index.android.ts @@ -0,0 +1,24 @@ +import CONST from '@src/CONST'; +import type InternetReachabilityCheck from './types'; + +/** + * Although Android supports internet reachability check, it only does on initiating the connection. + * We need to implement a test for a highly-available endpoint to cover the case internet is lost during connection. + */ +export default function checkInternetReachability(): InternetReachabilityCheck { + // Using the API url ensures reachability is tested over internet + return fetch(CONST.GOOGLE_CLOUD_URL, { + method: 'GET', + cache: 'no-cache', + }) + .then((response) => { + if (!response.ok) { + return Promise.resolve(false); + } + return response + .json() + .then((json) => Promise.resolve(json.jsonCode === 204)) + .catch(() => Promise.resolve(false)); + }) + .catch(() => Promise.resolve(false)); +} diff --git a/src/libs/checkInternetReachability/index.ts b/src/libs/checkInternetReachability/index.ts new file mode 100644 index 000000000000..d7d0808c6efc --- /dev/null +++ b/src/libs/checkInternetReachability/index.ts @@ -0,0 +1,5 @@ +import type InternetReachabilityCheck from './types'; + +export default function checkInternetReachability(): InternetReachabilityCheck { + return Promise.resolve(true); +} diff --git a/src/libs/checkInternetReachability/types.ts b/src/libs/checkInternetReachability/types.ts new file mode 100644 index 000000000000..3e1cb96cb26c --- /dev/null +++ b/src/libs/checkInternetReachability/types.ts @@ -0,0 +1,3 @@ +type InternetReachabilityCheck = Promise; + +export default InternetReachabilityCheck; From e3644d1acd2a3bba3ae94caa5a1e5ee2219ad280 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 08:22:14 +0200 Subject: [PATCH 047/686] Remove SCREENS.WORKSPACES_CENTRAL_PANE --- .../Navigators/FullScreenNavigator.tsx | 77 +++++++++++++++-- .../CustomFullScreenRouter.tsx | 20 +---- .../getTopmostWorkspacesCentralPaneName.ts | 17 +--- src/libs/Navigation/linkingConfig/config.ts | 82 +++++++++---------- .../linkingConfig/getAdaptedStateFromPath.ts | 6 +- src/libs/Navigation/types.ts | 56 ++++++++++++- 6 files changed, 172 insertions(+), 86 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 07b069462dd1..065e11495a01 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -5,7 +5,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; -import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators'; import SCREENS from '@src/SCREENS'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; @@ -17,19 +16,85 @@ function FullScreenNavigator() { const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles, StyleUtils); - return ( - + require('@pages/workspace/WorkspaceProfilePage').default as React.ComponentType} + /> + require('@pages/workspace/card/WorkspaceCardPage').default as React.ComponentType} + /> + require('@pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType} + /> + require('@pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType} + /> + require('@pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType} + /> + require('@pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType} + /> + require('@pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType} + /> + require('@pages/workspace/WorkspaceMembersPage').default as React.ComponentType} + /> + + require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType} + /> + + require('@pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType} + /> + require('@pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType} + /> + require('@pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType} + /> + require('@pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType} + /> + require('@pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType} /> diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx index eb19f891ecd5..99e6fb23b8ba 100644 --- a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx @@ -11,11 +11,6 @@ const isAtLeastOneInState = (state: StackState, screenName: string): boolean => function adaptStateIfNecessary(state: StackState) { const isNarrowLayout = getIsNarrowLayout(); const workspaceCentralPane = state.routes.at(-1); - const topmostWorkspaceCentralPaneRoute = workspaceCentralPane?.state?.routes[0]; - - // When a screen from the FullScreenNavigator is opened from the deeplink then params should be passed to SCREENS.WORKSPACE.INITIAL from the variable defined below. - const workspacesCentralPaneParams = - workspaceCentralPane?.params && 'params' in workspaceCentralPane.params ? (workspaceCentralPane.params.params as Record) : undefined; // There should always be WORKSPACE.INITIAL screen in the state to make sure go back works properly if we deeplinkg to a subpage of settings. if (!isAtLeastOneInState(state, SCREENS.WORKSPACE.INITIAL)) { @@ -28,7 +23,7 @@ function adaptStateIfNecessary(state: StackState) { // Unshift the root screen to fill left pane. state.routes.unshift({ name: SCREENS.WORKSPACE.INITIAL, - params: topmostWorkspaceCentralPaneRoute?.params ?? workspacesCentralPaneParams, + params: workspaceCentralPane?.params, }); } } @@ -37,22 +32,15 @@ function adaptStateIfNecessary(state: StackState) { // - WORKSPACE.INITIAL to cover left pane. // - WORKSPACES_CENTRAL_PANE to cover central pane. if (!isNarrowLayout) { - if (!isAtLeastOneInState(state, SCREENS.WORKSPACES_CENTRAL_PANE)) { + if (state.routes.length === 1 && state.routes[0].name === SCREENS.WORKSPACE.INITIAL) { // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.stale = true; // eslint-disable-line // Push the default settings central pane screen. if (state.stale === true) { state.routes.push({ - name: SCREENS.WORKSPACES_CENTRAL_PANE, - state: { - routes: [ - { - name: SCREENS.WORKSPACE.PROFILE, - params: state.routes[0]?.params, - }, - ], - }, + name: SCREENS.WORKSPACE.PROFILE, + params: state.routes[0]?.params, }); } } diff --git a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts index db11368c1345..ec3159ade059 100644 --- a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts +++ b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts @@ -1,5 +1,4 @@ import type {NavigationState, PartialState} from '@react-navigation/native'; -import SCREENS from '@src/SCREENS'; // Get the name of topmost report in the navigation stack. function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialState): string | undefined { @@ -7,21 +6,7 @@ function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialSta return; } - const topmostCentralPane = state.routes.filter((route) => typeof route !== 'number' && 'name' in route && route.name === SCREENS.WORKSPACES_CENTRAL_PANE).at(-1); - - if (!topmostCentralPane) { - return; - } - - if (!!topmostCentralPane.params && 'screen' in topmostCentralPane.params && typeof topmostCentralPane.params.screen === 'string') { - return topmostCentralPane.params.screen; - } - - if (!topmostCentralPane.state) { - return; - } - - return topmostCentralPane.state?.routes.at(-1)?.name; + return state.routes.at(-1)?.name } export default getTopmostWorkspacesCentralPaneName; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 95294b7711b5..468a5ce8c3d9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -635,49 +635,45 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.INITIAL]: { path: ROUTES.WORKSPACE_INITIAL.route, }, - [SCREENS.WORKSPACES_CENTRAL_PANE]: { - screens: { - [SCREENS.WORKSPACE.PROFILE]: ROUTES.WORKSPACE_PROFILE.route, - [SCREENS.WORKSPACE.CARD]: { - path: ROUTES.WORKSPACE_CARD.route, - }, - [SCREENS.WORKSPACE.WORKFLOWS]: { - path: ROUTES.WORKSPACE_WORKFLOWS.route, - }, - [SCREENS.WORKSPACE.REIMBURSE]: { - path: ROUTES.WORKSPACE_REIMBURSE.route, - }, - [SCREENS.WORKSPACE.BILLS]: { - path: ROUTES.WORKSPACE_BILLS.route, - }, - [SCREENS.WORKSPACE.INVOICES]: { - path: ROUTES.WORKSPACE_INVOICES.route, - }, - [SCREENS.WORKSPACE.TRAVEL]: { - path: ROUTES.WORKSPACE_TRAVEL.route, - }, - [SCREENS.WORKSPACE.MEMBERS]: { - path: ROUTES.WORKSPACE_MEMBERS.route, - }, - [SCREENS.WORKSPACE.ACCOUNTING]: { - path: ROUTES.WORKSPACE_ACCOUNTING.route, - }, - [SCREENS.WORKSPACE.CATEGORIES]: { - path: ROUTES.WORKSPACE_CATEGORIES.route, - }, - [SCREENS.WORKSPACE.MORE_FEATURES]: { - path: ROUTES.WORKSPACE_MORE_FEATURES.route, - }, - [SCREENS.WORKSPACE.TAGS]: { - path: ROUTES.WORKSPACE_TAGS.route, - }, - [SCREENS.WORKSPACE.TAXES]: { - path: ROUTES.WORKSPACE_TAXES.route, - }, - [SCREENS.WORKSPACE.DISTANCE_RATES]: { - path: ROUTES.WORKSPACE_DISTANCE_RATES.route, - }, - }, + [SCREENS.WORKSPACE.PROFILE]: ROUTES.WORKSPACE_PROFILE.route, + [SCREENS.WORKSPACE.CARD]: { + path: ROUTES.WORKSPACE_CARD.route, + }, + [SCREENS.WORKSPACE.WORKFLOWS]: { + path: ROUTES.WORKSPACE_WORKFLOWS.route, + }, + [SCREENS.WORKSPACE.REIMBURSE]: { + path: ROUTES.WORKSPACE_REIMBURSE.route, + }, + [SCREENS.WORKSPACE.BILLS]: { + path: ROUTES.WORKSPACE_BILLS.route, + }, + [SCREENS.WORKSPACE.INVOICES]: { + path: ROUTES.WORKSPACE_INVOICES.route, + }, + [SCREENS.WORKSPACE.TRAVEL]: { + path: ROUTES.WORKSPACE_TRAVEL.route, + }, + [SCREENS.WORKSPACE.MEMBERS]: { + path: ROUTES.WORKSPACE_MEMBERS.route, + }, + [SCREENS.WORKSPACE.ACCOUNTING]: { + path: ROUTES.WORKSPACE_ACCOUNTING.route, + }, + [SCREENS.WORKSPACE.CATEGORIES]: { + path: ROUTES.WORKSPACE_CATEGORIES.route, + }, + [SCREENS.WORKSPACE.MORE_FEATURES]: { + path: ROUTES.WORKSPACE_MORE_FEATURES.route, + }, + [SCREENS.WORKSPACE.TAGS]: { + path: ROUTES.WORKSPACE_TAGS.route, + }, + [SCREENS.WORKSPACE.TAXES]: { + path: ROUTES.WORKSPACE_TAXES.route, + }, + [SCREENS.WORKSPACE.DISTANCE_RATES]: { + path: ROUTES.WORKSPACE_DISTANCE_RATES.route, }, }, }, diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index d7bcfbb68952..52e62604c0a5 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -86,11 +86,9 @@ function createFullScreenNavigator(route?: NavigationPartialRoute; + [SCREENS.WORKSPACE.PROFILE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.CARD]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: { + policyID: string; + }; + [SCREENS.WORKSPACE.REIMBURSE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.BILLS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.INVOICES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TRAVEL]: { + policyID: string; + }; + [SCREENS.WORKSPACE.MEMBERS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING]: { + policyID: string; + }; + [SCREENS.WORKSPACE.CATEGORIES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.MORE_FEATURES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TAGS]: { + policyID: string; + tagName: string; + }; + [SCREENS.WORKSPACE.TAXES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.DISTANCE_RATES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING]: { + policyID: string; + }; }; type OnboardingModalNavigatorParamList = { From 4b1681c76db57613511ac7bbe4be900bbe7954fd Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 08:44:20 +0200 Subject: [PATCH 048/686] Add a comment to useActiveWorkspaceFromNavigationState --- src/hooks/useActiveWorkspaceFromNavigationState.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useActiveWorkspaceFromNavigationState.ts b/src/hooks/useActiveWorkspaceFromNavigationState.ts index 8f9da3d9df37..63a6865a516b 100644 --- a/src/hooks/useActiveWorkspaceFromNavigationState.ts +++ b/src/hooks/useActiveWorkspaceFromNavigationState.ts @@ -5,6 +5,7 @@ import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; * Get the currently selected policy ID stored in the navigation state. This hook should only be called only from screens in BottomTab. */ function useActiveWorkspaceFromNavigationState() { + // The last policyID value is always stored in the last route in BottomTabNavigator. const activeWorkpsaceID = useNavigationState((state) => state.routes.at(-1)?.params?.policyID); return activeWorkpsaceID; From 01aafb06eecf79d97444b3e57d41905136723a65 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Apr 2024 14:58:36 +0700 Subject: [PATCH 049/686] modify fetch logic --- src/libs/NetworkConnection.ts | 17 +++++++++++------ .../checkInternetReachability/index.android.ts | 10 +--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index c2b348cc0f6f..86253b0cca78 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -96,15 +96,20 @@ function subscribeToBackendAndInternetReachability(): () => void { }) .then((isBackendReachable: boolean) => { if (isBackendReachable) { - return Promise.resolve(true); + NetworkActions.setIsBackendReachable(true); + return; } - return checkInternetReachability().then((isInternetReachable: boolean) => { - NetworkActions.setIsOffline(!isInternetReachable); - return Promise.resolve(false); + checkInternetReachability().then((isInternetReachable: boolean) => { + setOfflineStatus(!isInternetReachable); + NetworkActions.setIsBackendReachable(false); }); }) - .then(NetworkActions.setIsBackendReachable) - .catch(() => NetworkActions.setIsBackendReachable(false)); + .catch(() => { + checkInternetReachability().then((isInternetReachable: boolean) => { + setOfflineStatus(!isInternetReachable); + NetworkActions.setIsBackendReachable(false); + }); + }); }, CONST.NETWORK.BACKEND_CHECK_INTERVAL_MS); return () => { diff --git a/src/libs/checkInternetReachability/index.android.ts b/src/libs/checkInternetReachability/index.android.ts index 2dfb015dc9ed..9e1a0170d3e4 100644 --- a/src/libs/checkInternetReachability/index.android.ts +++ b/src/libs/checkInternetReachability/index.android.ts @@ -11,14 +11,6 @@ export default function checkInternetReachability(): InternetReachabilityCheck { method: 'GET', cache: 'no-cache', }) - .then((response) => { - if (!response.ok) { - return Promise.resolve(false); - } - return response - .json() - .then((json) => Promise.resolve(json.jsonCode === 204)) - .catch(() => Promise.resolve(false)); - }) + .then((response) => Promise.resolve(response.status === 204)) .catch(() => Promise.resolve(false)); } From c851a49ecc1b83c3bc642b331d866c78d48804b8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Apr 2024 15:08:23 +0700 Subject: [PATCH 050/686] modify comment --- src/libs/checkInternetReachability/index.android.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/checkInternetReachability/index.android.ts b/src/libs/checkInternetReachability/index.android.ts index 9e1a0170d3e4..da46623c3e05 100644 --- a/src/libs/checkInternetReachability/index.android.ts +++ b/src/libs/checkInternetReachability/index.android.ts @@ -3,10 +3,9 @@ import type InternetReachabilityCheck from './types'; /** * Although Android supports internet reachability check, it only does on initiating the connection. - * We need to implement a test for a highly-available endpoint to cover the case internet is lost during connection. + * We need to implement a test for a highly-available endpoint in case of lost internet after initiation. */ export default function checkInternetReachability(): InternetReachabilityCheck { - // Using the API url ensures reachability is tested over internet return fetch(CONST.GOOGLE_CLOUD_URL, { method: 'GET', cache: 'no-cache', From bc8db3bb0d71df4415b80ebe1a79da7368f084cd Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 11:22:09 +0200 Subject: [PATCH 051/686] Refactor FullScreenNavigator --- .../Navigators/FullScreenNavigator.tsx | 99 +++++-------------- .../CustomFullScreenRouter.tsx | 2 +- src/libs/Navigation/getTopmostRouteName.ts | 12 +++ .../getTopmostWorkspacesCentralPaneName.ts | 12 --- src/pages/workspace/WorkspaceInitialPage.tsx | 4 +- 5 files changed, 42 insertions(+), 87 deletions(-) create mode 100644 src/libs/Navigation/getTopmostRouteName.ts delete mode 100644 src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 065e11495a01..42bca6e2de4c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -5,12 +5,32 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; +import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; const RootStack = createCustomFullScreenNavigator(); +type Screens = Partial React.ComponentType>>; + +const workspacesScreens = { + [SCREENS.WORKSPACE.PROFILE]: () => require('../../../../pages/workspace/WorkspaceProfilePage').default as React.ComponentType, + [SCREENS.WORKSPACE.CARD]: () => require('../../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, + [SCREENS.WORKSPACE.WORKFLOWS]: () => require('../../../../pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, + [SCREENS.WORKSPACE.BILLS]: () => require('../../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.INVOICES]: () => require('../../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType, + [SCREENS.WORKSPACE.ACCOUNTING]: () => require('../../../../pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAXES]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType, +} satisfies Screens; + function FullScreenNavigator() { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -24,78 +44,13 @@ function FullScreenNavigator() { options={screenOptions.homeScreen} getComponent={loadWorkspaceInitialPage} /> - require('@pages/workspace/WorkspaceProfilePage').default as React.ComponentType} - /> - require('@pages/workspace/card/WorkspaceCardPage').default as React.ComponentType} - /> - require('@pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType} - /> - require('@pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType} - /> - require('@pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType} - /> - require('@pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType} - /> - require('@pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMembersPage').default as React.ComponentType} - /> - - require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType} - /> - - require('@pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType} - /> - require('@pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType} - /> - require('@pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType} - /> - require('@pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType} - /> + {Object.entries(workspacesScreens).map(([screenName, componentGetter]) => ( + + ))} ); diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx index 99e6fb23b8ba..27e976d9be0c 100644 --- a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx @@ -30,7 +30,7 @@ function adaptStateIfNecessary(state: StackState) { // If the screen is wide, there should be at least two screens inside: // - WORKSPACE.INITIAL to cover left pane. - // - WORKSPACES_CENTRAL_PANE to cover central pane. + // - WORKSPACE.PROFILE (first workspace settings screen) to cover central pane. if (!isNarrowLayout) { if (state.routes.length === 1 && state.routes[0].name === SCREENS.WORKSPACE.INITIAL) { // @ts-expect-error Updating read only property diff --git a/src/libs/Navigation/getTopmostRouteName.ts b/src/libs/Navigation/getTopmostRouteName.ts new file mode 100644 index 000000000000..7ae3afaf2cc9 --- /dev/null +++ b/src/libs/Navigation/getTopmostRouteName.ts @@ -0,0 +1,12 @@ +import type {NavigationState, PartialState} from '@react-navigation/native'; + +// Get the name of topmost route in the navigation stack. +function getTopmostRouteName(state: NavigationState | PartialState): string | undefined { + if (!state) { + return; + } + + return state.routes.at(-1)?.name; +} + +export default getTopmostRouteName; diff --git a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts deleted file mode 100644 index ec3159ade059..000000000000 --- a/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {NavigationState, PartialState} from '@react-navigation/native'; - -// Get the name of topmost report in the navigation stack. -function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialState): string | undefined { - if (!state) { - return; - } - - return state.routes.at(-1)?.name -} - -export default getTopmostWorkspacesCentralPaneName; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index a6a131f5372c..f1c215447e4e 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -19,7 +19,7 @@ import usePrevious from '@hooks/usePrevious'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; -import getTopmostWorkspacesCentralPaneName from '@libs/Navigation/getTopmostWorkspacesCentralPaneName'; +import getTopmostRouteName from '@libs/Navigation/getTopmostRouteName'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; @@ -68,7 +68,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const hasPolicyCreationError = !!(policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && policy.errors); const waitForNavigate = useWaitForNavigation(); const {singleExecution, isExecuting} = useSingleExecution(); - const activeRoute = useNavigationState(getTopmostWorkspacesCentralPaneName); + const activeRoute = useNavigationState(getTopmostRouteName); const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); From a451aaf0ea7d7322f4439d78c0701cfae66bf3be Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 11:42:16 +0200 Subject: [PATCH 052/686] Refactor styles in FullScreenNavigator --- .../AppNavigator/Navigators/FullScreenNavigator.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 42bca6e2de4c..3b2d64db9778 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -7,6 +7,7 @@ import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/creat import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; +import useModalScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalScreenOptions'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; @@ -36,9 +37,11 @@ function FullScreenNavigator() { const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles, StyleUtils); + const workspaceScreenOptions = useModalScreenOptions((screenStyles) => ({cardStyle: screenStyles.navigationScreenCardStyle, headerShown: false})); + return ( - + Date: Fri, 12 Apr 2024 11:42:44 +0200 Subject: [PATCH 053/686] Remove WorkspaceSettingsModalStackNavigator --- .../WorkspaceSettingsModalStackNavigator.tsx | 89 ------------------- .../ModalStackNavigators/index.tsx | 2 - 2 files changed, 91 deletions(-) delete mode 100644 src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx deleted file mode 100644 index 2dce4247c7ae..000000000000 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/WorkspaceSettingsModalStackNavigator.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import {createStackNavigator} from '@react-navigation/stack'; -import React from 'react'; -import SCREENS from '@src/SCREENS'; -import useModalScreenOptions from './useModalScreenOptions'; - -const StackNavigator = createStackNavigator(); - -function WorkspaceSettingsModalStackNavigator() { - const screenOptions = useModalScreenOptions((styles) => ({cardStyle: styles.navigationScreenCardStyle, headerShown: false})); - - return ( - - require('@pages/workspace/WorkspaceProfilePage').default as React.ComponentType} - /> - require('@pages/workspace/card/WorkspaceCardPage').default as React.ComponentType} - /> - require('@pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType} - /> - require('@pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType} - /> - require('@pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType} - /> - require('@pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType} - /> - require('@pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMembersPage').default as React.ComponentType} - /> - - require('@pages/workspace/accounting/WorkspaceAccountingPage').default as React.ComponentType} - /> - - require('@pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType} - /> - require('@pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType} - /> - require('@pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType} - /> - require('@pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType} - /> - require('@pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType} - /> - - ); -} - -export default WorkspaceSettingsModalStackNavigator; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index c251f8143631..e8a5f5d17cb5 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -35,7 +35,6 @@ import type {ThemeStyles} from '@styles/index'; import type {Screen} from '@src/SCREENS'; import SCREENS from '@src/SCREENS'; import useModalScreenOptions from './useModalScreenOptions'; -import WorkspaceSettingsModalStackNavigator from './WorkspaceSettingsModalStackNavigator'; type Screens = Partial React.ComponentType>>; @@ -345,5 +344,4 @@ export { TaskModalStackNavigator, WalletStatementStackNavigator, ProcessMoneyRequestHoldStackNavigator, - WorkspaceSettingsModalStackNavigator, }; From 0578de4d75897be641adddb942c578f4fff13c23 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 12 Apr 2024 12:48:05 +0200 Subject: [PATCH 054/686] Remove wrapper for lhp screens --- src/SCREENS.ts | 3 --- .../ModalStackNavigators/index.tsx | 18 +++--------------- .../Navigators/FullScreenNavigator.tsx | 2 +- .../Navigators/LeftModalNavigator.tsx | 8 +++++--- src/libs/Navigation/linkingConfig/config.ts | 12 ++---------- src/libs/Navigation/types.ts | 16 +++------------- src/pages/SearchPage/index.tsx | 2 +- src/styles/theme/themes/dark.ts | 2 +- src/styles/theme/themes/light.ts | 2 +- tests/perf-test/SearchPage.perf-test.tsx | 2 +- 10 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index b3c2012e90d2..7b653da0ea29 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -98,9 +98,6 @@ const SCREENS = { SEARCH: 'Search', WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, - WORKSPACE_SWITCHER: { - ROOT: 'WorkspaceSwitcher_Root', - }, RIGHT_MODAL: { SETTINGS: 'Settings', NEW_CHAT: 'NewChat', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index e8a5f5d17cb5..94298d24f37e 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -22,14 +22,12 @@ import type { ReportSettingsNavigatorParamList, RoomInviteNavigatorParamList, RoomMembersNavigatorParamList, - SearchNavigatorParamList, SettingsNavigatorParamList, SignInNavigatorParamList, SplitDetailsNavigatorParamList, TaskDetailsNavigatorParamList, TeachersUniteNavigatorParamList, WalletStatementNavigatorParamList, - WorkspaceSwitcherNavigatorParamList, } from '@navigation/types'; import type {ThemeStyles} from '@styles/index'; import type {Screen} from '@src/SCREENS'; @@ -142,10 +140,6 @@ const RoomInviteModalStackNavigator = createModalStackNavigator require('../../../../pages/RoomInvitePage').default as React.ComponentType, }); -const SearchModalStackNavigator = createModalStackNavigator({ - [SCREENS.SEARCH_ROOT]: () => require('../../../../pages/SearchPage').default as React.ComponentType, -}); - const NewChatModalStackNavigator = createModalStackNavigator({ [SCREENS.NEW_CHAT.ROOT]: () => require('../../../../pages/NewChatSelectorPage').default as React.ComponentType, [SCREENS.NEW_CHAT.NEW_CHAT_CONFIRM]: () => require('../../../../pages/NewChatConfirmPage').default as React.ComponentType, @@ -173,10 +167,6 @@ const NewTeachersUniteNavigator = createModalStackNavigator require('../../../../pages/TeachersUnite/ImTeacherPage').default as React.ComponentType, }); -const WorkspaceSwitcherModalStackNavigator = createModalStackNavigator({ - [SCREENS.WORKSPACE_SWITCHER.ROOT]: () => require('../../../../pages/WorkspaceSwitcherPage').default as React.ComponentType, -}); - const SettingsModalStackNavigator = createModalStackNavigator({ [SCREENS.SETTINGS.SHARE_CODE]: () => require('../../../../pages/ShareCodePage').default as React.ComponentType, [SCREENS.SETTINGS.PROFILE.PRONOUNS]: () => require('../../../../pages/settings/Profile/PronounsPage').default as React.ComponentType, @@ -318,7 +308,6 @@ const ProcessMoneyRequestHoldStackNavigator = createModalStackNavigator({ export { AddPersonalBankAccountModalStackNavigator, DetailsModalStackNavigator, - OnboardEngagementModalStackNavigator, EditRequestStackNavigator, EnablePaymentsStackNavigator, FlagCommentStackNavigator, @@ -326,22 +315,21 @@ export { NewChatModalStackNavigator, NewTaskModalStackNavigator, NewTeachersUniteNavigator, + OnboardEngagementModalStackNavigator, PrivateNotesModalStackNavigator, + ProcessMoneyRequestHoldStackNavigator, ProfileModalStackNavigator, ReferralModalStackNavigator, - WorkspaceSwitcherModalStackNavigator, ReimbursementAccountModalStackNavigator, + ReportDescriptionModalStackNavigator, ReportDetailsModalStackNavigator, ReportParticipantsModalStackNavigator, ReportSettingsModalStackNavigator, - ReportDescriptionModalStackNavigator, RoomInviteModalStackNavigator, RoomMembersModalStackNavigator, - SearchModalStackNavigator, SettingsModalStackNavigator, SignInModalStackNavigator, SplitDetailsModalStackNavigator, TaskModalStackNavigator, WalletStatementStackNavigator, - ProcessMoneyRequestHoldStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 3b2d64db9778..f6fdba422fae 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -5,9 +5,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; +import useModalScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalScreenOptions'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; -import useModalScreenOptions from '@libs/Navigation/AppNavigator/ModalStackNavigators/useModalScreenOptions'; const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index 8f76d8fbdd7b..f83f0d7f0d59 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -6,7 +6,6 @@ import NoDropZone from '@components/DragAndDrop/NoDropZone'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/ModalNavigatorScreenOptions'; -import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators'; import type {AuthScreensParamList, LeftModalNavigatorParamList} from '@libs/Navigation/types'; import type NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; @@ -14,6 +13,9 @@ import Overlay from './Overlay'; type LeftModalNavigatorProps = StackScreenProps; +const loadSearchPage = () => require('../../../../pages/SearchPage').default as React.ComponentType; +const loadWorkspaceSwitcherPage = () => require('../../../../pages/WorkspaceSwitcherPage').default as React.ComponentType; + const Stack = createStackNavigator(); function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { @@ -33,11 +35,11 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 468a5ce8c3d9..3e14d0f01ac1 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -67,17 +67,9 @@ const config: LinkingOptions['config'] = { [SCREENS.NOT_FOUND]: '*', [NAVIGATORS.LEFT_MODAL_NAVIGATOR]: { screens: { - [SCREENS.LEFT_MODAL.SEARCH]: { - screens: { - [SCREENS.SEARCH_ROOT]: ROUTES.SEARCH, - }, - }, + [SCREENS.LEFT_MODAL.SEARCH]: ROUTES.SEARCH, [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { - screens: { - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { - path: ROUTES.WORKSPACE_SWITCHER, - }, - }, + path: ROUTES.WORKSPACE_SWITCHER, }, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c51be87b4191..c8587b2b84c6 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -62,10 +62,6 @@ type CentralPaneNavigatorParamList = { [SCREENS.SETTINGS.SAVE_THE_WORLD]: undefined; }; -type WorkspaceSwitcherNavigatorParamList = { - [SCREENS.WORKSPACE_SWITCHER.ROOT]: undefined; -}; - type BackToParams = { backTo?: Routes; }; @@ -294,10 +290,6 @@ type NewChatNavigatorParamList = { [SCREENS.NEW_CHAT.ROOT]: undefined; }; -type SearchNavigatorParamList = { - [SCREENS.SEARCH_ROOT]: undefined; -}; - type DetailsNavigatorParamList = { [SCREENS.DETAILS_ROOT]: { login: string; @@ -568,8 +560,8 @@ type PrivateNotesNavigatorParamList = { }; type LeftModalNavigatorParamList = { - [SCREENS.LEFT_MODAL.SEARCH]: NavigatorScreenParams; - [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: NavigatorScreenParams; + [SCREENS.LEFT_MODAL.SEARCH]: undefined; + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: undefined; }; type RightModalNavigatorParamList = { @@ -798,7 +790,7 @@ type AuthScreensParamList = SharedScreensParamList & { }; }; -type RootStackParamList = PublicScreensParamList & AuthScreensParamList & SearchNavigatorParamList; +type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList; type BottomTabName = keyof BottomTabNavigatorParamList; @@ -842,7 +834,6 @@ export type { ParticipantsNavigatorParamList, RoomMembersNavigatorParamList, RoomInviteNavigatorParamList, - SearchNavigatorParamList, NewChatNavigatorParamList, NewTaskNavigatorParamList, TeachersUniteNavigatorParamList, @@ -857,7 +848,6 @@ export type { ReferralDetailsNavigatorParamList, ReimbursementAccountNavigatorParamList, State, - WorkspaceSwitcherNavigatorParamList, OnboardEngagementNavigatorParamList, SwitchPolicyIDParams, FullScreenNavigatorParamList, diff --git a/src/pages/SearchPage/index.tsx b/src/pages/SearchPage/index.tsx index 7d2a5bfecbb8..99c2b084aec0 100644 --- a/src/pages/SearchPage/index.tsx +++ b/src/pages/SearchPage/index.tsx @@ -35,7 +35,7 @@ type SearchPageOnyxProps = { isSearchingForReports: OnyxEntry; }; -type SearchPageProps = SearchPageOnyxProps & StackScreenProps; +type SearchPageProps = SearchPageOnyxProps & StackScreenProps; type Options = OptionsListUtils.Options & {headerMessage: string}; diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts index e43341f3e8a9..2c3e36a584a2 100644 --- a/src/styles/theme/themes/dark.ts +++ b/src/styles/theme/themes/dark.ts @@ -128,7 +128,7 @@ const darkTheme = { backgroundColor: colors.productDark100, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, }, - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { backgroundColor: colors.productDark100, statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, }, diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts index 6444cb9d4073..716deb17893c 100644 --- a/src/styles/theme/themes/light.ts +++ b/src/styles/theme/themes/light.ts @@ -128,7 +128,7 @@ const lightTheme = { backgroundColor: colors.productLight100, statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, }, - [SCREENS.WORKSPACE_SWITCHER.ROOT]: { + [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { backgroundColor: colors.productLight100, statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, }, diff --git a/tests/perf-test/SearchPage.perf-test.tsx b/tests/perf-test/SearchPage.perf-test.tsx index ea759a1201b2..29bb016230e7 100644 --- a/tests/perf-test/SearchPage.perf-test.tsx +++ b/tests/perf-test/SearchPage.perf-test.tsx @@ -116,7 +116,7 @@ afterEach(() => { PusherHelper.teardown(); }); -type SearchPageProps = StackScreenProps & { +type SearchPageProps = StackScreenProps & { betas: OnyxEntry; reports: OnyxCollection; isSearchingForReports: OnyxEntry; From 1142478d389bcafdbb5868e826c2c47110c4abd4 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 12 Apr 2024 17:08:49 +0300 Subject: [PATCH 055/686] fix light box offline indicator --- src/components/Lightbox/index.tsx | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 86a52c2baf6c..909c4d942e28 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -2,12 +2,14 @@ import React, {useCallback, useContext, useEffect, useMemo, useState} from 'reac import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; +import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import Image from '@components/Image'; import type {ImageOnLoadEvent} from '@components/Image/types'; import MultiGestureCanvas, {DEFAULT_ZOOM_RANGE} from '@components/MultiGestureCanvas'; import type {CanvasSize, ContentSize, OnScaleChangedCallback, ZoomRange} from '@components/MultiGestureCanvas/types'; import {getCanvasFitScale} from '@components/MultiGestureCanvas/utils'; +import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import NUMBER_OF_CONCURRENT_LIGHTBOXES from './numberOfConcurrentLightboxes'; @@ -47,6 +49,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); + const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const { @@ -219,9 +222,9 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[contentSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onError={onError} - onLoad={updateContentSize} - onLoadEnd={() => { + onLoad={(e) => { setLightboxImageLoaded(true); + updateContentSize(e); }} /> @@ -236,21 +239,23 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan resizeMode="contain" style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} - onLoad={updateContentSize} - onLoadEnd={() => setFallbackImageLoaded(true)} + onLoad={(e) => { + setFallbackImageLoaded(true); + updateContentSize(e); + }} /> )} - - {/* Show activity indicator while the lightbox is still loading the image. */} - {isLoading && ( - - )} )} + {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} + {isLoading && !isOffline && ( + + )} + {isLoading && } ); } From df8edb102940b5e5c1d51437fd0c49b5cfff5c3f Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Fri, 12 Apr 2024 22:39:25 +0300 Subject: [PATCH 056/686] fix cached image case offline indicator --- src/components/Lightbox/index.tsx | 64 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 909c4d942e28..a6b30f1e6815 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, PixelRatio, StyleSheet, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; @@ -133,10 +133,12 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan const indexOutOfRange = page > activePage + indexCanvasOffset || page < activePage - indexCanvasOffset; return !indexOutOfRange; }, [activePage, hasSiblingCarouselItems, page]); - const [isLightboxImageLoaded, setLightboxImageLoaded] = useState(false); + const [isLightboxImageLoading, setLightboxImageLoading] = useState(false); + const isLightboxImageLoaded = useRef(false); const [isFallbackVisible, setFallbackVisible] = useState(!isLightboxVisible); - const [isFallbackImageLoaded, setFallbackImageLoaded] = useState(false); + const [isFallbackImageLoading, setFallbackImageLoading] = useState(false); + const isFallbackImageLoaded = useRef(false); const fallbackSize = useMemo(() => { if (!hasSiblingCarouselItems || !contentSize || isCanvasLoading) { return undefined; @@ -154,18 +156,19 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan // until the fallback gets hidden so that we don't see two overlapping images at the same time. // If there the Lightbox is not used within a carousel, we don't need to hide the Lightbox, // because it's only going to be rendered after the fallback image is hidden. - const shouldShowLightbox = isLightboxImageLoaded && !isFallbackVisible; + const shouldShowLightbox = isLightboxImageLoaded.current && !isFallbackVisible; - const isFallbackStillLoading = isFallbackVisible && !isFallbackImageLoaded; - const isLightboxStillLoading = isLightboxVisible && !isLightboxImageLoaded; - const isLoading = isActive && (isCanvasLoading || isFallbackStillLoading || isLightboxStillLoading); + const isFallbackStillLoading = isFallbackVisible && isFallbackImageLoading; + const isLightboxStillLoading = isLightboxVisible && isLightboxImageLoading; + const isLoading = isActive && (isFallbackStillLoading || isLightboxStillLoading); // Resets the lightbox when it becomes inactive useEffect(() => { if (isLightboxVisible) { return; } - setLightboxImageLoaded(false); + isLightboxImageLoaded.current = false; + setLightboxImageLoading(false); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); @@ -177,9 +180,10 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan } // When the carousel item is active and the lightbox has finished loading, we want to hide the fallback image - if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded) { + if (isActive && isFallbackVisible && isLightboxVisible && isLightboxImageLoaded.current) { setFallbackVisible(false); - setFallbackImageLoaded(false); + setFallbackImageLoading(false); + isFallbackImageLoaded.current = false; return; } @@ -187,7 +191,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan if (!isActive && !isLightboxVisible) { setFallbackVisible(true); } - }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoaded, isLightboxVisible]); + }, [hasSiblingCarouselItems, isActive, isFallbackVisible, isLightboxImageLoading, isLightboxVisible]); const scaleChange = useCallback( (scale: number) => { @@ -223,9 +227,18 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isAuthTokenRequired={isAuthTokenRequired} onError={onError} onLoad={(e) => { - setLightboxImageLoaded(true); + isLightboxImageLoaded.current = true; + setLightboxImageLoading(false); updateContentSize(e); }} + onLoadStart={() => { + setTimeout(() => { + if (isLightboxImageLoaded.current) { + return; + } + setLightboxImageLoading(true); + }, 200); + }} /> @@ -240,22 +253,31 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan style={[fallbackSize ?? styles.invisibleImage]} isAuthTokenRequired={isAuthTokenRequired} onLoad={(e) => { - setFallbackImageLoaded(true); + setFallbackImageLoading(false); + isFallbackImageLoaded.current = true; updateContentSize(e); }} + onLoadStart={() => { + setTimeout(() => { + if (isFallbackImageLoaded.current) { + return; + } + setFallbackImageLoading(true); + }, 200); + }} /> )} + {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} + {isLoading && !isOffline && ( + + )} + {isLoading && } )} - {/* Show activity or offline indicator (based on the connection status) while the lightbox is still loading the image. */} - {isLoading && !isOffline && ( - - )} - {isLoading && } ); } From ae46fdf8eb05d8ed6084cba1c66b521379b275d0 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 15 Apr 2024 11:23:57 +0200 Subject: [PATCH 057/686] Add warning to useActiveWorkspaceFromNavigationState --- src/hooks/useActiveWorkspaceFromNavigationState.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/hooks/useActiveWorkspaceFromNavigationState.ts b/src/hooks/useActiveWorkspaceFromNavigationState.ts index 63a6865a516b..5b117816d40f 100644 --- a/src/hooks/useActiveWorkspaceFromNavigationState.ts +++ b/src/hooks/useActiveWorkspaceFromNavigationState.ts @@ -1,12 +1,21 @@ import {useNavigationState} from '@react-navigation/native'; +import Log from '@libs/Log'; import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; +import SCREENS from '@src/SCREENS'; /** * Get the currently selected policy ID stored in the navigation state. This hook should only be called only from screens in BottomTab. */ function useActiveWorkspaceFromNavigationState() { // The last policyID value is always stored in the last route in BottomTabNavigator. - const activeWorkpsaceID = useNavigationState((state) => state.routes.at(-1)?.params?.policyID); + const activeWorkpsaceID = useNavigationState((state) => { + // SCREENS.HOME is a screen located in the BottomTabNavigator, if it's not in state.routeNames it means that this hook was called from a screen in another navigator. + if (!state.routeNames.includes(SCREENS.HOME)) { + Log.warn('useActiveWorkspaceFromNavigationState should be called only from BottomTab screens'); + } + + return state.routes.at(-1)?.params?.policyID; + }); return activeWorkpsaceID; } From fea7ada105b4d1cbc82e9ff5ebf723e9b40c07b9 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 15 Apr 2024 11:56:37 +0200 Subject: [PATCH 058/686] fix typography --- src/components/MoneyRequestHeader.tsx | 8 ++++---- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 8 ++++++-- src/components/ReportActionItem/ReportPreview.tsx | 8 ++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5ea46f339d46..7557799472e1 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -188,8 +188,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, title={ } @@ -202,8 +202,8 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, title={ } diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 08d693dd109a..63950e3fe8d0 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -313,7 +313,9 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.receiptScanInProgress')} + + {translate('iou.receiptScanInProgress')} + )} {isPending && ( @@ -324,7 +326,9 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.transactionPending')} + + {translate('iou.transactionPending')} + )} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 231b1176423b..2b5034708dac 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -330,7 +330,9 @@ function ReportPreview({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.receiptScanInProgress')} + + {translate('iou.receiptScanInProgress')} + )} {shouldShowPendingSubtitle && ( @@ -341,7 +343,9 @@ function ReportPreview({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - {translate('iou.transactionPending')} + + {translate('iou.transactionPending')} + )} From f0835125dda095f8088b9c5df5d75141658e6118 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 13:39:56 +0300 Subject: [PATCH 059/686] fix android loading indicator case --- src/components/Lightbox/index.tsx | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index a6b30f1e6815..e35dc1703b36 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -201,6 +201,19 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan [onScaleChangedContext, onScaleChangedProp], ); + useEffect(() => { + // To avoid showing loading or offline indicator for cached images we set loading + // states after a 200 ms delay based on whether the image is loaded or not by then. + setTimeout(() => { + if (!isFallbackImageLoaded.current) { + setFallbackImageLoading(true); + } + if (!isLightboxImageLoaded.current) { + setLightboxImageLoading(true); + } + }, 200); + }, []); + return ( { - setTimeout(() => { - if (isLightboxImageLoaded.current) { - return; - } - setLightboxImageLoading(true); - }, 200); - }} /> @@ -257,14 +262,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isFallbackImageLoaded.current = true; updateContentSize(e); }} - onLoadStart={() => { - setTimeout(() => { - if (isFallbackImageLoaded.current) { - return; - } - setFallbackImageLoading(true); - }, 200); - }} /> )} From 36ccc936df58fd041f097c2e2fe4290ab339d0af Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 14:09:27 +0300 Subject: [PATCH 060/686] minor fix --- src/components/Lightbox/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index e35dc1703b36..42467569a904 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -168,7 +168,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan return; } isLightboxImageLoaded.current = false; - setLightboxImageLoading(false); + setLightboxImageLoading(true); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); From dd4ae04abcefd6e6768ca036bbf32744a02c493c Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 15 Apr 2024 17:16:42 +0200 Subject: [PATCH 061/686] style fixes --- src/components/MoneyRequestHeader.tsx | 4 ++-- src/components/MoneyRequestHeaderStatusBar.tsx | 2 +- .../MoneyRequestPreviewContent.tsx | 8 ++------ src/components/ReportActionItem/ReportPreview.tsx | 12 ++++-------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 7557799472e1..c50913bc2d31 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -190,7 +190,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, src={Expensicons.CreditCardHourglass} height={variables.iconSizeSmall} width={variables.iconSizeSmall} - fill={theme.textSupporting} + fill={theme.icon} /> } description={translate('iou.transactionPendingDescription')} @@ -204,7 +204,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, src={Expensicons.ReceiptScan} height={variables.iconSizeSmall} width={variables.iconSizeSmall} - fill={theme.textSupporting} + fill={theme.icon} /> } description={translate('iou.receiptScanInProgressDescription')} diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx index d21e66ba39eb..0052768a4cf0 100644 --- a/src/components/MoneyRequestHeaderStatusBar.tsx +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -25,7 +25,7 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom {title} ) : ( - {title} + {title} )} {description} diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 63950e3fe8d0..c234eb749653 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -313,9 +313,7 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - - {translate('iou.receiptScanInProgress')} - + {translate('iou.receiptScanInProgress')} )} {isPending && ( @@ -326,9 +324,7 @@ function MoneyRequestPreviewContent({ width={variables.iconSizeExtraSmall} fill={theme.textSupporting} /> - - {translate('iou.transactionPending')} - + {translate('iou.transactionPending')} )} diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 2b5034708dac..766680fd378b 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -328,11 +328,9 @@ function ReportPreview({ src={Expensicons.ReceiptScan} height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} - fill={theme.textSupporting} + fill={theme.icon} /> - - {translate('iou.receiptScanInProgress')} - + {translate('iou.receiptScanInProgress')} )} {shouldShowPendingSubtitle && ( @@ -341,11 +339,9 @@ function ReportPreview({ src={Expensicons.CreditCardHourglass} height={variables.iconSizeExtraSmall} width={variables.iconSizeExtraSmall} - fill={theme.textSupporting} + fill={theme.icon} /> - - {translate('iou.transactionPending')} - + {translate('iou.transactionPending')} )} From 75a772eb376f562b9ef16c3ea1b36801344785e5 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 20:38:35 +0300 Subject: [PATCH 062/686] fix receipt offline indicator case --- src/components/AttachmentModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index 7d13524b78df..3938c2de8559 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -463,7 +463,7 @@ function AttachmentModal({ } const context = useMemo( () => ({ - pagerItems: [], + pagerItems: [{source: sourceForAttachmentView, index: 0, isActive: true}], activePage: 0, pagerRef: undefined, isPagerScrolling: nope, @@ -472,7 +472,7 @@ function AttachmentModal({ onScaleChanged: () => {}, onSwipeDown: closeModal, }), - [closeModal, nope], + [closeModal, nope, sourceForAttachmentView], ); return ( From bd00d41ed94ef5ab1ffc1bb31b4a312cab204351 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 15 Apr 2024 22:40:59 +0300 Subject: [PATCH 063/686] minor fix --- src/components/Lightbox/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 42467569a904..bf5067e81bbb 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -168,7 +168,6 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan return; } isLightboxImageLoaded.current = false; - setLightboxImageLoading(true); setContentSize(undefined); }, [isLightboxVisible, setContentSize]); From 92a626574ba95b9dd448df13967533aa41318b6c Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:01:51 +0100 Subject: [PATCH 064/686] Participants onyx migration --- src/libs/migrateOnyx.ts | 2 + src/libs/migrations/Participants.ts | 60 +++++++++++++++++++++++++++++ src/types/onyx/Report.ts | 2 - 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/libs/migrations/Participants.ts diff --git a/src/libs/migrateOnyx.ts b/src/libs/migrateOnyx.ts index 412a8e00f052..674b5b8242dc 100644 --- a/src/libs/migrateOnyx.ts +++ b/src/libs/migrateOnyx.ts @@ -2,6 +2,7 @@ import Log from './Log'; import CheckForPreviousReportActionID from './migrations/CheckForPreviousReportActionID'; import KeyReportActionsDraftByReportActionID from './migrations/KeyReportActionsDraftByReportActionID'; import NVPMigration from './migrations/NVPMigration'; +import Participants from './migrations/Participants'; import PronounsMigration from './migrations/PronounsMigration'; import RemoveEmptyReportActionsDrafts from './migrations/RemoveEmptyReportActionsDrafts'; import RenameReceiptFilename from './migrations/RenameReceiptFilename'; @@ -21,6 +22,7 @@ export default function () { RemoveEmptyReportActionsDrafts, NVPMigration, PronounsMigration, + Participants, ]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the diff --git a/src/libs/migrations/Participants.ts b/src/libs/migrations/Participants.ts new file mode 100644 index 000000000000..0e0cd8722348 --- /dev/null +++ b/src/libs/migrations/Participants.ts @@ -0,0 +1,60 @@ +import Onyx from 'react-native-onyx'; +import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; +import Log from '@libs/Log'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; +import type {Participant, Participants} from '@src/types/onyx/Report'; + +type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; +type OldReport = Report & {participantAccountIDs?: number[]; visibleChatMemberAccountIDs?: number[]}; +type OldReportCollection = Record>; + +function getReports(): Promise> { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (reports) => { + Onyx.disconnect(connectionID); + return resolve(reports); + }, + }); + }); +} + +export default function (): Promise { + return getReports().then((reports) => { + if (!reports) { + Log.info('[Migrate Onyx] Skipped Participants migration because there are no reports'); + return; + } + + const collection = Object.entries(reports).reduce((reportsCollection, [onyxKey, report]) => { + // If we have participantAccountIDs then this report is eligible for migration + if (report?.participantAccountIDs) { + const visibleParticipants = new Set(report.visibleChatMemberAccountIDs); + const participants = report.participantAccountIDs.reduce((reportParticipants, accountID) => { + const participant: Participant = { + hidden: !visibleParticipants.has(accountID), + }; + + // eslint-disable-next-line no-param-reassign + reportParticipants[accountID] = participant; + return reportParticipants; + }, {}); + + // eslint-disable-next-line no-param-reassign + reportsCollection[onyxKey as ReportKey] = { + participants, + participantAccountIDs: null, + visibleChatMemberAccountIDs: null, + }; + } + + return reportsCollection; + }, {}); + + // eslint-disable-next-line rulesdir/prefer-actions-set-data + return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, collection).then(() => Log.info('[Migrate Onyx] Ran migration Participants successfully')); + }); +} diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..b271255b06a6 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -140,8 +140,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< ownerAccountID?: number; ownerEmail?: string; participants?: Participants; - participantAccountIDs?: number[]; - visibleChatMemberAccountIDs?: number[]; total?: number; unheldTotal?: number; currency?: string; From 02b2315b9e91a1b96800045c82d23bedb018b164 Mon Sep 17 00:00:00 2001 From: ntdiary <2471314@gmail.com> Date: Tue, 16 Apr 2024 22:56:51 +0800 Subject: [PATCH 065/686] clean up mobile safari code --- src/libs/updateMultilineInputRange/index.ts | 11 ++---- .../report/ReportActionItemMessageEdit.tsx | 35 ++++--------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/src/libs/updateMultilineInputRange/index.ts b/src/libs/updateMultilineInputRange/index.ts index f5d71c5e2038..d0bc1c306dac 100644 --- a/src/libs/updateMultilineInputRange/index.ts +++ b/src/libs/updateMultilineInputRange/index.ts @@ -1,4 +1,3 @@ -import * as Browser from '@libs/Browser'; import type UpdateMultilineInputRange from './types'; /** @@ -17,14 +16,10 @@ const updateMultilineInputRange: UpdateMultilineInputRange = (input, shouldAutoF if ('value' in input && input.value && input.setSelectionRange) { const length = input.value.length; - - // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus - // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), - // so we need to ensure that it is only updated after focus. - const shouldSetSelection = !(Browser.isMobileSafari() && !shouldAutoFocus); - if (shouldSetSelection) { - input.setSelectionRange(length, length); + if (!shouldAutoFocus) { + return; } + input.setSelectionRange(length, length); // eslint-disable-next-line no-param-reassign input.scrollTop = input.scrollHeight; } diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index fc3c92434fc4..8920f789e167 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -22,7 +22,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as Browser from '@libs/Browser'; import * as ComposerUtils from '@libs/ComposerUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; @@ -68,7 +67,6 @@ type ReportActionItemMessageEditProps = { const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; -const isMobileSafari = Browser.isMobileSafari(); const shouldUseForcedSelectionRange = shouldUseEmojiPickerSelection(); function ReportActionItemMessageEdit( @@ -84,14 +82,6 @@ function ReportActionItemMessageEdit( const {isSmallScreenWidth} = useWindowDimensions(); const prevDraftMessage = usePrevious(draftMessage); - const getInitialSelection = () => { - if (isMobileSafari) { - return {start: 0, end: 0}; - } - - const length = draftMessage.length; - return {start: length, end: length}; - }; const emojisPresentBefore = useRef([]); const [draft, setDraft] = useState(() => { if (draftMessage) { @@ -99,7 +89,7 @@ function ReportActionItemMessageEdit( } return draftMessage; }); - const [selection, setSelection] = useState(getInitialSelection); + const [selection, setSelection] = useState({start: 0, end: 0}); const [isFocused, setIsFocused] = useState(false); const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); const [modal, setModal] = useState({ @@ -171,23 +161,10 @@ function ReportActionItemMessageEdit( ); useEffect(() => { - // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus - // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), - // so we need to ensure that it is only updated after focus. - if (isMobileSafari) { - setDraft((prevDraft) => { - setSelection({ - start: prevDraft.length, - end: prevDraft.length, - }); - return prevDraft; - }); - - // Scroll content of textInputRef to bottom - if (textInputRef.current) { - textInputRef.current.scrollTop = textInputRef.current.scrollHeight; - } - } + setSelection({ + start: draftMessage.length, + end: draftMessage.length, + }); return () => { InputFocus.callback(() => setIsFocused(false)); @@ -208,7 +185,7 @@ function ReportActionItemMessageEdit( // Show the main composer when the focused message is deleted from another client // to prevent the main composer stays hidden until we swtich to another chat. setShouldShowComposeInputKeyboardAware(true); - }; + } // eslint-disable-next-line react-hooks/exhaustive-deps -- this cleanup needs to be called only on unmount }, [action.reportActionID]); From a743ffcb12f76928915760d1524fc9eb036489b6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 17 Apr 2024 15:10:05 +0700 Subject: [PATCH 066/686] fix empty chat displayed in focus mode --- src/libs/ReportUtils.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f31b4a780c5a..5b3f0e66ffd8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4470,11 +4470,23 @@ function buildOptimisticMoneyRequestEntities( return [createdActionForChat, createdActionForIOUReport, iouAction, transactionThread, createdActionForTransactionThread]; } +// Check if the report is empty report +function isEmptyReport(report: OnyxEntry): boolean { + if (!report) { + return true; + } + const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(report.reportID); + return !report.lastMessageText && !report.lastMessageTranslationKey && !lastVisibleMessage.lastMessageText && !lastVisibleMessage.lastMessageTranslationKey; +} + function isUnread(report: OnyxEntry): boolean { if (!report) { return false; } + if (isEmptyReport(report)) { + return false; + } // lastVisibleActionCreated and lastReadTime are both datetime strings and can be compared directly const lastVisibleActionCreated = report.lastVisibleActionCreated ?? ''; const lastReadTime = report.lastReadTime ?? ''; @@ -4678,8 +4690,8 @@ function shouldReportBeInOptionList({ if (hasDraftComment || requiresAttentionFromCurrentUser(report)) { return true; } - const lastVisibleMessage = ReportActionsUtils.getLastVisibleMessage(report.reportID); - const isEmptyChat = !report.lastMessageText && !report.lastMessageTranslationKey && !lastVisibleMessage.lastMessageText && !lastVisibleMessage.lastMessageTranslationKey; + + const isEmptyChat = isEmptyReport(report); const canHideReport = shouldHideReport(report, currentReportId); // Include reports if they are pinned From 787ad3b011378172c0bc70d454d962a6a8676e36 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 17 Apr 2024 15:58:57 +0700 Subject: [PATCH 067/686] fix jest test --- tests/unit/SidebarOrderTest.ts | 16 +++++++----- tests/unit/SidebarTest.ts | 2 ++ tests/unit/UnreadIndicatorUpdaterTest.ts | 32 ++++++++++++++++++++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index 2758d43fb81e..0b8ec5b1385f 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -861,10 +861,10 @@ describe('Sidebar', () => { it('alphabetizes chats', () => { LHNTestUtils.getDefaultRenderedSidebarLinks(); - const report1 = LHNTestUtils.getFakeReport([1, 2], 3, true); - const report2 = LHNTestUtils.getFakeReport([3, 4], 2, true); - const report3 = LHNTestUtils.getFakeReport([5, 6], 1, true); - const report4 = LHNTestUtils.getFakeReport([7, 8], 0, true); + const report1 = {...LHNTestUtils.getFakeReport([1, 2], 3, true), lastMessageText: 'test'}; + const report2 = {...LHNTestUtils.getFakeReport([3, 4], 2, true), lastMessageText: 'test'}; + const report3 = {...LHNTestUtils.getFakeReport([5, 6], 1, true), lastMessageText: 'test'}; + const report4 = {...LHNTestUtils.getFakeReport([7, 8], 0, true), lastMessageText: 'test'}; const reportCollectionDataSet: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, @@ -918,9 +918,13 @@ describe('Sidebar', () => { chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, stateNum: CONST.REPORT.STATE_NUM.APPROVED, + lastMessageText: 'test', }; - const report2 = LHNTestUtils.getFakeReport([3, 4], 2, true); - const report3 = LHNTestUtils.getFakeReport([5, 6], 1, true); + const report2 = { + ...LHNTestUtils.getFakeReport([3, 4], 2, true), + lastMessageText: 'test', + }; + const report3 = {...LHNTestUtils.getFakeReport([5, 6], 1, true), lastMessageText: 'test'}; // Given the user is in all betas const betas = [CONST.BETAS.DEFAULT_ROOMS]; diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts index 23ea0d377634..75f8fa256c57 100644 --- a/tests/unit/SidebarTest.ts +++ b/tests/unit/SidebarTest.ts @@ -42,6 +42,7 @@ describe('Sidebar', () => { chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, stateNum: CONST.REPORT.STATE_NUM.APPROVED, + lastMessageText: 'test', }; const action = { @@ -94,6 +95,7 @@ describe('Sidebar', () => { chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, statusNum: CONST.REPORT.STATUS_NUM.CLOSED, stateNum: CONST.REPORT.STATE_NUM.APPROVED, + lastMessageText: 'test', }; const action = { ...LHNTestUtils.getFakeReportAction('email1@test.com', 3), diff --git a/tests/unit/UnreadIndicatorUpdaterTest.ts b/tests/unit/UnreadIndicatorUpdaterTest.ts index a5f58b57793a..22141eee791d 100644 --- a/tests/unit/UnreadIndicatorUpdaterTest.ts +++ b/tests/unit/UnreadIndicatorUpdaterTest.ts @@ -6,9 +6,23 @@ describe('UnreadIndicatorUpdaterTest', () => { describe('should return correct number of unread reports', () => { it('given last read time < last visible action created', () => { const reportsToBeUsed = { - 1: {reportID: '1', reportName: 'test', type: CONST.REPORT.TYPE.EXPENSE, lastReadTime: '2023-07-08 07:15:44.030', lastVisibleActionCreated: '2023-08-08 07:15:44.030'}, - 2: {reportID: '2', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, - 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK}, + 1: { + reportID: '1', + reportName: 'test', + type: CONST.REPORT.TYPE.EXPENSE, + lastReadTime: '2023-07-08 07:15:44.030', + lastVisibleActionCreated: '2023-08-08 07:15:44.030', + lastMessageText: 'test', + }, + 2: { + reportID: '2', + reportName: 'test', + type: CONST.REPORT.TYPE.TASK, + lastReadTime: '2023-02-05 09:12:05.000', + lastVisibleActionCreated: '2023-02-06 07:15:44.030', + lastMessageText: 'test', + }, + 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastMessageText: 'test'}, }; expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(2); }); @@ -31,9 +45,17 @@ describe('UnreadIndicatorUpdaterTest', () => { notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, lastReadTime: '2023-07-08 07:15:44.030', lastVisibleActionCreated: '2023-08-08 07:15:44.030', + lastMessageText: 'test', + }, + 2: { + reportID: '2', + reportName: 'test', + type: CONST.REPORT.TYPE.TASK, + lastReadTime: '2023-02-05 09:12:05.000', + lastVisibleActionCreated: '2023-02-06 07:15:44.030', + lastMessageText: 'test', }, - 2: {reportID: '2', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, - 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK}, + 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastMessageText: 'test'}, }; expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(1); }); From ed168c1a2206d839926655ad4bc7d3334ebdc510 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Wed, 17 Apr 2024 22:50:28 +0200 Subject: [PATCH 068/686] feat: setup Terms and Fees step --- src/languages/en.ts | 4 + src/languages/es.ts | 4 + .../TermsAndFees/TermsAndFees.tsx | 58 ++++++++++ .../TermsAndFees/substeps/FeesStep.tsx | 24 ++++ .../TermsAndFees/substeps/TermsStep.tsx | 106 ++++++++++++++++++ 5 files changed, 196 insertions(+) create mode 100644 src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx create mode 100644 src/pages/EnablePayments/TermsAndFees/substeps/FeesStep.tsx create mode 100644 src/pages/EnablePayments/TermsAndFees/substeps/TermsStep.tsx diff --git a/src/languages/en.ts b/src/languages/en.ts index fda7acc309bd..2aab5f00c830 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1564,6 +1564,7 @@ export default { }, termsStep: { headerTitle: 'Terms and fees', + headerTitleRefactor: 'Fees and terms', haveReadAndAgree: 'I have read and agree to receive ', electronicDisclosures: 'electronic disclosures', agreeToThe: 'I agree to the', @@ -1574,6 +1575,9 @@ export default { noOverdraftOrCredit: 'No overdraft/credit feature.', electronicFundsWithdrawal: 'Electronic funds withdrawal', standard: 'Standard', + takeALookAtSomeFees: 'Take a look at some fees.', + checkPlease: 'Check please.', + agreeToTerms: 'Agree to the terms and you’ll be good to go!', shortTermsForm: { expensifyPaymentsAccount: ({walletProgram}: WalletProgramParams) => `The Expensify Wallet is issued by ${walletProgram}.`, perPurchase: 'Per purchase', diff --git a/src/languages/es.ts b/src/languages/es.ts index 46a8d051bdfe..acdf9978de0b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1585,6 +1585,7 @@ export default { }, termsStep: { headerTitle: 'Condiciones y tarifas', + headerTitleRefactor: 'Tarifas y condiciones', haveReadAndAgree: 'He leído y acepto recibir ', electronicDisclosures: 'divulgaciones electrónicas', agreeToThe: 'Estoy de acuerdo con el ', @@ -1595,6 +1596,9 @@ export default { noOverdraftOrCredit: 'Sin función de sobregiro/crédito', electronicFundsWithdrawal: 'Retiro electrónico de fondos', standard: 'Estándar', + takeALookAtSomeFees: 'Echa un vistazo a algunas tarifas.', + checkPlease: 'Por favor, revisa.', + agreeToTerms: 'Debes aceptar los términos y condiciones para continuar.', shortTermsForm: { expensifyPaymentsAccount: ({walletProgram}: WalletProgramParams) => `La billetera Expensify es emitida por ${walletProgram}.`, perPurchase: 'Por compra', diff --git a/src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx b/src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx new file mode 100644 index 000000000000..7e26f652efe8 --- /dev/null +++ b/src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import {View} from 'react-native'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useSubStep from '@hooks/useSubStep'; +import type {SubStepProps} from '@hooks/useSubStep/types'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import FeesStep from './substeps/FeesStep'; +import TermsStep from './substeps/TermsStep'; + +const termsAndFeesSubsteps: Array> = [FeesStep, TermsStep]; + +function TermsAndFees() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const submit = () => {}; + const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent: termsAndFeesSubsteps, startFrom: 0, onFinished: submit}); + + const handleBackButtonPress = () => { + if (screenIndex === 0) { + return; + } + prevScreen(); + }; + + return ( + + + + + + + + ); +} + +TermsAndFees.displayName = 'TermsAndFees'; + +export default TermsAndFees; diff --git a/src/pages/EnablePayments/TermsAndFees/substeps/FeesStep.tsx b/src/pages/EnablePayments/TermsAndFees/substeps/FeesStep.tsx new file mode 100644 index 000000000000..78ff371b747c --- /dev/null +++ b/src/pages/EnablePayments/TermsAndFees/substeps/FeesStep.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import ScrollView from '@components/ScrollView'; +import useThemeStyles from '@hooks/useThemeStyles'; +import LongTermsForm from '@pages/EnablePayments/TermsPage/LongTermsForm'; +import ShortTermsForm from '@pages/EnablePayments/TermsPage/ShortTermsForm'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function FeesStep() { + const styles = useThemeStyles(); + const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + + return ( + + + + + ); +} + +export default FeesStep; diff --git a/src/pages/EnablePayments/TermsAndFees/substeps/TermsStep.tsx b/src/pages/EnablePayments/TermsAndFees/substeps/TermsStep.tsx new file mode 100644 index 000000000000..e77bf7db4817 --- /dev/null +++ b/src/pages/EnablePayments/TermsAndFees/substeps/TermsStep.tsx @@ -0,0 +1,106 @@ +import React, {useEffect, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import CheckboxWithLabel from '@components/CheckboxWithLabel'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import * as BankAccounts from '@userActions/BankAccounts'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function HaveReadAndAgreeLabel() { + const {translate} = useLocalize(); + + return ( + + {`${translate('termsStep.haveReadAndAgree')}`} + {`${translate('termsStep.electronicDisclosures')}.`} + + ); +} + +function AgreeToTheLabel() { + const {translate} = useLocalize(); + + return ( + + {`${translate('termsStep.agreeToThe')} `} + {`${translate('common.privacy')} `} + {`${translate('common.and')} `} + {`${translate('termsStep.walletAgreement')}.`} + + ); +} + +function TermsStep() { + const styles = useThemeStyles(); + const [hasAcceptedDisclosure, setHasAcceptedDisclosure] = useState(false); + const [hasAcceptedPrivacyPolicyAndWalletAgreement, setHasAcceptedPrivacyPolicyAndWalletAgreement] = useState(false); + const [error, setError] = useState(false); + const {translate} = useLocalize(); + + const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); + + const errorMessage = error ? 'common.error.acceptTerms' : ErrorUtils.getLatestErrorMessage(walletTerms ?? {}) ?? ''; + + const toggleDisclosure = () => { + setHasAcceptedDisclosure(!hasAcceptedDisclosure); + }; + + const togglePrivacyPolicy = () => { + setHasAcceptedPrivacyPolicyAndWalletAgreement(!hasAcceptedPrivacyPolicyAndWalletAgreement); + }; + + /** clear error */ + useEffect(() => { + if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { + return; + } + + setError(false); + }, [hasAcceptedDisclosure, hasAcceptedPrivacyPolicyAndWalletAgreement]); + + return ( + + {translate('termsStep.checkPlease')} + {translate('termsStep.agreeToTerms')} + + + { + if (!hasAcceptedDisclosure || !hasAcceptedPrivacyPolicyAndWalletAgreement) { + setError(true); + return; + } + + setError(false); + BankAccounts.acceptWalletTerms({ + hasAcceptedTerms: hasAcceptedDisclosure && hasAcceptedPrivacyPolicyAndWalletAgreement, + reportID: walletTerms?.chatReportID ?? '', + }); + }} + message={errorMessage} + isAlertVisible={error || Boolean(errorMessage)} + isLoading={!!walletTerms?.isLoading} + containerStyles={[styles.mh0, styles.mv4]} + /> + + ); +} + +export default TermsStep; From 7574fc9acbe20a012b7d92a90ff1523d20b373d6 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 18 Apr 2024 13:19:08 +0100 Subject: [PATCH 069/686] add taxAmount param to updateMoneyRequestAmountAndCurrency --- src/libs/actions/IOU.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 83caa65e1d77..43cc73df9c97 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4449,6 +4449,7 @@ type UpdateMoneyRequestAmountAndCurrencyParams = { transactionThreadReportID: string; currency: string; amount: number; + taxAmount?: number; policy?: OnyxEntry; policyTagList?: OnyxEntry; policyCategories?: OnyxEntry; @@ -4460,6 +4461,7 @@ function updateMoneyRequestAmountAndCurrency({ transactionThreadReportID, currency, amount, + taxAmount, policy, policyTagList, policyCategories, @@ -4467,6 +4469,7 @@ function updateMoneyRequestAmountAndCurrency({ const transactionChanges = { amount, currency, + ...(taxAmount && {taxAmount}), }; const {params, onyxData} = getUpdateMoneyRequestParams( transactionID, From 1ed12dfe94efdb4cf61a0cf865e98b2dd08f71ee Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 18 Apr 2024 13:19:58 +0100 Subject: [PATCH 070/686] should optimistically update tax amount when updating expense amount --- .../iou/request/step/IOURequestStepAmount.tsx | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index cb8e51120f01..7b56049811fc 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -16,7 +16,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Transaction} from '@src/types/onyx'; +import type {Policy, TaxRatesWithDefault, Transaction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; @@ -33,6 +33,9 @@ type IOURequestStepAmountOnyxProps = { /** The draft transaction object being modified in Onyx */ draftTransaction: OnyxEntry; + + /** The policy which the user has access to and which the report is tied to */ + policy: OnyxEntry; }; type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & @@ -40,7 +43,15 @@ type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & /** The transaction object being modified in Onyx */ transaction: OnyxEntry; }; - +function getTaxAmount(transaction: OnyxEntry, taxRates: TaxRatesWithDefault | undefined, newAmount: number) { + if (!transaction?.amount) { + return; + } + const transactionTaxCode = transaction?.taxCode ?? ''; + const defaultTaxValue = taxRates?.defaultValue; + const taxPercentage = (transactionTaxCode ? taxRates?.taxes[transactionTaxCode]?.value : defaultTaxValue) ?? ''; + return CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount)); +} function IOURequestStepAmount({ report, route: { @@ -49,6 +60,7 @@ function IOURequestStepAmount({ transaction, splitDraftTransaction, draftTransaction, + policy, }: IOURequestStepAmountProps) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -148,7 +160,9 @@ function IOURequestStepAmount({ return; } - IOU.updateMoneyRequestAmountAndCurrency({transactionID, transactionThreadReportID: reportID, currency, amount: newAmount}); + const taxAmount = getTaxAmount(transaction, policy?.taxRates, newAmount); + + IOU.updateMoneyRequestAmountAndCurrency({transactionID, transactionThreadReportID: reportID, currency, amount: newAmount, taxAmount}); Navigation.dismissModal(); }; @@ -190,6 +204,9 @@ export default withWritableReportOrNotFound( return `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`; }, }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, + }, })(IOURequestStepAmount), ), ); From 1c16b1aeab10ab4e85462dcc9b50cb399d6588f7 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 18 Apr 2024 13:33:58 +0100 Subject: [PATCH 071/686] add UpdateMoneyRequestTaxRateParams types to updateMoneyRequestTaxRate --- src/libs/actions/IOU.ts | 18 ++++++++++-------- .../request/step/IOURequestStepTaxRatePage.tsx | 9 ++++++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 43cc73df9c97..04dd151506bb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2379,15 +2379,17 @@ function updateMoneyRequestTaxAmount( API.write('UpdateMoneyRequestTaxAmount', params, onyxData); } +type UpdateMoneyRequestTaxRateParams = { + transactionID: string; + optimisticReportActionID: string; + taxCode: string; + policy: OnyxEntry; + policyTagList: OnyxEntry; + policyCategories: OnyxEntry; +}; + /** Updates the created tax rate of an expense */ -function updateMoneyRequestTaxRate( - transactionID: string, - optimisticReportActionID: string, - taxCode: string, - policy: OnyxEntry, - policyTagList: OnyxEntry, - policyCategories: OnyxEntry, -) { +function updateMoneyRequestTaxRate({transactionID, optimisticReportActionID, taxCode, policy, policyTagList, policyCategories}: UpdateMoneyRequestTaxRateParams) { const transactionChanges = { taxCode, }; diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index da3a244a2db2..3e14610652df 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -74,7 +74,14 @@ function IOURequestStepTaxRatePage({ navigateBack(); return; } - IOU.updateMoneyRequestTaxRate(transaction?.transactionID ?? '', report?.reportID ?? '', newTaxCode, policy, policyTags, policyCategories); + IOU.updateMoneyRequestTaxRate({ + transactionID: transaction?.transactionID ?? '', + optimisticReportActionID: report?.reportID ?? '', + taxCode: newTaxCode, + policy, + policyTagList: policyTags, + policyCategories, + }); navigateBack(); return; } From 8dce58a29da1500c7178168f31affbb669abeb4a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 18 Apr 2024 13:37:51 +0100 Subject: [PATCH 072/686] add taxAmount to UpdateMoneyRequestTaxRateParams types --- src/libs/actions/IOU.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 04dd151506bb..53fc9e408a06 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2383,15 +2383,17 @@ type UpdateMoneyRequestTaxRateParams = { transactionID: string; optimisticReportActionID: string; taxCode: string; + taxAmount?: number; policy: OnyxEntry; policyTagList: OnyxEntry; policyCategories: OnyxEntry; }; /** Updates the created tax rate of an expense */ -function updateMoneyRequestTaxRate({transactionID, optimisticReportActionID, taxCode, policy, policyTagList, policyCategories}: UpdateMoneyRequestTaxRateParams) { +function updateMoneyRequestTaxRate({transactionID, optimisticReportActionID, taxCode, taxAmount, policy, policyTagList, policyCategories}: UpdateMoneyRequestTaxRateParams) { const transactionChanges = { taxCode, + ...(taxAmount && {taxAmount}), }; const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, policy, policyTagList, policyCategories, true); API.write('UpdateMoneyRequestTaxRate', params, onyxData); From 228c47705cc4a06d8d0a343be222e8e952523039 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 18 Apr 2024 14:36:36 +0100 Subject: [PATCH 073/686] should optimistically update tax amount when updating tax rate --- .../iou/request/step/IOURequestStepTaxRatePage.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index 3e14610652df..55d43370c477 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -68,6 +68,12 @@ function IOURequestStepTaxRatePage({ : transactionTaxCode && TransactionUtils.getTaxName(taxRates.taxes, transactionTaxCode)); const updateTaxRates = (taxes: OptionsListUtils.TaxRatesOption) => { + if (!transaction || !taxes.text || !taxRates) { + Navigation.goBack(backTo); + return; + } + const taxAmount = getTaxAmount(taxRates, taxes.text, TransactionUtils.getAmount(transaction, false, true)); + if (isEditing) { const newTaxCode = taxes.data.code; if (newTaxCode === undefined || newTaxCode === TransactionUtils.getTaxCode(transaction)) { @@ -78,6 +84,7 @@ function IOURequestStepTaxRatePage({ transactionID: transaction?.transactionID ?? '', optimisticReportActionID: report?.reportID ?? '', taxCode: newTaxCode, + taxAmount: CurrencyUtils.convertToBackendAmount(taxAmount ?? 0), policy, policyTagList: policyTags, policyCategories, @@ -85,11 +92,7 @@ function IOURequestStepTaxRatePage({ navigateBack(); return; } - if (!transaction || !taxes.text || !taxRates) { - Navigation.goBack(backTo); - return; - } - const taxAmount = getTaxAmount(taxRates, taxes.text, transaction?.amount); + if (taxAmount === undefined) { Navigation.goBack(backTo); return; From a0ac9bac20da8c89b5a5f2e9bbaee1e314e7bb49 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 18 Apr 2024 14:39:33 +0100 Subject: [PATCH 074/686] fix lint --- src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index 55d43370c477..c310e88dc928 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -73,7 +73,7 @@ function IOURequestStepTaxRatePage({ return; } const taxAmount = getTaxAmount(taxRates, taxes.text, TransactionUtils.getAmount(transaction, false, true)); - + if (isEditing) { const newTaxCode = taxes.data.code; if (newTaxCode === undefined || newTaxCode === TransactionUtils.getTaxCode(transaction)) { From 0999bf7371971d8c50f9f39a9e03dc25023a2bea Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 18 Apr 2024 18:48:35 +0200 Subject: [PATCH 075/686] feat: rename terms and feex, fix styling, create temporary test route --- src/ROUTES.ts | 2 + src/SCREENS.ts | 2 + .../ModalStackNavigators/index.tsx | 2 + src/libs/Navigation/linkingConfig/config.ts | 5 +++ .../FeesAndTerms.tsx} | 8 ++-- .../FeesAndTerms/substeps/FeesStep.tsx | 37 +++++++++++++++++++ .../substeps/TermsStep.tsx | 32 ++++++++-------- .../TermsAndFees/substeps/FeesStep.tsx | 24 ------------ .../TermsPage/ShortTermsForm.tsx | 16 ++++---- src/styles/index.ts | 5 ++- 10 files changed, 80 insertions(+), 53 deletions(-) rename src/pages/EnablePayments/{TermsAndFees/TermsAndFees.tsx => FeesAndTerms/FeesAndTerms.tsx} (93%) create mode 100644 src/pages/EnablePayments/FeesAndTerms/substeps/FeesStep.tsx rename src/pages/EnablePayments/{TermsAndFees => FeesAndTerms}/substeps/TermsStep.tsx (79%) delete mode 100644 src/pages/EnablePayments/TermsAndFees/substeps/FeesStep.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 46f2e2fef049..2ed8be54f408 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -119,6 +119,8 @@ const ROUTES = { SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', SETTINGS_ADD_BANK_ACCOUNT_REFACTOR: 'settings/wallet/add-bank-account-refactor', SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', + // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 + SETTINGS_ENABLE_PAYMENTS_TEMPORARY_TERMS: 'settings/wallet/enable-payments-temporary-terms', SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: { route: 'settings/wallet/card/:domain/digital-details/update-address', getRoute: (domain: string) => `settings/wallet/card/${domain}/digital-details/update-address` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index acbb4b507b65..cf5723c43d31 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -84,6 +84,8 @@ const SCREENS = { TRANSFER_BALANCE: 'Settings_Wallet_Transfer_Balance', CHOOSE_TRANSFER_ACCOUNT: 'Settings_Wallet_Choose_Transfer_Account', ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments', + // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 + ENABLE_PAYMENTS_TEMPORARY_TERMS: 'Settings_Wallet_EnablePayments_Temporary_Terms', CARD_ACTIVATE: 'Settings_Wallet_Card_Activate', REPORT_VIRTUAL_CARD_FRAUD: 'Settings_Wallet_ReportVirtualCardFraud', CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index a596acf0a3ac..711e911fe9f1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -215,6 +215,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Wallet/TransferBalancePage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.CHOOSE_TRANSFER_ACCOUNT]: () => require('../../../../pages/settings/Wallet/ChooseTransferAccountPage').default as React.ComponentType, [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS]: () => require('../../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, + // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 + [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_TEMPORARY_TERMS]: () => require('../../../../pages/EnablePayments/FeesAndTerms/FeesAndTerms').default as React.ComponentType, [SCREENS.SETTINGS.ADD_DEBIT_CARD]: () => require('../../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType, [SCREENS.SETTINGS.ADD_BANK_ACCOUNT]: () => require('../../../../pages/AddPersonalBankAccountPage').default as React.ComponentType, [SCREENS.SETTINGS.ADD_BANK_ACCOUNT_REFACTOR]: () => require('../../../../pages/EnablePayments/AddBankAccount/AddBankAccount').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 05b7190fa181..c19ce019a621 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -156,6 +156,11 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_ENABLE_PAYMENTS, exact: true, }, + // TODO: Added temporarily for testing purposes, remove after refactor - https://github.com/Expensify/App/issues/36648 + [SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS_TEMPORARY_TERMS]: { + path: ROUTES.SETTINGS_ENABLE_PAYMENTS_TEMPORARY_TERMS, + exact: true, + }, [SCREENS.SETTINGS.WALLET.TRANSFER_BALANCE]: { path: ROUTES.SETTINGS_WALLET_TRANSFER_BALANCE, exact: true, diff --git a/src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx similarity index 93% rename from src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx rename to src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx index 7e26f652efe8..76955e860f13 100644 --- a/src/pages/EnablePayments/TermsAndFees/TermsAndFees.tsx +++ b/src/pages/EnablePayments/FeesAndTerms/FeesAndTerms.tsx @@ -13,7 +13,7 @@ import TermsStep from './substeps/TermsStep'; const termsAndFeesSubsteps: Array> = [FeesStep, TermsStep]; -function TermsAndFees() { +function FeesAndTerms() { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -29,7 +29,7 @@ function TermsAndFees() { return ( + {translate('termsStep.takeALookAtSomeFees')} + + + +