diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 332cc0d5ccf1..e5653de9a9c3 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1284,8 +1284,7 @@ const CONST = { }, }, THREAD_DISABLED: ['CREATED'], - // Used when displaying reportActions list to handle unread messages icon/button - SCROLL_VERTICAL_OFFSET_THRESHOLD: 200, + LATEST_MESSAGES_PILL_SCROLL_OFFSET_THRESHOLD: 2000, ACTION_VISIBLE_THRESHOLD: 250, MAX_GROUPING_TIME: 300000, }, diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index a151a87fa861..6f58d10e2e5f 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -357,7 +357,7 @@ function MoneyRequestReportActionsList({ /** * The reportActionID the unread marker should display above */ - const unreadMarkerReportActionID = useMemo(() => { + const [unreadMarkerReportActionID, unreadMarkerReportActionIndex] = useMemo(() => { // If there are message that were received while offline, // we can skip checking all messages later than the earliest received offline message. const startIndex = visibleReportActions.length - 1; @@ -384,20 +384,20 @@ function MoneyRequestReportActionsList({ // eslint-disable-next-line react-compiler/react-compiler if (shouldDisplayNewMarker) { - return reportAction.reportActionID; + return [reportAction.reportActionID, index]; } } - return null; + return [null, -1]; }, [currentUserAccountID, earliestReceivedOfflineMessageIndex, prevVisibleActionsMap, visibleReportActions, unreadMarkerTime]); prevUnreadMarkerReportActionID.current = unreadMarkerReportActionID; - const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling} = useReportUnreadMessageScrollTracking({ + const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling, onViewableItemsChanged} = useReportUnreadMessageScrollTracking({ reportID: report.reportID, currentVerticalScrollingOffsetRef: scrollingVerticalBottomOffset, - floatingMessageVisibleInitialValue: false, readActionSkippedRef: readActionSkipped, - hasUnreadMarkerReportAction: !!unreadMarkerReportActionID, + unreadMarkerReportActionIndex, + isInverted: false, onTrackScrolling: (event: NativeSyntheticEvent) => { const {layoutMeasurement, contentSize, contentOffset} = event.nativeEvent; const fullContentHeight = contentSize.height; @@ -675,6 +675,7 @@ function MoneyRequestReportActionsList({ )} @@ -694,6 +695,7 @@ function MoneyRequestReportActionsList({ style={styles.overscrollBehaviorContain} data={visibleReportActions} renderItem={renderItem} + onViewableItemsChanged={onViewableItemsChanged} keyExtractor={keyExtractor} onLayout={recordTimeToMeasureItemLayout} onEndReached={onEndReached} diff --git a/src/languages/de.ts b/src/languages/de.ts index ed6c1f598675..e15a5a7ae35f 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -880,6 +880,7 @@ const translations = { hereAlternateText: 'Benachrichtige alle in diesem Gespräch', }, newMessages: 'Neue Nachrichten', + latestMessages: 'Neueste Nachrichten', youHaveBeenBanned: 'Hinweis: Du wurdest von der Teilnahme an diesem Kanal ausgeschlossen.', reportTypingIndicator: { isTyping: 'tippt...', diff --git a/src/languages/en.ts b/src/languages/en.ts index ef7b9e4908b5..d4782e33cbe3 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -869,6 +869,7 @@ const translations = { hereAlternateText: 'Notify everyone in this conversation', }, newMessages: 'New messages', + latestMessages: 'Latest messages', youHaveBeenBanned: "Note: You've been banned from chatting in this channel.", reportTypingIndicator: { isTyping: 'is typing...', diff --git a/src/languages/es.ts b/src/languages/es.ts index e8f9f1bf89eb..cc0f0e0c4d99 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -862,6 +862,7 @@ const translations = { hereAlternateText: 'Notificar a todos en esta conversación', }, newMessages: 'Mensajes nuevos', + latestMessages: 'Últimos mensajes', youHaveBeenBanned: 'Nota: Se te ha prohibido comunicarte en este canal', reportTypingIndicator: { isTyping: 'está escribiendo...', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index a5fc1643ff78..7c2abee08efd 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -883,6 +883,7 @@ const translations = { hereAlternateText: 'Notifier tout le monde dans cette conversation', }, newMessages: 'Nouveaux messages', + latestMessages: 'Derniers messages', youHaveBeenBanned: 'Remarque : Vous avez été banni de la discussion dans ce canal.', reportTypingIndicator: { isTyping: 'est en train de taper...', diff --git a/src/languages/it.ts b/src/languages/it.ts index 1371ec2a2989..25628ec987a8 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -879,6 +879,7 @@ const translations = { hereAlternateText: 'Notifica tutti in questa conversazione', }, newMessages: 'Nuovi messaggi', + latestMessages: 'Ultimi messaggi', youHaveBeenBanned: 'Nota: Sei stato bannato dalla chat in questo canale.', reportTypingIndicator: { isTyping: 'sta scrivendo...', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index aebcef05c0a9..35d3e680680b 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -880,6 +880,7 @@ const translations = { hereAlternateText: 'この会話の全員に通知する', }, newMessages: '新しいメッセージ', + latestMessages: '最新のメッセージ', youHaveBeenBanned: '注意: このチャンネルでのチャットは禁止されています。', reportTypingIndicator: { isTyping: '入力中...', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index d43856e80f63..29ad265b9bc8 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -879,6 +879,7 @@ const translations = { hereAlternateText: 'Breng iedereen in dit gesprek op de hoogte', }, newMessages: 'Nieuwe berichten', + latestMessages: 'Laatste berichten', youHaveBeenBanned: 'Opmerking: Je bent verbannen van het chatten in dit kanaal.', reportTypingIndicator: { isTyping: 'is aan het typen...', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index d8977ee43551..526f8eca723f 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -879,6 +879,7 @@ const translations = { hereAlternateText: 'Powiadom wszystkich w tej rozmowie', }, newMessages: 'Nowe wiadomości', + latestMessages: 'Ostatnie wiadomości', youHaveBeenBanned: 'Uwaga: Zostałeś zbanowany z czatu na tym kanale.', reportTypingIndicator: { isTyping: 'pisze...', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index c81252be63bb..b825d52bc4ad 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -880,6 +880,7 @@ const translations = { hereAlternateText: 'Notificar todos nesta conversa', }, newMessages: 'Novas mensagens', + latestMessages: 'Mensagens recentes', youHaveBeenBanned: 'Nota: Você foi banido de conversar neste canal.', reportTypingIndicator: { isTyping: 'está digitando...', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 799557f2ca9f..94d7f69e4d38 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -878,6 +878,7 @@ const translations = { hereAlternateText: '通知此对话中的所有人', }, newMessages: '新消息', + latestMessages: '最新消息', youHaveBeenBanned: '注意:您已被禁止在此频道聊天。', reportTypingIndicator: { isTyping: '正在输入...', diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index a196704d2119..6c8e40a73856 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -14,6 +14,9 @@ type FloatingMessageCounterProps = { /** Whether the New Messages indicator is active */ isActive?: boolean; + /** Whether there are new messages */ + hasNewMessages: boolean; + /** Callback to be called when user clicks the New Messages indicator */ onClick?: () => void; }; @@ -21,7 +24,7 @@ type FloatingMessageCounterProps = { const MARKER_INACTIVE_TRANSLATE_Y = -40; const MARKER_ACTIVE_TRANSLATE_Y = 10; -function FloatingMessageCounter({isActive = false, onClick = () => {}}: FloatingMessageCounterProps) { +function FloatingMessageCounter({isActive = false, onClick = () => {}, hasNewMessages}: FloatingMessageCounterProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -60,7 +63,7 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index bb782c5438bc..d3601fbc0720 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -114,8 +114,6 @@ type ReportActionsListProps = { shouldEnableAutoScrollToTopThreshold?: boolean; }; -const IS_CLOSE_TO_NEWEST_THRESHOLD = 15; - // In the component we are subscribing to the arrival of new actions. // As there is the possibility that there are multiple instances of a ReportScreen // for the same report, we only ever want one subscription to be active, as @@ -247,7 +245,7 @@ function ReportActionsList({ /** * The reportActionID the unread marker should display above */ - const unreadMarkerReportActionID = useMemo(() => { + const [unreadMarkerReportActionID, unreadMarkerReportActionIndex] = useMemo(() => { // If there are message that were received while offline, // we can skip checking all messages later than the earliest received offline message. const startIndex = earliestReceivedOfflineMessageIndex ?? 0; @@ -272,11 +270,11 @@ function ReportActionsList({ prevUnreadMarkerReportActionID: prevUnreadMarkerReportActionID.current, }); if (shouldDisplayNewMarker) { - return reportAction.reportActionID; + return [reportAction.reportActionID, index]; } } - return null; + return [null, -1]; }, [accountID, earliestReceivedOfflineMessageIndex, prevSortedVisibleReportActionsObjects, sortedVisibleReportActions, unreadMarkerTime]); prevUnreadMarkerReportActionID.current = unreadMarkerReportActionID; @@ -330,28 +328,19 @@ function ReportActionsList({ // Display the new message indicator when comment linking and not close to the newest message. 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, trackVerticalScrolling} = useReportUnreadMessageScrollTracking({ + const {isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling, onViewableItemsChanged} = useReportUnreadMessageScrollTracking({ reportID: report.reportID, currentVerticalScrollingOffsetRef: scrollingVerticalOffset, - floatingMessageVisibleInitialValue: !isLinkedActionCloseToNewest, readActionSkippedRef: readActionSkipped, - hasUnreadMarkerReportAction: !!unreadMarkerReportActionID, + unreadMarkerReportActionIndex, + isInverted: true, onTrackScrolling: (event: NativeSyntheticEvent) => { scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y; onScroll?.(event); }, }); - useEffect(() => { - if (isLinkedActionCloseToNewest) { - return; - } - setIsFloatingMessageCounterVisible(true); - }, [isLinkedActionCloseToNewest, route, setIsFloatingMessageCounterVisible]); - useEffect(() => { if ( scrollingVerticalOffset.current < AUTOSCROLL_TO_TOP_THRESHOLD && @@ -777,6 +766,7 @@ function ReportActionsList({ return ( <> @@ -806,6 +796,7 @@ function ReportActionsList({ keyboardShouldPersistTaps="handled" onLayout={onLayoutInner} onScroll={trackVerticalScrolling} + onViewableItemsChanged={onViewableItemsChanged} onScrollToIndexFailed={onScrollToIndexFailed} extraData={extraData} key={listID} diff --git a/src/pages/home/report/useReportUnreadMessageScrollTracking.ts b/src/pages/home/report/useReportUnreadMessageScrollTracking.ts index 89b53342ee54..93d2e41c07f6 100644 --- a/src/pages/home/report/useReportUnreadMessageScrollTracking.ts +++ b/src/pages/home/report/useReportUnreadMessageScrollTracking.ts @@ -1,6 +1,6 @@ -import {useState} from 'react'; +import {useCallback, useEffect, useRef, useState} from 'react'; import type {RefObject} from 'react'; -import type {NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; +import type {NativeScrollEvent, NativeSyntheticEvent, ViewToken} from 'react-native'; import {readNewestAction} from '@userActions/Report'; import CONST from '@src/CONST'; @@ -8,17 +8,17 @@ type Args = { /** The report ID */ reportID: string; + /** Whether the FlatList is inverted, we need it to determine if the current unread message is visible. */ + isInverted: boolean; + /** The current offset of scrolling from either top or bottom of chat list */ currentVerticalScrollingOffsetRef: RefObject; /** Ref for whether read action was skipped */ readActionSkippedRef: RefObject; - /** The initial value for visibility of floating message button */ - floatingMessageVisibleInitialValue: boolean; - - /** Whether the unread marker is displayed for any report action */ - hasUnreadMarkerReportAction: boolean; + /** The index of the unread report action */ + unreadMarkerReportActionIndex: number; /** Callback to call on every scroll event */ onTrackScrolling: (event: NativeSyntheticEvent) => void; @@ -27,43 +27,95 @@ type Args = { export default function useReportUnreadMessageScrollTracking({ reportID, currentVerticalScrollingOffsetRef, - floatingMessageVisibleInitialValue, - hasUnreadMarkerReportAction, readActionSkippedRef, onTrackScrolling, + unreadMarkerReportActionIndex, + isInverted, }: Args) { - const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(floatingMessageVisibleInitialValue); + const [isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible] = useState(false); + const ref = useRef<{previousViewableItems: ViewToken[]; reportID: string; unreadMarkerReportActionIndex: number}>({reportID, unreadMarkerReportActionIndex, previousViewableItems: []}); + // We want to save the updated value on ref to use it in onViewableItemsChanged + // because FlatList requires the callback to be stable and we cannot add a dependency on the useCallback. + useEffect(() => { + ref.current.reportID = reportID; + ref.current.previousViewableItems = []; + }, [reportID]); /** * 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. + * Show/hide the latest message pill when user is scrolling back/forth in the history of messages. * Call any other callback that the component might need */ const trackVerticalScrolling = (event: NativeSyntheticEvent | undefined) => { if (event) { onTrackScrolling(event); } + const hasUnreadMarkerReportAction = unreadMarkerReportActionIndex !== -1; // display floating button if we're scrolled more than the offset - if (currentVerticalScrollingOffsetRef.current > CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD && !isFloatingMessageCounterVisible && hasUnreadMarkerReportAction) { + if ( + currentVerticalScrollingOffsetRef.current > CONST.REPORT.ACTIONS.LATEST_MESSAGES_PILL_SCROLL_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); - } - + // hide floating button if we're scrolled closer than the offset + if ( + currentVerticalScrollingOffsetRef.current < CONST.REPORT.ACTIONS.LATEST_MESSAGES_PILL_SCROLL_OFFSET_THRESHOLD && + isFloatingMessageCounterVisible && + !hasUnreadMarkerReportAction + ) { setIsFloatingMessageCounterVisible(false); } }; + const onViewableItemsChanged = useCallback(({viewableItems}: {viewableItems: ViewToken[]; changed: ViewToken[]}) => { + ref.current.previousViewableItems = viewableItems; + const viewableIndexes = viewableItems.map((viewableItem) => viewableItem.index).filter((value) => typeof value === 'number') as number[]; + const maxIndex = Math.max(...viewableIndexes); + const minIndex = Math.min(...viewableIndexes); + const unreadActionIndex = ref.current.unreadMarkerReportActionIndex; + const hasUnreadMarkerReportAction = unreadActionIndex !== -1; + const unreadActionVisible = isInverted ? unreadActionIndex >= minIndex : unreadActionIndex <= maxIndex; + + // display floating button if the unread report action is out of view + if (!unreadActionVisible && hasUnreadMarkerReportAction) { + setIsFloatingMessageCounterVisible(true); + } + // hide floating button if the unread report action becomes visible + if (unreadActionVisible && hasUnreadMarkerReportAction) { + setIsFloatingMessageCounterVisible(false); + } + + // if we're scrolled closer than the offset and read action has been skipped then mark message as read + if (unreadActionVisible && readActionSkippedRef.current) { + // eslint-disable-next-line react-compiler/react-compiler,no-param-reassign + readActionSkippedRef.current = false; + readNewestAction(ref.current.reportID); + } + + // FlatList requires a stable onViewableItemsChanged callback for optimal performance. + // Therefore, we use a ref to store values instead of adding them as dependencies. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // When unreadMarkerReportActionIndex changes we will manually call onViewableItemsChanged with previousViewableItems to recalculate + // the state of floating button because onViewableItemsChanged on FlatList will only be called when viewable items change. + useEffect(() => { + ref.current.unreadMarkerReportActionIndex = unreadMarkerReportActionIndex; + + if (ref.current.previousViewableItems.length) { + onViewableItemsChanged({viewableItems: ref.current.previousViewableItems, changed: []}); + } + }, [onViewableItemsChanged, unreadMarkerReportActionIndex]); + return { isFloatingMessageCounterVisible, setIsFloatingMessageCounterVisible, trackVerticalScrolling, + onViewableItemsChanged, }; } diff --git a/tests/unit/useReportUnreadMessageScrollTrackingTest.ts b/tests/unit/useReportUnreadMessageScrollTrackingTest.ts index 62f7ebda0b4c..1ff82220f99d 100644 --- a/tests/unit/useReportUnreadMessageScrollTrackingTest.ts +++ b/tests/unit/useReportUnreadMessageScrollTrackingTest.ts @@ -20,26 +20,36 @@ describe('useReportUnreadMessageScrollTracking', () => { describe('on init and without any scrolling', () => { const onTrackScrollingMockFn = jest.fn(); - it('returns initial floatingMessage visibility and sets no state', () => { + it('returns floatingMessage visibility that was set to a new value', () => { // Given const offsetRef = {current: 0}; - const {result} = renderHook(() => + const {result, rerender} = renderHook(() => useReportUnreadMessageScrollTracking({ reportID, currentVerticalScrollingOffsetRef: offsetRef, readActionSkippedRef: readActionRefFalse, - floatingMessageVisibleInitialValue: false, - hasUnreadMarkerReportAction: false, + unreadMarkerReportActionIndex: -1, + isInverted: true, onTrackScrolling: onTrackScrollingMockFn, }), ); + // When + act(() => { + result.current.setIsFloatingMessageCounterVisible(true); + }); + rerender({}); + // Then - expect(result.current.isFloatingMessageCounterVisible).toBe(false); + expect(result.current.isFloatingMessageCounterVisible).toBe(true); expect(onTrackScrollingMockFn).not.toBeCalled(); }); + }); - it('returns floatingMessage visibility that was set to a new value', () => { + describe('when scrolling', () => { + const onTrackScrollingMockFn = jest.fn(); + + it('returns floatingMessage visibility as true when scrolling outside of threshold', () => { // Given const offsetRef = {current: 0}; const {result, rerender} = renderHook(() => @@ -47,47 +57,49 @@ describe('useReportUnreadMessageScrollTracking', () => { reportID, currentVerticalScrollingOffsetRef: offsetRef, readActionSkippedRef: readActionRefFalse, - floatingMessageVisibleInitialValue: false, - hasUnreadMarkerReportAction: false, + isInverted: true, + unreadMarkerReportActionIndex: -1, onTrackScrolling: onTrackScrollingMockFn, }), ); // When act(() => { - result.current.setIsFloatingMessageCounterVisible(true); + offsetRef.current = CONST.REPORT.ACTIONS.LATEST_MESSAGES_PILL_SCROLL_OFFSET_THRESHOLD + 100; + result.current.trackVerticalScrolling(emptyScrollEventMock); }); rerender({}); // Then expect(result.current.isFloatingMessageCounterVisible).toBe(true); - expect(onTrackScrollingMockFn).not.toBeCalled(); + expect(onTrackScrollingMockFn).toBeCalledWith(emptyScrollEventMock); }); - }); - describe('when scrolling', () => { - const onTrackScrollingMockFn = jest.fn(); - - it('returns floatingMessage visibility as true when scrolling outside of threshold', () => { + it('returns floatingMessage visibility as true when the unread message is not visible in the view port', () => { // Given const offsetRef = {current: 0}; - const {result, rerender} = renderHook(() => + const {result} = renderHook(() => useReportUnreadMessageScrollTracking({ reportID, currentVerticalScrollingOffsetRef: offsetRef, readActionSkippedRef: readActionRefFalse, - floatingMessageVisibleInitialValue: false, - hasUnreadMarkerReportAction: true, + isInverted: true, + unreadMarkerReportActionIndex: 1, onTrackScrolling: onTrackScrollingMockFn, }), ); // When act(() => { - offsetRef.current = CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD + 100; - result.current.trackVerticalScrolling(emptyScrollEventMock); + result.current.onViewableItemsChanged({viewableItems: [{index: 1, key: 'reportActions_1', isViewable: true, item: {}}], changed: []}); + }); + + expect(result.current.isFloatingMessageCounterVisible).toBe(false); + + // When + act(() => { + result.current.onViewableItemsChanged({viewableItems: [{index: 2, key: 'reportActions_2', isViewable: true, item: {}}], changed: []}); }); - rerender({}); // Then expect(result.current.isFloatingMessageCounterVisible).toBe(true); @@ -97,64 +109,87 @@ describe('useReportUnreadMessageScrollTracking', () => { it('returns floatingMessage visibility as false when scrolling inside the threshold', () => { // Given const offsetRef = {current: 0}; - const {result, rerender} = renderHook(() => + const {result} = renderHook(() => useReportUnreadMessageScrollTracking({ reportID, currentVerticalScrollingOffsetRef: offsetRef, readActionSkippedRef: readActionRefFalse, - floatingMessageVisibleInitialValue: false, - hasUnreadMarkerReportAction: true, + unreadMarkerReportActionIndex: -1, + isInverted: true, onTrackScrolling: onTrackScrollingMockFn, }), ); // When act(() => { - offsetRef.current = CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD - 100; + offsetRef.current = CONST.REPORT.ACTIONS.LATEST_MESSAGES_PILL_SCROLL_OFFSET_THRESHOLD - 100; result.current.trackVerticalScrolling(emptyScrollEventMock); }); - rerender({}); // Then expect(result.current.isFloatingMessageCounterVisible).toBe(false); expect(onTrackScrollingMockFn).toBeCalledWith(emptyScrollEventMock); }); - it('calls readAction when scrolling inside the threshold and the message and read action skipped is true', () => { + it('returns floatingMessage visibility as false when unread message is visible', () => { // Given const offsetRef = {current: 0}; - const {result, rerender} = renderHook(() => + const {result} = renderHook(() => + useReportUnreadMessageScrollTracking({ + reportID, + currentVerticalScrollingOffsetRef: offsetRef, + readActionSkippedRef: readActionRefFalse, + unreadMarkerReportActionIndex: 1, + isInverted: true, + onTrackScrolling: onTrackScrollingMockFn, + }), + ); + + // When + act(() => { + result.current.onViewableItemsChanged({viewableItems: [{index: 2, key: 'reportActions_2', isViewable: true, item: {}}], changed: []}); + }); + expect(result.current.isFloatingMessageCounterVisible).toBe(true); + + act(() => { + result.current.onViewableItemsChanged({viewableItems: [{index: 1, key: 'reportActions_1', isViewable: true, item: {}}], changed: []}); + }); + + // Then + expect(result.current.isFloatingMessageCounterVisible).toBe(false); + expect(onTrackScrollingMockFn).toBeCalledWith(emptyScrollEventMock); + }); + + it('calls readAction when scrolling to an extent the unread message is visible and read action skipped is true', () => { + // Given + const offsetRef = {current: 0}; + const {result} = renderHook(() => useReportUnreadMessageScrollTracking({ reportID, currentVerticalScrollingOffsetRef: offsetRef, readActionSkippedRef: {current: true}, - floatingMessageVisibleInitialValue: false, - hasUnreadMarkerReportAction: true, + unreadMarkerReportActionIndex: 1, + isInverted: true, onTrackScrolling: onTrackScrollingMockFn, }), ); // When act(() => { - // offset greater, will set visible to true - offsetRef.current = CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD + 100; - result.current.trackVerticalScrolling(emptyScrollEventMock); + // if unread action is not visible, the floating button will be visible + result.current.onViewableItemsChanged({viewableItems: [{index: 2, key: 'reportActions_2', isViewable: true, item: {}}], changed: []}); }); expect(result.current.isFloatingMessageCounterVisible).toBe(true); expect(readNewestAction).toBeCalledTimes(0); - rerender({}); - act(() => { - // scrolling into the offset, should call readNewestAction - offsetRef.current = CONST.REPORT.ACTIONS.SCROLL_VERTICAL_OFFSET_THRESHOLD - 100; - result.current.trackVerticalScrolling(emptyScrollEventMock); + // scrolling so that the unread action is visible, should call readNewestAction + result.current.onViewableItemsChanged({viewableItems: [{index: 1, key: 'reportActions_1', isViewable: true, item: {}}], changed: []}); }); // Then expect(readNewestAction).toBeCalledTimes(1); - expect(onTrackScrollingMockFn).toBeCalledWith(emptyScrollEventMock); expect(readActionRefFalse.current).toBe(false); expect(result.current.isFloatingMessageCounterVisible).toBe(false); });