diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 057487627..15bb17e4e 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -453,6 +453,8 @@ "mobile.share_extension.error_message": "An error has occurred while using the share extension.", "mobile.share_extension.error_title": "Extension Error", "mobile.share_extension.team": "Team", + "mobile.share_extension.too_long_message": "Character count: {count}/{max}", + "mobile.share_extension.too_long_title": "Message is too long", "mobile.sidebar_settings.permanent": "Permanent Sidebar", "mobile.sidebar_settings.permanent_description": "Keep the sidebar open permanently", "mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.", diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js index b71c13094..ac90bd558 100644 --- a/share_extension/android/extension_post/extension_post.js +++ b/share_extension/android/extension_post/extension_post.js @@ -406,17 +406,34 @@ export default class ExtensionPost extends PureComponent { onPost = () => { const {channelId, files, value} = this.state; const {currentUserId} = this.props; + const {formatMessage} = this.context.intl; - const data = { - channelId, - currentUserId, - files, - token: this.token, - url: this.url, - value, - }; + if (value.length > MAX_MESSAGE_LENGTH) { + Alert.alert( + formatMessage({ + id: 'mobile.share_extension.too_long_title', + defaultMessage: 'Message is too long', + }), + formatMessage({ + id: 'mobile.share_extension.too_long_message', + defaultMessage: 'Character count: {count}/{max}', + }, { + count: value.length, + max: MAX_MESSAGE_LENGTH, + }) + ); + } else { + const data = { + channelId, + currentUserId, + files, + token: this.token, + url: this.url, + value, + }; - this.onClose(data); + this.onClose(data); + } }; renderBody = () => { @@ -435,7 +452,6 @@ export default class ExtensionPost extends PureComponent { { + if (count < 0) { + return styles.textTooLong; + } + return styles.textLengthOk; + } + + renderMessageLengthRemaining = () => { + const {value} = this.state; + const messageLengthRemaining = MAX_MESSAGE_LENGTH - value.length; + + if (value.length === 0) { + return null; + } + + const renderStyle = [styles.messageLengthRemaining, this.lengthCounterColor(messageLengthRemaining)]; + return ( + + {messageLengthRemaining} + + ); + }; + render() { const {formatMessage} = this.context.intl; const {maxFileSize} = this.props; @@ -610,6 +649,7 @@ export default class ExtensionPost extends PureComponent { {this.renderBody()} + {this.renderMessageLengthRemaining()} {this.renderTeamButton()} {this.renderChannelButton()} @@ -654,6 +694,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flex: 1, padding: 15, }, + messageLengthRemaining: { + paddingTop: 5, + paddingBottom: 5, + paddingLeft: 15, + paddingRight: 15, + opacity: 0.5, + }, + textLengthOk: { + color: theme.centerChannelColor, + }, + textTooLong: { + color: theme.errorTextColor, + }, input: { flex: 1, color: theme.centerChannelColor, diff --git a/share_extension/android/extension_post/extension_post.test.js b/share_extension/android/extension_post/extension_post.test.js new file mode 100644 index 000000000..2422be80a --- /dev/null +++ b/share_extension/android/extension_post/extension_post.test.js @@ -0,0 +1,76 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Alert, PermissionsAndroid} from 'react-native'; +import {shallowWithIntl} from 'test/intl-test-helper'; + +import ExtensionPost from './extension_post'; + +jest.spyOn(Alert, 'alert').mockReturnValue(true); +jest.spyOn(PermissionsAndroid, 'check').mockReturnValue(PermissionsAndroid.RESULTS.GRANTED); + +jest.mock('react-navigation-stack/dist/views/TouchableItem', () => {}); + +jest.mock('NativeModules', () => ({ + MattermostShare: { + close: jest.fn(), + }, + RNKeychainManager: { + SECURITY_LEVEL_ANY: 'ANY', + }, +})); + +jest.mock('app/mattermost_managed', () => ({ + getConfig: jest.fn().mockReturnValue(false), +})); + +const MAX_MESSAGE_LENGTH = 4000; + +describe('ExtensionPost', () => { + const baseProps = { + actions: { + getTeamChannels: jest.fn(), + }, + channelId: 'channel-id', + channels: {}, + currentUserId: 'current-user-id', + maxFileSize: 1024, + navigation: { + setParams: jest.fn(), + }, + teamId: 'team-id', + }; + + const wrapper = shallowWithIntl( + + ); + + const instance = wrapper.instance(); + + const postMessage = (message) => { + wrapper.setState({value: message}); + instance.onPost(); + }; + + test('should show Alert dialog if shared message is longer than maximum length permitted', () => { + const longMessage = 'a'.repeat(MAX_MESSAGE_LENGTH + 1); + postMessage(longMessage); + + expect(Alert.alert).toHaveBeenCalledTimes(1); + }); + + test('should not show Alert dialog if shared message is within maximum length permitted', () => { + const message = 'a'.repeat(MAX_MESSAGE_LENGTH - 1); + postMessage(message); + + expect(Alert.alert).not.toHaveBeenCalled(); + }); + + test('should not show Alert dialog if shared message is at maximum length permitted', () => { + const exactLengthMessage = 'a'.repeat(MAX_MESSAGE_LENGTH); + postMessage(exactLengthMessage); + + expect(Alert.alert).not.toHaveBeenCalled(); + }); +});