From 0f70af7b1636a85cb4630050c032184c4d3f2ffb Mon Sep 17 00:00:00 2001 From: Amit Uttam Date: Wed, 16 Oct 2019 03:51:06 -0300 Subject: [PATCH] MM-15469 (Android) Alert if shared text is longer than message limit (#3264) * MM-15469 Alert if shared text is longer than message limit One can't manually enter text into the share extension's text box past 4000 characters (the default/defined text size limit). However, this restriction can still be circumvented by sharing a large item of text. In this latter case, the textarea displays just the first 4000 characters, but the value buffer still contains the entire shared text. On hitting "Send", the oversize text can still be shared. This commit implements [MM-15469](https://mattermost.atlassian.net/browse/MM-15469) to better align the Android sharing user experience with the iOS experience. Since a pre-existing i18n string was used, no additional translations are required. Fixes https://github.com/mattermost/mattermost-server/issues/11431 * Add available characters remaining counter in Android Share textarea Shows number of available characters remaining until textarea limit (default = 4000). Similar to iOS experience. **iOS (normal)** ![iOS (normal)](https://user-images.githubusercontent.com/887849/65007527-d6d1f700-d8dc-11e9-9382-7e4b3c6f4f5d.png) **Android (normal)** ![Android (normal)](https://user-images.githubusercontent.com/887849/65007570-f2d59880-d8dc-11e9-9795-ed137e4dd506.jpg) **iOS (past limit)** ![iOS (past limit)](https://user-images.githubusercontent.com/887849/65007638-2b757200-d8dd-11e9-987d-f5e89b2aa0d3.png) **Android (past limit)** ![Android (past limit)](https://user-images.githubusercontent.com/887849/65007720-5e1f6a80-d8dd-11e9-97ba-370fa6c8db58.jpg) * Don't limit size of Android Share extension text input box Pasted shared text was getting cut off after hitting the `MAX_MESSAGE_LENGTH` for shared posts. Alert validation now checks this limit before allowing content to be posted, which made the `maxLength` limit redundant. Context: https://github.com/mattermost/mattermost-mobile/pull/3264#issuecomment-540962933 --- assets/base/i18n/en.json | 2 + .../android/extension_post/extension_post.js | 73 +++++++++++++++--- .../extension_post/extension_post.test.js | 76 +++++++++++++++++++ 3 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 share_extension/android/extension_post/extension_post.test.js 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(); + }); +});