From 33bd432926e63700d750f21cce6301e98fa35e8e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Feb 2025 12:08:45 +0700 Subject: [PATCH 1/5] fix: New message marker is not displayed when the chat opened offline --- src/hooks/useNetworkWithOfflineStatus.ts | 16 ++++++--- src/libs/ReportActionsUtils.ts | 2 +- src/libs/actions/Network.ts | 41 ++++++++++++++++++++++-- src/types/onyx/Network.ts | 3 ++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/hooks/useNetworkWithOfflineStatus.ts b/src/hooks/useNetworkWithOfflineStatus.ts index 9167b0cae969..ee663bddb7f0 100644 --- a/src/hooks/useNetworkWithOfflineStatus.ts +++ b/src/hooks/useNetworkWithOfflineStatus.ts @@ -1,28 +1,36 @@ import type {MutableRefObject} from 'react'; import {useEffect, useRef} from 'react'; +import {useOnyx} from 'react-native-onyx'; import DateUtils from '@libs/DateUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; 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 prevIsOffline = usePrevious(isOffline); const {preferredLocale} = useLocalize(); + const [network] = useOnyx(ONYXKEYS.NETWORK); + // 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 ? network?.lastOfflineAt : 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 (isOffline && !prevIsOffline) { lastOfflineAt.current = DateUtils.getLocalDateFromDatetime(preferredLocale); - } else { + } + + if (!isOffline && prevIsOffline) { lastOnlineAt.current = DateUtils.getLocalDateFromDatetime(preferredLocale); } - }, [isOffline, preferredLocale]); + }, [isOffline, preferredLocale, prevIsOffline]); return {isOffline, lastOfflineAt, lastOnlineAt}; } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 3dfd62d1c00a..1e998c0bbf0e 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1897,7 +1897,7 @@ function wasActionCreatedWhileOffline(action: ReportAction, isOffline: boolean, } // The user never was never offline. - if (!lastOfflineAt) { + if (!lastOfflineAt || !lastOnlineAt) { return false; } diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index f2228a008dad..2f3f085d7117 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,17 +1,52 @@ import Onyx from 'react-native-onyx'; +import DateUtils from '@libs/DateUtils'; import Log from '@libs/Log'; import type {NetworkStatus} from '@libs/NetworkConnection'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Locale} from '@src/types/onyx'; import type {ConnectionChanges} from '@src/types/onyx/Network'; -function setIsOffline(isOffline: boolean, reason = '') { +let isOffline: boolean | undefined; + +let preferredLocale: Locale; + +Onyx.connect({ + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + callback: (value) => { + if (!value) { + return; + } + preferredLocale = value; + }, +}); + +Onyx.connect({ + key: ONYXKEYS.NETWORK, + callback: (val) => { + if (!val?.lastOfflineAt) { + setNetworkLastOffline(DateUtils.getLocalDateFromDatetime(preferredLocale)); + } + + const newIsOffline = val?.isOffline ?? val?.shouldForceOffline; + if (newIsOffline && isOffline === false) { + setNetworkLastOffline(DateUtils.getLocalDateFromDatetime(preferredLocale)); + } + isOffline = newIsOffline; + }, +}); + +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) { 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; From 72b9ea7f2f7f1ae28b8749f683cbc776d710821d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Feb 2025 15:15:55 +0700 Subject: [PATCH 2/5] fix test --- src/libs/ReportActionsUtils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 1e998c0bbf0e..0def87ca3394 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1891,11 +1891,6 @@ 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 || !lastOnlineAt) { return false; From ed37d21e5f1a53f7ae2761185082e6541b117440 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Feb 2025 15:27:53 +0700 Subject: [PATCH 3/5] fix test --- src/hooks/useNetwork.ts | 6 +++--- src/hooks/useNetworkWithOfflineStatus.ts | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) 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 ee663bddb7f0..1fc0a9a9b9af 100644 --- a/src/hooks/useNetworkWithOfflineStatus.ts +++ b/src/hooks/useNetworkWithOfflineStatus.ts @@ -1,8 +1,6 @@ import type {MutableRefObject} from 'react'; import {useEffect, useRef} from 'react'; -import {useOnyx} from 'react-native-onyx'; import DateUtils from '@libs/DateUtils'; -import ONYXKEYS from '@src/ONYXKEYS'; import useLocalize from './useLocalize'; import useNetwork from './useNetwork'; import usePrevious from './usePrevious'; @@ -10,14 +8,12 @@ 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(); - const [network] = useOnyx(ONYXKEYS.NETWORK); - // The last time/date the user went/was offline. If the user was never offline, it is set to undefined. - const lastOfflineAt = useRef(isOffline ? network?.lastOfflineAt : 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)); From e00c97f5fdd75b666d883284edae7e0512e851bf Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 6 Feb 2025 17:41:22 +0700 Subject: [PATCH 4/5] fix test --- src/libs/DateUtils.ts | 30 ++++++++++++++++++++++++++ src/libs/actions/Network.ts | 43 ++++++++++--------------------------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 6952324a33d3..da53446da9e9 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/actions/Network.ts b/src/libs/actions/Network.ts index 2f3f085d7117..0ee99a19d82f 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,40 +1,9 @@ import Onyx from 'react-native-onyx'; -import DateUtils from '@libs/DateUtils'; import Log from '@libs/Log'; import type {NetworkStatus} from '@libs/NetworkConnection'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Locale} from '@src/types/onyx'; import type {ConnectionChanges} from '@src/types/onyx/Network'; -let isOffline: boolean | undefined; - -let preferredLocale: Locale; - -Onyx.connect({ - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - callback: (value) => { - if (!value) { - return; - } - preferredLocale = value; - }, -}); - -Onyx.connect({ - key: ONYXKEYS.NETWORK, - callback: (val) => { - if (!val?.lastOfflineAt) { - setNetworkLastOffline(DateUtils.getLocalDateFromDatetime(preferredLocale)); - } - - const newIsOffline = val?.isOffline ?? val?.shouldForceOffline; - if (newIsOffline && isOffline === false) { - setNetworkLastOffline(DateUtils.getLocalDateFromDatetime(preferredLocale)); - } - isOffline = newIsOffline; - }, -}); - function setNetworkLastOffline(lastOfflineAt: Date) { Onyx.merge(ONYXKEYS.NETWORK, {lastOfflineAt}); } @@ -85,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, +}; From c265fed28f011c6739ab9b2383b71cfec6a4dc0d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 10 Feb 2025 15:42:08 +0700 Subject: [PATCH 5/5] add comment --- src/hooks/useNetworkWithOfflineStatus.ts | 3 ++- src/libs/ReportActionsUtils.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/useNetworkWithOfflineStatus.ts b/src/hooks/useNetworkWithOfflineStatus.ts index 1fc0a9a9b9af..789f9dc76d30 100644 --- a/src/hooks/useNetworkWithOfflineStatus.ts +++ b/src/hooks/useNetworkWithOfflineStatus.ts @@ -19,10 +19,11 @@ export default function useNetworkWithOfflineStatus(): UseNetworkWithOfflineStat const lastOnlineAt = useRef(isOffline ? undefined : DateUtils.getLocalDateFromDatetime(preferredLocale)); useEffect(() => { + // 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); } - + // 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); } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 43f10f801aae..71080593baae 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1900,7 +1900,7 @@ function getReportActionsLength() { } function wasActionCreatedWhileOffline(action: ReportAction, isOffline: boolean, lastOfflineAt: Date | undefined, lastOnlineAt: Date | undefined, locale: Locale): boolean { - // The user never was never offline. + // The user has never gone offline or never come back online if (!lastOfflineAt || !lastOnlineAt) { return false; }