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)**  **Android (normal)**  **iOS (past limit)**  **Android (past limit)**  * 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
This commit is contained in:
parent
b253bb23e0
commit
0f70af7b16
3 changed files with 141 additions and 10 deletions
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<TextInput
|
||||
ref={this.getInputRef}
|
||||
autoCapitalize='sentences'
|
||||
maxLength={MAX_MESSAGE_LENGTH}
|
||||
multiline={true}
|
||||
onBlur={this.handleBlur}
|
||||
onChangeText={this.handleTextChange}
|
||||
|
|
@ -566,6 +582,29 @@ export default class ExtensionPost extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
lengthCounterColor = (count) => {
|
||||
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 (
|
||||
<Text style={renderStyle}>
|
||||
{messageLengthRemaining}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {maxFileSize} = this.props;
|
||||
|
|
@ -610,6 +649,7 @@ export default class ExtensionPost extends PureComponent {
|
|||
<View style={styles.wrapper}>
|
||||
{this.renderBody()}
|
||||
<View style={styles.flex}>
|
||||
{this.renderMessageLengthRemaining()}
|
||||
{this.renderTeamButton()}
|
||||
{this.renderChannelButton()}
|
||||
</View>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<ExtensionPost {...baseProps}/>
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue