Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import isSearchTopmostFullScreenRoute from '@navigation/helpers/isSearchTopmostF
import FloatingMessageCounter from '@pages/home/report/FloatingMessageCounter';
import ReportActionsListItemRenderer from '@pages/home/report/ReportActionsListItemRenderer';
import shouldDisplayNewMarkerOnReportAction from '@pages/home/report/shouldDisplayNewMarkerOnReportAction';
import useReportUnreadMessageScrollTracking from '@pages/home/report/useReportUnreadMessageScrollTracking';
import {openReport, readNewestAction, subscribeToNewActionEvent} from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -114,7 +115,6 @@ function MoneyRequestReportActionsList({report, policy, reportActions = [], tran
const [currentUserAccountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: (session) => session?.accountID});

const canPerformWriteAction = canUserPerformWriteAction(report);
const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false);

const {shouldUseNarrowLayout} = useResponsiveLayout();

Expand Down Expand Up @@ -184,22 +184,6 @@ function MoneyRequestReportActionsList({report, policy, reportActions = [], tran
loadNewerChats(false);
}, [loadNewerChats]);

useEffect(() => {
if (
scrollingVerticalBottomOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
previousLastIndex.current !== lastActionIndex &&
reportActionSize.current > reportActions.length &&
hasNewestReportAction
) {
setIsFloatingMessageCounterVisible(false);
reportScrollManager.scrollToEnd();
}

previousLastIndex.current = lastActionIndex;
reportActionSize.current = visibleReportActions.length;
hasNewestReportActionRef.current = hasNewestReportAction;
}, [lastActionIndex, reportActions, reportScrollManager, hasNewestReportAction, visibleReportActions.length]);

const prevUnreadMarkerReportActionID = useRef<string | null>(null);

const visibleActionsMap = useMemo(() => {
Expand Down Expand Up @@ -277,6 +261,40 @@ function MoneyRequestReportActionsList({report, policy, reportActions = [], tran
}, [currentUserAccountID, earliestReceivedOfflineMessageIndex, prevVisibleActionsMap, visibleReportActions, unreadMarkerTime]);
prevUnreadMarkerReportActionID.current = unreadMarkerReportActionID;

const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling} = useReportUnreadMessageScrollTracking({
reportID: report.reportID,
currentVerticalScrollingOffsetRef: scrollingVerticalBottomOffset,
floatingMessageVisibleInitialValue: false,
readActionSkippedRef: readActionSkipped,
hasUnreadMarkerReportAction: !!unreadMarkerReportActionID,
onTrackScrolling: (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const {layoutMeasurement, contentSize, contentOffset} = event.nativeEvent;
const fullContentHeight = contentSize.height;

/**
* Count the diff between current scroll position and the bottom of the list.
* Diff == (height of all items in the list) - (height of the layout with the list) - (how far user scrolled)
*/
scrollingVerticalBottomOffset.current = fullContentHeight - layoutMeasurement.height - contentOffset.y;
},
});

useEffect(() => {
if (
scrollingVerticalBottomOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD &&
previousLastIndex.current !== lastActionIndex &&
reportActionSize.current > reportActions.length &&
hasNewestReportAction
) {
setIsFloatingMessageCounterVisible(false);
reportScrollManager.scrollToEnd();
}

previousLastIndex.current = lastActionIndex;
reportActionSize.current = visibleReportActions.length;
hasNewestReportActionRef.current = hasNewestReportAction;
}, [lastActionIndex, reportActions, reportScrollManager, hasNewestReportAction, visibleReportActions.length, setIsFloatingMessageCounterVisible]);

/**
* Subscribe to read/unread events and update our unreadMarkerTime
*/
Expand Down Expand Up @@ -330,7 +348,7 @@ function MoneyRequestReportActionsList({report, policy, reportActions = [], tran
}, DELAY_FOR_SCROLLING_TO_END);
});
},
[reportScrollManager],
[reportScrollManager, setIsFloatingMessageCounterVisible],
);

useEffect(() => {
Expand Down Expand Up @@ -399,37 +417,7 @@ function MoneyRequestReportActionsList({report, policy, reportActions = [], tran
reportScrollManager.scrollToEnd();
readActionSkipped.current = false;
readNewestAction(report.reportID);
}, [report.reportID, reportScrollManager, hasNewestReportAction]);

/**
* Todo - extract to reusable logic - https://github.com/Expensify/App/issues/58891
* Show/hide the new floating message counter when user is scrolling back/forth in the history of messages.
*/
const handleUnreadFloatingButton = () => {
if (scrollingVerticalBottomOffset.current > CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && !isFloatingMessageCounterVisible && !!unreadMarkerReportActionID) {
setIsFloatingMessageCounterVisible(true);
}

if (scrollingVerticalBottomOffset.current < CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && isFloatingMessageCounterVisible) {
if (readActionSkipped.current) {
readActionSkipped.current = false;
readNewestAction(report.reportID);
}
setIsFloatingMessageCounterVisible(false);
}
};

const trackVerticalScrolling = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const {layoutMeasurement, contentSize, contentOffset} = event.nativeEvent;
const fullContentHeight = contentSize.height;

/**
* Count the diff between current scroll position and the bottom of the list.
* Diff == (height of all items in the list) - (height of the layout with the list) - (how far user scrolled)
*/
scrollingVerticalBottomOffset.current = fullContentHeight - layoutMeasurement.height - contentOffset.y;
handleUnreadFloatingButton();
};
}, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID]);

const reportHasComments = visibleReportActions.length > 0;

Expand Down
45 changes: 17 additions & 28 deletions src/pages/home/report/ReportActionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import getInitialNumToRender from './getInitialNumReportActionsToRender';
import ListBoundaryLoader from './ListBoundaryLoader';
import ReportActionsListItemRenderer from './ReportActionsListItemRenderer';
import shouldDisplayNewMarkerOnReportAction from './shouldDisplayNewMarkerOnReportAction';
import useReportUnreadMessageScrollTracking from './useReportUnreadMessageScrollTracking';

type ReportActionsListProps = {
/** The report currently being looked at */
Expand Down Expand Up @@ -316,14 +317,25 @@ function ReportActionsList({
const reportActionID = route?.params?.reportActionID;
const indexOfLinkedAction = reportActionID ? sortedVisibleReportActions.findIndex((action) => action.reportActionID === reportActionID) : -1;
const isLinkedActionCloseToNewest = indexOfLinkedAction < IS_CLOSE_TO_NEWEST_THRESHOLD;
const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(!isLinkedActionCloseToNewest);

const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling} = useReportUnreadMessageScrollTracking({
reportID: report.reportID,
currentVerticalScrollingOffsetRef: scrollingVerticalOffset,
floatingMessageVisibleInitialValue: !isLinkedActionCloseToNewest,
readActionSkippedRef: readActionSkipped,
hasUnreadMarkerReportAction: !!unreadMarkerReportActionID,
onTrackScrolling: (event: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y;
onScroll?.(event);
},
});

useEffect(() => {
if (isLinkedActionCloseToNewest) {
return;
}
setIsFloatingMessageCounterVisible(true);
}, [isLinkedActionCloseToNewest, route]);
}, [isLinkedActionCloseToNewest, route, setIsFloatingMessageCounterVisible]);

useEffect(() => {
if (
Expand All @@ -337,7 +349,7 @@ function ReportActionsList({
}
previousLastIndex.current = lastActionIndex;
reportActionSize.current = sortedVisibleReportActions.length;
}, [lastActionIndex, sortedVisibleReportActions, reportScrollManager, hasNewestReportAction, linkedReportActionID]);
}, [lastActionIndex, sortedVisibleReportActions, reportScrollManager, hasNewestReportAction, linkedReportActionID, setIsFloatingMessageCounterVisible]);

useEffect(() => {
userActiveSince.current = DateUtils.getDBTime();
Expand Down Expand Up @@ -414,7 +426,7 @@ function ReportActionsList({
setIsScrollToBottomEnabled(true);
});
},
[report.reportID, reportScrollManager],
[report.reportID, reportScrollManager, setIsFloatingMessageCounterVisible],
);
useEffect(() => {
// Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function?
Expand Down Expand Up @@ -463,29 +475,6 @@ function ReportActionsList({
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [lastAction]);

/**
* Show/hide the new floating message counter when user is scrolling back/forth in the history of messages.
*/
const handleUnreadFloatingButton = () => {
if (scrollingVerticalOffset.current > CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && !isFloatingMessageCounterVisible && !!unreadMarkerReportActionID) {
setIsFloatingMessageCounterVisible(true);
}

if (scrollingVerticalOffset.current < CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && isFloatingMessageCounterVisible) {
if (readActionSkipped.current) {
readActionSkipped.current = false;
readNewestAction(report.reportID);
}
setIsFloatingMessageCounterVisible(false);
}
};

const trackVerticalScrolling = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y;
handleUnreadFloatingButton();
onScroll?.(event);
};

const scrollToBottomAndMarkReportAsRead = useCallback(() => {
setIsFloatingMessageCounterVisible(false);

Expand All @@ -498,7 +487,7 @@ function ReportActionsList({
reportScrollManager.scrollToBottom();
readActionSkipped.current = false;
readNewestAction(report.reportID);
}, [report.reportID, reportScrollManager, hasNewestReportAction]);
}, [setIsFloatingMessageCounterVisible, hasNewestReportAction, reportScrollManager, report.reportID]);

/**
* Calculates the ideal number of report actions to render in the first render, based on the screen height and on
Expand Down
67 changes: 67 additions & 0 deletions src/pages/home/report/useReportUnreadMessageScrollTracking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {useState} from 'react';
import type {MutableRefObject} from 'react';
import type {NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import {readNewestAction} from '@userActions/Report';
import CONST from '@src/CONST';

type Args = {
/** The report ID */
reportID: string;

/** The current offset of scrolling from either top or bottom of chat list */
currentVerticalScrollingOffsetRef: MutableRefObject<number>;

/** Ref for whether read action was skipped */
readActionSkippedRef: MutableRefObject<boolean>;

/** The initial value for visibility of floating message button */
floatingMessageVisibleInitialValue: boolean;

/** Whether the unread marker is displayed for any report action */
hasUnreadMarkerReportAction: boolean;

/** Callback to call on every scroll event */
onTrackScrolling: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
};

export default function useReportUnreadMessageScrollTracking({
reportID,
currentVerticalScrollingOffsetRef,
floatingMessageVisibleInitialValue,
hasUnreadMarkerReportAction,
readActionSkippedRef,
onTrackScrolling,
}: Args) {
const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(floatingMessageVisibleInitialValue);

/**
* On every scroll event we want to:
* Show/hide the new floating message counter when user is scrolling back/forth in the history of messages.
* Call any other callback that the component might need
*/
const trackVerticalScrolling = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
onTrackScrolling(event);

// display floating button if we're scrolled more than the offset
if (currentVerticalScrollingOffsetRef.current > CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && !isFloatingMessageCounterVisible && hasUnreadMarkerReportAction) {
setIsFloatingMessageCounterVisible(true);
}

// hide floating button if we're scrolled closer than the offset and mark message as read
if (currentVerticalScrollingOffsetRef.current < CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && isFloatingMessageCounterVisible) {
if (readActionSkippedRef.current) {
// eslint-disable-next-line react-compiler/react-compiler,no-param-reassign
readActionSkippedRef.current = false;
readNewestAction(reportID);
}

setIsFloatingMessageCounterVisible(false);
}
};

return {
isFloatingMessageCounterVisible,
setIsFloatingMessageCounterVisible,
trackVerticalScrolling,
};
}
Loading
Loading