Merge branch 'release-1.6'
This commit is contained in:
commit
c4ad99d2b8
19 changed files with 290 additions and 242 deletions
|
|
@ -106,7 +106,7 @@ android {
|
|||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 23
|
||||
versionCode 82
|
||||
versionCode 84
|
||||
versionName "1.6.0"
|
||||
multiDexEnabled true
|
||||
ndk {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>82</string>
|
||||
<string>84</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
|
||||
@interface MattermostBucket : NSObject <RCTBridgeModule>
|
||||
- (NSUserDefaults *)bucketByName:(NSString*)name;
|
||||
-(NSString *)readFromFile:(NSString *)fileName appGroupId:(NSString *)appGroupId;
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.6.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>82</string>
|
||||
<string>84</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MattermostBucket.h"
|
||||
|
||||
@interface PerformRequests : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate>
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#import "ShareViewController.h"
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
#import "MattermostBucket.h"
|
||||
#import "PerformRequests.h"
|
||||
|
||||
NSExtensionContext* extensionContext;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@
|
|||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>82</string>
|
||||
<string>84</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ScrollView
|
||||
ref={this.getScrollViewRef}
|
||||
|
|
@ -329,7 +333,7 @@ export default class ExtensionPost extends PureComponent {
|
|||
|
||||
renderChannelButton = (styles) => {
|
||||
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 (
|
||||
<View
|
||||
onLayout={this.onLayout}
|
||||
style={styles.container}
|
||||
>
|
||||
<ExtensionNavBar
|
||||
authenticated={Boolean(credentials)}
|
||||
authenticated={authenticated}
|
||||
leftButtonTitle={sending ? null : formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'})}
|
||||
onLeftButtonPress={this.handleCancel}
|
||||
onRightButtonPress={this.sendMessage}
|
||||
rightButtonTitle={(totalSize >= MAX_FILE_SIZE || sending) ? null : formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'})}
|
||||
rightButtonTitle={postButtonText}
|
||||
theme={theme}
|
||||
/>
|
||||
{this.renderBody(styles)}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]}
|
||||
|
|
|
|||
Loading…
Reference in a new issue