mattermost-mobile/app/init/global_event_handler.js
Anurag Shivarathri fedf636b2f
Feat crt (#5455)
* [MM-31133]: participants screen component (#5116)

* Fixed imports

* Misc fixes

* Added i18n string

* Moved '(you)' after the name

* [MM-30386][MM-30387] Client and redux changes for collapsed reply threads (#5175)

* [MM-30386][MM-30387] Client and redux changes for collapsed reply threads

* fix tests

* add and use flat shim

* use test helper function

* use test helper function

* revert crt changes to unused client and action functions

* use toEqual

* incorporate code review feedback

* don't assume server success

* batch actions

* clearer code

* WIP in changes, websockets and more redux changes

* Added mark thread as read and mark all as read in a team

* Added isCollapsedThreadEnabled entity

* Fixed loading global screens on load if it's the last visited screen

* Added root count and websocker and redux events related to threads

* Fixed Message, Mention count root count on init and decrement actions

* Moved old client to new client/rest

* Misc fix

* Misc fix

* Misc.

* Old redux test cases fix

* Added polyfill back for array flat

* Fixes mark as unread and pariticipants error in thread

* Fixed initial load when CRT not enabled

* Handling CRT preference change

* Get threads fallsback to search in posts in case of unfollowed or unloaded threads

* Gets threads on reconnect

* DM, Channel counter fix

* Added threads redux tests

* Fixed channel dot issue when child post is arrived channel not active

* Cleaning up threads on load, not letting posts included in threads getting deleted which can trigger many posts to be loaded at once

* Fixed crash on team switch, partly fixed enable/disable from webapp

* Fix when viewing a post from notification, to prevent from navigating back to global threads

* Fixed when preferences are changed before app is launched

* Fixed issue of NO THREADS appearing on reconnect

* Excludes CRT handling for newly logged in users

* Fixed unread dot showing even when all messages are read

* Update app/mm-redux/selectors/entities/threads.ts

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* Not viewing threads action is pushed only when suer is in Global Threads

* Fixed cleanup to exclude current team when cleanup starts on app going to background

* Dispatch is not passed as param, misc code fixes

* Preferences check and handling code refactored

* added redux-mock-store

* Preference change fix

* Removed user login dispatch when handling crt change

* Fixed fallback thread support

* Return getThread

* Added threads to sidebar, moved teamId logic to action

* [MM-29106, MM-34847, MM-34843, MM-34846] Collapsed reply threads, Global Threads & In Channel experience UI implementation (#5376)

* feat: global threads screen

* Refactored global thread menu and misc design fixes

* Added empty states for threads, unread. Removing markdown from thread messages.

* Added friendly date, fixed long usernames & channel names in threads view.

* Mark thread as read

* Added intl, refactored code, seperated thread footer from thread item

* Added intl to global threads, added global threads to android

* Added Follow button to thread view

* Added thread badges

* In Channel experience .. WIP

* Clean posts in channel incase CRT is enabled.

* Added follow button, deleted messages doesnot have footer and design is now according to figma

* Removed old post and post header

* Added for follow/unfollow thread and fixed old one

* MISC design & code fixes

* Added options-follow/unfollow to the post's long press menu

* Shows ORIGINAL MESSAGE DELETED in thread when root post gets deleted

* Post list ts fix

* Old testcases fix

* Eslint fix

* Added custom status emoji in at mention autocomplete

* Fixed the width of the displayname and username

* Changed the max width for the full name and username in the autocomplete item

* Review fixes
Changed the behaviour of ellipses for long names

* Removed safearea bottom

* Fixes Mark and unread and thread header theme

* Fixes mark as unread and pariticipants error in thread

* Unfollowing post fix and misc

* Merge with master fix

* Added fallback to get thread from posts in case of not following/unloaded threads

* Refactored code and added tests for screens

* Loads threads from  global threads screen, marks thread as marked on new thread posts

* Fixed tabs render on no threads, threads footer items styling and tests

* Loads threads based on tab and items according to constant

* Added infinite loading, unreads based on redux and not active thread list

* Fixed the username cutting off after truncating display name issue

* Changed avatars to touchableOpacity and added prop for parent component to style

* Fixed infinite loading, tab switching scroll to top, misc styles, team switching, scroll indicator insets

* Added follow button to DMs, moved follow button further right only on ios

* Snapshot updated

* Moved from useSelector to connect HOC

* Scrolls to top on team change, changed thread constant from mm-redux

* Fix merge issue

* Test case fix

* Fixed participants list

* Update app/components/global_threads/global_threads.tsx

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* Update app/components/global_threads/thread_footer/thread_footer.tsx

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* Update app/components/sidebars/main/channels_list/channel_item/index.js

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* Update app/screens/thread/index.js

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* Moved date numbers to constants

* Fixed global threads component issues

* Thread item display logic moved to redux

* Misc fixes

* Reverted to TouchableWithFeedback with opacity

* Removed obselete snapshots

* Removing currentTeamId as we aregetting it in action

* Moved to connect component from useSelect for sidebar

* Thread read issue fix

* Removed react-native-navigation mock

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Manoj <manoj@brightscout.com>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
Co-authored-by: Manoj <manoj@brightscout.com>

Co-authored-by: Ashish Bhate <bhate.ashish@gmail.com>
Co-authored-by: Ashish Bhate <ashish.bhate@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
Co-authored-by: Manoj <manoj@brightscout.com>
2021-07-21 22:58:04 +05:30

375 lines
13 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AppState, Dimensions, Keyboard, Linking, Platform} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import CookieManager from '@react-native-cookies/cookies';
import DeviceInfo from 'react-native-device-info';
import {getLocales} from 'react-native-localize';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet} from '@actions/device';
import {dismissAllModals, popToRoot, showOverlay} from '@actions/navigation';
import {selectDefaultChannel} from '@actions/views/channel';
import {loadConfigAndLicense, purgeOfflineStore, setDeepLinkURL, startDataCleanup} from '@actions/views/root';
import {loadMe, logout} from '@actions/views/user';
import LocalConfig from '@assets/config';
import {NavigationTypes, ViewTypes} from '@constants';
import {resetMomentLocale} from '@i18n';
import {setupPermanentSidebar} from '@init/device';
import PushNotifications from '@init/push_notifications';
import {setAppState, setServerVersion} from '@mm-redux/actions/general';
import {getTeams} from '@mm-redux/actions/teams';
import {autoUpdateTimezone} from '@mm-redux/actions/timezone';
import {close as closeWebSocket} from '@actions/websocket';
import {Client4} from '@client/rest';
import {General} from '@mm-redux/constants';
import {getConfig} from '@mm-redux/selectors/entities/general';
import {getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
import {getCurrentUser, getUser} from '@mm-redux/selectors/entities/users';
import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import initialState from '@store/initial_state';
import EphemeralStore from '@store/ephemeral_store';
import Store from '@store/store';
import {deleteFileCache} from '@utils/file';
import {getDeviceTimezone} from '@utils/timezone';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import {getAppCredentials, removeAppCredentials} from './credentials';
import emmProvider from './emm_provider';
import {analytics} from '@init/analytics.ts';
const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 1000;
class GlobalEventHandler {
constructor() {
this.pushNotificationListener = false;
EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.onLogout);
EventEmitter.on(NavigationTypes.RESTART_APP, this.onRestartApp);
EventEmitter.on(General.SERVER_VERSION_CHANGED, this.onServerVersionChanged);
EventEmitter.on(General.CONFIG_CHANGED, this.onServerConfigChanged);
EventEmitter.on(General.CRT_PREFERENCE_CHANGED, this.onCRTPreferenceChanged);
EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, this.onSwitchToDefaultChannel);
Dimensions.addEventListener('change', this.onOrientationChange);
AppState.addEventListener('change', this.onAppStateChange);
Linking.addEventListener('url', this.onDeepLink);
}
appActive = async () => {
this.turnOnInAppNotificationHandling();
this.setUserTimezone();
// if the app is being controlled by an EMM provider
if (emmProvider.enabled && emmProvider.inAppPinCode) {
const authExpired = (Date.now() - emmProvider.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER;
// Once the app becomes active we check if the device needs to have a passcode set
const prompt = emmProvider.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
await emmProvider.handleAuthentication(prompt);
}
emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */
};
appInactive = () => {
this.turnOffInAppNotificationHandling();
const {dispatch} = Store.redux;
// 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
emmProvider.inBackgroundSince = Date.now();
dispatch(startDataCleanup());
};
configure = (opts) => {
this.launchApp = opts.launchApp;
// onAppStateChange may be called by the AppState listener before we
// configure the global event handler so we manually call it here
this.onAppStateChange('active');
const window = Dimensions.get('window');
this.onOrientationChange({window});
this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default;
this.JavascriptAndNativeErrorHandler.initializeErrorHandling(Store.redux);
mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
};
configureAnalytics = async () => {
const state = Store.redux.getState();
const config = getConfig(state);
if (config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.RudderApiKey) {
await analytics.init(config);
}
};
onAppStateChange = (appState) => {
const isActive = appState === 'active';
const isBackground = appState === 'background';
if (Store.redux) {
Store.redux.dispatch(setAppState(isActive));
if (isActive && (!emmProvider.enabled || emmProvider.previousAppState === 'background')) {
this.appActive();
} else if (isBackground) {
this.appInactive();
}
}
emmProvider.previousAppState = appState;
};
onCRTPreferenceChanged = () => {
Keyboard.dismiss();
requestAnimationFrame(async () => {
const componentId = EphemeralStore.getNavigationTopComponentId();
if (componentId) {
EventEmitter.emit(NavigationTypes.CLOSE_MAIN_SIDEBAR);
EventEmitter.emit(NavigationTypes.CLOSE_SETTINGS_SIDEBAR);
await dismissAllModals();
await popToRoot();
}
Store.redux.dispatch(purgeOfflineStore());
});
};
onDeepLink = (event) => {
const {url} = event;
if (url) {
Store.redux.dispatch(setDeepLinkURL(url));
}
};
onManagedConfigurationChange = () => {
emmProvider.handleManagedConfig(true);
};
clearCookiesAndWebData = async () => {
try {
await CookieManager.clearAll(true);
if (Platform.OS === 'ios') {
// on iOS we will also detele the Cookies stored by NSCookieStore
await CookieManager.clearAll(false);
}
} catch (error) {
// Nothing to do
}
};
onLogout = async () => {
Store.redux.dispatch(closeWebSocket(false));
Store.redux.dispatch(setServerVersion(''));
await analytics.reset();
mattermostBucket.removePreference('cert');
mattermostBucket.removePreference('emm');
if (Platform.OS === 'ios') {
mattermostBucket.removeFile('entities');
}
removeAppCredentials();
deleteFileCache();
resetMomentLocale();
await this.clearCookiesAndWebData();
PushNotifications.clearNotifications();
if (this.launchApp) {
this.launchApp();
}
// Reset the state after sending
// the user to the Select server URL screen
// To avoid unavailable data crashes while components are
// still mounted.
this.resetState();
};
onOrientationChange = (dimensions) => {
if (Store.redux) {
const {dispatch, getState} = Store.redux;
const deviceState = getState().device;
if (DeviceInfo.isTablet()) {
dispatch(setDeviceAsTablet());
}
const {height, width} = dimensions.window;
const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE';
const savedOrientation = deviceState?.orientation;
const savedDimension = deviceState?.dimension;
if (orientation !== savedOrientation) {
dispatch(setDeviceOrientation(orientation));
}
if (height !== savedDimension?.deviceHeight ||
width !== savedDimension?.deviceWidth) {
dispatch(setDeviceDimensions(height, width));
}
}
};
onRestartApp = async () => {
const {dispatch, getState} = Store.redux;
const state = getState();
const {currentUserId} = state.entities.users;
const user = getUser(state, currentUserId);
await dispatch(loadConfigAndLicense());
await dispatch(loadMe(user));
dispatch(getTeams());
const window = Dimensions.get('window');
this.onOrientationChange({window});
};
onServerConfigChanged = (config) => {
this.configureAnalytics(config);
if (isMinimumServerVersion(Client4.serverVersion, 5, 24) && config.ExtendSessionLengthWithActivity === 'true') {
PushNotifications.cancelAllLocalNotifications();
}
};
onServerVersionChanged = async (serverVersion) => {
const {dispatch, getState} = Store.redux;
const state = getState();
if (serverVersion && state.entities.users && state.entities.users.currentUserId) {
dispatch(setServerVersion(serverVersion));
dispatch(loadConfigAndLicense());
}
};
onSwitchToDefaultChannel = (teamId) => {
Store.redux.dispatch(selectDefaultChannel(teamId));
};
resetState = async () => {
try {
await AsyncStorage.clear();
await setupPermanentSidebar();
const state = Store.redux.getState();
const newState = {
...initialState,
app: {
build: DeviceInfo.getBuildNumber(),
version: DeviceInfo.getVersion(),
previousVersion: DeviceInfo.getVersion(),
},
entities: {
...initialState.entities,
general: {
...initialState.entities.general,
deviceToken: state.entities.general.deviceToken,
},
},
views: {
i18n: {
locale: getLocales()[0].languageTag,
},
root: {
hydrationComplete: true,
},
selectServer: {
serverUrl: state.views.selectServer.serverUrl,
},
},
_persist: {
rehydrated: true,
},
};
return Store.redux.dispatch({
type: General.OFFLINE_STORE_PURGE,
data: newState,
});
} catch (e) {
// clear error
return e;
}
}
serverUpgradeNeeded = async () => {
const {dispatch} = Store.redux;
dispatch(setServerVersion(''));
Client4.serverVersion = '';
const credentials = await getAppCredentials();
if (credentials) {
dispatch(logout());
}
};
turnOnInAppNotificationHandling = () => {
if (!this.pushNotificationListener) {
this.pushNotificationListener = true;
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
}
}
turnOffInAppNotificationHandling = () => {
this.pushNotificationListener = false;
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
}
handleInAppNotification = (notification) => {
const {payload} = notification;
const {getState} = Store.redux;
const state = getState();
const currentChannelId = getCurrentChannelId(state);
if (payload?.channel_id !== currentChannelId) {
const screen = 'Notification';
const passProps = {
notification,
};
EventEmitter.emit(NavigationTypes.NAVIGATION_SHOW_OVERLAY);
showOverlay(screen, passProps);
}
};
setUserTimezone = async () => {
const {dispatch, getState} = Store.redux;
const state = getState();
const currentUser = getCurrentUser(state);
const enableTimezone = isTimezoneEnabled(state);
if (enableTimezone && currentUser.id) {
const timezone = getDeviceTimezone();
const {
automaticTimezone,
manualTimezone,
useAutomaticTimezone,
} = currentUser.timezone;
let updateTimeZone = false;
if (useAutomaticTimezone) {
updateTimeZone = timezone !== automaticTimezone;
} else {
updateTimeZone = timezone !== manualTimezone;
}
if (updateTimeZone) {
dispatch(autoUpdateTimezone(timezone));
}
}
};
}
export default new GlobalEventHandler();