diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -43,6 +43,7 @@ localIDPrefix, trimMessage, useMessagePreview, + messageKey, type MessagePreviewResult, } from 'lib/shared/message-utils.js'; import SearchIndex from 'lib/shared/search-index.js'; @@ -579,14 +580,19 @@ editedMessage = ( - - Editing message - - - {message.text} - + + Editing message + + + {message.text} + + { + const editedMessage = this.getEditedMessage(); + if (!editedMessage) { + return; + } + const editedMessageKey = messageKey(editedMessage); + this.props.inputState?.scrollToMessage(editedMessageKey); + }; + exitEditMode = () => { this.props.inputState?.setEditedMessage(null, () => { this.updateText(this.props.draft); diff --git a/native/chat/chat-list.react.js b/native/chat/chat-list.react.js --- a/native/chat/chat-list.react.js +++ b/native/chat/chat-list.react.js @@ -18,6 +18,8 @@ import type { ChatNavigationProp } from './chat.react.js'; import NewMessagesPill from './new-messages-pill.react.js'; import { chatMessageItemHeight, chatMessageItemKey } from './utils.js'; +import { InputStateContext } from '../input/input-state.js'; +import type { InputState } from '../input/input-state.js'; import { type KeyboardState, KeyboardContext, @@ -48,6 +50,7 @@ +viewerID: ?string, // withKeyboardState +keyboardState: ?KeyboardState, + +inputState: ?InputState, ... }; type State = { @@ -81,6 +84,7 @@ this.props.navigation.getParent(); invariant(tabNavigation, 'ChatNavigator should be within TabNavigator'); tabNavigation.addListener('tabPress', this.onTabPress); + this.props.inputState?.addScrollToMessageListener(this.scrollToMessage); } componentWillUnmount() { @@ -88,6 +92,7 @@ this.props.navigation.getParent(); invariant(tabNavigation, 'ChatNavigator should be within TabNavigator'); tabNavigation.removeListener('tabPress', this.onTabPress); + this.props.inputState?.removeScrollToMessageListener(this.scrollToMessage); } onTabPress = () => { @@ -272,6 +277,24 @@ this.toggleNewMessagesPill(false); }; + scrollToMessage = (key?: string) => { + const { flatList } = this; + const { data } = this.props; + if (!flatList || !key) { + return; + } + const index = data.findIndex(item => chatMessageItemKey(item) === key); + if (index < 0) { + console.warn("Couldn't find message to scroll to"); + return; + } + flatList.scrollToIndex({ + index, + animated: true, + viewPosition: 0.5, + }); + }; + onPressBackground = () => { const { keyboardState } = this.props; keyboardState && keyboardState.dismissKeyboard(); @@ -292,11 +315,17 @@ const ConnectedChatList: React.ComponentType = React.memo( function ConnectedChatList(props: BaseProps) { const keyboardState = React.useContext(KeyboardContext); + const inputState = React.useContext(InputStateContext); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); return ( - + ); }, ); diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js --- a/native/input/input-state-container.react.js +++ b/native/input/input-state-container.react.js @@ -169,6 +169,7 @@ editInputBarCallbacks: Array< (params: EditInputBarMessageParameters) => void, > = []; + scrollToMessageCallbacks: Array<(messageID: string) => void> = []; pendingThreadCreations = new Map>(); pendingThreadUpdateHandlers = new Map mixed>(); @@ -406,9 +407,28 @@ setPendingThreadUpdateHandler: this.setPendingThreadUpdateHandler, editState, setEditedMessage: this.setEditedMessage, + scrollToMessage: this.scrollToMessage, + addScrollToMessageListener: this.addScrollToMessageListener, + removeScrollToMessageListener: this.removeScrollToMessageListener, }), ); + scrollToMessage = (messageID: string) => { + this.scrollToMessageCallbacks.forEach(callback => callback(messageID)); + }; + + addScrollToMessageListener = (callback: (messageID: string) => void) => { + this.scrollToMessageCallbacks.push(callback); + }; + + removeScrollToMessageListener = ( + callbackScrollToMessage: (messageID: string) => void, + ) => { + this.scrollToMessageCallbacks = this.scrollToMessageCallbacks.filter( + candidate => candidate !== callbackScrollToMessage, + ); + }; + uploadInProgress = () => { if (this.props.ongoingMessageCreation) { return true; diff --git a/native/input/input-state.js b/native/input/input-state.js --- a/native/input/input-state.js +++ b/native/input/input-state.js @@ -69,6 +69,9 @@ editedMessage: ?MessageInfo, callback?: () => void, ) => void, + +scrollToMessage: (messageKey: string) => void, + +addScrollToMessageListener: ((messageKey: string) => void) => void, + +removeScrollToMessageListener: ((messageKey: string) => void) => void, }; const InputStateContext: React.Context =