diff --git a/android/app/build.gradle b/android/app/build.gradle
index 3b7934374..e3abf4de3 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 82
+ versionCode 84
versionName "1.6.0"
multiDexEnabled true
ndk {
diff --git a/app/mattermost.js b/app/mattermost.js
index d0ef66517..5345ac694 100644
--- a/app/mattermost.js
+++ b/app/mattermost.js
@@ -199,7 +199,7 @@ export default class Mattermost {
const intl = this.getIntl();
if (isSecured) {
try {
- mattermostBucket.set('emm', vendor, LocalConfig.AppGroupId);
+ mattermostBucket.setPreference('emm', vendor, LocalConfig.AppGroupId);
await mattermostManaged.authenticate({
reason: intl.formatMessage({
id: 'mobile.managed.secured_by',
diff --git a/app/mattermost_bucket/index.js b/app/mattermost_bucket/index.js
index a858cc00b..70da1d49c 100644
--- a/app/mattermost_bucket/index.js
+++ b/app/mattermost_bucket/index.js
@@ -7,14 +7,14 @@ import {NativeModules, Platform} from 'react-native';
const MattermostBucket = Platform.OS === 'ios' ? NativeModules.MattermostBucket : null;
export default {
- set: (key, value, groupName) => {
+ setPreference: (key, value, groupName) => {
if (MattermostBucket) {
- MattermostBucket.set(key, value, groupName);
+ MattermostBucket.setPreference(key, value, groupName);
}
},
- get: async (key, groupName) => {
+ getPreference: async (key, groupName) => {
if (MattermostBucket) {
- const value = await MattermostBucket.get(key, groupName);
+ const value = await MattermostBucket.getPreference(key, groupName);
if (value) {
try {
return JSON.parse(value);
@@ -26,9 +26,33 @@ export default {
return null;
},
- remove: (key, groupName) => {
+ removePreference: (key, groupName) => {
if (MattermostBucket) {
- MattermostBucket.remove(key, groupName);
+ MattermostBucket.removePreference(key, groupName);
+ }
+ },
+ writeToFile: (fileName, content, groupName) => {
+ if (MattermostBucket) {
+ MattermostBucket.writeToFile(fileName, content, groupName);
+ }
+ },
+ readFromFile: async (fileName, groupName) => {
+ if (MattermostBucket) {
+ const value = await MattermostBucket.readFromFile(fileName, groupName);
+ if (value) {
+ try {
+ return JSON.parse(value);
+ } catch (e) {
+ return value;
+ }
+ }
+ }
+
+ return null;
+ },
+ removeFile: (fileName, groupName) => {
+ if (MattermostBucket) {
+ MattermostBucket.removeFile(fileName, groupName);
}
}
};
diff --git a/app/store/index.js b/app/store/index.js
index daef76094..9e61ec17a 100644
--- a/app/store/index.js
+++ b/app/store/index.js
@@ -2,7 +2,7 @@
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
-import {AsyncStorage} from 'react-native';
+import {AsyncStorage, Platform} from 'react-native';
import {createBlacklistFilter} from 'redux-persist-transform-filter';
import {createTransform, persistStore} from 'redux-persist';
@@ -13,10 +13,14 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {NavigationTypes, ViewTypes} from 'app/constants';
import appReducer from 'app/reducers';
+import {throttle} from 'app/utils/general';
import networkConnectionListener from 'app/utils/network';
import {createSentryMiddleware} from 'app/utils/sentry/middleware';
import {promiseTimeout} from 'app/utils/promise_timeout';
+import mattermostBucket from 'app/mattermost_bucket';
+import Config from 'assets/config';
+
import {messageRetention, shareExtensionData} from './middleware';
import {transformSet} from './utils';
@@ -130,6 +134,16 @@ export default function configureAppStore(initialState) {
let purging = false;
+ // for iOS write the entities to a shared file
+ if (Platform.OS === 'ios') {
+ store.subscribe(throttle(() => {
+ const state = store.getState();
+ if (state.entities) {
+ mattermostBucket.writeToFile('entities', JSON.stringify(state.entities), Config.AppGroupId);
+ }
+ }, 1000));
+ }
+
// check to see if the logout request was successful
store.subscribe(async () => {
const state = store.getState();
diff --git a/app/store/middleware.js b/app/store/middleware.js
index c67ca9626..3969a6db0 100644
--- a/app/store/middleware.js
+++ b/app/store/middleware.js
@@ -3,11 +3,7 @@
import DeviceInfo from 'react-native-device-info';
-import {ChannelTypes, GeneralTypes, TeamTypes, UserTypes} from 'mattermost-redux/action_types';
-import {General} from 'mattermost-redux/constants';
-import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
-import {getUserIdFromChannelName, getGroupDisplayNameFromUserIds} from 'mattermost-redux/utils/channel_utils';
-import {displayUsername} from 'mattermost-redux/utils/user_utils';
+import {UserTypes} from 'mattermost-redux/action_types';
import {ViewTypes} from 'app/constants';
import initialState from 'app/initial_state';
@@ -314,92 +310,15 @@ function cleanupState(action, keepCurrent = false) {
};
}
-export function shareExtensionData(store) {
+export function shareExtensionData() {
return (next) => (action) => {
// allow other middleware to do their things
const nextAction = next(action);
switch (action.type) {
- case 'persist/REHYDRATE': {
- const {entities} = action.payload;
- if (entities) {
- if (entities.general && entities.general.credentials && entities.general.credentials.token) {
- mattermostBucket.set('credentials', JSON.stringify(entities.general.credentials), Config.AppGroupId);
- }
-
- if (entities.teams) {
- const {currentTeamId, teams} = entities.teams;
- if (currentTeamId) {
- const team = teams[currentTeamId];
- const teamToSave = {
- id: currentTeamId,
- name: team.name,
- display_name: team.display_name
- };
- mattermostBucket.set('selectedTeam', JSON.stringify(teamToSave), Config.AppGroupId);
- }
- }
-
- if (entities.users) {
- const {currentUserId} = entities.users;
- if (currentUserId) {
- mattermostBucket.set('currentUserId', currentUserId, Config.AppGroupId);
- }
- }
- }
- break;
- }
- case GeneralTypes.RECEIVED_APP_CREDENTIALS:
- mattermostBucket.set('credentials', JSON.stringify(action.data), Config.AppGroupId);
- break;
- case ChannelTypes.SELECT_CHANNEL: {
- const state = store.getState();
- const {channels} = state.entities.channels;
- const {currentUserId, profiles, profilesInChannel} = state.entities.users;
- const channel = {...channels[action.data]};
- if (channel.type === General.DM_CHANNEL) {
- const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
- channel.display_name = displayUsername(profiles[teammateId], getTeammateNameDisplaySetting(state));
- } else if (channel.type === General.GM_CHANNEL) {
- channel.display_name = getGroupDisplayNameFromUserIds(
- profilesInChannel[channel.id],
- profiles,
- currentUserId,
- getTeammateNameDisplaySetting(state)
- );
- }
-
- const channelToSave = {
- id: channel.id,
- name: channel.name,
- display_name: channel.display_name,
- type: channel.type
- };
- mattermostBucket.set('selectedChannel', JSON.stringify(channelToSave), Config.AppGroupId);
- break;
- }
- case 'BATCH_SELECT_TEAM': {
- const teamData = action.payload.find((data) => data.type === TeamTypes.SELECT_TEAM);
- if (teamData && teamData.data) {
- const team = store.getState().entities.teams.teams[teamData.data];
- const teamToSave = {
- id: team.id,
- name: team.name,
- display_name: team.display_name
- };
- mattermostBucket.set('selectedTeam', JSON.stringify(teamToSave), Config.AppGroupId);
- }
- break;
- }
- case UserTypes.RECEIVED_ME:
- mattermostBucket.set('currentUserId', action.data.id, Config.AppGroupId);
- break;
case UserTypes.LOGOUT_SUCCESS:
- mattermostBucket.remove('credentials', Config.AppGroupId);
- mattermostBucket.remove('selectedChannel', Config.AppGroupId);
- mattermostBucket.remove('selectedTeam', Config.AppGroupId);
- mattermostBucket.remove('currentUserId', Config.AppGroupId);
- mattermostBucket.remove('emm', Config.AppGroupId);
+ mattermostBucket.removePreference('emm', Config.AppGroupId);
+ mattermostBucket.removeFile('entities', Config.AppGroupId);
break;
}
return nextAction;
diff --git a/app/utils/general.js b/app/utils/general.js
index b7df87a9f..aa81a3e5a 100644
--- a/app/utils/general.js
+++ b/app/utils/general.js
@@ -35,3 +35,25 @@ export function alertErrorIfInvalidPermissions(result) {
export function emptyFunction() {
return;
}
+
+export function throttle(fn, limit, ...args) {
+ let inThrottle;
+ let lastFunc;
+ let lastRan;
+
+ return () => {
+ if (inThrottle) {
+ clearTimeout(lastFunc);
+ lastFunc = setTimeout(() => {
+ if ((Date.now() - lastRan) >= limit) {
+ Reflect.apply(fn, this, args);
+ lastRan = Date.now();
+ }
+ }, limit - (Date.now() - lastRan));
+ } else {
+ Reflect.apply(fn, this, args);
+ lastRan = Date.now();
+ inThrottle = true;
+ }
+ };
+}
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 9eb4c05b6..d7733d519 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 = 82;
+ CURRENT_PROJECT_VERSION = 84;
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 = 82;
+ CURRENT_PROJECT_VERSION = 84;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist
index 1d95b85ae..31d7b95da 100644
--- a/ios/Mattermost/Info.plist
+++ b/ios/Mattermost/Info.plist
@@ -34,7 +34,7 @@
CFBundleVersion
- 82
+ 84
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/MattermostBucket.h b/ios/MattermostBucket.h
index 1e43783f5..79bda4b8a 100644
--- a/ios/MattermostBucket.h
+++ b/ios/MattermostBucket.h
@@ -3,4 +3,5 @@
@interface MattermostBucket : NSObject
- (NSUserDefaults *)bucketByName:(NSString*)name;
+-(NSString *)readFromFile:(NSString *)fileName appGroupId:(NSString *)appGroupId;
@end
diff --git a/ios/MattermostBucket.m b/ios/MattermostBucket.m
index 8772850b0..13358e70b 100644
--- a/ios/MattermostBucket.m
+++ b/ios/MattermostBucket.m
@@ -10,32 +10,25 @@
@implementation MattermostBucket
-- (NSUserDefaults *)bucketByName:(NSString*)name {
- return [[NSUserDefaults alloc] initWithSuiteName: name];
-}
-
-+ (BOOL)requiresMainQueueSetup
++(BOOL)requiresMainQueueSetup
{
return YES;
}
RCT_EXPORT_MODULE();
-RCT_EXPORT_METHOD(set:(NSString *) key
- value:(NSString *) value
- bucketName:(NSString*) bucketName)
-{
- NSUserDefaults* bucket = [self bucketByName: bucketName];
- [bucket setObject:value forKey:key];
+RCT_EXPORT_METHOD(writeToFile:(NSString *)fileName
+ content:(NSString *)content
+ bucketName:(NSString *)bucketName) {
+ [self writeToFile:fileName content:content appGroupId:bucketName];
}
-RCT_EXPORT_METHOD(get:(NSString *) key
- bucketName:(NSString*) bucketName
+RCT_EXPORT_METHOD(readFromFile:(NSString *)fileName
+ bucketName:(NSString*)bucketName
getWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
- NSUserDefaults* bucket = [self bucketByName: bucketName];
- id value = [bucket objectForKey:key];
+ id value = [self readFromFile:fileName appGroupId:bucketName];
if (value == nil) {
value = [NSNull null];
@@ -44,11 +37,86 @@ RCT_EXPORT_METHOD(get:(NSString *) key
resolve(value);
}
-RCT_EXPORT_METHOD(remove:(NSString *) key
- bucketName:(NSString*) bucketName)
+RCT_EXPORT_METHOD(removeFile:(NSString *)fileName
+ bucketName:(NSString*)bucketName)
{
- NSUserDefaults* bucket = [self bucketByName: bucketName];
- [bucket removeObjectForKey: key];
+ [self removeFile:fileName appGroupId:bucketName];
}
+RCT_EXPORT_METHOD(setPreference:(NSString *) key
+ value:(NSString *) value
+ bucketName:(NSString*) bucketName)
+{
+ [self setPreference:key value:value appGroupId:bucketName];
+}
+
+RCT_EXPORT_METHOD(getPreference:(NSString *) key
+ bucketName:(NSString*) bucketName
+ getWithResolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ id value = [self getPreference:key appGroupId:bucketName];
+
+ if (value == nil) {
+ value = [NSNull null];
+ }
+
+ resolve(value);
+}
+
+RCT_EXPORT_METHOD(removePreference:(NSString *) key
+ bucketName:(NSString*) bucketName)
+{
+ [self removePreference:key appGroupId:bucketName];
+}
+
+-(NSString *)fileUrl:(NSString *)fileName appGroupdId:(NSString *)appGroupId {
+ NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId];
+ return [NSString stringWithFormat:@"%@/%@", fileManagerURL.path, fileName];
+}
+
+-(void)writeToFile:(NSString *)fileName content:(NSString *)content appGroupId:(NSString *)appGroupId {
+ NSString *filePath = [self fileUrl:fileName appGroupdId:appGroupId];
+ NSFileManager *fileManager= [NSFileManager defaultManager];
+ if(![fileManager fileExistsAtPath:filePath]) {
+ [fileManager createFileAtPath:filePath contents:nil attributes:nil];
+ }
+ [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
+}
+
+-(NSString *)readFromFile:(NSString *)fileName appGroupId:(NSString *)appGroupId {
+ NSString *filePath = [self fileUrl:fileName appGroupdId:appGroupId];
+ NSFileManager *fileManager= [NSFileManager defaultManager];
+ if(![fileManager fileExistsAtPath:filePath]) {
+ return nil;
+ }
+ return [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
+}
+
+-(void)removeFile:(NSString *)fileName appGroupId:(NSString *)appGroupId {
+ NSString *filePath = [self fileUrl:fileName appGroupdId:appGroupId];
+ NSFileManager *fileManager= [NSFileManager defaultManager];
+ if([fileManager isDeletableFileAtPath:filePath]) {
+ [fileManager removeItemAtPath:filePath error:nil];
+ }
+}
+
+-(NSUserDefaults *)bucketByName:(NSString*)name {
+ return [[NSUserDefaults alloc] initWithSuiteName: name];
+}
+
+-(void) setPreference:(NSString *)key value:(NSString *) value appGroupId:(NSString*)appGroupId {
+ NSUserDefaults* bucket = [self bucketByName: appGroupId];
+ [bucket setObject:value forKey:key];
+}
+
+-(id) getPreference:(NSString *)key appGroupId:(NSString*)appGroupId {
+ NSUserDefaults* bucket = [self bucketByName: appGroupId];
+ return [bucket objectForKey:key];
+}
+
+-(void) removePreference:(NSString *)key appGroupId:(NSString*)appGroupId {
+ NSUserDefaults* bucket = [self bucketByName: appGroupId];
+ [bucket removeObjectForKey: key];
+}
@end
diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist
index fe9bf0879..d85f93606 100644
--- a/ios/MattermostShare/Info.plist
+++ b/ios/MattermostShare/Info.plist
@@ -23,7 +23,7 @@
CFBundleShortVersionString
1.6.0
CFBundleVersion
- 82
+ 84
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/ios/MattermostShare/PerformRequests.h b/ios/MattermostShare/PerformRequests.h
index 98b91d150..471c90213 100644
--- a/ios/MattermostShare/PerformRequests.h
+++ b/ios/MattermostShare/PerformRequests.h
@@ -7,6 +7,7 @@
//
#import
+#import "MattermostBucket.h"
@interface PerformRequests : NSObject
@property (nonatomic, strong) NSString *appGroupId;
@@ -18,7 +19,7 @@
@property (nonatomic, strong) NSString *serverUrl;
@property (nonatomic, strong) NSString *token;
@property (nonatomic, strong) NSExtensionContext *extensionContext;
-@property NSUserDefaults *bucket;
+@property MattermostBucket *bucket;
- (id) initWithPost:(NSDictionary *) post
withFiles:(NSArray *) files
diff --git a/ios/MattermostShare/PerformRequests.m b/ios/MattermostShare/PerformRequests.m
index 4e6d76215..8cc3639b9 100644
--- a/ios/MattermostShare/PerformRequests.m
+++ b/ios/MattermostShare/PerformRequests.m
@@ -11,7 +11,6 @@
#import "ShareViewController.h"
@implementation PerformRequests
-MattermostBucket *mattermostBucket;
- (id) initWithPost:(NSDictionary *) post
withFiles:(NSArray *) files
@@ -26,17 +25,17 @@ MattermostBucket *mattermostBucket;
self.requestId = requestId;
self.extensionContext = context;
- mattermostBucket = [[MattermostBucket alloc] init];
- self.bucket = [mattermostBucket bucketByName: appGroupId];
+ self.bucket = [[MattermostBucket alloc] init];
[self setCredentials];
}
return self;
}
-(void)setCredentials {
- NSString *credentialsString = [self.bucket objectForKey:@"credentials"];
- NSData *credentialsData = [credentialsString dataUsingEncoding:NSUTF8StringEncoding];
- NSDictionary *credentials = [NSJSONSerialization JSONObjectWithData:credentialsData options:NSJSONReadingMutableContainers error:nil];
+ NSString *entitiesString = [self.bucket readFromFile:@"entities" appGroupId:self.appGroupId];
+ NSData *entitiesData = [entitiesString dataUsingEncoding:NSUTF8StringEncoding];
+ NSDictionary *entities = [NSJSONSerialization JSONObjectWithData:entitiesData options:NSJSONReadingMutableContainers error:nil];
+ NSDictionary *credentials = [[entities objectForKey:@"general"] objectForKey:@"credentials"];
self.serverUrl = [credentials objectForKey:@"url"];
self.token = [credentials objectForKey:@"token"];
}
@@ -46,9 +45,9 @@ MattermostBucket *mattermostBucket;
NSLog(@"ERROR %@", [error userInfo]);
[self.extensionContext completeRequestReturningItems:nil
completionHandler:nil];
+ NSLog(@"invalidating session %@", self.requestId);
+ [session finishTasksAndInvalidate];
}
- NSLog(@"invalidating session %@", self.requestId);
- [session finishTasksAndInvalidate];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
@@ -123,8 +122,6 @@ MattermostBucket *mattermostBucket;
NSString* postAsString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding];
NSURL *createUrl = [NSURL URLWithString:[self.serverUrl stringByAppendingString:@"/api/v4/posts"]];
- NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.requestId stringByAppendingString:@"-post"]];
- config.sharedContainerIdentifier = self.appGroupId;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:createUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0];
[request setHTTPMethod:@"POST"];
@@ -132,6 +129,8 @@ MattermostBucket *mattermostBucket;
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:[postAsString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request];
NSLog(@"Executing post request");
diff --git a/ios/MattermostShare/ShareViewController.m b/ios/MattermostShare/ShareViewController.m
index b75c9d763..c3699f174 100644
--- a/ios/MattermostShare/ShareViewController.m
+++ b/ios/MattermostShare/ShareViewController.m
@@ -1,7 +1,6 @@
#import "ShareViewController.h"
#import
#import
-#import "MattermostBucket.h"
#import "PerformRequests.h"
NSExtensionContext* extensionContext;
diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist
index 2221397f4..f188ad74e 100644
--- a/ios/MattermostTests/Info.plist
+++ b/ios/MattermostTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 82
+ 84
diff --git a/share_extension/ios/extension_channels.js b/share_extension/ios/extension_channels.js
index fcd1e911c..a8596bb09 100644
--- a/share_extension/ios/extension_channels.js
+++ b/share_extension/ios/extension_channels.js
@@ -12,10 +12,8 @@ import {
import DeviceInfo from 'react-native-device-info';
import {intlShape} from 'react-intl';
-import {Client4} from 'mattermost-redux/client';
-import {General, Preferences} from 'mattermost-redux/constants';
-import {getUserIdFromChannelName} from 'mattermost-redux/utils/channel_utils';
-import {displayUsername} from 'mattermost-redux/utils/user_utils';
+import {General} from 'mattermost-redux/constants';
+import {getChannelsInTeam, getDirectChannels} from 'mattermost-redux/selectors/entities/channels';
import SearchBar from 'app/components/search_bar';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -26,8 +24,8 @@ import ExtensionNavBar from './extension_nav_bar';
export default class ExtensionChannels extends PureComponent {
static propTypes = {
+ entities: PropTypes.object,
currentChannelId: PropTypes.string.isRequired,
- currentUserId: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
onSelectChannel: PropTypes.func.isRequired,
teamId: PropTypes.string.isRequired,
@@ -98,16 +96,6 @@ export default class ExtensionChannels extends PureComponent {
this.setState({sections});
};
- getGroupDisplayNameFromUserIds = (userIds, profiles) => {
- const names = [];
- userIds.forEach((id) => {
- const profile = profiles.find((p) => p.id === id);
- names.push(displayUsername(profile, Preferences.DISPLAY_PREFER_FULL_NAME));
- });
-
- return names.sort(this.sort).join(', ');
- };
-
goBack = () => {
this.props.navigator.pop();
};
@@ -116,64 +104,13 @@ export default class ExtensionChannels extends PureComponent {
loadChannels = async () => {
try {
- const {currentUserId, teamId} = this.props;
- const channelsMap = {};
+ const {entities, teamId} = this.props;
// get the channels for the specified team
- const myChannels = await Client4.getMyChannels(teamId);
-
- // filter channels that are direct and group messages
- const dms = myChannels.filter((channel) => {
- return channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL;
- });
-
- 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 && 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) {
- 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
- }
- }
- }
-
- const channels = myChannels.map((channel) => {
- return {
- id: channel.id,
- display_name: channelsMap[channel.id] || channel.display_name,
- type: channel.type
- };
- });
+ const channelsInTeam = getChannelsInTeam({entities});
+ const channelIds = channelsInTeam[teamId] || [];
+ const direct = getDirectChannels({entities});
+ const channels = channelIds.map((id) => this.props.entities.channels.channels[id]).concat(direct);
this.setState({
channels
diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js
index 1ff4db49d..817c1f6bb 100644
--- a/share_extension/ios/extension_post.js
+++ b/share_extension/ios/extension_post.js
@@ -40,6 +40,7 @@ import {
import ExtensionChannels from './extension_channels';
import ExtensionNavBar from './extension_nav_bar';
import ExtensionTeams from './extension_teams';
+import {General} from 'mattermost-redux/constants/index';
const ShareExtension = NativeModules.MattermostShare;
const MAX_INPUT_HEIGHT = 95;
@@ -58,7 +59,8 @@ const extensionSvg = {
export default class ExtensionPost extends PureComponent {
static propTypes = {
- credentials: PropTypes.object,
+ authenticated: PropTypes.bool.isRequired,
+ entities: PropTypes.object,
navigator: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired
@@ -75,7 +77,7 @@ export default class ExtensionPost extends PureComponent {
const isLandscape = width > height;
this.state = {
- currentUserId: null,
+ entities: props.entities,
error: null,
files: [],
isLandscape,
@@ -97,9 +99,9 @@ export default class ExtensionPost extends PureComponent {
this.focusInput();
}
- auth = async () => {
+ emmAuthenticationIfNeeded = async () => {
try {
- const emmSecured = await mattermostBucket.get('emm', Config.AppGroupId);
+ const emmSecured = await mattermostBucket.getPreference('emm', Config.AppGroupId);
if (emmSecured) {
const {intl} = this.context;
await LocalAuth.authenticate({
@@ -132,7 +134,7 @@ export default class ExtensionPost extends PureComponent {
goToChannels = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
- const {channel, currentUserId, team} = this.state;
+ const {channel, entities, team} = this.state;
navigator.push({
component: ExtensionChannels,
@@ -142,7 +144,7 @@ export default class ExtensionPost extends PureComponent {
},
passProps: {
currentChannelId: channel.id,
- currentUserId,
+ entities,
onSelectChannel: this.selectChannel,
teamId: team.id,
theme,
@@ -164,6 +166,7 @@ export default class ExtensionPost extends PureComponent {
backgroundColor: theme.centerChannelBg
},
passProps: {
+ entities: this.state.entities,
currentTeamId: team.id,
onSelectTeam: this.selectTeam,
theme
@@ -180,12 +183,13 @@ export default class ExtensionPost extends PureComponent {
};
loadData = async () => {
- const {credentials} = this.props;
- if (credentials) {
+ const {entities} = this.state;
+ if (this.props.authenticated) {
try {
- const currentUserId = await mattermostBucket.get('currentUserId', Config.AppGroupId);
- const channel = await mattermostBucket.get('selectedChannel', Config.AppGroupId);
- const team = await mattermostBucket.get('selectedTeam', Config.AppGroupId);
+ const {credentials} = entities.general;
+ const {currentUserId} = entities.users;
+ const team = entities.teams.teams[entities.teams.currentTeamId];
+ const channel = entities.channels.channels[entities.channels.currentChannelId];
const items = await ShareExtension.data(Config.AppGroupId);
const text = [];
const urls = [];
@@ -231,7 +235,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, totalSize});
+ this.setState({channel, files, team, value, totalSize});
} catch (error) {
this.setState({error});
}
@@ -253,7 +257,7 @@ export default class ExtensionPost extends PureComponent {
renderBody = (styles) => {
const {formatMessage} = this.context.intl;
- const {credentials, theme} = this.props;
+ const {authenticated, theme} = this.props;
const {error, sending, totalSize, value} = this.state;
if (sending) {
@@ -283,7 +287,7 @@ export default class ExtensionPost extends PureComponent {
);
}
- if (credentials && !error) {
+ if (authenticated && !error) {
return (
{
const {formatMessage} = this.context.intl;
- const {credentials, theme} = this.props;
+ const {authenticated, theme} = this.props;
const {channel, sending} = this.state;
const channelName = channel ? channel.display_name : '';
@@ -337,7 +341,7 @@ export default class ExtensionPost extends PureComponent {
return null;
}
- if (!credentials) {
+ if (!authenticated) {
return null;
}
@@ -376,7 +380,6 @@ export default class ExtensionPost extends PureComponent {
renderFiles = (styles) => {
const {files} = this.state;
-
return files.map((file, index) => {
let component;
@@ -456,7 +459,7 @@ export default class ExtensionPost extends PureComponent {
renderTeamButton = (styles) => {
const {formatMessage} = this.context.intl;
- const {credentials, theme} = this.props;
+ const {authenticated, theme} = this.props;
const {sending, team} = this.state;
const teamName = team ? team.display_name : '';
@@ -464,7 +467,7 @@ export default class ExtensionPost extends PureComponent {
return null;
}
- if (!credentials) {
+ if (!authenticated) {
return null;
}
@@ -503,21 +506,35 @@ export default class ExtensionPost extends PureComponent {
selectTeam = (team, channel) => {
this.setState({channel, team});
+
+ // Update the channels for the team
+ Client4.getMyChannels(team.id).then((channels) => {
+ const defaultChannel = channels.find((c) => c.name === General.DEFAULT_CHANNEL && c.team_id === team.id);
+ this.updateChannelsInEntities(channels);
+ if (!channel) {
+ this.setState({channel: defaultChannel});
+ }
+ }).catch((error) => {
+ this.setState({error});
+ });
};
sendMessage = wrapWithPreventDoubleTap(async () => {
- const {credentials, onClose} = this.props;
- const {channel, currentUserId, files, value} = this.state;
+ const {authenticated, onClose} = this.props;
+ const {channel, entities, files, value} = this.state;
+ const {currentUserId} = entities.users;
// If no text and no files do nothing
if (!value && !files.length) {
return;
}
- if (currentUserId && credentials) {
- await this.auth();
+ if (currentUserId && authenticated) {
+ await this.emmAuthenticationIfNeeded();
try {
+ // Check to see if the use still belongs to the channel
+ await Client4.getMyChannelMember(channel.id);
const post = {
user_id: currentUserId,
channel_id: channel.id,
@@ -541,23 +558,56 @@ export default class ExtensionPost extends PureComponent {
}
});
+ updateChannelsInEntities = (newChannels) => {
+ const {entities} = this.state;
+ const newEntities = {
+ ...entities,
+ channels: {
+ ...entities.channels,
+ channels: {...entities.channels.channels},
+ channelsInTeam: {...entities.channels.channelsInTeam}
+ }
+ };
+ const {channels, channelsInTeam} = newEntities.channels;
+
+ newChannels.forEach((c) => {
+ channels[c.id] = c;
+ const channelIdsInTeam = channelsInTeam[c.team_id];
+ if (channelIdsInTeam) {
+ if (!channelIdsInTeam.includes(c.id)) {
+ channelsInTeam[c.team_id].push(c.id);
+ }
+ } else {
+ channelsInTeam[c.team_id] = [c.id];
+ }
+ });
+
+ this.setState({entities: newEntities});
+ mattermostBucket.writeToFile('entities', JSON.stringify(newEntities), Config.AppGroupId);
+ };
+
render() {
- const {credentials, theme} = this.props;
- const {totalSize, sending} = this.state;
+ const {authenticated, theme} = this.props;
+ const {channel, totalSize, sending} = this.state;
const {formatMessage} = this.context.intl;
const styles = getStyleSheet(theme);
+ let postButtonText = formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'});
+ if (totalSize >= MAX_FILE_SIZE || sending || !channel) {
+ postButtonText = null;
+ }
+
return (
= MAX_FILE_SIZE || sending) ? null : formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'})}
+ rightButtonTitle={postButtonText}
theme={theme}
/>
{this.renderBody(styles)}
diff --git a/share_extension/ios/extension_teams.js b/share_extension/ios/extension_teams.js
index 0a1479d4b..485b46740 100644
--- a/share_extension/ios/extension_teams.js
+++ b/share_extension/ios/extension_teams.js
@@ -7,8 +7,8 @@ import {ActivityIndicator, FlatList, Text, View} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {intlShape} from 'react-intl';
-import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
+import {getChannelsInTeam} from 'mattermost-redux/selectors/entities/channels';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -18,6 +18,7 @@ import ExtensionTeamItem from './extension_team_item';
export default class ExtensionTeams extends PureComponent {
static propTypes = {
currentTeamId: PropTypes.string.isRequired,
+ entities: PropTypes.object,
navigator: PropTypes.object.isRequired,
onSelectTeam: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired
@@ -53,17 +54,24 @@ export default class ExtensionTeams extends PureComponent {
loadTeams = async () => {
try {
const defaultChannels = {};
- const teams = await Client4.getMyTeams();
- const myMembers = await Client4.getMyTeamMembers();
+ const {teams, myMembers} = this.props.entities.teams;
const myTeams = [];
+ const channelsInTeam = getChannelsInTeam({entities: this.props.entities});
- 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);
+ for (const key in teams) {
+ if (teams.hasOwnProperty(key)) {
+ const team = teams[key];
+ const belong = myMembers[key];
+ if (belong) {
+ const channelIds = channelsInTeam[key];
+ let channels;
+ if (channelIds) {
+ channels = channelIds.map((id) => this.props.entities.channels.channels[id]);
+ defaultChannels[team.id] = channels.find((channel) => channel.name === General.DEFAULT_CHANNEL);
+ }
+
+ myTeams.push(team);
+ }
}
}
diff --git a/share_extension/ios/index.js b/share_extension/ios/index.js
index 86105796b..86d0081ae 100644
--- a/share_extension/ios/index.js
+++ b/share_extension/ios/index.js
@@ -37,8 +37,8 @@ export default class SharedApp extends PureComponent {
isLandscape
};
- mattermostBucket.get('credentials', Config.AppGroupId).then((value) => {
- this.credentials = value;
+ mattermostBucket.readFromFile('entities', Config.AppGroupId).then((value) => {
+ this.entities = value;
this.setState({init: true});
});
}
@@ -72,6 +72,11 @@ export default class SharedApp extends PureComponent {
}
};
+ userIsLoggedIn = () => {
+ return Boolean(this.entities && this.entities.general && this.entities.general.credentials &&
+ this.entities.general.credentials.token && this.entities.general.credentials.url);
+ };
+
render() {
const {init, isLandscape} = this.state;
@@ -86,7 +91,8 @@ export default class SharedApp extends PureComponent {
component: ExtensionPost,
title: 'Mattermost',
passProps: {
- credentials: this.credentials,
+ authenticated: this.userIsLoggedIn(),
+ entities: this.entities,
onClose: this.onClose,
isLandscape,
theme
@@ -113,7 +119,7 @@ export default class SharedApp extends PureComponent {
styles.container,
{
opacity: this.state.containerOpacity,
- height: this.credentials ? 250 : 130,
+ height: this.userIsLoggedIn() ? 250 : 130,
top: isLandscape ? 20 : 65
}
]}