From cd80cdfdf442a538d33fffd2a1d830832689d09d Mon Sep 17 00:00:00 2001 From: enahum Date: Wed, 31 Jan 2018 13:02:02 -0300 Subject: [PATCH] Fix race condition preventing the app opened from a PN to go to the correct channel (#1398) * Fix race condition preventing the app opened from a PN to go to the correct channel * Feedback review --- .../rnbeta/NotificationsLifecycleFacade.java | 45 ++- app/actions/views/channel.js | 3 +- app/actions/views/login.js | 5 +- app/components/root/index.js | 3 +- app/mattermost.js | 332 +++++++++--------- app/screens/channel/channel.js | 2 + app/screens/index.js | 2 - app/screens/load_team/index.js | 35 -- app/screens/load_team/load_team.js | 71 ---- app/screens/login/login.js | 12 +- app/screens/root/index.js | 27 +- app/screens/root/root.js | 101 ------ app/screens/select_server/select_server.js | 3 +- app/screens/sso/sso.js | 12 +- 14 files changed, 215 insertions(+), 438 deletions(-) delete mode 100644 app/screens/load_team/index.js delete mode 100644 app/screens/load_team/load_team.js delete mode 100644 app/screens/root/root.js diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java index 66568d287..17a1f6d07 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationsLifecycleFacade.java @@ -39,12 +39,11 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - - if (mVisibleActivity != null) { + if (context != null) { // Get the current configuration bundle RestrictionsManager myRestrictionsMgr = - (RestrictionsManager) mVisibleActivity - .getSystemService(Context.RESTRICTIONS_SERVICE); + (RestrictionsManager) context + .getSystemService(Context.RESTRICTIONS_SERVICE); managedConfig = myRestrictionsMgr.getApplicationRestrictions(); // Check current configuration settings, change your app's UI and @@ -66,11 +65,11 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { MattermostManagedModule managedModule = MattermostManagedModule.getInstance(); - if (managedModule != null && managedModule.isBlurAppScreenEnabled()) { + if (managedModule != null && managedModule.isBlurAppScreenEnabled() && activity != null) { activity.getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE); } - if (managedConfig!= null && managedConfig.size() > 0) { + if (managedConfig!= null && managedConfig.size() > 0 && activity != null) { activity.registerReceiver(restrictionsReceiver, restrictionsFilter); } } @@ -79,9 +78,11 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A public void onActivityResumed(Activity activity) { switchToVisible(activity); - if (managedConfig != null && managedConfig.size() > 0) { + ReactContext ctx = getRunningReactContext(); + if (managedConfig != null && managedConfig.size() > 0 && ctx != null) { + RestrictionsManager myRestrictionsMgr = - (RestrictionsManager) activity + (RestrictionsManager) ctx .getSystemService(Context.RESTRICTIONS_SERVICE); Bundle newConfig = myRestrictionsMgr.getApplicationRestrictions(); @@ -174,13 +175,15 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A } } - public synchronized void LoadManagedConfig(Activity activity) { - RestrictionsManager myRestrictionsMgr = - (RestrictionsManager) activity - .getSystemService(Context.RESTRICTIONS_SERVICE); + public synchronized void LoadManagedConfig(ReactContext ctx) { + if (ctx != null) { + RestrictionsManager myRestrictionsMgr = + (RestrictionsManager) ctx + .getSystemService(Context.RESTRICTIONS_SERVICE); - managedConfig = myRestrictionsMgr.getApplicationRestrictions(); - myRestrictionsMgr = null; + managedConfig = myRestrictionsMgr.getApplicationRestrictions(); + myRestrictionsMgr = null; + } } public synchronized Bundle getManagedConfig() { @@ -188,8 +191,10 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A return managedConfig; } - if (mVisibleActivity != null) { - LoadManagedConfig(mVisibleActivity); + ReactContext ctx = getRunningReactContext(); + + if (ctx != null) { + LoadManagedConfig(ctx); return managedConfig; } @@ -198,9 +203,11 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A public void sendConfigChanged(Bundle config) { Object result = Arguments.fromBundle(config); - getRunningReactContext(). - getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class). - emit("managedConfigDidChange", result); + ReactContext ctx = getRunningReactContext(); + if (ctx != null) { + ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class). + emit("managedConfigDidChange", result); + } } private boolean equalBundles(Bundle one, Bundle two) { diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 0301dee2f..d43dd914d 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -470,7 +470,8 @@ export function increasePostVisibility(channelId, focusedPostId) { // Check if we already have the posts that we want to show if (!focusedPostId) { - const loadedPostCount = state.entities.posts.postsInChannel[channelId].length; + const postsInChannel = state.entities.posts.postsInChannel[channelId] || []; + const loadedPostCount = postsInChannel.length; const desiredPostVisibility = currentPostVisibility + ViewTypes.POST_VISIBILITY_CHUNK_SIZE; if (loadedPostCount >= desiredPostVisibility) { diff --git a/app/actions/views/login.js b/app/actions/views/login.js index eb69aa0e6..f798f9d39 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -3,7 +3,7 @@ import {getDataRetentionPolicy} from 'mattermost-redux/actions/general'; import {GeneralTypes} from 'mattermost-redux/action_types'; -import {Client, Client4} from 'mattermost-redux/client'; +import {Client4} from 'mattermost-redux/client'; import {ViewTypes} from 'app/constants'; @@ -39,9 +39,6 @@ export function handleSuccessfulLogin() { } }, getState); - Client.setToken(token); - Client.setUrl(url); - if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' && license.IsLicensed === 'true' && license.DataRetention === 'true') { getDataRetentionPolicy()(dispatch, getState); diff --git a/app/components/root/index.js b/app/components/root/index.js index d13e6a873..55dc149ff 100644 --- a/app/components/root/index.js +++ b/app/components/root/index.js @@ -3,7 +3,7 @@ import {connect} from 'react-redux'; -import {Client, Client4} from 'mattermost-redux/client'; +import {Client4} from 'mattermost-redux/client'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general'; @@ -15,7 +15,6 @@ import Root from './root'; function mapStateToProps(state) { const locale = getCurrentLocale(state); - Client.setLocale(locale); Client4.setAcceptLanguage(locale); return { diff --git a/app/mattermost.js b/app/mattermost.js index c672344ad..d0ef66517 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -23,9 +23,9 @@ import {General} from 'mattermost-redux/constants'; import {setAppState, setDeviceToken, setServerVersion} from 'mattermost-redux/actions/general'; import {markChannelAsRead} from 'mattermost-redux/actions/channels'; import {logError} from 'mattermost-redux/actions/errors'; -import {logout} from 'mattermost-redux/actions/users'; +import {loadMe, logout} from 'mattermost-redux/actions/users'; import {close as closeWebSocket} from 'mattermost-redux/actions/websocket'; -import {Client, Client4} from 'mattermost-redux/client'; +import {Client4} from 'mattermost-redux/client'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; import { @@ -53,58 +53,41 @@ import mattermostBucket from 'app/mattermost_bucket'; import mattermostManaged from 'app/mattermost_managed'; import {deleteFileCache} from 'app/utils/file'; import {init as initAnalytics} from 'app/utils/segment'; -import { - captureException, - initializeSentry, - LOGGER_JAVASCRIPT, - LOGGER_NATIVE -} from 'app/utils/sentry'; +import {captureException, initializeSentry, LOGGER_JAVASCRIPT, LOGGER_NATIVE} from 'app/utils/sentry'; +import tracker from 'app/utils/time_tracker'; import {stripTrailingSlashes} from 'app/utils/url'; -import Config from 'assets/config'; +import LocalConfig from 'assets/config'; const {StatusBarManager} = NativeModules; const AUTHENTICATION_TIMEOUT = 5 * 60 * 1000; export default class Mattermost { constructor() { - if (Platform.OS === 'android') { - // This is to remove the warnings for the scaleY property used in android. - // The property is necessary because some Android devices won't render the posts - // properly if we use transform: {scaleY: -1} in the stylesheet. - console.ignoredYellowBox = ['`scaleY`']; //eslint-disable-line - } this.isConfigured = false; this.allowOtherServers = true; + this.startAppFromPushNotification = false; + this.emmEnabled = false; + Client4.setUserAgent(DeviceInfo.getUserAgent()); Orientation.unlockAllOrientations(); - initializeSentry(); - this.store = configureStore(initialState); + registerScreens(this.store, Provider); + this.unsubscribeFromStore = this.store.subscribe(this.listenForHydration); AppState.addEventListener('change', this.handleAppStateChange); - EventEmitter.on(General.CONFIG_CHANGED, this.handleConfigChanged); - EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.handleReset); - EventEmitter.on(General.DEFAULT_CHANNEL, this.handleResetDisplayName); + EventEmitter.on(General.CONFIG_CHANGED, this.handleServerConfigChanged); + EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.handleLogout); + EventEmitter.on(General.DEFAULT_CHANNEL, this.handleResetChannelDisplayName); EventEmitter.on(NavigationTypes.RESTART_APP, this.restartApp); Orientation.addOrientationListener(this.orientationDidChange); mattermostManaged.addEventListener('managedConfigDidChange', this.handleManagedConfig); - this.handleAppStateChange(AppState.currentState); - Client4.setUserAgent(DeviceInfo.getUserAgent()); - if (Platform.OS === 'ios') { StatusBarSizeIOS.addEventListener('willChange', this.handleStatusBarHeightChange); - StatusBarManager.getHeight( - (data) => { - this.handleStatusBarHeightChange(data.height); - } - ); } - registerScreens(this.store, Provider); - setJSExceptionHandler(this.errorHandler, false); setNativeExceptionHandler(this.nativeErrorHandler, false); } @@ -115,7 +98,10 @@ export default class Mattermost { const intl = this.getIntl(); closeWebSocket()(this.store.dispatch, this.store.getState); - logError(e)(this.store.dispatch); + + if (Client4.getUrl()) { + logError(e)(this.store.dispatch); + } if (isFatal) { Alert.alert( @@ -151,7 +137,7 @@ export default class Mattermost { }; configureAnalytics = (config) => { - if (config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && Config.SegmentApiKey) { + if (config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) { initAnalytics(config); } else { global.analytics = null; @@ -172,37 +158,39 @@ export default class Mattermost { handleAppStateChange = async (appState) => { const {dispatch, getState} = this.store; const isActive = appState === 'active'; + setAppState(isActive)(dispatch, getState); - if (isActive && this.shouldRelaunchonActive) { + if (isActive && this.shouldRelaunchWhenActive) { + // This handles when the app was started in the background + // cause of an iOS push notification reply this.launchApp(); - this.shouldRelaunchonActive = false; - } else { + this.shouldRelaunchWhenActive = false; + } else if (!isActive && !this.inBackgroundSince) { + // When the app is sent to the background we set the time when that happens + // and perform a data clean up to improve on performance + this.inBackgroundSince = Date.now(); + dispatch({type: ViewTypes.DATA_CLEANUP, payload: getState()}); + } else if (isActive && this.inBackgroundSince && (Date.now() - this.inBackgroundSince) >= AUTHENTICATION_TIMEOUT && this.emmEnabled) { + // Once the app becomes active after more than 5 minutes in the background and is controlled by an EMM try { - if (!isActive && !this.inBackgroundSince) { - this.inBackgroundSince = Date.now(); - dispatch({type: ViewTypes.DATA_CLEANUP, payload: getState()}); - } else if (isActive && this.inBackgroundSince && (Date.now() - this.inBackgroundSince) >= AUTHENTICATION_TIMEOUT) { - this.inBackgroundSince = null; - - if (this.mdmEnabled) { - const config = await mattermostManaged.getConfig(); - const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true'; - if (authNeeded) { - const authenticated = await this.handleAuthentication(config.vendor); - if (!authenticated) { - mattermostManaged.quitApp(); - } - } + const config = await mattermostManaged.getConfig(); + const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true'; + if (authNeeded) { + const authenticated = await this.handleAuthentication(config.vendor); + if (!authenticated) { + mattermostManaged.quitApp(); } - } else if (isActive) { - this.inBackgroundSince = null; - Keyboard.dismiss(); } } catch (error) { // do nothing } } + + if (isActive) { + this.inBackgroundSince = null; + Keyboard.dismiss(); + } }; handleAuthentication = async (vendor) => { @@ -211,7 +199,7 @@ export default class Mattermost { const intl = this.getIntl(); if (isSecured) { try { - mattermostBucket.set('emm', vendor, Config.AppGroupId); + mattermostBucket.set('emm', vendor, LocalConfig.AppGroupId); await mattermostManaged.authenticate({ reason: intl.formatMessage({ id: 'mobile.managed.secured_by', @@ -229,20 +217,20 @@ export default class Mattermost { return true; }; - handleConfigChanged = async (serverVersion) => { + handleServerConfigChanged = async (serverVersion) => { const {dispatch, getState} = this.store; const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0]; const intl = this.getIntl(); const state = getState(); if (serverVersion) { - if (semver.valid(version) && semver.lt(version, Config.MinServerVersion)) { + if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) { Alert.alert( intl.formatMessage({id: 'mobile.server_upgrade.title', defaultMessage: 'Server upgrade required'}), intl.formatMessage({id: 'mobile.server_upgrade.description', defaultMessage: '\nA server upgrade is required to use the Mattermost app. Please ask your System Administrator for details.\n'}), [{ text: intl.formatMessage({id: 'mobile.server_upgrade.button', defaultMessage: 'OK'}), - onPress: this.handleVersionUpgrade + onPress: this.handleServerVersionUpgradeNeeded }], {cancelable: false} ); @@ -265,15 +253,15 @@ export default class Mattermost { let serverUrl = null; let username = null; - if (Config.AutoSelectServerUrl) { - handleServerUrlChanged(Config.DefaultServerUrl)(dispatch, getState); + if (LocalConfig.AutoSelectServerUrl) { + handleServerUrlChanged(LocalConfig.DefaultServerUrl)(dispatch, getState); this.allowOtherServers = false; } try { const config = await mattermostManaged.getConfig(); - if (config) { - this.mdmEnabled = true; + if (config && Object.keys(config).length) { + this.emmEnabled = true; authNeeded = config.inAppPinCode && config.inAppPinCode === 'true'; blurApplicationScreen = config.blurApplicationScreen && config.blurApplicationScreen === 'true'; jailbreakProtection = config.jailbreakProtection && config.jailbreakProtection === 'true'; @@ -287,74 +275,72 @@ export default class Mattermost { this.allowOtherServers = false; } } + + if (jailbreakProtection) { + const isTrusted = mattermostManaged.isTrustedDevice(); + + if (!isTrusted) { + const intl = this.getIntl(); + Alert.alert( + intl.formatMessage({ + id: 'mobile.managed.blocked_by', + defaultMessage: 'Blocked by {vendor}' + }, {vendor}), + intl.formatMessage({ + id: 'mobile.managed.jailbreak', + defaultMessage: 'Jailbroken devices are not trusted by {vendor}, please exit the app.' + }, {vendor}), + [{ + text: intl.formatMessage({id: 'mobile.managed.exit', defaultMessage: 'Exit'}), + style: 'destructive', + onPress: () => { + mattermostManaged.quitApp(); + } + }], + {cancelable: false} + ); + return false; + } + } + + if (authNeeded && !serverConfig) { + if (Platform.OS === 'android') { + //Start a fake app as we need at least one activity for android + await this.startFakeApp(); + } + const authenticated = await this.handleAuthentication(vendor); + if (!authenticated) { + return false; + } + } + + if (blurApplicationScreen) { + mattermostManaged.blurAppScreen(true); + } + + if (serverUrl) { + handleServerUrlChanged(serverUrl)(dispatch, getState); + } + + if (username) { + handleLoginIdChanged(username)(dispatch, getState); + } } } catch (error) { return true; } - if (this.mdmEnabled) { - if (jailbreakProtection) { - const isTrusted = mattermostManaged.isTrustedDevice(); - - if (!isTrusted) { - const intl = this.getIntl(); - Alert.alert( - intl.formatMessage({ - id: 'mobile.managed.blocked_by', - defaultMessage: 'Blocked by {vendor}' - }, {vendor}), - intl.formatMessage({ - id: 'mobile.managed.jailbreak', - defaultMessage: 'Jailbroken devices are not trusted by {vendor}, please exit the app.' - }, {vendor}), - [{ - text: intl.formatMessage({id: 'mobile.managed.exit', defaultMessage: 'Exit'}), - style: 'destructive', - onPress: () => { - mattermostManaged.quitApp(); - } - }], - {cancelable: false} - ); - return false; - } - } - - if (authNeeded && !serverConfig) { - if (Platform.OS === 'android') { - //Start a fake app as we need at least one activity for android - await this.startFakeApp(); - } - const authenticated = await this.handleAuthentication(vendor); - if (!authenticated) { - return false; - } - } - - if (blurApplicationScreen) { - mattermostManaged.blurAppScreen(true); - } - - if (serverUrl) { - handleServerUrlChanged(serverUrl)(dispatch, getState); - } - - if (username) { - handleLoginIdChanged(username)(dispatch, getState); - } - } - return true; }; - handleReset = () => { + handleLogout = () => { this.appStarted = false; deleteFileCache(); this.resetBadgeAndVersion(); this.startApp('fade'); }; - handleResetDisplayName = (displayName) => { + handleResetChannelDisplayName = (displayName) => { this.store.dispatch(setChannelDisplayName(displayName)); }; @@ -362,48 +348,65 @@ export default class Mattermost { this.store.dispatch(setStatusBarHeight(nextStatusBarHeight)); }; - handleVersionUpgrade = async () => { + handleServerVersionUpgradeNeeded = async () => { const {dispatch, getState} = this.store; - Client4.serverVersion = ''; - PushNotifications.setApplicationIconBadgeNumber(0); + this.resetBadgeAndVersion(); if (getState().entities.general.credentials.token) { InteractionManager.runAfterInteractions(() => { logout()(dispatch, getState); }); - } else { - this.resetBadgeAndVersion(); } }; // We need to wait for hydration to occur before load the router. listenForHydration = () => { - const state = this.store.getState(); + const {dispatch, getState} = this.store; + const state = getState(); if (!this.isConfigured) { this.configurePushNotifications(); } if (state.views.root.hydrationComplete) { + const orientation = Orientation.getInitialOrientation(); + const {credentials, config} = state.entities.general; + const {currentUserId} = state.entities.users; + const isNotActive = AppState.currentState !== 'active'; + const notification = PushNotifications.getNotification(); + this.unsubscribeFromStore(); - const orientation = Orientation.getInitialOrientation(); + if (this.deviceToken) { + // If the deviceToken is set we need to dispatch it to the redux store + setDeviceToken(this.deviceToken)(dispatch, getState); + } + if (orientation) { this.orientationDidChange(orientation); } - const {config} = state.entities.general; if (config) { this.configureAnalytics(config); } - const {currentUserId} = state.entities.users; + if (credentials.token && credentials.url) { + Client4.setToken(credentials.token); + Client4.setUrl(stripTrailingSlashes(credentials.url)); + } + if (currentUserId) { Client4.setUserId(currentUserId); } - const isNotActive = AppState.currentState !== 'active'; - const notification = PushNotifications.getNotification(); + if (Platform.OS === 'ios') { + StatusBarManager.getHeight( + (data) => { + this.handleStatusBarHeightChange(data.height); + } + ); + } + if (notification || this.replyNotificationData) { // If we have a notification means that the app was started cause of a reply // and the app was not sitting in the background nor opened @@ -416,6 +419,10 @@ export default class Mattermost { if (Platform.OS === 'android') { // In case of Android we need to handle the bridge being initialized by HeadlessJS Promise.resolve(Navigation.isAppLaunched()).then((appLaunched) => { + if (this.startAppFromPushNotification) { + return; + } + if (appLaunched) { this.launchApp(); // App is launched -> show UI } else { @@ -424,7 +431,7 @@ export default class Mattermost { }); } else if (isNotActive) { // for IOS replying from push notification starts the app in the background - this.shouldRelaunchonActive = true; + this.shouldRelaunchWhenActive = true; this.startFakeApp(); } else { this.launchApp(); @@ -445,19 +452,23 @@ export default class Mattermost { prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE; } - setDeviceToken(`${prefix}:${data.token}`)(dispatch, getState); + const state = getState(); + const token = `${prefix}:${data.token}`; + if (state.views.root.hydrationComplete) { + setDeviceToken(token)(dispatch, getState); + } else { + this.deviceToken = token; + } }; onPushNotification = (deviceNotification) => { const {dispatch, getState} = this.store; const state = getState(); const {token, url} = state.entities.general.credentials; - let startAppFromPushNotification = false; // mark the app as started as soon as possible - if (token && url && !this.appStarted) { - this.appStarted = true; - startAppFromPushNotification = true; + if (!this.appStarted && Platform.OS !== 'ios') { + this.startAppFromPushNotification = true; } const {data, foreground, message, userInfo, userInteraction} = deviceNotification; @@ -476,18 +487,16 @@ export default class Mattermost { EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification); } else if (userInteraction && !notification.localNotification) { EventEmitter.emit('close_channel_drawer'); - if (startAppFromPushNotification) { - Client.setToken(token); + if (this.startAppFromPushNotification) { Client4.setToken(token); Client4.setUrl(stripTrailingSlashes(url)); - Client.setUrl(stripTrailingSlashes(url)); } InteractionManager.runAfterInteractions(async () => { await loadFromPushNotification(notification)(dispatch, getState); - if (startAppFromPushNotification) { - this.startAppFromPushNotification(); + if (this.startAppFromPushNotification) { + this.launchApp(); } else { EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED); } @@ -516,7 +525,7 @@ export default class Mattermost { Client4.setUrl(state.entities.general.credentials.url); } - if (!Client.getToken()) { + if (!Client4.getToken()) { // Make sure the Client has the server token set Client4.setToken(state.entities.general.credentials.token); } @@ -554,9 +563,7 @@ export default class Mattermost { resetBadgeAndVersion = () => { const {dispatch, getState} = this.store; Client4.serverVersion = ''; - Client.serverVersion = ''; - Client.token = null; - Client4.userId = ''; + Client4.setUserId(''); PushNotifications.setApplicationIconBadgeNumber(0); PushNotifications.cancelAllLocalNotifications(); setServerVersion('')(dispatch, getState); @@ -567,6 +574,7 @@ export default class Mattermost { const {dispatch, getState} = this.store; await loadConfigAndLicense()(dispatch, getState); + await loadMe()(dispatch, getState); this.appStarted = false; this.startApp('fade'); }; @@ -588,18 +596,29 @@ export default class Mattermost { statusBarHidden: false, statusBarHideWithNavBar: false } - }, - passProps: { - justInit: true } }); }; startApp = (animationType = 'none') => { if (!this.appStarted) { + const {dispatch, getState} = this.store; + const {entities} = getState(); + let screen = 'SelectServer'; + + if (entities) { + const {credentials} = entities.general; + + if (credentials.token && credentials.url) { + screen = 'Channel'; + tracker.initialLoad = Date.now(); + loadMe()(dispatch, getState); + } + } + Navigation.startSingleScreenApp({ screen: { - screen: 'Root', + screen, navigatorStyle: { navBarHidden: true, statusBarHidden: false, @@ -617,28 +636,7 @@ export default class Mattermost { }); this.appStarted = true; + this.startAppFromPushNotification = false; } }; - - startAppFromPushNotification = () => { - Navigation.startSingleScreenApp({ - screen: { - screen: 'Channel', - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: 'transparent' - } - }, - passProps: { - allowOtherServers: this.allowOtherServers - }, - appStyle: { - orientation: 'auto' - }, - animationType: 'none' - }); - this.appStarted = false; - }; } diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 1f05bf966..556fa9352 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -60,6 +60,8 @@ class Channel extends PureComponent { if (this.props.currentTeamId) { this.loadChannels(this.props.currentTeamId); + } else { + this.props.actions.selectFirstAvailableTeam(); } } diff --git a/app/screens/index.js b/app/screens/index.js index 6488414b6..053d1f428 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -22,7 +22,6 @@ import EditChannel from 'app/screens/edit_channel'; import EditPost from 'app/screens/edit_post'; import EditProfile from 'app/screens/edit_profile'; import ImagePreview from 'app/screens/image_preview'; -import LoadTeam from 'app/screens/load_team'; import Login from 'app/screens/login'; import LoginOptions from 'app/screens/login_options'; import Mfa from 'app/screens/mfa'; @@ -78,7 +77,6 @@ export function registerScreens(store, Provider) { Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider); Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(EditProfile), store, Provider); Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(ImagePreview), store, Provider); - Navigation.registerComponent('LoadTeam', () => wrapWithContextProvider(LoadTeam, false), store, Provider); Navigation.registerComponent('Login', () => wrapWithContextProvider(Login), store, Provider); Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(LoginOptions), store, Provider); Navigation.registerComponent('MFA', () => wrapWithContextProvider(Mfa), store, Provider); diff --git a/app/screens/load_team/index.js b/app/screens/load_team/index.js deleted file mode 100644 index d34c0aeef..000000000 --- a/app/screens/load_team/index.js +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {bindActionCreators} from 'redux'; -import {connect} from 'react-redux'; - -import {getTeams} from 'mattermost-redux/actions/teams'; -import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; - -import {handleTeamChange} from 'app/actions/views/select_team'; -import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; - -import LoadTeam from './load_team'; - -function mapStateToProps(state) { - return { - config: state.entities.general.config, - theme: getTheme(state), - teams: state.entities.teams.teams, - currentTeam: getCurrentTeam(state), - myMembers: state.entities.teams.myMembers, - notification: state.views.notification - }; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - getTeams, - handleTeamChange - }, dispatch) - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(LoadTeam); diff --git a/app/screens/load_team/load_team.js b/app/screens/load_team/load_team.js deleted file mode 100644 index dcd2706c0..000000000 --- a/app/screens/load_team/load_team.js +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {PureComponent} from 'react'; -import PropTypes from 'prop-types'; - -import EventEmitter from 'mattermost-redux/utils/event_emitter'; - -import {NavigationTypes} from 'app/constants'; - -export default class LoadTeam extends PureComponent { - static propTypes = { - actions: PropTypes.shape({ - getTeams: PropTypes.func.isRequired, - handleTeamChange: PropTypes.func.isRequired - }).isRequired, - currentTeam: PropTypes.object, - myMembers: PropTypes.object.isRequired, - navigator: PropTypes.object, - teams: PropTypes.object.isRequired, - theme: PropTypes.object.isRequired - }; - - componentDidMount() { - const {currentTeam, myMembers, teams} = this.props; - if (currentTeam && myMembers[currentTeam.id]) { - return this.onSelectTeam(currentTeam); - } - - return this.selectFirstTeam(teams, myMembers); - } - - selectFirstTeam(allTeams, myMembers) { - const teams = Object.keys(myMembers).map((key) => allTeams[key]); - const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0]; - - if (firstTeam) { - this.onSelectTeam(firstTeam); - } else { - const {getTeams} = this.props.actions; - getTeams().then(() => { - EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS); - }); - } - } - - onSelectTeam(team) { - const {handleTeamChange} = this.props.actions; - handleTeamChange(team.id).then(this.goToChannelView); - } - - goToChannelView = () => { - const {navigator, theme} = this.props; - - navigator.resetTo({ - screen: 'Channel', - animated: true, - animationType: 'fade', - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: theme.centerChannelBg - } - }); - }; - - render() { - return null; - } -} diff --git a/app/screens/login/login.js b/app/screens/login/login.js index 9f234431b..062b92577 100644 --- a/app/screens/login/login.js +++ b/app/screens/login/login.js @@ -77,7 +77,7 @@ class Login extends PureComponent { } goToLoadTeam = (expiresAt) => { - const {intl, navigator, theme} = this.props; + const {intl, navigator} = this.props; tracker.initialLoad = Date.now(); if (expiresAt) { @@ -94,17 +94,17 @@ class Login extends PureComponent { } navigator.resetTo({ - screen: 'LoadTeam', + screen: 'Channel', title: '', animated: false, backButtonTitle: '', navigatorStyle: { + animated: true, + animationType: 'fade', + navBarHidden: true, statusBarHidden: false, statusBarHideWithNavBar: false, - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg + screenBackgroundColor: 'transparent' } }); }; diff --git a/app/screens/root/index.js b/app/screens/root/index.js index e6ec7ecc7..49e2db631 100644 --- a/app/screens/root/index.js +++ b/app/screens/root/index.js @@ -1,29 +1,12 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import {bindActionCreators} from 'redux'; -import {connect} from 'react-redux'; +import React from 'react'; -import {loadMe} from 'mattermost-redux/actions/users'; -import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; -import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import Loading from 'app/components/loading'; -import Root from './root'; - -function mapStateToProps(state) { - return { - currentUser: getCurrentUser(state), - credentials: state.entities.general.credentials, - theme: getTheme(state) - }; +function Root() { + return ; } -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - loadMe - }, dispatch) - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(Root); +export default Root; diff --git a/app/screens/root/root.js b/app/screens/root/root.js deleted file mode 100644 index 395e50666..000000000 --- a/app/screens/root/root.js +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React, {Component} from 'react'; -import PropTypes from 'prop-types'; - -import {Client, Client4} from 'mattermost-redux/client'; - -import Loading from 'app/components/loading'; -import {stripTrailingSlashes} from 'app/utils/url'; -import tracker from 'app/utils/time_tracker'; - -export default class Root extends Component { - static propTypes = { - actions: PropTypes.shape({ - loadMe: PropTypes.func.isRequired - }).isRequired, - allowOtherServers: PropTypes.bool, - currentUser: PropTypes.object, - credentials: PropTypes.object, - justInit: PropTypes.bool, - navigator: PropTypes.object, - theme: PropTypes.object - }; - - shouldComponentUpdate(nextProps) { - if (nextProps.credentials !== this.props.credentials) { - return true; - } - return false; - } - - componentDidMount() { - if (!this.props.justInit) { - this.loadStoreAndScene(); - } - } - - goToLoadTeam = () => { - const {navigator, theme} = this.props; - tracker.initialLoad = Date.now(); - navigator.resetTo({ - screen: 'LoadTeam', - title: '', - animated: true, - animationType: 'fade', - backButtonTitle: '', - navigatorStyle: { - statusBarHidden: false, - statusBarHideWithNavBar: false, - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg - } - }); - }; - - goToSelectServer = () => { - const {allowOtherServers, navigator} = this.props; - - navigator.resetTo({ - screen: 'SelectServer', - animated: true, - animationType: 'fade', - navigatorStyle: { - navBarHidden: true, - navBarBackgroundColor: 'black', - statusBarHidden: false, - statusBarHideWithNavBar: false - }, - passProps: { - allowOtherServers - } - }); - }; - - loadStoreAndScene = () => { - const {actions, currentUser, credentials} = this.props; - const {loadMe} = actions; - if (credentials.token && credentials.url) { - Client.setToken(credentials.token); - Client4.setToken(credentials.token); - Client4.setUrl(stripTrailingSlashes(credentials.url)); - Client.setUrl(stripTrailingSlashes(credentials.url)); - - if (currentUser) { - loadMe(); - this.goToLoadTeam(); - } else { - loadMe().then(this.goToLoadTeam).catch(this.goToLoadTeam); - } - } else { - this.goToSelectServer(); - } - }; - - render() { - return ; - } -} diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index d23973cc9..71e121701 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -19,7 +19,7 @@ import { import Button from 'react-native-button'; import urlParse from 'url-parse'; -import {Client, Client4} from 'mattermost-redux/client'; +import {Client4} from 'mattermost-redux/client'; import Config from 'assets/config'; import ErrorText from 'app/components/error_text'; @@ -224,7 +224,6 @@ class SelectServer extends PureComponent { }); Client4.setUrl(url); - Client.setUrl(url); handleServerUrlChanged(url); let cancel = false; diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js index e9053eb3a..a1b1b8dc0 100644 --- a/app/screens/sso/sso.js +++ b/app/screens/sso/sso.js @@ -82,7 +82,7 @@ class SSO extends PureComponent { } goToLoadTeam = (expiresAt) => { - const {intl, navigator, theme} = this.props; + const {intl, navigator} = this.props; tracker.initialLoad = Date.now(); if (expiresAt) { @@ -99,17 +99,17 @@ class SSO extends PureComponent { } navigator.resetTo({ - screen: 'LoadTeam', + screen: 'Channel', title: '', animated: false, backButtonTitle: '', navigatorStyle: { + animated: true, + animationType: 'fade', + navBarHidden: true, statusBarHidden: false, statusBarHideWithNavBar: false, - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg + screenBackgroundColor: 'transparent' } }); };