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,6 +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 { MessageListContextProvider } from './message-list-types.js'; import { Message } from './message.react.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react'; @@ -17,8 +18,12 @@ type MessageResultProps = { +item: ChatMessageInfoItemWithHeight, +threadInfo: ThreadInfo, - +navigation: AppNavigationProp<'TogglePinModal'>, - +route: NavigationRoute<'TogglePinModal'>, + +navigation: + | AppNavigationProp<'TogglePinModal'> + | ChatNavigationProp<'MessageResultsScreen'>, + +route: + | NavigationRoute<'TogglePinModal'> + | NavigationRoute<'MessageResultsScreen'>, }; function MessageResult(props: MessageResultProps): React.Node { diff --git a/native/chat/message-results-screen.react.js b/native/chat/message-results-screen.react.js --- a/native/chat/message-results-screen.react.js +++ b/native/chat/message-results-screen.react.js @@ -1,11 +1,22 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; +import { View } from 'react-native'; +import { ScrollView } from 'react-native-gesture-handler'; +import { fetchPinnedMessages } from 'lib/actions/message-actions.js'; +import { messageListData } from 'lib/selectors/chat-selectors.js'; +import { createMessageInfo } from 'lib/shared/message-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; +import { useServerCall } from 'lib/utils/action-utils.js'; +import { useHeightMeasurer } from './chat-context.js'; import type { ChatNavigationProp } from './chat.react'; +import MessageResult from './message-result.react.js'; import type { NavigationRoute } from '../navigation/route-names'; +import { useSelector } from '../redux/redux-utils.js'; +import type { ChatMessageItemWithHeight } from '../types/chat-types.js'; export type MessageResultsScreenParams = { +threadInfo: ThreadInfo, @@ -16,9 +27,153 @@ +route: NavigationRoute<'MessageResultsScreen'>, }; -// eslint-disable-next-line no-unused-vars function MessageResultsScreen(props: MessageResultsScreenProps): React.Node { - return null; + const { navigation, route } = props; + const { threadInfo } = route.params; + const { id: threadID } = threadInfo; + const [rawMessageResults, setRawMessageResults] = React.useState([]); + + const measureMessages = useHeightMeasurer(); + const [measuredMessages, setMeasuredMessages] = React.useState([]); + + const callFetchPinnedMessages = useServerCall(fetchPinnedMessages); + const userInfos = useSelector(state => state.userStore.userInfos); + + React.useEffect(() => { + (async () => { + const result = await callFetchPinnedMessages({ threadID }); + setRawMessageResults(result.pinnedMessages); + })(); + }, [callFetchPinnedMessages, threadID]); + + const translatedMessageResults = React.useMemo(() => { + const threadInfos = { [threadID]: threadInfo }; + + return rawMessageResults + .map(messageInfo => + createMessageInfo(messageInfo, null, userInfos, threadInfos), + ) + .filter(Boolean); + }, [rawMessageResults, userInfos, threadID, threadInfo]); + + const chatMessageInfos = useSelector( + messageListData(threadInfo.id, translatedMessageResults), + ); + + const sortedUniqueChatMessageInfoItems = React.useMemo(() => { + if (!chatMessageInfos) { + return []; + } + + const chatMessageInfoItems = chatMessageInfos.filter( + item => item.itemType === 'message' && item.isPinned, + ); + + // By the nature of using messageListData and passing in + // the desired translatedMessageResults as additional + // messages, we will have duplicate ChatMessageInfoItems. + const uniqueChatMessageInfoItemsMap = new Map(); + chatMessageInfoItems.forEach( + item => + item.messageInfo && + item.messageInfo.id && + uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item), + ); + + // Push the items in the order they appear in the rawMessageResults + // since the messages fetched from the server are already sorted + // in the order of pin_time (newest first). + const sortedChatMessageInfoItems = []; + for (let i = 0; i < rawMessageResults.length; i++) { + sortedChatMessageInfoItems.push( + uniqueChatMessageInfoItemsMap.get(rawMessageResults[i].id), + ); + } + + return sortedChatMessageInfoItems.filter(Boolean); + }, [chatMessageInfos, rawMessageResults]); + + const measureCallback = React.useCallback( + (listDataWithHeights: $ReadOnlyArray) => { + setMeasuredMessages(listDataWithHeights); + }, + [], + ); + + React.useEffect(() => { + measureMessages( + sortedUniqueChatMessageInfoItems, + threadInfo, + measureCallback, + ); + }, [ + measureCallback, + measureMessages, + sortedUniqueChatMessageInfoItems, + threadInfo, + ]); + + const modifiedItems = React.useMemo( + () => + measuredMessages.map(item => { + invariant(item.itemType !== 'loader', 'should not be loader'); + invariant( + item.messageShapeType !== 'robotext', + 'should not be robotext', + ); + + if (item.messageShapeType === 'multimedia') { + return { + ...item, + startsConversation: false, + startsCluster: true, + endsCluster: true, + messageInfo: { + ...item.messageInfo, + creator: { + ...item.messageInfo.creator, + isViewer: false, + }, + }, + }; + } + + return { + ...item, + startsConversation: false, + startsCluster: true, + endsCluster: true, + messageInfo: { + ...item.messageInfo, + creator: { + ...item.messageInfo.creator, + isViewer: false, + }, + }, + }; + }), + [measuredMessages], + ); + + const messageResultsToDisplay = React.useMemo( + () => + modifiedItems.map(item => ( + + )), + [modifiedItems, threadInfo, navigation, route], + ); + + return ( + + {messageResultsToDisplay} + + ); } export default MessageResultsScreen; 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 @@ -31,8 +31,12 @@ +focused: boolean, +navigation: | ChatNavigationProp<'MessageList'> - | AppNavigationProp<'TogglePinModal'>, - +route: NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'>, + | AppNavigationProp<'TogglePinModal'> + | ChatNavigationProp<'MessageResultsScreen'>, + +route: + | NavigationRoute<'MessageList'> + | NavigationRoute<'TogglePinModal'> + | NavigationRoute<'MessageResultsScreen'>, +toggleFocus: (messageKey: string) => void, +verticalBounds: ?VerticalBounds, }; 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 @@ -30,8 +30,12 @@ +item: ChatRobotextMessageInfoItemWithHeight, +navigation: | ChatNavigationProp<'MessageList'> - | AppNavigationProp<'TogglePinModal'>, - +route: NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'>, + | AppNavigationProp<'TogglePinModal'> + | ChatNavigationProp<'MessageResultsScreen'>, + +route: + | NavigationRoute<'MessageList'> + | NavigationRoute<'TogglePinModal'> + | NavigationRoute<'MessageResultsScreen'>, +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 @@ -40,8 +40,12 @@ +item: ChatTextMessageInfoItemWithHeight, +navigation: | ChatNavigationProp<'MessageList'> - | AppNavigationProp<'TogglePinModal'>, - +route: NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'>, + | AppNavigationProp<'TogglePinModal'> + | ChatNavigationProp<'MessageResultsScreen'>, + +route: + | NavigationRoute<'MessageList'> + | NavigationRoute<'TogglePinModal'> + | NavigationRoute<'MessageResultsScreen'>, +focused: boolean, +toggleFocus: (messageKey: string) => void, +verticalBounds: ?VerticalBounds,