feat(lobby/native) LobbyScreen and LobbyChatScreen

This commit is contained in:
Calin Chitu 2022-04-26 13:15:31 +03:00 committed by Calinteodor
parent 037b9202a6
commit 3e004811e0
15 changed files with 485 additions and 225 deletions

View File

@ -37,6 +37,11 @@ type Props = {
*/ */
hasTabNavigator?: boolean, hasTabNavigator?: boolean,
/**
* Insets for the SafeAreaView.
*/
safeAreaInsets?: Array,
/** /**
* Additional style to be appended to the KeyboardAvoidingView containing the content of the modal. * Additional style to be appended to the KeyboardAvoidingView containing the content of the modal.
*/ */
@ -49,6 +54,7 @@ const JitsiScreen = ({
footerComponent, footerComponent,
hasTabNavigator = false, hasTabNavigator = false,
hasBottomTextInput = false, hasBottomTextInput = false,
safeAreaInsets = [ 'bottom', 'left', 'right' ],
style style
}: Props) => ( }: Props) => (
<View <View
@ -59,11 +65,7 @@ const JitsiScreen = ({
hasTabNavigator = { hasTabNavigator } hasTabNavigator = { hasTabNavigator }
style = { style }> style = { style }>
<SafeAreaView <SafeAreaView
edges = { [ edges = { safeAreaInsets }
'bottom',
'left',
'right'
] }
style = { styles.safeArea }> style = { styles.safeArea }>
{ children } { children }
</SafeAreaView> </SafeAreaView>

View File

@ -183,7 +183,7 @@ class Conference extends AbstractConference<Props, State> {
const { _showLobby } = this.props; const { _showLobby } = this.props;
if (!prevProps._showLobby && _showLobby) { if (!prevProps._showLobby && _showLobby) {
navigate(screen.lobby); navigate(screen.lobby.root);
} }
if (prevProps._showLobby && !_showLobby) { if (prevProps._showLobby && !_showLobby) {

View File

@ -0,0 +1,42 @@
import React from 'react';
import { translate } from '../../../base/i18n';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { connect } from '../../../base/redux';
import ChatInputBar from '../../../chat/components/native/ChatInputBar';
import MessageContainer from '../../../chat/components/native/MessageContainer';
import AbstractLobbyScreen, {
Props as AbstractProps,
_mapStateToProps as abstractMapStateToProps
} from '../AbstractLobbyScreen';
import styles from './styles';
/**
* Implements a chat screen that appears when communication is started
* between the moderator and the participant being in the lobby.
*/
class LobbyChatScreen extends
AbstractLobbyScreen<AbstractProps> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { _lobbyChatMessages } = this.props;
return (
<JitsiScreen style = { styles.lobbyChatWrapper }>
<MessageContainer messages = { _lobbyChatMessages } />
<ChatInputBar onSend = { this._onSendMessage } />
</JitsiScreen>
);
}
_onSendMessage: () => void;
}
export default translate(connect(abstractMapStateToProps)(LobbyChatScreen));

View File

@ -2,50 +2,50 @@
import React from 'react'; import React from 'react';
import { Text, View, TouchableOpacity, TextInput } from 'react-native'; import { Text, View, TouchableOpacity, TextInput } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Avatar } from '../../../base/avatar';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { Icon, IconClose, IconEdit } from '../../../base/icons';
import JitsiScreen from '../../../base/modal/components/JitsiScreen'; import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { LoadingIndicator } from '../../../base/react'; import { LoadingIndicator } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import ChatInputBar from '../../../chat/components/native/ChatInputBar'; import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui';
import MessageContainer from '../../../chat/components/native/MessageContainer'; import BaseTheme from '../../../base/ui/components/BaseTheme';
import AbstractLobbyScreen, { _mapStateToProps } from '../AbstractLobbyScreen'; import InviteButton
from '../../../invite/components/add-people-dialog/native/InviteButton';
import { LargeVideo } from '../../../large-video/components';
import { navigate }
from '../../../mobile/navigation/components/lobby/LobbyNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import AudioMuteButton from '../../../toolbox/components/AudioMuteButton';
import VideoMuteButton from '../../../toolbox/components/VideoMuteButton';
import AbstractLobbyScreen, {
Props as AbstractProps,
_mapStateToProps as abstractMapStateToProps } from '../AbstractLobbyScreen';
import styles from './styles'; import styles from './styles';
type Props = AbstractProps & {
/**
* The current aspect ratio of the screen.
*/
_aspectRatio: Symbol
}
/** /**
* Implements a waiting screen that represents the participant being in the lobby. * Implements a waiting screen that represents the participant being in the lobby.
*/ */
class LobbyScreen extends AbstractLobbyScreen { class LobbyScreen extends AbstractLobbyScreen<Props> {
/** /**
* Implements {@code PureComponent#render}. * Implements {@code PureComponent#render}.
* *
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { _meetingName, t } = this.props;
return ( return (
<JitsiScreen <>
style = { this.props._isLobbyChatActive && this.state.isChatOpen { this._renderLobby() }
? styles.lobbyChatWrapper </>
: styles.contentWrapper }>
{this.props._isLobbyChatActive && this.state.isChatOpen
? this._renderLobbyChat()
: <SafeAreaView>
<Text style = { styles.dialogTitle }>
{ t(this._getScreenTitleKey(), { moderator: this.props._lobbyMessageRecipient }) }
</Text>
<Text style = { styles.secondaryText }>
{ _meetingName }
</Text>
{ this._renderContent()}
</SafeAreaView> }
</JitsiScreen>
); );
} }
@ -69,35 +69,58 @@ class LobbyScreen extends AbstractLobbyScreen {
_onSwitchToPasswordMode: () => void; _onSwitchToPasswordMode: () => void;
_onSendMessage: () => void;
_onToggleChat: () => void;
_renderContent: () => React$Element<*>; _renderContent: () => React$Element<*>;
_renderToolbarButtons: () => React$Element<*>;
_renderLobby: () => React$Element<*>;
_onNavigateToLobbyChat: () => void;
/** /**
* Renders the lobby chat. * Navigates to the lobby chat screen.
*
* @private
* @returns {void}
*/
_onNavigateToLobbyChat() {
navigate(screen.lobby.chat);
}
/**
* Renders the lobby.
* *
* @inheritdoc * @inheritdoc
*/ */
_renderLobbyChat() { _renderLobby() {
const { t } = this.props; const { _aspectRatio } = this.props;
let contentStyles;
let largeVideoContainerStyles;
let contentContainerStyles;
if (_aspectRatio === ASPECT_RATIO_NARROW) {
largeVideoContainerStyles = styles.largeVideoContainer;
contentContainerStyles = styles.contentContainer;
} else {
contentStyles = styles.contentWide;
largeVideoContainerStyles = styles.largeVideoContainerWide;
contentContainerStyles = styles.contentContainerWide;
}
return ( return (
<> <JitsiScreen
<View style = { styles.lobbyChatHeader }> safeAreaInsets = { [ 'right' ] }
<Text style = { styles.lobbyChatTitle }> style = { styles.contentWrapper }>
{ t(this._getScreenTitleKey(), { moderator: this.props._lobbyMessageRecipient }) } <View style = { contentStyles }>
</Text> <View style = { largeVideoContainerStyles }>
<TouchableOpacity onPress = { this._onToggleChat }> <LargeVideo />
<Icon
src = { IconClose }
style = { styles.lobbyChatCloseButton } />
</TouchableOpacity>
</View> </View>
<MessageContainer messages = { this.props._lobbyChatMessages } /> <View style = { contentContainerStyles }>
<ChatInputBar onSend = { this._onSendMessage } /> { this._renderContent() }
</> { this._renderToolbarButtons() }
</View>
</View>
</JitsiScreen>
); );
} }
@ -108,15 +131,15 @@ class LobbyScreen extends AbstractLobbyScreen {
*/ */
_renderJoining() { _renderJoining() {
return ( return (
<> <View style = { styles.formWrapper }>
<LoadingIndicator <LoadingIndicator
color = 'black' color = { BaseTheme.palette.icon01 }
style = { styles.loadingIndicator } /> style = { styles.loadingIndicator } />
<Text style = { styles.joiningMessage }> <Text style = { styles.joiningMessage }>
{ this.props.t('lobby.joiningMessage') } { this.props.t('lobby.joiningMessage') }
</Text> </Text>
{ this._renderStandardButtons() } { this._renderStandardButtons() }
</> </View>
); );
} }
@ -127,7 +150,7 @@ class LobbyScreen extends AbstractLobbyScreen {
*/ */
_renderParticipantForm() { _renderParticipantForm() {
const { t } = this.props; const { t } = this.props;
const { displayName, email } = this.state; const { displayName } = this.state;
return ( return (
<View style = { styles.formWrapper }> <View style = { styles.formWrapper }>
@ -138,13 +161,6 @@ class LobbyScreen extends AbstractLobbyScreen {
onChangeText = { this._onChangeDisplayName } onChangeText = { this._onChangeDisplayName }
style = { styles.field } style = { styles.field }
value = { displayName } /> value = { displayName } />
<Text style = { styles.fieldLabel }>
{ t('lobby.emailField') }
</Text>
<TextInput
onChangeText = { this._onChangeEmail }
style = { styles.field }
value = { email } />
</View> </View>
); );
} }
@ -155,28 +171,7 @@ class LobbyScreen extends AbstractLobbyScreen {
* @inheritdoc * @inheritdoc
*/ */
_renderParticipantInfo() { _renderParticipantInfo() {
const { displayName, email } = this.state; return this._renderParticipantForm();
return (
<View style = { styles.participantBox }>
<TouchableOpacity
onPress = { this._onEnableEdit }
style = { styles.editButton }>
<Icon
src = { IconEdit }
style = { styles.editIcon } />
</TouchableOpacity>
<Avatar
participantId = { this.props._participantId }
size = { 64 } />
<Text style = { styles.displayNameText }>
{ displayName }
</Text>
{ Boolean(email) && <Text style = { styles.secondaryText }>
{ email }
</Text> }
</View>
);
} }
/** /**
@ -215,7 +210,17 @@ class LobbyScreen extends AbstractLobbyScreen {
const { t } = this.props; const { t } = this.props;
return ( return (
<> <View style = { styles.passwordJoinButtonsWrapper }>
<TouchableOpacity
onPress = { this._onSwitchToKnockMode }
style = { [
styles.button,
styles.primaryButton
] }>
<Text style = { styles.primaryButtonText }>
{ t('lobby.backToKnockModeButton') }
</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
disabled = { !this.state.password } disabled = { !this.state.password }
onPress = { this._onJoinWithPassword } onPress = { this._onJoinWithPassword }
@ -227,17 +232,34 @@ class LobbyScreen extends AbstractLobbyScreen {
{ t('lobby.passwordJoinButton') } { t('lobby.passwordJoinButton') }
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity </View>
onPress = { this._onSwitchToKnockMode } );
style = { [ }
styles.button,
styles.secondaryButton /**
] }> * Renders the toolbar buttons menu.
<Text> *
{ t('lobby.backToKnockModeButton') } * @inheritdoc
</Text> */
</TouchableOpacity> _renderToolbarButtons() {
</> const { _aspectRatio } = this.props;
let toolboxContainerStyles;
if (_aspectRatio === ASPECT_RATIO_NARROW) {
toolboxContainerStyles = styles.toolboxContainer;
} else {
toolboxContainerStyles = styles.toolboxContainerWide;
}
return (
<View style = { toolboxContainerStyles }>
<AudioMuteButton
styles = { styles.buttonStylesBorderless } />
<VideoMuteButton
styles = { styles.buttonStylesBorderless } />
<InviteButton
styles = { styles.buttonStylesBorderless } />
</View>
); );
} }
@ -248,50 +270,72 @@ class LobbyScreen extends AbstractLobbyScreen {
*/ */
_renderStandardButtons() { _renderStandardButtons() {
const { _knocking, _renderPassword, _isLobbyChatActive, t } = this.props; const { _knocking, _renderPassword, _isLobbyChatActive, t } = this.props;
const { displayName } = this.state;
const askToJoinButtonStyles
= displayName ? styles.primaryButton : styles.primaryButtonDisabled;
return ( return (
<> <View style = { styles.standardButtonWrapper }>
{ _knocking || <TouchableOpacity { _knocking && _isLobbyChatActive && <TouchableOpacity
disabled = { !this.state.displayName } onPress = { this._onNavigateToLobbyChat }
onPress = { this._onAskToJoin }
style = { [ style = { [
styles.button, styles.button,
styles.primaryButton styles.primaryButton
] }> ] }>
<Text style = { styles.primaryButtonText }> <Text style = { styles.primaryButtonText }>
{ t('lobby.knockButton') }
</Text>
</TouchableOpacity> }
{ _knocking && _isLobbyChatActive && <TouchableOpacity
onPress = { this._onToggleChat }
style = { [
styles.button,
styles.secondaryButton
] }>
<Text>
{ t('toolbar.openChat') } { t('toolbar.openChat') }
</Text> </Text>
</TouchableOpacity>} </TouchableOpacity>}
{ _knocking || <TouchableOpacity
disabled = { !displayName }
onPress = { this._onAskToJoin }
style = { [
styles.button,
askToJoinButtonStyles
] }>
<Text style = { styles.primaryButtonText }>
{ t('lobby.knockButton') }
</Text>
</TouchableOpacity> }
{ _renderPassword && <TouchableOpacity { _renderPassword && <TouchableOpacity
onPress = { this._onSwitchToPasswordMode } onPress = { this._onSwitchToPasswordMode }
style = { [ style = { [
styles.button, styles.button,
styles.secondaryButton styles.primaryButton
] }> ] }>
<Text> <Text style = { styles.primaryButtonText }>
{ t('lobby.enterPasswordButton') } { t('lobby.enterPasswordButton') }
</Text> </Text>
</TouchableOpacity> } </TouchableOpacity> }
<TouchableOpacity <TouchableOpacity
onPress = { this._onCancel } onPress = { this._onCancel }
style = { styles.cancelButton }> style = { [
<Text> styles.button,
styles.cancelButton
] }>
<Text style = { styles.cancelButtonText }>
{ t('dialog.Cancel') } { t('dialog.Cancel') }
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</> </View>
); );
} }
} }
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {{
* _aspectRatio: Symbol
* }}
*/
function _mapStateToProps(state: Object, ownProps: Props) {
return {
...abstractMapStateToProps(state, ownProps),
_aspectRatio: state['features/base/responsive-ui'].aspectRatio
};
}
export default translate(connect(_mapStateToProps)(LobbyScreen)); export default translate(connect(_mapStateToProps)(LobbyScreen));

View File

@ -2,3 +2,4 @@
export { default as KnockingParticipantList } from './KnockingParticipantList'; export { default as KnockingParticipantList } from './KnockingParticipantList';
export { default as LobbyScreen } from './LobbyScreen'; export { default as LobbyScreen } from './LobbyScreen';
export { default as LobbyChatScreen } from './LobbyChatScreen';

View File

@ -1,6 +1,6 @@
// @flow // @flow
import BaseTheme from '../../../base/ui/components/BaseTheme'; import BaseTheme from '../../../base/ui/components/BaseTheme.native';
const SECONDARY_COLOR = BaseTheme.palette.border04; const SECONDARY_COLOR = BaseTheme.palette.border04;
@ -8,8 +8,24 @@ export default {
button: { button: {
alignItems: 'center', alignItems: 'center',
borderRadius: 4, borderRadius: 4,
marginVertical: BaseTheme.spacing[1], padding: BaseTheme.spacing[2],
paddingVertical: BaseTheme.spacing[2] width: '100%'
},
buttonStylesBorderless: {
iconStyle: {
backgroundColor: BaseTheme.palette.action02Active,
color: BaseTheme.palette.icon01,
fontSize: 24
},
style: {
backgroundColor: BaseTheme.palette.action02Active,
flexDirection: 'row',
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[3],
height: 24,
width: 24
}
}, },
lobbyChatWrapper: { lobbyChatWrapper: {
@ -26,29 +42,66 @@ export default {
}, },
lobbyChatTitle: { lobbyChatTitle: {
color: '#fff', color: BaseTheme.palette.text01,
fontSize: 20, fontSize: 20,
fontWeight: 'bold', fontWeight: 'bold',
flexShrink: 1 flexShrink: 1
}, },
lobbyChatCloseButton: { lobbyChatCloseButton: {
fontSize: 20, fontSize: 24,
marginLeft: 20, marginLeft: BaseTheme.spacing[3],
color: '#fff' marginTop: BaseTheme.spacing[1],
color: BaseTheme.palette.icon01
}, },
contentWrapper: { contentWrapper: {
alignItems: 'center', backgroundColor: BaseTheme.palette.ui02,
display: 'flex', flex: 1
flexDirection: 'column',
justifyItems: 'center',
height: '100%'
}, },
closeIcon: { contentWide: {
color: 'red', backgroundColor: BaseTheme.palette.ui02,
fontSize: 20 flex: 1,
flexDirection: 'row'
},
largeVideoContainer: {
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
minHeight: '50%'
},
largeVideoContainerWide: {
height: '100%',
width: '50%'
},
contentContainer: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
contentContainerWide: {
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[2],
width: '50%'
},
toolboxContainer: {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
marginTop: BaseTheme.spacing[4]
},
toolboxContainerWide: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: BaseTheme.spacing[4]
}, },
dialogTitle: { dialogTitle: {
@ -75,40 +128,55 @@ export default {
}, },
field: { field: {
backgroundColor: BaseTheme.palette.field02,
borderColor: SECONDARY_COLOR, borderColor: SECONDARY_COLOR,
borderRadius: 4, borderRadius: BaseTheme.shape.borderRadius,
borderWidth: 1, borderWidth: 2,
marginVertical: 8, marginHorizontal: BaseTheme.spacing[3],
padding: 8 padding: BaseTheme.spacing[2]
}, },
fieldError: { fieldError: {
color: BaseTheme.palette.warning07, color: BaseTheme.palette.warning03,
fontSize: 10 marginLeft: BaseTheme.spacing[3],
}, fontSize: 16
fieldRow: {
paddingTop: 16
}, },
fieldLabel: { fieldLabel: {
color: BaseTheme.palette.text01,
marginVertical: BaseTheme.spacing[4],
textAlign: 'center' textAlign: 'center'
}, },
formWrapper: { formWrapper: {
alignItems: 'stretch', alignSelf: 'stretch'
},
standardButtonWrapper: {
alignSelf: 'stretch', alignSelf: 'stretch',
paddingVertical: 16 marginHorizontal: BaseTheme.spacing[3]
},
joiningContainer: {
alignItems: 'center',
display: 'flex',
justifyContent: 'center'
}, },
joiningMessage: { joiningMessage: {
color: 'rgba(0, 0, 0, .7)', color: BaseTheme.palette.text01,
paddingBottom: 36, marginBottom: BaseTheme.spacing[2],
textAlign: 'center' textAlign: 'center'
}, },
passwordJoinButtonsWrapper: {
alignItems: 'stretch',
alignSelf: 'stretch',
marginHorizontal: BaseTheme.spacing[3]
},
loadingIndicator: { loadingIndicator: {
marginVertical: 36 marginVertical: BaseTheme.spacing[4]
}, },
participantBox: { participantBox: {
@ -122,29 +190,33 @@ export default {
}, },
primaryButton: { primaryButton: {
alignSelf: 'stretch', backgroundColor: BaseTheme.palette.action01,
backgroundColor: 'rgb(3, 118, 218)' marginTop: BaseTheme.spacing[4]
},
primaryButtonDisabled: {
backgroundColor: BaseTheme.palette.action03Disabled,
marginTop: BaseTheme.spacing[4]
}, },
primaryButtonText: { primaryButtonText: {
color: 'white' color: BaseTheme.palette.text01
}, },
secondaryButton: { primaryText: {
alignSelf: 'stretch', color: BaseTheme.palette.text01,
backgroundColor: 'transparent'
},
secondaryText: {
color: 'rgba(0, 0, 0, .7)',
margin: 'auto', margin: 'auto',
textAlign: 'center' textAlign: 'center'
}, },
cancelButton: { cancelButton: {
alignItems: 'center', alignItems: 'center',
backgroundColor: 'transparent', backgroundColor: BaseTheme.palette.action02Disabled,
marginVertical: 4 marginTop: BaseTheme.spacing[4]
},
cancelButtonText: {
color: BaseTheme.palette.text01
}, },
// KnockingParticipantList // KnockingParticipantList

View File

@ -1,5 +1,3 @@
// @flow
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import React from 'react'; import React from 'react';

View File

@ -1,8 +1,5 @@
// @flow
import React from 'react'; import React from 'react';
// $FlowExpectedError
export const conferenceNavigationRef = React.createRef(); export const conferenceNavigationRef = React.createRef();
/** /**
@ -13,7 +10,6 @@ export const conferenceNavigationRef = React.createRef();
* @returns {Function} * @returns {Function}
*/ */
export function navigate(name: string, params: Object) { export function navigate(name: string, params: Object) {
// $FlowExpectedError
return conferenceNavigationRef.current?.navigate(name, params); return conferenceNavigationRef.current?.navigate(name, params);
} }
@ -23,7 +19,6 @@ export function navigate(name: string, params: Object) {
* @returns {Function} * @returns {Function}
*/ */
export function goBack() { export function goBack() {
// $FlowExpectedError
return conferenceNavigationRef.current?.goBack(); return conferenceNavigationRef.current?.goBack();
} }
@ -34,7 +29,6 @@ export function goBack() {
* @returns {Function} * @returns {Function}
*/ */
export function setParams(params: Object) { export function setParams(params: Object) {
// $FlowExpectedError
return conferenceNavigationRef.current?.setParams(params); return conferenceNavigationRef.current?.setParams(params);
} }

View File

@ -1,5 +1,3 @@
// @flow
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import React from 'react'; import React from 'react';
@ -13,7 +11,6 @@ import { SharedDocument } from '../../../../../etherpad';
import { GifsMenu } from '../../../../../gifs/components'; import { GifsMenu } from '../../../../../gifs/components';
import AddPeopleDialog import AddPeopleDialog
from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog'; from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
import LobbyScreen from '../../../../../lobby/components/native/LobbyScreen';
import { ParticipantsPane } from '../../../../../participants-pane/components/native'; import { ParticipantsPane } from '../../../../../participants-pane/components/native';
import { StartLiveStreamDialog } from '../../../../../recording'; import { StartLiveStreamDialog } from '../../../../../recording';
import { StartRecordingDialog } import { StartRecordingDialog }
@ -31,7 +28,6 @@ import {
gifsMenuOptions, gifsMenuOptions,
inviteScreenOptions, inviteScreenOptions,
liveStreamScreenOptions, liveStreamScreenOptions,
lobbyScreenOptions,
navigationContainerTheme, navigationContainerTheme,
participantsScreenOptions, participantsScreenOptions,
recordingScreenOptions, recordingScreenOptions,
@ -42,6 +38,8 @@ import {
} from '../../../screenOptions'; } from '../../../screenOptions';
import ChatAndPollsNavigationContainer import ChatAndPollsNavigationContainer
from '../../chat/components/ChatAndPollsNavigationContainer'; from '../../chat/components/ChatAndPollsNavigationContainer';
import LobbyNavigationContainer
from '../../lobby/components/LobbyNavigationContainer';
import { import {
conferenceNavigationRef conferenceNavigationRef
} from '../ConferenceNavigationContainerRef'; } from '../ConferenceNavigationContainerRef';
@ -134,9 +132,12 @@ const ConferenceNavigationContainer = () => {
title: t('notify.gifsMenu') title: t('notify.gifsMenu')
}} /> }} />
<ConferenceStack.Screen <ConferenceStack.Screen
component = { LobbyScreen } component = { LobbyNavigationContainer }
name = { screen.lobby } name = { screen.lobby.root }
options = { lobbyScreenOptions } /> options = {{
gestureEnabled: false,
headerShown: false
}} />
<ConferenceStack.Screen <ConferenceStack.Screen
component = { AddPeopleDialog } component = { AddPeopleDialog }
name = { screen.conference.invite } name = { screen.conference.invite }

View File

@ -0,0 +1,23 @@
import React from 'react';
export const lobbyNavigationContainerRef = React.createRef();
/**
* User defined navigation action included inside the reference to the container.
*
* @param {string} name - Destination name of the route that has been defined somewhere.
* @param {Object} params - Params to pass to the destination route.
* @returns {Function}
*/
export function navigate(name: string, params: Object) {
return lobbyNavigationContainerRef.current?.navigate(name, params);
}
/**
* User defined navigation action included inside the reference to the container.
*
* @returns {Function}
*/
export function goBack() {
return lobbyNavigationContainerRef.current?.goBack();
}

View File

@ -0,0 +1,47 @@
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { useSelector } from 'react-redux';
import { LobbyChatScreen, LobbyScreen } from '../../../../../lobby';
import { screen } from '../../../routes';
import {
lobbyChatScreenOptions,
lobbyScreenOptions, navigationContainerTheme
} from '../../../screenOptions';
import { lobbyNavigationContainerRef } from '../LobbyNavigationContainerRef';
const LobbyStack = createStackNavigator();
const LobbyNavigationContainer = () => {
const { isLobbyChatActive }
= useSelector(state => state['features/chat']);
return (
<NavigationContainer
independent = { true }
ref = { lobbyNavigationContainerRef }
theme = { navigationContainerTheme }>
<LobbyStack.Navigator
screenOptions = {{
presentation: 'modal'
}}>
<LobbyStack.Screen
component = { LobbyScreen }
name = { screen.lobby.main }
options = { lobbyScreenOptions } />
{
isLobbyChatActive
&& <LobbyStack.Screen
component = { LobbyChatScreen }
name = { screen.lobby.chat }
options = { lobbyChatScreenOptions } />
}
</LobbyStack.Navigator>
</NavigationContainer>
);
};
export default LobbyNavigationContainer;

View File

@ -0,0 +1,31 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Platform } from 'react-native';
import { IconClose } from '../../base/icons';
import HeaderNavigationButton from './components/HeaderNavigationButton';
/**
* Close icon/text button based on platform.
*
* @param {Function} goBack - Goes back to the previous screen function.
* @returns {React.Component}
*/
export function screenHeaderCloseButton(goBack: Function) {
const { t } = useTranslation();
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
onPress = { goBack }
src = { IconClose } />
);
}

View File

@ -1,8 +1,7 @@
// @flow
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { SET_ROOM } from '../../base/conference/actionTypes'; import { CONFERENCE_FAILED, SET_ROOM } from '../../base/conference/actionTypes';
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../../base/redux'; import { MiddlewareRegistry } from '../../base/redux';
import { readyToClose } from '../external-api/actions'; import { readyToClose } from '../external-api/actions';
@ -16,6 +15,9 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) { switch (action.type) {
case SET_ROOM: case SET_ROOM:
return _setRoom(store, next, action); return _setRoom(store, next, action);
case CONFERENCE_FAILED:
return _conferenceFailed(store, next, action);
} }
return next(action); return next(action);
@ -61,3 +63,31 @@ function _setRoom({ dispatch, getState }, next, action) {
return result; return result;
} }
/**
* Function to handle the conference failed event and navigate the user to the lobby screen
* based on the failure reason.
*
* @param {Object} store - The Redux store.
* @param {Function} next - The Redux next function.
* @param {Object} action - The Redux action.
* @returns {Object}
*/
function _conferenceFailed({ dispatch, getState }, next, action) {
const state = getState();
const isWelcomePageEnabled = isWelcomePageAppEnabled(state);
const { error } = action;
// We need to cover the case where knocking participant
// is rejected from entering the conference
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
if (isWelcomePageEnabled) {
navigateRoot(screen.root);
} else {
// For JitsiSDK, WelcomePage is not available
_sendReadyToClose(dispatch);
}
}
return next(action);
}

View File

@ -34,5 +34,9 @@ export const screen = {
invite: 'Invite', invite: 'Invite',
sharedDocument: 'Shared document' sharedDocument: 'Shared document'
}, },
lobby: 'Lobby' lobby: {
root: 'Lobby root',
main: 'Lobby',
chat: 'Lobby chat'
}
}; };

View File

@ -1,13 +1,9 @@
// @flow
import { TransitionPresets } from '@react-navigation/stack'; import { TransitionPresets } from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { Platform } from 'react-native'; import { Platform } from 'react-native';
import { import {
Icon, Icon,
IconClose,
IconHelp, IconHelp,
IconHome, IconHome,
IconInfo, IconInfo,
@ -15,8 +11,9 @@ import {
} from '../../base/icons'; } from '../../base/icons';
import BaseTheme from '../../base/ui/components/BaseTheme.native'; import BaseTheme from '../../base/ui/components/BaseTheme.native';
import HeaderNavigationButton from './components/HeaderNavigationButton';
import { goBack } from './components/conference/ConferenceNavigationContainerRef'; import { goBack } from './components/conference/ConferenceNavigationContainerRef';
import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
import { screenHeaderCloseButton } from './functions';
/** /**
@ -167,16 +164,12 @@ export const helpScreenOptions = {
/** /**
* Screen options for conference. * Screen options for conference.
*/ */
export const conferenceScreenOptions = { export const conferenceScreenOptions = fullScreenOptions;
...fullScreenOptions
};
/** /**
* Screen options for lobby modal. * Screen options for lobby modal.
*/ */
export const lobbyScreenOptions = { export const lobbyScreenOptions = fullScreenOptions;
...fullScreenOptions
};
/** /**
* Tab bar options for chat screen. * Tab bar options for chat screen.
@ -198,23 +191,7 @@ export const chatTabBarOptions = {
export const presentationScreenOptions = { export const presentationScreenOptions = {
...conferenceModalPresentation, ...conferenceModalPresentation,
headerBackTitleVisible: false, headerBackTitleVisible: false,
headerLeft: () => { headerLeft: () => screenHeaderCloseButton(goBack),
const { t } = useTranslation();
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
onPress = { goBack }
src = { IconClose } />
);
},
headerStatusBarHeight: 0, headerStatusBarHeight: 0,
headerStyle: { headerStyle: {
backgroundColor: BaseTheme.palette.screen01Header backgroundColor: BaseTheme.palette.screen01Header
@ -227,50 +204,44 @@ export const presentationScreenOptions = {
/** /**
* Screen options for chat. * Screen options for chat.
*/ */
export const chatScreenOptions = { export const chatScreenOptions = presentationScreenOptions;
...presentationScreenOptions
};
/** /**
* Screen options for invite modal. * Screen options for invite modal.
*/ */
export const inviteScreenOptions = { export const inviteScreenOptions = presentationScreenOptions;
...presentationScreenOptions
};
/** /**
* Screen options for participants modal. * Screen options for participants modal.
*/ */
export const participantsScreenOptions = { export const participantsScreenOptions = presentationScreenOptions;
...presentationScreenOptions
};
/** /**
* Screen options for speaker stats modal. * Screen options for speaker stats modal.
*/ */
export const speakerStatsScreenOptions = { export const speakerStatsScreenOptions = presentationScreenOptions;
...presentationScreenOptions
};
/** /**
* Screen options for security options modal. * Screen options for security options modal.
*/ */
export const securityScreenOptions = { export const securityScreenOptions = presentationScreenOptions;
...presentationScreenOptions
};
/** /**
* Screen options for recording modal. * Screen options for recording modal.
*/ */
export const recordingScreenOptions = { export const recordingScreenOptions = presentationScreenOptions;
...presentationScreenOptions
};
/** /**
* Screen options for live stream modal. * Screen options for live stream modal.
*/ */
export const liveStreamScreenOptions = { export const liveStreamScreenOptions = presentationScreenOptions;
...presentationScreenOptions
/**
* Screen options for lobby chat modal.
*/
export const lobbyChatScreenOptions = {
...presentationScreenOptions,
headerLeft: () => screenHeaderCloseButton(goBackToLobbyScreen)
}; };
/** /**