diff --git a/lib/hooks/search-sidebars.js b/lib/hooks/search-threads.js rename from lib/hooks/search-sidebars.js rename to lib/hooks/search-threads.js --- a/lib/hooks/search-sidebars.js +++ b/lib/hooks/search-threads.js @@ -2,24 +2,32 @@ import * as React from 'react'; -import { sidebarInfoSelector } from '../selectors/thread-selectors'; import SearchIndex from '../shared/search-index'; import { threadSearchText } from '../shared/thread-utils'; import type { SetState } from '../types/hook-types'; -import type { SidebarInfo, ThreadInfo } from '../types/thread-types'; +import type { BaseAppState } from '../types/redux-types'; +import type { + ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, + ThreadInfo, +} from '../types/thread-types'; import { useSelector } from '../utils/redux-utils'; -type SidebarSearchState = { +export type ThreadSearchState = { +text: string, +results: $ReadOnlySet, }; -function useSearchSidebars( +function useSearchThreads( threadInfo: ThreadInfo, + threadInfosSelector: ( + state: BaseAppState, + ) => { + +[id: string]: $ReadOnlyArray, + }, ): { - +listData: $ReadOnlyArray, - +searchState: SidebarSearchState, - +setSearchState: SetState, + +listData: $ReadOnlyArray, + +searchState: ThreadSearchState, + +setSearchState: SetState, +onChangeSearchInputText: (text: string) => mixed, +clearQuery: (event: SyntheticEvent) => void, } { @@ -30,33 +38,33 @@ const userInfos = useSelector(state => state.userStore.userInfos); - const sidebarInfos = useSelector( - state => sidebarInfoSelector(state)[threadInfo.id] ?? [], + const threadInfos = useSelector( + state => threadInfosSelector(state)[threadInfo.id] ?? [], ); const listData = React.useMemo(() => { if (!searchState.text) { - return sidebarInfos; + return threadInfos; } - return sidebarInfos.filter(sidebarInfo => - searchState.results.has(sidebarInfo.threadInfo.id), + return threadInfos.filter(thread => + searchState.results.has(thread.threadInfo.id), ); - }, [sidebarInfos, searchState]); + }, [threadInfos, 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 threadInfos) { + const threadInfoFromItem = thread.threadInfo; index.addEntry( - threadInfoFromSidebarInfo.id, - threadSearchText(threadInfoFromSidebarInfo, userInfos, viewerID), + threadInfoFromItem.id, + threadSearchText(threadInfoFromItem, userInfos, viewerID), ); } return index; - }, [sidebarInfos, userInfos, viewerID]); + }, [threadInfos, userInfos, viewerID]); const onChangeSearchInputText = React.useCallback( (text: string) => { @@ -94,4 +102,4 @@ ); } -export { useSearchSidebars }; +export { useSearchThreads }; diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js --- a/lib/selectors/chat-selectors.js +++ b/lib/selectors/chat-selectors.js @@ -35,7 +35,7 @@ import { type ThreadInfo, type RawThreadInfo, - type SidebarInfo, + type ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, maxReadSidebars, maxUnreadSidebars, threadTypes, @@ -51,7 +51,7 @@ export type SidebarItem = | { - ...SidebarInfo, + ...ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +type: 'sidebar', } | { @@ -121,7 +121,7 @@ threadInfo: ThreadInfo, messageStore: MessageStore, messages: { +[id: string]: ?MessageInfo }, - sidebarInfos: ?$ReadOnlyArray, + sidebarInfos: ?$ReadOnlyArray, ): ChatThreadItem { const mostRecentMessageInfo = getMostRecentMessageInfo( threadInfo, @@ -207,7 +207,9 @@ threadInfos: { +[id: string]: ThreadInfo }, messageStore: MessageStore, messageInfos: { +[id: string]: ?MessageInfo }, - sidebarInfos: { +[id: string]: $ReadOnlyArray }, + sidebarInfos: { + +[id: string]: $ReadOnlyArray, + }, ): $ReadOnlyArray => getChatThreadItems( threadInfos, @@ -247,7 +249,9 @@ threadInfos: { +[id: string]: ThreadInfo }, messageStore: MessageStore, messageInfos: { +[id: string]: ?MessageInfo }, - sidebarInfos: { +[id: string]: $ReadOnlyArray }, + sidebarInfos: { + +[id: string]: $ReadOnlyArray, + }, filterFunction: (threadInfo: ?(ThreadInfo | RawThreadInfo)) => boolean, ): $ReadOnlyArray { return _flow( diff --git a/lib/selectors/thread-selectors.js b/lib/selectors/thread-selectors.js --- a/lib/selectors/thread-selectors.js +++ b/lib/selectors/thread-selectors.js @@ -35,7 +35,7 @@ type RelativeMemberInfo, threadPermissions, threadTypes, - type SidebarInfo, + type ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, threadTypeIsCommunityRoot, } from '../types/thread-types'; import { dateString, dateFromString } from '../utils/date-utils'; @@ -207,38 +207,50 @@ return null; } -const sidebarInfoSelector: ( +const threadInfosWithLastUpdatedTimeAndNonLocalMessageSelector: ( + threadRejectionCondition: (ThreadInfo) => boolean, +) => ( state: BaseAppState<*>, -) => { +[id: string]: $ReadOnlyArray } = createObjectSelector( - childThreadInfos, - (state: BaseAppState<*>) => state.messageStore, - (childThreads: $ReadOnlyArray, messageStore: MessageStore) => { - const sidebarInfos = []; - for (const childThreadInfo of childThreads) { - if ( - !threadInChatList(childThreadInfo) || - childThreadInfo.type !== threadTypes.SIDEBAR - ) { - continue; +) => { + +[id: string]: $ReadOnlyArray, +} = _memoize((threadRejectionCondition: ThreadInfo => boolean) => + createObjectSelector( + childThreadInfos, + (state: BaseAppState<*>) => state.messageStore, + (childThreads: $ReadOnlyArray, messageStore: MessageStore) => { + const sidebarInfos = []; + for (const childThreadInfo of childThreads) { + if (threadRejectionCondition(childThreadInfo)) { + continue; + } + const mostRecentRawMessageInfo = getMostRecentRawMessageInfo( + childThreadInfo, + messageStore, + ); + const lastUpdatedTime = + mostRecentRawMessageInfo?.time ?? childThreadInfo.creationTime; + const mostRecentNonLocalMessage = getMostRecentNonLocalMessageID( + childThreadInfo.id, + messageStore, + ); + sidebarInfos.push({ + threadInfo: childThreadInfo, + lastUpdatedTime, + mostRecentNonLocalMessage, + }); } - const mostRecentRawMessageInfo = getMostRecentRawMessageInfo( - childThreadInfo, - messageStore, - ); - const lastUpdatedTime = - mostRecentRawMessageInfo?.time ?? childThreadInfo.creationTime; - const mostRecentNonLocalMessage = getMostRecentNonLocalMessageID( - childThreadInfo.id, - messageStore, - ); - sidebarInfos.push({ - threadInfo: childThreadInfo, - lastUpdatedTime, - mostRecentNonLocalMessage, - }); - } - return _orderBy('lastUpdatedTime')('desc')(sidebarInfos); - }, + return _orderBy('lastUpdatedTime')('desc')(sidebarInfos); + }, + ), +); + +const sidebarInfoSelector: ( + state: BaseAppState<*>, +) => { + +[id: string]: $ReadOnlyArray, +} = threadInfosWithLastUpdatedTimeAndNonLocalMessageSelector( + (thread: ThreadInfo) => + !threadInChatList(thread) || thread.type !== threadTypes.SIDEBAR, ); const unreadCount: (state: BaseAppState<*>) => number = createSelector( @@ -430,4 +442,5 @@ sidebarInfoSelector, threadInfoFromSourceMessageIDSelector, pendingToRealizedThreadIDsSelector, + threadInfosWithLastUpdatedTimeAndNonLocalMessageSelector, }; diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -434,7 +434,7 @@ +userInfos: $ReadOnlyArray, }; -export type SidebarInfo = { +export type ThreadInfoWithLastUpdatedTimeAndNonLocalMessage = { +threadInfo: ThreadInfo, +lastUpdatedTime: number, +mostRecentNonLocalMessage: ?string, diff --git a/native/chat/chat-thread-list-sidebar.react.js b/native/chat/chat-thread-list-sidebar.react.js --- a/native/chat/chat-thread-list-sidebar.react.js +++ b/native/chat/chat-thread-list-sidebar.react.js @@ -3,7 +3,10 @@ import * as React from 'react'; import { View } from 'react-native'; -import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; +import type { + ThreadInfo, + ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +} from 'lib/types/thread-types'; import ExtendedArrow from '../components/arrow-extended.react'; import Arrow from '../components/arrow.react'; @@ -14,7 +17,7 @@ import SwipeableThread from './swipeable-thread.react'; type Props = { - +sidebarInfo: SidebarInfo, + +sidebarInfo: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +onPressItem: (threadInfo: ThreadInfo) => void, +onSwipeableWillOpen: (threadInfo: ThreadInfo) => void, +currentlyOpenedSwipeableId: string, diff --git a/native/chat/sidebar-item.react.js b/native/chat/sidebar-item.react.js --- a/native/chat/sidebar-item.react.js +++ b/native/chat/sidebar-item.react.js @@ -3,14 +3,14 @@ import * as React from 'react'; import { Text, View } from 'react-native'; -import type { SidebarInfo } from 'lib/types/thread-types'; +import type { ThreadInfoWithLastUpdatedTimeAndNonLocalMessage } from 'lib/types/thread-types'; import { shortAbsoluteDate } from 'lib/utils/date-utils'; import { SingleLine } from '../components/single-line.react'; import { useStyles } from '../themes/colors'; type Props = { - +sidebarInfo: SidebarInfo, + +sidebarInfo: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, }; function SidebarItem(props: Props): React.Node { const { lastUpdatedTime } = props.sidebarInfo; diff --git a/native/chat/sidebar-list-modal.react.js b/native/chat/sidebar-list-modal.react.js --- a/native/chat/sidebar-list-modal.react.js +++ b/native/chat/sidebar-list-modal.react.js @@ -3,8 +3,12 @@ import * as React from 'react'; import { TextInput, FlatList, View } from 'react-native'; -import { useSearchSidebars } from 'lib/hooks/search-sidebars'; -import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; +import { useSearchThreads } from 'lib/hooks/search-threads'; +import { sidebarInfoSelector } from 'lib/selectors/thread-selectors'; +import type { + ThreadInfo, + ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +} from 'lib/types/thread-types'; import ExtendedArrow from '../components/arrow-extended.react'; import Arrow from '../components/arrow.react'; @@ -22,10 +26,15 @@ +threadInfo: ThreadInfo, }; -function keyExtractor(sidebarInfo: SidebarInfo) { +function keyExtractor( + sidebarInfo: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +) { return sidebarInfo.threadInfo.id; } -function getItemLayout(data: ?$ReadOnlyArray, index: number) { +function getItemLayout( + data: ?$ReadOnlyArray, + index: number, +) { return { length: 24, offset: 24 * index, index }; } @@ -39,7 +48,7 @@ searchState, setSearchState, onChangeSearchInputText, - } = useSearchSidebars(props.route.params.threadInfo); + } = useSearchThreads(props.route.params.threadInfo, sidebarInfoSelector); const searchTextInputRef = React.useRef(); const setSearchTextInputRef = React.useCallback( @@ -79,7 +88,11 @@ ); const renderItem = React.useCallback( - (row: { item: SidebarInfo, index: number, ... }) => { + (row: { + item: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, + index: number, + ... + }) => { let extendArrow: boolean = false; if (row.index < numOfSidebarsWithExtendedArrow) { extendArrow = true; @@ -119,7 +132,7 @@ } function Item(props: { - item: SidebarInfo, + item: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, onPressItem: (threadInfo: ThreadInfo) => void, extendArrow: boolean, }): React.Node { diff --git a/web/chat/chat-thread-list-sidebar.react.js b/web/chat/chat-thread-list-sidebar.react.js --- a/web/chat/chat-thread-list-sidebar.react.js +++ b/web/chat/chat-thread-list-sidebar.react.js @@ -3,7 +3,7 @@ import classNames from 'classnames'; import * as React from 'react'; -import type { SidebarInfo } from 'lib/types/thread-types'; +import type { ThreadInfoWithLastUpdatedTimeAndNonLocalMessage } from 'lib/types/thread-types'; import { useOnClickThread, @@ -14,7 +14,7 @@ import SidebarItem from './sidebar-item.react'; type Props = { - +sidebarInfo: SidebarInfo, + +sidebarInfo: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +isSubsequentItem: boolean, }; function ChatThreadListSidebar(props: Props): React.Node { diff --git a/web/chat/sidebar-item.react.js b/web/chat/sidebar-item.react.js --- a/web/chat/sidebar-item.react.js +++ b/web/chat/sidebar-item.react.js @@ -3,13 +3,13 @@ import classNames from 'classnames'; import * as React from 'react'; -import type { SidebarInfo } from 'lib/types/thread-types'; +import type { ThreadInfoWithLastUpdatedTimeAndNonLocalMessage } from 'lib/types/thread-types'; import { useOnClickThread } from '../selectors/thread-selectors'; import css from './chat-thread-list.css'; type Props = { - +sidebarInfo: SidebarInfo, + +sidebarInfo: ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +extendArrow?: boolean, }; function SidebarItem(props: Props): React.Node { diff --git a/web/selectors/chat-selectors.js b/web/selectors/chat-selectors.js --- a/web/selectors/chat-selectors.js +++ b/web/selectors/chat-selectors.js @@ -14,7 +14,10 @@ } from 'lib/selectors/thread-selectors'; import { threadIsPending } from 'lib/shared/thread-utils'; import type { MessageStore, MessageInfo } from 'lib/types/message-types'; -import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types'; +import type { + ThreadInfo, + ThreadInfoWithLastUpdatedTimeAndNonLocalMessage, +} from 'lib/types/thread-types'; import type { AppState } from '../redux/redux-setup'; import { useSelector } from '../redux/redux-utils'; @@ -34,7 +37,9 @@ messageInfos: { +[id: string]: ?MessageInfo }, activeChatThreadID: ?string, pendingThreadInfo: ?ThreadInfo, - sidebarInfos: { +[id: string]: $ReadOnlyArray }, + sidebarInfos: { + +[id: string]: $ReadOnlyArray, + }, ): ?ChatThreadItem => { if (!activeChatThreadID) { return null;