diff --git a/app/components/post_draft/post_draft.js b/app/components/post_draft/post_draft.js index 4e03925f0..66d43cfe5 100644 --- a/app/components/post_draft/post_draft.js +++ b/app/components/post_draft/post_draft.js @@ -132,62 +132,41 @@ export default class PostDraft extends PureComponent { }; doSubmitMessage = () => { - if (this.input.current) { - const {createPost, currentUserId, channelId, files, handleClearFiles, rootId} = this.props; - const value = this.input.current.getValue() || ''; - const postFiles = files.filter((f) => !f.failed); - const post = { - user_id: currentUserId, - channel_id: channelId, - root_id: rootId, - parent_id: rootId, - message: value, + const {createPost, currentUserId, channelId, files, handleClearFiles, rootId} = this.props; + const value = this.input.current.getValue(); + const postFiles = files.filter((f) => !f.failed); + const post = { + user_id: currentUserId, + channel_id: channelId, + root_id: rootId, + parent_id: rootId, + message: value, + }; + + createPost(post, postFiles); + + if (postFiles.length) { + handleClearFiles(channelId, rootId); + } + + this.input.current.setValue(''); + this.setState({sendingMessage: false}); + + this.input.current.changeDraft(''); + + if (Platform.OS === 'android') { + // Fixes the issue where Android predictive text would prepend suggestions to the post draft when messages + // are typed successively without blurring the input + const nextState = { + keyboardType: 'email-address', }; - createPost(post, postFiles); + const callback = () => this.setState({keyboardType: 'default'}); - if (postFiles.length) { - handleClearFiles(channelId, rootId); - } - - if (Platform.OS === 'ios') { - // On iOS, if the PostInput height increases from its - // initial height (due to a multiline post or a post whose - // message wraps, for example), then when the text is cleared - // the PostInput height decrease will be animated. This - // animation in conjunction with the PostList animation as it - // receives the newly created post is causing issues in the iOS - // PostList component as it fails to properly react to its content - // size changes. While a proper fix is determined for the PostList - // component, a small delay in triggering the height decrease - // animation gives the PostList enough time to first handle content - // size changes from the new post. - setTimeout(() => { - this.input.current.setValue(''); - this.setState({sendingMessage: false}); - }, 250); - } else { - this.input.current.setValue(''); - this.setState({sendingMessage: false}); - } - - this.input.current.changeDraft(''); - - let callback; - if (Platform.OS === 'android') { - // Fixes the issue where Android predictive text would prepend suggestions to the post draft when messages - // are typed successively without blurring the input - const nextState = { - keyboardType: 'email-address', - }; - - callback = () => this.setState({keyboardType: 'default'}); - - this.setState(nextState, callback); - } - - EventEmitter.emit('scroll-to-bottom'); + this.setState(nextState, callback); } + + EventEmitter.emit('scroll-to-bottom'); }; getStatusFromSlashCommand = (message) => { @@ -237,52 +216,62 @@ export default class PostDraft extends PureComponent { }; handleSendMessage = () => { - if (!this.isSendButtonEnabled()) { + if (!this.input.current) { return; } - this.setState({sendingMessage: true}); + this.input.current.resetTextInput(); - const {channelId, files, handleClearFailedFiles, rootId} = this.props; - const value = this.input.current?.getValue() || ''; + requestAnimationFrame(() => { + const value = this.input.current.getValue(); + if (!this.isSendButtonEnabled()) { + this.input.current.setValue(value); + return; + } - const isReactionMatch = value.match(IS_REACTION_REGEX); - if (isReactionMatch) { - const emoji = isReactionMatch[2]; - this.sendReaction(emoji); - return; - } + this.setState({sendingMessage: true}); - const hasFailedAttachments = files.some((f) => f.failed); - if (hasFailedAttachments) { - const {intl} = this.context; + const {channelId, files, handleClearFailedFiles, rootId} = this.props; - Alert.alert( - intl.formatMessage({ - id: 'mobile.post_textbox.uploadFailedTitle', - defaultMessage: 'Attachment failure', - }), - intl.formatMessage({ - id: 'mobile.post_textbox.uploadFailedDesc', - defaultMessage: 'Some attachments failed to upload to the server. Are you sure you want to post the message?', - }), - [{ - text: intl.formatMessage({id: 'mobile.channel_info.alertNo', defaultMessage: 'No'}), - onPress: () => { - this.setState({sendingMessage: false}); - }, - }, { - text: intl.formatMessage({id: 'mobile.channel_info.alertYes', defaultMessage: 'Yes'}), - onPress: () => { - // Remove only failed files - handleClearFailedFiles(channelId, rootId); - this.sendMessage(); - }, - }], - ); - } else { - this.sendMessage(); - } + const isReactionMatch = value.match(IS_REACTION_REGEX); + if (isReactionMatch) { + const emoji = isReactionMatch[2]; + this.sendReaction(emoji); + return; + } + + const hasFailedAttachments = files.some((f) => f.failed); + if (hasFailedAttachments) { + const {intl} = this.context; + + Alert.alert( + intl.formatMessage({ + id: 'mobile.post_textbox.uploadFailedTitle', + defaultMessage: 'Attachment failure', + }), + intl.formatMessage({ + id: 'mobile.post_textbox.uploadFailedDesc', + defaultMessage: 'Some attachments failed to upload to the server. Are you sure you want to post the message?', + }), + [{ + text: intl.formatMessage({id: 'mobile.channel_info.alertNo', defaultMessage: 'No'}), + onPress: () => { + this.input.current.setValue(value); + this.setState({sendingMessage: false}); + }, + }, { + text: intl.formatMessage({id: 'mobile.channel_info.alertYes', defaultMessage: 'Yes'}), + onPress: () => { + // Remove only failed files + handleClearFailedFiles(channelId, rootId); + this.sendMessage(); + }, + }], + ); + } else { + this.sendMessage(); + } + }); }; handleUploadFiles = async (files) => { @@ -357,6 +346,7 @@ export default class PostDraft extends PureComponent { this.setState({sendingMessage: false}); if (error) { + this.input.current.setValue(msg); Alert.alert( intl.formatMessage({ id: 'mobile.commands.error_title', @@ -367,24 +357,25 @@ export default class PostDraft extends PureComponent { return; } - if (this.input.current) { - this.input.current.setValue(''); - this.input.current.changeDraft(''); - } + this.input.current.setValue(''); + this.input.current.changeDraft(''); }; sendMessage = () => { - const value = this.input.current?.getValue() || ''; - const {enableConfirmNotificationsToChannel, membersCount, useChannelMentions} = this.props; - const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions; - const toAllOrChannel = this.textContainsAtAllAtChannel(value); + const value = this.input.current.getValue(); - if (value.indexOf('/') === 0) { - this.sendCommand(value); - } else if (notificationsToChannel && membersCount > NOTIFY_ALL_MEMBERS && toAllOrChannel) { - this.showSendToAllOrChannelAlert(membersCount); - } else { - this.doSubmitMessage(); + if (value) { + const {enableConfirmNotificationsToChannel, membersCount, useChannelMentions} = this.props; + const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions; + const toAllOrChannel = this.textContainsAtAllAtChannel(value); + + if (value.indexOf('/') === 0) { + this.sendCommand(value); + } else if (notificationsToChannel && membersCount > NOTIFY_ALL_MEMBERS && toAllOrChannel) { + this.showSendToAllOrChannelAlert(membersCount, value); + } else { + this.doSubmitMessage(); + } } }; @@ -392,10 +383,8 @@ export default class PostDraft extends PureComponent { const {addReactionToLatestPost, rootId} = this.props; addReactionToLatestPost(emoji, rootId); - if (this.input.current) { - this.input.current.setValue(''); - this.input.current.changeDraft(''); - } + this.input.current.setValue(''); + this.input.current.changeDraft(''); this.setState({sendingMessage: false}); }; @@ -422,7 +411,7 @@ export default class PostDraft extends PureComponent { ); }; - showSendToAllOrChannelAlert = (membersCount) => { + showSendToAllOrChannelAlert = (membersCount, msg) => { const {intl} = this.context; const {channelTimezoneCount} = this.state; const {isTimezoneEnabled} = this.props; @@ -468,6 +457,7 @@ export default class PostDraft extends PureComponent { defaultMessage: 'Cancel', }), onPress: () => { + this.input.current.setValue(msg); this.setState({sendingMessage: false}); }, }, diff --git a/app/components/post_draft/post_input/post_input.js b/app/components/post_draft/post_input/post_input.js index 7bfe9e7d8..f1255eb3b 100644 --- a/app/components/post_draft/post_input/post_input.js +++ b/app/components/post_draft/post_input/post_input.js @@ -265,6 +265,14 @@ export default class PostInput extends PureComponent { }); }; + resetTextInput = () => { + if (this.input.current) { + this.input.current.setNativeProps({ + text: '', + }); + } + } + setValue = (value, autocomplete = false) => { this.value = value; if (this.input.current) {