diff --git a/src/hooks/useNetwork.ts b/src/hooks/useNetwork.ts index 69aaebc415a5..206741713882 100644 --- a/src/hooks/useNetwork.ts +++ b/src/hooks/useNetwork.ts @@ -6,14 +6,14 @@ type UseNetworkProps = { onReconnect?: () => void; }; -type UseNetwork = {isOffline: boolean}; +type UseNetwork = {isOffline: boolean; lastOfflineAt?: Date}; export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = {}): UseNetwork { const callback = useRef(onReconnect); // eslint-disable-next-line react-compiler/react-compiler callback.current = onReconnect; - const {isOffline, networkStatus} = useContext(NetworkContext) ?? {...CONST.DEFAULT_NETWORK_DATA, networkStatus: CONST.NETWORK.NETWORK_STATUS.UNKNOWN}; + const {isOffline, networkStatus, lastOfflineAt} = useContext(NetworkContext) ?? {...CONST.DEFAULT_NETWORK_DATA, networkStatus: CONST.NETWORK.NETWORK_STATUS.UNKNOWN}; const prevOfflineStatusRef = useRef(isOffline); useEffect(() => { // If we were offline before and now we are not offline then we just reconnected @@ -31,5 +31,5 @@ export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = { }, [isOffline]); // If the network status is undefined, we don't treat it as offline. Otherwise, we utilize the isOffline prop. - return {isOffline: networkStatus === CONST.NETWORK.NETWORK_STATUS.UNKNOWN ? false : isOffline}; + return {isOffline: networkStatus === CONST.NETWORK.NETWORK_STATUS.UNKNOWN ? false : isOffline, lastOfflineAt}; } diff --git a/src/hooks/useNetworkWithOfflineStatus.ts b/src/hooks/useNetworkWithOfflineStatus.ts index 9167b0cae969..789f9dc76d30 100644 --- a/src/hooks/useNetworkWithOfflineStatus.ts +++ b/src/hooks/useNetworkWithOfflineStatus.ts @@ -3,26 +3,31 @@ import {useEffect, useRef} from 'react'; import DateUtils from '@libs/DateUtils'; import useLocalize from './useLocalize'; import useNetwork from './useNetwork'; +import usePrevious from './usePrevious'; type UseNetworkWithOfflineStatus = {isOffline: boolean; lastOfflineAt: MutableRefObject; lastOnlineAt: MutableRefObject}; export default function useNetworkWithOfflineStatus(): UseNetworkWithOfflineStatus { - const {isOffline} = useNetwork(); + const {isOffline, lastOfflineAt: lastOfflineAtFromOnyx} = useNetwork(); + const prevIsOffline = usePrevious(isOffline); const {preferredLocale} = useLocalize(); // The last time/date the user went/was offline. If the user was never offline, it is set to undefined. - const lastOfflineAt = useRef(isOffline ? DateUtils.getLocalDateFromDatetime(preferredLocale) : undefined); + const lastOfflineAt = useRef(isOffline ? lastOfflineAtFromOnyx : undefined); // The last time/date the user went/was online. If the user was never online, it is set to undefined. const lastOnlineAt = useRef(isOffline ? undefined : DateUtils.getLocalDateFromDatetime(preferredLocale)); useEffect(() => { - if (isOffline) { + // If the user has just gone offline (was online before but is now offline), update `lastOfflineAt` with the current local date/time. + if (isOffline && !prevIsOffline) { lastOfflineAt.current = DateUtils.getLocalDateFromDatetime(preferredLocale); - } else { + } + // If the user has just come back online (was offline before but is now online), update `lastOnlineAt` with the current local date/time. + if (!isOffline && prevIsOffline) { lastOnlineAt.current = DateUtils.getLocalDateFromDatetime(preferredLocale); } - }, [isOffline, preferredLocale]); + }, [isOffline, preferredLocale, prevIsOffline]); return {isOffline, lastOfflineAt, lastOnlineAt}; } diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index b89b899bf80f..55c404459488 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -43,6 +43,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import {timezoneBackwardMap} from '@src/TIMEZONES'; import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails'; import {setCurrentDate} from './actions/CurrentDate'; +import {setNetworkLastOffline} from './actions/Network'; import {translate, translateLocal} from './Localize'; import Log from './Log'; @@ -88,6 +89,35 @@ Onyx.connect({ callback: (value) => (networkTimeSkew = value?.timeSkew ?? 0), }); +let isOffline: boolean | undefined; + +let preferredLocaleFromOnyx: Locale; + +Onyx.connect({ + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + callback: (value) => { + if (!value) { + return; + } + preferredLocaleFromOnyx = value; + }, +}); + +Onyx.connect({ + key: ONYXKEYS.NETWORK, + callback: (val) => { + if (!val?.lastOfflineAt) { + setNetworkLastOffline(getLocalDateFromDatetime(preferredLocaleFromOnyx)); + } + + const newIsOffline = val?.isOffline ?? val?.shouldForceOffline; + if (newIsOffline && isOffline === false) { + setNetworkLastOffline(getLocalDateFromDatetime(preferredLocaleFromOnyx)); + } + isOffline = newIsOffline; + }, +}); + function isDate(arg: unknown): arg is Date { return Object.prototype.toString.call(arg) === '[object Date]'; } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 6eba24b464bb..71080593baae 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1900,13 +1900,8 @@ function getReportActionsLength() { } function wasActionCreatedWhileOffline(action: ReportAction, isOffline: boolean, lastOfflineAt: Date | undefined, lastOnlineAt: Date | undefined, locale: Locale): boolean { - // The user was never online. - if (!lastOnlineAt) { - return true; - } - - // The user never was never offline. - if (!lastOfflineAt) { + // The user has never gone offline or never come back online + if (!lastOfflineAt || !lastOnlineAt) { return false; } diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index f2228a008dad..0ee99a19d82f 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -4,14 +4,18 @@ import type {NetworkStatus} from '@libs/NetworkConnection'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ConnectionChanges} from '@src/types/onyx/Network'; -function setIsOffline(isOffline: boolean, reason = '') { +function setNetworkLastOffline(lastOfflineAt: Date) { + Onyx.merge(ONYXKEYS.NETWORK, {lastOfflineAt}); +} + +function setIsOffline(isNetworkOffline: boolean, reason = '') { if (reason) { let textToLog = '[Network] Client is'; - textToLog += isOffline ? ' entering offline mode' : ' back online'; + textToLog += isNetworkOffline ? ' entering offline mode' : ' back online'; textToLog += ` because: ${reason}`; Log.info(textToLog); } - Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); + Onyx.merge(ONYXKEYS.NETWORK, {isOffline: isNetworkOffline}); } function setNetWorkStatus(status: NetworkStatus) { @@ -50,4 +54,14 @@ function setConnectionChanges(connectionChanges: ConnectionChanges) { Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges}); } -export {setIsOffline, setShouldForceOffline, setConnectionChanges, setShouldSimulatePoorConnection, setPoorConnectionTimeoutID, setShouldFailAllRequests, setTimeSkew, setNetWorkStatus}; +export { + setIsOffline, + setShouldForceOffline, + setConnectionChanges, + setShouldSimulatePoorConnection, + setPoorConnectionTimeoutID, + setShouldFailAllRequests, + setTimeSkew, + setNetWorkStatus, + setNetworkLastOffline, +}; diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index 74fb1202a8a2..7a22ff6eeee2 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -34,6 +34,9 @@ type Network = { /** The network's status */ networkStatus?: NetworkStatus; + + /** The time when network change from online to offline */ + lastOfflineAt?: Date; }; export default Network;