feat(prejoin) native prejoin screen and other navigation updates

* feat(prejoin) created native Prejoin screen

* feat(prejoin) fixed useState callback and updates warnings

* feat(prejoin) created styles file

* feat(prejoin) moved nav from middleware to appNavigate, created native DeviceStatus

* feat(prejoin) updated styles

* feat(prejoin) review remarks pt. 1

* feat(prejoin) removed unused styles

* feat(prejoin) review remarks pt. 2

* feat(prejoin) comment fix

* feat(prejoin) added header title

* feat(prejoin) review remarks

* feat(lobby) updated styles

* feat(prejoin) updated lobby screen header button functionality

* feat(prejoin) review remarks pt 3

* feat(welcome) removed VideoSwitch component

* feat(mobile/navigation) fixed linter

* feat(welcome) moved isWelcomePageEnabled to functions.ts

* feat(mobile/navigation) screen options and order updates

* feat(app) review remark

* feat(welcome) added translation for screen header title and fixed build

* feat(mobile/navigation) added screen title translation and created screen option

* feat(mobile/navigation) fixed screenOptions import

* feat(mobile/navigation) added DialInSummary title translation, fixed animation and close button

* feat(welcome) fixed build

* feat(welcome) removed extra check

* feat(prejoin) review remarks pt 4

* feat(prejoin) added Join in low bandwidth mode btn

* feat(welcome) changed welcome screen header title

* fixup lobby close
This commit is contained in:
Calinteodor 2022-06-16 12:49:07 +03:00 committed by GitHub
parent d31eb3b248
commit dbf7bf4750
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 487 additions and 329 deletions

View File

@ -208,6 +208,9 @@
"selectADevice": "Select a device",
"testAudio": "Play a test sound"
},
"dialIn": {
"screenTitle": "Dial-in summary"
},
"dialOut": {
"statusMessage": "is now {{status}}"
},
@ -815,6 +818,7 @@
"initiated": "Call initiated",
"joinAudioByPhone": "Join with phone audio",
"joinMeeting": "Join meeting",
"joinMeetingInLowBandwidthMode": "Join in low bandwidth mode",
"joinWithoutAudio": "Join without audio",
"keyboardShortcuts": "Enable Keyboard shortcuts",
"linkCopied": "Link copied to clipboard",
@ -949,6 +953,7 @@
"playSounds": "Play sound on",
"reactions": "Meeting reactions",
"sameAsSystem": "Same as system ({{label}})",
"screenTitle": "Settings",
"selectAudioOutput": "Audio output",
"selectCamera": "Camera",
"selectMic": "Microphone",

View File

@ -19,7 +19,11 @@ import {
parseURIString,
toURLString
} from '../base/util';
import { navigateRoot } from '../mobile/navigation/rootNavigationContainerRef';
import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
import {
goBackToRoot,
navigateRoot
} from '../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { setFatalError } from '../overlay';
@ -128,7 +132,15 @@ export function appNavigate(uri: ?string) {
if (room) {
dispatch(createDesiredLocalTracks());
dispatch(connect());
if (isPrejoinPageEnabled(getState())) {
navigateRoot(screen.preJoin);
} else {
dispatch(connect());
navigateRoot(screen.conference.root);
}
} else {
goBackToRoot(getState(), dispatch);
}
};
}

View File

@ -7,7 +7,8 @@ import { toState } from '../base/redux';
import { Conference } from '../conference';
import { getDeepLinkingPage } from '../deep-linking';
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
import { BlankPage, isWelcomePageUserEnabled, WelcomePage } from '../welcome';
import { BlankPage, WelcomePage } from '../welcome';
import { isWelcomePageEnabled } from '../welcome/functions';
/**
* Determines which route is to be rendered in order to depict a specific Redux
@ -72,7 +73,7 @@ function _getWebConferenceRoute(state) {
function _getWebWelcomePageRoute(state) {
const route = _getEmptyRoute();
if (isWelcomePageUserEnabled(state)) {
if (isWelcomePageEnabled(state)) {
if (isSupportedBrowser()) {
route.component = WelcomePage;
} else {

View File

@ -160,6 +160,12 @@ export const OVERFLOW_MENU_ENABLED = 'overflow-menu.enabled';
*/
export const PIP_ENABLED = 'pip.enabled';
/**
* Flag indicating if the prejoin page should be enabled.
* Default: enabled (true).
*/
export const PREJOIN_PAGE_ENABLED = 'prejoinpage.enabled';
/**
* Flag indicating if raise hand feature should be enabled.
* Default: enabled.

View File

@ -1,23 +1,21 @@
// @flow
import React, { PureComponent } from 'react';
import { Linking, Platform, View } from 'react-native';
import { Linking, View } from 'react-native';
import { WebView } from 'react-native-webview';
import { type Dispatch } from 'redux';
import { openDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { IconClose } from '../../../../base/icons';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { LoadingIndicator } from '../../../../base/react';
import { connect } from '../../../../base/redux';
import HeaderNavigationButton
from '../../../../mobile/navigation/components/HeaderNavigationButton';
import { getDialInfoPageURLForURIString } from '../../../functions';
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
import styles, { INDICATOR_COLOR } from './styles';
type Props = {
dispatch: Dispatch<any>,
@ -65,28 +63,9 @@ class DialInSummary extends PureComponent<Props> {
*/
componentDidMount() {
const { navigation, t } = this.props;
const onNavigationClose = () => {
navigation.goBack();
};
navigation.setOptions({
headerLeft: () => {
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
// eslint-disable-next-line react/jsx-no-bind
onPress = { onNavigationClose } />
);
}
return (
<HeaderNavigationButton
// eslint-disable-next-line react/jsx-no-bind
onPress = { onNavigationClose }
src = { IconClose } />
);
}
headerTitle: t('dialIn.screenTitle')
});
}

View File

@ -15,12 +15,10 @@ export default {
buttonStylesBorderless: {
iconStyle: {
backgroundColor: 'transparent',
color: BaseTheme.palette.icon01,
fontSize: 24
},
style: {
backgroundColor: 'transparent',
flexDirection: 'row',
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[3],
@ -70,25 +68,25 @@ export default {
},
largeVideoContainerWide: {
position: 'absolute',
marginRight: 'auto',
height: '100%',
marginRight: 'auto',
position: 'absolute',
width: '50%'
},
contentContainer: {
display: 'flex',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
minHeight: '50%'
},
contentContainerWide: {
height: '100%',
justifyContent: 'center',
left: '50%',
marginHorizontal: BaseTheme.spacing[6],
marginVertical: BaseTheme.spacing[3],
height: '100%',
left: '50%',
position: 'absolute',
width: '50%'
},
@ -124,7 +122,7 @@ export default {
formWrapper: {
alignSelf: 'stretch',
marginTop: 45
justifyContent: 'center'
},
field: {

View File

@ -1,8 +1,11 @@
// @flow
import debounce from 'lodash/debounce';
import { NativeModules } from 'react-native';
import { getAppProp } from '../../base/app';
import { readyToClose } from '../external-api/actions';
/**
* Sends a specific event to the native counterpart of the External API. Native
@ -24,3 +27,10 @@ export function sendEvent(store: Object, name: string, data: Object) {
externalAPIScope
&& NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
}
/**
* Debounced sending of `readyToClose`.
*/
export const _sendReadyToClose = debounce(dispatch => {
dispatch(readyToClose());
}, 2500, { leading: true });

View File

@ -4,20 +4,25 @@ import React, { useCallback } from 'react';
import { connect } from '../../../base/redux';
import { DialInSummary } from '../../../invite';
import Prejoin from '../../../prejoin/components/Prejoin.native';
import { isWelcomePageEnabled } from '../../../welcome/functions';
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
import { rootNavigationRef } from '../rootNavigationContainerRef';
import { screen } from '../routes';
import {
conferenceNavigationContainerScreenOptions,
connectingScreenOptions,
dialInSummaryScreenOptions,
drawerNavigatorScreenOptions,
navigationContainerTheme
navigationContainerTheme,
preJoinScreenOptions
} from '../screenOptions';
import ConnectingPage from './ConnectingPage';
import ConferenceNavigationContainer
from './conference/components/ConferenceNavigationContainer';
import WelcomePageNavigationContainer from './welcome/components/WelcomePageNavigationContainer';
import { isWelcomePageAppEnabled } from './welcome/functions';
import WelcomePageNavigationContainer
from './welcome/components/WelcomePageNavigationContainer';
const RootStack = createNativeStackNavigator();
@ -70,17 +75,15 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
<RootStack.Screen
component = { ConnectingPage }
name = { screen.connecting }
options = {{
gestureEnabled: false,
headerShown: false
}} />
options = { connectingScreenOptions } />
<RootStack.Screen
component = { Prejoin }
name = { screen.preJoin }
options = { preJoinScreenOptions } />
<RootStack.Screen
component = { ConferenceNavigationContainer }
name = { screen.conference.root }
options = {{
gestureEnabled: false,
headerShown: false
}} />
options = { conferenceNavigationContainerScreenOptions } />
</RootStack.Navigator>
</NavigationContainer>
);
@ -94,7 +97,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
*/
function mapStateToProps(state: Object) {
return {
isWelcomePageAvailable: isWelcomePageAppEnabled(state)
isWelcomePageAvailable: isWelcomePageEnabled(state)
};
}

View File

@ -31,6 +31,7 @@ import {
gifsMenuOptions,
inviteScreenOptions,
liveStreamScreenOptions,
lobbyNavigationContainerScreenOptions,
navigationContainerTheme,
participantsScreenOptions,
recordingScreenOptions,
@ -101,15 +102,11 @@ const ConferenceNavigationContainer = () => {
<ConferenceStack.Screen
component = { StartRecordingDialog }
name = { screen.conference.recording }
options = {{
...recordingScreenOptions
}} />
options = { recordingScreenOptions } />
<ConferenceStack.Screen
component = { StartLiveStreamDialog }
name = { screen.conference.liveStream }
options = {{
...liveStreamScreenOptions
}} />
options = { liveStreamScreenOptions } />
<ConferenceStack.Screen
component = { SpeakerStats }
name = { screen.conference.speakerStats }
@ -134,10 +131,7 @@ const ConferenceNavigationContainer = () => {
<ConferenceStack.Screen
component = { LobbyNavigationContainer }
name = { screen.lobby.root }
options = {{
gestureEnabled: false,
headerShown: false
}} />
options = { lobbyNavigationContainerScreenOptions } />
<ConferenceStack.Screen
component = { AddPeopleDialog }
name = { screen.conference.invite }
@ -158,7 +152,7 @@ const ConferenceNavigationContainer = () => {
options = {{
...carmodeScreenOptions,
title: t('carmode.labels.title')
}}/>
}} />
</ConferenceStack.Navigator>
</NavigationContainer>
);

View File

@ -2,25 +2,11 @@
import React from 'react';
import { getFeatureFlag, WELCOME_PAGE_ENABLED } from '../../../../base/flags';
import { IconArrowBack } from '../../../../base/icons';
import HeaderNavigationButton
from '../HeaderNavigationButton';
import { navigationStyles } from '../styles';
/**
* Determines whether the {@code WelcomePage} is enabled by the app itself
* (e.g. Programmatically via the Jitsi Meet SDK for Android and iOS). Not to be
* confused with {@link isWelcomePageUserEnabled}.
*
* @param {Function|Object} stateful - The redux state or {@link getState}
* function.
* @returns {boolean} If the {@code WelcomePage} is enabled by the app, then
* {@code true}; otherwise, {@code false}.
*/
export function isWelcomePageAppEnabled(stateful: Function | Object) {
return Boolean(getFeatureFlag(stateful, WELCOME_PAGE_ENABLED));
}
/**
* Render header arrow back button for navigation.

View File

@ -1,11 +1,19 @@
import React from 'react';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Platform } from 'react-native';
import { useDispatch } from 'react-redux';
import { appNavigate } from '../../app/actions';
import {
getFeatureFlag,
PREJOIN_PAGE_ENABLED
} from '../../base/flags';
import { IconClose } from '../../base/icons';
import { cancelKnocking } from '../../lobby/actions.native';
import HeaderNavigationButton from './components/HeaderNavigationButton';
/**
* Close icon/text button based on platform.
*
@ -29,3 +37,44 @@ export function screenHeaderCloseButton(goBack: Function) {
src = { IconClose } />
);
}
/**
* Determines whether the {@code Prejoin page} is enabled by the app itself
* (e.g. Programmatically via the Jitsi Meet SDK for Android and iOS).
*
* @param {Function|Object} stateful - The redux state or {@link getState}
* function.
* @returns {boolean} If the {@code Prejoin} is enabled by the app, then
* {@code true}; otherwise, {@code false}.
*/
export function isPrejoinPageEnabled(stateful: Function | Object) {
return getFeatureFlag(stateful, PREJOIN_PAGE_ENABLED, true);
}
/**
* Close icon/text button for lobby screen based on platform.
*
* @returns {React.Component}
*/
export function lobbyScreenHeaderCloseButton() {
const dispatch = useDispatch();
const { t } = useTranslation();
const goBack = useCallback(() => {
dispatch(cancelKnocking());
dispatch(appNavigate(undefined));
}, [ dispatch ]);
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
onPress = { goBack }
src = { IconClose } />
);
}

View File

@ -1,20 +1,11 @@
import debounce from 'lodash/debounce';
import { CONFERENCE_FAILED, SET_ROOM } from '../../base/conference/actionTypes';
import { appNavigate } from '../../app/actions';
import { CONFERENCE_FAILED } from '../../base/conference/actionTypes';
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
import { MiddlewareRegistry } from '../../base/redux';
import { readyToClose } from '../external-api/actions';
import { isWelcomePageAppEnabled } from './components/welcome/functions';
import { navigateRoot } from './rootNavigationContainerRef';
import { screen } from './routes';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_ROOM:
return _setRoom(store, next, action);
case CONFERENCE_FAILED:
return _conferenceFailed(store, next, action);
@ -23,47 +14,6 @@ MiddlewareRegistry.register(store => next => action => {
return next(action);
});
/**
* Debounced sending of `readyToClose`.
*/
const _sendReadyToClose = debounce(dispatch => {
dispatch(readyToClose());
}, 2500, { leading: true });
/**
* Notifies the feature base/conference that the action
* {@code SET_ROOM} is being dispatched within a specific
* redux store.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The redux action {@code SET_ROOM}
* which is being dispatched in the specified {@code store}.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _setRoom({ dispatch, getState }, next, action) {
const { room: oldRoom } = getState()['features/base/conference'];
const result = next(action);
const { room: newRoom } = getState()['features/base/conference'];
const isWelcomePageEnabled = isWelcomePageAppEnabled(getState());
if (!oldRoom && newRoom) {
navigateRoot(screen.conference.root);
} else if (!newRoom) {
if (isWelcomePageEnabled) {
navigateRoot(screen.root);
} else {
// For JitsiSDK, WelcomePage is not available
_sendReadyToClose(dispatch);
}
}
return result;
}
/**
* Function to handle the conference failed event and navigate the user to the lobby screen
* based on the failure reason.
@ -73,20 +23,13 @@ function _setRoom({ dispatch, getState }, next, action) {
* @param {Object} action - The Redux action.
* @returns {Object}
*/
function _conferenceFailed({ dispatch, getState }, next, action) {
const state = getState();
const isWelcomePageEnabled = isWelcomePageAppEnabled(state);
function _conferenceFailed({ dispatch }, next, action) {
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);
}
dispatch(appNavigate(undefined));
}
return next(action);

View File

@ -1,10 +1,14 @@
// @flow
import React from 'react';
// $FlowExpectedError
import { toState } from '../../base/redux';
import { isWelcomePageEnabled } from '../../welcome/functions';
import { _sendReadyToClose } from '../external-api/functions';
import { screen } from './routes';
export const rootNavigationRef = React.createRef();
/**
* User defined navigation action included inside the reference to the container.
*
@ -13,7 +17,32 @@ export const rootNavigationRef = React.createRef();
* @returns {Function}
*/
export function navigateRoot(name: string, params: Object) {
// $FlowExpectedError
return rootNavigationRef.current?.navigate(name, params);
}
/**
* User defined navigation action included inside the reference to the container.
*
* @returns {Function}
*/
export function goBack() {
return rootNavigationRef.current?.goBack();
}
/**
* Navigates back to Welcome page, if it's available.
*
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
* @param {Function} dispatch - Redux dispatch function.
* @returns {void}
*/
export function goBackToRoot(stateful: Function | Object, dispatch: Function) {
const state = toState(stateful);
if (isWelcomePageEnabled(state)) {
navigateRoot(screen.root);
} else {
// For JitsiSDK, WelcomePage is not available
_sendReadyToClose(dispatch);
}
}

View File

@ -13,6 +13,7 @@ export const screen = {
},
dialInSummary: 'Dial-In Info',
connecting: 'Connecting',
preJoin: 'Pre-Join',
conference: {
root: 'Conference root',
main: 'Conference',

View File

@ -12,7 +12,8 @@ import BaseTheme from '../../base/ui/components/BaseTheme.native';
import { goBack } from './components/conference/ConferenceNavigationContainerRef';
import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
import { screenHeaderCloseButton } from './functions';
import { lobbyScreenHeaderCloseButton, screenHeaderCloseButton } from './functions';
import { goBack as goBackToWelcomeScreen } from './rootNavigationContainerRef';
/**
@ -81,8 +82,9 @@ export const welcomeScreenOptions = {
headerStyle: {
backgroundColor: BaseTheme.palette.screen01Header
},
// eslint-disable-next-line no-empty-function
headerTitle: () => {}
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
@ -164,7 +166,6 @@ export const chatTabBarOptions = {
* Screen options for presentation type modals.
*/
export const presentationScreenOptions = {
animation: 'slide_from_right',
headerBackTitleVisible: false,
headerLeft: () => screenHeaderCloseButton(goBack),
headerStatusBarHeight: 0,
@ -195,7 +196,8 @@ export const chatScreenOptions = presentationScreenOptions;
*/
export const dialInSummaryScreenOptions = {
...presentationScreenOptions,
headerLeft: undefined
animation: 'slide_from_bottom',
headerLeft: () => screenHeaderCloseButton(goBackToWelcomeScreen)
};
/**
@ -231,7 +233,10 @@ export const liveStreamScreenOptions = presentationScreenOptions;
/**
* Screen options for lobby modal.
*/
export const lobbyScreenOptions = presentationScreenOptions;
export const lobbyScreenOptions = {
...presentationScreenOptions,
headerLeft: () => lobbyScreenHeaderCloseButton()
};
/**
* Screen options for lobby chat modal.
@ -269,3 +274,38 @@ export const sharedDocumentScreenOptions = {
android: 'all'
})
};
/**
* Screen options for connecting screen.
*/
export const connectingScreenOptions = {
gestureEnabled: false,
headerShown: false
};
/**
* Screen options for pre-join screen.
*/
export const preJoinScreenOptions = {
gestureEnabled: false,
headerStyle: {
backgroundColor: BaseTheme.palette.screen02Header
},
headerTitle: ''
};
/**
* Screen options for conference navigation container screen.
*/
export const conferenceNavigationContainerScreenOptions = {
gestureEnabled: false,
headerShown: false
};
/**
* Screen options for lobby navigation container screen.
*/
export const lobbyNavigationContainerScreenOptions = {
gestureEnabled: false,
headerShown: false
};

View File

@ -0,0 +1,147 @@
import React, { useCallback, useLayoutEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, View, TouchableOpacity, TextInput, Platform } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { appNavigate } from '../../app/actions.native';
import { setAudioOnly } from '../../base/audio-only/actions';
import { connect } from '../../base/connection/actions.native';
import { IconClose } from '../../base/icons';
import JitsiScreen from '../../base/modal/components/JitsiScreen';
import { getLocalParticipant } from '../../base/participants';
import { getFieldValue } from '../../base/react';
import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
import { LargeVideo } from '../../large-video/components';
import HeaderNavigationButton from '../../mobile/navigation/components/HeaderNavigationButton';
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../../mobile/navigation/routes';
import AudioMuteButton from '../../toolbox/components/AudioMuteButton';
import VideoMuteButton from '../../toolbox/components/VideoMuteButton';
import styles from './styles';
interface Props {
navigation: any;
}
const Prejoin: ({ navigation }: Props) => JSX.Element = ({ navigation }: Props) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const aspectRatio = useSelector(
(state: any) => state['features/base/responsive-ui']?.aspectRatio
);
const localParticipant = useSelector(state => getLocalParticipant(state));
const participantName = localParticipant?.name;
const [ displayName, setDisplayName ]
= useState(participantName || '');
const onChangeDisplayName = useCallback(event => {
const fieldValue = getFieldValue(event);
setDisplayName(fieldValue);
dispatch(updateSettings({
displayName: fieldValue
}));
}, [ displayName ]);
const onJoin = useCallback(() => {
dispatch(connect());
navigateRoot(screen.conference.root);
}, [ dispatch ]);
const onJoinLowBandwidth = useCallback(() => {
dispatch(setAudioOnly(true));
onJoin();
}, [ dispatch ]);
const goBack = useCallback(() => {
dispatch(appNavigate(undefined));
}, [ dispatch ]);
let contentWrapperStyles;
let contentContainerStyles;
let largeVideoContainerStyles;
let toolboxContainerStyles;
if (aspectRatio === ASPECT_RATIO_NARROW) {
contentWrapperStyles = styles.contentWrapper;
contentContainerStyles = styles.contentContainer;
largeVideoContainerStyles = styles.largeVideoContainer;
toolboxContainerStyles = styles.toolboxContainer;
} else {
contentWrapperStyles = styles.contentWrapperWide;
contentContainerStyles = styles.contentContainerWide;
largeVideoContainerStyles = styles.largeVideoContainerWide;
toolboxContainerStyles = styles.toolboxContainerWide;
}
const headerLeft = useCallback(() => {
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
onPress = { goBack }
src = { IconClose } />
);
}, []);
useLayoutEffect(() => {
navigation.setOptions({
headerLeft
});
}, [ navigation ]);
return (
<JitsiScreen
safeAreaInsets = { [ 'right' ] }
style = { contentWrapperStyles }>
<View style = { largeVideoContainerStyles }>
<LargeVideo />
</View>
<View style = { contentContainerStyles }>
<View style = { styles.formWrapper }>
<TextInput
onChangeText = { onChangeDisplayName }
placeholder = { t('dialog.enterDisplayName') }
style = { styles.field }
value = { displayName } />
<TouchableOpacity
onPress = { onJoin }
style = { [
styles.button,
styles.primaryButton
] }>
<Text style = { styles.primaryButtonText }>
{ t('prejoin.joinMeeting') }
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress = { onJoinLowBandwidth }
style = { [
styles.button,
styles.secondaryButton
] }>
<Text style = { styles.secondaryButtonText }>
{ t('prejoin.joinMeetingInLowBandwidthMode') }
</Text>
</TouchableOpacity>
</View>
<View style = { toolboxContainerStyles }>
<AudioMuteButton
styles = { styles.buttonStylesBorderless } />
<VideoMuteButton
styles = { styles.buttonStylesBorderless } />
</View>
</View>
</JitsiScreen>
);
};
export default Prejoin;

View File

@ -0,0 +1,121 @@
import BaseTheme from '../../base/ui/components/BaseTheme.native';
const SECONDARY_COLOR = BaseTheme.palette.border04;
const btn = {
marginTop: BaseTheme.spacing[4]
};
const btnText = {
...BaseTheme.typography.labelButtonLarge,
color: BaseTheme.palette.text01,
lineHeight: 30
};
export default {
button: {
alignItems: 'center',
borderRadius: BaseTheme.shape.borderRadius,
padding: BaseTheme.spacing[2],
height: BaseTheme.spacing[7]
},
primaryButton: {
...btn,
backgroundColor: BaseTheme.palette.action01
},
primaryButtonText: {
...btnText
},
secondaryButton: {
...btn,
backgroundColor: BaseTheme.palette.action02
},
secondaryButtonText: {
...btnText
},
buttonStylesBorderless: {
iconStyle: {
color: BaseTheme.palette.icon01,
fontSize: 24
},
style: {
flexDirection: 'row',
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[3],
height: 24,
width: 24
}
},
contentWrapper: {
flex: 1
},
contentWrapperWide: {
flex: 1,
flexDirection: 'row'
},
largeVideoContainer: {
minHeight: '50%'
},
largeVideoContainerWide: {
height: '100%',
marginRight: 'auto',
position: 'absolute',
width: '50%'
},
contentContainer: {
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
minHeight: '50%'
},
contentContainerWide: {
height: '100%',
justifyContent: 'center',
left: '50%',
marginHorizontal: BaseTheme.spacing[6],
marginVertical: BaseTheme.spacing[3],
position: 'absolute',
width: '50%'
},
toolboxContainer: {
alignSelf: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
marginTop: BaseTheme.spacing[4]
},
toolboxContainerWide: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: BaseTheme.spacing[4]
},
formWrapper: {
alignSelf: 'stretch',
justifyContent: 'center',
marginHorizontal: BaseTheme.spacing[3]
},
field: {
backgroundColor: BaseTheme.palette.field02,
borderColor: SECONDARY_COLOR,
borderRadius: BaseTheme.shape.borderRadius,
borderWidth: 2,
height: BaseTheme.spacing[7],
marginTop: BaseTheme.spacing[2],
textAlign: 'center'
}
};

View File

@ -1,145 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Switch, TouchableWithoutFeedback, View } from 'react-native';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { translate } from '../../base/i18n';
import { Text } from '../../base/react';
import { connect } from '../../base/redux';
import { updateSettings } from '../../base/settings';
import styles, { SWITCH_THUMB_COLOR, SWITCH_UNDER_COLOR } from './styles';
/**
* The type of the React {@code Component} props of {@link VideoSwitch}.
*/
type Props = {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The i18n translate function.
*/
t: Function,
/**
* Color schemed style of the header component.
*/
_headerStyles: Object,
/**
* The current settings from redux.
*/
_settings: Object
};
/**
* Renders the "Video <-> Voice" switch on the {@code WelcomePage}.
*/
class VideoSwitch extends Component<Props> {
/**
* Initializes a new {@code VideoSwitch} instance.
*
* @inheritdoc
*/
constructor(props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onStartAudioOnlyChange = this._onStartAudioOnlyChange.bind(this);
this._onStartAudioOnlyFalse = this._onStartAudioOnlyChangeFn(false);
this._onStartAudioOnlyTrue = this._onStartAudioOnlyChangeFn(true);
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
render() {
const { t, _headerStyles, _settings } = this.props;
return (
<View style = { styles.audioVideoSwitchContainer }>
<TouchableWithoutFeedback
onPress = { this._onStartAudioOnlyFalse }>
<View style = { styles.switchLabel }>
<Text style = { _headerStyles.headerText }>
{ t('welcomepage.audioVideoSwitch.video') }
</Text>
</View>
</TouchableWithoutFeedback>
<Switch
onValueChange = { this._onStartAudioOnlyChange }
style = { styles.audioVideoSwitch }
thumbColor = { SWITCH_THUMB_COLOR }
trackColor = {{ true: SWITCH_UNDER_COLOR }}
value = { _settings.startAudioOnly } />
<TouchableWithoutFeedback
onPress = { this._onStartAudioOnlyTrue }>
<View style = { styles.switchLabel }>
<Text style = { _headerStyles.headerText }>
{ t('welcomepage.audioVideoSwitch.audio') }
</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}
_onStartAudioOnlyChange: boolean => void;
/**
* Handles the audio-video switch changes.
*
* @private
* @param {boolean} startAudioOnly - The new startAudioOnly value.
* @returns {void}
*/
_onStartAudioOnlyChange(startAudioOnly) {
const { dispatch } = this.props;
dispatch(updateSettings({
startAudioOnly
}));
}
/**
* Creates a function that forwards the {@code startAudioOnly} changes to
* the function that handles it.
*
* @private
* @param {boolean} startAudioOnly - The new {@code startAudioOnly} value.
* @returns {void}
*/
_onStartAudioOnlyChangeFn(startAudioOnly) {
return () => this._onStartAudioOnlyChange(startAudioOnly);
}
_onStartAudioOnlyFalse: () => void;
_onStartAudioOnlyTrue: () => void;
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code VideoSwitch}.
*
* @param {Object} state - The redux state.
* @protected
* @returns {{
* _settings: Object
* }}
*/
export function _mapStateToProps(state: Object) {
return {
_headerStyles: ColorSchemeRegistry.get(state, 'Header'),
_settings: state['features/base/settings']
};
}
export default translate(connect(_mapStateToProps)(VideoSwitch));

View File

@ -27,7 +27,6 @@ import {
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from './AbstractWelcomePage';
import VideoSwitch from './VideoSwitch';
import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
@ -101,7 +100,8 @@ class WelcomePage extends AbstractWelcomePage<*> {
const {
_headerStyles,
navigation
navigation,
t
} = this.props;
navigation.setOptions({
@ -118,9 +118,7 @@ class WelcomePage extends AbstractWelcomePage<*> {
style = { _headerStyles.headerButtonIcon } />
</TouchableOpacity>
),
// eslint-disable-next-line react/no-multi-comp
headerRight: () =>
<VideoSwitch />
headerTitle: t('welcomepage.headerTitle')
});
navigation.addListener('focus', () => {

View File

@ -159,13 +159,15 @@ class SettingsView extends AbstractSettingsView<Props, State> {
*/
componentDidMount() {
const {
navigation
navigation,
t
} = this.props;
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
navigation.navigate(screen.welcome.main))
navigation.navigate(screen.welcome.main)),
headerTitle: t('settings.screenTitle')
});
}

View File

@ -26,22 +26,6 @@ const TEXT_COLOR = BaseTheme.palette.text01;
*/
export default {
/**
* The audio-video switch itself.
*/
audioVideoSwitch: {
marginHorizontal: 5
},
/**
* View that contains the audio-video switch and the labels.
*/
audioVideoSwitchContainer: {
alignItems: 'center',
flexDirection: 'row',
marginRight: BaseTheme.spacing[3]
},
blankPageText: {
color: TEXT_COLOR,
fontSize: 18

View File

@ -1,24 +1,20 @@
// @flow
import { WELCOME_PAGE_ENABLED } from '../base/flags/constants';
import { getFeatureFlag } from '../base/flags/functions';
import { toState } from '../base/redux/functions';
import { toState } from '../base/redux';
declare var APP: Object;
/**
* Determines whether the {@code WelcomePage} is enabled by the user either
* herself or through her deployment config(uration). Not to be confused with
* {@link isWelcomePageAppEnabled}.
* Determines whether the {@code WelcomePage} is enabled.
*
* @param {Function|Object} stateful - The redux state or {@link getState}
* function.
* @returns {boolean} If the {@code WelcomePage} is enabled by the user, then
* @returns {boolean} If the {@code WelcomePage} is enabled by the app, then
* {@code true}; otherwise, {@code false}.
*/
export function isWelcomePageUserEnabled(stateful: Function | Object) {
return (
typeof APP === 'undefined'
? true
: toState(stateful)['features/base/config'].enableWelcomePage);
export function isWelcomePageEnabled(stateful: Function | Object) {
if (navigator.product === 'ReactNative') {
return getFeatureFlag(stateful, WELCOME_PAGE_ENABLED, false);
}
return toState(stateful)['features/base/config'].enableWelcomePage;
}

View File

@ -1,2 +1 @@
export * from './components';
export * from './functions';