[CHORE] Update react-navigation to v5 (#2154)
* react-navigation v5 installed
* compiling
* Outside working
* InsideStack compiling
* Switch stack
* Starting room
* RoomView header
* SafeAreaView
* Slide from right stack animation
* stash
* Fix params
* Create channel
* inapp notification
* Custom status
* Add server working
* Refactor appStart
* Attachment
* in-app notification
* AuthLoadingView
* Remove compat
* Navigation
* Outside animations
* Fix new server icon
* block modal
* AttachmentView header
* Remove unnecessary code
* SelectedUsersView header
* StatusView
* CreateDiscussionView
* RoomInfoView
* RoomInfoEditView style
* RoomMembersView
* RoomsListView header
* RoomView header
* Share extension
* getParam
* Focus/blur
* Trying to fix inapp
* Lint
* Simpler app container
* Update libs
* Revert "Simpler app container"
This reverts commit 1e49d80bb4
.
* Load messages faster
* Fix safearea on ReactionsModal
* Update safe area to v3
* lint
* Fix transition
* stash - drawer replace working
* stash - modal nav
* RoomActionsView as tablet modal
* RoomStack
* Stop showing RoomView header when there's no room
* Custom Header and different navigation based on stack
* Refactor setHeader
* MasterDetailContext
* RoomView header
* Fix isMasterDetail rule
* KeyCommands kind of working
* Create channel on tablet
* RoomView sCU
* Remove withSplit
* Settings opening as modal
* Settings
* StatusView headerLeft
* Admin panel
* TwoFactor style
* DirectoryView
* ServerDropdown and SortDropdown animations
* ThreadMessagesView
* Navigate to empty RoomView on server switch when in master detail
* ProfileView header
* Fix navigation issues
* Nav to any room info on tablet
* Room info
* Refactoring
* Fix rooms search
* Roomslist commands
* SearchMessagesView close modal
* Key commands
* Fix undefined subscription
* Disallow navigate to focused room
* isFocused state on RoomsListView
* Blur text inputs when focus is lost
* Replace animation
* Default nav theme
* Refactoring
* Always open Attachment with close modal button
* ModalContainer backdrop following themes
* Screen tracking
* Refactor get active route for in-app notification
* Only mark room as focused when in master detail layout
* Lint
* Open modals as fade from bottom on Android
* typo
* Fixing tests
* Fix in-app update
* Fixing goRoom issues
* Refactor stack names
* Fix unreadsCount
* Fix stack
* Fix header animation
* Refactor ShareNavigation
* Refactor navigation theme
* Make sure title is set
* Fix create discussion navigation
* Remove unused variable
* Create discussions from actions fixed
* Layout animation
* Screen lock on share extension
* Unnecessary change
* Admin border
* Set header after state callback
* Fix key commands on outside stack
* Fix back button pressed
* Remove layout animations from Android
* Tweak animations on Android
* Disable swipe gesture to open drawer
* Fix current item on RoomsListView
* Fix add server
* Fix drawer
* Fix broadcast
* LayoutAnimation instead of Transitions
* Fix onboarding back press
* Fix assorted tests
* Create discussion fix
* RoomInfoView header
* Drawer active item
This commit is contained in:
parent
5dcf2212e0
commit
98ed84ba5c
|
@ -0,0 +1,117 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context';
|
||||
|
||||
import Navigation from './lib/Navigation';
|
||||
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
|
||||
import {
|
||||
ROOT_LOADING, ROOT_OUTSIDE, ROOT_NEW_SERVER, ROOT_INSIDE, ROOT_SET_USERNAME, ROOT_BACKGROUND
|
||||
} from './actions/app';
|
||||
|
||||
// Stacks
|
||||
import AuthLoadingView from './views/AuthLoadingView';
|
||||
|
||||
// SetUsername Stack
|
||||
import SetUsernameView from './views/SetUsernameView';
|
||||
|
||||
import OutsideStack from './stacks/OutsideStack';
|
||||
import InsideStack from './stacks/InsideStack';
|
||||
import MasterDetailStack from './stacks/MasterDetailStack';
|
||||
import { ThemeContext } from './theme';
|
||||
import { setCurrentScreen } from './utils/log';
|
||||
|
||||
// SetUsernameStack
|
||||
const SetUsername = createStackNavigator();
|
||||
const SetUsernameStack = () => (
|
||||
<SetUsername.Navigator screenOptions={defaultHeader}>
|
||||
<SetUsername.Screen
|
||||
name='SetUsernameView'
|
||||
component={SetUsernameView}
|
||||
/>
|
||||
</SetUsername.Navigator>
|
||||
);
|
||||
|
||||
// App
|
||||
const Stack = createStackNavigator();
|
||||
const App = React.memo(({ root, isMasterDetail }) => {
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
const navTheme = navigationTheme(theme);
|
||||
|
||||
React.useEffect(() => {
|
||||
const state = Navigation.navigationRef.current.getRootState();
|
||||
const currentRouteName = getActiveRouteName(state);
|
||||
Navigation.routeNameRef.current = currentRouteName;
|
||||
setCurrentScreen(currentRouteName);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
||||
<NavigationContainer
|
||||
theme={navTheme}
|
||||
ref={Navigation.navigationRef}
|
||||
onStateChange={(state) => {
|
||||
const previousRouteName = Navigation.routeNameRef.current;
|
||||
const currentRouteName = getActiveRouteName(state);
|
||||
if (previousRouteName !== currentRouteName) {
|
||||
setCurrentScreen(currentRouteName);
|
||||
}
|
||||
Navigation.routeNameRef.current = currentRouteName;
|
||||
}}
|
||||
>
|
||||
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
|
||||
<>
|
||||
{root === ROOT_LOADING || root === ROOT_BACKGROUND ? (
|
||||
<Stack.Screen
|
||||
name='AuthLoading'
|
||||
component={AuthLoadingView}
|
||||
/>
|
||||
) : null}
|
||||
{root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? (
|
||||
<Stack.Screen
|
||||
name='OutsideStack'
|
||||
component={OutsideStack}
|
||||
/>
|
||||
) : null}
|
||||
{root === ROOT_INSIDE && isMasterDetail ? (
|
||||
<Stack.Screen
|
||||
name='MasterDetailStack'
|
||||
component={MasterDetailStack}
|
||||
/>
|
||||
) : null}
|
||||
{root === ROOT_INSIDE && !isMasterDetail ? (
|
||||
<Stack.Screen
|
||||
name='InsideStack'
|
||||
component={InsideStack}
|
||||
/>
|
||||
) : null}
|
||||
{root === ROOT_SET_USERNAME ? (
|
||||
<Stack.Screen
|
||||
name='SetUsernameStack'
|
||||
component={SetUsernameStack}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
});
|
||||
const mapStateToProps = state => ({
|
||||
root: state.app.root,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
App.propTypes = {
|
||||
root: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
const AppContainer = connect(mapStateToProps)(App);
|
||||
export default AppContainer;
|
|
@ -33,7 +33,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
|||
'CLOSE_SEARCH_HEADER'
|
||||
]);
|
||||
export const ROOM = createRequestTypes('ROOM', ['SUBSCRIBE', 'UNSUBSCRIBE', 'LEAVE', 'DELETE', 'REMOVED', 'CLOSE', 'FORWARD', 'USER_TYPING']);
|
||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { APP } from './actionsTypes';
|
||||
|
||||
export const ROOT_OUTSIDE = 'outside';
|
||||
export const ROOT_INSIDE = 'inside';
|
||||
export const ROOT_LOADING = 'loading';
|
||||
export const ROOT_NEW_SERVER = 'newServer';
|
||||
export const ROOT_SET_USERNAME = 'setUsername';
|
||||
export const ROOT_BACKGROUND = 'background';
|
||||
|
||||
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
|
||||
};
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import * as types from '../constants/types';
|
||||
import { APP } from './actionsTypes';
|
||||
|
||||
export function appStart(root, text) {
|
||||
return {
|
||||
type: APP.START,
|
||||
root,
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
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 setCurrentServer(server) {
|
||||
return {
|
||||
type: types.SET_CURRENT_SERVER,
|
||||
payload: server
|
||||
};
|
||||
}
|
||||
|
||||
export function login() {
|
||||
return {
|
||||
type: 'LOGIN'
|
||||
};
|
||||
}
|
|
@ -44,9 +44,10 @@ export function serverFailure(err) {
|
|||
};
|
||||
}
|
||||
|
||||
export function serverInitAdd() {
|
||||
export function serverInitAdd(previousServer) {
|
||||
return {
|
||||
type: SERVER.INIT_ADD
|
||||
type: SERVER.INIT_ADD,
|
||||
previousServer
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable no-bitwise */
|
||||
import { constants } from 'react-native-keycommands';
|
||||
import KeyCommands, { constants } from 'react-native-keycommands';
|
||||
|
||||
import I18n from './i18n';
|
||||
|
||||
|
@ -17,7 +17,7 @@ const KEY_ADD_SERVER = __DEV__ ? 'l' : 'n';
|
|||
const KEY_SEND_MESSAGE = '\r';
|
||||
const KEY_SELECT = '123456789';
|
||||
|
||||
export const defaultCommands = [
|
||||
const keyCommands = [
|
||||
{
|
||||
// Focus messageBox
|
||||
input: KEY_TYPING,
|
||||
|
@ -29,10 +29,7 @@ export const defaultCommands = [
|
|||
input: KEY_SEND_MESSAGE,
|
||||
modifierFlags: 0,
|
||||
discoverabilityTitle: I18n.t('Send')
|
||||
}
|
||||
];
|
||||
|
||||
export const keyCommands = [
|
||||
},
|
||||
{
|
||||
// Open Preferences Modal
|
||||
input: KEY_PREFERENCES,
|
||||
|
@ -139,6 +136,10 @@ export const keyCommands = [
|
|||
})))
|
||||
];
|
||||
|
||||
export const setKeyCommands = () => KeyCommands.setKeyCommands(keyCommands);
|
||||
|
||||
export const deleteKeyCommands = () => KeyCommands.deleteKeyCommands(keyCommands);
|
||||
|
||||
export const KEY_COMMAND = 'KEY_COMMAND';
|
||||
|
||||
export const commandHandle = (event, key, flags = []) => {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export const MAX_SIDEBAR_WIDTH = 321;
|
||||
export const MAX_CONTENT_WIDTH = '90%';
|
||||
export const MAX_SCREEN_CONTENT_WIDTH = '50%';
|
||||
export const MIN_WIDTH_SPLIT_LAYOUT = 700;
|
||||
export const MIN_WIDTH_MASTER_DETAIL_LAYOUT = 700;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
|
||||
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||
export const ADD_SETTINGS = 'ADD_SETTINGS';
|
||||
export const CLEAR_SETTINGS = 'CLEAR_SETTINGS';
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
|
||||
import { themes } from '../constants/colors';
|
||||
import sharedStyles from '../views/Styles';
|
||||
|
@ -10,6 +9,7 @@ import KeyboardView from '../presentation/KeyboardView';
|
|||
import StatusBar from './StatusBar';
|
||||
import AppVersion from './AppVersion';
|
||||
import { isTablet } from '../utils/deviceInfo';
|
||||
import SafeAreaView from './SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollView: {
|
||||
|
@ -31,7 +31,7 @@ const FormContainer = ({ children, theme, testID }) => (
|
|||
>
|
||||
<StatusBar theme={theme} />
|
||||
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
|
||||
<SafeAreaView style={sharedStyles.container} forceInset={{ top: 'never' }} testID={testID}>
|
||||
<SafeAreaView testID={testID} theme={theme} style={{ backgroundColor: themes[theme].backgroundColor }}>
|
||||
{children}
|
||||
<AppVersion theme={theme} />
|
||||
</SafeAreaView>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
|
||||
// Get from https://github.com/react-navigation/react-navigation/blob/master/packages/stack/src/views/Header/HeaderSegment.tsx#L69
|
||||
export const headerHeight = isIOS ? 44 : 56;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
height: headerHeight,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
elevation: 4
|
||||
}
|
||||
});
|
||||
|
||||
const Header = ({
|
||||
theme, headerLeft, headerTitle, headerRight
|
||||
}) => (
|
||||
<SafeAreaView style={{ backgroundColor: themes[theme].headerBackground }} edges={['top', 'left', 'right']}>
|
||||
<View style={[styles.container, { ...themedHeader(theme).headerStyle }]}>
|
||||
{headerLeft ? headerLeft() : null}
|
||||
{headerTitle ? headerTitle() : null}
|
||||
{headerRight ? headerRight() : null}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
Header.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
headerLeft: PropTypes.element,
|
||||
headerTitle: PropTypes.element,
|
||||
headerRight: PropTypes.element
|
||||
};
|
||||
|
||||
export default Header;
|
|
@ -5,7 +5,6 @@ import {
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { withNavigation } from 'react-navigation';
|
||||
|
||||
import { withTheme } from '../theme';
|
||||
import sharedStyles from '../views/Styles';
|
||||
|
@ -361,4 +360,4 @@ const mapDispatchToProps = dispatch => ({
|
|||
loginRequest: params => dispatch(loginRequestAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(withNavigation(LoginServices)));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LoginServices));
|
||||
|
|
|
@ -32,7 +32,8 @@ class MessageActions extends React.Component {
|
|||
Message_AllowEditing_BlockEditInMinutes: PropTypes.number,
|
||||
Message_AllowPinning: PropTypes.bool,
|
||||
Message_AllowStarring: PropTypes.bool,
|
||||
Message_Read_Receipt_Store_Users: PropTypes.bool
|
||||
Message_Read_Receipt_Store_Users: PropTypes.bool,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -254,7 +255,7 @@ class MessageActions extends React.Component {
|
|||
}
|
||||
|
||||
handleUnread = async() => {
|
||||
const { message, room } = this.props;
|
||||
const { message, room, isMasterDetail } = this.props;
|
||||
const { id: messageId, ts } = message;
|
||||
const { rid } = room;
|
||||
try {
|
||||
|
@ -270,7 +271,11 @@ class MessageActions extends React.Component {
|
|||
// do nothing
|
||||
}
|
||||
});
|
||||
Navigation.navigate('RoomsListView');
|
||||
if (isMasterDetail) {
|
||||
Navigation.replace('RoomView');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
|
@ -376,8 +381,13 @@ class MessageActions extends React.Component {
|
|||
}
|
||||
|
||||
handleCreateDiscussion = () => {
|
||||
const { message, room: channel } = this.props;
|
||||
Navigation.navigate('CreateDiscussionView', { message, channel });
|
||||
const { message, room: channel, isMasterDetail } = this.props;
|
||||
const params = { message, channel, showCloseModal: true };
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('ModalStackNavigator', { screen: 'CreateDiscussionView', params });
|
||||
} else {
|
||||
Navigation.navigate('NewMessageStackNavigator', { screen: 'CreateDiscussionView', params });
|
||||
}
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
|
@ -450,7 +460,8 @@ const mapStateToProps = state => ({
|
|||
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes,
|
||||
Message_AllowPinning: state.settings.Message_AllowPinning,
|
||||
Message_AllowStarring: state.settings.Message_AllowStarring,
|
||||
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users
|
||||
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(MessageActions);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, SafeAreaView, PermissionsAndroid, Text
|
||||
View, PermissionsAndroid, Text
|
||||
} from 'react-native';
|
||||
import { AudioRecorder, AudioUtils } from 'react-native-audio';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
|
@ -13,6 +13,7 @@ import I18n from '../../i18n';
|
|||
import { isIOS, isAndroid } from '../../utils/deviceInfo';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { themes } from '../../constants/colors';
|
||||
import SafeAreaView from '../SafeAreaView';
|
||||
|
||||
export const _formatTime = function(seconds) {
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
|
@ -134,6 +135,7 @@ export default class extends React.PureComponent {
|
|||
return (
|
||||
<SafeAreaView
|
||||
testID='messagebox-recording'
|
||||
theme={theme}
|
||||
style={[
|
||||
styles.textBox,
|
||||
{ borderTopColor: themes[theme].borderColor }
|
||||
|
|
|
@ -15,7 +15,6 @@ import { isIOS } from '../../utils/deviceInfo';
|
|||
import { themes } from '../../constants/colors';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { withTheme } from '../../theme';
|
||||
import { withSplit } from '../../split';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modal: {
|
||||
|
@ -91,7 +90,7 @@ class UploadModal extends Component {
|
|||
submit: PropTypes.func,
|
||||
window: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
isMasterDetail: PropTypes.bool
|
||||
}
|
||||
|
||||
state = {
|
||||
|
@ -113,16 +112,11 @@ class UploadModal extends Component {
|
|||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { name, description, file } = this.state;
|
||||
const {
|
||||
window, isVisible, split, theme
|
||||
} = this.props;
|
||||
const { window, isVisible, theme } = this.props;
|
||||
|
||||
if (nextState.name !== name) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.split !== split) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
|
@ -194,9 +188,9 @@ class UploadModal extends Component {
|
|||
}
|
||||
|
||||
renderPreview() {
|
||||
const { file, split, theme } = this.props;
|
||||
const { file, theme, isMasterDetail } = this.props;
|
||||
if (file.mime && file.mime.match(/image/)) {
|
||||
return (<Image source={{ isStatic: true, uri: file.path }} style={[styles.image, split && styles.bigPreview]} />);
|
||||
return (<Image source={{ isStatic: true, uri: file.path }} style={[styles.image, isMasterDetail && styles.bigPreview]} />);
|
||||
}
|
||||
if (file.mime && file.mime.match(/video/)) {
|
||||
return (
|
||||
|
@ -210,7 +204,7 @@ class UploadModal extends Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
window: { width }, isVisible, close, split, theme
|
||||
window: { width }, isVisible, close, isMasterDetail, theme
|
||||
} = this.props;
|
||||
const { name, description } = this.state;
|
||||
return (
|
||||
|
@ -225,7 +219,7 @@ class UploadModal extends Component {
|
|||
hideModalContentWhileAnimating
|
||||
avoidKeyboard
|
||||
>
|
||||
<View style={[styles.container, { width: width - 32, backgroundColor: themes[theme].chatComponentBackground }, split && [sharedStyles.modal, sharedStyles.modalFormSheet]]}>
|
||||
<View style={[styles.container, { width: width - 32, backgroundColor: themes[theme].chatComponentBackground }, isMasterDetail && sharedStyles.modalFormSheet]}>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Upload_file_question_mark')}</Text>
|
||||
</View>
|
||||
|
@ -252,4 +246,4 @@ class UploadModal extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default responsive(withTheme(withSplit(UploadModal)));
|
||||
export default responsive(withTheme(UploadModal));
|
||||
|
|
|
@ -95,6 +95,7 @@ class MessageBox extends Component {
|
|||
typing: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
replyCancel: PropTypes.func,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
navigation: PropTypes.object
|
||||
}
|
||||
|
||||
|
@ -183,11 +184,14 @@ class MessageBox extends Component {
|
|||
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
|
||||
this.didFocusListener = navigation.addListener('didFocus', () => {
|
||||
this.unsubscribeFocus = navigation.addListener('focus', () => {
|
||||
if (this.tracking && this.tracking.resetTracking) {
|
||||
this.tracking.resetTracking();
|
||||
}
|
||||
});
|
||||
this.unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
this.component?.blur();
|
||||
});
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
|
@ -268,8 +272,11 @@ class MessageBox extends Component {
|
|||
if (this.getSlashCommands && this.getSlashCommands.stop) {
|
||||
this.getSlashCommands.stop();
|
||||
}
|
||||
if (this.didFocusListener && this.didFocusListener.remove) {
|
||||
this.didFocusListener.remove();
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
if (this.unsubscribeBlur) {
|
||||
this.unsubscribeBlur();
|
||||
}
|
||||
if (isTablet) {
|
||||
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||
|
@ -592,7 +599,13 @@ class MessageBox extends Component {
|
|||
}
|
||||
|
||||
createDiscussion = () => {
|
||||
Navigation.navigate('CreateDiscussionView', { channel: this.room });
|
||||
const { isMasterDetail } = this.props;
|
||||
const params = { channel: this.room, showCloseModal: true };
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('ModalStackNavigator', { screen: 'CreateDiscussionView', params });
|
||||
} else {
|
||||
Navigation.navigate('NewMessageStackNavigator', { screen: 'CreateDiscussionView', params });
|
||||
}
|
||||
}
|
||||
|
||||
showUploadModal = (file) => {
|
||||
|
@ -875,7 +888,9 @@ class MessageBox extends Component {
|
|||
render() {
|
||||
console.count(`${ this.constructor.name }.render calls`);
|
||||
const { showEmojiKeyboard, file } = this.state;
|
||||
const { user, baseUrl, theme } = this.props;
|
||||
const {
|
||||
user, baseUrl, theme, isMasterDetail
|
||||
} = this.props;
|
||||
return (
|
||||
<MessageboxContext.Provider
|
||||
value={{
|
||||
|
@ -903,6 +918,7 @@ class MessageBox extends Component {
|
|||
file={file}
|
||||
close={() => this.setState({ file: {} })}
|
||||
submit={this.sendMediaMessage}
|
||||
isMasterDetail={isMasterDetail}
|
||||
/>
|
||||
</MessageboxContext.Provider>
|
||||
);
|
||||
|
@ -910,6 +926,7 @@ class MessageBox extends Component {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
baseUrl: state.server.server,
|
||||
threadsEnabled: state.settings.Threads_enabled,
|
||||
user: getUserSelector(state),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View, Text, FlatList, StyleSheet, SafeAreaView
|
||||
View, Text, FlatList, StyleSheet
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from 'react-native-modal';
|
||||
|
@ -12,8 +12,12 @@ import { CustomIcon } from '../lib/Icons';
|
|||
import sharedStyles from '../views/Styles';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import SafeAreaView from './SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: {
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 10
|
||||
|
@ -95,7 +99,7 @@ const ModalContent = React.memo(({
|
|||
}) => {
|
||||
if (message && message.reactions) {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1 }}>
|
||||
<SafeAreaView theme={props.theme} style={styles.safeArea}>
|
||||
<Touchable onPress={onClose}>
|
||||
<View style={styles.titleContainer}>
|
||||
<CustomIcon
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context';
|
||||
import { themes } from '../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
||||
const SafeAreaView = React.memo(({
|
||||
style, children, testID, theme, vertical = true, ...props
|
||||
}) => (
|
||||
<SafeAreaContext
|
||||
style={[styles.view, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
|
||||
edges={vertical ? ['right', 'left'] : undefined}
|
||||
testID={testID}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SafeAreaContext>
|
||||
));
|
||||
|
||||
SafeAreaView.propTypes = {
|
||||
testID: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
vertical: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.element
|
||||
};
|
||||
|
||||
export default SafeAreaView;
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
|
||||
const StatusBar = React.memo(({ theme }) => {
|
||||
let barStyle = 'light-content';
|
||||
|
@ -18,4 +17,4 @@ StatusBar.propTypes = {
|
|||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default withTheme(StatusBar);
|
||||
export default StatusBar;
|
||||
|
|
|
@ -5,12 +5,12 @@ import PropTypes from 'prop-types';
|
|||
import { sha256 } from 'js-sha256';
|
||||
import Modal from 'react-native-modal';
|
||||
import useDeepCompareEffect from 'use-deep-compare-effect';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import TextInput from '../TextInput';
|
||||
import I18n from '../../i18n';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import { withTheme } from '../../theme';
|
||||
import { withSplit } from '../../split';
|
||||
import { themes } from '../../constants/colors';
|
||||
import Button from '../Button';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
@ -36,7 +36,7 @@ const methods = {
|
|||
}
|
||||
};
|
||||
|
||||
const TwoFactor = React.memo(({ theme, split }) => {
|
||||
const TwoFactor = React.memo(({ theme, isMasterDetail }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [data, setData] = useState({});
|
||||
const [code, setCode] = useState('');
|
||||
|
@ -93,7 +93,7 @@ const TwoFactor = React.memo(({ theme, split }) => {
|
|||
hideModalContentWhileAnimating
|
||||
>
|
||||
<View style={styles.container} testID='two-factor'>
|
||||
<View style={[styles.content, split && [sharedStyles.modal, sharedStyles.modalFormSheet], { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<View style={[styles.content, isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text>
|
||||
{method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null}
|
||||
<TextInput
|
||||
|
@ -134,7 +134,11 @@ const TwoFactor = React.memo(({ theme, split }) => {
|
|||
});
|
||||
TwoFactor.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
export default withSplit(withTheme(TwoFactor));
|
||||
const mapStateToProps = state => ({
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(TwoFactor));
|
||||
|
|
|
@ -37,5 +37,8 @@ export default StyleSheet.create({
|
|||
buttonContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
tablet: {
|
||||
height: undefined
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,7 +15,6 @@ import { CustomIcon } from '../../lib/Icons';
|
|||
import sharedStyles from '../../views/Styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
||||
import { withSplit } from '../../split';
|
||||
import MessageContext from './Context';
|
||||
import ActivityIndicator from '../ActivityIndicator';
|
||||
|
||||
|
@ -98,7 +97,6 @@ class MessageAudio extends React.Component {
|
|||
static propTypes = {
|
||||
file: PropTypes.object.isRequired,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool,
|
||||
getCustomEmoji: PropTypes.func
|
||||
}
|
||||
|
||||
|
@ -138,7 +136,7 @@ class MessageAudio extends React.Component {
|
|||
const {
|
||||
currentTime, duration, paused, loading
|
||||
} = this.state;
|
||||
const { file, split, theme } = this.props;
|
||||
const { file, theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
|
@ -154,9 +152,6 @@ class MessageAudio extends React.Component {
|
|||
if (!equal(nextProps.file, file)) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.split !== split) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
|
@ -248,9 +243,7 @@ class MessageAudio extends React.Component {
|
|||
const {
|
||||
loading, paused, currentTime, duration
|
||||
} = this.state;
|
||||
const {
|
||||
file, getCustomEmoji, split, theme
|
||||
} = this.props;
|
||||
const { file, getCustomEmoji, theme } = this.props;
|
||||
const { description } = file;
|
||||
const { baseUrl, user } = this.context;
|
||||
|
||||
|
@ -263,8 +256,7 @@ class MessageAudio extends React.Component {
|
|||
<View
|
||||
style={[
|
||||
styles.audioContainer,
|
||||
{ backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor },
|
||||
split && sharedStyles.tabletContent
|
||||
{ backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor }
|
||||
]}
|
||||
>
|
||||
<Button loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} />
|
||||
|
@ -289,4 +281,4 @@ class MessageAudio extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default withSplit(MessageAudio);
|
||||
export default MessageAudio;
|
||||
|
|
|
@ -10,19 +10,17 @@ import Touchable from './Touchable';
|
|||
import Markdown from '../markdown';
|
||||
import styles from './styles';
|
||||
import { formatAttachmentUrl } from '../../lib/utils';
|
||||
import { withSplit } from '../../split';
|
||||
import { themes } from '../../constants/colors';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import MessageContext from './Context';
|
||||
|
||||
const ImageProgress = createImageProgress(FastImage);
|
||||
|
||||
const Button = React.memo(({
|
||||
children, onPress, split, theme
|
||||
children, onPress, theme
|
||||
}) => (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
style={[styles.imageContainer, split && sharedStyles.tabletContent]}
|
||||
style={styles.imageContainer}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
{children}
|
||||
|
@ -42,7 +40,7 @@ export const MessageImage = React.memo(({ img, theme }) => (
|
|||
));
|
||||
|
||||
const ImageContainer = React.memo(({
|
||||
file, imageUrl, showAttachment, getCustomEmoji, split, theme
|
||||
file, imageUrl, showAttachment, getCustomEmoji, theme
|
||||
}) => {
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||
|
@ -54,7 +52,7 @@ const ImageContainer = React.memo(({
|
|||
|
||||
if (file.description) {
|
||||
return (
|
||||
<Button split={split} theme={theme} onPress={onPress}>
|
||||
<Button theme={theme} onPress={onPress}>
|
||||
<View>
|
||||
<MessageImage img={img} theme={theme} />
|
||||
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
|
@ -64,19 +62,18 @@ const ImageContainer = React.memo(({
|
|||
}
|
||||
|
||||
return (
|
||||
<Button split={split} theme={theme} onPress={onPress}>
|
||||
<Button theme={theme} onPress={onPress}>
|
||||
<MessageImage img={img} theme={theme} />
|
||||
</Button>
|
||||
);
|
||||
}, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.split === nextProps.split && prevProps.theme === nextProps.theme);
|
||||
}, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
|
||||
|
||||
ImageContainer.propTypes = {
|
||||
file: PropTypes.object,
|
||||
imageUrl: PropTypes.string,
|
||||
showAttachment: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
split: PropTypes.bool
|
||||
getCustomEmoji: PropTypes.func
|
||||
};
|
||||
ImageContainer.displayName = 'MessageImageContainer';
|
||||
|
||||
|
@ -89,9 +86,8 @@ ImageContainer.displayName = 'MessageImage';
|
|||
Button.propTypes = {
|
||||
children: PropTypes.node,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
theme: PropTypes.string
|
||||
};
|
||||
ImageContainer.displayName = 'MessageButton';
|
||||
|
||||
export default withSplit(ImageContainer);
|
||||
export default ImageContainer;
|
||||
|
|
|
@ -9,7 +9,6 @@ import Markdown from '../markdown';
|
|||
import openLink from '../../utils/openLink';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withSplit } from '../../split';
|
||||
import MessageContext from './Context';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -126,7 +125,7 @@ const Fields = React.memo(({ attachment, theme }) => {
|
|||
}, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme);
|
||||
|
||||
const Reply = React.memo(({
|
||||
attachment, timeFormat, index, getCustomEmoji, split, theme
|
||||
attachment, timeFormat, index, getCustomEmoji, theme
|
||||
}) => {
|
||||
if (!attachment) {
|
||||
return null;
|
||||
|
@ -156,8 +155,7 @@ const Reply = React.memo(({
|
|||
{
|
||||
backgroundColor: themes[theme].chatComponentBackground,
|
||||
borderColor: themes[theme].borderColor
|
||||
},
|
||||
split && sharedStyles.tabletContent
|
||||
}
|
||||
]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
|
@ -173,15 +171,14 @@ const Reply = React.memo(({
|
|||
</View>
|
||||
</Touchable>
|
||||
);
|
||||
}, (prevProps, nextProps) => isEqual(prevProps.attachment, nextProps.attachment) && prevProps.split === nextProps.split && prevProps.theme === nextProps.theme);
|
||||
}, (prevProps, nextProps) => isEqual(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme);
|
||||
|
||||
Reply.propTypes = {
|
||||
attachment: PropTypes.object,
|
||||
timeFormat: PropTypes.string,
|
||||
index: PropTypes.number,
|
||||
theme: PropTypes.string,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
split: PropTypes.bool
|
||||
getCustomEmoji: PropTypes.func
|
||||
};
|
||||
Reply.displayName = 'MessageReply';
|
||||
|
||||
|
@ -205,4 +202,4 @@ Fields.propTypes = {
|
|||
};
|
||||
Fields.displayName = 'MessageReplyFields';
|
||||
|
||||
export default withSplit(Reply);
|
||||
export default Reply;
|
||||
|
|
|
@ -11,7 +11,6 @@ import openLink from '../../utils/openLink';
|
|||
import sharedStyles from '../../views/Styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { withSplit } from '../../split';
|
||||
import { LISTENER } from '../Toast';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -80,9 +79,7 @@ const UrlContent = React.memo(({ title, description, theme }) => (
|
|||
return true;
|
||||
});
|
||||
|
||||
const Url = React.memo(({
|
||||
url, index, split, theme
|
||||
}) => {
|
||||
const Url = React.memo(({ url, index, theme }) => {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
@ -105,8 +102,7 @@ const Url = React.memo(({
|
|||
{
|
||||
backgroundColor: themes[theme].chatComponentBackground,
|
||||
borderColor: themes[theme].borderColor
|
||||
},
|
||||
split && sharedStyles.tabletContent
|
||||
}
|
||||
]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
|
@ -116,19 +112,17 @@ const Url = React.memo(({
|
|||
</>
|
||||
</Touchable>
|
||||
);
|
||||
}, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
|
||||
}, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.theme === newProps.theme);
|
||||
|
||||
const Urls = React.memo(({
|
||||
urls, split, theme
|
||||
}) => {
|
||||
const Urls = React.memo(({ urls, theme }) => {
|
||||
if (!urls || urls.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return urls.map((url, index) => (
|
||||
<Url url={url} key={url.url} index={index} split={split} theme={theme} />
|
||||
<Url url={url} key={url.url} index={index} theme={theme} />
|
||||
));
|
||||
}, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
|
||||
}, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme);
|
||||
|
||||
UrlImage.propTypes = {
|
||||
image: PropTypes.string
|
||||
|
@ -145,16 +139,14 @@ UrlContent.displayName = 'MessageUrlContent';
|
|||
Url.propTypes = {
|
||||
url: PropTypes.object.isRequired,
|
||||
index: PropTypes.number,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
theme: PropTypes.string
|
||||
};
|
||||
Url.displayName = 'MessageUrl';
|
||||
|
||||
Urls.propTypes = {
|
||||
urls: PropTypes.array,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
theme: PropTypes.string
|
||||
};
|
||||
Urls.displayName = 'MessageUrls';
|
||||
|
||||
export default withTheme(withSplit(Urls));
|
||||
export default withTheme(Urls);
|
||||
|
|
|
@ -6,11 +6,10 @@ import isEqual from 'deep-equal';
|
|||
import Touchable from './Touchable';
|
||||
import Markdown from '../markdown';
|
||||
import openLink from '../../utils/openLink';
|
||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { formatAttachmentUrl } from '../../lib/utils';
|
||||
import { themes } from '../../constants/colors';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import MessageContext from './Context';
|
||||
|
||||
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
||||
|
@ -46,7 +45,7 @@ const Video = React.memo(({
|
|||
<>
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
style={[styles.button, { backgroundColor: themes[theme].videoBackground }, isTablet && sharedStyles.tabletContent]}
|
||||
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
<CustomIcon
|
||||
|
|
649
app/index.js
649
app/index.js
|
@ -1,16 +1,10 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View, Linking, BackHandler, ScrollView
|
||||
} from 'react-native';
|
||||
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
|
||||
import { createStackNavigator } from 'react-navigation-stack';
|
||||
import { createDrawerNavigator } from 'react-navigation-drawer';
|
||||
import { Linking, Dimensions } from 'react-native';
|
||||
import { AppearanceProvider } from 'react-native-appearance';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
import Modal from 'react-native-modal';
|
||||
import KeyCommands, { KeyCommandsEmitter } from 'react-native-keycommands';
|
||||
import { KeyCommandsEmitter } from 'react-native-keycommands';
|
||||
import RNScreens from 'react-native-screens';
|
||||
|
||||
import {
|
||||
defaultTheme,
|
||||
|
@ -19,40 +13,25 @@ import {
|
|||
unsubscribeTheme
|
||||
} from './utils/theme';
|
||||
import EventEmitter from './utils/events';
|
||||
import { appInit, appInitLocalSettings } from './actions';
|
||||
import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app';
|
||||
import { deepLinkingOpen } from './actions/deepLinking';
|
||||
import Navigation from './lib/Navigation';
|
||||
import Sidebar from './views/SidebarView';
|
||||
import parseQuery from './lib/methods/helpers/parseQuery';
|
||||
import { initializePushNotifications, onNotification } from './notifications/push';
|
||||
import store from './lib/createStore';
|
||||
import NotificationBadge from './notifications/inApp';
|
||||
import {
|
||||
defaultHeader, onNavigationStateChange, cardStyle, getActiveRouteName
|
||||
} from './utils/navigation';
|
||||
import { loggerConfig, analytics } from './utils/log';
|
||||
import Toast from './containers/Toast';
|
||||
import { ThemeContext } from './theme';
|
||||
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
||||
import { MIN_WIDTH_SPLIT_LAYOUT } from './constants/tablet';
|
||||
import { MIN_WIDTH_MASTER_DETAIL_LAYOUT } from './constants/tablet';
|
||||
import {
|
||||
isTablet, isSplited, isIOS, setWidth, supportSystemTheme, isAndroid
|
||||
isTablet, supportSystemTheme
|
||||
} from './utils/deviceInfo';
|
||||
import { KEY_COMMAND } from './commands';
|
||||
import Tablet, { initTabletNav } from './tablet';
|
||||
import sharedStyles from './views/Styles';
|
||||
import { SplitContext } from './split';
|
||||
import AppContainer from './AppContainer';
|
||||
import TwoFactor from './containers/TwoFactor';
|
||||
|
||||
import RoomsListView from './views/RoomsListView';
|
||||
import RoomView from './views/RoomView';
|
||||
import ScreenLockedView from './views/ScreenLockedView';
|
||||
import ChangePasscodeView from './views/ChangePasscodeView';
|
||||
|
||||
if (isIOS) {
|
||||
const RNScreens = require('react-native-screens');
|
||||
RNScreens.useScreens();
|
||||
}
|
||||
RNScreens.enableScreens();
|
||||
|
||||
const parseDeepLinking = (url) => {
|
||||
if (url) {
|
||||
|
@ -68,543 +47,12 @@ const parseDeepLinking = (url) => {
|
|||
return null;
|
||||
};
|
||||
|
||||
// Outside
|
||||
const OutsideStack = createStackNavigator({
|
||||
OnboardingView: {
|
||||
getScreen: () => require('./views/OnboardingView').default,
|
||||
header: null
|
||||
},
|
||||
NewServerView: {
|
||||
getScreen: () => require('./views/NewServerView').default
|
||||
},
|
||||
WorkspaceView: {
|
||||
getScreen: () => require('./views/WorkspaceView').default
|
||||
},
|
||||
LoginView: {
|
||||
getScreen: () => require('./views/LoginView').default
|
||||
},
|
||||
ForgotPasswordView: {
|
||||
getScreen: () => require('./views/ForgotPasswordView').default
|
||||
},
|
||||
RegisterView: {
|
||||
getScreen: () => require('./views/RegisterView').default
|
||||
},
|
||||
LegalView: {
|
||||
getScreen: () => require('./views/LegalView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const AuthenticationWebViewStack = createStackNavigator({
|
||||
AuthenticationWebView: {
|
||||
getScreen: () => require('./views/AuthenticationWebView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const OutsideStackModal = createStackNavigator({
|
||||
OutsideStack,
|
||||
AuthenticationWebViewStack
|
||||
},
|
||||
{
|
||||
mode: 'modal',
|
||||
headerMode: 'none',
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const RoomRoutes = {
|
||||
RoomView,
|
||||
ThreadMessagesView: {
|
||||
getScreen: () => require('./views/ThreadMessagesView').default
|
||||
},
|
||||
MarkdownTableView: {
|
||||
getScreen: () => require('./views/MarkdownTableView').default
|
||||
},
|
||||
ReadReceiptsView: {
|
||||
getScreen: () => require('./views/ReadReceiptView').default
|
||||
}
|
||||
};
|
||||
|
||||
// Inside
|
||||
const ChatsStack = createStackNavigator({
|
||||
RoomsListView,
|
||||
RoomActionsView: {
|
||||
getScreen: () => require('./views/RoomActionsView').default
|
||||
},
|
||||
RoomInfoView: {
|
||||
getScreen: () => require('./views/RoomInfoView').default
|
||||
},
|
||||
RoomInfoEditView: {
|
||||
getScreen: () => require('./views/RoomInfoEditView').default
|
||||
},
|
||||
RoomMembersView: {
|
||||
getScreen: () => require('./views/RoomMembersView').default
|
||||
},
|
||||
SearchMessagesView: {
|
||||
getScreen: () => require('./views/SearchMessagesView').default
|
||||
},
|
||||
SelectedUsersView: {
|
||||
getScreen: () => require('./views/SelectedUsersView').default
|
||||
},
|
||||
InviteUsersView: {
|
||||
getScreen: () => require('./views/InviteUsersView').default
|
||||
},
|
||||
InviteUsersEditView: {
|
||||
getScreen: () => require('./views/InviteUsersEditView').default
|
||||
},
|
||||
MessagesView: {
|
||||
getScreen: () => require('./views/MessagesView').default
|
||||
},
|
||||
AutoTranslateView: {
|
||||
getScreen: () => require('./views/AutoTranslateView').default
|
||||
},
|
||||
DirectoryView: {
|
||||
getScreen: () => require('./views/DirectoryView').default
|
||||
},
|
||||
NotificationPrefView: {
|
||||
getScreen: () => require('./views/NotificationPreferencesView').default
|
||||
},
|
||||
VisitorNavigationView: {
|
||||
getScreen: () => require('./views/VisitorNavigationView').default
|
||||
},
|
||||
ForwardLivechatView: {
|
||||
getScreen: () => require('./views/ForwardLivechatView').default
|
||||
},
|
||||
LivechatEditView: {
|
||||
getScreen: () => require('./views/LivechatEditView').default
|
||||
},
|
||||
PickerView: {
|
||||
getScreen: () => require('./views/PickerView').default
|
||||
},
|
||||
...RoomRoutes
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
// Inside
|
||||
const RoomStack = createStackNavigator({
|
||||
...RoomRoutes
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
ChatsStack.navigationOptions = ({ navigation }) => {
|
||||
let drawerLockMode = 'unlocked';
|
||||
if (navigation.state.index > 0 || isSplited()) {
|
||||
drawerLockMode = 'locked-closed';
|
||||
}
|
||||
return {
|
||||
drawerLockMode
|
||||
};
|
||||
};
|
||||
|
||||
const ProfileStack = createStackNavigator({
|
||||
ProfileView: {
|
||||
getScreen: () => require('./views/ProfileView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
ProfileStack.navigationOptions = ({ navigation }) => {
|
||||
let drawerLockMode = 'unlocked';
|
||||
if (navigation.state.index > 0) {
|
||||
drawerLockMode = 'locked-closed';
|
||||
}
|
||||
return {
|
||||
drawerLockMode
|
||||
};
|
||||
};
|
||||
|
||||
const SettingsStack = createStackNavigator({
|
||||
SettingsView: {
|
||||
getScreen: () => require('./views/SettingsView').default
|
||||
},
|
||||
LanguageView: {
|
||||
getScreen: () => require('./views/LanguageView').default
|
||||
},
|
||||
ThemeView: {
|
||||
getScreen: () => require('./views/ThemeView').default
|
||||
},
|
||||
DefaultBrowserView: {
|
||||
getScreen: () => require('./views/DefaultBrowserView').default
|
||||
},
|
||||
ScreenLockConfigView: {
|
||||
getScreen: () => require('./views/ScreenLockConfigView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const AdminPanelStack = createStackNavigator({
|
||||
AdminPanelView: {
|
||||
getScreen: () => require('./views/AdminPanelView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
SettingsStack.navigationOptions = ({ navigation }) => {
|
||||
let drawerLockMode = 'unlocked';
|
||||
if (navigation.state.index > 0) {
|
||||
drawerLockMode = 'locked-closed';
|
||||
}
|
||||
return {
|
||||
drawerLockMode
|
||||
};
|
||||
};
|
||||
|
||||
const ChatsDrawer = createDrawerNavigator({
|
||||
ChatsStack,
|
||||
ProfileStack,
|
||||
SettingsStack,
|
||||
AdminPanelStack
|
||||
}, {
|
||||
contentComponent: Sidebar,
|
||||
overlayColor: '#00000090'
|
||||
});
|
||||
|
||||
const NewMessageStack = createStackNavigator({
|
||||
NewMessageView: {
|
||||
getScreen: () => require('./views/NewMessageView').default
|
||||
},
|
||||
SelectedUsersViewCreateChannel: {
|
||||
getScreen: () => require('./views/SelectedUsersView').default
|
||||
},
|
||||
CreateChannelView: {
|
||||
getScreen: () => require('./views/CreateChannelView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const AttachmentStack = createStackNavigator({
|
||||
AttachmentView: {
|
||||
getScreen: () => require('./views/AttachmentView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const ModalBlockStack = createStackNavigator({
|
||||
ModalBlockView: {
|
||||
getScreen: () => require('./views/ModalBlockView').default
|
||||
}
|
||||
}, {
|
||||
mode: 'modal',
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const CreateDiscussionStack = createStackNavigator({
|
||||
CreateDiscussionView: {
|
||||
getScreen: () => require('./views/CreateDiscussionView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const StatusStack = createStackNavigator({
|
||||
StatusView: {
|
||||
getScreen: () => require('./views/StatusView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const InsideStackModal = createStackNavigator({
|
||||
Main: ChatsDrawer,
|
||||
NewMessageStack,
|
||||
AttachmentStack,
|
||||
ModalBlockStack,
|
||||
StatusStack,
|
||||
CreateDiscussionStack,
|
||||
JitsiMeetView: {
|
||||
getScreen: () => require('./views/JitsiMeetView').default
|
||||
}
|
||||
},
|
||||
{
|
||||
mode: 'modal',
|
||||
headerMode: 'none',
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const SetUsernameStack = createStackNavigator({
|
||||
SetUsernameView: {
|
||||
getScreen: () => require('./views/SetUsernameView').default
|
||||
}
|
||||
},
|
||||
{
|
||||
cardStyle
|
||||
});
|
||||
|
||||
class CustomInsideStack extends React.Component {
|
||||
static router = InsideStackModal.router;
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
screenProps: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation, screenProps } = this.props;
|
||||
return (
|
||||
<>
|
||||
<InsideStackModal navigation={navigation} screenProps={screenProps} />
|
||||
{ !isTablet ? <NotificationBadge navigation={navigation} /> : null }
|
||||
{ !isTablet ? <Toast /> : null }
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomRoomStack extends React.Component {
|
||||
static router = RoomStack.router;
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
screenProps: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation, screenProps } = this.props;
|
||||
return (
|
||||
<>
|
||||
<RoomStack navigation={navigation} screenProps={screenProps} />
|
||||
<Toast />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MessagesStack = createStackNavigator({
|
||||
NewMessageView: {
|
||||
getScreen: () => require('./views/NewMessageView').default
|
||||
},
|
||||
SelectedUsersViewCreateChannel: {
|
||||
getScreen: () => require('./views/SelectedUsersView').default
|
||||
},
|
||||
CreateChannelView: {
|
||||
getScreen: () => require('./views/CreateChannelView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const DirectoryStack = createStackNavigator({
|
||||
DirectoryView: {
|
||||
getScreen: () => require('./views/DirectoryView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const SidebarStack = createStackNavigator({
|
||||
SettingsView: {
|
||||
getScreen: () => require('./views/SettingsView').default
|
||||
},
|
||||
ProfileView: {
|
||||
getScreen: () => require('./views/ProfileView').default
|
||||
},
|
||||
AdminPanelView: {
|
||||
getScreen: () => require('./views/AdminPanelView').default
|
||||
},
|
||||
StatusView: {
|
||||
getScreen: () => require('./views/StatusView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
const RoomActionsStack = createStackNavigator({
|
||||
RoomActionsView: {
|
||||
getScreen: () => require('./views/RoomActionsView').default
|
||||
},
|
||||
RoomInfoView: {
|
||||
getScreen: () => require('./views/RoomInfoView').default
|
||||
},
|
||||
RoomInfoEditView: {
|
||||
getScreen: () => require('./views/RoomInfoEditView').default
|
||||
},
|
||||
RoomMembersView: {
|
||||
getScreen: () => require('./views/RoomMembersView').default
|
||||
},
|
||||
SearchMessagesView: {
|
||||
getScreen: () => require('./views/SearchMessagesView').default
|
||||
},
|
||||
SelectedUsersView: {
|
||||
getScreen: () => require('./views/SelectedUsersView').default
|
||||
},
|
||||
MessagesView: {
|
||||
getScreen: () => require('./views/MessagesView').default
|
||||
},
|
||||
AutoTranslateView: {
|
||||
getScreen: () => require('./views/AutoTranslateView').default
|
||||
},
|
||||
ReadReceiptsView: {
|
||||
getScreen: () => require('./views/ReadReceiptView').default
|
||||
},
|
||||
NotificationPrefView: {
|
||||
getScreen: () => require('./views/NotificationPreferencesView').default
|
||||
},
|
||||
AttachmentView: {
|
||||
getScreen: () => require('./views/AttachmentView').default
|
||||
},
|
||||
PickerView: {
|
||||
getScreen: () => require('./views/PickerView').default
|
||||
}
|
||||
}, {
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
|
||||
|
||||
const ModalSwitch = createSwitchNavigator({
|
||||
MessagesStack,
|
||||
DirectoryStack,
|
||||
SidebarStack,
|
||||
RoomActionsStack,
|
||||
SettingsStack,
|
||||
ModalBlockStack,
|
||||
CreateDiscussionStack,
|
||||
AuthLoading: () => null
|
||||
},
|
||||
{
|
||||
initialRouteName: 'AuthLoading'
|
||||
});
|
||||
|
||||
class CustomModalStack extends React.Component {
|
||||
static router = ModalSwitch.router;
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
showModal: PropTypes.bool,
|
||||
closeModal: PropTypes.func,
|
||||
screenProps: PropTypes.object
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.closeModal);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.backHandler.remove();
|
||||
}
|
||||
|
||||
closeModal = () => {
|
||||
const { closeModal, navigation } = this.props;
|
||||
const { state } = navigation;
|
||||
if (state && state.routes[state.index] && state.routes[state.index].index === 0) {
|
||||
closeModal();
|
||||
return true;
|
||||
}
|
||||
if (state && state.routes[state.index] && state.routes[state.index].routes && state.routes[state.index].routes.length > 1) {
|
||||
navigation.goBack();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
navigation, showModal, closeModal, screenProps
|
||||
} = this.props;
|
||||
|
||||
const pageSheetViews = ['AttachmentView'];
|
||||
const pageSheet = pageSheetViews.includes(getActiveRouteName(navigation.state));
|
||||
|
||||
const androidProps = isAndroid && !pageSheet && {
|
||||
style: { marginBottom: 0 }
|
||||
};
|
||||
|
||||
let content = (
|
||||
<View style={[sharedStyles.modal, pageSheet ? sharedStyles.modalPageSheet : sharedStyles.modalFormSheet]}>
|
||||
<ModalSwitch navigation={navigation} screenProps={{ ...screenProps, closeModal: this.closeModal }} />
|
||||
</View>
|
||||
);
|
||||
|
||||
if (isAndroid && !pageSheet) {
|
||||
content = (
|
||||
<ScrollView overScrollMode='never'>
|
||||
{content}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
useNativeDriver
|
||||
coverScreen={false}
|
||||
isVisible={showModal}
|
||||
onBackdropPress={closeModal}
|
||||
hideModalContentWhileAnimating
|
||||
avoidKeyboard
|
||||
{...androidProps}
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNotificationStack extends React.Component {
|
||||
static router = InsideStackModal.router;
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
screenProps: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation, screenProps } = this.props;
|
||||
return <NotificationBadge navigation={navigation} screenProps={screenProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
export const App = createAppContainer(createSwitchNavigator(
|
||||
{
|
||||
OutsideStack: OutsideStackModal,
|
||||
InsideStack: CustomInsideStack,
|
||||
AuthLoading: {
|
||||
getScreen: () => require('./views/AuthLoadingView').default
|
||||
},
|
||||
SetUsernameStack
|
||||
},
|
||||
{
|
||||
initialRouteName: 'AuthLoading'
|
||||
}
|
||||
));
|
||||
|
||||
export const RoomContainer = createAppContainer(CustomRoomStack);
|
||||
|
||||
export const ModalContainer = createAppContainer(CustomModalStack);
|
||||
|
||||
export const NotificationContainer = createAppContainer(CustomNotificationStack);
|
||||
|
||||
export default class Root extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.init();
|
||||
this.initCrashReport();
|
||||
this.state = {
|
||||
split: false,
|
||||
inside: false,
|
||||
showModal: false,
|
||||
theme: defaultTheme(),
|
||||
themePreferences: {
|
||||
currentTheme: supportSystemTheme() ? 'automatic' : 'light',
|
||||
|
@ -625,22 +73,12 @@ export default class Root extends React.Component {
|
|||
}
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
componentDidUpdate(_, prevState) {
|
||||
if (isTablet) {
|
||||
const { split, inside } = this.state;
|
||||
if (inside && split !== prevState.split) {
|
||||
// Reset app on split mode changes
|
||||
Navigation.navigate('RoomsListView');
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
Dimensions.addEventListener('change', this.onDimensionsChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.listenerTimeout);
|
||||
Dimensions.removeEventListener('change', this.onDimensionsChange);
|
||||
|
||||
unsubscribeTheme();
|
||||
|
||||
|
@ -663,6 +101,22 @@ export default class Root extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
getMasterDetail = (width) => {
|
||||
if (!isTablet) {
|
||||
return false;
|
||||
}
|
||||
return width > MIN_WIDTH_MASTER_DETAIL_LAYOUT;
|
||||
}
|
||||
|
||||
setMasterDetail = (width) => {
|
||||
const isMasterDetail = this.getMasterDetail(width);
|
||||
store.dispatch(setMasterDetailAction(isMasterDetail));
|
||||
};
|
||||
|
||||
onDimensionsChange = ({ window: { width } }) => {
|
||||
this.setMasterDetail(width);
|
||||
}
|
||||
|
||||
setTheme = (newTheme = {}) => {
|
||||
// change theme state
|
||||
this.setState(prevState => newThemeState(prevState, newTheme), () => {
|
||||
|
@ -672,12 +126,14 @@ export default class Root extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
initTablet = async() => {
|
||||
initTabletNav(args => this.setState(args));
|
||||
await KeyCommands.setKeyCommands([]);
|
||||
initTablet = () => {
|
||||
const { width } = Dimensions.get('window');
|
||||
this.setMasterDetail(width);
|
||||
this.onKeyCommands = KeyCommandsEmitter.addListener(
|
||||
'onKeyCommand',
|
||||
command => EventEmitter.emit(KEY_COMMAND, { event: command })
|
||||
(command) => {
|
||||
EventEmitter.emit(KEY_COMMAND, { event: command });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -692,45 +148,8 @@ export default class Root extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onLayout = ({ nativeEvent: { layout: { width } } }) => (isTablet ? this.setSplit(width) : null);
|
||||
|
||||
setSplit = (width) => {
|
||||
this.setState({ split: width > MIN_WIDTH_SPLIT_LAYOUT });
|
||||
setWidth(width);
|
||||
}
|
||||
|
||||
closeModal = () => this.setState({ showModal: false });
|
||||
|
||||
render() {
|
||||
const { split, themePreferences, theme } = this.state;
|
||||
|
||||
let content = (
|
||||
<App
|
||||
ref={(navigatorRef) => {
|
||||
Navigation.setTopLevelNavigator(navigatorRef);
|
||||
}}
|
||||
screenProps={{ split, theme }}
|
||||
onNavigationStateChange={onNavigationStateChange}
|
||||
/>
|
||||
);
|
||||
|
||||
if (isTablet) {
|
||||
const { inside, showModal } = this.state;
|
||||
content = (
|
||||
<SplitContext.Provider value={{ split }}>
|
||||
<Tablet
|
||||
theme={theme}
|
||||
tablet={split}
|
||||
inside={inside}
|
||||
showModal={showModal}
|
||||
closeModal={this.closeModal}
|
||||
onLayout={this.onLayout}
|
||||
>
|
||||
{content}
|
||||
</Tablet>
|
||||
</SplitContext.Provider>
|
||||
);
|
||||
}
|
||||
const { themePreferences, theme } = this.state;
|
||||
return (
|
||||
<AppearanceProvider>
|
||||
<Provider store={store}>
|
||||
|
@ -741,7 +160,7 @@ export default class Root extends React.Component {
|
|||
setTheme: this.setTheme
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
<AppContainer />
|
||||
<TwoFactor />
|
||||
<ScreenLockedView />
|
||||
<ChangePasscodeView />
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { NavigationActions } from 'react-navigation';
|
||||
|
||||
let _navigatorModal;
|
||||
|
||||
function setTopLevelNavigator(navigatorRef) {
|
||||
_navigatorModal = navigatorRef;
|
||||
}
|
||||
|
||||
function navigate(routeName, params) {
|
||||
_navigatorModal.dispatch(
|
||||
NavigationActions.navigate({
|
||||
routeName,
|
||||
params
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
navigate,
|
||||
setTopLevelNavigator
|
||||
};
|
|
@ -1,28 +1,25 @@
|
|||
import { NavigationActions } from 'react-navigation';
|
||||
import * as React from 'react';
|
||||
import { CommonActions, StackActions } from '@react-navigation/native';
|
||||
|
||||
let _navigator;
|
||||
const navigationRef = React.createRef();
|
||||
const routeNameRef = React.createRef();
|
||||
|
||||
function setTopLevelNavigator(navigatorRef) {
|
||||
_navigator = navigatorRef;
|
||||
function navigate(name, params) {
|
||||
navigationRef.current?.navigate(name, params);
|
||||
}
|
||||
|
||||
function back() {
|
||||
_navigator.dispatch(
|
||||
NavigationActions.back()
|
||||
);
|
||||
navigationRef.current?.dispatch(CommonActions.goBack());
|
||||
}
|
||||
|
||||
function navigate(routeName, params) {
|
||||
_navigator.dispatch(
|
||||
NavigationActions.navigate({
|
||||
routeName,
|
||||
params
|
||||
})
|
||||
);
|
||||
function replace(name, params) {
|
||||
navigationRef.current?.dispatch(StackActions.replace(name, params));
|
||||
}
|
||||
|
||||
export default {
|
||||
back,
|
||||
navigationRef,
|
||||
routeNameRef,
|
||||
navigate,
|
||||
setTopLevelNavigator
|
||||
back,
|
||||
replace
|
||||
};
|
||||
|
|
|
@ -1,21 +1,9 @@
|
|||
import { NavigationActions } from 'react-navigation';
|
||||
import { createRef } from 'react';
|
||||
|
||||
let _shareNavigator;
|
||||
|
||||
function setTopLevelNavigator(navigatorRef) {
|
||||
_shareNavigator = navigatorRef;
|
||||
}
|
||||
|
||||
function navigate(routeName, params) {
|
||||
_shareNavigator.dispatch(
|
||||
NavigationActions.navigate({
|
||||
routeName,
|
||||
params
|
||||
})
|
||||
);
|
||||
}
|
||||
const navigationRef = createRef();
|
||||
const routeNameRef = createRef();
|
||||
|
||||
export default {
|
||||
navigate,
|
||||
setTopLevelNavigator
|
||||
navigationRef,
|
||||
routeNameRef
|
||||
};
|
||||
|
|
|
@ -6,8 +6,14 @@ async function load({ rid: roomId, latest, t }) {
|
|||
if (latest) {
|
||||
params = { ...params, latest: new Date(latest).toISOString() };
|
||||
}
|
||||
|
||||
const apiType = this.roomTypeToApiType(t);
|
||||
if (!apiType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// RC 0.48.0
|
||||
const data = await this.sdk.get(`${ this.roomTypeToApiType(t) }.history`, params);
|
||||
const data = await this.sdk.get(`${ apiType }.history`, params);
|
||||
if (!data || data.status === 'error') {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ import sharedStyles from '../../views/Styles';
|
|||
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
||||
import { withTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { getActiveRoute } from '../../utils/navigation';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
|
||||
const AVATAR_SIZE = 48;
|
||||
const ANIMATION_DURATION = 300;
|
||||
|
@ -71,7 +74,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
class NotificationBadge extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
notification: PropTypes.object,
|
||||
|
@ -102,14 +105,18 @@ class NotificationBadge extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { notification: { payload }, navigation } = this.props;
|
||||
const navState = this.getNavState(navigation.state);
|
||||
if (payload.rid) {
|
||||
if (navState && navState.routeName === 'RoomView' && navState.params && navState.params.rid === payload.rid) {
|
||||
return;
|
||||
componentDidUpdate(prevProps) {
|
||||
const { notification: { payload } } = this.props;
|
||||
const { notification: { payload: prevPayload } } = prevProps;
|
||||
if (!equal(prevPayload, payload)) {
|
||||
const state = Navigation.navigationRef.current.getRootState();
|
||||
const route = getActiveRoute(state);
|
||||
if (payload.rid) {
|
||||
if (route?.name === 'RoomView' && route.params?.rid === payload.rid) {
|
||||
return;
|
||||
}
|
||||
this.show();
|
||||
}
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,15 +157,8 @@ class NotificationBadge extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
getNavState = (routes) => {
|
||||
if (!routes.routes) {
|
||||
return routes;
|
||||
}
|
||||
return this.getNavState(routes.routes[routes.index]);
|
||||
}
|
||||
|
||||
goToRoom = async() => {
|
||||
const { notification, navigation, baseUrl } = this.props;
|
||||
goToRoom = () => {
|
||||
const { notification, isMasterDetail, baseUrl } = this.props;
|
||||
const { payload } = notification;
|
||||
const { rid, type, prid } = payload;
|
||||
if (!rid) {
|
||||
|
@ -167,10 +167,13 @@ class NotificationBadge extends React.Component {
|
|||
const name = type === 'd' ? payload.sender.username : payload.name;
|
||||
// if sub is not on local database, title will be null, so we use payload from notification
|
||||
const { title = name } = notification;
|
||||
await navigation.navigate('RoomsListView');
|
||||
navigation.navigate('RoomView', {
|
||||
const item = {
|
||||
rid, name: title, t: type, prid, baseUrl
|
||||
});
|
||||
};
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
}
|
||||
goRoom({ item, isMasterDetail });
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
@ -234,7 +237,8 @@ class NotificationBadge extends React.Component {
|
|||
const mapStateToProps = state => ({
|
||||
user: getUserSelector(state),
|
||||
baseUrl: state.server.server,
|
||||
notification: state.notification
|
||||
notification: state.notification,
|
||||
isMasterDetail: PropTypes.bool
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -25,7 +25,8 @@ class Touchable extends React.Component {
|
|||
toggleRead: PropTypes.func,
|
||||
hideChannel: PropTypes.func,
|
||||
children: PropTypes.element,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
isFocused: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -167,7 +168,7 @@ class Touchable extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
testID, isRead, width, favorite, children, theme
|
||||
testID, isRead, width, favorite, children, theme, isFocused
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -203,7 +204,7 @@ class Touchable extends React.Component {
|
|||
theme={theme}
|
||||
testID={testID}
|
||||
style={{
|
||||
backgroundColor: themes[theme].backgroundColor
|
||||
backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -28,7 +28,8 @@ const attrs = [
|
|||
'favorite',
|
||||
'status',
|
||||
'connected',
|
||||
'theme'
|
||||
'theme',
|
||||
'isFocused'
|
||||
];
|
||||
|
||||
const arePropsEqual = (oldProps, newProps) => {
|
||||
|
@ -41,7 +42,39 @@ const arePropsEqual = (oldProps, newProps) => {
|
|||
};
|
||||
|
||||
const RoomItem = React.memo(({
|
||||
onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, useRealName, getUserPresence, isGroupChat, connected, theme
|
||||
onPress,
|
||||
width,
|
||||
favorite,
|
||||
toggleFav,
|
||||
isRead,
|
||||
rid,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
testID,
|
||||
unread,
|
||||
userMentions,
|
||||
name,
|
||||
_updatedAt,
|
||||
alert,
|
||||
type,
|
||||
avatarSize,
|
||||
baseUrl,
|
||||
userId,
|
||||
username,
|
||||
token,
|
||||
id,
|
||||
prid,
|
||||
showLastMessage,
|
||||
hideUnreadStatus,
|
||||
lastMessage,
|
||||
status,
|
||||
avatar,
|
||||
useRealName,
|
||||
getUserPresence,
|
||||
isGroupChat,
|
||||
connected,
|
||||
theme,
|
||||
isFocused
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
if (connected && type === 'd' && id) {
|
||||
|
@ -79,6 +112,7 @@ const RoomItem = React.memo(({
|
|||
testID={testID}
|
||||
type={type}
|
||||
theme={theme}
|
||||
isFocused={isFocused}
|
||||
>
|
||||
<View
|
||||
style={styles.container}
|
||||
|
@ -200,7 +234,8 @@ RoomItem.propTypes = {
|
|||
getUserPresence: PropTypes.func,
|
||||
connected: PropTypes.bool,
|
||||
isGroupChat: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
isFocused: PropTypes.bool
|
||||
};
|
||||
|
||||
RoomItem.defaultProps = {
|
||||
|
|
|
@ -2,6 +2,8 @@ import { APP, APP_STATE } from '../actions/actionsTypes';
|
|||
|
||||
const initialState = {
|
||||
root: null,
|
||||
isMasterDetail: false,
|
||||
text: null,
|
||||
ready: false,
|
||||
foreground: true,
|
||||
background: false
|
||||
|
@ -24,7 +26,8 @@ export default function app(state = initialState, action) {
|
|||
case APP.START:
|
||||
return {
|
||||
...state,
|
||||
root: action.root
|
||||
root: action.root,
|
||||
text: action.text
|
||||
};
|
||||
case APP.INIT:
|
||||
return {
|
||||
|
@ -36,6 +39,11 @@ export default function app(state = initialState, action) {
|
|||
...state,
|
||||
ready: true
|
||||
};
|
||||
case APP.SET_MASTER_DETAIL:
|
||||
return {
|
||||
...state,
|
||||
isMasterDetail: action.isMasterDetail
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function(state = initialState, action) {
|
|||
return {
|
||||
...state,
|
||||
rooms: state.rooms
|
||||
.filter(room => room.rid === action.rid)
|
||||
.filter(rid => rid !== action.rid)
|
||||
};
|
||||
case ROOM.LEAVE:
|
||||
return {
|
||||
|
|
|
@ -7,7 +7,8 @@ const initialState = {
|
|||
server: '',
|
||||
version: null,
|
||||
loading: true,
|
||||
adding: false
|
||||
adding: false,
|
||||
previousServer: null
|
||||
};
|
||||
|
||||
|
||||
|
@ -54,12 +55,14 @@ export default function server(state = initialState, action) {
|
|||
case SERVER.INIT_ADD:
|
||||
return {
|
||||
...state,
|
||||
adding: true
|
||||
adding: true,
|
||||
previousServer: action.previousServer
|
||||
};
|
||||
case SERVER.FINISH_ADD:
|
||||
return {
|
||||
...state,
|
||||
adding: false
|
||||
adding: false,
|
||||
previousServer: null
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
|
|
@ -10,6 +10,7 @@ import RocketChat from '../lib/rocketchat';
|
|||
import Navigation from '../lib/Navigation';
|
||||
import database from '../lib/database';
|
||||
import I18n from '../i18n';
|
||||
import { goRoom } from '../utils/goRoom';
|
||||
|
||||
const createChannel = function createChannel(data) {
|
||||
return RocketChat.createChannel(data);
|
||||
|
@ -55,9 +56,12 @@ const handleRequest = function* handleRequest({ data }) {
|
|||
}
|
||||
};
|
||||
|
||||
const handleSuccess = function handleSuccess({ data }) {
|
||||
const { rid, t } = data;
|
||||
Navigation.navigate('RoomView', { rid, t, name: RocketChat.getRoomTitle(data) });
|
||||
const handleSuccess = function* handleSuccess({ data }) {
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
}
|
||||
goRoom({ item: data, isMasterDetail });
|
||||
};
|
||||
|
||||
const handleFailure = function handleFailure({ err }) {
|
||||
|
|
|
@ -10,8 +10,9 @@ import { inviteLinksSetToken, inviteLinksRequest } from '../actions/inviteLinks'
|
|||
import database from '../lib/database';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import EventEmitter from '../utils/events';
|
||||
import { appStart } from '../actions';
|
||||
import { appStart, ROOT_INSIDE } from '../actions/app';
|
||||
import { localAuthenticate } from '../utils/localAuthentication';
|
||||
import { goRoom } from '../utils/goRoom';
|
||||
|
||||
const roomTypes = {
|
||||
channel: 'c', direct: 'd', group: 'p', channels: 'l'
|
||||
|
@ -29,19 +30,25 @@ const handleInviteLink = function* handleInviteLink({ params, requireLogin = fal
|
|||
};
|
||||
|
||||
const navigate = function* navigate({ params }) {
|
||||
yield put(appStart('inside'));
|
||||
yield put(appStart({ root: ROOT_INSIDE }));
|
||||
if (params.path) {
|
||||
const [type, name] = params.path.split('/');
|
||||
if (type !== 'invite') {
|
||||
const room = yield RocketChat.canOpenRoom(params);
|
||||
if (room) {
|
||||
yield Navigation.navigate('RoomsListView');
|
||||
Navigation.navigate('RoomView', {
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
const item = {
|
||||
name,
|
||||
t: roomTypes[type],
|
||||
roomUserId: RocketChat.getUidDirectMessage(room),
|
||||
...room
|
||||
});
|
||||
};
|
||||
goRoom({ item, isMasterDetail });
|
||||
}
|
||||
} else {
|
||||
yield handleInviteLink({ params });
|
||||
|
|
|
@ -4,14 +4,12 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
|||
import RNBootSplash from 'react-native-bootsplash';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import { selectServerRequest } from '../actions/server';
|
||||
import { setAllPreferences } from '../actions/sortPreferences';
|
||||
import { toggleCrashReport } from '../actions/crashReport';
|
||||
import { APP } from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import log from '../utils/log';
|
||||
import Navigation from '../lib/Navigation';
|
||||
import {
|
||||
SERVERS, SERVER_ICON, SERVER_NAME, SERVER_URL, TOKEN, USER_ID
|
||||
} from '../constants/userDefaults';
|
||||
|
@ -19,6 +17,7 @@ import { isIOS } from '../utils/deviceInfo';
|
|||
import database from '../lib/database';
|
||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||
import { localAuthenticate } from '../utils/localAuthentication';
|
||||
import { appStart, ROOT_OUTSIDE, appReady } from '../actions/app';
|
||||
|
||||
export const initLocalSettings = function* initLocalSettings() {
|
||||
const sortPreferences = yield RocketChat.getSortPreferences();
|
||||
|
@ -96,7 +95,7 @@ const restore = function* restore() {
|
|||
RNUserDefaults.clear(RocketChat.TOKEN_KEY),
|
||||
RNUserDefaults.clear('currentServer')
|
||||
]);
|
||||
yield put(actions.appStart('outside'));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
} else {
|
||||
const serversDB = database.servers;
|
||||
const serverCollections = serversDB.collections.get('servers');
|
||||
|
@ -106,23 +105,14 @@ const restore = function* restore() {
|
|||
yield put(selectServerRequest(server, serverObj && serverObj.version));
|
||||
}
|
||||
|
||||
yield put(actions.appReady({}));
|
||||
yield put(appReady({}));
|
||||
} catch (e) {
|
||||
log(e);
|
||||
yield put(actions.appStart('outside'));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
}
|
||||
};
|
||||
|
||||
const start = function* start({ root, text }) {
|
||||
if (root === 'inside') {
|
||||
yield Navigation.navigate('InsideStack');
|
||||
} else if (root === 'setUsername') {
|
||||
yield Navigation.navigate('SetUsernameStack');
|
||||
} else if (root === 'outside') {
|
||||
yield Navigation.navigate('OutsideStack');
|
||||
} else if (root === 'loading') {
|
||||
yield Navigation.navigate('AuthLoading', { text });
|
||||
}
|
||||
const start = function start() {
|
||||
RNBootSplash.hide();
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ import moment from 'moment';
|
|||
import 'moment/min/locales';
|
||||
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { appStart } from '../actions';
|
||||
import {
|
||||
appStart, ROOT_SET_USERNAME, ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE
|
||||
} from '../actions/app';
|
||||
import { serverFinishAdd, selectServerRequest } from '../actions/server';
|
||||
import {
|
||||
loginFailure, loginSuccess, setUser, logout
|
||||
|
@ -40,7 +42,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
|
|||
if (!result.username) {
|
||||
yield put(serverFinishAdd());
|
||||
yield put(setUser(result));
|
||||
yield put(appStart('setUsername'));
|
||||
yield put(appStart({ root: ROOT_SET_USERNAME }));
|
||||
} else {
|
||||
const server = yield select(getServer);
|
||||
yield localAuthenticate(server);
|
||||
|
@ -133,17 +135,17 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
let currentRoot;
|
||||
if (adding) {
|
||||
yield put(serverFinishAdd());
|
||||
yield put(appStart('inside'));
|
||||
yield put(appStart({ root: ROOT_INSIDE }));
|
||||
} else {
|
||||
currentRoot = yield select(state => state.app.root);
|
||||
if (currentRoot !== 'inside') {
|
||||
yield put(appStart('inside'));
|
||||
if (currentRoot !== ROOT_INSIDE) {
|
||||
yield put(appStart({ root: ROOT_INSIDE }));
|
||||
}
|
||||
}
|
||||
|
||||
// after a successful login, check if it's been invited via invite link
|
||||
currentRoot = yield select(state => state.app.root);
|
||||
if (currentRoot === 'inside') {
|
||||
if (currentRoot === ROOT_INSIDE) {
|
||||
const inviteLinkToken = yield select(state => state.inviteLinks.token);
|
||||
if (inviteLinkToken) {
|
||||
yield put(inviteLinksRequest(inviteLinkToken));
|
||||
|
@ -155,7 +157,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
};
|
||||
|
||||
const handleLogout = function* handleLogout({ forcedByServer }) {
|
||||
yield put(appStart('loading', I18n.t('Logging_out')));
|
||||
yield put(appStart({ root: ROOT_LOADING, text: I18n.t('Logging_out') }));
|
||||
const server = yield select(getServer);
|
||||
if (server) {
|
||||
try {
|
||||
|
@ -163,7 +165,7 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
|
|||
|
||||
// if the user was logged out by the server
|
||||
if (forcedByServer) {
|
||||
yield put(appStart('outside'));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
|
||||
EventEmitter.emit('NewServer', { server });
|
||||
} else {
|
||||
|
@ -183,10 +185,10 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
|
|||
}
|
||||
}
|
||||
// if there's no servers, go outside
|
||||
yield put(appStart('outside'));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
}
|
||||
} catch (e) {
|
||||
yield put(appStart('outside'));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { takeLatest } from 'redux-saga/effects';
|
||||
import { takeLatest, select } from 'redux-saga/effects';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import Navigation from '../lib/Navigation';
|
||||
|
@ -6,32 +6,28 @@ import { MESSAGES } from '../actions/actionsTypes';
|
|||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/database';
|
||||
import log from '../utils/log';
|
||||
|
||||
const goRoom = function goRoom({
|
||||
rid, name, fname, message
|
||||
}) {
|
||||
Navigation.navigate('RoomsListView');
|
||||
Navigation.navigate('RoomView', {
|
||||
rid, name, fname, t: 'd', message
|
||||
});
|
||||
};
|
||||
import { goRoom } from '../utils/goRoom';
|
||||
|
||||
const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
||||
try {
|
||||
const db = database.active;
|
||||
const { username, name } = message.u;
|
||||
const { username } = message.u;
|
||||
const subsCollection = db.collections.get('subscriptions');
|
||||
const subscriptions = yield subsCollection.query(Q.where('name', username)).fetch();
|
||||
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
|
||||
if (subscriptions.length) {
|
||||
yield goRoom({
|
||||
rid: subscriptions[0].rid, name: username, fname: name, message
|
||||
});
|
||||
goRoom({ item: subscriptions[0], isMasterDetail, message });
|
||||
} else {
|
||||
const result = yield RocketChat.createDirectMessage(username);
|
||||
if (result?.success) {
|
||||
yield goRoom({
|
||||
rid: result?.room.rid, t: 'd', name: username, fname: name, message
|
||||
});
|
||||
goRoom({ item: result?.room, isMasterDetail, message });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -31,7 +31,12 @@ const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
|||
};
|
||||
|
||||
const handleRemovedRoom = function* handleRemovedRoom() {
|
||||
yield Navigation.navigate('RoomsListView');
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (isMasterDetail) {
|
||||
yield Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
yield Navigation.navigate('RoomsListView');
|
||||
}
|
||||
// types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg
|
||||
const { timeout } = yield race({
|
||||
deleteFinished: take(types.ROOM.REMOVED),
|
||||
|
@ -74,7 +79,12 @@ const handleCloseRoom = function* handleCloseRoom({ rid }) {
|
|||
const closeRoom = async(comment = '') => {
|
||||
try {
|
||||
await RocketChat.closeLivechat(rid, comment);
|
||||
Navigation.navigate('RoomsListView');
|
||||
const isMasterDetail = await select(state => state.app.isMasterDetail);
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -105,7 +115,12 @@ const handleForwardRoom = function* handleForwardRoom({ transferData }) {
|
|||
try {
|
||||
const result = yield RocketChat.forwardLivechat(transferData);
|
||||
if (result === true) {
|
||||
Navigation.navigate('RoomsListView');
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
} else {
|
||||
showErrorAlert(I18n.t('No_available_agents_to_transfer'), I18n.t('Oops'));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import semver from 'semver';
|
|||
|
||||
import Navigation from '../lib/Navigation';
|
||||
import { SERVER } from '../actions/actionsTypes';
|
||||
import * as actions from '../actions';
|
||||
import {
|
||||
serverFailure, selectServerRequest, selectServerSuccess, selectServerFailure
|
||||
} from '../actions/server';
|
||||
|
@ -19,6 +18,7 @@ import { extractHostname } from '../utils/server';
|
|||
import I18n from '../i18n';
|
||||
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';
|
||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
|
||||
import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app';
|
||||
|
||||
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||
try {
|
||||
|
@ -103,10 +103,10 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
|||
yield put(clearSettings());
|
||||
yield RocketChat.connect({ server, user, logoutOnError: true });
|
||||
yield put(setUser(user));
|
||||
yield put(actions.appStart('inside'));
|
||||
yield put(appStart({ root: ROOT_INSIDE }));
|
||||
} else {
|
||||
yield RocketChat.connect({ server });
|
||||
yield put(actions.appStart('outside'));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
}
|
||||
|
||||
// We can't use yield here because fetch of Settings & Custom Emojis is slower
|
||||
|
|
|
@ -5,10 +5,11 @@ import { setBadgeCount } from '../notifications/push';
|
|||
import log from '../utils/log';
|
||||
import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication';
|
||||
import { APP_STATE } from '../actions/actionsTypes';
|
||||
import { ROOT_OUTSIDE } from '../actions/app';
|
||||
|
||||
const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
||||
const appRoot = yield select(state => state.app.root);
|
||||
if (appRoot === 'outside') {
|
||||
if (appRoot === ROOT_OUTSIDE) {
|
||||
return;
|
||||
}
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
|
@ -27,7 +28,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
|
|||
|
||||
const appHasComeBackToBackground = function* appHasComeBackToBackground() {
|
||||
const appRoot = yield select(state => state.app.root);
|
||||
if (appRoot === 'outside') {
|
||||
if (appRoot === ROOT_OUTSIDE) {
|
||||
return;
|
||||
}
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
|
|
192
app/share.js
192
app/share.js
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { AppearanceProvider } from 'react-native-appearance';
|
||||
import { createStackNavigator } from 'react-navigation-stack';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { Provider } from 'react-redux';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
|
||||
|
@ -14,61 +14,114 @@ import {
|
|||
} from './utils/theme';
|
||||
import Navigation from './lib/ShareNavigation';
|
||||
import store from './lib/createStore';
|
||||
import sharedStyles from './views/Styles';
|
||||
import { hasNotch, supportSystemTheme } from './utils/deviceInfo';
|
||||
import { defaultHeader, onNavigationStateChange, cardStyle } from './utils/navigation';
|
||||
import { supportSystemTheme } from './utils/deviceInfo';
|
||||
import {
|
||||
defaultHeader, themedHeader, getActiveRouteName, navigationTheme
|
||||
} from './utils/navigation';
|
||||
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
|
||||
import { ThemeContext } from './theme';
|
||||
import { localAuthenticate } from './utils/localAuthentication';
|
||||
import ScreenLockedView from './views/ScreenLockedView';
|
||||
|
||||
const InsideNavigator = createStackNavigator({
|
||||
ShareListView: {
|
||||
getScreen: () => require('./views/ShareListView').default
|
||||
},
|
||||
ShareView: {
|
||||
getScreen: () => require('./views/ShareView').default
|
||||
},
|
||||
SelectServerView: {
|
||||
getScreen: () => require('./views/SelectServerView').default
|
||||
}
|
||||
}, {
|
||||
initialRouteName: 'ShareListView',
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
// Outside Stack
|
||||
import WithoutServersView from './views/WithoutServersView';
|
||||
|
||||
const OutsideNavigator = createStackNavigator({
|
||||
WithoutServersView: {
|
||||
getScreen: () => require('./views/WithoutServersView').default
|
||||
}
|
||||
}, {
|
||||
initialRouteName: 'WithoutServersView',
|
||||
defaultNavigationOptions: defaultHeader,
|
||||
cardStyle
|
||||
});
|
||||
// Inside Stack
|
||||
import ShareListView from './views/ShareListView';
|
||||
import ShareView from './views/ShareView';
|
||||
import SelectServerView from './views/SelectServerView';
|
||||
import { setCurrentScreen } from './utils/log';
|
||||
import AuthLoadingView from './views/AuthLoadingView';
|
||||
|
||||
const AppContainer = createAppContainer(createSwitchNavigator({
|
||||
OutsideStack: OutsideNavigator,
|
||||
InsideStack: InsideNavigator,
|
||||
AuthLoading: {
|
||||
getScreen: () => require('./views/AuthLoadingView').default
|
||||
}
|
||||
},
|
||||
{
|
||||
initialRouteName: 'AuthLoading'
|
||||
}));
|
||||
const Inside = createStackNavigator();
|
||||
const InsideStack = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const screenOptions = {
|
||||
...defaultHeader,
|
||||
...themedHeader(theme)
|
||||
};
|
||||
screenOptions.headerStyle = {
|
||||
...screenOptions.headerStyle,
|
||||
// TODO: fix on multiple files PR :)
|
||||
height: 57
|
||||
};
|
||||
|
||||
return (
|
||||
<Inside.Navigator screenOptions={screenOptions}>
|
||||
<Inside.Screen
|
||||
name='ShareListView'
|
||||
component={ShareListView}
|
||||
/>
|
||||
<Inside.Screen
|
||||
name='ShareView'
|
||||
component={ShareView}
|
||||
/>
|
||||
<Inside.Screen
|
||||
name='SelectServerView'
|
||||
component={SelectServerView}
|
||||
options={SelectServerView.navigationOptions}
|
||||
/>
|
||||
</Inside.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const Outside = createStackNavigator();
|
||||
const OutsideStack = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme) }}>
|
||||
<Outside.Screen
|
||||
name='WithoutServersView'
|
||||
component={WithoutServersView}
|
||||
options={WithoutServersView.navigationOptions}
|
||||
/>
|
||||
</Outside.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
// App
|
||||
const Stack = createStackNavigator();
|
||||
export const App = ({ root }) => (
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
<>
|
||||
{!root ? (
|
||||
<Stack.Screen
|
||||
name='AuthLoading'
|
||||
component={AuthLoadingView}
|
||||
/>
|
||||
) : null}
|
||||
{root === 'outside' ? (
|
||||
<Stack.Screen
|
||||
name='OutsideStack'
|
||||
component={OutsideStack}
|
||||
/>
|
||||
) : null}
|
||||
{root === 'inside' ? (
|
||||
<Stack.Screen
|
||||
name='InsideStack'
|
||||
component={InsideStack}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
App.propTypes = {
|
||||
root: PropTypes.string
|
||||
};
|
||||
|
||||
class Root extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLandscape: false,
|
||||
theme: defaultTheme(),
|
||||
themePreferences: {
|
||||
currentTheme: supportSystemTheme() ? 'automatic' : 'light',
|
||||
darkLevel: 'dark'
|
||||
}
|
||||
},
|
||||
root: ''
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
@ -85,11 +138,16 @@ class Root extends React.Component {
|
|||
|
||||
if (currentServer && token) {
|
||||
await localAuthenticate(currentServer);
|
||||
await Navigation.navigate('InsideStack');
|
||||
this.setState({ root: 'inside' });
|
||||
await RocketChat.shareExtensionInit(currentServer);
|
||||
} else {
|
||||
await Navigation.navigate('OutsideStack');
|
||||
this.setState({ root: 'outside' });
|
||||
}
|
||||
|
||||
const state = Navigation.navigationRef.current.getRootState();
|
||||
const currentRouteName = getActiveRouteName(state);
|
||||
Navigation.routeNameRef.current = currentRouteName;
|
||||
setCurrentScreen(currentRouteName);
|
||||
}
|
||||
|
||||
setTheme = (newTheme = {}) => {
|
||||
|
@ -101,32 +159,30 @@ class Root extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
handleLayout = (event) => {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
this.setState({ isLandscape: width > height });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLandscape, theme } = this.state;
|
||||
const { theme, root } = this.state;
|
||||
const navTheme = navigationTheme(theme);
|
||||
return (
|
||||
<AppearanceProvider>
|
||||
<View
|
||||
style={[sharedStyles.container, isLandscape && hasNotch ? sharedStyles.notchLandscapeContainer : {}]}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
<Provider store={store}>
|
||||
<ThemeContext.Provider value={{ theme }}>
|
||||
<AppContainer
|
||||
ref={(navigatorRef) => {
|
||||
Navigation.setTopLevelNavigator(navigatorRef);
|
||||
}}
|
||||
onNavigationStateChange={onNavigationStateChange}
|
||||
screenProps={{ theme }}
|
||||
/>
|
||||
<ScreenLockedView />
|
||||
</ThemeContext.Provider>
|
||||
</Provider>
|
||||
</View>
|
||||
<Provider store={store}>
|
||||
<ThemeContext.Provider value={{ theme }}>
|
||||
<NavigationContainer
|
||||
theme={navTheme}
|
||||
ref={Navigation.navigationRef}
|
||||
onStateChange={(state) => {
|
||||
const previousRouteName = Navigation.routeNameRef.current;
|
||||
const currentRouteName = getActiveRouteName(state);
|
||||
if (previousRouteName !== currentRouteName) {
|
||||
setCurrentScreen(currentRouteName);
|
||||
}
|
||||
Navigation.routeNameRef.current = currentRouteName;
|
||||
}}
|
||||
>
|
||||
<App root={root} />
|
||||
</NavigationContainer>
|
||||
<ScreenLockedView />
|
||||
</ThemeContext.Provider>
|
||||
</Provider>
|
||||
</AppearanceProvider>
|
||||
);
|
||||
}
|
||||
|
|
19
app/split.js
19
app/split.js
|
@ -1,19 +0,0 @@
|
|||
import React from 'react';
|
||||
import hoistNonReactStatics from 'hoist-non-react-statics';
|
||||
|
||||
import { isTablet } from './utils/deviceInfo';
|
||||
|
||||
export const SplitContext = React.createContext(null);
|
||||
|
||||
export function withSplit(Component) {
|
||||
if (isTablet) {
|
||||
const SplitComponent = props => (
|
||||
<SplitContext.Consumer>
|
||||
{contexts => <Component {...props} {...contexts} />}
|
||||
</SplitContext.Consumer>
|
||||
);
|
||||
hoistNonReactStatics(SplitComponent, Component);
|
||||
return SplitComponent;
|
||||
}
|
||||
return Component;
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
|
||||
import { ThemeContext } from '../theme';
|
||||
import {
|
||||
defaultHeader, themedHeader, ModalAnimation, StackAnimation
|
||||
} from '../utils/navigation';
|
||||
import Toast from '../containers/Toast';
|
||||
import Sidebar from '../views/SidebarView';
|
||||
import NotificationBadge from '../notifications/inApp';
|
||||
|
||||
// Chats Stack
|
||||
import RoomView from '../views/RoomView';
|
||||
import RoomsListView from '../views/RoomsListView';
|
||||
import RoomActionsView from '../views/RoomActionsView';
|
||||
import RoomInfoView from '../views/RoomInfoView';
|
||||
import RoomInfoEditView from '../views/RoomInfoEditView';
|
||||
import RoomMembersView from '../views/RoomMembersView';
|
||||
import SearchMessagesView from '../views/SearchMessagesView';
|
||||
import SelectedUsersView from '../views/SelectedUsersView';
|
||||
import InviteUsersView from '../views/InviteUsersView';
|
||||
import InviteUsersEditView from '../views/InviteUsersEditView';
|
||||
import MessagesView from '../views/MessagesView';
|
||||
import AutoTranslateView from '../views/AutoTranslateView';
|
||||
import DirectoryView from '../views/DirectoryView';
|
||||
import NotificationPrefView from '../views/NotificationPreferencesView';
|
||||
import VisitorNavigationView from '../views/VisitorNavigationView';
|
||||
import ForwardLivechatView from '../views/ForwardLivechatView';
|
||||
import LivechatEditView from '../views/LivechatEditView';
|
||||
import PickerView from '../views/PickerView';
|
||||
import ThreadMessagesView from '../views/ThreadMessagesView';
|
||||
import MarkdownTableView from '../views/MarkdownTableView';
|
||||
import ReadReceiptsView from '../views/ReadReceiptView';
|
||||
|
||||
// Profile Stack
|
||||
import ProfileView from '../views/ProfileView';
|
||||
|
||||
// Settings Stack
|
||||
import SettingsView from '../views/SettingsView';
|
||||
import LanguageView from '../views/LanguageView';
|
||||
import ThemeView from '../views/ThemeView';
|
||||
import DefaultBrowserView from '../views/DefaultBrowserView';
|
||||
import ScreenLockConfigView from '../views/ScreenLockConfigView';
|
||||
|
||||
// Admin Stack
|
||||
import AdminPanelView from '../views/AdminPanelView';
|
||||
|
||||
// NewMessage Stack
|
||||
import NewMessageView from '../views/NewMessageView';
|
||||
import CreateChannelView from '../views/CreateChannelView';
|
||||
|
||||
// InsideStackNavigator
|
||||
import AttachmentView from '../views/AttachmentView';
|
||||
import ModalBlockView from '../views/ModalBlockView';
|
||||
import JitsiMeetView from '../views/JitsiMeetView';
|
||||
import StatusView from '../views/StatusView';
|
||||
import CreateDiscussionView from '../views/CreateDiscussionView';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator();
|
||||
const ChatsStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
return (
|
||||
<ChatsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<ChatsStack.Screen
|
||||
name='RoomsListView'
|
||||
component={RoomsListView}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='RoomView'
|
||||
component={RoomView}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='RoomActionsView'
|
||||
component={RoomActionsView}
|
||||
options={RoomActionsView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='RoomInfoView'
|
||||
component={RoomInfoView}
|
||||
options={RoomInfoView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='RoomInfoEditView'
|
||||
component={RoomInfoEditView}
|
||||
options={RoomInfoEditView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='RoomMembersView'
|
||||
component={RoomMembersView}
|
||||
options={RoomMembersView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='SearchMessagesView'
|
||||
component={SearchMessagesView}
|
||||
options={SearchMessagesView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='SelectedUsersView'
|
||||
component={SelectedUsersView}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='InviteUsersView'
|
||||
component={InviteUsersView}
|
||||
options={InviteUsersView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='InviteUsersEditView'
|
||||
component={InviteUsersEditView}
|
||||
options={InviteUsersEditView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='MessagesView'
|
||||
component={MessagesView}
|
||||
options={MessagesView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='AutoTranslateView'
|
||||
component={AutoTranslateView}
|
||||
options={AutoTranslateView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='DirectoryView'
|
||||
component={DirectoryView}
|
||||
options={DirectoryView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='NotificationPrefView'
|
||||
component={NotificationPrefView}
|
||||
options={NotificationPrefView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='VisitorNavigationView'
|
||||
component={VisitorNavigationView}
|
||||
options={VisitorNavigationView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='ForwardLivechatView'
|
||||
component={ForwardLivechatView}
|
||||
options={ForwardLivechatView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='LivechatEditView'
|
||||
component={LivechatEditView}
|
||||
options={LivechatEditView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='PickerView'
|
||||
component={PickerView}
|
||||
options={PickerView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='ThreadMessagesView'
|
||||
component={ThreadMessagesView}
|
||||
options={ThreadMessagesView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='MarkdownTableView'
|
||||
component={MarkdownTableView}
|
||||
options={MarkdownTableView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='ReadReceiptsView'
|
||||
component={ReadReceiptsView}
|
||||
options={ReadReceiptsView.navigationOptions}
|
||||
/>
|
||||
</ChatsStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
// ProfileStackNavigator
|
||||
const ProfileStack = createStackNavigator();
|
||||
const ProfileStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
return (
|
||||
<ProfileStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<ProfileStack.Screen
|
||||
name='ProfileView'
|
||||
component={ProfileView}
|
||||
options={ProfileView.navigationOptions}
|
||||
/>
|
||||
</ProfileStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
// SettingsStackNavigator
|
||||
const SettingsStack = createStackNavigator();
|
||||
const SettingsStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<SettingsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<SettingsStack.Screen
|
||||
name='SettingsView'
|
||||
component={SettingsView}
|
||||
options={SettingsView.navigationOptions}
|
||||
/>
|
||||
<SettingsStack.Screen
|
||||
name='LanguageView'
|
||||
component={LanguageView}
|
||||
options={LanguageView.navigationOptions}
|
||||
/>
|
||||
<SettingsStack.Screen
|
||||
name='ThemeView'
|
||||
component={ThemeView}
|
||||
options={ThemeView.navigationOptions}
|
||||
/>
|
||||
<SettingsStack.Screen
|
||||
name='DefaultBrowserView'
|
||||
component={DefaultBrowserView}
|
||||
options={DefaultBrowserView.navigationOptions}
|
||||
/>
|
||||
<SettingsStack.Screen
|
||||
name='ScreenLockConfigView'
|
||||
component={ScreenLockConfigView}
|
||||
options={ScreenLockConfigView.navigationOptions}
|
||||
/>
|
||||
</SettingsStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
// AdminPanelStackNavigator
|
||||
const AdminPanelStack = createStackNavigator();
|
||||
const AdminPanelStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<AdminPanelStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<AdminPanelStack.Screen
|
||||
name='AdminPanelView'
|
||||
component={AdminPanelView}
|
||||
options={AdminPanelView.navigationOptions}
|
||||
/>
|
||||
</AdminPanelStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
// DrawerNavigator
|
||||
const Drawer = createDrawerNavigator();
|
||||
const DrawerNavigator = () => (
|
||||
<Drawer.Navigator
|
||||
drawerContent={({ navigation, state }) => <Sidebar navigation={navigation} state={state} />}
|
||||
screenOptions={{ swipeEnabled: false }}
|
||||
drawerType='back'
|
||||
>
|
||||
<Drawer.Screen name='ChatsStackNavigator' component={ChatsStackNavigator} />
|
||||
<Drawer.Screen name='ProfileStackNavigator' component={ProfileStackNavigator} />
|
||||
<Drawer.Screen name='SettingsStackNavigator' component={SettingsStackNavigator} />
|
||||
<Drawer.Screen name='AdminPanelStackNavigator' component={AdminPanelStackNavigator} />
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
|
||||
// NewMessageStackNavigator
|
||||
const NewMessageStack = createStackNavigator();
|
||||
const NewMessageStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<NewMessageStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<NewMessageStack.Screen
|
||||
name='NewMessageView'
|
||||
component={NewMessageView}
|
||||
options={NewMessageView.navigationOptions}
|
||||
/>
|
||||
<NewMessageStack.Screen
|
||||
name='SelectedUsersViewCreateChannel'
|
||||
component={SelectedUsersView}
|
||||
/>
|
||||
<NewMessageStack.Screen
|
||||
name='CreateChannelView'
|
||||
component={CreateChannelView}
|
||||
options={CreateChannelView.navigationOptions}
|
||||
/>
|
||||
<NewMessageStack.Screen
|
||||
name='CreateDiscussionView'
|
||||
component={CreateDiscussionView}
|
||||
/>
|
||||
</NewMessageStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
// InsideStackNavigator
|
||||
const InsideStack = createStackNavigator();
|
||||
const InsideStackNavigator = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<InsideStack.Navigator mode='modal' screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation }}>
|
||||
<InsideStack.Screen
|
||||
name='DrawerNavigator'
|
||||
component={DrawerNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='NewMessageStackNavigator'
|
||||
component={NewMessageStackNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='AttachmentView'
|
||||
component={AttachmentView}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='StatusView'
|
||||
component={StatusView}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='ModalBlockView'
|
||||
component={ModalBlockView}
|
||||
options={ModalBlockView.navigationOptions}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='JitsiMeetView'
|
||||
component={JitsiMeetView}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</InsideStack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const RootInsideStack = ({ navigation, route }) => (
|
||||
<>
|
||||
<InsideStackNavigator navigation={navigation} />
|
||||
<NotificationBadge navigation={navigation} route={route} />
|
||||
<Toast />
|
||||
</>
|
||||
);
|
||||
RootInsideStack.propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object
|
||||
};
|
||||
|
||||
export default RootInsideStack;
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { TouchableWithoutFeedback, View, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFill
|
||||
}
|
||||
});
|
||||
|
||||
export const ModalContainer = ({ navigation, children, theme }) => (
|
||||
<View style={[styles.root, { backgroundColor: `${ themes[theme].backdropColor }70` }]}>
|
||||
<TouchableWithoutFeedback onPress={() => navigation.pop()}>
|
||||
<View style={styles.backdrop} />
|
||||
</TouchableWithoutFeedback>
|
||||
<View style={sharedStyles.modalFormSheet}>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
ModalContainer.propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
children: PropTypes.element,
|
||||
theme: PropTypes.string
|
||||
};
|
|
@ -0,0 +1,307 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
|
||||
import { ThemeContext } from '../../theme';
|
||||
import {
|
||||
defaultHeader, themedHeader, StackAnimation, FadeFromCenterModal
|
||||
} from '../../utils/navigation';
|
||||
import Toast from '../../containers/Toast';
|
||||
import NotificationBadge from '../../notifications/inApp';
|
||||
import { ModalContainer } from './ModalContainer';
|
||||
|
||||
// Chats Stack
|
||||
import RoomView from '../../views/RoomView';
|
||||
import RoomsListView from '../../views/RoomsListView';
|
||||
import RoomActionsView from '../../views/RoomActionsView';
|
||||
import RoomInfoView from '../../views/RoomInfoView';
|
||||
import RoomInfoEditView from '../../views/RoomInfoEditView';
|
||||
import RoomMembersView from '../../views/RoomMembersView';
|
||||
import SearchMessagesView from '../../views/SearchMessagesView';
|
||||
import SelectedUsersView from '../../views/SelectedUsersView';
|
||||
import InviteUsersView from '../../views/InviteUsersView';
|
||||
import InviteUsersEditView from '../../views/InviteUsersEditView';
|
||||
import MessagesView from '../../views/MessagesView';
|
||||
import AutoTranslateView from '../../views/AutoTranslateView';
|
||||
import DirectoryView from '../../views/DirectoryView';
|
||||
import NotificationPrefView from '../../views/NotificationPreferencesView';
|
||||
import VisitorNavigationView from '../../views/VisitorNavigationView';
|
||||
import ForwardLivechatView from '../../views/ForwardLivechatView';
|
||||
import LivechatEditView from '../../views/LivechatEditView';
|
||||
import PickerView from '../../views/PickerView';
|
||||
import ThreadMessagesView from '../../views/ThreadMessagesView';
|
||||
import MarkdownTableView from '../../views/MarkdownTableView';
|
||||
import ReadReceiptsView from '../../views/ReadReceiptView';
|
||||
import ProfileView from '../../views/ProfileView';
|
||||
import SettingsView from '../../views/SettingsView';
|
||||
import LanguageView from '../../views/LanguageView';
|
||||
import ThemeView from '../../views/ThemeView';
|
||||
import DefaultBrowserView from '../../views/DefaultBrowserView';
|
||||
import ScreenLockConfigView from '../../views/ScreenLockConfigView';
|
||||
import AdminPanelView from '../../views/AdminPanelView';
|
||||
import NewMessageView from '../../views/NewMessageView';
|
||||
import CreateChannelView from '../../views/CreateChannelView';
|
||||
|
||||
// InsideStackNavigator
|
||||
import AttachmentView from '../../views/AttachmentView';
|
||||
import ModalBlockView from '../../views/ModalBlockView';
|
||||
import JitsiMeetView from '../../views/JitsiMeetView';
|
||||
import StatusView from '../../views/StatusView';
|
||||
import CreateDiscussionView from '../../views/CreateDiscussionView';
|
||||
|
||||
import { setKeyCommands, deleteKeyCommands } from '../../commands';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator();
|
||||
const ChatsStackNavigator = React.memo(() => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
const isFocused = useIsFocused();
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
setKeyCommands();
|
||||
} else {
|
||||
deleteKeyCommands();
|
||||
}
|
||||
return () => {
|
||||
deleteKeyCommands();
|
||||
};
|
||||
}, [isFocused]);
|
||||
|
||||
return (
|
||||
<ChatsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<ChatsStack.Screen
|
||||
name='RoomView'
|
||||
component={RoomView}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</ChatsStack.Navigator>
|
||||
);
|
||||
});
|
||||
|
||||
// DrawerNavigator
|
||||
const Drawer = createDrawerNavigator();
|
||||
const DrawerNavigator = React.memo(() => (
|
||||
<Drawer.Navigator
|
||||
drawerContent={({ navigation, state }) => <RoomsListView navigation={navigation} state={state} />}
|
||||
drawerType='permanent'
|
||||
>
|
||||
<Drawer.Screen name='ChatsStackNavigator' component={ChatsStackNavigator} />
|
||||
</Drawer.Navigator>
|
||||
));
|
||||
|
||||
const ModalStack = createStackNavigator();
|
||||
const ModalStackNavigator = React.memo(({ navigation }) => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
return (
|
||||
<ModalContainer navigation={navigation} theme={theme}>
|
||||
<ModalStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
<ModalStack.Screen
|
||||
name='RoomActionsView'
|
||||
component={RoomActionsView}
|
||||
options={props => RoomActionsView.navigationOptions({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='RoomInfoView'
|
||||
component={RoomInfoView}
|
||||
options={RoomInfoView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='RoomInfoEditView'
|
||||
component={RoomInfoEditView}
|
||||
options={RoomInfoEditView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='RoomMembersView'
|
||||
component={RoomMembersView}
|
||||
options={RoomMembersView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='SearchMessagesView'
|
||||
component={SearchMessagesView}
|
||||
options={SearchMessagesView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='SelectedUsersView'
|
||||
component={SelectedUsersView}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='InviteUsersView'
|
||||
component={InviteUsersView}
|
||||
options={InviteUsersView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='InviteUsersEditView'
|
||||
component={InviteUsersEditView}
|
||||
options={InviteUsersEditView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='MessagesView'
|
||||
component={MessagesView}
|
||||
options={MessagesView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='AutoTranslateView'
|
||||
component={AutoTranslateView}
|
||||
options={AutoTranslateView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='DirectoryView'
|
||||
component={DirectoryView}
|
||||
options={props => DirectoryView.navigationOptions({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='NotificationPrefView'
|
||||
component={NotificationPrefView}
|
||||
options={NotificationPrefView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='VisitorNavigationView'
|
||||
component={VisitorNavigationView}
|
||||
options={VisitorNavigationView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='ForwardLivechatView'
|
||||
component={ForwardLivechatView}
|
||||
options={ForwardLivechatView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='LivechatEditView'
|
||||
component={LivechatEditView}
|
||||
options={LivechatEditView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='PickerView'
|
||||
component={PickerView}
|
||||
options={PickerView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='ThreadMessagesView'
|
||||
component={ThreadMessagesView}
|
||||
options={props => ThreadMessagesView.navigationOptions({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='MarkdownTableView'
|
||||
component={MarkdownTableView}
|
||||
options={MarkdownTableView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='ReadReceiptsView'
|
||||
component={ReadReceiptsView}
|
||||
options={ReadReceiptsView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='SettingsView'
|
||||
component={SettingsView}
|
||||
options={props => SettingsView.navigationOptions({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='LanguageView'
|
||||
component={LanguageView}
|
||||
options={LanguageView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='ThemeView'
|
||||
component={ThemeView}
|
||||
options={ThemeView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='DefaultBrowserView'
|
||||
component={DefaultBrowserView}
|
||||
options={DefaultBrowserView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='ScreenLockConfigView'
|
||||
component={ScreenLockConfigView}
|
||||
options={ScreenLockConfigView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='StatusView'
|
||||
component={StatusView}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='ProfileView'
|
||||
component={ProfileView}
|
||||
options={props => ProfileView.navigationOptions({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='AdminPanelView'
|
||||
component={AdminPanelView}
|
||||
options={props => AdminPanelView.navigationOptions({ ...props, isMasterDetail: true })}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='NewMessageView'
|
||||
component={NewMessageView}
|
||||
options={NewMessageView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='SelectedUsersViewCreateChannel'
|
||||
component={SelectedUsersView}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='CreateChannelView'
|
||||
component={CreateChannelView}
|
||||
options={CreateChannelView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='CreateDiscussionView'
|
||||
component={CreateDiscussionView}
|
||||
/>
|
||||
</ModalStack.Navigator>
|
||||
</ModalContainer>
|
||||
);
|
||||
});
|
||||
|
||||
ModalStackNavigator.propTypes = {
|
||||
navigation: PropTypes.object
|
||||
};
|
||||
|
||||
// InsideStackNavigator
|
||||
const InsideStack = createStackNavigator();
|
||||
const InsideStackNavigator = React.memo(() => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
return (
|
||||
<InsideStack.Navigator mode='modal' screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...FadeFromCenterModal }}>
|
||||
<InsideStack.Screen
|
||||
name='DrawerNavigator'
|
||||
component={DrawerNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='ModalStackNavigator'
|
||||
component={ModalStackNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='AttachmentView'
|
||||
component={AttachmentView}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='ModalBlockView'
|
||||
component={ModalBlockView}
|
||||
options={ModalBlockView.navigationOptions}
|
||||
/>
|
||||
<InsideStack.Screen
|
||||
name='JitsiMeetView'
|
||||
component={JitsiMeetView}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</InsideStack.Navigator>
|
||||
);
|
||||
});
|
||||
|
||||
const RootInsideStack = React.memo(({ navigation, route }) => (
|
||||
<>
|
||||
<InsideStackNavigator navigation={navigation} />
|
||||
<NotificationBadge navigation={navigation} route={route} />
|
||||
<Toast />
|
||||
</>
|
||||
));
|
||||
RootInsideStack.propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object
|
||||
};
|
||||
|
||||
export default RootInsideStack;
|
|
@ -0,0 +1,101 @@
|
|||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { ThemeContext } from '../theme';
|
||||
import {
|
||||
defaultHeader, themedHeader, StackAnimation, ModalAnimation
|
||||
} from '../utils/navigation';
|
||||
|
||||
// Outside Stack
|
||||
import OnboardingView from '../views/OnboardingView';
|
||||
import NewServerView from '../views/NewServerView';
|
||||
import WorkspaceView from '../views/WorkspaceView';
|
||||
import LoginView from '../views/LoginView';
|
||||
import ForgotPasswordView from '../views/ForgotPasswordView';
|
||||
import RegisterView from '../views/RegisterView';
|
||||
import LegalView from '../views/LegalView';
|
||||
import AuthenticationWebView from '../views/AuthenticationWebView';
|
||||
import { ROOT_OUTSIDE } from '../actions/app';
|
||||
|
||||
// Outside
|
||||
const Outside = createStackNavigator();
|
||||
const _OutsideStack = ({ root }) => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
|
||||
{root === ROOT_OUTSIDE ? (
|
||||
<Outside.Screen
|
||||
name='OnboardingView'
|
||||
component={OnboardingView}
|
||||
options={OnboardingView.navigationOptions}
|
||||
/>
|
||||
) : null}
|
||||
<Outside.Screen
|
||||
name='NewServerView'
|
||||
component={NewServerView}
|
||||
options={NewServerView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen
|
||||
name='WorkspaceView'
|
||||
component={WorkspaceView}
|
||||
options={WorkspaceView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen
|
||||
name='LoginView'
|
||||
component={LoginView}
|
||||
options={LoginView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen
|
||||
name='ForgotPasswordView'
|
||||
component={ForgotPasswordView}
|
||||
options={ForgotPasswordView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen
|
||||
name='RegisterView'
|
||||
component={RegisterView}
|
||||
options={RegisterView.navigationOptions}
|
||||
/>
|
||||
<Outside.Screen
|
||||
name='LegalView'
|
||||
component={LegalView}
|
||||
options={LegalView.navigationOptions}
|
||||
/>
|
||||
</Outside.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
root: state.app.root
|
||||
});
|
||||
|
||||
_OutsideStack.propTypes = {
|
||||
root: PropTypes.string
|
||||
};
|
||||
|
||||
const OutsideStack = connect(mapStateToProps)(_OutsideStack);
|
||||
|
||||
// OutsideStackModal
|
||||
const OutsideModal = createStackNavigator();
|
||||
const OutsideStackModal = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<OutsideModal.Navigator mode='modal' screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation }}>
|
||||
<OutsideModal.Screen
|
||||
name='OutsideStack'
|
||||
component={OutsideStack}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<OutsideModal.Screen
|
||||
name='AuthenticationWebView'
|
||||
component={AuthenticationWebView}
|
||||
options={AuthenticationWebView.navigationOptions}
|
||||
/>
|
||||
</OutsideModal.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutsideStackModal;
|
222
app/tablet.js
222
app/tablet.js
|
@ -1,222 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavigationActions, StackActions } from 'react-navigation';
|
||||
import KeyCommands from 'react-native-keycommands';
|
||||
|
||||
import Navigation from './lib/Navigation';
|
||||
import { isSplited } from './utils/deviceInfo';
|
||||
import {
|
||||
App, RoomContainer, ModalContainer, NotificationContainer
|
||||
} from './index';
|
||||
import { MAX_SIDEBAR_WIDTH } from './constants/tablet';
|
||||
import ModalNavigation from './lib/ModalNavigation';
|
||||
import { keyCommands, defaultCommands } from './commands';
|
||||
import { themes } from './constants/colors';
|
||||
|
||||
import sharedStyles from './views/Styles';
|
||||
|
||||
let modalRef;
|
||||
let roomRef;
|
||||
let notificationRef;
|
||||
|
||||
export const initTabletNav = (setState) => {
|
||||
let inCall = false;
|
||||
|
||||
const defaultApp = App.router.getStateForAction;
|
||||
const defaultModal = ModalContainer.router.getStateForAction;
|
||||
const defaultRoom = RoomContainer.router.getStateForAction;
|
||||
const defaultNotification = NotificationContainer.router.getStateForAction;
|
||||
|
||||
NotificationContainer.router.getStateForAction = (action, state) => {
|
||||
if (action.type === NavigationActions.NAVIGATE && isSplited()) {
|
||||
const { routeName, params } = action;
|
||||
if (routeName === 'RoomView') {
|
||||
const resetAction = StackActions.reset({
|
||||
index: 0,
|
||||
actions: [NavigationActions.navigate({ routeName, params })]
|
||||
});
|
||||
roomRef.dispatch(resetAction);
|
||||
}
|
||||
}
|
||||
return defaultNotification(action, state);
|
||||
};
|
||||
|
||||
RoomContainer.router.getStateForAction = (action, state) => {
|
||||
if (action.type === NavigationActions.NAVIGATE && isSplited()) {
|
||||
const { routeName, params } = action;
|
||||
if (routeName === 'RoomActionsView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
if (routeName === 'AttachmentView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (action.type === 'Navigation/RESET' && isSplited()) {
|
||||
const { params } = action.actions[action.index];
|
||||
const routes = state.routes[state.index] && state.routes[state.index].params;
|
||||
if (params && params.rid && routes && routes.rid && params.rid === routes.rid) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return defaultRoom(action, state);
|
||||
};
|
||||
|
||||
ModalContainer.router.getStateForAction = (action, state) => {
|
||||
if (action.type === 'Navigation/POP' && isSplited()) {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName: 'AuthLoading' }));
|
||||
setState({ showModal: false });
|
||||
}
|
||||
if (action.type === NavigationActions.NAVIGATE && isSplited()) {
|
||||
const { routeName, params } = action;
|
||||
if (routeName === 'RoomView') {
|
||||
Navigation.navigate(routeName, params);
|
||||
}
|
||||
}
|
||||
return defaultModal(action, state);
|
||||
};
|
||||
|
||||
App.router.getStateForAction = (action, state) => {
|
||||
if (action.type === NavigationActions.NAVIGATE) {
|
||||
const { routeName, params } = action;
|
||||
|
||||
if (routeName === 'InsideStack') {
|
||||
let commands = defaultCommands;
|
||||
let newState = { inside: true };
|
||||
if (isSplited()) {
|
||||
commands = [...commands, ...keyCommands];
|
||||
newState = { ...newState, showModal: false };
|
||||
}
|
||||
KeyCommands.setKeyCommands(commands);
|
||||
setState(newState);
|
||||
}
|
||||
if (isSplited()) {
|
||||
if (routeName === 'ReadReceiptsView') {
|
||||
roomRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
return null;
|
||||
}
|
||||
if (routeName === 'OutsideStack') {
|
||||
KeyCommands.deleteKeyCommands([...defaultCommands, ...keyCommands]);
|
||||
setState({ inside: false, showModal: false });
|
||||
}
|
||||
if (routeName === 'JitsiMeetView') {
|
||||
inCall = true;
|
||||
KeyCommands.deleteKeyCommands([...defaultCommands, ...keyCommands]);
|
||||
setState({ inside: false, showModal: false });
|
||||
}
|
||||
if (routeName === 'OnboardingView' || routeName === 'NewServerView') {
|
||||
KeyCommands.deleteKeyCommands([...defaultCommands, ...keyCommands]);
|
||||
setState({ inside: false, showModal: false });
|
||||
}
|
||||
if (routeName === 'ModalBlockView' || routeName === 'StatusView' || routeName === 'CreateDiscussionView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
if (routeName === 'RoomView') {
|
||||
const resetAction = StackActions.reset({
|
||||
index: 0,
|
||||
actions: [NavigationActions.navigate({ routeName, params })]
|
||||
});
|
||||
roomRef.dispatch(resetAction);
|
||||
notificationRef.dispatch(resetAction);
|
||||
setState({ showModal: false });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (routeName === 'RoomsListView') {
|
||||
const resetAction = StackActions.reset({
|
||||
index: 0,
|
||||
actions: [NavigationActions.navigate({ routeName: 'RoomView', params: {} })]
|
||||
});
|
||||
roomRef.dispatch(resetAction);
|
||||
notificationRef.dispatch(resetAction);
|
||||
setState({ showModal: false });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (routeName === 'NewMessageView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
if (routeName === 'DirectoryView') {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action.type === 'Navigation/TOGGLE_DRAWER' && isSplited()) {
|
||||
modalRef.dispatch(NavigationActions.navigate({ routeName: 'SettingsView' }));
|
||||
setState({ showModal: true });
|
||||
return null;
|
||||
}
|
||||
if (action.type === 'Navigation/POP' && inCall) {
|
||||
KeyCommands.setKeyCommands([...defaultCommands, ...keyCommands]);
|
||||
setState({ inside: true, showModal: false });
|
||||
}
|
||||
return defaultApp(action, state);
|
||||
};
|
||||
};
|
||||
|
||||
const Split = ({
|
||||
split, tablet, showModal, closeModal, setModalRef, theme
|
||||
}) => {
|
||||
if (split) {
|
||||
return (
|
||||
<>
|
||||
<View style={[sharedStyles.container, sharedStyles.separatorLeft, { borderColor: themes[theme].separatorColor }]}>
|
||||
<RoomContainer ref={ref => roomRef = ref} screenProps={{ split: tablet, theme }} />
|
||||
</View>
|
||||
<ModalContainer showModal={showModal} closeModal={closeModal} ref={setModalRef} screenProps={{ split: tablet, theme }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const Tablet = ({
|
||||
children, tablet, theme, inside, showModal, closeModal, onLayout
|
||||
}) => {
|
||||
const setModalRef = (ref) => {
|
||||
modalRef = ref;
|
||||
ModalNavigation.setTopLevelNavigator(modalRef);
|
||||
};
|
||||
|
||||
const split = tablet && inside;
|
||||
return (
|
||||
<View style={sharedStyles.containerSplitView} onLayout={onLayout}>
|
||||
<View style={[sharedStyles.container, split && { maxWidth: MAX_SIDEBAR_WIDTH }]}>
|
||||
{children}
|
||||
</View>
|
||||
<Split split={split} tablet={tablet} theme={theme} showModal={showModal} closeModal={closeModal} setModalRef={setModalRef} />
|
||||
<NotificationContainer ref={ref => notificationRef = ref} screenProps={{ theme }} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
Split.propTypes = {
|
||||
split: PropTypes.bool,
|
||||
tablet: PropTypes.bool,
|
||||
showModal: PropTypes.bool,
|
||||
closeModal: PropTypes.func,
|
||||
setModalRef: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
Tablet.propTypes = {
|
||||
children: PropTypes.node,
|
||||
tablet: PropTypes.bool,
|
||||
inside: PropTypes.bool,
|
||||
showModal: PropTypes.bool,
|
||||
closeModal: PropTypes.func,
|
||||
onLayout: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default Tablet;
|
|
@ -1,8 +1,6 @@
|
|||
import { Platform } from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import { MIN_WIDTH_SPLIT_LAYOUT } from '../constants/tablet';
|
||||
|
||||
export const hasNotch = DeviceInfo.hasNotch();
|
||||
export const isIOS = Platform.OS === 'ios';
|
||||
export const isAndroid = !isIOS;
|
||||
|
@ -18,10 +16,3 @@ export const supportSystemTheme = () => {
|
|||
|
||||
// Tablet info
|
||||
export const isTablet = DeviceInfo.isTablet();
|
||||
|
||||
// We need to use this when app is used on splitview with another app
|
||||
// to handle cases on app view not-larger sufficient to show splited views (room list/room)
|
||||
// https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/1300#discussion_r341405245
|
||||
let _width = null;
|
||||
export const setWidth = width => _width = width;
|
||||
export const isSplited = () => isTablet && _width > MIN_WIDTH_SPLIT_LAYOUT;
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import Navigation from '../lib/Navigation';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const navigate = (item) => {
|
||||
Navigation.navigate('RoomView', {
|
||||
const navigate = ({ item, isMasterDetail, ...props }) => {
|
||||
let navigationMethod = Navigation.navigate;
|
||||
|
||||
if (isMasterDetail) {
|
||||
navigationMethod = Navigation.replace;
|
||||
}
|
||||
|
||||
navigationMethod('RoomView', {
|
||||
rid: item.rid,
|
||||
name: RocketChat.getRoomTitle(item),
|
||||
t: item.t,
|
||||
|
@ -10,30 +16,32 @@ const navigate = (item) => {
|
|||
room: item,
|
||||
search: item.search,
|
||||
visitor: item.visitor,
|
||||
roomUserId: RocketChat.getUidDirectMessage(item)
|
||||
roomUserId: RocketChat.getUidDirectMessage(item),
|
||||
...props
|
||||
});
|
||||
};
|
||||
|
||||
export const goRoom = async(item = {}) => {
|
||||
if (!item.search) {
|
||||
return navigate(item);
|
||||
}
|
||||
if (item.t === 'd') {
|
||||
export const goRoom = async({ item = {}, isMasterDetail = false, ...props }) => {
|
||||
if (item.t === 'd' && item.search) {
|
||||
// if user is using the search we need first to join/create room
|
||||
try {
|
||||
const { username } = item;
|
||||
const result = await RocketChat.createDirectMessage(username);
|
||||
if (result.success) {
|
||||
return navigate({
|
||||
rid: result.room._id,
|
||||
name: username,
|
||||
t: 'd'
|
||||
item: {
|
||||
rid: result.room._id,
|
||||
name: username,
|
||||
t: 'd'
|
||||
},
|
||||
isMasterDetail,
|
||||
...props
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
} else {
|
||||
return navigate(item);
|
||||
}
|
||||
|
||||
return navigate({ item, isMasterDetail, ...props });
|
||||
};
|
||||
|
|
|
@ -16,6 +16,11 @@ export const logServerVersion = (serverVersion) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const setCurrentScreen = (currentScreen) => {
|
||||
analytics().setCurrentScreen(currentScreen);
|
||||
leaveBreadcrumb(currentScreen, { type: 'navigation' });
|
||||
};
|
||||
|
||||
export default (e) => {
|
||||
if (e instanceof Error && e.message !== 'Aborted' && !__DEV__) {
|
||||
bugsnag.notify(e, (report) => {
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { analytics, leaveBreadcrumb } from './log';
|
||||
import { themes } from '../constants/colors';
|
||||
|
||||
export const defaultHeader = {
|
||||
headerBackTitle: null
|
||||
};
|
||||
|
||||
export const cardStyle = {
|
||||
backgroundColor: 'rgba(0,0,0,0)'
|
||||
};
|
||||
|
||||
const borderBottom = theme => ({
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: themes[theme].headerBorder,
|
||||
elevation: 0
|
||||
});
|
||||
|
||||
export const themedHeader = theme => ({
|
||||
headerStyle: {
|
||||
...borderBottom(theme),
|
||||
backgroundColor: themes[theme].headerBackground
|
||||
},
|
||||
headerTintColor: themes[theme].headerTintColor,
|
||||
headerTitleStyle: { color: themes[theme].headerTitleColor }
|
||||
});
|
||||
|
||||
// gets the current screen from navigation state
|
||||
export const getActiveRouteName = (navigationState) => {
|
||||
if (!navigationState) {
|
||||
return null;
|
||||
}
|
||||
const route = navigationState.routes[navigationState.index];
|
||||
// dive into nested navigators
|
||||
if (route.routes) {
|
||||
return getActiveRouteName(route);
|
||||
}
|
||||
return route.routeName;
|
||||
};
|
||||
|
||||
export const onNavigationStateChange = (prevState, currentState) => {
|
||||
const currentScreen = getActiveRouteName(currentState);
|
||||
const prevScreen = getActiveRouteName(prevState);
|
||||
|
||||
if (prevScreen !== currentScreen) {
|
||||
analytics().setCurrentScreen(currentScreen);
|
||||
leaveBreadcrumb(currentScreen, { type: 'navigation' });
|
||||
}
|
||||
};
|
|
@ -0,0 +1,96 @@
|
|||
import { Easing, Animated } from 'react-native';
|
||||
import { TransitionPresets, HeaderStyleInterpolators } from '@react-navigation/stack';
|
||||
|
||||
import { isAndroid } from '../deviceInfo';
|
||||
import conditional from './conditional';
|
||||
|
||||
const { multiply } = Animated;
|
||||
|
||||
const forFadeFromCenter = ({
|
||||
current,
|
||||
closing
|
||||
}) => {
|
||||
const opacity = conditional(
|
||||
closing,
|
||||
current.progress,
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 0.5, 0.9, 1],
|
||||
outputRange: [0, 0.25, 0.7, 1]
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
cardStyle: {
|
||||
opacity
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const FadeIn = {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 250,
|
||||
easing: Easing.out(Easing.poly(5))
|
||||
}
|
||||
};
|
||||
|
||||
const FadeOut = {
|
||||
animation: 'timing',
|
||||
config: {
|
||||
duration: 150,
|
||||
easing: Easing.in(Easing.poly(5))
|
||||
}
|
||||
};
|
||||
|
||||
export const FadeFromCenterModal = {
|
||||
gestureDirection: 'vertical',
|
||||
transitionSpec: {
|
||||
open: FadeIn,
|
||||
close: FadeOut
|
||||
},
|
||||
cardStyleInterpolator: forFadeFromCenter
|
||||
};
|
||||
|
||||
const forStackAndroid = ({
|
||||
current,
|
||||
inverted,
|
||||
layouts: { screen },
|
||||
closing
|
||||
}) => {
|
||||
const translateX = multiply(
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [screen.width, 0]
|
||||
}),
|
||||
inverted
|
||||
);
|
||||
|
||||
const opacity = conditional(
|
||||
closing,
|
||||
current.progress,
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1]
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
cardStyle: {
|
||||
opacity,
|
||||
transform: [{ translateX }]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const StackAndroid = {
|
||||
gestureDirection: 'horizontal',
|
||||
transitionSpec: {
|
||||
open: FadeIn,
|
||||
close: FadeOut
|
||||
},
|
||||
cardStyleInterpolator: forStackAndroid,
|
||||
headerStyleInterpolator: HeaderStyleInterpolators.forFade
|
||||
};
|
||||
|
||||
export const StackAnimation = isAndroid ? StackAndroid : TransitionPresets.SlideFromRightIOS;
|
||||
export const ModalAnimation = TransitionPresets.ModalTransition;
|
|
@ -0,0 +1,30 @@
|
|||
// Taken from https://github.com/react-navigation/react-navigation/blob/master/packages/stack/src/utils/conditional.tsx
|
||||
import { Animated } from 'react-native';
|
||||
|
||||
const { add, multiply } = Animated;
|
||||
|
||||
/**
|
||||
* Use an Animated Node based on a condition. Similar to Reanimated's `cond`.
|
||||
*
|
||||
* @param condition Animated Node representing the condition, must be 0 or 1, 1 means `true`, 0 means `false`
|
||||
* @param main Animated Node to use if the condition is `true`
|
||||
* @param fallback Animated Node to use if the condition is `false`
|
||||
*/
|
||||
export default function conditional(condition, main, fallback) {
|
||||
// To implement this behavior, we multiply the main node with the condition.
|
||||
// So if condition is 0, result will be 0, and if condition is 1, result will be main node.
|
||||
// Then we multiple reverse of the condition (0 if condition is 1) with the fallback.
|
||||
// So if condition is 0, result will be fallback node, and if condition is 1, result will be 0,
|
||||
// This way, one of them will always be 0, and other one will be the value we need.
|
||||
// In the end we add them both together, 0 + value we need = value we need
|
||||
return add(
|
||||
multiply(condition, main),
|
||||
multiply(
|
||||
condition.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 0]
|
||||
}),
|
||||
fallback
|
||||
)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
import { DefaultTheme, DarkTheme } from '@react-navigation/native';
|
||||
|
||||
import { themes } from '../../constants/colors';
|
||||
|
||||
export * from './animations';
|
||||
|
||||
export const defaultHeader = {
|
||||
headerBackTitleVisible: false,
|
||||
cardOverlayEnabled: true,
|
||||
cardStyle: { backgroundColor: 'transparent' }
|
||||
};
|
||||
|
||||
|
||||
export const cardStyle = {
|
||||
backgroundColor: 'rgba(0,0,0,0)'
|
||||
};
|
||||
|
||||
export const borderBottom = theme => ({
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: themes[theme].headerBorder,
|
||||
elevation: 0
|
||||
});
|
||||
|
||||
export const themedHeader = theme => ({
|
||||
headerStyle: {
|
||||
...borderBottom(theme),
|
||||
backgroundColor: themes[theme].headerBackground
|
||||
},
|
||||
headerTintColor: themes[theme].headerTintColor,
|
||||
headerTitleStyle: { color: themes[theme].headerTitleColor }
|
||||
});
|
||||
|
||||
export const navigationTheme = (theme) => {
|
||||
const defaultNavTheme = theme === 'light' ? DefaultTheme : DarkTheme;
|
||||
|
||||
return {
|
||||
...defaultNavTheme,
|
||||
colors: {
|
||||
...defaultNavTheme.colors,
|
||||
background: themes[theme].backgroundColor,
|
||||
border: themes[theme].borderColor
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Gets the current screen from navigation state
|
||||
export const getActiveRoute = (state) => {
|
||||
const route = state.routes[state.index];
|
||||
|
||||
if (route.state) {
|
||||
// Dive into nested navigators
|
||||
return getActiveRoute(route.state);
|
||||
}
|
||||
|
||||
return route;
|
||||
};
|
||||
|
||||
export const getActiveRouteName = state => getActiveRoute(state).name;
|
|
@ -1,22 +1,18 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { DrawerButton } from '../../containers/HeaderButton';
|
||||
import styles from '../Styles';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class AdminPanelView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||
...themedHeader(screenProps.theme),
|
||||
headerLeft: <DrawerButton navigation={navigation} />,
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => ({
|
||||
headerLeft: isMasterDetail ? undefined : () => <DrawerButton navigation={navigation} />,
|
||||
title: I18n.t('Admin_Panel')
|
||||
})
|
||||
|
||||
|
@ -32,7 +28,7 @@ class AdminPanelView extends React.Component {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='terms-view'>
|
||||
<SafeAreaView theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<WebView
|
||||
source={{ uri: `${ baseUrl }/admin/info?layout=embedded` }}
|
||||
|
|
|
@ -13,7 +13,6 @@ import EventEmitter from '../utils/events';
|
|||
import I18n from '../i18n';
|
||||
import { withTheme } from '../theme';
|
||||
import ImageViewer from '../presentation/ImageViewer';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { themes } from '../constants/colors';
|
||||
import { formatAttachmentUrl } from '../lib/utils';
|
||||
import RCActivityIndicator from '../containers/ActivityIndicator';
|
||||
|
@ -28,26 +27,9 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class AttachmentView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const { theme } = screenProps;
|
||||
const attachment = navigation.getParam('attachment');
|
||||
const from = navigation.getParam('from');
|
||||
const handleSave = navigation.getParam('handleSave', () => {});
|
||||
const { title } = attachment;
|
||||
const options = {
|
||||
title: decodeURI(title),
|
||||
...themedHeader(theme),
|
||||
headerRight: <SaveButton testID='save-image' onPress={handleSave} />
|
||||
};
|
||||
if (from !== 'MessagesView') {
|
||||
options.gesturesEnabled = false;
|
||||
options.headerLeft = <CloseModalButton testID='close-attachment-view' navigation={navigation} />;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
|
@ -58,15 +40,14 @@ class AttachmentView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const attachment = props.navigation.getParam('attachment');
|
||||
const attachment = props.route.params?.attachment;
|
||||
this.state = { attachment, loading: true };
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
navigation.setParams({ handleSave: this.handleSave });
|
||||
|
||||
this.willBlurListener = navigation.addListener('willBlur', () => {
|
||||
this.unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
if (this.videoRef && this.videoRef.stopAsync) {
|
||||
this.videoRef.stopAsync();
|
||||
}
|
||||
|
@ -74,11 +55,23 @@ class AttachmentView extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.willBlurListener && this.willBlurListener.remove) {
|
||||
this.willBlurListener.remove();
|
||||
if (this.unsubscribeBlur) {
|
||||
this.unsubscribeBlur();
|
||||
}
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { route, navigation } = this.props;
|
||||
const attachment = route.params?.attachment;
|
||||
const { title } = attachment;
|
||||
const options = {
|
||||
headerLeft: () => <CloseModalButton testID='close-attachment-view' navigation={navigation} />,
|
||||
title: decodeURI(title),
|
||||
headerRight: () => <SaveButton testID='save-image' onPress={this.handleSave} />
|
||||
};
|
||||
navigation.setOptions(options);
|
||||
}
|
||||
|
||||
getVideoRef = ref => this.videoRef = ref;
|
||||
|
||||
handleSave = async() => {
|
||||
|
|
|
@ -2,6 +2,8 @@ import React from 'react';
|
|||
import {
|
||||
View, Text, StyleSheet, ActivityIndicator
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
|
@ -24,17 +26,25 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
export default React.memo(withTheme(({ theme, navigation }) => {
|
||||
const text = navigation.getParam('text');
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<StatusBar theme={theme} />
|
||||
{text && (
|
||||
<>
|
||||
<ActivityIndicator color={themes[theme].auxiliaryText} size='large' />
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{`${ text }\n${ I18n.t('Please_wait') }`}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}));
|
||||
const AuthLoadingView = React.memo(({ theme, text }) => (
|
||||
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<StatusBar theme={theme} />
|
||||
{text && (
|
||||
<>
|
||||
<ActivityIndicator color={themes[theme].auxiliaryText} size='large' />
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{`${ text }\n${ I18n.t('Please_wait') }`}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
));
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
text: state.app.text
|
||||
});
|
||||
|
||||
AuthLoadingView.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
text: PropTypes.string
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AuthLoadingView));
|
||||
|
|
|
@ -6,29 +6,20 @@ import parse from 'url-parse';
|
|||
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { CloseModalButton } from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import debounce from '../utils/debounce';
|
||||
import { CloseModalButton } from '../containers/HeaderButton';
|
||||
|
||||
const userAgent = isIOS
|
||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
|
||||
: 'Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36';
|
||||
|
||||
class AuthenticationWebView extends React.PureComponent {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const authType = navigation.getParam('authType', 'oauth');
|
||||
return {
|
||||
...themedHeader(screenProps.theme),
|
||||
headerLeft: <CloseModalButton navigation={navigation} />,
|
||||
title: authType === 'saml' || authType === 'cas' ? 'SSO' : 'OAuth'
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
server: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
@ -39,7 +30,6 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
logging: false,
|
||||
loading: false
|
||||
};
|
||||
this.authType = props.navigation.getParam('authType', 'oauth');
|
||||
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||
}
|
||||
|
||||
|
@ -76,14 +66,15 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
|
||||
onNavigationStateChange = (webViewState) => {
|
||||
const url = decodeURIComponent(webViewState.url);
|
||||
if (this.authType === 'saml' || this.authType === 'cas') {
|
||||
const { navigation } = this.props;
|
||||
const ssoToken = navigation.getParam('ssoToken');
|
||||
const { route } = this.props;
|
||||
const { authType } = route.params;
|
||||
if (authType === 'saml' || authType === 'cas') {
|
||||
const { ssoToken } = route.params;
|
||||
const parsedUrl = parse(url, true);
|
||||
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
||||
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
||||
let payload;
|
||||
if (this.authType === 'saml') {
|
||||
if (authType === 'saml') {
|
||||
const token = parsedUrl.query?.saml_idp_credentialToken || ssoToken;
|
||||
const credentialToken = { credentialToken: token };
|
||||
payload = { ...credentialToken, saml: true };
|
||||
|
@ -94,7 +85,7 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.authType === 'oauth') {
|
||||
if (authType === 'oauth') {
|
||||
if (this.redirectRegex.test(url)) {
|
||||
const parts = url.split('#');
|
||||
const credentials = JSON.parse(parts[1]);
|
||||
|
@ -105,13 +96,13 @@ class AuthenticationWebView extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const { loading } = this.state;
|
||||
const { navigation, theme } = this.props;
|
||||
const uri = navigation.getParam('url');
|
||||
const { route, theme } = this.props;
|
||||
const { url } = route.params;
|
||||
return (
|
||||
<>
|
||||
<StatusBar theme={theme} />
|
||||
<WebView
|
||||
source={{ uri }}
|
||||
source={{ uri: url }}
|
||||
userAgent={userAgent}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
onLoadStart={() => {
|
||||
|
@ -131,4 +122,12 @@ const mapStateToProps = state => ({
|
|||
server: state.server.server
|
||||
});
|
||||
|
||||
AuthenticationWebView.navigationOptions = ({ route, navigation }) => {
|
||||
const { authType } = route.params;
|
||||
return {
|
||||
headerLeft: () => <CloseModalButton navigation={navigation} />,
|
||||
title: authType === 'saml' || authType === 'cas' ? 'SSO' : 'OAuth'
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AuthenticationWebView));
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList, Switch, View, StyleSheet
|
||||
FlatList, Switch, View, StyleSheet, ScrollView
|
||||
} from 'react-native';
|
||||
import { SafeAreaView, ScrollView } from 'react-navigation';
|
||||
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -15,7 +14,7 @@ import Separator from '../../containers/Separator';
|
|||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainerStyle: {
|
||||
|
@ -49,20 +48,19 @@ SectionSeparator.propTypes = {
|
|||
};
|
||||
|
||||
class AutoTranslateView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Auto_Translate'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Auto_Translate')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
const room = props.navigation.getParam('room');
|
||||
this.rid = props.route.params?.rid;
|
||||
const room = props.route.params?.room;
|
||||
|
||||
if (room && room.observe) {
|
||||
this.roomObservable = room.observe();
|
||||
|
@ -163,11 +161,7 @@ class AutoTranslateView extends React.Component {
|
|||
const { languages } = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[sharedStyles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
testID='auto-translate-view'
|
||||
>
|
||||
<SafeAreaView testID='auto-translate-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
|
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
View, Text, Switch, ScrollView, StyleSheet, FlatList
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import TextInput from '../presentation/TextInput';
|
||||
|
@ -20,9 +19,9 @@ import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
|
|||
import StatusBar from '../containers/StatusBar';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { Review } from '../utils/review';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -73,22 +72,8 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class CreateChannelView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const submit = navigation.getParam('submit', () => {});
|
||||
const showSubmit = navigation.getParam('showSubmit');
|
||||
return {
|
||||
...themedHeader(screenProps.theme),
|
||||
title: I18n.t('Create_Channel'),
|
||||
headerRight: (
|
||||
showSubmit
|
||||
? (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={I18n.t('Create')} onPress={submit} testID='create-channel-submit' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
: null
|
||||
)
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Create_Channel')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
|
@ -114,11 +99,6 @@ class CreateChannelView extends React.Component {
|
|||
broadcast: false
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
navigation.setParams({ submit: this.submit });
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
channelName, type, readOnly, broadcast
|
||||
|
@ -148,9 +128,19 @@ class CreateChannelView extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
onChangeText = (channelName) => {
|
||||
toggleRightButton = (channelName) => {
|
||||
const { navigation } = this.props;
|
||||
navigation.setParams({ showSubmit: channelName.trim().length > 0 });
|
||||
navigation.setOptions({
|
||||
headerRight: () => channelName.trim().length > 0 && (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={I18n.t('Create')} onPress={this.submit} testID='create-channel-submit' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
onChangeText = (channelName) => {
|
||||
this.toggleRightButton(channelName);
|
||||
this.setState({ channelName });
|
||||
}
|
||||
|
||||
|
@ -294,7 +284,7 @@ class CreateChannelView extends React.Component {
|
|||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<SafeAreaView testID='create-channel-view' style={styles.container} forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView testID='create-channel-view' theme={theme}>
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}>
|
||||
<TextInput
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, Text } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import Loading from '../../containers/Loading';
|
||||
|
@ -13,7 +12,6 @@ import { CustomHeaderButtons, Item, CloseModalButton } from '../../containers/He
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import TextInput from '../../containers/TextInput';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
@ -25,29 +23,13 @@ import SelectChannel from './SelectChannel';
|
|||
import SelectUsers from './SelectUsers';
|
||||
|
||||
import styles from './styles';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
|
||||
class CreateChannelView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const submit = navigation.getParam('submit', () => {});
|
||||
const showSubmit = navigation.getParam('showSubmit', navigation.getParam('message'));
|
||||
return {
|
||||
...themedHeader(screenProps.theme),
|
||||
title: I18n.t('Create_Discussion'),
|
||||
headerRight: (
|
||||
showSubmit
|
||||
? (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={I18n.t('Create')} onPress={submit} testID='create-discussion-submit' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
: null
|
||||
),
|
||||
headerLeft: <CloseModalButton navigation={navigation} />
|
||||
};
|
||||
}
|
||||
|
||||
propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
server: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
create: PropTypes.func,
|
||||
|
@ -55,31 +37,32 @@ class CreateChannelView extends React.Component {
|
|||
result: PropTypes.object,
|
||||
failure: PropTypes.bool,
|
||||
error: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { navigation } = props;
|
||||
navigation.setParams({ submit: this.submit });
|
||||
this.channel = navigation.getParam('channel');
|
||||
const message = navigation.getParam('message', {});
|
||||
const { route } = props;
|
||||
this.channel = route.params?.channel;
|
||||
const message = route.params?.message ?? {};
|
||||
this.state = {
|
||||
channel: this.channel,
|
||||
message,
|
||||
name: message.msg || '',
|
||||
name: message?.msg || '',
|
||||
users: [],
|
||||
reply: ''
|
||||
};
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
loading, failure, error, result, navigation
|
||||
loading, failure, error, result, isMasterDetail
|
||||
} = this.props;
|
||||
|
||||
if (!isEqual(this.state, prevState)) {
|
||||
navigation.setParams({ showSubmit: this.valid() });
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
if (!loading && loading !== prevProps.loading) {
|
||||
|
@ -89,17 +72,38 @@ class CreateChannelView extends React.Component {
|
|||
showErrorAlert(msg);
|
||||
} else {
|
||||
const { rid, t, prid } = result;
|
||||
if (this.channel) {
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
Navigation.navigate('RoomView', {
|
||||
const item = {
|
||||
rid, name: RocketChat.getRoomTitle(result), t, prid
|
||||
});
|
||||
};
|
||||
goRoom({ item, isMasterDetail });
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { navigation, route } = this.props;
|
||||
const showCloseModal = route.params?.showCloseModal;
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Create_Discussion'),
|
||||
headerRight: (
|
||||
this.valid()
|
||||
? () => (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={I18n.t('Create')} onPress={this.submit} testID='create-discussion-submit' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
: null
|
||||
),
|
||||
headerLeft: showCloseModal ? () => <CloseModalButton navigation={navigation} /> : undefined
|
||||
});
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
const {
|
||||
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users
|
||||
|
@ -137,7 +141,7 @@ class CreateChannelView extends React.Component {
|
|||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<SafeAreaView testID='create-discussion-view' style={styles.container} forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView testID='create-discussion-view' style={styles.container} theme={theme}>
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text>
|
||||
<SelectChannel
|
||||
|
@ -187,7 +191,8 @@ const mapStateToProps = state => ({
|
|||
error: state.createDiscussion.error,
|
||||
failure: state.createDiscussion.failure,
|
||||
loading: state.createDiscussion.isFetching,
|
||||
result: state.createDiscussion.result
|
||||
result: state.createDiscussion.result,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -3,11 +3,9 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
StyleSheet, FlatList, View, Text, Linking
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { withTheme } from '../theme';
|
||||
import { themes } from '../constants/colors';
|
||||
import sharedStyles from './Styles';
|
||||
|
@ -17,6 +15,7 @@ import ListItem from '../containers/ListItem';
|
|||
import { CustomIcon } from '../lib/Icons';
|
||||
import { DEFAULT_BROWSER_KEY } from '../utils/openLink';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
||||
const DEFAULT_BROWSERS = [
|
||||
{
|
||||
|
@ -60,10 +59,9 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class DefaultBrowserView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Default_browser'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Default_browser')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
theme: PropTypes.string
|
||||
|
@ -164,11 +162,7 @@ class DefaultBrowserView extends React.Component {
|
|||
const { supported } = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[sharedStyles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
testID='default-browser-view'
|
||||
>
|
||||
<SafeAreaView testID='default-browser-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
data={DEFAULT_BROWSERS.concat(supported)}
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
View, FlatList, Text
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
@ -22,17 +21,17 @@ import Options from './Options';
|
|||
import { withTheme } from '../../theme';
|
||||
import { themes } from '../../constants/colors';
|
||||
import styles from './styles';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
|
||||
class DirectoryView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
||||
const options = {
|
||||
...themedHeader(screenProps.theme),
|
||||
title: I18n.t('Directory')
|
||||
};
|
||||
if (screenProps.split) {
|
||||
options.headerLeft = <CloseModalButton navigation={navigation} testID='directory-view-close' />;
|
||||
if (isMasterDetail) {
|
||||
options.headerLeft = () => <CloseModalButton navigation={navigation} testID='directory-view-close' />;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
@ -46,7 +45,8 @@ class DirectoryView extends React.Component {
|
|||
token: PropTypes.string
|
||||
}),
|
||||
theme: PropTypes.string,
|
||||
directoryDefaultView: PropTypes.string
|
||||
directoryDefaultView: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -125,14 +125,14 @@ class DirectoryView extends React.Component {
|
|||
this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown }));
|
||||
}
|
||||
|
||||
goRoom = async({
|
||||
rid, name, t, search
|
||||
}) => {
|
||||
const { navigation } = this.props;
|
||||
await navigation.navigate('RoomsListView');
|
||||
navigation.navigate('RoomView', {
|
||||
rid, name, t, search
|
||||
});
|
||||
goRoom = (item) => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
if (isMasterDetail) {
|
||||
navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
navigation.navigate('RoomsListView');
|
||||
}
|
||||
goRoom({ item, isMasterDetail });
|
||||
}
|
||||
|
||||
onPressItem = async(item) => {
|
||||
|
@ -230,7 +230,11 @@ class DirectoryView extends React.Component {
|
|||
} = this.state;
|
||||
const { isFederationEnabled, theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={[styles.safeAreaView, { backgroundColor: themes[theme].backgroundColor }]} testID='directory-view' forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
testID='directory-view'
|
||||
theme={theme}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
data={data}
|
||||
|
@ -267,7 +271,8 @@ const mapStateToProps = state => ({
|
|||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
isFederationEnabled: state.settings.FEDERATION_Enabled,
|
||||
directoryDefaultView: state.settings.Accounts_Directory_DefaultView
|
||||
directoryDefaultView: state.settings.Accounts_Directory_DefaultView,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(DirectoryView));
|
||||
|
|
|
@ -3,9 +3,6 @@ import { StyleSheet } from 'react-native';
|
|||
import sharedStyles from '../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
safeAreaView: {
|
||||
flex: 1
|
||||
},
|
||||
list: {
|
||||
flex: 1
|
||||
},
|
||||
|
|
|
@ -11,17 +11,12 @@ import I18n from '../i18n';
|
|||
import RocketChat from '../lib/rocketchat';
|
||||
import { withTheme } from '../theme';
|
||||
import { themes } from '../constants/colors';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
|
||||
class ForgotPasswordView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const title = navigation.getParam('title', 'Rocket.Chat');
|
||||
return {
|
||||
title,
|
||||
...themedHeader(screenProps.theme)
|
||||
};
|
||||
}
|
||||
static navigationOptions = ({ route }) => ({
|
||||
title: route.params?.title ?? 'Rocket.Chat'
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
|
|
|
@ -18,14 +18,16 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const ForwardLivechatView = ({ forwardRoom, navigation, theme }) => {
|
||||
const ForwardLivechatView = ({
|
||||
forwardRoom, navigation, route, theme
|
||||
}) => {
|
||||
const [departments, setDepartments] = useState([]);
|
||||
const [departmentId, setDepartment] = useState();
|
||||
const [users, setUsers] = useState([]);
|
||||
const [userId, setUser] = useState();
|
||||
const [room, setRoom] = useState();
|
||||
|
||||
const rid = navigation.getParam('rid');
|
||||
const rid = route.params?.rid;
|
||||
|
||||
const getDepartments = async() => {
|
||||
try {
|
||||
|
@ -137,6 +139,7 @@ const ForwardLivechatView = ({ forwardRoom, navigation, theme }) => {
|
|||
ForwardLivechatView.propTypes = {
|
||||
forwardRoom: PropTypes.func,
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
ForwardLivechatView.navigationOptions = {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import { connect } from 'react-redux';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
|
||||
|
@ -17,8 +16,8 @@ import I18n from '../../i18n';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import Separator from '../../containers/Separator';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
const OPTIONS = {
|
||||
days: [{
|
||||
|
@ -60,13 +59,13 @@ const OPTIONS = {
|
|||
};
|
||||
|
||||
class InviteUsersView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Invite_users'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Invite_users')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
timeDateFormat: PropTypes.string,
|
||||
createInviteLink: PropTypes.func,
|
||||
|
@ -75,7 +74,7 @@ class InviteUsersView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.rid = props.route.params?.rid;
|
||||
}
|
||||
|
||||
onValueChangePicker = (key, value) => {
|
||||
|
@ -111,7 +110,7 @@ class InviteUsersView extends React.Component {
|
|||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} theme={theme}>
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
|
|
|
@ -3,9 +3,6 @@ import { StyleSheet } from 'react-native';
|
|||
import sharedStyles from '../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
innerContainer: {
|
||||
paddingHorizontal: 20
|
||||
},
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Share, ScrollView } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -18,16 +17,16 @@ import I18n from '../../i18n';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class InviteUsersView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Invite_users'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Invite_users')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
timeDateFormat: PropTypes.string,
|
||||
invite: PropTypes.object,
|
||||
|
@ -37,7 +36,7 @@ class InviteUsersView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.rid = props.route.params?.rid;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -100,7 +99,7 @@ class InviteUsersView extends React.Component {
|
|||
theme, invite
|
||||
} = this.props;
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} theme={theme}>
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
innerContainer: {
|
||||
padding: 20,
|
||||
paddingBottom: 0
|
||||
|
|
|
@ -16,6 +16,7 @@ const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
|
|||
class JitsiMeetView extends React.Component {
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
|
@ -27,14 +28,14 @@ class JitsiMeetView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.rid = props.route.params?.rid;
|
||||
this.onConferenceTerminated = this.onConferenceTerminated.bind(this);
|
||||
this.onConferenceJoined = this.onConferenceJoined.bind(this);
|
||||
this.jitsiTimeout = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation, user, baseUrl } = this.props;
|
||||
const { route, user, baseUrl } = this.props;
|
||||
const {
|
||||
name: displayName, id: userId, token, username
|
||||
} = user;
|
||||
|
@ -47,8 +48,8 @@ class JitsiMeetView extends React.Component {
|
|||
displayName,
|
||||
avatar
|
||||
};
|
||||
const url = navigation.getParam('url');
|
||||
const onlyAudio = navigation.getParam('onlyAudio', false);
|
||||
const url = route.params?.url;
|
||||
const onlyAudio = route.params?.onlyAudio ?? false;
|
||||
if (onlyAudio) {
|
||||
JitsiMeet.audioCall(url, userInfo);
|
||||
} else {
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FlatList } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -16,10 +15,10 @@ import ListItem from '../../containers/ListItem';
|
|||
import Separator from '../../containers/Separator';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { appStart as appStartAction } from '../../actions';
|
||||
import { appStart as appStartAction, ROOT_LOADING, ROOT_INSIDE } from '../../actions/app';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import database from '../../lib/database';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
const LANGUAGES = [
|
||||
{
|
||||
|
@ -59,10 +58,9 @@ const LANGUAGES = [
|
|||
];
|
||||
|
||||
class LanguageView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Change_Language'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Change_Language')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
user: PropTypes.object,
|
||||
|
@ -105,12 +103,12 @@ class LanguageView extends React.Component {
|
|||
|
||||
const { appStart } = this.props;
|
||||
|
||||
await appStart('loading', I18n.t('Change_language_loading'));
|
||||
await appStart({ root: ROOT_LOADING, text: I18n.t('Change_language_loading') });
|
||||
|
||||
// shows loading for at least 300ms
|
||||
await Promise.all([this.changeLanguage(language), new Promise(resolve => setTimeout(resolve, 300))]);
|
||||
|
||||
await appStart('inside');
|
||||
await appStart({ root: ROOT_INSIDE });
|
||||
}
|
||||
|
||||
changeLanguage = async(language) => {
|
||||
|
@ -175,11 +173,7 @@ class LanguageView extends React.Component {
|
|||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[sharedStyles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
testID='language-view'
|
||||
>
|
||||
<SafeAreaView testID='language-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
data={LANGUAGES}
|
||||
|
@ -205,7 +199,7 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setUser: params => dispatch(setUserAction(params)),
|
||||
appStart: (...params) => dispatch(appStartAction(...params))
|
||||
appStart: params => dispatch(appStartAction(params))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(LanguageView));
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import {
|
||||
Text, ScrollView, View, StyleSheet
|
||||
} from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Touch from '../utils/touch';
|
||||
|
@ -15,12 +14,9 @@ import StatusBar from '../containers/StatusBar';
|
|||
import { themes } from '../constants/colors';
|
||||
import openLink from '../utils/openLink';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
scroll: {
|
||||
marginTop: 35,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
|
@ -52,11 +48,6 @@ Separator.propTypes = {
|
|||
};
|
||||
|
||||
class LegalView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Legal'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
server: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
|
@ -88,14 +79,7 @@ class LegalView extends React.Component {
|
|||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: themes[theme].auxiliaryBackground }
|
||||
]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
testID='legal-view'
|
||||
>
|
||||
<SafeAreaView testID='legal-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
|
@ -120,4 +104,8 @@ const mapStateToProps = state => ({
|
|||
server: state.server.server
|
||||
});
|
||||
|
||||
LegalView.navigationOptions = {
|
||||
title: I18n.t('Legal')
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(LegalView));
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, StyleSheet, ScrollView } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { withTheme } from '../theme';
|
||||
|
@ -18,6 +17,7 @@ import scrollPersistTaps from '../utils/scrollPersistTaps';
|
|||
import { getUserSelector } from '../selectors/login';
|
||||
import Chips from '../containers/UIKit/MultiSelect/Chips';
|
||||
import Button from '../containers/Button';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -36,15 +36,17 @@ Title.propTypes = {
|
|||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
const LivechatEditView = ({ user, navigation, theme }) => {
|
||||
const LivechatEditView = ({
|
||||
user, navigation, route, theme
|
||||
}) => {
|
||||
const [customFields, setCustomFields] = useState({});
|
||||
const [availableUserTags, setAvailableUserTags] = useState([]);
|
||||
|
||||
const params = {};
|
||||
const inputs = {};
|
||||
|
||||
const livechat = navigation.getParam('room', {});
|
||||
const visitor = navigation.getParam('roomUser', {});
|
||||
const livechat = route.params?.room ?? {};
|
||||
const visitor = route.params?.roomUser ?? {};
|
||||
|
||||
const getCustomFields = async() => {
|
||||
const result = await RocketChat.getCustomFields();
|
||||
|
@ -148,8 +150,8 @@ const LivechatEditView = ({ user, navigation, theme }) => {
|
|||
contentContainerStyle={sharedStyles.container}
|
||||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<ScrollView {...scrollPersistTaps}>
|
||||
<SafeAreaView style={[sharedStyles.container, styles.container]} forceInset={{ vertical: 'never' }}>
|
||||
<ScrollView {...scrollPersistTaps} style={styles.container}>
|
||||
<SafeAreaView theme={theme}>
|
||||
<Title
|
||||
title={visitor?.username}
|
||||
theme={theme}
|
||||
|
@ -271,6 +273,7 @@ const LivechatEditView = ({ user, navigation, theme }) => {
|
|||
LivechatEditView.propTypes = {
|
||||
user: PropTypes.object,
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
LivechatEditView.navigationOptions = ({
|
||||
|
|
|
@ -13,7 +13,6 @@ import I18n from '../i18n';
|
|||
import { LegalButton } from '../containers/HeaderButton';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import { loginRequest as loginRequestAction } from '../actions/login';
|
||||
|
@ -51,14 +50,10 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class LoginView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const title = navigation.getParam('title', 'Rocket.Chat');
|
||||
return {
|
||||
...themedHeader(screenProps.theme),
|
||||
title,
|
||||
headerRight: <LegalButton testID='login-view-more' navigation={navigation} />
|
||||
};
|
||||
}
|
||||
static navigationOptions = ({ route, navigation }) => ({
|
||||
title: route.params?.title ?? 'Rocket.Chat',
|
||||
headerRight: () => <LegalButton testID='login-view-more' navigation={navigation} />
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
|
@ -205,11 +200,11 @@ class LoginView extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { Accounts_ShowFormLogin, theme } = this.props;
|
||||
const { Accounts_ShowFormLogin, theme, navigation } = this.props;
|
||||
return (
|
||||
<FormContainer theme={theme} testID='login-view'>
|
||||
<FormContainerInner>
|
||||
<LoginServices separator={Accounts_ShowFormLogin} />
|
||||
<LoginServices separator={Accounts_ShowFormLogin} navigation={navigation} />
|
||||
{this.renderUserForm()}
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
|
|
|
@ -6,23 +6,21 @@ import I18n from '../i18n';
|
|||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
|
||||
class MarkdownTableView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
...themedHeader(screenProps.theme),
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Table')
|
||||
});
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation, theme } = this.props;
|
||||
const renderRows = navigation.getParam('renderRows');
|
||||
const tableWidth = navigation.getParam('tableWidth');
|
||||
const { route, theme } = this.props;
|
||||
const renderRows = route.params?.renderRows;
|
||||
const tableWidth = route.params?.tableWidth;
|
||||
|
||||
if (isIOS) {
|
||||
return (
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import ActionSheet from 'react-native-action-sheet';
|
||||
|
||||
|
@ -15,26 +14,24 @@ import StatusBar from '../../containers/StatusBar';
|
|||
import getFileUrlFromMessage from '../../lib/methods/helpers/getFileUrlFromMessage';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { withSplit } from '../../split';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
const ACTION_INDEX = 0;
|
||||
const CANCEL_INDEX = 1;
|
||||
|
||||
class MessagesView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||
title: I18n.t(navigation.state.params.name),
|
||||
...themedHeader(screenProps.theme)
|
||||
static navigationOptions = ({ route }) => ({
|
||||
title: I18n.t(route.params?.name)
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
customEmojis: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
split: PropTypes.bool
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -44,9 +41,9 @@ class MessagesView extends React.Component {
|
|||
messages: [],
|
||||
fileLoading: true
|
||||
};
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.t = props.navigation.getParam('t');
|
||||
this.content = this.defineMessagesViewContent(props.navigation.getParam('name'));
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.content = this.defineMessagesViewContent(props.route.params?.name);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -223,12 +220,8 @@ class MessagesView extends React.Component {
|
|||
}
|
||||
|
||||
showAttachment = (attachment) => {
|
||||
const { navigation, split } = this.props;
|
||||
let params = { attachment };
|
||||
if (split) {
|
||||
params = { ...params, from: 'MessagesView' };
|
||||
}
|
||||
navigation.navigate('AttachmentView', params);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('AttachmentView', { attachment });
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
|
@ -295,12 +288,9 @@ class MessagesView extends React.Component {
|
|||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[
|
||||
styles.list,
|
||||
{ backgroundColor: themes[theme].backgroundColor }
|
||||
]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
testID={this.content.testID}
|
||||
theme={theme}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
|
@ -322,4 +312,4 @@ const mapStateToProps = state => ({
|
|||
customEmojis: state.customEmojis
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withSplit(withTheme(MessagesView)));
|
||||
export default connect(mapStateToProps)(withTheme(MessagesView));
|
||||
|
|
|
@ -6,7 +6,6 @@ import { connect } from 'react-redux';
|
|||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import EventEmitter from '../utils/events';
|
||||
import { themes } from '../constants/colors';
|
||||
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
|
||||
|
@ -57,41 +56,18 @@ const mapElementToState = ({ element, blockId, elements = [] }) => {
|
|||
const reduceState = (obj, el) => (Array.isArray(el[0]) ? { ...obj, ...Object.fromEntries(el) } : { ...obj, [el[0]]: el[1] });
|
||||
|
||||
class ModalBlockView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const { theme, closeModal } = screenProps;
|
||||
const data = navigation.getParam('data');
|
||||
const cancel = navigation.getParam('cancel', () => {});
|
||||
const submitting = navigation.getParam('submitting', false);
|
||||
static navigationOptions = ({ route }) => {
|
||||
const data = route.params?.data;
|
||||
const { view } = data;
|
||||
const { title, submit, close } = view;
|
||||
const { title } = view;
|
||||
return {
|
||||
title: textParser([title]),
|
||||
...themedHeader(theme),
|
||||
headerLeft: close ? (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
title={textParser([close.text])}
|
||||
style={styles.submit}
|
||||
onPress={!submitting && (() => cancel({ closeModal }))}
|
||||
testID='close-modal-uikit'
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
) : null,
|
||||
headerRight: submit ? (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
title={textParser([submit.text])}
|
||||
style={styles.submit}
|
||||
onPress={!submitting && (navigation.getParam('submit', () => {}))}
|
||||
testID='submit-modal-uikit'
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
) : null
|
||||
title: textParser([title])
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
language: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
|
@ -103,21 +79,18 @@ class ModalBlockView extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.submitting = false;
|
||||
const { navigation } = props;
|
||||
const data = navigation.getParam('data');
|
||||
const data = props.route.params?.data;
|
||||
this.values = data.view.blocks.filter(filterInputFields).map(mapElementToState).reduce(reduceState, {});
|
||||
this.state = {
|
||||
data,
|
||||
loading: false
|
||||
};
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { data } = this.state;
|
||||
const { navigation } = this.props;
|
||||
const { viewId } = data;
|
||||
navigation.setParams({ submit: this.submit, cancel: this.cancel });
|
||||
|
||||
EventEmitter.addEventListener(viewId, this.handleUpdate);
|
||||
}
|
||||
|
||||
|
@ -133,9 +106,9 @@ class ModalBlockView extends React.Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { navigation } = this.props;
|
||||
const oldData = prevProps.navigation.getParam('data', {});
|
||||
const newData = navigation.getParam('data', {});
|
||||
const { navigation, route } = this.props;
|
||||
const oldData = prevProps.route.params?.data ?? {};
|
||||
const newData = route.params?.data ?? {};
|
||||
if (oldData.viewId !== newData.viewId) {
|
||||
navigation.push('ModalBlockView', { data: newData });
|
||||
}
|
||||
|
@ -147,14 +120,43 @@ class ModalBlockView extends React.Component {
|
|||
EventEmitter.removeListener(viewId, this.handleUpdate);
|
||||
}
|
||||
|
||||
handleUpdate = ({ type, ...data }) => {
|
||||
setHeader = () => {
|
||||
const { data } = this.state;
|
||||
const { navigation } = this.props;
|
||||
const { view } = data;
|
||||
const { title, close, submit } = view;
|
||||
navigation.setOptions({
|
||||
title: textParser([title]),
|
||||
headerLeft: close ? () => (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
title={textParser([close.text])}
|
||||
style={styles.submit}
|
||||
onPress={this.cancel}
|
||||
testID='close-modal-uikit'
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
) : null,
|
||||
headerRight: submit ? () => (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
title={textParser([submit.text])}
|
||||
style={styles.submit}
|
||||
onPress={this.submit}
|
||||
testID='submit-modal-uikit'
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
) : null
|
||||
});
|
||||
}
|
||||
|
||||
handleUpdate = ({ type, ...data }) => {
|
||||
if ([MODAL_ACTIONS.ERRORS].includes(type)) {
|
||||
const { errors } = data;
|
||||
this.setState({ errors });
|
||||
} else {
|
||||
this.setState({ data });
|
||||
navigation.setParams({ data });
|
||||
this.setHeader();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -187,8 +189,11 @@ class ModalBlockView extends React.Component {
|
|||
|
||||
submit = async() => {
|
||||
const { data } = this.state;
|
||||
const { navigation } = this.props;
|
||||
navigation.setParams({ submitting: true });
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
const { appId, viewId } = data;
|
||||
this.setState({ loading: true });
|
||||
|
@ -207,7 +212,7 @@ class ModalBlockView extends React.Component {
|
|||
// do nothing
|
||||
}
|
||||
|
||||
navigation.setParams({ submitting: false });
|
||||
this.submitting = false;
|
||||
this.setState({ loading: false });
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
View, StyleSheet, FlatList, Text
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import { orderBy } from 'lodash';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -22,16 +21,13 @@ import { CloseModalButton } from '../containers/HeaderButton';
|
|||
import StatusBar from '../containers/StatusBar';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { getUserSelector } from '../selectors/login';
|
||||
import Navigation from '../lib/Navigation';
|
||||
import { createChannelRequest } from '../actions/createChannel';
|
||||
import { goRoom } from '../utils/goRoom';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeAreaView: {
|
||||
flex: 1
|
||||
},
|
||||
separator: {
|
||||
marginLeft: 60
|
||||
},
|
||||
|
@ -54,9 +50,8 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class NewMessageView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||
...themedHeader(screenProps.theme),
|
||||
headerLeft: <CloseModalButton navigation={navigation} testID='new-message-view-close' />,
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
headerLeft: () => <CloseModalButton navigation={navigation} testID='new-message-view-close' />,
|
||||
title: I18n.t('New_Message')
|
||||
})
|
||||
|
||||
|
@ -69,7 +64,8 @@ class NewMessageView extends React.Component {
|
|||
}),
|
||||
createChannel: PropTypes.func,
|
||||
maxUsers: PropTypes.number,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -150,6 +146,14 @@ class NewMessageView extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
goRoom = (item) => {
|
||||
const { isMasterDetail, navigation } = this.props;
|
||||
if (isMasterDetail) {
|
||||
navigation.pop();
|
||||
}
|
||||
goRoom({ item, isMasterDetail });
|
||||
}
|
||||
|
||||
renderButton = ({
|
||||
onPress, testID, title, icon, first
|
||||
}) => {
|
||||
|
@ -226,7 +230,7 @@ class NewMessageView extends React.Component {
|
|||
<UserItem
|
||||
name={item.search ? item.name : item.fname}
|
||||
username={item.search ? item.username : item.name}
|
||||
onPress={() => goRoom(item)}
|
||||
onPress={() => this.goRoom(item)}
|
||||
baseUrl={baseUrl}
|
||||
testID={`new-message-view-item-${ item.name }`}
|
||||
style={style}
|
||||
|
@ -256,11 +260,7 @@ class NewMessageView extends React.Component {
|
|||
render = () => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[styles.safeAreaView, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
testID='new-message-view'
|
||||
>
|
||||
<SafeAreaView testID='new-message-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
{this.renderList()}
|
||||
</SafeAreaView>
|
||||
|
@ -269,6 +269,7 @@ class NewMessageView extends React.Component {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
baseUrl: state.server.server,
|
||||
maxUsers: state.settings.DirectMesssage_maxUsers || 1,
|
||||
user: getUserSelector(state)
|
||||
|
|
|
@ -12,10 +12,7 @@ import { encode } from 'base-64';
|
|||
import parse from 'url-parse';
|
||||
|
||||
import EventEmitter from '../utils/events';
|
||||
import {
|
||||
selectServerRequest, serverRequest, serverInitAdd, serverFinishAdd
|
||||
} from '../actions/server';
|
||||
import { appStart as appStartAction } from '../actions';
|
||||
import { selectServerRequest, serverRequest } from '../actions/server';
|
||||
import sharedStyles from './Styles';
|
||||
import Button from '../containers/Button';
|
||||
import TextInput from '../containers/TextInput';
|
||||
|
@ -28,7 +25,6 @@ import log from '../utils/log';
|
|||
import { animateNextTransition } from '../utils/layoutAnimation';
|
||||
import { withTheme } from '../theme';
|
||||
import { setBasicAuth, BASIC_AUTH_KEY } from '../utils/fetch';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { CloseModalButton } from '../containers/HeaderButton';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -65,14 +61,8 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class NewServerView extends React.Component {
|
||||
static navigationOptions = ({ screenProps, navigation }) => {
|
||||
const previousServer = navigation.getParam('previousServer', null);
|
||||
const close = navigation.getParam('close', () => {});
|
||||
return {
|
||||
headerLeft: previousServer ? <CloseModalButton navigation={navigation} onPress={close} testID='new-server-view-close' /> : undefined,
|
||||
title: I18n.t('Workspaces'),
|
||||
...themedHeader(screenProps.theme)
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Workspaces')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
|
@ -81,15 +71,17 @@ class NewServerView extends React.Component {
|
|||
connecting: PropTypes.bool.isRequired,
|
||||
connectServer: PropTypes.func.isRequired,
|
||||
selectServer: PropTypes.func.isRequired,
|
||||
currentServer: PropTypes.string,
|
||||
initAdd: PropTypes.func,
|
||||
finishAdd: PropTypes.func
|
||||
adding: PropTypes.bool,
|
||||
previousServer: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.previousServer = props.navigation.getParam('previousServer');
|
||||
props.navigation.setParams({ close: this.close, previousServer: this.previousServer });
|
||||
if (props.adding) {
|
||||
props.navigation.setOptions({
|
||||
headerLeft: () => <CloseModalButton navigation={props.navigation} onPress={this.close} testID='new-server-view-close' />
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel
|
||||
this.options = [I18n.t('Cancel')];
|
||||
|
@ -108,21 +100,14 @@ class NewServerView extends React.Component {
|
|||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { initAdd } = this.props;
|
||||
if (this.previousServer) {
|
||||
initAdd();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
handleBackPress = () => {
|
||||
const { navigation } = this.props;
|
||||
if (navigation.isFocused() && this.previousServer) {
|
||||
const { navigation, previousServer } = this.props;
|
||||
if (navigation.isFocused() && previousServer) {
|
||||
this.close();
|
||||
return true;
|
||||
}
|
||||
|
@ -134,11 +119,8 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
|
||||
close = () => {
|
||||
const { selectServer, currentServer, finishAdd } = this.props;
|
||||
if (this.previousServer !== currentServer) {
|
||||
selectServer(this.previousServer);
|
||||
}
|
||||
finishAdd();
|
||||
const { selectServer, previousServer } = this.props;
|
||||
selectServer(previousServer);
|
||||
}
|
||||
|
||||
handleNewServerEvent = (event) => {
|
||||
|
@ -344,15 +326,14 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
connecting: state.server.connecting
|
||||
connecting: state.server.connecting,
|
||||
adding: state.server.adding,
|
||||
previousServer: state.server.previousServer
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
connectServer: (server, certificate) => dispatch(serverRequest(server, certificate)),
|
||||
initAdd: () => dispatch(serverInitAdd()),
|
||||
finishAdd: () => dispatch(serverFinishAdd()),
|
||||
selectServer: server => dispatch(selectServerRequest(server)),
|
||||
appStart: root => dispatch(appStartAction(root))
|
||||
selectServer: server => dispatch(selectServerRequest(server))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewServerView));
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
View, ScrollView, Switch, Text
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||
|
@ -13,11 +12,10 @@ import Separator from '../../containers/Separator';
|
|||
import I18n from '../../i18n';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import styles from './styles';
|
||||
import sharedStyles from '../Styles';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
const SectionTitle = React.memo(({ title, theme }) => (
|
||||
<Text
|
||||
|
@ -140,21 +138,21 @@ const OPTIONS = {
|
|||
};
|
||||
|
||||
class NotificationPreferencesView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Notification_Preferences'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Notification_Preferences')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.mounted = false;
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
const room = props.navigation.getParam('room');
|
||||
this.rid = props.route.params?.rid;
|
||||
const room = props.route.params?.room;
|
||||
this.state = {
|
||||
room: room || {}
|
||||
};
|
||||
|
@ -245,7 +243,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
const { room } = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={sharedStyles.container} testID='notification-preference-view' forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView testID='notification-preference-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<ScrollView
|
||||
{...scrollPersistTaps}
|
||||
|
|
|
@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import { connect } from 'react-redux';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
|
||||
import { appStart as appStartAction } from '../../actions';
|
||||
import { appStart as appStartAction, ROOT_BACKGROUND } from '../../actions/app';
|
||||
import I18n from '../../i18n';
|
||||
import Button from '../../containers/Button';
|
||||
import styles from './styles';
|
||||
|
@ -16,9 +16,9 @@ import { withTheme } from '../../theme';
|
|||
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
||||
|
||||
class OnboardingView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
header: null
|
||||
})
|
||||
static navigationOptions = {
|
||||
headerShown: false
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
|
@ -28,12 +28,23 @@ class OnboardingView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
if (!isTablet) {
|
||||
Orientation.lockToPortrait();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
this.unsubscribeFocus = navigation.addListener('focus', () => {
|
||||
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
});
|
||||
this.unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
if (this.backHandler && this.backHandler.remove) {
|
||||
this.backHandler.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { theme } = this.props;
|
||||
if (theme !== nextProps.theme) {
|
||||
|
@ -43,12 +54,17 @@ class OnboardingView extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
if (this.unsubscribeBlur) {
|
||||
this.unsubscribeBlur();
|
||||
}
|
||||
}
|
||||
|
||||
handleBackPress = () => {
|
||||
const { appStart } = this.props;
|
||||
appStart('background');
|
||||
appStart({ root: ROOT_BACKGROUND });
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -98,7 +114,7 @@ class OnboardingView extends React.Component {
|
|||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
appStart: root => dispatch(appStartAction(root))
|
||||
appStart: params => dispatch(appStartAction(params))
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTheme(OnboardingView));
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
} from 'react-native';
|
||||
|
||||
import I18n from '../i18n';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import { withTheme } from '../theme';
|
||||
import { themes } from '../constants/colors';
|
||||
import debounce from '../utils/debounce';
|
||||
|
@ -57,29 +56,29 @@ Item.propTypes = {
|
|||
};
|
||||
|
||||
class PickerView extends React.PureComponent {
|
||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||
title: navigation.getParam('title', I18n.t('Select_an_option')),
|
||||
...themedHeader(screenProps.theme)
|
||||
static navigationOptions = ({ route }) => ({
|
||||
title: route.params?.title ?? I18n.t('Select_an_option')
|
||||
})
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const data = props.navigation.getParam('data', []);
|
||||
const value = props.navigation.getParam('value');
|
||||
const data = props.route.params?.data ?? [];
|
||||
const value = props.route.params?.value;
|
||||
this.state = { data, value };
|
||||
|
||||
this.onSearch = props.navigation.getParam('onChangeText');
|
||||
this.onSearch = props.route.params?.onChangeText;
|
||||
}
|
||||
|
||||
onChangeValue = (value) => {
|
||||
const { navigation } = this.props;
|
||||
const goBack = navigation.getParam('goBack', true);
|
||||
const onChange = navigation.getParam('onChangeValue', () => {});
|
||||
const { navigation, route } = this.props;
|
||||
const goBack = route.params?.goBack ?? true;
|
||||
const onChange = route.params?.onChangeValue ?? (() => {});
|
||||
onChange(value);
|
||||
if (goBack) {
|
||||
navigation.goBack();
|
||||
|
|
|
@ -6,8 +6,6 @@ import prompt from 'react-native-prompt-android';
|
|||
import SHA256 from 'js-sha256';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import { HeaderBackButton } from 'react-navigation-stack';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
|
@ -30,22 +28,19 @@ import { DrawerButton } from '../../containers/HeaderButton';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class ProfileView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => ({
|
||||
...themedHeader(screenProps.theme),
|
||||
headerLeft: screenProps.split ? (
|
||||
<HeaderBackButton
|
||||
onPress={() => navigation.navigate('SettingsView')}
|
||||
tintColor={themes[screenProps.theme].headerTintColor}
|
||||
/>
|
||||
) : (
|
||||
<DrawerButton navigation={navigation} />
|
||||
),
|
||||
title: I18n.t('Profile')
|
||||
})
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
||||
const options = {
|
||||
title: I18n.t('Profile')
|
||||
};
|
||||
if (!isMasterDetail) {
|
||||
options.headerLeft = () => <DrawerButton navigation={navigation} />;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
|
@ -440,7 +435,7 @@ class ProfileView extends React.Component {
|
|||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<SafeAreaView style={sharedStyles.container} testID='profile-view' forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView testID='profile-view' theme={theme}>
|
||||
<ScrollView
|
||||
contentContainerStyle={sharedStyles.containerScrollView}
|
||||
testID='profile-view-list'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text } from 'react-native';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -13,18 +12,17 @@ import I18n from '../../i18n';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class ReadReceiptView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Read_Receipt'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Read_Receipt')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
Message_TimeFormat: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
|
@ -33,7 +31,7 @@ class ReadReceiptView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.messageId = props.navigation.getParam('messageId');
|
||||
this.messageId = props.route.params?.messageId;
|
||||
this.state = {
|
||||
loading: false,
|
||||
receipts: []
|
||||
|
@ -135,11 +133,7 @@ class ReadReceiptView extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: themes[theme].chatComponentBackground }]}
|
||||
forceInset={{ bottom: 'always' }}
|
||||
testID='read-receipt-view'
|
||||
>
|
||||
<SafeAreaView testID='read-receipt-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<View>
|
||||
{loading
|
||||
|
|
|
@ -28,9 +28,6 @@ export default StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
padding: 10
|
||||
},
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
list: {
|
||||
...sharedStyles.separatorVertical,
|
||||
marginVertical: 10
|
||||
|
|
|
@ -13,7 +13,6 @@ import I18n from '../i18n';
|
|||
import { LegalButton } from '../containers/HeaderButton';
|
||||
import { themes } from '../constants/colors';
|
||||
import { withTheme } from '../theme';
|
||||
import { themedHeader } from '../utils/navigation';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import isValidEmail from '../utils/isValidEmail';
|
||||
|
@ -53,14 +52,10 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class RegisterView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const title = navigation.getParam('title', 'Rocket.Chat');
|
||||
return {
|
||||
...themedHeader(screenProps.theme),
|
||||
title,
|
||||
headerRight: <LegalButton navigation={navigation} testID='register-view-more' />
|
||||
};
|
||||
}
|
||||
static navigationOptions = ({ route, navigation }) => ({
|
||||
title: route.params?.title ?? 'Rocket.Chat',
|
||||
headerRight: () => <LegalButton testID='register-view-more' navigation={navigation} />
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
|
@ -228,11 +223,11 @@ class RegisterView extends React.Component {
|
|||
|
||||
render() {
|
||||
const { saving } = this.state;
|
||||
const { theme, showLoginButton } = this.props;
|
||||
const { theme, showLoginButton, navigation } = this.props;
|
||||
return (
|
||||
<FormContainer theme={theme} testID='register-view'>
|
||||
<FormContainerInner>
|
||||
<LoginServices />
|
||||
<LoginServices navigation={navigation} />
|
||||
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>
|
||||
<TextInput
|
||||
label='Name'
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
View, SectionList, Text, Alert, Share
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
|
@ -24,20 +23,19 @@ import DisclosureIndicator from '../../containers/DisclosureIndicator';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { CloseModalButton } from '../../containers/HeaderButton';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import Markdown from '../../containers/markdown';
|
||||
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class RoomActionsView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
static navigationOptions = ({ navigation, isMasterDetail }) => {
|
||||
const options = {
|
||||
...themedHeader(screenProps.theme),
|
||||
title: I18n.t('Actions')
|
||||
};
|
||||
if (screenProps.split) {
|
||||
options.headerLeft = <CloseModalButton navigation={navigation} testID='room-actions-view-close' />;
|
||||
if (isMasterDetail) {
|
||||
options.headerLeft = () => <CloseModalButton navigation={navigation} testID='room-actions-view-close' />;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
@ -45,6 +43,7 @@ class RoomActionsView extends React.Component {
|
|||
static propTypes = {
|
||||
baseUrl: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
|
@ -59,10 +58,10 @@ class RoomActionsView extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.mounted = false;
|
||||
const room = props.navigation.getParam('room');
|
||||
const member = props.navigation.getParam('member');
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.t = props.navigation.getParam('t');
|
||||
const room = props.route.params?.room;
|
||||
const member = props.route.params?.member;
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.state = {
|
||||
room: room || { rid: this.rid, t: this.t },
|
||||
membersCount: 0,
|
||||
|
@ -647,7 +646,7 @@ class RoomActionsView extends React.Component {
|
|||
render() {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={styles.container} testID='room-actions-view' forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView testID='room-actions-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<SectionList
|
||||
contentContainerStyle={[styles.contentContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
Text, View, ScrollView, TouchableOpacity, Keyboard, Alert
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import equal from 'deep-equal';
|
||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
@ -27,11 +26,11 @@ import random from '../../utils/random';
|
|||
import log from '../../utils/log';
|
||||
import I18n from '../../i18n';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
|
||||
import { MessageTypeValues } from '../../utils/messageTypes';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
const PERMISSION_SET_READONLY = 'set-readonly';
|
||||
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
|
||||
|
@ -49,13 +48,12 @@ const PERMISSIONS_ARRAY = [
|
|||
];
|
||||
|
||||
class RoomInfoEditView extends React.Component {
|
||||
static navigationOptions = ({ screenProps }) => ({
|
||||
title: I18n.t('Room_Info_Edit'),
|
||||
...themedHeader(screenProps.theme)
|
||||
})
|
||||
static navigationOptions = {
|
||||
title: I18n.t('Room_Info_Edit')
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
deleteRoom: PropTypes.func,
|
||||
serverVersion: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
|
@ -101,8 +99,8 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
loadRoom = async() => {
|
||||
const { navigation } = this.props;
|
||||
const rid = navigation.getParam('rid', null);
|
||||
const { route } = this.props;
|
||||
const rid = route.params?.rid;
|
||||
if (!rid) {
|
||||
return;
|
||||
}
|
||||
|
@ -349,12 +347,16 @@ class RoomInfoEditView extends React.Component {
|
|||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<StatusBar theme={theme} />
|
||||
<ScrollView
|
||||
contentContainerStyle={sharedStyles.containerScrollView}
|
||||
testID='room-info-edit-view-list'
|
||||
{...scrollPersistTaps}
|
||||
<SafeAreaView
|
||||
testID='room-info-edit-view'
|
||||
theme={theme}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
>
|
||||
<SafeAreaView style={sharedStyles.container} testID='room-info-edit-view' forceInset={{ vertical: 'never' }}>
|
||||
<ScrollView
|
||||
contentContainerStyle={sharedStyles.containerScrollView}
|
||||
testID='room-info-edit-view-list'
|
||||
{...scrollPersistTaps}
|
||||
>
|
||||
<RCTextInput
|
||||
inputRef={(e) => { this.name = e; }}
|
||||
label={I18n.t('Name')}
|
||||
|
@ -542,8 +544,8 @@ class RoomInfoEditView extends React.Component {
|
|||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Loading visible={saving} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import { View, Text, ScrollView } from 'react-native';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import UAParser from 'ua-parser-js';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
@ -16,21 +15,21 @@ import sharedStyles from '../Styles';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomHeaderButtons } from '../../containers/HeaderButton';
|
||||
import { CustomHeaderButtons, CloseModalButton } from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import log from '../../utils/log';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { withSplit } from '../../split';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import Markdown from '../../containers/markdown';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
|
||||
import Livechat from './Livechat';
|
||||
import Channel from './Channel';
|
||||
import Item from './Item';
|
||||
import Direct from './Direct';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
|
||||
const PERMISSION_EDIT_ROOM = 'edit-room';
|
||||
const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd'
|
||||
|
@ -50,50 +49,29 @@ const getRoomTitle = (room, type, name, username, statusText, theme) => (type ==
|
|||
);
|
||||
|
||||
class RoomInfoView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const t = navigation.getParam('t');
|
||||
const rid = navigation.getParam('rid');
|
||||
const room = navigation.getParam('room');
|
||||
const roomUser = navigation.getParam('roomUser');
|
||||
const showEdit = navigation.getParam('showEdit', t === 'l');
|
||||
return {
|
||||
title: t === 'd' ? I18n.t('User_Info') : I18n.t('Room_Info'),
|
||||
...themedHeader(screenProps.theme),
|
||||
headerRight: showEdit
|
||||
? (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
iconName='edit'
|
||||
onPress={() => navigation.navigate(t === 'l' ? 'LivechatEditView' : 'RoomInfoEditView', { rid, room, roomUser })}
|
||||
testID='room-info-view-edit-button'
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
}),
|
||||
baseUrl: PropTypes.string,
|
||||
rooms: PropTypes.array,
|
||||
split: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const room = props.navigation.getParam('room');
|
||||
const roomUser = props.navigation.getParam('member');
|
||||
this.rid = props.navigation.getParam('rid');
|
||||
this.t = props.navigation.getParam('t');
|
||||
const room = props.route.params?.room;
|
||||
const roomUser = props.route.params?.member;
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.state = {
|
||||
room: room || { rid: this.rid, t: this.t },
|
||||
roomUser: roomUser || {}
|
||||
roomUser: roomUser || {},
|
||||
showEdit: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -103,9 +81,10 @@ class RoomInfoView extends React.Component {
|
|||
} else {
|
||||
this.loadRoom();
|
||||
}
|
||||
this.setHeader();
|
||||
|
||||
const { navigation } = this.props;
|
||||
this.willFocusListener = navigation.addListener('willFocus', () => {
|
||||
this.unsubscribeFocus = navigation.addListener('focus', () => {
|
||||
if (this.isLivechat) {
|
||||
this.loadVisitor();
|
||||
}
|
||||
|
@ -116,11 +95,34 @@ class RoomInfoView extends React.Component {
|
|||
if (this.subscription && this.subscription.unsubscribe) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
if (this.willFocusListener && this.willFocusListener.remove) {
|
||||
this.willFocusListener.remove();
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { roomUser, room, showEdit } = this.state;
|
||||
const { navigation, route } = this.props;
|
||||
const t = route.params?.t;
|
||||
const rid = route.params?.rid;
|
||||
const showCloseModal = route.params?.showCloseModal;
|
||||
navigation.setOptions({
|
||||
headerLeft: showCloseModal ? () => <CloseModalButton navigation={navigation} /> : undefined,
|
||||
title: t === 'd' ? I18n.t('User_Info') : I18n.t('Room_Info'),
|
||||
headerRight: showEdit
|
||||
? () => (
|
||||
<CustomHeaderButtons>
|
||||
<Item
|
||||
iconName='edit'
|
||||
onPress={() => navigation.navigate(t === 'l' ? 'LivechatEditView' : 'RoomInfoEditView', { rid, room, roomUser })}
|
||||
testID='room-info-view-edit-button'
|
||||
/>
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
: null
|
||||
});
|
||||
}
|
||||
|
||||
get isDirect() {
|
||||
const { room } = this.state;
|
||||
return room.t === 'd';
|
||||
|
@ -147,8 +149,6 @@ class RoomInfoView extends React.Component {
|
|||
|
||||
loadVisitor = async() => {
|
||||
const { room } = this.state;
|
||||
const { navigation } = this.props;
|
||||
|
||||
try {
|
||||
const result = await RocketChat.getVisitorInfo(room?.visitor?._id);
|
||||
if (result.success) {
|
||||
|
@ -159,8 +159,7 @@ class RoomInfoView extends React.Component {
|
|||
visitor.os = `${ ua.getOS().name } ${ ua.getOS().version }`;
|
||||
visitor.browser = `${ ua.getBrowser().name } ${ ua.getBrowser().version }`;
|
||||
}
|
||||
this.setState({ roomUser: visitor });
|
||||
navigation.setParams({ roomUser: visitor });
|
||||
this.setState({ roomUser: visitor }, () => this.setHeader());
|
||||
}
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
|
@ -195,14 +194,13 @@ class RoomInfoView extends React.Component {
|
|||
}
|
||||
|
||||
loadRoom = async() => {
|
||||
const { navigation } = this.props;
|
||||
let room = navigation.getParam('room');
|
||||
const { route } = this.props;
|
||||
let room = route.params?.room;
|
||||
if (room && room.observe) {
|
||||
this.roomObservable = room.observe();
|
||||
this.subscription = this.roomObservable
|
||||
.subscribe((changes) => {
|
||||
this.setState({ room: changes });
|
||||
navigation.setParams({ room: changes });
|
||||
this.setState({ room: changes }, () => this.setHeader());
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
|
@ -218,7 +216,7 @@ class RoomInfoView extends React.Component {
|
|||
|
||||
const permissions = await RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
||||
navigation.setParams({ showEdit: true });
|
||||
this.setState({ showEdit: true }, () => this.setHeader());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,28 +234,31 @@ class RoomInfoView extends React.Component {
|
|||
goRoom = () => {
|
||||
const { roomUser, room } = this.state;
|
||||
const { name, username } = roomUser;
|
||||
const { rooms, navigation, split } = this.props;
|
||||
const { rooms, navigation, isMasterDetail } = this.props;
|
||||
const params = {
|
||||
rid: room.rid,
|
||||
name: RocketChat.getRoomTitle({
|
||||
t: room.t,
|
||||
fname: name,
|
||||
name: username
|
||||
}),
|
||||
t: room.t,
|
||||
roomUserId: RocketChat.getUidDirectMessage(room)
|
||||
};
|
||||
|
||||
if (room.rid) {
|
||||
let navigate = navigation.push;
|
||||
|
||||
// if this is a room focused
|
||||
if (rooms.includes(room.rid)) {
|
||||
({ navigate } = navigation);
|
||||
} else if (split) {
|
||||
({ navigate } = Navigation);
|
||||
// if it's on master detail layout, we close the modal and replace RoomView
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
goRoom({ item: params, isMasterDetail });
|
||||
} else {
|
||||
let navigate = navigation.push;
|
||||
// if this is a room focused
|
||||
if (rooms.includes(room.rid)) {
|
||||
({ navigate } = navigation);
|
||||
}
|
||||
navigate('RoomView', params);
|
||||
}
|
||||
|
||||
navigate('RoomView', {
|
||||
rid: room.rid,
|
||||
name: RocketChat.getRoomTitle({
|
||||
t: room.t,
|
||||
fname: name,
|
||||
name: username
|
||||
}),
|
||||
t: room.t,
|
||||
roomUserId: RocketChat.getUidDirectMessage(room)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,8 +328,8 @@ class RoomInfoView extends React.Component {
|
|||
<ScrollView style={[styles.scroll, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||
<StatusBar theme={theme} />
|
||||
<SafeAreaView
|
||||
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
forceInset={{ vertical: 'never' }}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
theme={theme}
|
||||
testID='room-info-view'
|
||||
>
|
||||
<View style={[styles.avatarContainer, this.isDirect && styles.avatarContainerDirectRoom, { backgroundColor: themes[theme].auxiliaryBackground }]}>
|
||||
|
@ -346,7 +347,8 @@ class RoomInfoView extends React.Component {
|
|||
const mapStateToProps = state => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state),
|
||||
rooms: state.room.rooms
|
||||
rooms: state.room.rooms,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withSplit(withTheme(RoomInfoView)));
|
||||
export default connect(mapStateToProps)(withTheme(RoomInfoView));
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import { FlatList, View } from 'react-native';
|
||||
import ActionSheet from 'react-native-action-sheet';
|
||||
import { connect } from 'react-redux';
|
||||
import { SafeAreaView } from 'react-navigation';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
|
@ -22,30 +21,17 @@ import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
|||
import StatusBar from '../../containers/StatusBar';
|
||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import { withTheme } from '../../theme';
|
||||
import { themedHeader } from '../../utils/navigation';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
class RoomMembersView extends React.Component {
|
||||
static navigationOptions = ({ navigation, screenProps }) => {
|
||||
const toggleStatus = navigation.getParam('toggleStatus', () => {});
|
||||
const allUsers = navigation.getParam('allUsers');
|
||||
const toggleText = allUsers ? I18n.t('Online') : I18n.t('All');
|
||||
return {
|
||||
title: I18n.t('Members'),
|
||||
...themedHeader(screenProps.theme),
|
||||
headerRight: (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={toggleText} onPress={toggleStatus} testID='room-members-view-toggle-status' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
route: PropTypes.object,
|
||||
rid: PropTypes.string,
|
||||
members: PropTypes.array,
|
||||
baseUrl: PropTypes.string,
|
||||
|
@ -54,7 +40,8 @@ class RoomMembersView extends React.Component {
|
|||
id: PropTypes.string,
|
||||
token: PropTypes.string
|
||||
}),
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -63,8 +50,8 @@ class RoomMembersView extends React.Component {
|
|||
this.CANCEL_INDEX = 0;
|
||||
this.MUTE_INDEX = 1;
|
||||
this.actionSheetOptions = [''];
|
||||
const { rid } = props.navigation.state.params;
|
||||
const room = props.navigation.getParam('room');
|
||||
const rid = props.route.params?.rid;
|
||||
const room = props.route.params?.room;
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
allUsers: false,
|
||||
|
@ -87,15 +74,15 @@ class RoomMembersView extends React.Component {
|
|||
}
|
||||
});
|
||||
}
|
||||
this.setHeader();
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.fetchMembers();
|
||||
|
||||
const { navigation } = this.props;
|
||||
const { rid } = navigation.state.params;
|
||||
navigation.setParams({ toggleStatus: this.toggleStatus });
|
||||
const { route } = this.props;
|
||||
const rid = route.params?.rid;
|
||||
this.permissions = await RocketChat.hasPermission(['mute-user'], rid);
|
||||
}
|
||||
|
||||
|
@ -105,6 +92,20 @@ class RoomMembersView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { allUsers } = this.state;
|
||||
const { navigation } = this.props;
|
||||
const toggleText = allUsers ? I18n.t('Online') : I18n.t('All');
|
||||
navigation.setOptions({
|
||||
title: I18n.t('Members'),
|
||||
headerRight: () => (
|
||||
<CustomHeaderButtons>
|
||||
<Item title={toggleText} onPress={this.toggleStatus} testID='room-members-view-toggle-status' />
|
||||
</CustomHeaderButtons>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
onSearchChangeText = protectedFunction((text) => {
|
||||
const { members } = this.state;
|
||||
let membersFiltered = [];
|
||||
|
@ -122,11 +123,11 @@ class RoomMembersView extends React.Component {
|
|||
const query = await subsCollection.query(Q.where('name', item.username)).fetch();
|
||||
if (query.length) {
|
||||
const [room] = query;
|
||||
this.goRoom({ rid: room.rid, name: item.username, room });
|
||||
this.goRoom(room);
|
||||
} else {
|
||||
const result = await RocketChat.createDirectMessage(item.username);
|
||||
if (result.success) {
|
||||
this.goRoom({ rid: result.room._id, name: item.username });
|
||||
this.goRoom({ rid: result.room?._id, name: item.username, t: 'd' });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -180,7 +181,6 @@ class RoomMembersView extends React.Component {
|
|||
const {
|
||||
rid, members, isLoading, allUsers, end
|
||||
} = this.state;
|
||||
const { navigation } = this.props;
|
||||
if (isLoading || end) {
|
||||
return;
|
||||
}
|
||||
|
@ -194,19 +194,21 @@ class RoomMembersView extends React.Component {
|
|||
isLoading: false,
|
||||
end: newMembers.length < PAGE_SIZE
|
||||
});
|
||||
navigation.setParams({ allUsers });
|
||||
this.setHeader();
|
||||
} catch (e) {
|
||||
log(e);
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
goRoom = async({ rid, name, room }) => {
|
||||
const { navigation } = this.props;
|
||||
await navigation.popToTop();
|
||||
navigation.navigate('RoomView', {
|
||||
rid, name, t: 'd', room
|
||||
});
|
||||
goRoom = (item) => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
if (isMasterDetail) {
|
||||
navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
navigation.popToTop();
|
||||
}
|
||||
goRoom({ item, isMasterDetail });
|
||||
}
|
||||
|
||||
handleMute = async() => {
|
||||
|
@ -261,7 +263,7 @@ class RoomMembersView extends React.Component {
|
|||
} = this.state;
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={styles.list} testID='room-members-view' forceInset={{ vertical: 'never' }}>
|
||||
<SafeAreaView testID='room-members-view' theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
data={filtering ? membersFiltered : members}
|
||||
|
@ -289,7 +291,8 @@ class RoomMembersView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
baseUrl: state.server.server,
|
||||
user: getUserSelector(state)
|
||||
user: getUserSelector(state),
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(RoomMembersView));
|
||||
|
|
|
@ -18,7 +18,8 @@ const styles = StyleSheet.create({
|
|||
container: {
|
||||
flex: 1,
|
||||
marginRight: isAndroid ? 15 : 5,
|
||||
marginLeft: isAndroid ? androidMarginLeft : -10
|
||||
marginLeft: isAndroid ? androidMarginLeft : -10,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
titleContainer: {
|
||||
alignItems: 'center',
|
||||
|
@ -126,7 +127,7 @@ HeaderTitle.propTypes = {
|
|||
};
|
||||
|
||||
const Header = React.memo(({
|
||||
title, subtitle, type, status, usersTyping, width, height, prid, tmid, widthOffset, connecting, goRoomActionsView, roomUserId, theme
|
||||
title, subtitle, type, status, usersTyping, width, height, prid, tmid, connecting, goRoomActionsView, roomUserId, theme
|
||||
}) => {
|
||||
const portrait = height > width;
|
||||
let scale = 1;
|
||||
|
@ -143,7 +144,7 @@ const Header = React.memo(({
|
|||
<TouchableOpacity
|
||||
testID='room-view-header-actions'
|
||||
onPress={onPress}
|
||||
style={[styles.container, { width: width - widthOffset }]}
|
||||
style={styles.container}
|
||||
disabled={tmid}
|
||||
>
|
||||
<View style={[styles.titleContainer, tmid && styles.threadContainer]}>
|
||||
|
@ -173,7 +174,6 @@ Header.propTypes = {
|
|||
status: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
usersTyping: PropTypes.array,
|
||||
widthOffset: PropTypes.number,
|
||||
connecting: PropTypes.bool,
|
||||
roomUserId: PropTypes.string,
|
||||
goRoomActionsView: PropTypes.func
|
||||
|
|
|
@ -43,7 +43,7 @@ const Icon = React.memo(({
|
|||
} else if (type === 'c') {
|
||||
icon = 'hash';
|
||||
} else if (type === 'l') {
|
||||
icon = 'livechat';
|
||||
icon = 'omnichannel';
|
||||
} else if (type === 'd') {
|
||||
icon = 'team';
|
||||
} else {
|
||||
|
|
|
@ -14,6 +14,7 @@ class RightButtonsContainer extends React.PureComponent {
|
|||
t: PropTypes.string,
|
||||
tmid: PropTypes.string,
|
||||
navigation: PropTypes.object,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
toggleFollowThread: PropTypes.func
|
||||
};
|
||||
|
||||
|
@ -57,8 +58,14 @@ class RightButtonsContainer extends React.PureComponent {
|
|||
}
|
||||
|
||||
goThreadsView = () => {
|
||||
const { rid, t, navigation } = this.props;
|
||||
navigation.navigate('ThreadMessagesView', { rid, t });
|
||||
const {
|
||||
rid, t, navigation, isMasterDetail
|
||||
} = this.props;
|
||||
if (isMasterDetail) {
|
||||
navigation.navigate('ModalStackNavigator', { screen: 'ThreadMessagesView', params: { rid, t } });
|
||||
} else {
|
||||
navigation.navigate('ThreadMessagesView', { rid, t });
|
||||
}
|
||||
}
|
||||
|
||||
toggleFollowThread = () => {
|
||||
|
@ -104,7 +111,8 @@ class RightButtonsContainer extends React.PureComponent {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
userId: getUserSelector(state).id,
|
||||
threadsEnabled: state.settings.Threads_enabled
|
||||
threadsEnabled: state.settings.Threads_enabled,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(RightButtonsContainer);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { HeaderBackButton } from 'react-navigation-stack';
|
||||
import { HeaderBackButton } from '@react-navigation/stack';
|
||||
|
||||
import { isIOS } from '../../../utils/deviceInfo';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import Avatar from '../../../containers/Avatar';
|
||||
|
||||
|
@ -14,19 +13,20 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const RoomHeaderLeft = ({
|
||||
tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, split
|
||||
const RoomHeaderLeft = React.memo(({
|
||||
tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail
|
||||
}) => {
|
||||
if (!split || tmid) {
|
||||
if (!isMasterDetail || tmid) {
|
||||
const onPress = useCallback(() => navigation.goBack());
|
||||
return (
|
||||
<HeaderBackButton
|
||||
title={unreadsCount > 999 ? '+999' : unreadsCount || ' '}
|
||||
backTitleVisible={isIOS}
|
||||
onPress={() => navigation.goBack()}
|
||||
label={unreadsCount > 999 ? '+999' : unreadsCount || ' '}
|
||||
onPress={onPress}
|
||||
tintColor={themes[theme].headerTintColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const onPress = useCallback(() => goRoomActionsView(), []);
|
||||
if (baseUrl && userId && token) {
|
||||
return (
|
||||
<Avatar
|
||||
|
@ -37,12 +37,12 @@ const RoomHeaderLeft = ({
|
|||
style={styles.avatar}
|
||||
userId={userId}
|
||||
token={token}
|
||||
onPress={goRoomActionsView}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
});
|
||||
|
||||
RoomHeaderLeft.propTypes = {
|
||||
tmid: PropTypes.string,
|
||||
|
@ -55,7 +55,7 @@ RoomHeaderLeft.propTypes = {
|
|||
t: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
goRoomActionsView: PropTypes.func,
|
||||
split: PropTypes.bool
|
||||
isMasterDetail: PropTypes.bool
|
||||
};
|
||||
|
||||
export default RoomHeaderLeft;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { FlatList, InteractionManager, RefreshControl } from 'react-native';
|
||||
import { FlatList, RefreshControl } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -57,7 +57,7 @@ class List extends React.Component {
|
|||
animated: false
|
||||
};
|
||||
this.init();
|
||||
this.didFocusListener = props.navigation.addListener('didFocus', () => {
|
||||
this.unsubscribeFocus = props.navigation.addListener('focus', () => {
|
||||
if (this.mounted) {
|
||||
this.setState({ animated: true });
|
||||
} else {
|
||||
|
@ -106,17 +106,15 @@ class List extends React.Component {
|
|||
this.unsubscribeMessages();
|
||||
this.messagesSubscription = this.messagesObservable
|
||||
.subscribe((data) => {
|
||||
this.interaction = InteractionManager.runAfterInteractions(() => {
|
||||
if (tmid && this.thread) {
|
||||
data = [this.thread, ...data];
|
||||
}
|
||||
const messages = orderBy(data, ['ts'], ['desc']);
|
||||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
} else {
|
||||
this.state.messages = messages;
|
||||
}
|
||||
});
|
||||
if (tmid && this.thread) {
|
||||
data = [this.thread, ...data];
|
||||
}
|
||||
const messages = orderBy(data, ['ts'], ['desc']);
|
||||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
} else {
|
||||
this.state.messages = messages;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -157,14 +155,11 @@ class List extends React.Component {
|
|||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeMessages();
|
||||
if (this.interaction && this.interaction.cancel) {
|
||||
this.interaction.cancel();
|
||||
}
|
||||
if (this.onEndReached && this.onEndReached.stop) {
|
||||
this.onEndReached.stop();
|
||||
}
|
||||
if (this.didFocusListener && this.didFocusListener.remove) {
|
||||
this.didFocusListener.remove();
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
console.countReset(`${ this.constructor.name }.render calls`);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue