Skip to content

Commit

Permalink
Merge pull request #49984 from software-mansion-labs/kicu/49123-searc…
Browse files Browse the repository at this point in the history
…h-24-cleanup

Use SearchRouter everywhere and drop Chat finder
  • Loading branch information
luacmartins authored Oct 16, 2024
2 parents afe5258 + 980e7d0 commit 4a25c36
Show file tree
Hide file tree
Showing 29 changed files with 200 additions and 465 deletions.
2 changes: 1 addition & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ const CONST = {
},
TIMING: {
CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action',
CHAT_FINDER_RENDER: 'search_render',
SEARCH_ROUTER_RENDER: 'search_router_render',
CHAT_RENDER: 'chat_render',
OPEN_REPORT: 'open_report',
HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render',
Expand Down
1 change: 0 additions & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ const ROUTES = {
route: 'flag/:reportID/:reportActionID',
getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`flag/${reportID}/${reportActionID}` as const, backTo),
},
CHAT_FINDER: 'chat-finder',
PROFILE: {
route: 'a/:accountID',
getRoute: (accountID?: string | number, backTo?: string, login?: string) => {
Expand Down
1 change: 0 additions & 1 deletion src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ const SCREENS = {
ROOT: 'SaveTheWorld_Root',
},
LEFT_MODAL: {
CHAT_FINDER: 'ChatFinder',
WORKSPACE_SWITCHER: 'WorkspaceSwitcher',
},
RIGHT_MODAL: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/HeaderWithBackButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ function HeaderWithBackButton({
/>
)}
{middleContent}
<View style={[styles.reportOptions, styles.flexRow, styles.pr5, styles.alignItemsCenter]}>
<View style={[styles.reportOptions, styles.flexRow, styles.pr5, styles.gap4, styles.alignItemsCenter]}>
{children}
{shouldShowDownloadButton && (
<Tooltip text={translate('common.download')}>
Expand Down
25 changes: 16 additions & 9 deletions src/components/Search/SearchRouter/SearchButton.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithoutFeedback} from '@components/Pressable';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Permissions from '@libs/Permissions';
import Performance from '@libs/Performance';
import * as Session from '@userActions/Session';
import Timing from '@userActions/Timing';
import CONST from '@src/CONST';
import {useSearchRouterContext} from './SearchRouterContext';

function SearchButton() {
type SearchButtonProps = {
style?: StyleProp<ViewStyle>;
};

function SearchButton({style}: SearchButtonProps) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const {openSearchRouter} = useSearchRouterContext();

if (!Permissions.canUseNewSearchRouter()) {
return;
}

return (
<PressableWithoutFeedback
accessibilityLabel={translate('common.search')}
style={[styles.flexRow, styles.touchableButtonImage]}
onPress={() => {
style={[styles.flexRow, styles.touchableButtonImage, style]}
onPress={Session.checkIfActionIsAllowed(() => {
Timing.start(CONST.TIMING.SEARCH_ROUTER_RENDER);
Performance.markStart(CONST.TIMING.SEARCH_ROUTER_RENDER);

openSearchRouter();
}}
})}
>
<Icon
src={Expensicons.MagnifyingGlass}
Expand Down
47 changes: 25 additions & 22 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,27 @@ import * as SearchUtils from '@libs/SearchUtils';
import Navigation from '@navigation/Navigation';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import Timing from '@userActions/Timing';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {useSearchRouterContext} from './SearchRouterContext';
import SearchRouterInput from './SearchRouterInput';
import SearchRouterList from './SearchRouterList';

const SEARCH_DEBOUNCE_DELAY = 150;

function SearchRouter() {
type SearchRouterProps = {
onRouterClose: () => void;
};

function SearchRouter({onRouterClose}: SearchRouterProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES);
const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false});

const {isSmallScreenWidth} = useResponsiveLayout();
const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext();
const listRef = useRef<SelectionListHandle>(null);

const taxRates = getAllTaxRates();
Expand Down Expand Up @@ -69,7 +72,9 @@ function SearchRouter() {
};
}

Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS);
const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedInputValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true});
Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS);

return {
recentReports: newOptions.recentReports,
Expand All @@ -91,15 +96,6 @@ function SearchRouter() {
Report.searchInServer(debouncedInputValue.trim());
}, [debouncedInputValue]);

useEffect(() => {
if (!textInputValue && isSearchRouterDisplayed) {
return;
}
listRef.current?.updateAndScrollToFocusedIndex(0);
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSearchRouterDisplayed]);

const contextualReportData = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined;

const clearUserQuery = () => {
Expand Down Expand Up @@ -136,50 +132,57 @@ function SearchRouter() {
};

const closeAndClearRouter = useCallback(() => {
closeSearchRouter();
onRouterClose();
clearUserQuery();
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [closeSearchRouter]);
}, [onRouterClose]);

const onSearchSubmit = useCallback(
(query: SearchQueryJSON | undefined) => {
if (!query) {
return;
}
closeSearchRouter();
onRouterClose();
const standardizedQuery = SearchUtils.standardizeQueryJSON(query, cardList, taxRates);
const queryString = SearchUtils.buildSearchQueryString(standardizedQuery);
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryString}));
clearUserQuery();
},
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
[closeSearchRouter],
[onRouterClose],
);

useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => {
closeSearchRouter();
clearUserQuery();
closeAndClearRouter();
});

const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth};
const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.searchRouterPopoverWidth};

return (
<View style={[styles.flex1, modalWidth, styles.h100, !isSmallScreenWidth && styles.mh85vh]}>
<View
style={[styles.flex1, modalWidth, styles.h100, !isSmallScreenWidth && styles.mh85vh]}
testID={SearchRouter.displayName}
>
{isSmallScreenWidth && (
<HeaderWithBackButton
title={translate('common.search')}
onBackButtonPress={() => closeSearchRouter()}
onBackButtonPress={() => onRouterClose()}
/>
)}
<SearchRouterInput
value={textInputValue}
setValue={setTextInputValue}
isFullWidth={isSmallScreenWidth}
updateSearch={onSearchChange}
onSubmit={() => {
onSearchSubmit(SearchUtils.buildSearchQueryJSON(textInputValue));
}}
routerListRef={listRef}
wrapperStyle={[isSmallScreenWidth ? styles.mv3 : styles.mv2, isSmallScreenWidth ? styles.mh5 : styles.mh2, styles.border]}
shouldShowOfflineMessage
wrapperStyle={[styles.border, styles.alignItemsCenter]}
outerWrapperStyle={[isSmallScreenWidth ? styles.mv3 : styles.mv2, isSmallScreenWidth ? styles.mh5 : styles.mh2]}
wrapperFocusedStyle={[styles.borderColorFocus]}
isSearchingForReports={isSearchingForReports}
/>
Expand Down
32 changes: 29 additions & 3 deletions src/components/Search/SearchRouter/SearchRouterContext.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, {useContext, useMemo, useState} from 'react';
import React, {useContext, useMemo, useRef, useState} from 'react';
import * as Modal from '@userActions/Modal';
import type ChildrenProps from '@src/types/utils/ChildrenProps';

const defaultSearchContext = {
isSearchRouterDisplayed: false,
openSearchRouter: () => {},
closeSearchRouter: () => {},
toggleSearchRouter: () => {},
};

type SearchRouterContext = typeof defaultSearchContext;
Expand All @@ -13,15 +15,39 @@ const Context = React.createContext<SearchRouterContext>(defaultSearchContext);

function SearchRouterContextProvider({children}: ChildrenProps) {
const [isSearchRouterDisplayed, setIsSearchRouterDisplayed] = useState(false);
const searchRouterDisplayedRef = useRef(false);

const routerContext = useMemo(() => {
const openSearchRouter = () => setIsSearchRouterDisplayed(true);
const closeSearchRouter = () => setIsSearchRouterDisplayed(false);
const openSearchRouter = () => {
Modal.close(
() => {
setIsSearchRouterDisplayed(true);
searchRouterDisplayedRef.current = true;
},
false,
true,
);
};
const closeSearchRouter = () => {
setIsSearchRouterDisplayed(false);
searchRouterDisplayedRef.current = false;
};

// There are callbacks that live outside of React render-loop and interact with SearchRouter
// So we need a function that is based on ref to correctly open/close it
const toggleSearchRouter = () => {
if (searchRouterDisplayedRef.current) {
closeSearchRouter();
} else {
openSearchRouter();
}
};

return {
isSearchRouterDisplayed,
openSearchRouter,
closeSearchRouter,
toggleSearchRouter,
};
}, [isSearchRouterDisplayed, setIsSearchRouterDisplayed]);

Expand Down
77 changes: 50 additions & 27 deletions src/components/Search/SearchRouter/SearchRouterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React, {useState} from 'react';
import type {ReactNode, RefObject} from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import FormHelpMessage from '@components/FormHelpMessage';
import type {SelectionListHandle} from '@components/SelectionList/types';
import TextInput from '@components/TextInput';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand All @@ -31,6 +33,9 @@ type SearchRouterInputProps = {
/** Whether the input is disabled */
disabled?: boolean;

/** Whether the offline message should be shown */
shouldShowOfflineMessage?: boolean;

/** Whether the input should be focused */
autoFocus?: boolean;

Expand All @@ -40,6 +45,9 @@ type SearchRouterInputProps = {
/** Any additional styles to apply when input is focused */
wrapperFocusedStyle?: StyleProp<ViewStyle>;

/** Any additional styles to apply to text input along with FormHelperMessage */
outerWrapperStyle?: StyleProp<ViewStyle>;

/** Component to be displayed on the right */
rightComponent?: ReactNode;

Expand All @@ -55,15 +63,19 @@ function SearchRouterInput({
routerListRef,
isFullWidth,
disabled = false,
shouldShowOfflineMessage = false,
autoFocus = true,
wrapperStyle,
wrapperFocusedStyle,
outerWrapperStyle,
rightComponent,
isSearchingForReports,
}: SearchRouterInputProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [isFocused, setIsFocused] = useState<boolean>(false);
const {isOffline} = useNetwork();
const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : '';

const onChangeText = (text: string) => {
setValue(text);
Expand All @@ -73,34 +85,45 @@ function SearchRouterInput({
const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth};

return (
<View style={[styles.flexRow, styles.alignItemsCenter, wrapperStyle ?? styles.searchRouterTextInputContainer, isFocused && wrapperFocusedStyle]}>
<View style={styles.flex1}>
<TextInput
value={value}
onChangeText={onChangeText}
autoFocus={autoFocus}
loadingSpinnerStyle={[styles.mt0, styles.mr2]}
role={CONST.ROLE.PRESENTATION}
placeholder={translate('search.searchPlaceholder')}
autoCapitalize="none"
autoCorrect={false}
disabled={disabled}
onSubmitEditing={onSubmit}
shouldUseDisabledStyles={false}
textInputContainerStyles={styles.borderNone}
inputStyle={[inputWidth, styles.pl3, styles.pr3]}
onFocus={() => {
setIsFocused(true);
routerListRef?.current?.updateExternalTextInputFocus(true);
}}
onBlur={() => {
setIsFocused(false);
routerListRef?.current?.updateExternalTextInputFocus(false);
}}
isLoading={!!isSearchingForReports}
/>
<View style={[outerWrapperStyle]}>
<View style={[styles.flexRow, styles.alignItemsCenter, wrapperStyle ?? styles.searchRouterTextInputContainer, isFocused && wrapperFocusedStyle]}>
<View style={styles.flex1}>
<TextInput
testID="search-router-text-input"
value={value}
onChangeText={onChangeText}
autoFocus={autoFocus}
loadingSpinnerStyle={[styles.mt0, styles.mr2]}
role={CONST.ROLE.PRESENTATION}
placeholder={translate('search.searchPlaceholder')}
autoCapitalize="none"
autoCorrect={false}
spellCheck={false}
enterKeyHint="search"
accessibilityLabel={translate('search.searchPlaceholder')}
disabled={disabled}
onSubmitEditing={onSubmit}
shouldUseDisabledStyles={false}
textInputContainerStyles={[styles.borderNone, styles.pb0]}
inputStyle={[styles.searchInputStyle, inputWidth, styles.pl3, styles.pr3]}
onFocus={() => {
setIsFocused(true);
routerListRef?.current?.updateExternalTextInputFocus(true);
}}
onBlur={() => {
setIsFocused(false);
routerListRef?.current?.updateExternalTextInputFocus(false);
}}
isLoading={!!isSearchingForReports}
/>
</View>
{rightComponent && <View style={styles.pr3}>{rightComponent}</View>}
</View>
{rightComponent && <View style={styles.pr3}>{rightComponent}</View>}
<FormHelpMessage
style={styles.ph3}
isError={false}
message={offlineMessage}
/>
</View>
);
}
Expand Down
Loading

0 comments on commit 4a25c36

Please sign in to comment.