Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Display unread marker for messages that were received while offline #49480

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c7d3751
feat: unread marker for messages while offline
chrispader Sep 19, 2024
64644ac
Merge branch 'Expensify:main' into @chrispader/unread-marker-not-disp…
chrispader Sep 19, 2024
fcc738c
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Sep 21, 2024
6d4ff5a
add comments
chrispader Sep 21, 2024
077ba40
add comment and fix logic
chrispader Sep 21, 2024
51bd4c2
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Sep 26, 2024
3c4ad02
fix: don't show unread marker for own messages
chrispader Sep 26, 2024
31dfa77
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Oct 1, 2024
744e54c
re-structure offline message detection logic
chrispader Oct 1, 2024
9a639a7
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Oct 17, 2024
d7465bf
simplify offline message check
chrispader Oct 17, 2024
dd99fe7
simplify code
chrispader Oct 17, 2024
0678a25
WIP: improve unread marker check
chrispader Oct 17, 2024
1900337
fix: unread marker not shown
chrispader Oct 17, 2024
40d0acf
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Oct 21, 2024
9d66dbb
fix: move offline/online at logic into seperate hook and defer values
chrispader Oct 21, 2024
c446f1f
fix: use ref instead of state for lastOffline/lastOnline at
chrispader Oct 21, 2024
e524d38
fix: move ReportAction util functions
chrispader Oct 21, 2024
35e071b
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Oct 22, 2024
3a06c85
fix: fix and simplify offline message check
chrispader Oct 22, 2024
91bbc8f
fix: wrong parameters
chrispader Oct 22, 2024
4cf4ff5
fix: remove dependency array eslint exception
chrispader Oct 22, 2024
9c86695
fix: re-implement offline message logic
chrispader Oct 23, 2024
2af086d
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Oct 23, 2024
9c4344c
Merge branch 'main' into @chrispader/unread-marker-not-displayed
chrispader Oct 29, 2024
d0f6d94
fix: wrong condition in early return
chrispader Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions src/hooks/useNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,63 @@
import {useContext, useEffect, useRef} from 'react';
import {useContext, useEffect, useMemo, useRef, useState} from 'react';
import {NetworkContext} from '@components/OnyxProvider';
import DateUtils from '@libs/DateUtils';
import CONST from '@src/CONST';
import useLocalize from './useLocalize';

type UseNetworkProps = {
onReconnect?: () => void;
};

type UseNetwork = {isOffline: boolean};
type UseNetwork = {isOffline: boolean; lastOfflineAt?: Date; lastOnlineAt?: Date};

export default function useNetwork({onReconnect = () => {}}: UseNetworkProps = {}): UseNetwork {
const callback = useRef(onReconnect);
callback.current = onReconnect;

const {isOffline, networkStatus} = useContext(NetworkContext) ?? {...CONST.DEFAULT_NETWORK_DATA, networkStatus: CONST.NETWORK.NETWORK_STATUS.UNKNOWN};
const prevOfflineStatusRef = useRef(isOffline);
const {preferredLocale} = useLocalize();
const {isOffline: isOfflineContext, networkStatus} = useContext(NetworkContext) ?? {...CONST.DEFAULT_NETWORK_DATA, networkStatus: CONST.NETWORK.NETWORK_STATUS.UNKNOWN};
const prevOfflineStatusRef = useRef(isOfflineContext);
useEffect(() => {
// If we were offline before and now we are not offline then we just reconnected
const didReconnect = prevOfflineStatusRef.current && !isOffline;
const didReconnect = prevOfflineStatusRef.current && !isOfflineContext;
if (!didReconnect) {
return;
}

callback.current();
}, [isOffline]);
}, [isOfflineContext]);

useEffect(() => {
// Used to store previous prop values to compare on next render
prevOfflineStatusRef.current = isOffline;
}, [isOffline]);
prevOfflineStatusRef.current = isOfflineContext;
}, [isOfflineContext]);

// 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};
const isOffline = useMemo(() => (networkStatus === CONST.NETWORK.NETWORK_STATUS.UNKNOWN ? false : isOfflineContext), [isOfflineContext, networkStatus]);

// Used to get the last time the user went offline.
// Set to a JS Date object if the user was offline before, otherwise undefined.
const [lastOfflineAt, setLastOfflineAt] = useState(() => (isOffline ? DateUtils.getLocalDateFromDatetime(preferredLocale) : undefined));
useEffect(() => {
if (!isOffline) {
return;
}
setLastOfflineAt(DateUtils.getLocalDateFromDatetime(preferredLocale));
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOffline]);

// Used to get the last time the user went back online after being offline.
// Set to a JS Date object if the user was online before, otherwise undefined.
const [lastOnlineAt, setLastOnlineAt] = useState(() => (isOffline ? undefined : DateUtils.getLocalDateFromDatetime(preferredLocale)));
useEffect(() => {
if (isOffline) {
return;
}
setLastOnlineAt(DateUtils.getLocalDateFromDatetime(preferredLocale));
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOffline]);

return {isOffline, lastOfflineAt, lastOnlineAt};
}
28 changes: 26 additions & 2 deletions src/pages/home/report/ReportActionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ function keyExtractor(item: OnyxTypes.ReportAction): string {
return item.reportActionID;
}

function wasMessageReceivedWhileOffline(message: OnyxTypes.ReportAction, offlineLastAt: Date | undefined, onlineLastAt: Date | undefined, locale: OnyxTypes.Locale): boolean {
if (!onlineLastAt || !offlineLastAt) {
return false;
}

const messageCreatedAt = DateUtils.getLocalDateFromDatetime(locale, message.created);

if (messageCreatedAt > offlineLastAt && messageCreatedAt <= onlineLastAt) {
return true;
}
return false;
}

function isMessageUnread(message: OnyxTypes.ReportAction, lastReadTime?: string): boolean {
if (!lastReadTime) {
return !ReportActionsUtils.isCreatedAction(message);
Expand Down Expand Up @@ -162,7 +175,8 @@ function ReportActionsList({
const {windowHeight} = useWindowDimensions();
const {shouldUseNarrowLayout} = useResponsiveLayout();

const {isOffline} = useNetwork();
const {preferredLocale} = useLocalize();
const {isOffline, lastOfflineAt, lastOnlineAt} = useNetwork();
const route = useRoute<RouteProp<AuthScreensParamList, typeof SCREENS.REPORT>>();
const reportScrollManager = useReportScrollManager();
const userActiveSince = useRef<string>(DateUtils.getDBTime());
Expand Down Expand Up @@ -220,6 +234,16 @@ function ReportActionsList({
const unreadMarkerReportActionID = useMemo(() => {
const shouldDisplayNewMarker = (reportAction: OnyxTypes.ReportAction, index: number): boolean => {
const nextMessage = sortedVisibleReportActions[index + 1];

// If the user recevied new messages while being offline, we want to display the unread marker above the first offline message.
if (!ReportActionsUtils.wasActionTakenByCurrentUser(reportAction)) {
const isCurrentMessageOffline = wasMessageReceivedWhileOffline(reportAction, lastOfflineAt, lastOnlineAt, preferredLocale);
const isNextMessageOffline = (nextMessage && wasMessageReceivedWhileOffline(nextMessage, lastOfflineAt, lastOnlineAt, preferredLocale)) || !nextMessage;
if (isCurrentMessageOffline && !isNextMessageOffline) {
return true;
}
}

const isCurrentMessageUnread = isMessageUnread(reportAction, unreadMarkerTime);
const isNextMessageRead = !nextMessage || !isMessageUnread(nextMessage, unreadMarkerTime);
const shouldDisplay = isCurrentMessageUnread && isNextMessageRead && !ReportActionsUtils.shouldHideNewMarker(reportAction);
Expand All @@ -236,7 +260,7 @@ function ReportActionsList({
}

return null;
}, [sortedVisibleReportActions, unreadMarkerTime]);
}, [sortedVisibleReportActions, lastOfflineAt, lastOnlineAt, preferredLocale, unreadMarkerTime]);

/**
* Subscribe to read/unread events and update our unreadMarkerTime
Expand Down
Loading