diff --git a/lib/hooks/search-sidebars.js b/lib/hooks/search-threads.js similarity index 53% rename from lib/hooks/search-sidebars.js rename to lib/hooks/search-threads.js index 24a7aac6c..3d1d0f495 100644 --- a/lib/hooks/search-sidebars.js +++ b/lib/hooks/search-threads.js @@ -1,97 +1,123 @@ // @flow import * as React from 'react'; +import { + type ChatThreadItem, + useFilteredChatListData, +} from '../selectors/chat-selectors'; import { sidebarInfoSelector } from '../selectors/thread-selectors'; import SearchIndex from '../shared/search-index'; -import { threadSearchText } from '../shared/thread-utils'; +import { threadIsChannel, threadSearchText } from '../shared/thread-utils'; import type { SetState } from '../types/hook-types'; -import type { SidebarInfo, ThreadInfo } from '../types/thread-types'; +import type { + SidebarInfo, + ThreadInfo, + RawThreadInfo, +} from '../types/thread-types'; import { useSelector } from '../utils/redux-utils'; -type SidebarSearchState = { +export type ThreadSearchState = { +text: string, +results: $ReadOnlySet, }; -function useSearchSidebars( - threadInfo: ThreadInfo, -): { - +listData: $ReadOnlyArray, - +searchState: SidebarSearchState, - +setSearchState: SetState, +type SearchThreadsResult = { + +listData: $ReadOnlyArray, + +searchState: ThreadSearchState, + +setSearchState: SetState, +onChangeSearchInputText: (text: string) => mixed, +clearQuery: (event: SyntheticEvent) => void, -} { +}; + +function useSearchThreads( + threadInfo: ThreadInfo, + childThreadInfos: $ReadOnlyArray, +): SearchThreadsResult { const [searchState, setSearchState] = React.useState({ text: '', results: new Set(), }); const userInfos = useSelector(state => state.userStore.userInfos); - const sidebarInfos = useSelector( - state => sidebarInfoSelector(state)[threadInfo.id] ?? [], - ); - const listData = React.useMemo(() => { if (!searchState.text) { - return sidebarInfos; + return childThreadInfos; } - return sidebarInfos.filter(sidebarInfo => - searchState.results.has(sidebarInfo.threadInfo.id), + return childThreadInfos.filter(thread => + searchState.results.has(thread.threadInfo.id), ); - }, [sidebarInfos, searchState]); + }, [childThreadInfos, searchState]); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); const searchIndex = React.useMemo(() => { const index = new SearchIndex(); - for (const sidebarInfo of sidebarInfos) { - const threadInfoFromSidebarInfo = sidebarInfo.threadInfo; + for (const thread of childThreadInfos) { + const threadInfoFromItem = thread.threadInfo; index.addEntry( - threadInfoFromSidebarInfo.id, - threadSearchText(threadInfoFromSidebarInfo, userInfos, viewerID), + threadInfoFromItem.id, + threadSearchText(threadInfoFromItem, userInfos, viewerID), ); } return index; - }, [sidebarInfos, userInfos, viewerID]); + }, [childThreadInfos, userInfos, viewerID]); const onChangeSearchInputText = React.useCallback( (text: string) => { setSearchState({ text, results: new Set(searchIndex.getSearchResults(text)), }); }, [searchIndex, setSearchState], ); const clearQuery = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); setSearchState({ text: '', results: new Set() }); }, [setSearchState], ); return React.useMemo( () => ({ listData, searchState, setSearchState, onChangeSearchInputText, clearQuery, }), [ listData, setSearchState, searchState, onChangeSearchInputText, clearQuery, ], ); } -export { useSearchSidebars }; +function useSearchSidebars( + threadInfo: ThreadInfo, +): SearchThreadsResult { + const childThreadInfos = useSelector( + state => sidebarInfoSelector(state)[threadInfo.id] ?? [], + ); + return useSearchThreads(threadInfo, childThreadInfos); +} + +function useSearchSubchannels( + threadInfo: ThreadInfo, +): SearchThreadsResult { + const childThreadInfos = useFilteredChatListData( + (thread: ?(ThreadInfo | RawThreadInfo)) => + threadIsChannel(thread) && thread?.parentThreadID === threadInfo.id, + ); + return useSearchThreads(threadInfo, childThreadInfos); +} + +export { useSearchSubchannels, useSearchSidebars }; diff --git a/native/chat/sidebar-list-modal.react.js b/native/chat/sidebar-list-modal.react.js index b32c8589c..af33a6213 100644 --- a/native/chat/sidebar-list-modal.react.js +++ b/native/chat/sidebar-list-modal.react.js @@ -1,200 +1,200 @@ // @flow import * as React from 'react'; import { TextInput, FlatList, View } from 'react-native'; -import { useSearchSidebars } from 'lib/hooks/search-sidebars'; +import { useSearchSidebars } from 'lib/hooks/search-threads'; import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; import ExtendedArrow from '../components/arrow-extended.react'; import Arrow from '../components/arrow.react'; import Button from '../components/button.react'; import Modal from '../components/modal.react'; import Search from '../components/search.react'; import type { RootNavigationProp } from '../navigation/root-navigator.react'; import type { NavigationRoute } from '../navigation/route-names'; import { useColors, useIndicatorStyle, useStyles } from '../themes/colors'; import { waitForModalInputFocus } from '../utils/timers'; import { useNavigateToThread } from './message-list-types'; import { SidebarItem } from './sidebar-item.react'; export type SidebarListModalParams = { +threadInfo: ThreadInfo, }; function keyExtractor(sidebarInfo: SidebarInfo) { return sidebarInfo.threadInfo.id; } function getItemLayout(data: ?$ReadOnlyArray, index: number) { return { length: 24, offset: 24 * index, index }; } type Props = { +navigation: RootNavigationProp<'SidebarListModal'>, +route: NavigationRoute<'SidebarListModal'>, }; function SidebarListModal(props: Props): React.Node { const { listData, searchState, setSearchState, onChangeSearchInputText, } = useSearchSidebars(props.route.params.threadInfo); const searchTextInputRef = React.useRef(); const setSearchTextInputRef = React.useCallback( async (textInput: ?React.ElementRef) => { searchTextInputRef.current = textInput; if (!textInput) { return; } await waitForModalInputFocus(); if (searchTextInputRef.current) { searchTextInputRef.current.focus(); } }, [], ); const navigateToThread = useNavigateToThread(); const onPressItem = React.useCallback( (threadInfo: ThreadInfo) => { setSearchState({ text: '', results: new Set(), }); if (searchTextInputRef.current) { searchTextInputRef.current.blur(); } navigateToThread({ threadInfo }); }, [navigateToThread, setSearchState], ); const styles = useStyles(unboundStyles); const numOfSidebarsWithExtendedArrow = React.useMemo( () => listData.length - 1, [listData], ); const renderItem = React.useCallback( (row: { item: SidebarInfo, index: number, ... }) => { let extendArrow: boolean = false; if (row.index < numOfSidebarsWithExtendedArrow) { extendArrow = true; } return ( ); }, [onPressItem, numOfSidebarsWithExtendedArrow], ); const indicatorStyle = useIndicatorStyle(); return ( ); } function Item(props: { item: SidebarInfo, onPressItem: (threadInfo: ThreadInfo) => void, extendArrow: boolean, }): React.Node { const { item, onPressItem, extendArrow } = props; const { threadInfo } = item; const onPressButton = React.useCallback(() => onPressItem(threadInfo), [ onPressItem, threadInfo, ]); const colors = useColors(); const styles = useStyles(unboundStyles); let arrow; if (extendArrow) { arrow = ( ); } else { arrow = ( ); } return ( ); } const unboundStyles = { arrow: { position: 'absolute', top: -12, }, extendedArrow: { position: 'absolute', top: -6, }, search: { marginBottom: 8, }, sidebar: { backgroundColor: 'listBackground', paddingLeft: 0, paddingRight: 5, }, sidebarItemContainer: { flex: 1, }, sidebarRowContainer: { flex: 1, flexDirection: 'row', }, spacer: { width: 30, }, }; export default SidebarListModal;