iOS Share extension to share files up to 20mb (#1394)
This commit is contained in:
parent
aebea2688b
commit
a3d9eaa385
6 changed files with 87 additions and 33 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue