diff --git a/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap b/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap index aed26b3cb..ce5fb031d 100644 --- a/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap +++ b/app/components/post_draft/quick_actions/__snapshots__/quick_actions.test.js.snap @@ -86,7 +86,7 @@ exports[`QuickActions should match snapshot 1`] = ` { +const FileQuickAction = ({disabled, fileCount = 0, intl, maxFileCount, onUploadFiles, testID = '', theme}: QuickActionAttachmentProps) => { const attachFileFromFiles = async () => { const hasPermission = await hasStoragePermission(intl); const browseFileTypes = DocumentPicker.types.allFiles; diff --git a/app/components/post_draft/quick_actions/image_quick_action/index.tsx b/app/components/post_draft/quick_actions/image_quick_action/index.tsx index df75e2f83..745c968ad 100644 --- a/app/components/post_draft/quick_actions/image_quick_action/index.tsx +++ b/app/components/post_draft/quick_actions/image_quick_action/index.tsx @@ -9,7 +9,7 @@ import {launchImageLibrary, ImageLibraryOptions} from 'react-native-image-picker import CompassIcon from '@components/compass_icon'; import TouchableWithFeedback from '@components/touchable_with_feedback'; import {NavigationTypes} from '@constants'; -import {ICON_SIZE, MAX_FILE_COUNT, MAX_FILE_COUNT_WARNING} from '@constants/post_draft'; +import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft'; import EventEmitter from '@mm-redux/utils/event_emitter'; import {lookupMimeType} from '@utils/file'; import {hasPhotoPermission} from '@utils/permission'; @@ -27,7 +27,7 @@ const style = StyleSheet.create({ }, }); -const ImageQuickAction = ({disabled, fileCount = 0, intl, maxFileCount = MAX_FILE_COUNT, onUploadFiles, testID = '', theme}: QuickActionAttachmentProps) => { +const ImageQuickAction = ({disabled, fileCount = 0, intl, maxFileCount, onUploadFiles, testID = '', theme}: QuickActionAttachmentProps) => { const attachFileFromLibrary = async () => { const selectionLimit = maxFileCount - fileCount; const options: ImageLibraryOptions = { diff --git a/app/components/post_draft/quick_actions/index.js b/app/components/post_draft/quick_actions/index.js index 5b868f4eb..697887a0d 100644 --- a/app/components/post_draft/quick_actions/index.js +++ b/app/components/post_draft/quick_actions/index.js @@ -3,8 +3,8 @@ import {connect} from 'react-redux'; -import {canUploadFilesOnMobile, getConfig} from '@mm-redux/selectors/entities/general'; -import {getAllowedServerMaxFileSize} from '@utils/file'; +import {canUploadFilesOnMobile, getConfig, getServerVersion} from '@mm-redux/selectors/entities/general'; +import {getAllowedServerMaxFileSize, getMaxFileCount} from '@utils/file'; import QuickActions from './quick_actions'; @@ -14,6 +14,7 @@ function mapStateToProps(state) { return { canUploadFiles: canUploadFilesOnMobile(state), maxFileSize: getAllowedServerMaxFileSize(config), + maxFileCount: getMaxFileCount(getServerVersion(state)), }; } diff --git a/app/components/post_draft/quick_actions/quick_actions.js b/app/components/post_draft/quick_actions/quick_actions.js index 8833f0071..2e6dfa84d 100644 --- a/app/components/post_draft/quick_actions/quick_actions.js +++ b/app/components/post_draft/quick_actions/quick_actions.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import React, {PureComponent} from 'react'; import {Platform, StyleSheet, View} from 'react-native'; -import {MAX_FILE_COUNT, UPLOAD_FILES} from '@constants/post_draft'; +import {UPLOAD_FILES} from '@constants/post_draft'; import EventEmitter from '@mm-redux/utils/event_emitter'; import CameraAction from './camera_quick_action'; @@ -21,6 +21,7 @@ export default class QuickActions extends PureComponent { fileCount: PropTypes.number, inputEventType: PropTypes.string.isRequired, maxFileSize: PropTypes.number.isRequired, + maxFileCount: PropTypes.number.isRequired, onTextChange: PropTypes.func.isRequired, theme: PropTypes.object.isRequired, }; @@ -69,6 +70,7 @@ export default class QuickActions extends PureComponent { testID, canUploadFiles, fileCount, + maxFileCount, theme, } = this.props; const atInputActionTestID = `${testID}.at_input_action`; @@ -79,7 +81,7 @@ export default class QuickActions extends PureComponent { const uploadProps = { disabled: !canUploadFiles, fileCount, - maxFileCount: MAX_FILE_COUNT, + maxFileCount, theme, onUploadFiles: this.handleUploadFiles, }; diff --git a/app/components/post_draft/quick_actions/quick_actions.test.js b/app/components/post_draft/quick_actions/quick_actions.test.js index c0d947ddc..ce0707d2e 100644 --- a/app/components/post_draft/quick_actions/quick_actions.test.js +++ b/app/components/post_draft/quick_actions/quick_actions.test.js @@ -16,6 +16,7 @@ describe('QuickActions', () => { fileCount: 1, inputEventType: 'input-event-type', maxFileSize: 10, + maxFileCount: 10, screenId: 'Channel', theme: Preferences.THEMES.default, }; diff --git a/app/components/post_draft/uploads/index.js b/app/components/post_draft/uploads/index.js index bc0a37bbc..4e6a979cb 100644 --- a/app/components/post_draft/uploads/index.js +++ b/app/components/post_draft/uploads/index.js @@ -5,10 +5,10 @@ import {connect} from 'react-redux'; import {handleRemoveLastFile, initUploadFiles} from '@actions/views/file_upload'; import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels'; -import {canUploadFilesOnMobile, getConfig} from '@mm-redux/selectors/entities/general'; +import {canUploadFilesOnMobile, getConfig, getServerVersion} from '@mm-redux/selectors/entities/general'; import {getDimensions} from '@selectors/device'; import {checkForFileUploadingInChannel} from '@selectors/file'; -import {getAllowedServerMaxFileSize} from '@utils/file'; +import {getAllowedServerMaxFileSize, getMaxFileCount} from '@utils/file'; import Uploads from './uploads'; @@ -24,6 +24,7 @@ function mapStateToProps(state, ownProps) { deviceHeight, filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, channelId, ownProps.rootId), maxFileSize: getAllowedServerMaxFileSize(config), + maxFileCount: getMaxFileCount(getServerVersion(state)), }; } diff --git a/app/components/post_draft/uploads/uploads.js b/app/components/post_draft/uploads/uploads.js index 8f0248f1d..eebbc4533 100644 --- a/app/components/post_draft/uploads/uploads.js +++ b/app/components/post_draft/uploads/uploads.js @@ -16,7 +16,7 @@ import * as Animatable from 'react-native-animatable'; import RNFetchBlob from 'rn-fetch-blob'; import FormattedText from '@components/formatted_text'; -import {MAX_FILE_COUNT, MAX_FILE_COUNT_WARNING, UPLOAD_FILES, PASTE_FILES} from '@constants/post_draft'; +import {MAX_FILE_COUNT_WARNING, UPLOAD_FILES, PASTE_FILES} from '@constants/post_draft'; import EventEmitter from '@mm-redux/utils/event_emitter'; import {getFormattedFileSize} from '@mm-redux/utils/file_utils'; import {openGalleryAtIndex} from '@utils/gallery'; @@ -39,6 +39,7 @@ export default class Uploads extends PureComponent { handleRemoveLastFile: PropTypes.func.isRequired, initUploadFiles: PropTypes.func.isRequired, maxFileSize: PropTypes.number.isRequired, + maxFileCount: PropTypes.number.isRequired, rootId: PropTypes.string, screenId: PropTypes.string, theme: PropTypes.object.isRequired, @@ -92,7 +93,7 @@ export default class Uploads extends PureComponent { this.showOrHideContainer(); } - if (prevProps.files.length === MAX_FILE_COUNT && this.state.showFileMaxWarning) { + if (prevProps.files.length === this.props.maxFileCount && this.state.showFileMaxWarning) { this.hideError(); } } @@ -168,8 +169,8 @@ export default class Uploads extends PureComponent { return; } - const {canUploadFiles, maxFileSize} = this.props; - const availableCount = MAX_FILE_COUNT - this.props.files.length; + const {canUploadFiles, maxFileSize, maxFileCount} = this.props; + const availableCount = maxFileCount - this.props.files.length; if (!canUploadFiles) { this.handleUploadDisabled(); @@ -336,7 +337,10 @@ export default class Uploads extends PureComponent { )} {Boolean(fileSizeWarning) && diff --git a/app/constants/post_draft.js b/app/constants/post_draft.js index 855dc5044..d600a5e9f 100644 --- a/app/constants/post_draft.js +++ b/app/constants/post_draft.js @@ -8,7 +8,6 @@ export const ICON_SIZE = 24; export const INSERT_TO_COMMENT = 'insert_to_comment'; export const INSERT_TO_DRAFT = 'insert_to_draft'; export const IS_REACTION_REGEX = /(^\+:([^:\s]*):)$/i; -export const MAX_FILE_COUNT = 5; export const MAX_MESSAGE_LENGTH_FALLBACK = 4000; export const TYPING_VISIBLE = 'typingVisible'; export const TYPING_HEIGHT = 18; diff --git a/app/utils/file.js b/app/utils/file.js index 8dcb3ac9d..177558ae6 100644 --- a/app/utils/file.js +++ b/app/utils/file.js @@ -6,6 +6,7 @@ import {Platform} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import {DeviceTypes} from '@constants'; +import {isMinimumServerVersion} from '@mm-redux/utils/helpers'; const EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/; const CONTENT_DISPOSITION_REGEXP = /inline;filename=".*\.([a-z]+)";/i; @@ -163,6 +164,10 @@ export const getAllowedServerMaxFileSize = (config) => { return config && config.MaxFileSize ? parseInt(config.MaxFileSize, 10) : DEFAULT_SERVER_MAX_FILE_SIZE; }; +export const getMaxFileCount = (serverVersion) => { + return isMinimumServerVersion(serverVersion, 6, 0) ? 10 : 5; +}; + export const isGif = (file) => { let mime = file?.mime_type || file?.type || ''; if (mime && mime.includes(';')) { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 072e50b92..6bc60a6ba 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -350,7 +350,7 @@ "mobile.file_upload.disabled": "File uploads from mobile are disabled. Please contact your System Admin for more details.", "mobile.file_upload.disabled2": "File uploads from mobile are disabled.", "mobile.file_upload.library": "Photo Library", - "mobile.file_upload.max_warning": "Uploads limited to 5 files maximum.", + "mobile.file_upload.max_warning": "Uploads limited to {count} files maximum.", "mobile.file_upload.unsupportedMimeType": "Only BMP, JPG or PNG images may be used for profile pictures.", "mobile.file_upload.video": "Video Library", "mobile.files_paste.error_description": "An error occurred while pasting the file(s). Please try again.", diff --git a/share_extension/screens/share.tsx b/share_extension/screens/share.tsx index c858d674e..5462168c5 100644 --- a/share_extension/screens/share.tsx +++ b/share_extension/screens/share.tsx @@ -9,10 +9,10 @@ import {Alert, BackHandler, NativeModules, StyleSheet, View} from 'react-native' import {useSelector} from 'react-redux'; import {Client4} from '@client/rest'; -import {MAX_FILE_COUNT, MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; +import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; import {Preferences} from '@mm-redux/constants'; import {getCurrentChannel} from '@mm-redux/selectors/entities/channels'; -import {getConfig, canUploadFilesOnMobile} from '@mm-redux/selectors/entities/general'; +import {getConfig, getServerVersion, canUploadFilesOnMobile} from '@mm-redux/selectors/entities/general'; import {getCurrentTeam} from '@mm-redux/selectors/entities/teams'; import {getCurrentUserId} from '@mm-redux/selectors/entities/users'; import {Channel} from '@mm-redux/types/channels'; @@ -23,7 +23,7 @@ import CloseHeaderButton from '@share/components/close_header_button'; import PostHeaderButton, {SHARE_EXTENSION_POST_EVENT} from '@share/components/post_header_button'; import TeamButton from '@share/components/team_button'; import {isAuthorized, getErrorElement, getSharedItems, permissionEnabled} from '@share/utils'; -import {getAllowedServerMaxFileSize} from '@utils/file'; +import {getAllowedServerMaxFileSize, getMaxFileCount} from '@utils/file'; import {changeOpacity} from '@utils/theme'; import type {Team} from '@mm-redux/types/teams'; @@ -60,11 +60,12 @@ const Share = ({intl}: ShareProps) => { const [team, setTeam] = useState(currentTeam); const [channel, setChannel] = useState(currentChannel); const maxFileSize = getAllowedServerMaxFileSize(config); + const maxFileCount = getMaxFileCount(useSelector(getServerVersion)); const showPostButton = (error?: string, text?: string, extensionFiles?: Array, calculatedSize?: number) => { const files = extensionFiles || state.files; const totalSize = calculatedSize || state.totalSize; - const filesOK = files.length ? files.length <= MAX_FILE_COUNT : false; + const filesOK = files.length ? files.length <= maxFileCount : false; const sizeOK = totalSize ? totalSize <= maxFileSize : false; if ((!error && ((filesOK && sizeOK) || text?.length)) && team?.id && channel?.id) { @@ -174,7 +175,7 @@ const Share = ({intl}: ShareProps) => { }, []), ); - const errorElement = getErrorElement(state, canUploadFiles, maxFileSize, team, intl); + const errorElement = getErrorElement(state, canUploadFiles, maxFileSize, maxFileCount, team, intl); if (errorElement) { return errorElement; diff --git a/share_extension/utils/index.tsx b/share_extension/utils/index.tsx index 0ad1ed20b..5ea53ffb5 100644 --- a/share_extension/utils/index.tsx +++ b/share_extension/utils/index.tsx @@ -8,7 +8,6 @@ import LocalAuth from 'react-native-local-auth'; import RNFetchBlob from 'rn-fetch-blob'; import Loading from '@components/loading'; -import {MAX_FILE_COUNT} from '@constants/post_draft'; import {getAppCredentials} from '@init/credentials'; import mattermostManaged from '@mattermost-managed'; import {getFormattedFileSize, lookupMimeType} from '@mm-redux/utils/file_utils'; @@ -119,6 +118,7 @@ export function getErrorElement( state: ShareState, canUploadFiles: boolean, maxFileSize: number, + maxFileCount: number, team: Team | undefined | null, intl: typeof intlShape, ): ReactNode | undefined { @@ -157,11 +157,11 @@ export function getErrorElement( return ; } - if (state.files.length > MAX_FILE_COUNT) { + if (state.files.length > maxFileCount) { const fileCount = intl.formatMessage({ id: 'mobile.extension.file_limit', - defaultMessage: 'Sharing is limited to a maximum of 5 files.', - }); + defaultMessage: 'Sharing is limited to a maximum of {count} files.', + }, {count: maxFileCount}); return ; }