Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/components/FlashList/InvertedFlashList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {FlashListProps} from '@shopify/flash-list';
import React from 'react';
import type FlatListRefType from '@components/FlashList/types';
import useFlashListScrollKey from '@components/FlashList/useFlashListScrollKey';
import type {FlatListRefType} from '@pages/inbox/ReportScreenContext';
import FlashList from '..';
import CellRendererComponent from './CellRendererComponent';

Expand Down
7 changes: 7 additions & 0 deletions src/components/FlashList/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type {RefObject} from 'react';
import type {FlatList} from 'react-native';

/** Ref to the underlying list instance attached via `ref={}`. */
type FlatListRefType = RefObject<FlatList<unknown> | null> | null;

export default FlatListRefType;
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import {useIsFocused, useRoute} from '@react-navigation/native';
import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import isEmpty from 'lodash/isEmpty';
import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {LayoutChangeEvent, ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {FlatList, LayoutChangeEvent, ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import {DeviceEventEmitter, View} from 'react-native';
import FlatListWithScrollKey from '@components/FlatList/FlatListWithScrollKey';
import ScrollView from '@components/ScrollView';
Expand Down Expand Up @@ -50,14 +50,14 @@ import markOpenReportEnd from '@libs/telemetry/markOpenReportEnd';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import Visibility from '@libs/Visibility';
import isSearchTopmostFullScreenRoute from '@navigation/helpers/isSearchTopmostFullScreenRoute';
import {useActionListContext} from '@pages/inbox/ActionListContext';
import {useConciergeDraft} from '@pages/inbox/ConciergeDraftContext';
import FloatingMessageCounter from '@pages/inbox/report/FloatingMessageCounter';
import getInitialNumToRender from '@pages/inbox/report/getInitialNumReportActionsToRender';
import ReportActionIndexContext from '@pages/inbox/report/ReportActionIndexContext';
import ReportActionsListItemRenderer from '@pages/inbox/report/ReportActionsListItemRenderer';
import {getUnreadMarkerReportAction} from '@pages/inbox/report/shouldDisplayNewMarkerOnReportAction';
import useReportUnreadMessageScrollTracking from '@pages/inbox/report/useReportUnreadMessageScrollTracking';
import {ActionListContext} from '@pages/inbox/ReportScreenContext';
import variables from '@styles/variables';
import {getOlderActions, openReport, readNewestAction, subscribeToNewActionEvent} from '@userActions/Report';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -196,7 +196,17 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)

const lastAction = visibleReportActions.at(-1);

const {scrollOffsetRef} = useContext(ActionListContext);
const {scrollOffsetRef, registerListRef} = useActionListContext();

// Own the list ref locally and publish it so handlers resolve it via `getListRef()`. Use a
// layout effect so the ref is registered at commit — before any layout-time scroll handler
// reads it via `getListRef()` — rather than after paint.
const listRef = useRef<FlatList>(null);
useLayoutEffect(() => {
registerListRef(listRef);
return () => registerListRef(null);
}, [registerListRef]);

const scrollingVerticalBottomOffset = useRef(0);
const scrollingVerticalTopOffset = useRef(0);
const wrapperViewRef = useRef<View>(null);
Expand Down Expand Up @@ -754,7 +764,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
keyboardShouldPersistTaps="handled"
onScroll={trackVerticalScrolling}
contentContainerStyle={[shouldUseNarrowLayout ? styles.pt4 : styles.pt3]}
ref={reportScrollManager.ref}
ref={listRef}
ListEmptyComponent={!isOffline && showReportActionsLoadingState ? <ReportActionsListLoadingSkeleton reasonAttributes={skeletonReasonAttributes} /> : undefined} // This skeleton component is only used for loading state, the empty state is handled by SearchMoneyRequestReportEmptyState
removeClippedSubviews={false}
initialScrollKey={linkedReportActionID}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Reactions/EmojiReactionBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Text from '@components/Text';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import type {ReactionListEvent} from '@pages/inbox/ReportScreenContext';
import type {ReactionListEvent} from '@pages/inbox/ReactionListContext';
import CONST from '@src/CONST';

type EmojiReactionBubbleProps = {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Reactions/ReportActionReactionBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React, {useContext, useRef} from 'react';
import {View} from 'react-native';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
import {ReactionListContext} from '@pages/inbox/ReportScreenContext';
import type {ReactionListAnchor, ReactionListEvent} from '@pages/inbox/ReportScreenContext';
import {ReactionListContext} from '@pages/inbox/ReactionListContext';
import type {ReactionListAnchor, ReactionListEvent} from '@pages/inbox/ReactionListContext';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import EmojiReactionBubble from './EmojiReactionBubble';
import ReactionTooltipContent from './ReactionTooltipContent';
Expand Down
13 changes: 0 additions & 13 deletions src/hooks/useActionListContextValue.ts

This file was deleted.

10 changes: 3 additions & 7 deletions src/hooks/useReportActionsScroll.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useRoute} from '@react-navigation/native';
import {useContext, useEffect, useEffectEvent, useState} from 'react';
import {useEffect, useEffectEvent, useState} from 'react';
import type {NativeScrollEvent, NativeSyntheticEvent, ViewToken} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/FlatList/hooks/useFlatListScrollKey';
Expand All @@ -12,9 +12,9 @@ import TransitionTracker from '@libs/Navigation/TransitionTracker';
import {isReportPreviewAction, isSentMoneyReportAction, isTransactionThread} from '@libs/ReportActionsUtils';
import {getReportLastVisibleActionCreated, isInvoiceReport, isMoneyRequestReport} from '@libs/ReportUtils';
import type {ReportsSplitNavigatorParamList} from '@navigation/types';
import {useActionListContext} from '@pages/inbox/ActionListContext';
import useReportActionsNewActionLiveTail from '@pages/inbox/report/useReportActionsNewActionLiveTail';
import useReportUnreadMessageScrollTracking from '@pages/inbox/report/useReportUnreadMessageScrollTracking';
import {ActionListContext} from '@pages/inbox/ReportScreenContext';
import {openReport} from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -74,9 +74,6 @@ type UseReportActionsScrollParams = {
};

type UseReportActionsScrollResult = {
/** Ref to attach to the inverted FlashList */
listRef: ReturnType<typeof useReportScrollManager>['ref'];

/** Scroll handler that tracks vertical offset and floating counter visibility */
trackVerticalScrolling: (event: NativeSyntheticEvent<NativeScrollEvent> | undefined) => void;

Expand Down Expand Up @@ -132,7 +129,7 @@ function useReportActionsScroll({
setTreatAsNoPaginationAnchor,
}: UseReportActionsScrollParams): UseReportActionsScrollResult {
const reportScrollManager = useReportScrollManager();
const {scrollOffsetRef} = useContext(ActionListContext);
const {scrollOffsetRef} = useActionListContext();
const route = useRoute<PlatformStackRouteProp<ReportsSplitNavigatorParamList, typeof SCREENS.REPORT>>();
const linkedReportActionID = route?.params?.reportActionID;
const backTo = route?.params?.backTo;
Expand Down Expand Up @@ -355,7 +352,6 @@ function useReportActionsScroll({
}, [shouldFocusToTopOnMount, shouldAutoscrollToBottom, prevHasOnceLoadedReportActions, reportLoadingState?.hasOnceLoadedReportActions]);

return {
listRef: reportScrollManager.ref,
trackVerticalScrolling,
onViewableItemsChanged,
isFloatingMessageCounterVisible,
Expand Down
39 changes: 22 additions & 17 deletions src/hooks/useReportScrollManager/index.native.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,66 @@
import {useContext} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {ScrollView} from 'react-native';
import {ActionListContext} from '@pages/inbox/ReportScreenContext';
import {useActionListContext} from '@pages/inbox/ActionListContext';
import type ReportScrollManagerData from './types';

function useReportScrollManager(): ReportScrollManagerData {
const {flatListRef, scrollPositionRef} = useContext(ActionListContext);
const {getListRef, scrollPositionRef} = useActionListContext();

/**
* Scroll to the provided index.
* Scroll to the provided index. `isEditing` is accepted for signature parity with the web
* implementation but is a no-op here, matching the previous native behavior. `animated`
* defaults to `false` to match FlashList's default (the previous native call omitted it).
*/
const scrollToIndex = (index: number) => {
if (!flatListRef?.current) {
const scrollToIndex = (index: number, {animated = false}: {isEditing?: boolean; animated?: boolean} = {}) => {
const listRef = getListRef();
if (!listRef?.current) {
return;
}
flatListRef.current.scrollToIndex({index});
listRef.current.scrollToIndex({index, animated});
};

/**
* Scroll to the bottom of the inverted FlatList.
* When FlatList is inverted it's "bottom" is really it's top
*/
const scrollToBottom = () => {
if (!flatListRef?.current) {
const listRef = getListRef();
if (!listRef?.current) {
return;
}

scrollPositionRef.current = {offset: 0};
flatListRef.current.scrollToIndex({animated: false, index: 0});
listRef.current.scrollToIndex({animated: false, index: 0});
};

/**
* Scroll to the end of the FlatList.
*/
const scrollToEnd = () => {
if (!flatListRef?.current) {
const listRef = getListRef();
if (!listRef?.current) {
return;
}

const scrollViewRef = flatListRef.current.getNativeScrollRef();
const scrollViewRef = listRef.current.getNativeScrollRef?.() as ScrollView | undefined;
// Try to scroll on underlying scrollView if available, fallback to usual listRef
if (scrollViewRef && 'scrollToEnd' in scrollViewRef) {
(scrollViewRef as ScrollView).scrollToEnd({animated: false});
if (scrollViewRef && typeof scrollViewRef.scrollToEnd === 'function') {
scrollViewRef.scrollToEnd({animated: false});
return;
}

flatListRef.current.scrollToEnd({animated: false});
listRef.current.scrollToEnd({animated: false});
};

const scrollToOffset = (offset: number) => {
if (!flatListRef?.current) {
const listRef = getListRef();
if (!listRef?.current) {
return;
}
flatListRef.current.scrollToOffset({offset, animated: false});
listRef.current.scrollToOffset({offset, animated: false});
};

return {ref: flatListRef, scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset};
return {scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset};
}

export default useReportScrollManager;
32 changes: 18 additions & 14 deletions src/hooks/useReportScrollManager/index.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,57 @@
import {useContext} from 'react';
import {ActionListContext} from '@pages/inbox/ReportScreenContext';
import {useActionListContext} from '@pages/inbox/ActionListContext';
import type ReportScrollManagerData from './types';

function useReportScrollManager(): ReportScrollManagerData {
const {flatListRef} = useContext(ActionListContext);
const {getListRef} = useActionListContext();

/**
* Scroll to the provided index. On non-native implementations we do not want to scroll when we are scrolling because
* Scroll to the provided index. When `isEditing` is set we skip the scroll so the list doesn't
* jump while the user is editing a message.
*/
const scrollToIndex = (index: number, isEditing?: boolean) => {
if (!flatListRef?.current || isEditing) {
const scrollToIndex = (index: number, {isEditing = false, animated = true}: {isEditing?: boolean; animated?: boolean} = {}) => {
const listRef = getListRef();
if (!listRef?.current || isEditing) {
return;
}

flatListRef.current.scrollToIndex({index, animated: true});
listRef.current.scrollToIndex({index, animated});
};

/**
* Scroll to the bottom of the inverted FlatList.
* When FlatList is inverted it's "bottom" is really it's top
*/
const scrollToBottom = () => {
if (!flatListRef?.current) {
const listRef = getListRef();
if (!listRef?.current) {
return;
}

flatListRef.current.scrollToIndex({animated: false, index: 0});
listRef.current.scrollToIndex({animated: false, index: 0});
};

/**
* Scroll to the end of the FlatList.
*/
const scrollToEnd = () => {
if (!flatListRef?.current) {
const listRef = getListRef();
if (!listRef?.current) {
return;
}

flatListRef.current.scrollToEnd({animated: false});
listRef.current.scrollToEnd({animated: false});
};

const scrollToOffset = (offset: number) => {
if (!flatListRef?.current) {
const listRef = getListRef();
if (!listRef?.current) {
return;
}

flatListRef.current.scrollToOffset({animated: true, offset});
listRef.current.scrollToOffset({animated: true, offset});
};

return {ref: flatListRef, scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset};
return {scrollToIndex, scrollToBottom, scrollToEnd, scrollToOffset};
}

export default useReportScrollManager;
11 changes: 7 additions & 4 deletions src/hooks/useReportScrollManager/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type {FlatListRefType} from '@pages/inbox/ReportScreenContext';

type ReportScrollManagerData = {
ref: FlatListRefType;
scrollToIndex: (index: number, isEditing?: boolean) => void;
/**
* Scroll to a list index. `isEditing` suppresses the scroll (web only, defaults to `false`).
* When `animated` is omitted each platform keeps its prior default: web animates, native jumps
* instantly. ReportActionItemMessageEdit's Android Chrome keyboard hack passes `{animated: false}`
* to scroll instantly when the edit composer gains focus and the soft keyboard shifts the viewport.
*/
scrollToIndex: (index: number, options?: {isEditing?: boolean; animated?: boolean}) => void;
scrollToBottom: () => void;
scrollToEnd: () => void;
scrollToOffset: (offset: number) => void;
Expand Down
9 changes: 3 additions & 6 deletions src/pages/Search/SearchMoneyRequestReportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import ScreenWrapper from '@components/ScreenWrapper';
import {useSearchResultsContext} from '@components/Search/SearchContext';
import useShowSuperWideRHPVersion from '@components/WideRHPContextProvider/useShowSuperWideRHPVersion';
import WideRHPOverlayWrapper from '@components/WideRHPOverlayWrapper';
import useActionListContextValue from '@hooks/useActionListContextValue';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDismissOnMoneyRequestReportRemoval from '@hooks/useDismissOnMoneyRequestReportRemoval';
import useDocumentTitle from '@hooks/useDocumentTitle';
Expand All @@ -37,10 +36,10 @@ import {isMoneyRequestReportPendingDeletion, isValidReportIDFromPath} from '@lib
import {cancelSpansByPrefix} from '@libs/telemetry/activeSpans';
import {doesDeleteNavigateBackUrlIncludeDuplicatesReview, getParentReportActionDeletionStatus, hasLoadedReportActions, isThreadReportDeleted} from '@libs/TransactionNavigationUtils';
import Navigation from '@navigation/Navigation';
import {ActionListContextProvider} from '@pages/inbox/ActionListContext';
import ReactionListWrapper from '@pages/inbox/ReactionListWrapper';
import {ReportActionEditMessageContextProvider} from '@pages/inbox/report/ReportActionEditMessageContext';
import useClearReportActionDraftsOnReportChange from '@pages/inbox/report/useClearReportActionDraftsOnReportChange';
import {ActionListContext} from '@pages/inbox/ReportScreenContext';
import {clearDeleteTransactionNavigateBackUrl, createTransactionThreadReport, openReport, updateLastVisitTime} from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -116,8 +115,6 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {

const {isEditingDisabled, isCurrentReportLoadedFromOnyx} = useIsReportReadyToDisplay(report, reportIDFromRoute, isReportArchived);

const actionListValue = useActionListContextValue();

const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const {transactions: allReportTransactions, violations: allReportViolations} = useTransactionsAndViolationsForReport(reportIDFromRoute);
Expand Down Expand Up @@ -380,7 +377,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
effectiveTransactionThreadReportID={effectiveTransactionThreadReportID}
>
<WideRHPOverlayWrapper>
<ActionListContext.Provider value={actionListValue}>
<ActionListContextProvider>
<ReactionListWrapper>
<ScreenWrapper
testID="SearchMoneyRequestReportPage"
Expand Down Expand Up @@ -409,7 +406,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
</FullPageNotFoundView>
</ScreenWrapper>
</ReactionListWrapper>
</ActionListContext.Provider>
</ActionListContextProvider>
</WideRHPOverlayWrapper>
</ReportActionEditMessageContextProvider>
);
Expand Down
Loading
Loading