From dbf7bf47503101a082e9a15c38cbb50fd7e8dc80 Mon Sep 17 00:00:00 2001 From: Calinteodor Date: Thu, 16 Jun 2022 12:49:07 +0300 Subject: [PATCH] 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 --- lang/main.json | 5 + react/features/app/actions.native.js | 16 +- react/features/app/getRouteToRender.web.js | 5 +- react/features/base/flags/constants.js | 6 + .../dial-in-summary/native/DialInSummary.js | 27 +--- .../lobby/components/native/styles.js | 14 +- .../features/mobile/external-api/functions.js | 10 ++ .../components/RootNavigationContainer.js | 27 ++-- .../ConferenceNavigationContainer.tsx | 16 +- .../components/welcome/functions.js | 14 -- react/features/mobile/navigation/functions.js | 51 +++++- .../features/mobile/navigation/middleware.js | 65 +------- .../navigation/rootNavigationContainerRef.js | 37 ++++- react/features/mobile/navigation/routes.js | 1 + .../mobile/navigation/screenOptions.js | 52 ++++++- .../prejoin/components/Prejoin.native.tsx | 147 ++++++++++++++++++ react/features/prejoin/components/styles.js | 121 ++++++++++++++ .../welcome/components/VideoSwitch.js | 145 ----------------- .../welcome/components/WelcomePage.native.js | 8 +- .../settings/components/SettingsView.js | 6 +- react/features/welcome/components/styles.js | 16 -- react/features/welcome/functions.js | 26 ++-- react/features/welcome/index.js | 1 - 23 files changed, 487 insertions(+), 329 deletions(-) create mode 100644 react/features/prejoin/components/Prejoin.native.tsx create mode 100644 react/features/prejoin/components/styles.js delete mode 100644 react/features/welcome/components/VideoSwitch.js diff --git a/lang/main.json b/lang/main.json index 4f5320f90..e03743dc2 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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", diff --git a/react/features/app/actions.native.js b/react/features/app/actions.native.js index 54b27325e..bc172c8d5 100644 --- a/react/features/app/actions.native.js +++ b/react/features/app/actions.native.js @@ -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); } }; } diff --git a/react/features/app/getRouteToRender.web.js b/react/features/app/getRouteToRender.web.js index 416720cd3..29f22d5df 100644 --- a/react/features/app/getRouteToRender.web.js +++ b/react/features/app/getRouteToRender.web.js @@ -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 { diff --git a/react/features/base/flags/constants.js b/react/features/base/flags/constants.js index afbb328e5..20650270f 100644 --- a/react/features/base/flags/constants.js +++ b/react/features/base/flags/constants.js @@ -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. diff --git a/react/features/invite/components/dial-in-summary/native/DialInSummary.js b/react/features/invite/components/dial-in-summary/native/DialInSummary.js index f8096aca7..defa85a40 100644 --- a/react/features/invite/components/dial-in-summary/native/DialInSummary.js +++ b/react/features/invite/components/dial-in-summary/native/DialInSummary.js @@ -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, @@ -65,28 +63,9 @@ class DialInSummary extends PureComponent { */ componentDidMount() { const { navigation, t } = this.props; - const onNavigationClose = () => { - navigation.goBack(); - }; navigation.setOptions({ - headerLeft: () => { - if (Platform.OS === 'ios') { - return ( - - ); - } - - return ( - - ); - } + headerTitle: t('dialIn.screenTitle') }); } diff --git a/react/features/lobby/components/native/styles.js b/react/features/lobby/components/native/styles.js index ec3440125..ba90f9ddf 100644 --- a/react/features/lobby/components/native/styles.js +++ b/react/features/lobby/components/native/styles.js @@ -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: { diff --git a/react/features/mobile/external-api/functions.js b/react/features/mobile/external-api/functions.js index 2451cd8df..0c55ace99 100644 --- a/react/features/mobile/external-api/functions.js +++ b/react/features/mobile/external-api/functions.js @@ -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 }); diff --git a/react/features/mobile/navigation/components/RootNavigationContainer.js b/react/features/mobile/navigation/components/RootNavigationContainer.js index 9ce41b2d7..a1a10394a 100644 --- a/react/features/mobile/navigation/components/RootNavigationContainer.js +++ b/react/features/mobile/navigation/components/RootNavigationContainer.js @@ -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) => + options = { connectingScreenOptions } /> + + options = { conferenceNavigationContainerScreenOptions } /> ); @@ -94,7 +97,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) => */ function mapStateToProps(state: Object) { return { - isWelcomePageAvailable: isWelcomePageAppEnabled(state) + isWelcomePageAvailable: isWelcomePageEnabled(state) }; } diff --git a/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx b/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx index 94a514b21..ec97c38d8 100644 --- a/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx +++ b/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx @@ -31,6 +31,7 @@ import { gifsMenuOptions, inviteScreenOptions, liveStreamScreenOptions, + lobbyNavigationContainerScreenOptions, navigationContainerTheme, participantsScreenOptions, recordingScreenOptions, @@ -101,15 +102,11 @@ const ConferenceNavigationContainer = () => { + options = { recordingScreenOptions } /> + options = { liveStreamScreenOptions } /> { + options = { lobbyNavigationContainerScreenOptions } /> { options = {{ ...carmodeScreenOptions, title: t('carmode.labels.title') - }}/> + }} /> ); diff --git a/react/features/mobile/navigation/components/welcome/functions.js b/react/features/mobile/navigation/components/welcome/functions.js index 728de670b..da5b6f340 100644 --- a/react/features/mobile/navigation/components/welcome/functions.js +++ b/react/features/mobile/navigation/components/welcome/functions.js @@ -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. diff --git a/react/features/mobile/navigation/functions.js b/react/features/mobile/navigation/functions.js index ff9d78665..df002f447 100644 --- a/react/features/mobile/navigation/functions.js +++ b/react/features/mobile/navigation/functions.js @@ -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 ( + + ); + } + + return ( + + ); +} diff --git a/react/features/mobile/navigation/middleware.js b/react/features/mobile/navigation/middleware.js index 1e2cc17a6..7329346c5 100644 --- a/react/features/mobile/navigation/middleware.js +++ b/react/features/mobile/navigation/middleware.js @@ -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); diff --git a/react/features/mobile/navigation/rootNavigationContainerRef.js b/react/features/mobile/navigation/rootNavigationContainerRef.js index ba7cf277a..a2667092b 100644 --- a/react/features/mobile/navigation/rootNavigationContainerRef.js +++ b/react/features/mobile/navigation/rootNavigationContainerRef.js @@ -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); + } +} diff --git a/react/features/mobile/navigation/routes.js b/react/features/mobile/navigation/routes.js index 09a605097..7b772b20b 100644 --- a/react/features/mobile/navigation/routes.js +++ b/react/features/mobile/navigation/routes.js @@ -13,6 +13,7 @@ export const screen = { }, dialInSummary: 'Dial-In Info', connecting: 'Connecting', + preJoin: 'Pre-Join', conference: { root: 'Conference root', main: 'Conference', diff --git a/react/features/mobile/navigation/screenOptions.js b/react/features/mobile/navigation/screenOptions.js index 0494726b9..c6a0a330a 100644 --- a/react/features/mobile/navigation/screenOptions.js +++ b/react/features/mobile/navigation/screenOptions.js @@ -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 +}; diff --git a/react/features/prejoin/components/Prejoin.native.tsx b/react/features/prejoin/components/Prejoin.native.tsx new file mode 100644 index 000000000..fbe75debb --- /dev/null +++ b/react/features/prejoin/components/Prejoin.native.tsx @@ -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 ( + + ); + } + + return ( + + ); + }, []); + + useLayoutEffect(() => { + navigation.setOptions({ + headerLeft + }); + }, [ navigation ]); + + return ( + + + + + + + + + + { t('prejoin.joinMeeting') } + + + + + { t('prejoin.joinMeetingInLowBandwidthMode') } + + + + + + + + + + ); +}; + +export default Prejoin; diff --git a/react/features/prejoin/components/styles.js b/react/features/prejoin/components/styles.js new file mode 100644 index 000000000..496462b0d --- /dev/null +++ b/react/features/prejoin/components/styles.js @@ -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' + } +}; diff --git a/react/features/welcome/components/VideoSwitch.js b/react/features/welcome/components/VideoSwitch.js deleted file mode 100644 index 722547613..000000000 --- a/react/features/welcome/components/VideoSwitch.js +++ /dev/null @@ -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 { - /** - * 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 ( - - - - - { t('welcomepage.audioVideoSwitch.video') } - - - - - - - - { t('welcomepage.audioVideoSwitch.audio') } - - - - - ); - } - - _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)); diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index 92ae1f78d..5502f3987 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -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 } /> ), - // eslint-disable-next-line react/no-multi-comp - headerRight: () => - + headerTitle: t('welcomepage.headerTitle') }); navigation.addListener('focus', () => { diff --git a/react/features/welcome/components/native/settings/components/SettingsView.js b/react/features/welcome/components/native/settings/components/SettingsView.js index d70a60df3..5e653a6af 100644 --- a/react/features/welcome/components/native/settings/components/SettingsView.js +++ b/react/features/welcome/components/native/settings/components/SettingsView.js @@ -159,13 +159,15 @@ class SettingsView extends AbstractSettingsView { */ 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') }); } diff --git a/react/features/welcome/components/styles.js b/react/features/welcome/components/styles.js index 3d74310d7..c8b9bab6d 100644 --- a/react/features/welcome/components/styles.js +++ b/react/features/welcome/components/styles.js @@ -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 diff --git a/react/features/welcome/functions.js b/react/features/welcome/functions.js index b7ae5ef17..a68328d4e 100644 --- a/react/features/welcome/functions.js +++ b/react/features/welcome/functions.js @@ -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; } - - diff --git a/react/features/welcome/index.js b/react/features/welcome/index.js index 8b45add51..07635cbbc 100644 --- a/react/features/welcome/index.js +++ b/react/features/welcome/index.js @@ -1,2 +1 @@ export * from './components'; -export * from './functions';