diff --git a/Makefile b/Makefile
index f8fd16db6..4e1a49a1e 100644
--- a/Makefile
+++ b/Makefile
@@ -88,7 +88,6 @@ start: | pre-run ## Starts the React Native packager server
node ./node_modules/react-native/local-cli/cli.js start; \
else \
echo React Native packager server already running; \
- ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' > server.PID; \
fi
stop: ## Stops the React Native packager server
diff --git a/app/actions/views/file_upload.js b/app/actions/views/file_upload.js
index bf4f29d8b..b2309dbad 100644
--- a/app/actions/views/file_upload.js
+++ b/app/actions/views/file_upload.js
@@ -1,22 +1,15 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import {Platform} from 'react-native';
-import {uploadFile} from 'mattermost-redux/actions/files';
+import {FileTypes} from 'mattermost-redux/action_types';
-import {
- buildFileUploadData,
- encodeHeaderURIStringToUTF8,
- generateId,
-} from 'app/utils/file';
import {ViewTypes} from 'app/constants';
+import {buildFileUploadData, generateId} from 'app/utils/file';
-export function handleUploadFiles(files, rootId) {
- return async (dispatch, getState) => {
+export function initUploadFiles(files, rootId) {
+ return (dispatch, getState) => {
const state = getState();
-
const channelId = state.entities.channels.currentChannelId;
- const formData = new FormData();
const clientIds = [];
files.forEach((file) => {
@@ -27,30 +20,36 @@ export function handleUploadFiles(files, rootId) {
clientId,
localPath: fileData.uri,
name: fileData.name,
- type: fileData.mimeType,
+ type: fileData.type,
extension: fileData.extension,
});
-
- fileData.name = encodeHeaderURIStringToUTF8(fileData.name);
- formData.append('files', fileData);
- formData.append('channel_id', channelId);
- formData.append('client_ids', clientId);
});
- let formBoundary;
- if (Platform.os === 'ios') {
- formBoundary = '--mobile.client.file.upload';
- }
-
dispatch({
type: ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT,
clientIds,
channelId,
rootId,
});
+ };
+}
- const clientIdsArray = clientIds.map((c) => c.clientId);
- await uploadFile(channelId, rootId, clientIdsArray, formData, formBoundary)(dispatch, getState);
+export function uploadFailed(clientIds, channelId, rootId, error) {
+ return {
+ type: FileTypes.UPLOAD_FILES_FAILURE,
+ clientIds,
+ channelId,
+ rootId,
+ error,
+ };
+}
+
+export function uploadComplete(data, channelId, rootId) {
+ return {
+ type: FileTypes.RECEIVED_UPLOAD_FILES,
+ data,
+ channelId,
+ rootId,
};
}
@@ -59,20 +58,6 @@ export function retryFileUpload(file, rootId) {
const state = getState();
const channelId = state.entities.channels.currentChannelId;
- const formData = new FormData();
- const fileData = buildFileUploadData(file);
-
- fileData.uri = file.localPath;
-
- fileData.name = encodeHeaderURIStringToUTF8(fileData.name);
- formData.append('files', fileData);
- formData.append('channel_id', channelId);
- formData.append('client_ids', file.clientId);
-
- let formBoundary;
- if (Platform.os === 'ios') {
- formBoundary = '--mobile.client.file.upload';
- }
dispatch({
type: ViewTypes.RETRY_UPLOAD_FILE_FOR_POST,
@@ -80,8 +65,6 @@ export function retryFileUpload(file, rootId) {
channelId,
rootId,
});
-
- await uploadFile(channelId, rootId, [file.clientId], formData, formBoundary)(dispatch, getState);
};
}
diff --git a/app/components/attachment_button.js b/app/components/attachment_button.js
index bb068746b..7e6fe9bfd 100644
--- a/app/components/attachment_button.js
+++ b/app/components/attachment_button.js
@@ -1,6 +1,6 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {injectIntl, intlShape} from 'react-intl';
+import {intlShape} from 'react-intl';
import {
Alert,
Platform,
@@ -14,12 +14,11 @@ import Permissions from 'react-native-permissions';
import {PermissionTypes} from 'app/constants';
import {changeOpacity} from 'app/utils/theme';
-class AttachmentButton extends PureComponent {
+export default class AttachmentButton extends PureComponent {
static propTypes = {
blurTextBox: PropTypes.func.isRequired,
children: PropTypes.node,
fileCount: PropTypes.number,
- intl: intlShape.isRequired,
maxFileCount: PropTypes.number.isRequired,
navigator: PropTypes.object.isRequired,
onShowFileMaxWarning: PropTypes.func,
@@ -32,8 +31,12 @@ class AttachmentButton extends PureComponent {
maxFileCount: 5,
};
+ static contextTypes = {
+ intl: intlShape.isRequired,
+ };
+
attachFileFromCamera = async () => {
- const {formatMessage} = this.props.intl;
+ const {formatMessage} = this.context.intl;
const options = {
quality: 1.0,
noData: true,
@@ -72,7 +75,7 @@ class AttachmentButton extends PureComponent {
};
attachFileFromLibrary = () => {
- const {formatMessage} = this.props.intl;
+ const {formatMessage} = this.context.intl;
const options = {
quality: 1.0,
noData: true,
@@ -107,7 +110,7 @@ class AttachmentButton extends PureComponent {
};
attachVideoFromLibraryAndroid = () => {
- const {formatMessage} = this.props.intl;
+ const {formatMessage} = this.context.intl;
const options = {
quality: 1.0,
mediaType: 'video',
@@ -140,7 +143,7 @@ class AttachmentButton extends PureComponent {
hasPhotoPermission = async () => {
if (Platform.OS === 'ios') {
- const {formatMessage} = this.props.intl;
+ const {formatMessage} = this.context.intl;
let permissionRequest;
const hasPermissionToStorage = await Permissions.check('photo');
@@ -312,4 +315,3 @@ const style = StyleSheet.create({
},
});
-export default injectIntl(AttachmentButton);
diff --git a/app/components/file_attachment_list/file_attachment_image.js b/app/components/file_attachment_list/file_attachment_image.js
index 82f4d6847..8c4c45dcf 100644
--- a/app/components/file_attachment_list/file_attachment_image.js
+++ b/app/components/file_attachment_list/file_attachment_image.js
@@ -144,13 +144,13 @@ export default class FileAttachmentImage extends PureComponent {
if (this.state.retry === 4) {
source = imageIcon;
+ } else if (file.failed || file.localPath) {
+ source = {uri: file.localPath};
} else if (file.id) {
source = {uri: this.handleGetImageURL()};
- } else if (file.failed) {
- source = {uri: file.localPath};
}
- const isInFetchCache = fetchCache[source.uri];
+ const isInFetchCache = fetchCache[source.uri] || Boolean(file.localPath);
const imageComponentLoaders = {
onError: isInFetchCache ? null : this.handleLoadError,
@@ -164,7 +164,7 @@ export default class FileAttachmentImage extends PureComponent {
let imageStyle = {height, width};
if (imageSize === IMAGE_SIZE.Preview) {
height = 100;
- width = this.calculateNeededWidth(file.height, file.width, height);
+ width = this.calculateNeededWidth(file.height, file.width, height) || 100;
imageStyle = {height, width, position: 'absolute', top: 0, left: 0, borderBottomLeftRadius: 2, borderTopLeftRadius: 2};
}
diff --git a/app/components/file_upload_preview/file_upload_item/file_upload_item.js b/app/components/file_upload_preview/file_upload_item/file_upload_item.js
new file mode 100644
index 000000000..0d4afec23
--- /dev/null
+++ b/app/components/file_upload_preview/file_upload_item/file_upload_item.js
@@ -0,0 +1,290 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {Platform, StyleSheet, Text, View} from 'react-native';
+import RNFetchBlob from 'react-native-fetch-blob';
+import {AnimatedCircularProgress} from 'react-native-circular-progress';
+
+import {Client4} from 'mattermost-redux/client';
+
+import FileAttachmentImage from 'app/components/file_attachment_list/file_attachment_image';
+import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
+import FileUploadRetry from 'app/components/file_upload_preview/file_upload_retry';
+import FileUploadRemove from 'app/components/file_upload_preview/file_upload_remove';
+import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file';
+
+export default class FileUploadItem extends PureComponent {
+ static propTypes = {
+ actions: PropTypes.shape({
+ addFileToFetchCache: PropTypes.func.isRequired,
+ handleRemoveFile: PropTypes.func.isRequired,
+ retryFileUpload: PropTypes.func.isRequired,
+ uploadComplete: PropTypes.func.isRequired,
+ uploadFailed: PropTypes.func.isRequired,
+ }).isRequired,
+ channelId: PropTypes.string.isRequired,
+ fetchCache: PropTypes.object.isRequired,
+ file: PropTypes.object.isRequired,
+ rootId: PropTypes.string,
+ theme: PropTypes.object.isRequired,
+ };
+
+ state = {
+ progress: 0,
+ };
+
+ componentDidMount() {
+ if (this.props.file.loading) {
+ this.uploadFile();
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const {file} = this.props;
+ const {file: nextFile} = nextProps;
+
+ if (file.failed !== nextFile.failed && nextFile.loading) {
+ this.uploadFile();
+ }
+ }
+
+ handleRetryFileUpload = (file) => {
+ if (!file.failed) {
+ return;
+ }
+
+ this.props.actions.retryFileUpload(file, this.props.rootId);
+ };
+
+ handleRemoveFile = (clientId, channelId, rootId) => {
+ const {handleRemoveFile} = this.props.actions;
+ if (this.uploadPromise) {
+ this.uploadPromise.cancel(() => {
+ this.canceled = true;
+ handleRemoveFile(clientId, channelId, rootId);
+ });
+ } else {
+ handleRemoveFile(clientId, channelId, rootId);
+ }
+ };
+
+ handleUploadCompleted = (res) => {
+ const {actions, channelId, file, rootId} = this.props;
+ const response = JSON.parse(res.data);
+ if (res.respInfo.status === 200 || res.respInfo.status === 201) {
+ this.setState({progress: 100}, () => {
+ const data = response.file_infos.map((f) => {
+ return {
+ ...f,
+ clientId: file.clientId,
+ };
+ });
+ actions.uploadComplete(data, channelId, rootId);
+ });
+ } else {
+ actions.uploadFailed([file.clientId], channelId, rootId, response.message);
+ }
+ this.uploadPromise = null;
+ };
+
+ handleUploadError = (error) => {
+ const {actions, channelId, file, rootId} = this.props;
+ if (!this.canceled) {
+ actions.uploadFailed([file.clientId], channelId, rootId, error);
+ }
+ this.uploadPromise = null;
+ };
+
+ handleUploadProgress = (loaded, total) => {
+ this.setState({progress: Math.floor((loaded / total) * 100)});
+ };
+
+ isImageType = () => {
+ const {file} = this.props;
+
+ if (file.has_preview_image || file.mime_type === 'image/gif' ||
+ (file.localPath && file.type && file.type.includes('image'))) {
+ return true;
+ }
+
+ return false;
+ };
+
+ uploadFile = () => {
+ const {channelId, file} = this.props;
+ const fileData = buildFileUploadData(file);
+
+ const headers = {
+ Authorization: `Bearer ${Client4.getToken()}`,
+ 'Content-Type': 'multipart/form-data',
+ };
+
+ const fileInfo = {
+ name: 'files',
+ filename: encodeHeaderURIStringToUTF8(fileData.name),
+ data: RNFetchBlob.wrap(file.localPath.replace('file://', '')),
+ type: fileData.type,
+ };
+
+ const data = [
+ {name: 'channel_id', data: channelId},
+ {name: 'client_ids', data: file.clientId},
+ fileInfo,
+ ];
+
+ Client4.trackEvent('api', 'api_files_upload');
+
+ this.uploadPromise = RNFetchBlob.fetch('POST', Client4.getFilesRoute(), headers, data);
+ this.uploadPromise.uploadProgress(this.handleUploadProgress);
+ this.uploadPromise.then(this.handleUploadCompleted).catch(this.handleUploadError);
+ };
+
+ renderProgress = (fill) => {
+ const realFill = Number(fill.toFixed(0));
+
+ return (
+
+
+
+ {`${realFill}%`}
+
+
+
+ );
+ };
+
+ render() {
+ const {
+ actions,
+ channelId,
+ fetchCache,
+ file,
+ rootId,
+ theme,
+ } = this.props;
+ const {addFileToFetchCache} = actions;
+ const {progress} = this.state;
+ let filePreviewComponent;
+
+ if (this.isImageType()) {
+ filePreviewComponent = (
+
+ );
+ } else {
+ filePreviewComponent = (
+
+ );
+ }
+
+ return (
+
+
+ {filePreviewComponent}
+ {file.failed &&
+
+ }
+ {file.loading && !file.failed &&
+
+
+ {this.renderProgress}
+
+
+ }
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ preview: {
+ justifyContent: 'flex-end',
+ height: 115,
+ width: 115,
+ },
+ previewShadow: {
+ height: 100,
+ width: 100,
+ elevation: 10,
+ ...Platform.select({
+ ios: {
+ backgroundColor: '#fff',
+ shadowColor: '#000',
+ shadowOpacity: 0.5,
+ shadowRadius: 4,
+ shadowOffset: {
+ width: 0,
+ height: 0,
+ },
+ },
+ }),
+ },
+ progressCircle: {
+ alignItems: 'center',
+ height: '100%',
+ justifyContent: 'center',
+ width: '100%',
+ },
+ progressCircleContent: {
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
+ height: 100,
+ justifyContent: 'center',
+ position: 'absolute',
+ width: 100,
+ },
+ progressCirclePercentage: {
+ alignItems: 'center',
+ flex: 1,
+ },
+ progressContent: {
+ alignItems: 'center',
+ height: '100%',
+ justifyContent: 'center',
+ left: 0,
+ position: 'absolute',
+ top: 40,
+ width: '100%',
+ },
+ progressText: {
+ color: 'white',
+ fontSize: 18,
+ },
+});
diff --git a/app/components/file_upload_preview/file_upload_item/index.js b/app/components/file_upload_preview/file_upload_item/index.js
new file mode 100644
index 000000000..f90c0c4f2
--- /dev/null
+++ b/app/components/file_upload_preview/file_upload_item/index.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+
+import {handleRemoveFile, retryFileUpload, uploadComplete, uploadFailed} from 'app/actions/views/file_upload';
+import {addFileToFetchCache} from 'app/actions/views/file_preview';
+
+import FileUploadItem from './file_upload_item';
+
+function mapStateToProps(state) {
+ return {
+ fetchCache: state.views.fetchCache,
+ theme: getTheme(state),
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ addFileToFetchCache,
+ handleRemoveFile,
+ retryFileUpload,
+ uploadComplete,
+ uploadFailed,
+ }, dispatch),
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(FileUploadItem);
diff --git a/app/components/file_upload_preview/file_upload_preview.js b/app/components/file_upload_preview/file_upload_preview.js
index c63c4b8e9..d277f4a38 100644
--- a/app/components/file_upload_preview/file_upload_preview.js
+++ b/app/components/file_upload_preview/file_upload_preview.js
@@ -4,105 +4,39 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
- Platform,
ScrollView,
StyleSheet,
- TouchableOpacity,
View,
} from 'react-native';
-import Icon from 'react-native-vector-icons/Ionicons';
import FormattedText from 'app/components/formatted_text';
-import FileAttachmentImage from 'app/components/file_attachment_list/file_attachment_image';
-import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
+
+import FileUploadItem from './file_upload_item';
export default class FileUploadPreview extends PureComponent {
static propTypes = {
- actions: PropTypes.shape({
- addFileToFetchCache: PropTypes.func.isRequired,
- handleRemoveFile: PropTypes.func.isRequired,
- retryFileUpload: PropTypes.func.isRequired,
- }).isRequired,
channelId: PropTypes.string.isRequired,
channelIsLoading: PropTypes.bool,
createPostRequestStatus: PropTypes.string.isRequired,
deviceHeight: PropTypes.number.isRequired,
- fetchCache: PropTypes.object.isRequired,
files: PropTypes.array.isRequired,
+ filesUploadingForCurrentChannel: PropTypes.bool.isRequired,
inputHeight: PropTypes.number.isRequired,
rootId: PropTypes.string,
- theme: PropTypes.object.isRequired,
- filesUploadingForCurrentChannel: PropTypes.bool.isRequired,
showFileMaxWarning: PropTypes.bool.isRequired,
- };
-
- handleRetryFileUpload = (file) => {
- if (!file.failed) {
- return;
- }
-
- this.props.actions.retryFileUpload(file, this.props.rootId);
+ theme: PropTypes.object.isRequired,
};
buildFilePreviews = () => {
return this.props.files.map((file) => {
- let filePreviewComponent;
- if (file.loading | (file.has_preview_image || file.mime_type === 'image/gif')) {
- filePreviewComponent = (
-
- );
- } else {
- filePreviewComponent = (
-
- );
- }
return (
-
-
- {filePreviewComponent}
- {file.failed &&
- this.handleRetryFileUpload(file)}
- >
-
-
- }
-
- this.props.actions.handleRemoveFile(file.clientId, this.props.channelId, this.props.rootId)}
- >
-
-
-
+ channelId={this.props.channelId}
+ file={file}
+ rootId={this.props.rootId}
+ theme={this.props.theme}
+ />
);
});
};
@@ -151,59 +85,6 @@ const style = StyleSheet.create({
position: 'absolute',
width: '100%',
},
- failed: {
- backgroundColor: 'rgba(0, 0, 0, 0.8)',
- position: 'absolute',
- height: '100%',
- width: '100%',
- alignItems: 'center',
- justifyContent: 'center',
- },
- preview: {
- justifyContent: 'flex-end',
- height: 115,
- width: 115,
- },
- previewShadow: {
- height: 100,
- width: 100,
- elevation: 10,
- ...Platform.select({
- ios: {
- backgroundColor: '#fff',
- shadowColor: '#000',
- shadowOpacity: 0.5,
- shadowRadius: 4,
- shadowOffset: {
- width: 0,
- height: 0,
- },
- },
- }),
- },
- removeButtonIcon: Platform.select({
- ios: {
- marginTop: 2,
- },
- android: {
- marginLeft: 1,
- },
- }),
- removeButtonWrapper: {
- alignItems: 'center',
- justifyContent: 'center',
- position: 'absolute',
- overflow: 'hidden',
- elevation: 11,
- top: 7,
- right: 7,
- width: 24,
- height: 24,
- borderRadius: 12,
- backgroundColor: '#000',
- borderWidth: 1,
- borderColor: '#fff',
- },
scrollView: {
flex: 1,
marginBottom: 10,
diff --git a/app/components/file_upload_preview/file_upload_remove.js b/app/components/file_upload_preview/file_upload_remove.js
new file mode 100644
index 000000000..20c7c4ad7
--- /dev/null
+++ b/app/components/file_upload_preview/file_upload_remove.js
@@ -0,0 +1,64 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {Platform, StyleSheet, TouchableOpacity} from 'react-native';
+import Icon from 'react-native-vector-icons/Ionicons';
+
+export default class FileUploadRemove extends PureComponent {
+ static propTypes = {
+ channelId: PropTypes.string,
+ clientId: PropTypes.string,
+ onPress: PropTypes.func.isRequired,
+ rootId: PropTypes.string,
+ };
+
+ handleOnPress = () => {
+ const {channelId, clientId, onPress, rootId} = this.props;
+
+ onPress(clientId, channelId, rootId);
+ };
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+const style = StyleSheet.create({
+ removeButtonIcon: Platform.select({
+ ios: {
+ marginTop: 2,
+ },
+ android: {
+ marginLeft: 1,
+ },
+ }),
+ removeButtonWrapper: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ position: 'absolute',
+ overflow: 'hidden',
+ elevation: 11,
+ top: 7,
+ right: 7,
+ width: 24,
+ height: 24,
+ borderRadius: 12,
+ backgroundColor: '#000',
+ borderWidth: 1,
+ borderColor: '#fff',
+ },
+});
diff --git a/app/components/file_upload_preview/file_upload_retry.js b/app/components/file_upload_preview/file_upload_retry.js
new file mode 100644
index 000000000..03ac44789
--- /dev/null
+++ b/app/components/file_upload_preview/file_upload_retry.js
@@ -0,0 +1,46 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {StyleSheet, TouchableOpacity} from 'react-native';
+import Icon from 'react-native-vector-icons/Ionicons';
+
+export default class FileUploadRetry extends PureComponent {
+ static propTypes = {
+ file: PropTypes.object.isRequired,
+ onPress: PropTypes.func.isRequired,
+ };
+
+ handleOnPress = () => {
+ const {file, onPress} = this.props;
+
+ onPress(file);
+ };
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+const style = StyleSheet.create({
+ failed: {
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ position: 'absolute',
+ height: '100%',
+ width: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+});
diff --git a/app/components/file_upload_preview/index.js b/app/components/file_upload_preview/index.js
index 42448023e..3e0fd8201 100644
--- a/app/components/file_upload_preview/index.js
+++ b/app/components/file_upload_preview/index.js
@@ -1,34 +1,14 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
-import {createSelector} from 'reselect';
-
-import {handleRemoveFile, retryFileUpload} from 'app/actions/views/file_upload';
-import {addFileToFetchCache} from 'app/actions/views/file_preview';
-import {getDimensions} from 'app/selectors/device';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getDimensions} from 'app/selectors/device';
+import {checkForFileUploadingInChannel} from 'app/selectors/file';
+
import FileUploadPreview from './file_upload_preview';
-const checkForFileUploadingInChannel = createSelector(
- (state, channelId, rootId) => {
- if (rootId) {
- return state.views.thread.drafts[rootId];
- }
-
- return state.views.channel.drafts[channelId];
- },
- (draft) => {
- if (!draft || !draft.files) {
- return false;
- }
-
- return draft.files.some((f) => f.loading);
- }
-);
-
function mapStateToProps(state, ownProps) {
const {deviceHeight} = getDimensions(state);
@@ -36,20 +16,9 @@ function mapStateToProps(state, ownProps) {
channelIsLoading: state.views.channel.loading,
createPostRequestStatus: state.requests.posts.createPost.status,
deviceHeight,
- fetchCache: state.views.fetchCache,
filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, ownProps.channelId, ownProps.rootId),
theme: getTheme(state),
};
}
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- addFileToFetchCache,
- handleRemoveFile,
- retryFileUpload,
- }, dispatch),
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(FileUploadPreview);
+export default connect(mapStateToProps)(FileUploadPreview);
diff --git a/app/components/post_textbox/index.js b/app/components/post_textbox/index.js
index d88b7aa96..7a8d75b40 100644
--- a/app/components/post_textbox/index.js
+++ b/app/components/post_textbox/index.js
@@ -14,7 +14,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {executeCommand} from 'app/actions/views/command';
import {addReactionToLatestPost} from 'app/actions/views/emoji';
import {handlePostDraftChanged, handlePostDraftSelectionChanged} from 'app/actions/views/channel';
-import {handleClearFiles, handleClearFailedFiles, handleRemoveLastFile, handleUploadFiles} from 'app/actions/views/file_upload';
+import {handleClearFiles, handleClearFailedFiles, handleRemoveLastFile, initUploadFiles} from 'app/actions/views/file_upload';
import {handleCommentDraftChanged, handleCommentDraftSelectionChanged} from 'app/actions/views/thread';
import {userTyping} from 'app/actions/views/typing';
import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views';
@@ -62,7 +62,7 @@ function mapDispatchToProps(dispatch) {
handleCommentDraftChanged,
handlePostDraftChanged,
handleRemoveLastFile,
- handleUploadFiles,
+ initUploadFiles,
userTyping,
handlePostDraftSelectionChanged,
handleCommentDraftSelectionChanged,
diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js
index adab21440..a0814ceab 100644
--- a/app/components/post_textbox/post_textbox.js
+++ b/app/components/post_textbox/post_textbox.js
@@ -31,7 +31,7 @@ export default class PostTextbox extends PureComponent {
handleClearFiles: PropTypes.func.isRequired,
handleClearFailedFiles: PropTypes.func.isRequired,
handleRemoveLastFile: PropTypes.func.isRequired,
- handleUploadFiles: PropTypes.func.isRequired,
+ initUploadFiles: PropTypes.func.isRequired,
userTyping: PropTypes.func.isRequired,
handlePostDraftSelectionChanged: PropTypes.func.isRequired,
handleCommentDraftSelectionChanged: PropTypes.func.isRequired,
@@ -260,7 +260,7 @@ export default class PostTextbox extends PureComponent {
};
handleUploadFiles = (images) => {
- this.props.actions.handleUploadFiles(images, this.props.rootId);
+ this.props.actions.initUploadFiles(images, this.props.rootId);
};
renderSendButton = () => {
diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js
index e465c99cd..44e86c13a 100644
--- a/app/reducers/views/channel.js
+++ b/app/reducers/views/channel.js
@@ -114,6 +114,7 @@ function handleReceivedUploadFiles(state, action) {
return {
...file,
localPath: tempFile.localPath,
+ loading: false,
};
}
diff --git a/app/reducers/views/thread.js b/app/reducers/views/thread.js
index d71a9ad62..c9792a55c 100644
--- a/app/reducers/views/thread.js
+++ b/app/reducers/views/thread.js
@@ -118,6 +118,7 @@ function handleReceiveUploadFiles(state, action) {
return {
...file,
localPath: tempFile.localPath,
+ loading: false,
};
}
diff --git a/app/selectors/file.js b/app/selectors/file.js
new file mode 100644
index 000000000..d28350933
--- /dev/null
+++ b/app/selectors/file.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {createSelector} from 'reselect';
+
+export const checkForFileUploadingInChannel = createSelector(
+ (state, channelId, rootId) => {
+ if (rootId) {
+ return state.views.thread.drafts[rootId];
+ }
+
+ return state.views.channel.drafts[channelId];
+ },
+ (draft) => {
+ if (!draft || !draft.files) {
+ return false;
+ }
+
+ return draft.files.some((f) => f.loading);
+ }
+);
diff --git a/app/utils/file.js b/app/utils/file.js
index 895844d75..03f4f633e 100644
--- a/app/utils/file.js
+++ b/app/utils/file.js
@@ -59,8 +59,8 @@ export async function deleteFileCache() {
export function buildFileUploadData(file) {
const re = /heic/i;
const uri = file.uri;
- let name = file.fileName || file.path || file.uri;
- let mimeType = lookupMimeType(name);
+ let name = file.fileName || file.name || file.path || file.uri;
+ let mimeType = lookupMimeType(name.toLowerCase());
let extension = name.split('.').pop().replace('.', '');
if (re.test(extension)) {
diff --git a/package.json b/package.json
index 86ad3735d..49fb0be93 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"react-native-drawer": "2.5.0",
"react-native-exception-handler": "2.7.1",
"react-native-fast-image": "4.0.0",
- "react-native-fetch-blob": "0.10.8",
+ "react-native-fetch-blob": "enahum/react-native-fetch-blob.git",
"react-native-image-picker": "0.26.7",
"react-native-keyboard-aware-scroll-view": "0.5.0",
"react-native-linear-gradient": "2.4.0",
diff --git a/yarn.lock b/yarn.lock
index 90912f968..8689ae3cc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5916,9 +5916,9 @@ react-native-fast-image@4.0.0:
dependencies:
prop-types "^15.5.10"
-react-native-fetch-blob@0.10.8:
+react-native-fetch-blob@enahum/react-native-fetch-blob.git:
version "0.10.8"
- resolved "https://registry.yarnpkg.com/react-native-fetch-blob/-/react-native-fetch-blob-0.10.8.tgz#4fc256abae0cb5f10e7c41f28c11b3ff330d72a9"
+ resolved "https://codeload.github.com/enahum/react-native-fetch-blob/tar.gz/75d5abfa1886665d7eaa947e70b9297b981f0983"
dependencies:
base-64 "0.1.0"
glob "7.0.6"