diff --git a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap index ea489c424..5404cf1e3 100644 --- a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap +++ b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap @@ -98,7 +98,7 @@ exports[`PostTextBox should match, full snapshot 1`] = ` visible={false} > { expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledWith(baseProps.channelId, value); expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledTimes(1); }); + + describe('send button', () => { + test('should initially disable and hide the send button', () => { + const wrapper = shallowWithIntl( + + ); + + expect(wrapper.find(Fade).prop('visible')).toBe(false); + expect(wrapper.find(SendButton).prop('disabled')).toBe(true); + }); + + test('should show a disabled send button when uploading a file', () => { + const props = { + ...baseProps, + files: [{loading: true}], + }; + + const wrapper = shallowWithIntl( + + ); + + expect(wrapper.find(Fade).prop('visible')).toBe(true); + expect(wrapper.find(SendButton).prop('disabled')).toBe(true); + }); + + test('should show an enabled send button after uploading a file', () => { + const props = { + ...baseProps, + files: [{loading: false}], + }; + + const wrapper = shallowWithIntl( + + ); + + expect(wrapper.find(Fade).prop('visible')).toBe(true); + expect(wrapper.find(SendButton).prop('disabled')).toBe(false); + }); + + test('should show an enabled send button with a message', () => { + const props = { + ...baseProps, + value: 'test', + }; + + const wrapper = shallowWithIntl( + + ); + + expect(wrapper.find(Fade).prop('visible')).toBe(true); + expect(wrapper.find(SendButton).prop('disabled')).toBe(false); + }); + + test('should show a disabled send button while sending the message', () => { + const props = { + ...baseProps, + value: 'test', + }; + + const wrapper = shallowWithIntl( + + ); + + wrapper.setState({sendingMessage: true}); + + expect(wrapper.find(Fade).prop('visible')).toBe(true); + expect(wrapper.find(SendButton).prop('disabled')).toBe(true); + }); + }); }); diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js index 0fa6403b8..8a7347424 100644 --- a/app/components/post_textbox/post_textbox_base.js +++ b/app/components/post_textbox/post_textbox_base.js @@ -91,6 +91,7 @@ export default class PostTextBoxBase extends PureComponent { this.state = { cursorPosition: 0, keyboardType: 'default', + sendingMessage: false, top: 0, value: props.value, }; @@ -278,10 +279,12 @@ export default class PostTextBoxBase extends PureComponent { }; handleSendMessage = () => { - if (!this.canSend()) { + if (!this.canSend() || this.state.sendingMessage) { return; } + this.setState({sendingMessage: true}); + const {actions, channelId, files, rootId} = this.props; const {value} = this.state; @@ -307,6 +310,9 @@ export default class PostTextBoxBase extends PureComponent { }), [{ 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: () => { @@ -388,81 +394,72 @@ export default class PostTextBoxBase extends PureComponent { return this.canSend() || this.isFileLoading(); } + isSendButtonEnabled() { + return this.canSend() && !this.state.sendingMessage; + } + sendMessage = () => { const {actions, currentUserId, channelId, files, rootId} = this.props; - const {intl} = this.context; const {value} = this.state; - if (files.length === 0 && !value) { - Alert.alert( - intl.formatMessage({ - id: 'mobile.post_textbox.empty.title', - defaultMessage: 'Empty Message', - }), - intl.formatMessage({ - id: 'mobile.post_textbox.empty.message', - defaultMessage: 'You are trying to send an empty message.\nPlease make sure you have a message or at least one attached file.', - }), - [{ - text: intl.formatMessage({id: 'mobile.post_textbox.empty.ok', defaultMessage: 'OK'}), - }], - ); + if (value.indexOf('/') === 0) { + this.sendCommand(value); } else { - if (value.indexOf('/') === 0) { - this.sendCommand(value); - } else { - const postFiles = files.filter((f) => !f.failed); - const post = { - user_id: currentUserId, - channel_id: channelId, - root_id: rootId, - parent_id: rootId, - message: value, - }; + const postFiles = files.filter((f) => !f.failed); + const post = { + user_id: currentUserId, + channel_id: channelId, + root_id: rootId, + parent_id: rootId, + message: value, + }; - actions.createPost(post, postFiles); + actions.createPost(post, postFiles); - if (postFiles.length) { - actions.handleClearFiles(channelId, rootId); - } + if (postFiles.length) { + actions.handleClearFiles(channelId, rootId); } - - if (Platform.OS === 'ios') { - // On iOS, if the PostTextbox 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 PostTextbox 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.handleTextChange(''); - }, 250); - } else { - this.handleTextChange(''); - } - - this.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'); } + + if (Platform.OS === 'ios') { + // On iOS, if the PostTextbox 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 PostTextbox 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.handleTextChange(''); + + this.setState({sendingMessage: false}); + }, 250); + } else { + this.handleTextChange(''); + + this.setState({sendingMessage: false}); + } + + this.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'); }; getStatusFromSlashCommand = (message) => { @@ -516,6 +513,7 @@ export default class PostTextBoxBase extends PureComponent { const {actions, rootId} = this.props; actions.addReactionToLatestPost(emoji, rootId); this.handleTextChange(''); + this.setState({sendingMessage: false}); this.changeDraft(''); }; @@ -628,7 +626,7 @@ export default class PostTextBoxBase extends PureComponent { /> diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index d913b13b7..3bb98a2af 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -352,9 +352,6 @@ "mobile.post_pre_header.flagged": "Flagged", "mobile.post_pre_header.pinned": "Pinned", "mobile.post_pre_header.pinned_flagged": "Pinned and Flagged", - "mobile.post_textbox.empty.message": "You are trying to send an empty message.\nPlease make sure you have a message or at least one attached file.", - "mobile.post_textbox.empty.ok": "OK", - "mobile.post_textbox.empty.title": "Empty Message", "mobile.post_textbox.uploadFailedDesc": "Some attachments failed to upload to the server. Are you sure you want to post the message?", "mobile.post_textbox.uploadFailedTitle": "Attachment failure", "mobile.post.cancel": "Cancel",