Merge branch 'release-1.6'

This commit is contained in:
Elias Nahum 2018-02-02 19:23:48 -03:00
commit c4ad99d2b8
No known key found for this signature in database
GPG key ID: E038DB71E0B61702
19 changed files with 290 additions and 242 deletions

View file

@ -106,7 +106,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 23
versionCode 82
versionCode 84
versionName "1.6.0"
multiDexEnabled true
ndk {

View file

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

View file

@ -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);
}
}
};

View file

@ -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();

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>82</string>
<string>84</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View file

@ -3,4 +3,5 @@
@interface MattermostBucket : NSObject <RCTBridgeModule>
- (NSUserDefaults *)bucketByName:(NSString*)name;
-(NSString *)readFromFile:(NSString *)fileName appGroupId:(NSString *)appGroupId;
@end

View file

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

View file

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

View file

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

View file

@ -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");

View file

@ -1,7 +1,6 @@
#import "ShareViewController.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "MattermostBucket.h"
#import "PerformRequests.h"
NSExtensionContext* extensionContext;

View file

@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>82</string>
<string>84</string>
</dict>
</plist>

View file

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

View file

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

View file

@ -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);
}
}
}

View file

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