diff --git a/native/chat/message-result.react.js b/native/chat/message-result.react.js
index 4932db7f0..091b50a6d 100644
--- a/native/chat/message-result.react.js
+++ b/native/chat/message-result.react.js
@@ -1,82 +1,84 @@
// @flow
import * as React from 'react';
import { Text, View } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
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';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import type { NavigationRoute } from '../navigation/route-names';
import { useStyles } from '../themes/colors.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
type MessageResultProps = {
+item: ChatMessageInfoItemWithHeight,
+threadInfo: ThreadInfo,
+navigation:
| AppNavigationProp<'TogglePinModal'>
- | ChatNavigationProp<'MessageResultsScreen'>,
+ | ChatNavigationProp<'MessageResultsScreen'>
+ | ChatNavigationProp<'MessageSearch'>,
+route:
| NavigationRoute<'TogglePinModal'>
- | NavigationRoute<'MessageResultsScreen'>,
+ | NavigationRoute<'MessageResultsScreen'>
+ | NavigationRoute<'MessageSearch'>,
+messageVerticalBounds: ?VerticalBounds,
};
function MessageResult(props: MessageResultProps): React.Node {
const styles = useStyles(unboundStyles);
const onToggleFocus = React.useCallback(() => {}, []);
const item = React.useMemo(
() => modifyItemForResultScreen(props.item),
[props.item],
);
return (
{longAbsoluteDate(props.item.messageInfo.time)}
);
}
const unboundStyles = {
container: {
marginTop: 5,
backgroundColor: 'panelForeground',
overflow: 'scroll',
maxHeight: 400,
},
viewContainer: {
marginTop: 10,
marginBottom: 10,
},
messageDate: {
color: 'messageLabel',
fontSize: 12,
marginLeft: 55,
},
};
export default MessageResult;
diff --git a/native/chat/message.react.js b/native/chat/message.react.js
index 79eaa063c..c1f44ea1b 100644
--- a/native/chat/message.react.js
+++ b/native/chat/message.react.js
@@ -1,151 +1,153 @@
// @flow
import _isEqual from 'lodash/fp/isEqual.js';
import * as React from 'react';
import {
LayoutAnimation,
TouchableWithoutFeedback,
PixelRatio,
} from 'react-native';
import shallowequal from 'shallowequal';
import { messageKey } from 'lib/shared/message-utils.js';
import type { ChatNavigationProp } from './chat.react.js';
import MultimediaMessage from './multimedia-message.react.js';
import { RobotextMessage } from './robotext-message.react.js';
import { TextMessage } from './text-message.react.js';
import { messageItemHeight } from './utils.js';
import {
type KeyboardState,
KeyboardContext,
} from '../keyboard/keyboard-state.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import type { NavigationRoute } from '../navigation/route-names.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
import { type VerticalBounds } from '../types/layout-types.js';
import type { LayoutEvent } from '../types/react-native.js';
type BaseProps = {
+item: ChatMessageInfoItemWithHeight,
+focused: boolean,
+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,
};
type Props = {
...BaseProps,
+keyboardState: ?KeyboardState,
};
class Message extends React.Component {
shouldComponentUpdate(nextProps: Props): boolean {
const { item, ...props } = this.props;
const { item: nextItem, ...newProps } = nextProps;
return !_isEqual(item, nextItem) || !shallowequal(props, newProps);
}
componentDidUpdate(prevProps: Props) {
if (
(prevProps.focused || prevProps.item.startsConversation) !==
(this.props.focused || this.props.item.startsConversation)
) {
LayoutAnimation.easeInEaseOut();
}
}
render() {
let message;
if (this.props.item.messageShapeType === 'text') {
message = (
);
} else if (this.props.item.messageShapeType === 'multimedia') {
message = (
);
} else {
message = (
);
}
const onLayout = __DEV__ ? this.onLayout : undefined;
return (
{message}
);
}
onLayout = (event: LayoutEvent) => {
if (this.props.focused) {
return;
}
const measuredHeight = event.nativeEvent.layout.height;
const expectedHeight = messageItemHeight(this.props.item);
const pixelRatio = 1 / PixelRatio.get();
const distance = Math.abs(measuredHeight - expectedHeight);
if (distance < pixelRatio) {
return;
}
const approxMeasuredHeight = Math.round(measuredHeight * 100) / 100;
const approxExpectedHeight = Math.round(expectedHeight * 100) / 100;
console.log(
`Message height for ${this.props.item.messageShapeType} ` +
`${messageKey(this.props.item.messageInfo)} was expected to be ` +
`${approxExpectedHeight} but is actually ${approxMeasuredHeight}. ` +
"This means MessageList's FlatList isn't getting the right item " +
'height for some of its nodes, which is guaranteed to cause glitchy ' +
'behavior. Please investigate!!',
);
};
dismissKeyboard = () => {
const { keyboardState } = this.props;
keyboardState && keyboardState.dismissKeyboard();
};
}
const ConnectedMessage: React.ComponentType = React.memo(
function ConnectedMessage(props: BaseProps) {
const keyboardState = React.useContext(KeyboardContext);
return ;
},
);
export { ConnectedMessage as Message };
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
index c81d4e1d1..431c6c694 100644
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -1,221 +1,223 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import { messageKey } from 'lib/shared/message-utils.js';
import { useCanCreateSidebarFromMessage } from 'lib/shared/thread-utils.js';
import { inlineEngagementCenterStyle } from './chat-constants.js';
import type { ChatNavigationProp } from './chat.react.js';
import { InlineEngagement } from './inline-engagement.react.js';
import { InnerRobotextMessage } from './inner-robotext-message.react.js';
import { Timestamp } from './timestamp.react.js';
import { getMessageTooltipKey, useContentAndHeaderOpacity } from './utils.js';
import { ChatContext } from '../chat/chat-context.js';
import { KeyboardContext } from '../keyboard/keyboard-state.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import { OverlayContext } from '../navigation/overlay-context.js';
import { RobotextMessageTooltipModalRouteName } from '../navigation/route-names.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { useStyles } from '../themes/colors.js';
import { fixedTooltipHeight } from '../tooltip/tooltip.react.js';
import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
import { AnimatedView } from '../types/styles.js';
type Props = {
...React.ElementConfig,
+item: ChatRobotextMessageInfoItemWithHeight,
+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,
};
function RobotextMessage(props: Props): React.Node {
const {
item,
navigation,
route,
focused,
toggleFocus,
verticalBounds,
...viewProps
} = props;
let timestamp = null;
if (focused || item.startsConversation) {
timestamp = (
);
}
const styles = useStyles(unboundStyles);
let inlineEngagement = null;
if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) {
inlineEngagement = (
);
}
const chatContext = React.useContext(ChatContext);
const keyboardState = React.useContext(KeyboardContext);
const key = messageKey(item.messageInfo);
const onPress = React.useCallback(() => {
const didDismiss =
keyboardState && keyboardState.dismissKeyboardIfShowing();
if (!didDismiss) {
toggleFocus(key);
}
}, [keyboardState, toggleFocus, key]);
const overlayContext = React.useContext(OverlayContext);
const viewRef = React.useRef>();
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
item.threadInfo,
item.messageInfo,
);
const visibleEntryIDs = React.useMemo(() => {
const result = [];
if (item.threadCreatedFromMessage || canCreateSidebarFromMessage) {
result.push('sidebar');
}
return result;
}, [item.threadCreatedFromMessage, canCreateSidebarFromMessage]);
const openRobotextTooltipModal = React.useCallback(
(x, y, width, height, pageX, pageY) => {
invariant(
verticalBounds,
'verticalBounds should be present in openRobotextTooltipModal',
);
const coordinates = { x: pageX, y: pageY, width, height };
const messageTop = pageY;
const messageBottom = pageY + height;
const boundsTop = verticalBounds.y;
const boundsBottom = verticalBounds.y + verticalBounds.height;
const belowMargin = 20;
const belowSpace = fixedTooltipHeight + belowMargin;
const { isViewer } = item.messageInfo.creator;
const aboveMargin = isViewer ? 30 : 50;
const aboveSpace = fixedTooltipHeight + aboveMargin;
let margin = 0;
if (
messageBottom + belowSpace > boundsBottom &&
messageTop - aboveSpace > boundsTop
) {
margin = aboveMargin;
}
const currentInputBarHeight =
chatContext?.chatInputBarHeights.get(item.threadInfo.id) ?? 0;
props.navigation.navigate<'RobotextMessageTooltipModal'>({
name: RobotextMessageTooltipModalRouteName,
params: {
presentedFrom: props.route.key,
initialCoordinates: coordinates,
verticalBounds,
visibleEntryIDs,
tooltipLocation: 'fixed',
margin,
item,
chatInputBarHeight: currentInputBarHeight,
},
key: getMessageTooltipKey(item),
});
},
[
item,
props.navigation,
props.route.key,
verticalBounds,
visibleEntryIDs,
chatContext,
],
);
const onLongPress = React.useCallback(() => {
if (keyboardState && keyboardState.dismissKeyboardIfShowing()) {
return;
}
if (visibleEntryIDs.length === 0) {
return;
}
if (!viewRef.current || !verticalBounds) {
return;
}
if (!focused) {
toggleFocus(messageKey(item.messageInfo));
}
invariant(overlayContext, 'RobotextMessage should have OverlayContext');
overlayContext.setScrollBlockingModalStatus('open');
viewRef.current?.measure(openRobotextTooltipModal);
}, [
focused,
item,
keyboardState,
overlayContext,
toggleFocus,
verticalBounds,
viewRef,
visibleEntryIDs,
openRobotextTooltipModal,
]);
const onLayout = React.useCallback(() => {}, []);
const contentAndHeaderOpacity = useContentAndHeaderOpacity(item);
return (
{timestamp}
{inlineEngagement}
);
}
const unboundStyles = {
sidebar: {
marginTop: inlineEngagementCenterStyle.topOffset,
marginBottom: -inlineEngagementCenterStyle.topOffset,
alignSelf: 'center',
},
};
export { RobotextMessage };
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
index 9cd4bcc78..093a2d2aa 100644
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -1,296 +1,298 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import { useCanEditMessage } from 'lib/shared/edit-messages-utils.js';
import { messageKey } from 'lib/shared/message-utils.js';
import {
threadHasPermission,
useCanCreateSidebarFromMessage,
} from 'lib/shared/thread-utils.js';
import { threadPermissions } from 'lib/types/thread-permission-types.js';
import type { ChatNavigationProp } from './chat.react.js';
import ComposedMessage from './composed-message.react.js';
import { InnerTextMessage } from './inner-text-message.react.js';
import {
MessagePressResponderContext,
type MessagePressResponderContextType,
} from './message-press-responder-context.js';
import textMessageSendFailed from './text-message-send-failed.js';
import { getMessageTooltipKey } from './utils.js';
import { ChatContext, type ChatContextType } from '../chat/chat-context.js';
import { MarkdownContext } from '../markdown/markdown-context.js';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
import {
OverlayContext,
type OverlayContextType,
} from '../navigation/overlay-context.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { TextMessageTooltipModalRouteName } from '../navigation/route-names.js';
import { fixedTooltipHeight } from '../tooltip/tooltip.react.js';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
import { useShouldRenderEditButton } from '../utils/edit-messages-utils.js';
type BaseProps = {
...React.ElementConfig,
+item: ChatTextMessageInfoItemWithHeight,
+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,
+shouldDisplayPinIndicator: boolean,
};
type Props = {
...BaseProps,
// Redux state
+canCreateSidebarFromMessage: boolean,
// withOverlayContext
+overlayContext: ?OverlayContextType,
// ChatContext
+chatContext: ?ChatContextType,
// MarkdownContext
+isLinkModalActive: boolean,
+canEditMessage: boolean,
+shouldRenderEditButton: boolean,
+canTogglePins: boolean,
};
class TextMessage extends React.PureComponent {
message: ?React.ElementRef;
messagePressResponderContext: MessagePressResponderContextType;
constructor(props: Props) {
super(props);
this.messagePressResponderContext = {
onPressMessage: this.onPress,
};
}
render() {
const {
item,
navigation,
route,
focused,
toggleFocus,
verticalBounds,
shouldDisplayPinIndicator,
overlayContext,
chatContext,
isLinkModalActive,
canCreateSidebarFromMessage,
canEditMessage,
shouldRenderEditButton,
canTogglePins,
...viewProps
} = this.props;
let swipeOptions = 'none';
const canReply = this.canReply();
const canNavigateToSidebar = this.canNavigateToSidebar();
if (isLinkModalActive) {
swipeOptions = 'none';
} else if (canReply && canNavigateToSidebar) {
swipeOptions = 'both';
} else if (canReply) {
swipeOptions = 'reply';
} else if (canNavigateToSidebar) {
swipeOptions = 'sidebar';
}
return (
);
}
messageRef = (message: ?React.ElementRef) => {
this.message = message;
};
canReply() {
return threadHasPermission(
this.props.item.threadInfo,
threadPermissions.VOICED,
);
}
canNavigateToSidebar() {
return (
this.props.item.threadCreatedFromMessage ||
this.props.canCreateSidebarFromMessage
);
}
visibleEntryIDs() {
const result = ['copy'];
if (this.canReply()) {
result.push('reply');
}
if (this.props.canEditMessage && this.props.shouldRenderEditButton) {
result.push('edit');
}
if (this.props.canTogglePins) {
this.props.item.isPinned ? result.push('unpin') : result.push('pin');
}
if (
this.props.item.threadCreatedFromMessage ||
this.props.canCreateSidebarFromMessage
) {
result.push('sidebar');
}
if (!this.props.item.messageInfo.creator.isViewer) {
result.push('report');
}
return result;
}
onPress = () => {
const visibleEntryIDs = this.visibleEntryIDs();
if (visibleEntryIDs.length === 0) {
return;
}
const {
message,
props: { verticalBounds, isLinkModalActive },
} = this;
if (!message || !verticalBounds || isLinkModalActive) {
return;
}
const { focused, toggleFocus, item } = this.props;
if (!focused) {
toggleFocus(messageKey(item.messageInfo));
}
const { overlayContext } = this.props;
invariant(overlayContext, 'TextMessage should have OverlayContext');
overlayContext.setScrollBlockingModalStatus('open');
message.measure((x, y, width, height, pageX, pageY) => {
const coordinates = { x: pageX, y: pageY, width, height };
const messageTop = pageY;
const messageBottom = pageY + height;
const boundsTop = verticalBounds.y;
const boundsBottom = verticalBounds.y + verticalBounds.height;
const belowMargin = 20;
const belowSpace = fixedTooltipHeight + belowMargin;
const { isViewer } = item.messageInfo.creator;
const aboveMargin = isViewer ? 30 : 50;
const aboveSpace = fixedTooltipHeight + aboveMargin;
let margin = belowMargin;
if (
messageBottom + belowSpace > boundsBottom &&
messageTop - aboveSpace > boundsTop
) {
margin = aboveMargin;
}
const currentInputBarHeight =
this.props.chatContext?.chatInputBarHeights.get(item.threadInfo.id) ??
0;
this.props.navigation.navigate<'TextMessageTooltipModal'>({
name: TextMessageTooltipModalRouteName,
params: {
presentedFrom: this.props.route.key,
initialCoordinates: coordinates,
verticalBounds,
visibleEntryIDs,
tooltipLocation: 'fixed',
margin,
item,
chatInputBarHeight: currentInputBarHeight,
},
key: getMessageTooltipKey(item),
});
});
};
}
const ConnectedTextMessage: React.ComponentType =
React.memo(function ConnectedTextMessage(props: BaseProps) {
const overlayContext = React.useContext(OverlayContext);
const chatContext = React.useContext(ChatContext);
const markdownContext = React.useContext(MarkdownContext);
invariant(markdownContext, 'markdownContext should be set');
const { linkModalActive, clearMarkdownContextData } = markdownContext;
const key = messageKey(props.item.messageInfo);
// We check if there is an key in the object - if not, we
// default to false. The likely situation where the former statement
// evaluates to null is when the thread is opened for the first time.
const isLinkModalActive = linkModalActive[key] ?? false;
const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage(
props.item.threadInfo,
props.item.messageInfo,
);
const shouldRenderEditButton = useShouldRenderEditButton();
const canEditMessage = useCanEditMessage(
props.item.threadInfo,
props.item.messageInfo,
);
const canTogglePins = threadHasPermission(
props.item.threadInfo,
threadPermissions.MANAGE_PINS,
);
React.useEffect(() => clearMarkdownContextData, [clearMarkdownContextData]);
return (
);
});
export { ConnectedTextMessage as TextMessage };
diff --git a/native/search/message-search.react.js b/native/search/message-search.react.js
index 30b2d6602..e93280fef 100644
--- a/native/search/message-search.react.js
+++ b/native/search/message-search.react.js
@@ -1,75 +1,228 @@
// @flow
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,
};
export type MessageSearchProps = {
+navigation: ChatNavigationProp<'MessageSearch'>,
+route: NavigationRoute<'MessageSearch'>,
};
function MessageSearch(props: MessageSearchProps): React.Node {
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) => {
+ (newMessages: $ReadOnlyArray, 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) => {
+ 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 ;
+ }
+ return (
+
+ );
+ },
+ [messageVerticalBounds, props.navigation, props.route, threadInfo],
+ );
+
+ const footer = React.useMemo(() => {
+ if (query === '') {
+ return ;
+ }
+ if (!endReached) {
+ return null;
+ }
+ if (measuredMessages.length > 0) {
+ return ;
+ }
+ const text =
+ 'No results, please try using different keywords to refine your search';
+ return ;
+ }, [query, endReached, measuredMessages.length]);
+
+ const onEndOfLoadedMessagesReached = React.useCallback(() => {
+ if (endReached) {
+ return;
+ }
+ setLastID(oldestMessageID(measuredMessages));
+ }, [endReached, measuredMessages, setLastID]);
const styles = useStyles(unboundStyles);
- if (query === '') {
- return (
-
-
+ return (
+
+
+
- );
- }
+
+ );
+}
- return null;
+function oldestMessageID(data: $ReadOnlyArray) {
+ 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 = {
content: {
height: '100%',
backgroundColor: 'panelBackground',
},
};
export default MessageSearch;