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 @@ -198,6 +198,7 @@ // We set it to null, when new selection is set. androidPreviousText: ?string; iosPreviousSelection: ?Selection; + iosDelayedSelectionTimeout: ?TimeoutID; constructor(props: Props) { super(props); @@ -213,6 +214,7 @@ this.setUpSendIconAnimations(); this.androidPreviousText = null; this.iosPreviousSelection = null; + this.iosDelayedSelectionTimeout = null; } setUpActionIconAnimations() { @@ -687,23 +689,66 @@ }); }, 400); - focusAndUpdateText = (text: string) => { - const { textInput } = this; - if (!textInput) { - return; - } + focusAndUpdateTextAndSelection = (text: string, selection: Selection) => { + // On iOS, the update of text below will trigger a selection change which + // will overwrite our update of selection. + // To address this, we schedule the second update of selection + // after waiting for 100ms. + // We tried waiting for the animation frame to flush, + // but it turned out to be unreliable and nondeterministic. + this.setState( + { + text, + textEdited: true, + selection, + controlSelection: true, + }, + () => { + this.refreshIOSSelection(selection); + }, + ); + this.saveDraft(text); + this.focusAndUpdateButtonsVisibility(); + }; + + focusAndUpdateText = (text: string) => { const currentText = this.state.text; if (!currentText.startsWith(text)) { const prependedText = text.concat(currentText); this.updateText(prependedText); - this.immediatelyShowSendButton(); - this.immediatelyHideButtons(); } + this.focusAndUpdateButtonsVisibility(); + }; + + focusAndUpdateButtonsVisibility = () => { + const { textInput } = this; + if (!textInput) { + return; + } + + this.immediatelyShowSendButton(); + this.immediatelyHideButtons(); textInput.focus(); }; + refreshIOSSelection = (selection: Selection) => { + if (Platform.OS !== 'ios') { + return; + } + + if (this.iosDelayedSelectionTimeout) { + clearTimeout(this.iosDelayedSelectionTimeout); + } + this.iosDelayedSelectionTimeout = setTimeout(() => { + this.setState({ + selection, + controlSelection: true, + }); + }, 100); + }; + onSend = async () => { if (!trimMessage(this.state.text)) { return;