Skip to content

Commit 978bdaf

Browse files
committed
Merge main
2 parents 088bd1c + d078267 commit 978bdaf

15 files changed

Lines changed: 757 additions & 612 deletions

File tree

src/CONST/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6473,7 +6473,7 @@ const CONST = {
64736473
TAX_RATE: 'tax-rate',
64746474
CARD_ID: 'card',
64756475
FEED: 'feed',
6476-
REPORT_ID: 'reportid',
6476+
REPORT_ID: 'report-id',
64776477
KEYWORD: 'keyword',
64786478
IN: 'in',
64796479
SUBMITTED: 'submitted',

src/components/ScrollOffsetContextProvider.tsx

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {ParamListBase} from '@react-navigation/native';
2+
import {findFocusedRoute} from '@react-navigation/native';
23
import React, {createContext, useCallback, useEffect, useMemo, useRef} from 'react';
34
import useOnyx from '@hooks/useOnyx';
45
import usePrevious from '@hooks/usePrevious';
@@ -40,11 +41,21 @@ const defaultValue: ScrollOffsetContextValue = {
4041

4142
const ScrollOffsetContext = createContext<ScrollOffsetContextValue>(defaultValue);
4243

43-
/** This function is prepared to work with HOME screens. May need modification if we want to handle other types of screens. */
44+
/** This function is prepared to work with HOME and SEARCH screens. */
4445
function getKey(route: PlatformStackRouteProp<ParamListBase> | NavigationPartialRoute): string {
46+
// Handle routes with direct policyID parameter (HOME screens)
4547
if (route.params && 'policyID' in route.params && typeof route.params.policyID === 'string') {
4648
return `${route.name}-${route.params.policyID}`;
4749
}
50+
51+
// Handle SEARCH screens with query parameters
52+
if (route.name === SCREENS.SEARCH.ROOT && route.params && 'q' in route.params && typeof route.params.q === 'string') {
53+
// Encode the query to handle spaces and special characters
54+
const encodedQuery = encodeURIComponent(route.params.q);
55+
return `${route.name}-${encodedQuery}`;
56+
}
57+
58+
// For other routes, just use route name
4859
return `${route.name}-global`;
4960
}
5061

@@ -58,9 +69,9 @@ function ScrollOffsetContextProvider({children}: ScrollOffsetContextProviderProp
5869
return;
5970
}
6071

61-
// If the priority mode changes, we need to clear the scroll offsets for the home screens because it affects the size of the elements and scroll positions wouldn't be correct.
72+
// If the priority mode changes, we need to clear the scroll offsets for the home and search screens because it affects the size of the elements and scroll positions wouldn't be correct.
6273
for (const key of Object.keys(scrollOffsetsRef.current)) {
63-
if (key.includes(SCREENS.HOME)) {
74+
if (key.includes(SCREENS.HOME) || key.includes(SCREENS.SEARCH.ROOT)) {
6475
delete scrollOffsetsRef.current[key];
6576
}
6677
}
@@ -77,16 +88,39 @@ function ScrollOffsetContextProvider({children}: ScrollOffsetContextProviderProp
7788
return scrollOffsetsRef.current[getKey(route)];
7889
}, []);
7990

80-
const cleanStaleScrollOffsets: ScrollOffsetContextValue['cleanStaleScrollOffsets'] = useCallback((state) => {
81-
const sidebarRoutes = state.routes.filter((route) => isSidebarScreenName(route.name));
82-
const scrollOffsetKeysOfExistingScreens = sidebarRoutes.map((route) => getKey(route));
83-
for (const key of Object.keys(scrollOffsetsRef.current)) {
84-
if (!scrollOffsetKeysOfExistingScreens.includes(key)) {
85-
delete scrollOffsetsRef.current[key];
91+
const cleanScrollOffsets = useCallback((keys: string[], shouldDelete: (key: string) => boolean) => {
92+
keys.forEach((key) => {
93+
if (!shouldDelete(key)) {
94+
return;
8695
}
87-
}
96+
97+
delete scrollOffsetsRef.current[key];
98+
});
8899
}, []);
89100

101+
const cleanStaleScrollOffsets: ScrollOffsetContextValue['cleanStaleScrollOffsets'] = useCallback(
102+
(state) => {
103+
const sidebarRoutes = state.routes.filter((route) => isSidebarScreenName(route.name));
104+
const existingScreenKeys = sidebarRoutes.map(getKey);
105+
106+
const focusedRoute = findFocusedRoute(state);
107+
const routeName = focusedRoute?.name;
108+
109+
const isSearchScreen = routeName === SCREENS.SEARCH.ROOT;
110+
const isSearchMoneyRequestReport = routeName === SCREENS.SEARCH.MONEY_REQUEST_REPORT || routeName === SCREENS.SEARCH.REPORT_RHP;
111+
112+
const scrollOffsetKeys = Object.keys(scrollOffsetsRef.current);
113+
114+
if (isSearchScreen || isSearchMoneyRequestReport) {
115+
const currentKey = focusedRoute ? getKey(focusedRoute) : null;
116+
cleanScrollOffsets(scrollOffsetKeys, (key) => key.startsWith(SCREENS.SEARCH.ROOT) && key !== currentKey && !isSearchMoneyRequestReport);
117+
return;
118+
}
119+
cleanScrollOffsets(scrollOffsetKeys, (key) => !existingScreenKeys.includes(key));
120+
},
121+
[cleanScrollOffsets],
122+
);
123+
90124
const saveScrollIndex: ScrollOffsetContextValue['saveScrollIndex'] = useCallback((route, scrollIndex) => {
91125
scrollOffsetsRef.current[getKey(route)] = scrollIndex;
92126
}, []);
@@ -95,6 +129,7 @@ function ScrollOffsetContextProvider({children}: ScrollOffsetContextProviderProp
95129
if (!scrollOffsetsRef.current) {
96130
return;
97131
}
132+
98133
return scrollOffsetsRef.current[getKey(route)];
99134
}, []);
100135

src/components/Search/SearchList.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import {useIsFocused} from '@react-navigation/native';
1+
import {useIsFocused, useRoute} from '@react-navigation/native';
22
import {FlashList} from '@shopify/flash-list';
33
import type {FlashListProps, ViewToken} from '@shopify/flash-list';
4-
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
4+
import React, {forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
55
import type {ForwardedRef} from 'react';
66
import {View} from 'react-native';
77
import type {NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
@@ -12,6 +12,7 @@ import MenuItem from '@components/MenuItem';
1212
import Modal from '@components/Modal';
1313
import {usePersonalDetails} from '@components/OnyxListItemProvider';
1414
import {PressableWithFeedback} from '@components/Pressable';
15+
import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider';
1516
import type ChatListItem from '@components/SelectionList/ChatListItem';
1617
import type TaskListItem from '@components/SelectionList/Search/TaskListItem';
1718
import type TransactionGroupListItem from '@components/SelectionList/Search/TransactionGroupListItem';
@@ -182,6 +183,9 @@ function SearchList(
182183
const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.validated, canBeMissing: true});
183184
const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID, {canBeMissing: true});
184185

186+
const route = useRoute();
187+
const {saveScrollOffset, getScrollOffset} = useContext(ScrollOffsetContext);
188+
185189
const handleLongPressRow = useCallback(
186190
(item: SearchListItem) => {
187191
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
@@ -420,6 +424,39 @@ function SearchList(
420424
};
421425
}, [calculatedListHeight, calculatedListWidth]);
422426

427+
const handleScroll = useCallback<NonNullable<FlashListProps<SearchListItem>['onScroll']>>(
428+
(e) => {
429+
if (onScroll && typeof onScroll === 'function') {
430+
onScroll(e);
431+
}
432+
433+
if (e.nativeEvent.layoutMeasurement.height > 0) {
434+
saveScrollOffset(route, e.nativeEvent.contentOffset.y);
435+
}
436+
},
437+
[onScroll, route, saveScrollOffset],
438+
);
439+
440+
const handleLayout = useCallback(() => {
441+
if (onLayout && typeof onLayout === 'function') {
442+
onLayout();
443+
}
444+
445+
const offset = getScrollOffset(route);
446+
if (!offset || !listRef.current) {
447+
return;
448+
}
449+
450+
// Use requestAnimationFrame to ensure proper scrolling on iOS
451+
requestAnimationFrame(() => {
452+
if (!offset || !listRef.current) {
453+
return;
454+
}
455+
456+
listRef.current.scrollToOffset({offset});
457+
});
458+
}, [onLayout, getScrollOffset, route]);
459+
423460
return (
424461
<View style={[styles.flex1, !isKeyboardShown && safeAreaPaddingBottomStyle, containerStyle]}>
425462
{tableHeaderVisible && (
@@ -457,15 +494,15 @@ function SearchList(
457494
data={data}
458495
renderItem={renderItem}
459496
keyExtractor={keyExtractor}
460-
onScroll={onScroll}
497+
onScroll={handleScroll}
461498
showsVerticalScrollIndicator={false}
462499
ref={listRef}
463500
extraData={[focusedIndex, isFocused]}
464501
onEndReached={onEndReached}
465502
onEndReachedThreshold={onEndReachedThreshold}
466503
ListFooterComponent={ListFooterComponent}
467504
onViewableItemsChanged={onViewableItemsChanged}
468-
onLayout={onLayout}
505+
onLayout={handleLayout}
469506
removeClippedSubviews
470507
drawDistance={1000}
471508
estimatedItemSize={estimatedItemSize}

src/libs/API/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,6 @@ const SIDE_EFFECT_REQUEST_COMMANDS = {
11791179
DISCONNECT_AS_DELEGATE: 'DisconnectAsDelegate',
11801180
COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding',
11811181
CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP: 'ConnectPolicyToQuickbooksDesktop',
1182-
BANK_ACCOUNT_CREATE_CORPAY: 'BankAccount_CreateCorpay',
11831182
MERGE_INTO_ACCOUNT_AND_LOGIN: 'MergeIntoAccountAndLogIn',
11841183

11851184
// PayMoneyRequestOnSearch only works online (pattern C) and we need to play the success sound only when the request is successful
@@ -1205,7 +1204,6 @@ type SideEffectRequestCommandParameters = {
12051204
[SIDE_EFFECT_REQUEST_COMMANDS.DISCONNECT_AS_DELEGATE]: EmptyObject;
12061205
[SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject;
12071206
[SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP]: Parameters.ConnectPolicyToQuickBooksDesktopParams;
1208-
[SIDE_EFFECT_REQUEST_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY]: Parameters.BankAccountCreateCorpayParams;
12091207
[SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH]: Parameters.PayMoneyRequestOnSearchParams;
12101208
[SIDE_EFFECT_REQUEST_COMMANDS.MERGE_INTO_ACCOUNT_AND_LOGIN]: Parameters.MergeIntoAccountAndLogInParams;
12111209
[SIDE_EFFECT_REQUEST_COMMANDS.LOG_OUT]: Parameters.LogOutParams;

0 commit comments

Comments
 (0)