Page MenuHomePhabricator

D7774.id26937.diff
No OneTemporary

D7774.id26937.diff

diff --git a/native/chat/message-result.react.js b/native/chat/message-result.react.js
--- a/native/chat/message-result.react.js
+++ b/native/chat/message-result.react.js
@@ -7,7 +7,7 @@
import { type ThreadInfo } from 'lib/types/thread-types.js';
import { longAbsoluteDate } from 'lib/utils/date-utils.js';
-import type { ChatNavigationProp } from './chat.react';
+import { type ChatNavigationProp } from './chat.react.js';
import { MessageListContextProvider } from './message-list-types.js';
import { Message } from './message.react.js';
import { modifyItemForResultScreen } from './utils.js';
@@ -22,10 +22,12 @@
+threadInfo: ThreadInfo,
+navigation:
| AppNavigationProp<'TogglePinModal'>
- | ChatNavigationProp<'MessageResultsScreen'>,
+ | ChatNavigationProp<'MessageResultsScreen'>
+ | ChatNavigationProp<'MessageSearch'>,
+route:
| NavigationRoute<'TogglePinModal'>
- | NavigationRoute<'MessageResultsScreen'>,
+ | NavigationRoute<'MessageResultsScreen'>
+ | NavigationRoute<'MessageSearch'>,
+messageVerticalBounds: ?VerticalBounds,
};
diff --git a/native/chat/message.react.js b/native/chat/message.react.js
--- a/native/chat/message.react.js
+++ b/native/chat/message.react.js
@@ -32,11 +32,13 @@
+navigation:
| ChatNavigationProp<'MessageList'>
| AppNavigationProp<'TogglePinModal'>
- | ChatNavigationProp<'MessageResultsScreen'>,
+ | ChatNavigationProp<'MessageResultsScreen'>
+ | ChatNavigationProp<'MessageSearch'>,
+route:
| NavigationRoute<'MessageList'>
| NavigationRoute<'TogglePinModal'>
- | NavigationRoute<'MessageResultsScreen'>,
+ | NavigationRoute<'MessageResultsScreen'>
+ | NavigationRoute<'MessageSearch'>,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
shouldDisplayPinIndicator: boolean,
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -31,11 +31,13 @@
+navigation:
| ChatNavigationProp<'MessageList'>
| AppNavigationProp<'TogglePinModal'>
- | ChatNavigationProp<'MessageResultsScreen'>,
+ | ChatNavigationProp<'MessageResultsScreen'>
+ | ChatNavigationProp<'MessageSearch'>,
+route:
| NavigationRoute<'MessageList'>
| NavigationRoute<'TogglePinModal'>
- | NavigationRoute<'MessageResultsScreen'>,
+ | NavigationRoute<'MessageResultsScreen'>
+ | NavigationRoute<'MessageSearch'>,
+focused: boolean,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -41,11 +41,13 @@
+navigation:
| ChatNavigationProp<'MessageList'>
| AppNavigationProp<'TogglePinModal'>
- | ChatNavigationProp<'MessageResultsScreen'>,
+ | ChatNavigationProp<'MessageResultsScreen'>
+ | ChatNavigationProp<'MessageSearch'>,
+route:
| NavigationRoute<'MessageList'>
| NavigationRoute<'TogglePinModal'>
- | NavigationRoute<'MessageResultsScreen'>,
+ | NavigationRoute<'MessageResultsScreen'>
+ | NavigationRoute<'MessageSearch'>,
+focused: boolean,
+toggleFocus: (messageKey: string) => void,
+verticalBounds: ?VerticalBounds,
diff --git a/native/search/message-search.react.js b/native/search/message-search.react.js
--- a/native/search/message-search.react.js
+++ b/native/search/message-search.react.js
@@ -3,15 +3,25 @@
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
+import { FlatList } from 'react-native-gesture-handler';
-import type { MessageInfo } from 'lib/types/message-types.js';
+import { messageListData } from 'lib/selectors/chat-selectors.js';
+import { createMessageInfo } from 'lib/shared/message-utils.js';
+import { useSearchMessages } from 'lib/shared/search-utils.js';
+import type { RawMessageInfo } from 'lib/types/message-types.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import SearchFooter from './search-footer.react.js';
import { MessageSearchContext } from './search-provider.react.js';
+import { useHeightMeasurer } from '../chat/chat-context.js';
import type { ChatNavigationProp } from '../chat/chat.react.js';
+import { MessageListContextProvider } from '../chat/message-list-types.js';
+import MessageResult from '../chat/message-result.react.js';
+import ListLoadingIndicator from '../components/list-loading-indicator.react.js';
import type { NavigationRoute } from '../navigation/route-names.js';
+import { useSelector } from '../redux/redux-utils.js';
import { useStyles } from '../themes/colors.js';
+import type { ChatMessageItemWithHeight } from '../types/chat-types.js';
export type MessageSearchParams = {
+threadInfo: ThreadInfo,
@@ -26,43 +36,186 @@
const searchContext = React.useContext(MessageSearchContext);
invariant(searchContext, 'searchContext should be set');
const { query, clearQuery } = searchContext;
+ const { threadInfo } = props.route.params;
React.useEffect(() => {
- return props.navigation.addListener('beforeRemove', () => {
- clearQuery();
- });
+ return props.navigation.addListener('beforeRemove', clearQuery);
}, [props.navigation, clearQuery]);
- // eslint-disable-next-line no-unused-vars
const [lastID, setLastID] = React.useState();
-
- // eslint-disable-next-line no-unused-vars
const [searchResults, setSearchResults] = React.useState([]);
+ const [endReached, setEndReached] = React.useState(false);
- // eslint-disable-next-line no-unused-vars
const appendSearchResults = React.useCallback(
- (newMessages: $ReadOnlyArray<MessageInfo>) => {
+ (newMessages: $ReadOnlyArray<RawMessageInfo>, end: boolean) => {
setSearchResults(oldMessages => [...oldMessages, ...newMessages]);
+ setEndReached(end);
},
[],
);
+ const searchMessages = useSearchMessages();
+
React.useEffect(() => {
setSearchResults([]);
setLastID(undefined);
- }, [query]);
+ setEndReached(false);
+ }, [query, searchMessages]);
+
+ React.useEffect(
+ () => searchMessages(query, threadInfo.id, appendSearchResults, lastID),
+ [appendSearchResults, lastID, query, searchMessages, threadInfo.id],
+ );
+
+ const userInfos = useSelector(state => state.userStore.userInfos);
+
+ const translatedSearchResults = React.useMemo(() => {
+ const threadInfos = { [threadInfo.id]: threadInfo };
+ return searchResults
+ .map(rawMessageInfo =>
+ createMessageInfo(rawMessageInfo, null, userInfos, threadInfos),
+ )
+ .filter(Boolean);
+ }, [searchResults, threadInfo, userInfos]);
+
+ const chatMessageInfos = useSelector(
+ messageListData(threadInfo.id, translatedSearchResults),
+ );
+
+ const filteredChatMessageInfos = React.useMemo(() => {
+ if (!chatMessageInfos) {
+ return null;
+ }
+
+ const idSet = new Set(translatedSearchResults.map(item => item.id));
+
+ const chatMessageInfoItems = chatMessageInfos.filter(
+ item => item.messageInfo && idSet.has(item.messageInfo.id),
+ );
+
+ const uniqueChatMessageInfoItemsMap = new Map();
+ chatMessageInfoItems.forEach(
+ item =>
+ item.messageInfo &&
+ item.messageInfo.id &&
+ uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item),
+ );
+
+ const sortedChatMessageInfoItems = [];
+ for (let i = 0; i < translatedSearchResults.length; i++) {
+ sortedChatMessageInfoItems.push(
+ uniqueChatMessageInfoItemsMap.get(translatedSearchResults[i].id),
+ );
+ }
+ if (!endReached) {
+ sortedChatMessageInfoItems.push({ itemType: 'loader' });
+ }
+
+ return sortedChatMessageInfoItems.filter(Boolean);
+ }, [chatMessageInfos, endReached, translatedSearchResults]);
+
+ const [measuredMessages, setMeasuredMessages] = React.useState([]);
+
+ const measureMessages = useHeightMeasurer();
+ const measureCallback = React.useCallback(
+ (listDataWithHeights: $ReadOnlyArray<ChatMessageItemWithHeight>) => {
+ setMeasuredMessages(listDataWithHeights);
+ },
+ [setMeasuredMessages],
+ );
+
+ React.useEffect(() => {
+ measureMessages(filteredChatMessageInfos, threadInfo, measureCallback);
+ }, [filteredChatMessageInfos, measureCallback, measureMessages, threadInfo]);
+
+ const [messageVerticalBounds, setMessageVerticalBounds] = React.useState();
+ const scrollViewContainerRef = React.useRef();
+
+ const onLayout = React.useCallback(() => {
+ scrollViewContainerRef.current?.measure(
+ (x, y, width, height, pageX, pageY) => {
+ if (
+ height === null ||
+ height === undefined ||
+ pageY === null ||
+ pageY === undefined
+ ) {
+ return;
+ }
+
+ setMessageVerticalBounds({ height, y: pageY });
+ },
+ );
+ }, []);
+
+ const renderItem = React.useCallback(
+ ({ item }) => {
+ if (item.itemType === 'loader') {
+ return <ListLoadingIndicator />;
+ }
+ return (
+ <MessageResult
+ key={item.messageInfo.id}
+ item={item}
+ threadInfo={threadInfo}
+ navigation={props.navigation}
+ route={props.route}
+ messageVerticalBounds={messageVerticalBounds}
+ />
+ );
+ },
+ [messageVerticalBounds, props.navigation, props.route, threadInfo],
+ );
+
+ const footer = React.useMemo(() => {
+ if (query === '') {
+ return <SearchFooter text="Your search results will appear here" />;
+ }
+ if (!endReached) {
+ return null;
+ }
+ if (measuredMessages.length > 0) {
+ return <SearchFooter text="End of results" />;
+ }
+ const text =
+ 'No results, please try using different keywords to refine your search';
+ return <SearchFooter text={text} />;
+ }, [query, endReached, measuredMessages.length]);
+
+ const onEndOfLoadedMessagesReached = React.useCallback(() => {
+ if (endReached) {
+ return;
+ }
+ setLastID(oldestMessageID(measuredMessages));
+ }, [endReached, measuredMessages, setLastID]);
const styles = useStyles(unboundStyles);
- if (query === '') {
- return (
- <View style={styles.content}>
- <SearchFooter text="Your search results will appear here" />
+ return (
+ <MessageListContextProvider threadInfo={threadInfo}>
+ <View
+ style={styles.content}
+ ref={scrollViewContainerRef}
+ onLayout={onLayout}
+ >
+ <FlatList
+ renderItem={renderItem}
+ data={measuredMessages}
+ onEndReached={onEndOfLoadedMessagesReached}
+ ListFooterComponent={footer}
+ />
</View>
- );
- }
+ </MessageListContextProvider>
+ );
+}
- return null;
+function oldestMessageID(data: $ReadOnlyArray<ChatMessageItemWithHeight>) {
+ for (let i = data.length - 1; i >= 0; i--) {
+ if (data[i].itemType === 'message' && data[i].messageInfo.id) {
+ return data[i].messageInfo.id;
+ }
+ }
+ return undefined;
}
const unboundStyles = {

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 27, 1:28 AM (14 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2586808
Default Alt Text
D7774.id26937.diff (10 KB)

Event Timeline