iOS Share extension to share files up to 20mb (#1394)

This commit is contained in:
enahum 2018-01-30 13:27:20 -03:00 committed by GitHub
parent aebea2688b
commit a3d9eaa385
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 33 deletions

View file

@ -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",

View file

@ -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

View file

@ -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 {

View file

@ -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,

View file

@ -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 (
<View style={styles.sendingContainer}>
<ActivityIndicator/>
<Text style={styles.sendingText}>
{formatMessage({
id: 'mobile.extension.posting',
defaultMessage: 'Posting...'
})}
</Text>
</View>
);
}
if (totalSize >= MAX_FILE_SIZE) {
return (
<View style={styles.unauthenticatedContainer}>
<Text style={styles.unauthenticated}>
{formatMessage({
id: 'mobile.extension.max_file_size',
defaultMessage: 'File attachments shared in Mattermost must be less than 20 Mb.'
})}
</Text>
</View>
);
}
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}`}
</Text>
</View>
);
@ -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 {
>
<ExtensionNavBar
authenticated={Boolean(credentials)}
leftButtonTitle={formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'})}
leftButtonTitle={sending ? null : formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'})}
onLeftButtonPress={this.handleCancel}
onRightButtonPress={this.sendMessage}
rightButtonTitle={formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'})}
rightButtonTitle={(totalSize >= 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
}
};
});

View file

@ -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) => {