Chore: Migrate redux module app to typescript (#3598)

* chore: migrate activeUsers reducer and action to TS

* chore: init types folder and set redux and BaseScreen interface

* chore: remove mapDispatchToProps to use dispatch prop and clear some types

* chore: type selectedUsers action and reducer and improvement in the code of other files

* chore: move IUser to base types

* chore: move state props to ISelectedUsersViewProps

* chore: create mocketStore

* chore: remove applyAppStateMiddleware

* test: create activeUser and selectedUser tests

* test: add more selectedUsers tests

* chore: fix action type

* chore: move types to definition folder and fix imports

* chore: remove unused const

* chore: migrate redux tests to reducer folder and add eslint jest plugin

* chore: exprot initial state and then import on tests

* chore: move interfaces to reducer and import on screen

* chore: set eslint-plugin-jest version to 24.7.0

* chore: fix IUser import

* chore: update interfaces and types names

* chore: update definitions

* chore: update IBaseScreen definitions

* chore: init reducer/app migration to ts

* chore: add tests and migrate RootEnum

* wip: migrate fixed consts to RootEnum

* chore: remove redux action inferences

* fix types

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Gleidson Daniel Silva 2022-02-02 15:27:10 -03:00 committed by GitHub
parent 7ad900a515
commit 6fa35cd748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 223 additions and 156 deletions

View File

@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes'; import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
import Navigation from './lib/Navigation'; import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app'; import { RootEnum } from './definitions';
// Stacks // Stacks
import AuthLoadingView from './views/AuthLoadingView'; import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack // SetUsername Stack
@ -56,13 +56,13 @@ const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail
}}> }}>
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}> <Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
<> <>
{root === ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null} {root === RootEnum.ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null}
{root === ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null} {root === RootEnum.ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null}
{root === ROOT_INSIDE && isMasterDetail ? ( {root === RootEnum.ROOT_INSIDE && isMasterDetail ? (
<Stack.Screen name='MasterDetailStack' component={MasterDetailStack} /> <Stack.Screen name='MasterDetailStack' component={MasterDetailStack} />
) : null} ) : null}
{root === ROOT_INSIDE && !isMasterDetail ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null} {root === RootEnum.ROOT_INSIDE && !isMasterDetail ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null}
{root === ROOT_SET_USERNAME ? <Stack.Screen name='SetUsernameStack' component={SetUsernameStack} /> : null} {root === RootEnum.ROOT_SET_USERNAME ? <Stack.Screen name='SetUsernameStack' component={SetUsernameStack} /> : null}
</> </>
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>

View File

@ -2,8 +2,8 @@ const REQUEST = 'REQUEST';
const SUCCESS = 'SUCCESS'; const SUCCESS = 'SUCCESS';
const FAILURE = 'FAILURE'; const FAILURE = 'FAILURE';
const defaultTypes = [REQUEST, SUCCESS, FAILURE]; const defaultTypes = [REQUEST, SUCCESS, FAILURE];
function createRequestTypes(base = {}, types = defaultTypes): Record<any, any> { function createRequestTypes(base = {}, types = defaultTypes): Record<string, string> {
const res: Record<any, any> = {}; const res: Record<string, string> = {};
types.forEach(type => (res[type] = `${base}_${type}`)); types.forEach(type => (res[type] = `${base}_${type}`));
return res; return res;
} }

View File

@ -3,7 +3,7 @@ import { Action } from 'redux';
import { IActiveUsers } from '../reducers/activeUsers'; import { IActiveUsers } from '../reducers/activeUsers';
import { SET_ACTIVE_USERS } from './actionsTypes'; import { SET_ACTIVE_USERS } from './actionsTypes';
export interface ISetActiveUsers extends Action { interface ISetActiveUsers extends Action {
activeUsers: IActiveUsers; activeUsers: IActiveUsers;
} }

View File

@ -1,39 +0,0 @@
import { APP } from './actionsTypes';
export const ROOT_OUTSIDE = 'outside';
export const ROOT_INSIDE = 'inside';
export const ROOT_LOADING = 'loading';
export const ROOT_SET_USERNAME = 'setUsername';
export function appStart({ root, ...args }) {
return {
type: APP.START,
root,
...args
};
}
export function appReady() {
return {
type: APP.READY
};
}
export function appInit() {
return {
type: APP.INIT
};
}
export function appInitLocalSettings() {
return {
type: APP.INIT_LOCAL_SETTINGS
};
}
export function setMasterDetail(isMasterDetail) {
return {
type: APP.SET_MASTER_DETAIL,
isMasterDetail
};
}

53
app/actions/app.ts Normal file
View File

@ -0,0 +1,53 @@
import { Action } from 'redux';
import { RootEnum } from '../definitions';
import { APP } from './actionsTypes';
interface IAppStart extends Action {
root: RootEnum;
text?: string;
}
interface ISetMasterDetail extends Action {
isMasterDetail: boolean;
}
export type TActionApp = IAppStart & ISetMasterDetail;
interface Params {
root: RootEnum;
[key: string]: any;
}
export function appStart({ root, ...args }: Params): IAppStart {
return {
type: APP.START,
root,
...args
};
}
export function appReady(): Action {
return {
type: APP.READY
};
}
export function appInit(): Action {
return {
type: APP.INIT
};
}
export function appInitLocalSettings(): Action {
return {
type: APP.INIT_LOCAL_SETTINGS
};
}
export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
return {
type: APP.SET_MASTER_DETAIL,
isMasterDetail
};
}

View File

@ -30,3 +30,4 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
} }
export * from './redux'; export * from './redux';
export * from './redux/TRootEnum';

View File

@ -0,0 +1,6 @@
export enum RootEnum {
ROOT_OUTSIDE = 'outside',
ROOT_INSIDE = 'inside',
ROOT_LOADING = 'loading',
ROOT_SET_USERNAME = 'setUsername'
}

View File

@ -1,5 +1,6 @@
// ACTIONS // ACTIONS
import { TActionActiveUsers } from '../../actions/activeUsers'; import { TActionActiveUsers } from '../../actions/activeUsers';
import { TActionApp } from '../../actions/app';
import { TActionCreateChannel } from '../../actions/createChannel'; import { TActionCreateChannel } from '../../actions/createChannel';
import { TActionCustomEmojis } from '../../actions/customEmojis'; import { TActionCustomEmojis } from '../../actions/customEmojis';
import { TActionEncryption } from '../../actions/encryption'; import { TActionEncryption } from '../../actions/encryption';
@ -13,6 +14,7 @@ import { TActionSortPreferences } from '../../actions/sortPreferences';
import { TActionUserTyping } from '../../actions/usersTyping'; import { TActionUserTyping } from '../../actions/usersTyping';
// REDUCERS // REDUCERS
import { IActiveUsers } from '../../reducers/activeUsers'; import { IActiveUsers } from '../../reducers/activeUsers';
import { IApp } from '../../reducers/app';
import { IConnect } from '../../reducers/connect'; import { IConnect } from '../../reducers/connect';
import { ICreateChannel } from '../../reducers/createChannel'; import { ICreateChannel } from '../../reducers/createChannel';
import { IEncryption } from '../../reducers/encryption'; import { IEncryption } from '../../reducers/encryption';
@ -29,8 +31,8 @@ export interface IApplicationState {
meteor: IConnect; meteor: IConnect;
server: IServer; server: IServer;
selectedUsers: ISelectedUsers; selectedUsers: ISelectedUsers;
app: IApp;
createChannel: ICreateChannel; createChannel: ICreateChannel;
app: any;
room: any; room: any;
rooms: any; rooms: any;
sortPreferences: any; sortPreferences: any;
@ -58,4 +60,5 @@ export type TApplicationActions = TActionActiveUsers &
TActionUserTyping & TActionUserTyping &
TActionCreateChannel & TActionCreateChannel &
TActionsShare & TActionsShare &
TActionServer; TActionServer &
TActionApp;

44
app/reducers/app.test.ts Normal file
View File

@ -0,0 +1,44 @@
import { appStart, appInit, setMasterDetail } from '../actions/app';
import { initialState } from './app';
import { mockedStore } from './mockedStore';
import { RootEnum } from '../definitions';
import { APP_STATE } from '../actions/actionsTypes';
describe('test reducer', () => {
it('should return initial state', () => {
const state = mockedStore.getState().app;
expect(state).toEqual(initialState);
});
it('should return root state after dispatch appStart action', () => {
mockedStore.dispatch(appStart({ root: RootEnum.ROOT_INSIDE }));
const { root } = mockedStore.getState().app;
expect(root).toEqual(RootEnum.ROOT_INSIDE);
});
it('should return ready state after dispatch appInit action', () => {
mockedStore.dispatch(appInit());
const { ready } = mockedStore.getState().app;
expect(ready).toEqual(false);
});
it('should return ready state after dispatch setMasterDetail action', () => {
mockedStore.dispatch(setMasterDetail(false));
const { isMasterDetail } = mockedStore.getState().app;
expect(isMasterDetail).toEqual(false);
});
it('should return correct state after app go to foreground', () => {
mockedStore.dispatch({ type: APP_STATE.FOREGROUND });
const { foreground, background } = mockedStore.getState().app;
expect(foreground).toEqual(true);
expect(background).toEqual(false);
});
it('should return correct state after app go to background', () => {
mockedStore.dispatch({ type: APP_STATE.BACKGROUND });
const { foreground, background } = mockedStore.getState().app;
expect(foreground).toEqual(false);
expect(background).toEqual(true);
});
});

View File

@ -1,15 +1,26 @@
import { TActionApp } from '../actions/app';
import { RootEnum } from '../definitions';
import { APP, APP_STATE } from '../actions/actionsTypes'; import { APP, APP_STATE } from '../actions/actionsTypes';
const initialState = { export interface IApp {
root: null, root?: RootEnum;
isMasterDetail: boolean;
text?: string;
ready: boolean;
foreground: boolean;
background: boolean;
}
export const initialState: IApp = {
root: undefined,
isMasterDetail: false, isMasterDetail: false,
text: null, text: undefined,
ready: false, ready: false,
foreground: true, foreground: true,
background: false background: false
}; };
export default function app(state = initialState, action) { export default function app(state = initialState, action: TActionApp): IApp {
switch (action.type) { switch (action.type) {
case APP_STATE.FOREGROUND: case APP_STATE.FOREGROUND:
return { return {

View File

@ -8,11 +8,12 @@ import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'
import database from '../lib/database'; import database from '../lib/database';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { ROOT_INSIDE, ROOT_OUTSIDE, appInit, appStart } from '../actions/app'; import { appInit, appStart } from '../actions/app';
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
import { loginRequest } from '../actions/login'; import { loginRequest } from '../actions/login';
import log from '../utils/log'; import log from '../utils/log';
import { RootEnum } from '../definitions';
const roomTypes = { const roomTypes = {
channel: 'c', channel: 'c',
@ -41,7 +42,7 @@ const popToRoot = function popToRoot({ isMasterDetail }) {
}; };
const navigate = function* navigate({ params }) { const navigate = function* navigate({ params }) {
yield put(appStart({ root: ROOT_INSIDE })); yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
if (params.path || params.rid) { if (params.path || params.rid) {
let type; let type;
let name; let name;
@ -192,7 +193,7 @@ const handleOpen = function* handleOpen({ params }) {
yield fallbackNavigation(); yield fallbackNavigation();
return; return;
} }
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
yield put(serverInitAdd(server)); yield put(serverInitAdd(server));
yield delay(1000); yield delay(1000);
EventEmitter.emit('NewServer', { server: host }); EventEmitter.emit('NewServer', { server: host });

View File

@ -9,7 +9,8 @@ import RocketChat from '../lib/rocketchat';
import log from '../utils/log'; import log from '../utils/log';
import database from '../lib/database'; import database from '../lib/database';
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { ROOT_OUTSIDE, appReady, appStart } from '../actions/app'; import { appReady, appStart } from '../actions/app';
import { RootEnum } from '../definitions';
export const initLocalSettings = function* initLocalSettings() { export const initLocalSettings = function* initLocalSettings() {
const sortPreferences = yield RocketChat.getSortPreferences(); const sortPreferences = yield RocketChat.getSortPreferences();
@ -22,7 +23,7 @@ const restore = function* restore() {
let userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); let userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);
if (!server) { if (!server) {
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} else if (!userId) { } else if (!userId) {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.get('servers'); const serversCollection = serversDB.get('servers');
@ -38,7 +39,7 @@ const restore = function* restore() {
} }
} }
} }
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} else { } else {
const serversDB = database.servers; const serversDB = database.servers;
const serverCollections = serversDB.get('servers'); const serverCollections = serversDB.get('servers');
@ -56,7 +57,7 @@ const restore = function* restore() {
yield put(appReady({})); yield put(appReady({}));
} catch (e) { } catch (e) {
log(e); log(e);
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} }
}; };

View File

@ -3,7 +3,7 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME, appStart } from '../actions/app'; import { appStart } from '../actions/app';
import { selectServerRequest, serverFinishAdd } from '../actions/server'; import { selectServerRequest, serverFinishAdd } from '../actions/server';
import { loginFailure, loginSuccess, logout, setUser } from '../actions/login'; import { loginFailure, loginSuccess, logout, setUser } from '../actions/login';
import { roomsRequest } from '../actions/rooms'; import { roomsRequest } from '../actions/rooms';
@ -20,6 +20,7 @@ import { encryptionInit, encryptionStop } from '../actions/encryption';
import UserPreferences from '../lib/userPreferences'; import UserPreferences from '../lib/userPreferences';
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry'; import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib'; import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
import { RootEnum } from '../definitions';
const getServer = state => state.server.server; const getServer = state => state.server.server;
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args); const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
@ -38,7 +39,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
if (!result.username) { if (!result.username) {
yield put(serverFinishAdd()); yield put(serverFinishAdd());
yield put(setUser(result)); yield put(setUser(result));
yield put(appStart({ root: ROOT_SET_USERNAME })); yield put(appStart({ root: RootEnum.ROOT_SET_USERNAME }));
} else { } else {
const server = yield select(getServer); const server = yield select(getServer);
yield localAuthenticate(server); yield localAuthenticate(server);
@ -167,7 +168,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(setUser(user)); yield put(setUser(user));
EventEmitter.emit('connected'); EventEmitter.emit('connected');
yield put(appStart({ root: ROOT_INSIDE })); yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
const inviteLinkToken = yield select(state => state.inviteLinks.token); const inviteLinkToken = yield select(state => state.inviteLinks.token);
if (inviteLinkToken) { if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken)); yield put(inviteLinksRequest(inviteLinkToken));
@ -179,7 +180,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
const handleLogout = function* handleLogout({ forcedByServer }) { const handleLogout = function* handleLogout({ forcedByServer }) {
yield put(encryptionStop()); yield put(encryptionStop());
yield put(appStart({ root: ROOT_LOADING, text: I18n.t('Logging_out') })); yield put(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Logging_out') }));
const server = yield select(getServer); const server = yield select(getServer);
if (server) { if (server) {
try { try {
@ -187,7 +188,7 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
// if the user was logged out by the server // if the user was logged out by the server
if (forcedByServer) { if (forcedByServer) {
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops')); showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
yield delay(300); yield delay(300);
EventEmitter.emit('NewServer', { server }); EventEmitter.emit('NewServer', { server });
@ -209,10 +210,10 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
} }
} }
// if there's no servers, go outside // if there's no servers, go outside
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} }
} catch (e) { } catch (e) {
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
log(e); log(e);
} }
} }

View File

@ -15,11 +15,12 @@ import database from '../lib/database';
import log, { logServerVersion } from '../utils/log'; import log, { logServerVersion } from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch'; import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
import { ROOT_INSIDE, ROOT_OUTSIDE, appStart } from '../actions/app'; import { appStart } from '../actions/app';
import UserPreferences from '../lib/userPreferences'; import UserPreferences from '../lib/userPreferences';
import { encryptionStop } from '../actions/encryption'; import { encryptionStop } from '../actions/encryption';
import SSLPinning from '../utils/sslPinning'; import SSLPinning from '../utils/sslPinning';
import { inquiryReset } from '../ee/omnichannel/actions/inquiry'; import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
import { RootEnum } from '../definitions';
const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
try { try {
@ -111,10 +112,10 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
yield put(clearSettings()); yield put(clearSettings());
yield RocketChat.connect({ server, user, logoutOnError: true }); yield RocketChat.connect({ server, user, logoutOnError: true });
yield put(setUser(user)); yield put(setUser(user));
yield put(appStart({ root: ROOT_INSIDE })); yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
} else { } else {
yield RocketChat.connect({ server }); yield RocketChat.connect({ server });
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} }
// We can't use yield here because fetch of Settings & Custom Emojis is slower // We can't use yield here because fetch of Settings & Custom Emojis is slower

View File

@ -5,11 +5,11 @@ import { setBadgeCount } from '../notifications/push';
import log from '../utils/log'; import log from '../utils/log';
import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication'; import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication';
import { APP_STATE } from '../actions/actionsTypes'; import { APP_STATE } from '../actions/actionsTypes';
import { ROOT_OUTSIDE } from '../actions/app'; import { RootEnum } from '../definitions';
const appHasComeBackToForeground = function* appHasComeBackToForeground() { const appHasComeBackToForeground = function* appHasComeBackToForeground() {
const appRoot = yield select(state => state.app.root); const appRoot = yield select(state => state.app.root);
if (appRoot === ROOT_OUTSIDE) { if (appRoot === RootEnum.ROOT_OUTSIDE) {
return; return;
} }
const login = yield select(state => state.login); const login = yield select(state => state.login);
@ -29,7 +29,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
const appHasComeBackToBackground = function* appHasComeBackToBackground() { const appHasComeBackToBackground = function* appHasComeBackToBackground() {
const appRoot = yield select(state => state.app.root); const appRoot = yield select(state => state.app.root);
if (appRoot === ROOT_OUTSIDE) { if (appRoot === RootEnum.ROOT_OUTSIDE) {
return; return;
} }
try { try {

View File

@ -1,31 +1,29 @@
import React from 'react'; import React from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import RNRestart from 'react-native-restart'; import RNRestart from 'react-native-restart';
import { Dispatch } from 'redux'; import { connect } from 'react-redux';
import { appStart } from '../../actions/app';
import { setUser } from '../../actions/login';
import { themes } from '../../constants/colors';
import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { IApplicationState, IBaseScreen, RootEnum } from '../../definitions';
import I18n, { isRTL, LANGUAGES } from '../../i18n';
import database from '../../lib/database';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import I18n, { LANGUAGES, isRTL } from '../../i18n'; import { getUserSelector } from '../../selectors/login';
import { SettingsStackParamList } from '../../stacks/types';
import { withTheme } from '../../theme';
import { showErrorAlert } from '../../utils/info'; import { showErrorAlert } from '../../utils/info';
import log, { events, logEvent } from '../../utils/log'; import log, { events, logEvent } from '../../utils/log';
import { setUser as setUserAction } from '../../actions/login';
import StatusBar from '../../containers/StatusBar';
import * as List from '../../containers/List';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { ROOT_INSIDE, ROOT_LOADING, appStart as appStartAction } from '../../actions/app';
import { getUserSelector } from '../../selectors/login';
import database from '../../lib/database';
import SafeAreaView from '../../containers/SafeAreaView';
interface ILanguageViewProps { interface ILanguageViewProps extends IBaseScreen<SettingsStackParamList, 'LanguageView'> {
user: { user: {
id: string; id: string;
language: string; language: string;
}; };
setUser(user: object): void;
appStart(params: any): void;
theme: string;
} }
interface ILanguageViewState { interface ILanguageViewState {
@ -69,11 +67,11 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
return; return;
} }
const { appStart, user } = this.props; const { dispatch, user } = this.props;
const shouldRestart = isRTL(language) || isRTL(user.language); const shouldRestart = isRTL(language) || isRTL(user.language);
await appStart({ root: ROOT_LOADING, text: I18n.t('Change_language_loading') }); dispatch(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Change_language_loading') }));
// shows loading for at least 300ms // shows loading for at least 300ms
await Promise.all([this.changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]); await Promise.all([this.changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]);
@ -81,13 +79,13 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
if (shouldRestart) { if (shouldRestart) {
await RNRestart.Restart(); await RNRestart.Restart();
} else { } else {
await appStart({ root: ROOT_INSIDE }); dispatch(appStart({ root: RootEnum.ROOT_INSIDE }));
} }
}; };
changeLanguage = async (language: string) => { changeLanguage = async (language: string) => {
logEvent(events.LANG_SET_LANGUAGE); logEvent(events.LANG_SET_LANGUAGE);
const { user, setUser } = this.props; const { user, dispatch } = this.props;
const params: { language?: string } = {}; const params: { language?: string } = {};
@ -98,7 +96,7 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
try { try {
await RocketChat.saveUserPreferences(params); await RocketChat.saveUserPreferences(params);
setUser({ language: params.language }); dispatch(setUser({ language: params.language }));
const serversDB = database.servers; const serversDB = database.servers;
const usersCollection = serversDB.get('users'); const usersCollection = serversDB.get('users');
@ -158,13 +156,8 @@ class LanguageView extends React.Component<ILanguageViewProps, ILanguageViewStat
} }
} }
const mapStateToProps = (state: any) => ({ const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state) user: getUserSelector(state)
}); });
const mapDispatchToProps = (dispatch: Dispatch) => ({ export default connect(mapStateToProps)(withTheme(LanguageView));
setUser: (params: any) => dispatch(setUserAction(params)),
appStart: (params: any) => dispatch(appStartAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));

View File

@ -8,7 +8,7 @@ import * as List from '../../containers/List';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
import { toggleServerDropdown } from '../../actions/rooms'; import { toggleServerDropdown } from '../../actions/rooms';
import { selectServerRequest, serverInitAdd } from '../../actions/server'; import { selectServerRequest, serverInitAdd } from '../../actions/server';
import { appStart, ROOT_OUTSIDE } from '../../actions/app'; import { appStart } from '../../actions/app';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n'; import I18n from '../../i18n';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
@ -24,6 +24,8 @@ import log, { events, logEvent } from '../../utils/log';
import { headerHeight } from '../../containers/Header'; import { headerHeight } from '../../containers/Header';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import UserPreferences from '../../lib/userPreferences'; import UserPreferences from '../../lib/userPreferences';
import { RootEnum } from '../../definitions';
import styles from './styles'; import styles from './styles';
const ROW_HEIGHT = 68; const ROW_HEIGHT = 68;
@ -106,7 +108,7 @@ class ServerDropdown extends Component {
navToNewServer = previousServer => { navToNewServer = previousServer => {
const { dispatch } = this.props; const { dispatch } = this.props;
batch(() => { batch(() => {
dispatch(appStart({ root: ROOT_OUTSIDE })); dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE }));
dispatch(serverInitAdd(previousServer)); dispatch(serverInitAdd(previousServer));
}); });
}; };

View File

@ -13,12 +13,13 @@ import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../presentation/R
import log, { logEvent, events } from '../../utils/log'; import log, { logEvent, events } from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { closeSearchHeader, closeServerDropdown, openSearchHeader, roomsRequest } from '../../actions/rooms'; import { closeSearchHeader, closeServerDropdown, openSearchHeader, roomsRequest } from '../../actions/rooms';
import { appStart, ROOT_OUTSIDE } from '../../actions/app'; import { appStart } from '../../actions/app';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import { serverInitAdd } from '../../actions/server';
import { animateNextTransition } from '../../utils/layoutAnimation'; import { animateNextTransition } from '../../utils/layoutAnimation';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -43,6 +44,7 @@ import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import { E2E_BANNER_TYPE } from '../../lib/encryption/constants'; import { E2E_BANNER_TYPE } from '../../lib/encryption/constants';
import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry';
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../../ee/omnichannel/lib'; import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../../ee/omnichannel/lib';
import { RootEnum } from '../../definitions';
import { DisplayMode, SortBy } from '../../constants/constantDisplayMode'; import { DisplayMode, SortBy } from '../../constants/constantDisplayMode';
import styles from './styles'; import styles from './styles';
import ServerDropdown from './ServerDropdown'; import ServerDropdown from './ServerDropdown';
@ -132,8 +134,7 @@ class RoomsListView extends React.Component {
createDirectMessagePermission: PropTypes.array, createDirectMessagePermission: PropTypes.array,
createPublicChannelPermission: PropTypes.array, createPublicChannelPermission: PropTypes.array,
createPrivateChannelPermission: PropTypes.array, createPrivateChannelPermission: PropTypes.array,
createDiscussionPermission: PropTypes.array, createDiscussionPermission: PropTypes.array
initAdd: PropTypes.func
}; };
constructor(props) { constructor(props) {
@ -831,7 +832,7 @@ class RoomsListView extends React.Component {
}; };
handleCommands = ({ event }) => { handleCommands = ({ event }) => {
const { navigation, server, isMasterDetail, dispatch, initAdd } = this.props; const { navigation, server, isMasterDetail, dispatch } = this.props;
const { input } = event; const { input } = event;
if (handleCommandShowPreferences(event)) { if (handleCommandShowPreferences(event)) {
navigation.navigate('SettingsView'); navigation.navigate('SettingsView');
@ -851,8 +852,8 @@ class RoomsListView extends React.Component {
} }
} else if (handleCommandAddNewServer(event)) { } else if (handleCommandAddNewServer(event)) {
batch(() => { batch(() => {
dispatch(appStart({ root: ROOT_OUTSIDE })); dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE }));
initAdd(server); dispatch(serverInitAdd(server));
}); });
} }
}; };

View File

@ -1,50 +1,44 @@
import CookieManager from '@react-native-cookies/cookies';
import { StackNavigationOptions } from '@react-navigation/stack';
import FastImage from '@rocket.chat/react-native-fast-image';
import React from 'react'; import React from 'react';
import { Clipboard, Linking, Share } from 'react-native'; import { Clipboard, Linking, Share } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import FastImage from '@rocket.chat/react-native-fast-image';
import CookieManager from '@react-native-cookies/cookies';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { SettingsStackParamList } from '../../stacks/types'; import { appStart } from '../../actions/app';
import { logout as logoutAction } from '../../actions/login'; import { logout } from '../../actions/login';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; import { selectServerRequest } from '../../actions/server';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { isFDroidBuild } from '../../constants/environment';
import { APP_STORE_LINK, FDROID_MARKET_LINK, LICENSE_LINK, PLAY_MARKET_LINK } from '../../constants/links';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { LISTENER } from '../../containers/Toast';
import { IApplicationState, IBaseScreen, RootEnum } from '../../definitions';
import I18n from '../../i18n'; import I18n from '../../i18n';
import database from '../../lib/database';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import { IServer } from '../../reducers/server';
import { getUserSelector } from '../../selectors/login';
import { SettingsStackParamList } from '../../stacks/types';
import { withTheme } from '../../theme';
import { getDeviceModel, getReadableVersion, isAndroid } from '../../utils/deviceInfo'; import { getDeviceModel, getReadableVersion, isAndroid } from '../../utils/deviceInfo';
import openLink from '../../utils/openLink'; import EventEmitter from '../../utils/events';
import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import { events, logEvent } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
import { APP_STORE_LINK, FDROID_MARKET_LINK, LICENSE_LINK, PLAY_MARKET_LINK } from '../../constants/links'; import openLink from '../../utils/openLink';
import { withTheme } from '../../theme';
import SidebarView from '../SidebarView';
import { LISTENER } from '../../containers/Toast';
import EventEmitter from '../../utils/events';
import { ROOT_LOADING, appStart as appStartAction } from '../../actions/app';
import { onReviewPress } from '../../utils/review'; import { onReviewPress } from '../../utils/review';
import SafeAreaView from '../../containers/SafeAreaView'; import SidebarView from '../SidebarView';
import database from '../../lib/database';
import { isFDroidBuild } from '../../constants/environment';
import { getUserSelector } from '../../selectors/login';
interface ISettingsViewProps { interface ISettingsViewProps extends IBaseScreen<SettingsStackParamList, 'SettingsView'> {
navigation: StackNavigationProp<SettingsStackParamList, 'SettingsView'>; server: IServer;
server: {
version: string;
server: string;
};
theme: string;
isMasterDetail: boolean; isMasterDetail: boolean;
logout: Function;
selectServerRequest: Function;
user: { user: {
roles: []; roles: [];
id: string; id: string;
}; };
appStart: Function;
} }
class SettingsView extends React.Component<ISettingsViewProps, any> { class SettingsView extends React.Component<ISettingsViewProps, any> {
@ -59,7 +53,7 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
}); });
checkCookiesAndLogout = async () => { checkCookiesAndLogout = async () => {
const { logout, user } = this.props; const { dispatch, user } = this.props;
const db = database.servers; const db = database.servers;
const usersCollection = db.get('users'); const usersCollection = db.get('users');
try { try {
@ -72,14 +66,14 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
dismissText: I18n.t('Clear_cookies_no'), dismissText: I18n.t('Clear_cookies_no'),
onPress: async () => { onPress: async () => {
await CookieManager.clearAll(true); await CookieManager.clearAll(true);
logout(); dispatch(logout());
}, },
onCancel: () => { onCancel: () => {
logout(); dispatch(logout());
} }
}); });
} else { } else {
logout(); dispatch(logout());
} }
} catch { } catch {
// Do nothing: user not found // Do nothing: user not found
@ -103,15 +97,14 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
onPress: async () => { onPress: async () => {
const { const {
server: { server }, server: { server },
appStart, dispatch
selectServerRequest
} = this.props; } = this.props;
appStart({ root: ROOT_LOADING, text: I18n.t('Clear_cache_loading') }); dispatch(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Clear_cache_loading') }));
await RocketChat.clearCache({ server }); await RocketChat.clearCache({ server });
await FastImage.clearMemoryCache(); await FastImage.clearMemoryCache();
await FastImage.clearDiskCache(); await FastImage.clearDiskCache();
RocketChat.disconnect(); RocketChat.disconnect();
selectServerRequest(server); dispatch(selectServerRequest(server));
} }
}); });
}; };
@ -156,8 +149,9 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
const { const {
server: { version } server: { version }
} = this.props; } = this.props;
logEvent(events.SE_COPY_SERVER_VERSION, { serverVersion: version }); const vers = version as string;
this.saveToClipboard(version); logEvent(events.SE_COPY_SERVER_VERSION, { serverVersion: vers });
this.saveToClipboard(vers);
}; };
copyAppVersion = () => { copyAppVersion = () => {
@ -298,16 +292,10 @@ class SettingsView extends React.Component<ISettingsViewProps, any> {
} }
} }
const mapStateToProps = (state: any) => ({ const mapStateToProps = (state: IApplicationState) => ({
server: state.server, server: state.server,
user: getUserSelector(state), user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail
}); });
const mapDispatchToProps = (dispatch: any) => ({ export default connect(mapStateToProps)(withTheme(SettingsView));
logout: () => dispatch(logoutAction()),
selectServerRequest: (params: any) => dispatch(selectServerRequestAction(params)),
appStart: (params: any) => dispatch(appStartAction(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(SettingsView));