diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index d5ba2d273..d436fdde8 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -2014,7 +2014,9 @@
"mobile.error_handler.title": "Unexpected error occurred",
"mobile.extension.authentication_required": "Authentication required: Please first login using the app.",
"mobile.extension.file_limit": "Sharing is limited to a maximum of 5 files.",
+ "mobile.extension.max_file_size": "File attachments shared in Mattermost must be less than 20 Mb.",
"mobile.extension.permission": "Mattermost needs access to the device storage to share files.",
+ "mobile.extension.posting": "Posting...",
"mobile.extension.title": "Share in Mattermost",
"mobile.file_upload.camera": "Take Photo or Video",
"mobile.file_upload.library": "Photo Library",
diff --git a/ios/MattermostShare/PerformRequests.h b/ios/MattermostShare/PerformRequests.h
index 17edcfa3f..98b91d150 100644
--- a/ios/MattermostShare/PerformRequests.h
+++ b/ios/MattermostShare/PerformRequests.h
@@ -17,12 +17,14 @@
@property (nonatomic, strong) NSString *serverUrl;
@property (nonatomic, strong) NSString *token;
+@property (nonatomic, strong) NSExtensionContext *extensionContext;
@property NSUserDefaults *bucket;
- (id) initWithPost:(NSDictionary *) post
withFiles:(NSArray *) files
forRequestId:(NSString *)requestId
- inAppGroupId:(NSString *) appGroupId;
+ inAppGroupId:(NSString *) appGroupId
+ inContext:(NSExtensionContext *) extensionContext;
-(void)createPost;
@end
diff --git a/ios/MattermostShare/PerformRequests.m b/ios/MattermostShare/PerformRequests.m
index b3d9819b0..4e6d76215 100644
--- a/ios/MattermostShare/PerformRequests.m
+++ b/ios/MattermostShare/PerformRequests.m
@@ -16,13 +16,15 @@ MattermostBucket *mattermostBucket;
- (id) initWithPost:(NSDictionary *) post
withFiles:(NSArray *) files
forRequestId:(NSString *)requestId
- inAppGroupId:(NSString *) appGroupId {
+ inAppGroupId:(NSString *) appGroupId
+ inContext:(NSExtensionContext *) context {
self = [super init];
if (self) {
self.post = post;
self.files = files;
self.appGroupId = appGroupId;
self.requestId = requestId;
+ self.extensionContext = context;
mattermostBucket = [[MattermostBucket alloc] init];
self.bucket = [mattermostBucket bucketByName: appGroupId];
@@ -42,6 +44,8 @@ MattermostBucket *mattermostBucket;
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionDataTask *)task didCompleteWithError:(nullable NSError *)error {
if(error != nil) {
NSLog(@"ERROR %@", [error userInfo]);
+ [self.extensionContext completeRequestReturningItems:nil
+ completionHandler:nil];
}
NSLog(@"invalidating session %@", self.requestId);
[session finishTasksAndInvalidate];
@@ -103,7 +107,7 @@ MattermostBucket *mattermostBucket;
[dataForm appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", POST_BODY_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]];
[uploadRequest setHTTPBody:dataForm];
- NSURLSession *uploadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
+ NSURLSession *uploadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *uploadTask = [uploadSession dataTaskWithRequest:uploadRequest];
NSLog(@"Executing file request");
[uploadTask resume];
@@ -132,6 +136,9 @@ MattermostBucket *mattermostBucket;
NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request];
NSLog(@"Executing post request");
[createTask resume];
+ [self.extensionContext completeRequestReturningItems:nil
+ completionHandler:nil];
+ NSLog(@"Extension closed");
}
- (void) cleanUpTempFiles {
diff --git a/ios/MattermostShare/ShareViewController.m b/ios/MattermostShare/ShareViewController.m
index ed2532f49..b75c9d763 100644
--- a/ios/MattermostShare/ShareViewController.m
+++ b/ios/MattermostShare/ShareViewController.m
@@ -54,13 +54,13 @@ RCT_EXPORT_METHOD(close:(NSDictionary *)data appGroupId:(NSString *)appGroupId)
NSArray *files = [data objectForKey:@"files"];
NSString *requestId = [data objectForKey:@"requestId"];
NSLog(@"Call createPost");
- PerformRequests *request = [[PerformRequests alloc] initWithPost:post withFiles:files forRequestId:requestId inAppGroupId:appGroupId];
+ PerformRequests *request = [[PerformRequests alloc] initWithPost:post withFiles:files forRequestId:requestId inAppGroupId:appGroupId inContext:extensionContext];
[request createPost];
- }
-
- [extensionContext completeRequestReturningItems:nil
+ } else {
+ [extensionContext completeRequestReturningItems:nil
completionHandler:nil];
- NSLog(@"Extension closed");
+ NSLog(@"Extension closed");
+ }
}
RCT_REMAP_METHOD(data,
diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js
index dc45bc000..1ff4db49d 100644
--- a/share_extension/ios/extension_post.js
+++ b/share_extension/ios/extension_post.js
@@ -5,6 +5,7 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
+ ActivityIndicator,
Dimensions,
Image,
NativeModules,
@@ -17,9 +18,10 @@ import {
import IonIcon from 'react-native-vector-icons/Ionicons';
import Video from 'react-native-video';
import LocalAuth from 'react-native-local-auth';
+import RNFetchBlob from 'react-native-fetch-blob';
import {Client4} from 'mattermost-redux/client';
-import {lookupMimeType} from 'mattermost-redux/utils/file_utils';
+import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_utils';
import mattermostBucket from 'app/mattermost_bucket';
import {generateId} from 'app/utils/file';
@@ -42,6 +44,7 @@ import ExtensionTeams from './extension_teams';
const ShareExtension = NativeModules.MattermostShare;
const MAX_INPUT_HEIGHT = 95;
const MAX_MESSAGE_LENGTH = 4000;
+const MAX_FILE_SIZE = 20 * 1024 * 1024;
const extensionSvg = {
csv: ExcelSvg,
@@ -76,7 +79,9 @@ export default class ExtensionPost extends PureComponent {
error: null,
files: [],
isLandscape,
- value: ''
+ totalSize: 0,
+ value: '',
+ sending: false
};
}
@@ -185,6 +190,7 @@ export default class ExtensionPost extends PureComponent {
const text = [];
const urls = [];
const files = [];
+ let totalSize = 0;
for (let i = 0; i < items.length; i++) {
const item = items[i];
@@ -198,14 +204,18 @@ export default class ExtensionPost extends PureComponent {
default: {
const fullPath = item.value;
const filePath = fullPath.replace('file://', '');
+ const fileSize = await RNFetchBlob.fs.stat(filePath);
const filename = fullPath.replace(/^.*[\\/]/, '');
const extension = filename.split('.').pop();
+
+ totalSize += fileSize.size;
files.push({
extension,
filename,
filePath,
fullPath,
mimeType: lookupMimeType(filename.toLowerCase()),
+ size: getFormattedFileSize(fileSize),
type: item.type
});
break;
@@ -221,7 +231,7 @@ export default class ExtensionPost extends PureComponent {
Client4.setUrl(credentials.url);
Client4.setToken(credentials.token);
Client4.setUserId(currentUserId);
- this.setState({channel, currentUserId, files, team, value});
+ this.setState({channel, currentUserId, files, team, value, totalSize});
} catch (error) {
this.setState({error});
}
@@ -244,7 +254,34 @@ export default class ExtensionPost extends PureComponent {
renderBody = (styles) => {
const {formatMessage} = this.context.intl;
const {credentials, theme} = this.props;
- const {error, value} = this.state;
+ const {error, sending, totalSize, value} = this.state;
+
+ if (sending) {
+ return (
+
+
+
+ {formatMessage({
+ id: 'mobile.extension.posting',
+ defaultMessage: 'Posting...'
+ })}
+
+
+ );
+ }
+
+ if (totalSize >= MAX_FILE_SIZE) {
+ return (
+
+
+ {formatMessage({
+ id: 'mobile.extension.max_file_size',
+ defaultMessage: 'File attachments shared in Mattermost must be less than 20 Mb.'
+ })}
+
+
+ );
+ }
if (credentials && !error) {
return (
@@ -293,9 +330,13 @@ export default class ExtensionPost extends PureComponent {
renderChannelButton = (styles) => {
const {formatMessage} = this.context.intl;
const {credentials, theme} = this.props;
- const {channel} = this.state;
+ const {channel, sending} = this.state;
const channelName = channel ? channel.display_name : '';
+ if (sending) {
+ return null;
+ }
+
if (!credentials) {
return null;
}
@@ -406,7 +447,7 @@ export default class ExtensionPost extends PureComponent {
numberOfLines={1}
style={styles.filename}
>
- {file.filename}
+ {`${file.size} - ${file.filename}`}
);
@@ -416,9 +457,13 @@ export default class ExtensionPost extends PureComponent {
renderTeamButton = (styles) => {
const {formatMessage} = this.context.intl;
const {credentials, theme} = this.props;
- const {team} = this.state;
+ const {sending, team} = this.state;
const teamName = team ? team.display_name : '';
+ if (sending) {
+ return null;
+ }
+
if (!credentials) {
return null;
}
@@ -485,6 +530,7 @@ export default class ExtensionPost extends PureComponent {
requestId: generateId()
};
+ this.setState({sending: true});
onClose(data);
} catch (error) {
this.setState({error});
@@ -497,6 +543,7 @@ export default class ExtensionPost extends PureComponent {
render() {
const {credentials, theme} = this.props;
+ const {totalSize, sending} = this.state;
const {formatMessage} = this.context.intl;
const styles = getStyleSheet(theme);
@@ -507,10 +554,10 @@ export default class ExtensionPost extends PureComponent {
>
= MAX_FILE_SIZE || sending) ? null : formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'})}
theme={theme}
/>
{this.renderBody(styles)}
@@ -645,6 +692,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
justifyContent: 'center',
overflow: 'hidden',
width: 38
+ },
+ sendingContainer: {
+ alignItems: 'center',
+ flex: 1,
+ justifyContent: 'center',
+ paddingHorizontal: 15
+ },
+ sendingText: {
+ color: theme.centerChannelColor,
+ fontSize: 16,
+ paddingTop: 10
}
};
});
diff --git a/share_extension/ios/index.js b/share_extension/ios/index.js
index 229390e1f..86105796b 100644
--- a/share_extension/ios/index.js
+++ b/share_extension/ios/index.js
@@ -61,22 +61,7 @@ export default class SharedApp extends PureComponent {
}
onClose = (data) => {
- Animated.parallel([
- Animated.timing(
- this.state.backdropOpacity,
- {
- toValue: 0,
- duration: 250
- }),
- Animated.timing(
- this.state.containerOpacity,
- {
- toValue: 0,
- duration: 250
- })
- ]).start(() => {
- ShareExtension.close(data, Config.AppGroupId);
- });
+ ShareExtension.close(data, Config.AppGroupId);
};
onLayout = (e) => {