diff --git a/android/app/build.gradle b/android/app/build.gradle
index 2fbb0e576..3b7934374 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -106,7 +106,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 23
- versionCode 81
+ versionCode 82
versionName "1.6.0"
multiDexEnabled true
ndk {
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 57cd38df8..b82c71425 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -2022,7 +2022,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/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index bdfdae302..9eb4c05b6 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -2400,7 +2400,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 81;
+ CURRENT_PROJECT_VERSION = 82;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2449,7 +2449,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 81;
+ CURRENT_PROJECT_VERSION = 82;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist
index 0e7958018..1d95b85ae 100644
--- a/ios/Mattermost/Info.plist
+++ b/ios/Mattermost/Info.plist
@@ -34,7 +34,7 @@
CFBundleVersion
- 81
+ 82
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist
index 4e9e402ca..fe9bf0879 100644
--- a/ios/MattermostShare/Info.plist
+++ b/ios/MattermostShare/Info.plist
@@ -23,7 +23,7 @@
CFBundleShortVersionString
1.6.0
CFBundleVersion
- 81
+ 82
NSAppTransportSecurity
NSAllowsArbitraryLoads
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/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist
index e8fa092cf..2221397f4 100644
--- a/ios/MattermostTests/Info.plist
+++ b/ios/MattermostTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 81
+ 82
diff --git a/share_extension/ios/extension_channels.js b/share_extension/ios/extension_channels.js
index 13ad5fb94..fcd1e911c 100644
--- a/share_extension/ios/extension_channels.js
+++ b/share_extension/ios/extension_channels.js
@@ -119,28 +119,51 @@ export default class ExtensionChannels extends PureComponent {
const {currentUserId, teamId} = this.props;
const channelsMap = {};
+ // get the channels for the specified team
const myChannels = await Client4.getMyChannels(teamId);
- const myPreferences = await Client4.getMyPreferences();
- const usersInDms = myPreferences.filter((pref) => pref.category === Preferences.CATEGORY_DIRECT_CHANNEL_SHOW).map((pref) => pref.name);
+ // filter channels that are direct and group messages
const dms = myChannels.filter((channel) => {
- const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
- return (channel.type === General.DM_CHANNEL && usersInDms.includes(teammateId)) || channel.type === General.GM_CHANNEL;
+ return channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL;
});
- const dmProfiles = await Client4.getProfilesByIds(usersInDms);
+ const usersInDms = dms.filter((channel) => {
+ return channel.type === General.DM_CHANNEL;
+ }).map((channel) => {
+ return getUserIdFromChannelName(currentUserId, channel.name);
+ }).reduce((acc, teammateId) => {
+ if (!acc.includes(teammateId)) {
+ acc.push(teammateId);
+ }
+
+ return acc;
+ }, []);
+
+ // get the user ids that belong to DMs & GMs
+ let dmProfiles;
+ if (usersInDms.length) {
+ try {
+ dmProfiles = await Client4.getProfilesByIds(usersInDms);
+ } catch (e) {
+ // do nothing
+ }
+ }
for (let i = 0; i < dms.length; i++) {
const channel = dms[i];
- if (channel.type === General.DM_CHANNEL) {
+ if (channel.type === General.DM_CHANNEL && dmProfiles) {
const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
const profile = dmProfiles.find((p) => p.id === teammateId);
channelsMap[channel.id] = displayUsername(profile, Preferences.DISPLAY_PREFER_FULL_NAME);
} else if (channel.type === General.GM_CHANNEL) {
- const members = await Client4.getChannelMembers(channel.id, 0, General.MAX_USERS_IN_GM);
- const userIds = members.filter((m) => m.user_id !== currentUserId).map((m) => m.user_id);
- const gmProfiles = await Client4.getProfilesByIds(userIds);
- channelsMap[channel.id] = this.getGroupDisplayNameFromUserIds(userIds, gmProfiles);
+ try {
+ const members = await Client4.getChannelMembers(channel.id, 0, General.MAX_USERS_IN_GM);
+ const userIds = members.filter((m) => m.user_id !== currentUserId).map((m) => m.user_id);
+ const gmProfiles = await Client4.getProfilesByIds(userIds);
+ channelsMap[channel.id] = this.getGroupDisplayNameFromUserIds(userIds, gmProfiles);
+ } catch (e) {
+ // do nothing
+ }
}
}
diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js
index 2ce26f2b0..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;
@@ -220,7 +230,8 @@ export default class ExtensionPost extends PureComponent {
Client4.setUrl(credentials.url);
Client4.setToken(credentials.token);
- this.setState({channel, currentUserId, files, team, value});
+ Client4.setUserId(currentUserId);
+ this.setState({channel, currentUserId, files, team, value, totalSize});
} catch (error) {
this.setState({error});
}
@@ -243,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 (
@@ -292,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;
}
@@ -405,7 +447,7 @@ export default class ExtensionPost extends PureComponent {
numberOfLines={1}
style={styles.filename}
>
- {file.filename}
+ {`${file.size} - ${file.filename}`}
);
@@ -415,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;
}
@@ -484,6 +530,7 @@ export default class ExtensionPost extends PureComponent {
requestId: generateId()
};
+ this.setState({sending: true});
onClose(data);
} catch (error) {
this.setState({error});
@@ -496,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);
@@ -506,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)}
@@ -644,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/extension_teams.js b/share_extension/ios/extension_teams.js
index c9c841b25..0a1479d4b 100644
--- a/share_extension/ios/extension_teams.js
+++ b/share_extension/ios/extension_teams.js
@@ -57,14 +57,15 @@ export default class ExtensionTeams extends PureComponent {
const myMembers = await Client4.getMyTeamMembers();
const myTeams = [];
- teams.forEach(async (team) => {
+ for (let i = 0; i < teams.length; i++) {
+ const team = teams[i];
const belong = myMembers.find((member) => member.team_id === team.id);
if (belong) {
const channels = await Client4.getMyChannels(team.id);
defaultChannels[team.id] = channels.find((channel) => channel.name === General.DEFAULT_CHANNEL);
myTeams.push(team);
}
- });
+ }
this.setState({
defaultChannels,
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) => {