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:
parent
d31eb3b248
commit
dbf7bf4750
|
@ -208,6 +208,9 @@
|
||||||
"selectADevice": "Select a device",
|
"selectADevice": "Select a device",
|
||||||
"testAudio": "Play a test sound"
|
"testAudio": "Play a test sound"
|
||||||
},
|
},
|
||||||
|
"dialIn": {
|
||||||
|
"screenTitle": "Dial-in summary"
|
||||||
|
},
|
||||||
"dialOut": {
|
"dialOut": {
|
||||||
"statusMessage": "is now {{status}}"
|
"statusMessage": "is now {{status}}"
|
||||||
},
|
},
|
||||||
|
@ -815,6 +818,7 @@
|
||||||
"initiated": "Call initiated",
|
"initiated": "Call initiated",
|
||||||
"joinAudioByPhone": "Join with phone audio",
|
"joinAudioByPhone": "Join with phone audio",
|
||||||
"joinMeeting": "Join meeting",
|
"joinMeeting": "Join meeting",
|
||||||
|
"joinMeetingInLowBandwidthMode": "Join in low bandwidth mode",
|
||||||
"joinWithoutAudio": "Join without audio",
|
"joinWithoutAudio": "Join without audio",
|
||||||
"keyboardShortcuts": "Enable Keyboard shortcuts",
|
"keyboardShortcuts": "Enable Keyboard shortcuts",
|
||||||
"linkCopied": "Link copied to clipboard",
|
"linkCopied": "Link copied to clipboard",
|
||||||
|
@ -949,6 +953,7 @@
|
||||||
"playSounds": "Play sound on",
|
"playSounds": "Play sound on",
|
||||||
"reactions": "Meeting reactions",
|
"reactions": "Meeting reactions",
|
||||||
"sameAsSystem": "Same as system ({{label}})",
|
"sameAsSystem": "Same as system ({{label}})",
|
||||||
|
"screenTitle": "Settings",
|
||||||
"selectAudioOutput": "Audio output",
|
"selectAudioOutput": "Audio output",
|
||||||
"selectCamera": "Camera",
|
"selectCamera": "Camera",
|
||||||
"selectMic": "Microphone",
|
"selectMic": "Microphone",
|
||||||
|
|
|
@ -19,7 +19,11 @@ import {
|
||||||
parseURIString,
|
parseURIString,
|
||||||
toURLString
|
toURLString
|
||||||
} from '../base/util';
|
} 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 { screen } from '../mobile/navigation/routes';
|
||||||
import { setFatalError } from '../overlay';
|
import { setFatalError } from '../overlay';
|
||||||
|
|
||||||
|
@ -128,7 +132,15 @@ export function appNavigate(uri: ?string) {
|
||||||
|
|
||||||
if (room) {
|
if (room) {
|
||||||
dispatch(createDesiredLocalTracks());
|
dispatch(createDesiredLocalTracks());
|
||||||
dispatch(connect());
|
|
||||||
|
if (isPrejoinPageEnabled(getState())) {
|
||||||
|
navigateRoot(screen.preJoin);
|
||||||
|
} else {
|
||||||
|
dispatch(connect());
|
||||||
|
navigateRoot(screen.conference.root);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
goBackToRoot(getState(), dispatch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { toState } from '../base/redux';
|
||||||
import { Conference } from '../conference';
|
import { Conference } from '../conference';
|
||||||
import { getDeepLinkingPage } from '../deep-linking';
|
import { getDeepLinkingPage } from '../deep-linking';
|
||||||
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
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
|
* Determines which route is to be rendered in order to depict a specific Redux
|
||||||
|
@ -72,7 +73,7 @@ function _getWebConferenceRoute(state) {
|
||||||
function _getWebWelcomePageRoute(state) {
|
function _getWebWelcomePageRoute(state) {
|
||||||
const route = _getEmptyRoute();
|
const route = _getEmptyRoute();
|
||||||
|
|
||||||
if (isWelcomePageUserEnabled(state)) {
|
if (isWelcomePageEnabled(state)) {
|
||||||
if (isSupportedBrowser()) {
|
if (isSupportedBrowser()) {
|
||||||
route.component = WelcomePage;
|
route.component = WelcomePage;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -160,6 +160,12 @@ export const OVERFLOW_MENU_ENABLED = 'overflow-menu.enabled';
|
||||||
*/
|
*/
|
||||||
export const PIP_ENABLED = 'pip.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.
|
* Flag indicating if raise hand feature should be enabled.
|
||||||
* Default: enabled.
|
* Default: enabled.
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { PureComponent } from 'react';
|
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 { WebView } from 'react-native-webview';
|
||||||
import { type Dispatch } from 'redux';
|
import { type Dispatch } from 'redux';
|
||||||
|
|
||||||
import { openDialog } from '../../../../base/dialog';
|
import { openDialog } from '../../../../base/dialog';
|
||||||
import { translate } from '../../../../base/i18n';
|
import { translate } from '../../../../base/i18n';
|
||||||
import { IconClose } 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 HeaderNavigationButton
|
|
||||||
from '../../../../mobile/navigation/components/HeaderNavigationButton';
|
|
||||||
import { getDialInfoPageURLForURIString } from '../../../functions';
|
import { getDialInfoPageURLForURIString } from '../../../functions';
|
||||||
|
|
||||||
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
|
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
|
||||||
import styles, { INDICATOR_COLOR } from './styles';
|
import styles, { INDICATOR_COLOR } from './styles';
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
dispatch: Dispatch<any>,
|
dispatch: Dispatch<any>,
|
||||||
|
@ -65,28 +63,9 @@ class DialInSummary extends PureComponent<Props> {
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { navigation, t } = this.props;
|
const { navigation, t } = this.props;
|
||||||
const onNavigationClose = () => {
|
|
||||||
navigation.goBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerLeft: () => {
|
headerTitle: t('dialIn.screenTitle')
|
||||||
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 } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,10 @@ export default {
|
||||||
|
|
||||||
buttonStylesBorderless: {
|
buttonStylesBorderless: {
|
||||||
iconStyle: {
|
iconStyle: {
|
||||||
backgroundColor: 'transparent',
|
|
||||||
color: BaseTheme.palette.icon01,
|
color: BaseTheme.palette.icon01,
|
||||||
fontSize: 24
|
fontSize: 24
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: 'transparent',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginHorizontal: BaseTheme.spacing[3],
|
marginHorizontal: BaseTheme.spacing[3],
|
||||||
|
@ -70,25 +68,25 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
largeVideoContainerWide: {
|
largeVideoContainerWide: {
|
||||||
position: 'absolute',
|
|
||||||
marginRight: 'auto',
|
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
marginRight: 'auto',
|
||||||
|
position: 'absolute',
|
||||||
width: '50%'
|
width: '50%'
|
||||||
},
|
},
|
||||||
|
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
minHeight: '50%'
|
minHeight: '50%'
|
||||||
},
|
},
|
||||||
|
|
||||||
contentContainerWide: {
|
contentContainerWide: {
|
||||||
|
height: '100%',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
left: '50%',
|
||||||
marginHorizontal: BaseTheme.spacing[6],
|
marginHorizontal: BaseTheme.spacing[6],
|
||||||
marginVertical: BaseTheme.spacing[3],
|
marginVertical: BaseTheme.spacing[3],
|
||||||
height: '100%',
|
|
||||||
left: '50%',
|
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: '50%'
|
width: '50%'
|
||||||
},
|
},
|
||||||
|
@ -124,7 +122,7 @@ export default {
|
||||||
|
|
||||||
formWrapper: {
|
formWrapper: {
|
||||||
alignSelf: 'stretch',
|
alignSelf: 'stretch',
|
||||||
marginTop: 45
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
|
|
||||||
field: {
|
field: {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
|
|
||||||
import { getAppProp } from '../../base/app';
|
import { getAppProp } from '../../base/app';
|
||||||
|
import { readyToClose } from '../external-api/actions';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a specific event to the native counterpart of the External API. Native
|
* 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
|
externalAPIScope
|
||||||
&& NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
|
&& NativeModules.ExternalAPI.sendEvent(name, data, externalAPIScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounced sending of `readyToClose`.
|
||||||
|
*/
|
||||||
|
export const _sendReadyToClose = debounce(dispatch => {
|
||||||
|
dispatch(readyToClose());
|
||||||
|
}, 2500, { leading: true });
|
||||||
|
|
|
@ -4,20 +4,25 @@ import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { DialInSummary } from '../../../invite';
|
import { DialInSummary } from '../../../invite';
|
||||||
|
import Prejoin from '../../../prejoin/components/Prejoin.native';
|
||||||
|
import { isWelcomePageEnabled } from '../../../welcome/functions';
|
||||||
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
|
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
|
||||||
import { rootNavigationRef } from '../rootNavigationContainerRef';
|
import { rootNavigationRef } from '../rootNavigationContainerRef';
|
||||||
import { screen } from '../routes';
|
import { screen } from '../routes';
|
||||||
import {
|
import {
|
||||||
|
conferenceNavigationContainerScreenOptions,
|
||||||
|
connectingScreenOptions,
|
||||||
dialInSummaryScreenOptions,
|
dialInSummaryScreenOptions,
|
||||||
drawerNavigatorScreenOptions,
|
drawerNavigatorScreenOptions,
|
||||||
navigationContainerTheme
|
navigationContainerTheme,
|
||||||
|
preJoinScreenOptions
|
||||||
} from '../screenOptions';
|
} from '../screenOptions';
|
||||||
|
|
||||||
import ConnectingPage from './ConnectingPage';
|
import ConnectingPage from './ConnectingPage';
|
||||||
import ConferenceNavigationContainer
|
import ConferenceNavigationContainer
|
||||||
from './conference/components/ConferenceNavigationContainer';
|
from './conference/components/ConferenceNavigationContainer';
|
||||||
import WelcomePageNavigationContainer from './welcome/components/WelcomePageNavigationContainer';
|
import WelcomePageNavigationContainer
|
||||||
import { isWelcomePageAppEnabled } from './welcome/functions';
|
from './welcome/components/WelcomePageNavigationContainer';
|
||||||
|
|
||||||
const RootStack = createNativeStackNavigator();
|
const RootStack = createNativeStackNavigator();
|
||||||
|
|
||||||
|
@ -70,17 +75,15 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
|
||||||
<RootStack.Screen
|
<RootStack.Screen
|
||||||
component = { ConnectingPage }
|
component = { ConnectingPage }
|
||||||
name = { screen.connecting }
|
name = { screen.connecting }
|
||||||
options = {{
|
options = { connectingScreenOptions } />
|
||||||
gestureEnabled: false,
|
<RootStack.Screen
|
||||||
headerShown: false
|
component = { Prejoin }
|
||||||
}} />
|
name = { screen.preJoin }
|
||||||
|
options = { preJoinScreenOptions } />
|
||||||
<RootStack.Screen
|
<RootStack.Screen
|
||||||
component = { ConferenceNavigationContainer }
|
component = { ConferenceNavigationContainer }
|
||||||
name = { screen.conference.root }
|
name = { screen.conference.root }
|
||||||
options = {{
|
options = { conferenceNavigationContainerScreenOptions } />
|
||||||
gestureEnabled: false,
|
|
||||||
headerShown: false
|
|
||||||
}} />
|
|
||||||
</RootStack.Navigator>
|
</RootStack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
|
@ -94,7 +97,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
|
||||||
*/
|
*/
|
||||||
function mapStateToProps(state: Object) {
|
function mapStateToProps(state: Object) {
|
||||||
return {
|
return {
|
||||||
isWelcomePageAvailable: isWelcomePageAppEnabled(state)
|
isWelcomePageAvailable: isWelcomePageEnabled(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
gifsMenuOptions,
|
gifsMenuOptions,
|
||||||
inviteScreenOptions,
|
inviteScreenOptions,
|
||||||
liveStreamScreenOptions,
|
liveStreamScreenOptions,
|
||||||
|
lobbyNavigationContainerScreenOptions,
|
||||||
navigationContainerTheme,
|
navigationContainerTheme,
|
||||||
participantsScreenOptions,
|
participantsScreenOptions,
|
||||||
recordingScreenOptions,
|
recordingScreenOptions,
|
||||||
|
@ -101,15 +102,11 @@ const ConferenceNavigationContainer = () => {
|
||||||
<ConferenceStack.Screen
|
<ConferenceStack.Screen
|
||||||
component = { StartRecordingDialog }
|
component = { StartRecordingDialog }
|
||||||
name = { screen.conference.recording }
|
name = { screen.conference.recording }
|
||||||
options = {{
|
options = { recordingScreenOptions } />
|
||||||
...recordingScreenOptions
|
|
||||||
}} />
|
|
||||||
<ConferenceStack.Screen
|
<ConferenceStack.Screen
|
||||||
component = { StartLiveStreamDialog }
|
component = { StartLiveStreamDialog }
|
||||||
name = { screen.conference.liveStream }
|
name = { screen.conference.liveStream }
|
||||||
options = {{
|
options = { liveStreamScreenOptions } />
|
||||||
...liveStreamScreenOptions
|
|
||||||
}} />
|
|
||||||
<ConferenceStack.Screen
|
<ConferenceStack.Screen
|
||||||
component = { SpeakerStats }
|
component = { SpeakerStats }
|
||||||
name = { screen.conference.speakerStats }
|
name = { screen.conference.speakerStats }
|
||||||
|
@ -134,10 +131,7 @@ const ConferenceNavigationContainer = () => {
|
||||||
<ConferenceStack.Screen
|
<ConferenceStack.Screen
|
||||||
component = { LobbyNavigationContainer }
|
component = { LobbyNavigationContainer }
|
||||||
name = { screen.lobby.root }
|
name = { screen.lobby.root }
|
||||||
options = {{
|
options = { lobbyNavigationContainerScreenOptions } />
|
||||||
gestureEnabled: false,
|
|
||||||
headerShown: false
|
|
||||||
}} />
|
|
||||||
<ConferenceStack.Screen
|
<ConferenceStack.Screen
|
||||||
component = { AddPeopleDialog }
|
component = { AddPeopleDialog }
|
||||||
name = { screen.conference.invite }
|
name = { screen.conference.invite }
|
||||||
|
@ -158,7 +152,7 @@ const ConferenceNavigationContainer = () => {
|
||||||
options = {{
|
options = {{
|
||||||
...carmodeScreenOptions,
|
...carmodeScreenOptions,
|
||||||
title: t('carmode.labels.title')
|
title: t('carmode.labels.title')
|
||||||
}}/>
|
}} />
|
||||||
</ConferenceStack.Navigator>
|
</ConferenceStack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,25 +2,11 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getFeatureFlag, WELCOME_PAGE_ENABLED } from '../../../../base/flags';
|
|
||||||
import { IconArrowBack } from '../../../../base/icons';
|
import { IconArrowBack } from '../../../../base/icons';
|
||||||
import HeaderNavigationButton
|
import HeaderNavigationButton
|
||||||
from '../HeaderNavigationButton';
|
from '../HeaderNavigationButton';
|
||||||
import { navigationStyles } from '../styles';
|
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.
|
* Render header arrow back button for navigation.
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Platform } from 'react-native';
|
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 { IconClose } from '../../base/icons';
|
||||||
|
import { cancelKnocking } from '../../lobby/actions.native';
|
||||||
|
|
||||||
import HeaderNavigationButton from './components/HeaderNavigationButton';
|
import HeaderNavigationButton from './components/HeaderNavigationButton';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close icon/text button based on platform.
|
* Close icon/text button based on platform.
|
||||||
*
|
*
|
||||||
|
@ -29,3 +37,44 @@ export function screenHeaderCloseButton(goBack: Function) {
|
||||||
src = { IconClose } />
|
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 } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
import debounce from 'lodash/debounce';
|
import { appNavigate } from '../../app/actions';
|
||||||
|
import { CONFERENCE_FAILED } from '../../base/conference/actionTypes';
|
||||||
import { CONFERENCE_FAILED, SET_ROOM } from '../../base/conference/actionTypes';
|
|
||||||
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
|
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
|
||||||
import { MiddlewareRegistry } from '../../base/redux';
|
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 => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_ROOM:
|
|
||||||
return _setRoom(store, next, action);
|
|
||||||
|
|
||||||
case CONFERENCE_FAILED:
|
case CONFERENCE_FAILED:
|
||||||
return _conferenceFailed(store, next, action);
|
return _conferenceFailed(store, next, action);
|
||||||
|
@ -23,47 +14,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
return 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
|
* Function to handle the conference failed event and navigate the user to the lobby screen
|
||||||
* based on the failure reason.
|
* based on the failure reason.
|
||||||
|
@ -73,20 +23,13 @@ function _setRoom({ dispatch, getState }, next, action) {
|
||||||
* @param {Object} action - The Redux action.
|
* @param {Object} action - The Redux action.
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
function _conferenceFailed({ dispatch, getState }, next, action) {
|
function _conferenceFailed({ dispatch }, next, action) {
|
||||||
const state = getState();
|
|
||||||
const isWelcomePageEnabled = isWelcomePageAppEnabled(state);
|
|
||||||
const { error } = action;
|
const { error } = action;
|
||||||
|
|
||||||
// We need to cover the case where knocking participant
|
// We need to cover the case where knocking participant
|
||||||
// is rejected from entering the conference
|
// is rejected from entering the conference
|
||||||
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||||
if (isWelcomePageEnabled) {
|
dispatch(appNavigate(undefined));
|
||||||
navigateRoot(screen.root);
|
|
||||||
} else {
|
|
||||||
// For JitsiSDK, WelcomePage is not available
|
|
||||||
_sendReadyToClose(dispatch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from 'react';
|
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();
|
export const rootNavigationRef = React.createRef();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User defined navigation action included inside the reference to the container.
|
* User defined navigation action included inside the reference to the container.
|
||||||
*
|
*
|
||||||
|
@ -13,7 +17,32 @@ export const rootNavigationRef = React.createRef();
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function navigateRoot(name: string, params: Object) {
|
export function navigateRoot(name: string, params: Object) {
|
||||||
// $FlowExpectedError
|
|
||||||
return rootNavigationRef.current?.navigate(name, params);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const screen = {
|
||||||
},
|
},
|
||||||
dialInSummary: 'Dial-In Info',
|
dialInSummary: 'Dial-In Info',
|
||||||
connecting: 'Connecting',
|
connecting: 'Connecting',
|
||||||
|
preJoin: 'Pre-Join',
|
||||||
conference: {
|
conference: {
|
||||||
root: 'Conference root',
|
root: 'Conference root',
|
||||||
main: 'Conference',
|
main: 'Conference',
|
||||||
|
|
|
@ -12,7 +12,8 @@ import BaseTheme from '../../base/ui/components/BaseTheme.native';
|
||||||
|
|
||||||
import { goBack } from './components/conference/ConferenceNavigationContainerRef';
|
import { goBack } from './components/conference/ConferenceNavigationContainerRef';
|
||||||
import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
|
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: {
|
headerStyle: {
|
||||||
backgroundColor: BaseTheme.palette.screen01Header
|
backgroundColor: BaseTheme.palette.screen01Header
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line no-empty-function
|
headerTitleStyle: {
|
||||||
headerTitle: () => {}
|
color: BaseTheme.palette.text01
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,7 +166,6 @@ export const chatTabBarOptions = {
|
||||||
* Screen options for presentation type modals.
|
* Screen options for presentation type modals.
|
||||||
*/
|
*/
|
||||||
export const presentationScreenOptions = {
|
export const presentationScreenOptions = {
|
||||||
animation: 'slide_from_right',
|
|
||||||
headerBackTitleVisible: false,
|
headerBackTitleVisible: false,
|
||||||
headerLeft: () => screenHeaderCloseButton(goBack),
|
headerLeft: () => screenHeaderCloseButton(goBack),
|
||||||
headerStatusBarHeight: 0,
|
headerStatusBarHeight: 0,
|
||||||
|
@ -195,7 +196,8 @@ export const chatScreenOptions = presentationScreenOptions;
|
||||||
*/
|
*/
|
||||||
export const dialInSummaryScreenOptions = {
|
export const dialInSummaryScreenOptions = {
|
||||||
...presentationScreenOptions,
|
...presentationScreenOptions,
|
||||||
headerLeft: undefined
|
animation: 'slide_from_bottom',
|
||||||
|
headerLeft: () => screenHeaderCloseButton(goBackToWelcomeScreen)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -231,7 +233,10 @@ export const liveStreamScreenOptions = presentationScreenOptions;
|
||||||
/**
|
/**
|
||||||
* Screen options for lobby modal.
|
* Screen options for lobby modal.
|
||||||
*/
|
*/
|
||||||
export const lobbyScreenOptions = presentationScreenOptions;
|
export const lobbyScreenOptions = {
|
||||||
|
...presentationScreenOptions,
|
||||||
|
headerLeft: () => lobbyScreenHeaderCloseButton()
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen options for lobby chat modal.
|
* Screen options for lobby chat modal.
|
||||||
|
@ -269,3 +274,38 @@ export const sharedDocumentScreenOptions = {
|
||||||
android: 'all'
|
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
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
|
@ -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'
|
||||||
|
}
|
||||||
|
};
|
|
@ -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));
|
|
|
@ -27,7 +27,6 @@ import {
|
||||||
_mapStateToProps as _abstractMapStateToProps,
|
_mapStateToProps as _abstractMapStateToProps,
|
||||||
type Props as AbstractProps
|
type Props as AbstractProps
|
||||||
} from './AbstractWelcomePage';
|
} from './AbstractWelcomePage';
|
||||||
import VideoSwitch from './VideoSwitch';
|
|
||||||
import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
|
import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,7 +100,8 @@ class WelcomePage extends AbstractWelcomePage<*> {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
_headerStyles,
|
_headerStyles,
|
||||||
navigation
|
navigation,
|
||||||
|
t
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
|
@ -118,9 +118,7 @@ class WelcomePage extends AbstractWelcomePage<*> {
|
||||||
style = { _headerStyles.headerButtonIcon } />
|
style = { _headerStyles.headerButtonIcon } />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
),
|
),
|
||||||
// eslint-disable-next-line react/no-multi-comp
|
headerTitle: t('welcomepage.headerTitle')
|
||||||
headerRight: () =>
|
|
||||||
<VideoSwitch />
|
|
||||||
});
|
});
|
||||||
|
|
||||||
navigation.addListener('focus', () => {
|
navigation.addListener('focus', () => {
|
||||||
|
|
|
@ -159,13 +159,15 @@ class SettingsView extends AbstractSettingsView<Props, State> {
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
navigation
|
navigation,
|
||||||
|
t
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerLeft: () =>
|
headerLeft: () =>
|
||||||
renderArrowBackButton(() =>
|
renderArrowBackButton(() =>
|
||||||
navigation.navigate(screen.welcome.main))
|
navigation.navigate(screen.welcome.main)),
|
||||||
|
headerTitle: t('settings.screenTitle')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,22 +26,6 @@ const TEXT_COLOR = BaseTheme.palette.text01;
|
||||||
*/
|
*/
|
||||||
export default {
|
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: {
|
blankPageText: {
|
||||||
color: TEXT_COLOR,
|
color: TEXT_COLOR,
|
||||||
fontSize: 18
|
fontSize: 18
|
||||||
|
|
|
@ -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
|
* Determines whether the {@code WelcomePage} is enabled.
|
||||||
* herself or through her deployment config(uration). Not to be confused with
|
|
||||||
* {@link isWelcomePageAppEnabled}.
|
|
||||||
*
|
*
|
||||||
* @param {Function|Object} stateful - The redux state or {@link getState}
|
* @param {Function|Object} stateful - The redux state or {@link getState}
|
||||||
* function.
|
* 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}.
|
* {@code true}; otherwise, {@code false}.
|
||||||
*/
|
*/
|
||||||
export function isWelcomePageUserEnabled(stateful: Function | Object) {
|
export function isWelcomePageEnabled(stateful: Function | Object) {
|
||||||
return (
|
if (navigator.product === 'ReactNative') {
|
||||||
typeof APP === 'undefined'
|
return getFeatureFlag(stateful, WELCOME_PAGE_ENABLED, false);
|
||||||
? true
|
}
|
||||||
: toState(stateful)['features/base/config'].enableWelcomePage);
|
|
||||||
|
return toState(stateful)['features/base/config'].enableWelcomePage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './functions';
|
|
||||||
|
|
Loading…
Reference in New Issue