feat(rn,welcome) React Navigation drawer

This commit is contained in:
Calinteodor 2021-11-11 16:32:56 +02:00 committed by GitHub
parent f5cdd5fca1
commit 4e2fea1e12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1137 additions and 1100 deletions

View File

@ -1140,6 +1140,12 @@
"button": "Invite others",
"youAreAlone": "You are the only one in the meeting"
},
"termsView": {
"header": "Terms"
},
"privacyView": {
"header": "Privacy"
},
"helpView": {
"header": "Help center"
},

28
package-lock.json generated
View File

@ -40,6 +40,7 @@
"@react-native-community/netinfo": "4.1.5",
"@react-native-community/slider": "3.0.3",
"@react-native-masked-view/masked-view": "0.2.6",
"@react-navigation/drawer": "5.12.9",
"@react-navigation/material-top-tabs": "5.3.19",
"@react-navigation/native": "5.9.8",
"@react-navigation/stack": "5.14.9",
@ -4255,6 +4256,24 @@
"node": ">=4"
}
},
"node_modules/@react-navigation/drawer": {
"version": "5.12.9",
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-5.12.9.tgz",
"integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==",
"dependencies": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",
"react": "*",
"react-native": "*",
"react-native-gesture-handler": ">= 1.0.0",
"react-native-reanimated": ">= 1.0.0",
"react-native-safe-area-context": ">= 0.6.0",
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
}
},
"node_modules/@react-navigation/material-top-tabs": {
"version": "5.3.19",
"resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-5.3.19.tgz",
@ -23379,6 +23398,15 @@
}
}
},
"@react-navigation/drawer": {
"version": "5.12.9",
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-5.12.9.tgz",
"integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==",
"requires": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
}
},
"@react-navigation/material-top-tabs": {
"version": "5.3.19",
"resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-5.3.19.tgz",

View File

@ -45,6 +45,7 @@
"@react-native-community/netinfo": "4.1.5",
"@react-native-community/slider": "3.0.3",
"@react-native-masked-view/masked-view": "0.2.6",
"@react-navigation/drawer": "5.12.9",
"@react-navigation/material-top-tabs": "5.3.19",
"@react-navigation/native": "5.9.8",
"@react-navigation/stack": "5.14.9",

View File

@ -2,8 +2,7 @@
import { isRoomValid } from '../base/conference';
import { toState } from '../base/redux';
import { ConferenceNavigationContainer } from '../conference';
import { isWelcomePageAppEnabled } from '../welcome';
import { BlankPage, WelcomePage } from '../welcome/components';
import RootNavigationContainer from '../welcome/components/RootNavigationContainer';
/**
* Determines which route is to be rendered in order to depict a specific Redux
@ -26,27 +25,17 @@ export function _getRouteToRender(stateful) {
* @returns {Promise}
*/
function _getMobileRoute(state) {
const route = _getEmptyRoute();
const route = {
component: null,
href: undefined
};
if (isRoomValid(state['features/base/conference'].room)) {
route.component = ConferenceNavigationContainer;
} else if (isWelcomePageAppEnabled(state)) {
route.component = WelcomePage;
} else {
route.component = BlankPage;
route.component = RootNavigationContainer;
}
return Promise.resolve(route);
}
/**
* Returns the default {@code Route}.
*
* @returns {Object}
*/
function _getEmptyRoute() {
return {
component: BlankPage,
href: undefined
};
}

View File

@ -7,9 +7,7 @@ import { toState } from '../base/redux';
import { Conference } from '../conference';
import { getDeepLinkingPage } from '../deep-linking';
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
import { isWelcomePageUserEnabled } from '../welcome';
import { BlankPage, WelcomePage } from '../welcome/components';
import { BlankPage, isWelcomePageUserEnabled, WelcomePage } from '../welcome';
/**
* Determines which route is to be rendered in order to depict a specific Redux

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px">
<path d="M 15 2 A 1 1 0 0 0 14.300781 2.2851562 L 3.3925781 11.207031 A 1 1 0 0 0 3.3554688 11.236328 L 3.3183594 11.267578 L 3.3183594 11.269531 A 1 1 0 0 0 3 12 A 1 1 0 0 0 4 13 L 5 13 L 5 24 C 5 25.105 5.895 26 7 26 L 23 26 C 24.105 26 25 25.105 25 24 L 25 13 L 26 13 A 1 1 0 0 0 27 12 A 1 1 0 0 0 26.681641 11.267578 L 26.666016 11.255859 A 1 1 0 0 0 26.597656 11.199219 L 25 9.8925781 L 25 6 C 25 5.448 24.552 5 24 5 L 23 5 C 22.448 5 22 5.448 22 6 L 22 7.4394531 L 15.677734 2.2675781 A 1 1 0 0 0 15 2 z M 18 15 L 22 15 L 22 23 L 18 23 L 18 15 z"/>
</svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px">
<path d="M 15 2 A 1 1 0 0 0 14.300781 2.2851562 L 3.3925781 11.207031 A 1 1 0 0 0 3.3554688 11.236328 L 3.3183594 11.267578 L 3.3183594 11.269531 A 1 1 0 0 0 3 12 A 1 1 0 0 0 4 13 L 5 13 L 5 24 C 5 25.105 5.895 26 7 26 L 23 26 C 24.105 26 25 25.105 25 24 L 25 13 L 26 13 A 1 1 0 0 0 27 12 A 1 1 0 0 0 26.681641 11.267578 L 26.666016 11.255859 A 1 1 0 0 0 26.597656 11.199219 L 25 9.8925781 L 25 6 C 25 5.448 24.552 5 24 5 L 23 5 C 22.448 5 22 5.448 22 6 L 22 7.4394531 L 15.677734 2.2675781 A 1 1 0 0 0 15 2 z M 18 15 L 22 15 L 22 23 L 18 23 L 18 15 z"/>
</svg>

After

Width:  |  Height:  |  Size: 692 B

View File

@ -60,6 +60,7 @@ export { default as IconFullScreen } from './full-screen.svg';
export { default as IconGoogle } from './google.svg';
export { default as IconHangup } from './hangup.svg';
export { default as IconHelp } from './help.svg';
export { default as IconHome } from './home.svg';
export { default as IconHorizontalPoints } from './horizontal-points.svg';
export { default as IconInfo } from './info.svg';
export { default as IconInviteMore } from './user-plus.svg';

View File

@ -1,188 +0,0 @@
// @flow
import React, { PureComponent } from 'react';
import { KeyboardAvoidingView, Platform, SafeAreaView } from 'react-native';
import { ColorSchemeRegistry } from '../../color-scheme';
import { HeaderWithNavigation, SlidingView } from '../../react';
import { connect } from '../../redux';
import { StyleType } from '../../styles';
import { setActiveModalId } from '../actions';
import styles from './styles';
type Props = {
/**
* The color schemed style of the common header component.
*/
_headerStyles: StyleType,
/**
* True if the modal should be shown, false otherwise.
*/
_show: boolean,
/**
* The color schemed style of the modal.
*/
_styles: StyleType,
/**
* The children component(s) of the Modal, to be rendered.
*/
children: React$Node,
/**
* The Redux Dispatch function.
*/
dispatch: Function,
/**
* Optional function that renders a footer component, if needed.
*/
footerComponent?: Function,
/**
* Props to be passed over to the header.
*
* See {@code HeaderWithNavigation} for more details.
*/
headerProps: Object,
/**
* True if the header with navigation should be hidden, false otherwise.
*/
hideHeaderWithNavigation?: boolean,
/**
* The ID of the modal that is being rendered. This is used to show/hide the modal.
*/
modalId: string,
/**
* Callback to be invoked when the modal closes.
*/
onClose?: Function,
/**
* The position from where the modal should be opened. This is derived from the
* props of the {@code SlidingView} with the same name.
*/
position?: string,
/**
* Additional style to be appended to the View containing the content of the modal.
*/
style?: StyleType
};
/**
* Implements a custom Jitsi Modal that doesn't use the built in native
* Modal component of React Native.
*/
class JitsiModal extends PureComponent<Props> {
static defaultProps = {
position: 'bottom',
hideHeaderWithNavigation: false
};
/**
* Instantiates a new component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onRequestClose = this._onRequestClose.bind(this);
}
/**
* Implements {@code PureComponent#render}.
*
* @inheritdoc
*/
render() {
const {
_headerStyles,
_show,
_styles,
children,
footerComponent,
headerProps,
position,
hideHeaderWithNavigation,
style
} = this.props;
return (
<SlidingView
onHide = { this._onRequestClose }
position = { position }
show = { _show }>
<KeyboardAvoidingView
behavior =
{
Platform.OS === 'ios'
? 'padding' : 'height'
}
enabled = { true }
style = { [
_headerStyles.page,
_styles.page,
style
] }>
<HeaderWithNavigation
{ ...headerProps }
hideHeaderWithNavigation = { hideHeaderWithNavigation }
onPressBack = { this._onRequestClose } />
<SafeAreaView style = { styles.safeArea }>
{ children }
</SafeAreaView>
{ footerComponent && footerComponent() }
</KeyboardAvoidingView>
</SlidingView>
);
}
_onRequestClose: () => boolean;
/**
* Callback to be invoked when the SlidingView requests closing.
*
* @returns {boolean}
*/
_onRequestClose() {
const { _show, dispatch, onClose } = this.props;
let shouldCloseModal = true;
if (_show) {
if (typeof onClose === 'function') {
shouldCloseModal = onClose();
}
shouldCloseModal && dispatch(setActiveModalId());
return shouldCloseModal;
}
return false;
}
}
/**
* Maps part of the Redix state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps): $Shape<Props> {
return {
_headerStyles: ColorSchemeRegistry.get(state, 'Header'),
_show: state['features/base/modal'].activeModalId === ownProps.modalId,
_styles: ColorSchemeRegistry.get(state, 'Modal')
};
}
export default connect(_mapStateToProps)(JitsiModal);

View File

@ -30,7 +30,7 @@ type Props = {
/**
* Is the screen rendering a tab navigator?
*/
hasTabNavigator: boolean,
hasTabNavigator?: boolean,
/**
* Additional style to be appended to the KeyboardAvoidingView containing the content of the modal.
@ -42,7 +42,7 @@ const JitsiScreen = ({
contentContainerStyle,
children,
footerComponent,
hasTabNavigator,
hasTabNavigator = false,
style
}: Props) => (
<View

View File

@ -0,0 +1,28 @@
// @flow
import React from 'react';
import WebView from 'react-native-webview';
import JitsiScreen from './JitsiScreen';
type Props = {
/**
* The URL to display.
*/
source: string,
/**
* The component's external style.
*/
style: Object
}
const JitsiScreenWebView = ({ source, style }: Props) => (
<JitsiScreen
style = { style }>
<WebView source = {{ uri: source }} />
</JitsiScreen>
);
export default JitsiScreenWebView;

View File

@ -0,0 +1,79 @@
// @flow
import React, { useCallback } from 'react';
import { StatusBar } from 'react-native';
import { ColorSchemeRegistry } from '../../color-scheme';
import { connect } from '../../redux';
import { isDarkColor } from '../../styles';
// Register style
import '../../react/components/native/headerstyles';
/**
* Constants for the (currently) supported statusbar colors.
*/
const STATUSBAR_DARK = 'dark-content';
const STATUSBAR_LIGHT = 'light-content';
type Props = {
/**
* The color schemed style of the component.
*/
_styles: Object
}
const JitsiStatusBar = ({ _styles }: Props) => {
const getStatusBarContentColor = useCallback(() => {
const { statusBarContent } = _styles;
if (statusBarContent) {
// We have the possibility to define the statusbar color in the
// color scheme feature, but since mobile devices (at the moment)
// only support two colors (light and dark) we need to normalize
// the value.
if (isDarkColor(statusBarContent)) {
return STATUSBAR_DARK;
}
return STATUSBAR_LIGHT;
}
// The statusbar color is not defined, so we need to base our choice
// on the header colors
const { statusBar, screenHeader } = _styles;
if (isDarkColor(statusBar || screenHeader.backgroundColor)) {
return STATUSBAR_LIGHT;
}
return STATUSBAR_DARK;
}, [ _styles ]);
return (
<StatusBar
backgroundColor = { _styles.statusBar }
barStyle = { getStatusBarContentColor() }
translucent = { false } />
);
};
/**
* Maps part of the Redux state to the props of the component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _styles: Object
* }}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Header')
};
}
export default connect(_mapStateToProps)(JitsiStatusBar);

View File

@ -1,34 +1,7 @@
// @flow
import { useEffect, useState } from 'react';
import { Keyboard } from 'react-native';
import { toState } from '../../redux';
export const useKeyboardHeight = () => {
const [ keyboardHeight, setKeyboardHeight ] = useState(0);
const onKeyboardDidShow = e => {
setKeyboardHeight(e.endCoordinates.height);
};
const onKeyboardDidHide = () => {
setKeyboardHeight(0);
};
useEffect(() => {
const keyboardShow = Keyboard.addListener('keyboardDidShow', onKeyboardDidShow);
const keyboardHide = Keyboard.addListener('keyboardDidHide', onKeyboardDidHide);
return () => {
keyboardShow.remove();
keyboardHide.remove();
};
}, []);
return keyboardHeight;
};
/**
*
* Returns the client width.
@ -39,7 +12,7 @@ export const useKeyboardHeight = () => {
* @returns {number}.
*/
export function getClientWidth(stateful: Object) {
const state = toState(stateful['features/base/responsive-ui']);
const state = toState(stateful)['features/base/responsive-ui'];
return state.clientWidth;
}
@ -54,7 +27,7 @@ export function getClientWidth(stateful: Object) {
* @returns {number}.
*/
export function getClientHeight(stateful: Object) {
const state = toState(stateful['features/base/responsive-ui']);
const state = toState(stateful)['features/base/responsive-ui'];
return state.clientHeight;
}

View File

@ -1,3 +0,0 @@
// @flow
export { default as JitsiModal } from './JitsiModal';

View File

@ -1,6 +0,0 @@
// @flow
import { Component } from 'react';
export const JitsiModal = Component;

View File

@ -2,4 +2,3 @@
export * from './actions';
export * from './actionTypes';
export * from './components';

View File

@ -1,122 +0,0 @@
// @flow
import React, { PureComponent, type Node } from 'react';
import { SafeAreaView, StatusBar, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { connect } from '../../../redux';
import { isDarkColor } from '../../../styles';
// Register style
import './headerstyles';
/**
* Constanst for the (currently) supported statusbar colors.
*/
const STATUSBAR_DARK = 'dark-content';
const STATUSBAR_LIGHT = 'light-content';
/**
* The type of the React {@code Component} props of {@link Header}.
*/
type Props = {
/**
* Children component(s).
*/
children: Node,
/**
* The component's external style.
*/
style: Object,
/**
* The color schemed style of the component.
*/
_styles: Object
}
/**
* A generic screen header component.
*/
class Header extends PureComponent<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { _styles } = this.props;
return (
<View style = { _styles.headerOverlay }>
<StatusBar
backgroundColor = { _styles.statusBar }
barStyle = { this._getStatusBarContentColor() }
translucent = { false } />
<SafeAreaView>
<View
style = { [
_styles.screenHeader,
this.props.style
] }>
{
this.props.children
}
</View>
</SafeAreaView>
</View>
);
}
/**
* Calculates the color of the statusbar content (light or dark) based on
* certain criteria.
*
* @returns {string}
*/
_getStatusBarContentColor() {
const { _styles } = this.props;
const { statusBarContent } = _styles;
if (statusBarContent) {
// We have the possibility to define the statusbar color in the
// color scheme feature, but since mobile devices (at the moment)
// only support two colors (light and dark) we need to normalize
// the value.
if (isDarkColor(statusBarContent)) {
return STATUSBAR_DARK;
}
return STATUSBAR_LIGHT;
}
// The statusbar color is not defined, so we need to base our choice
// on the header colors
const { statusBar, screenHeader } = _styles;
if (isDarkColor(statusBar || screenHeader.backgroundColor)) {
return STATUSBAR_LIGHT;
}
return STATUSBAR_DARK;
}
}
/**
* Maps part of the Redux state to the props of the component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _styles: Object
* }}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Header')
};
}
export default connect(_mapStateToProps)(Header);

View File

@ -1,72 +0,0 @@
// @flow
import React, { Component } from 'react';
import { translate } from '../../../i18n';
import BackButton from './BackButton';
import ForwardButton from './ForwardButton';
import Header from './Header';
import HeaderLabel from './HeaderLabel';
type Props = {
/**
* Boolean to set the forward button disabled.
*/
forwardDisabled: boolean,
/**
* The i18n key of the the forward button label.
*/
forwardLabelKey: ?string,
/**
* The i18n key of the header label (title).
*/
headerLabelKey: ?string,
/**
* True if the header with navigation should be hidden, false otherwise.
*/
hideHeaderWithNavigation?: boolean,
/**
* Callback to be invoked on pressing the back button.
*/
onPressBack: ?Function,
/**
* Callback to be invoked on pressing the forward button.
*/
onPressForward: ?Function,
}
/**
* Implements a header with the standard navigation content.
*/
class HeaderWithNavigation extends Component<Props> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
return (
!hideHeaderWithNavigation
&& <Header>
{ onPressBack && <BackButton onPress = { onPressBack } /> }
<HeaderLabel labelKey = { this.props.headerLabelKey } />
{ onPressForward && <ForwardButton
disabled = { this.props.forwardDisabled }
labelKey = { this.props.forwardLabelKey }
onPress = { onPressForward } /> }
</Header>
);
}
}
export default translate(HeaderWithNavigation);

View File

@ -6,9 +6,7 @@ export { default as BaseIndicator } from './BaseIndicator';
export { default as Button } from './Button';
export { default as Container } from './Container';
export { default as ForwardButton } from './ForwardButton';
export { default as Header } from './Header';
export { default as HeaderLabel } from './HeaderLabel';
export { default as HeaderWithNavigation } from './HeaderWithNavigation';
export { default as Image } from './Image';
export { default as Link } from './Link';
export { default as Linkify } from './Linkify';

View File

@ -18,6 +18,7 @@ export const colors = {
primary08: '#99BBF3',
primary09: '#CCDDF9',
primary10: '#17A0DB',
primary11: '#1081B2',
surface00: '#111111',
surface01: '#040404',
@ -32,12 +33,14 @@ export const colors = {
surface10: '#E0E0E0',
surface11: '#FFF',
surface12: '#AAAAAA',
surface13: '#495258',
success04: '#189B55',
success05: '#1EC26A',
warning05: '#F8AE1A',
warning06: '#ED9E1B'
warning06: '#ED9E1B',
warning07: '#D77976'
};
// Mapping between the token used and the color
@ -51,13 +54,18 @@ export const colorMap = {
ui03: 'surface04',
ui04: 'surface05',
ui05: 'surface06',
ui12: 'surface11',
// Primary buttons
action01: 'primary05',
action04: 'primary11',
// Screen header
screen01Header: 'primary10',
// Status bar
status01Bar: 'primary11',
// Hover state for primary buttons
action01Hover: 'primary06',
@ -115,6 +123,9 @@ export const colorMap = {
// Disabled state for danger buttons
actionDangerDisabled: 'error03',
// Underlay color for buttons
underlay01: 'surface13',
// Bottom sheet background
bottomSheet: 'surface00',
@ -130,6 +141,9 @@ export const colorMap = {
// Text for bottom sheet items
text04: 'surface12',
// Text for drawer menu displayed name
text05: 'surface06',
// error messages
textError: 'error06',
@ -216,7 +230,10 @@ export const colorMap = {
warning01: 'warning05',
// Color for indicating a raised hand
warning02: 'warning06'
warning02: 'warning06',
// Color for insecure room
warning03: 'warning07'
};

View File

@ -6,7 +6,6 @@ import React, { useEffect } from 'react';
import { translate } from '../../../base/i18n';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { connect } from '../../../base/redux';
import { screen } from '../../../conference/components/native/routes';
import { closeChat, openChat } from '../../actions.native';
import AbstractChat, {
_mapStateToProps,
@ -71,7 +70,8 @@ export default translate(connect(_mapStateToProps)(props => {
_nbUnreadMessages,
dispatch,
navigation,
route
route,
t
} = props;
const isChatScreenFocused = useIsFocused();
const privateMessageRecipient = route.params?.privateMessageRecipient;
@ -84,7 +84,7 @@ export default translate(connect(_mapStateToProps)(props => {
dispatch(openChat(privateMessageRecipient));
navigation.setOptions({
tabBarLabel: `${screen.conference.chatandpolls.tab.chat} ${nrUnreadMessages}`
tabBarLabel: `${t('chat.tabs.chat')} ${nrUnreadMessages}`
});
return () => dispatch(closeChat());

View File

@ -3,6 +3,7 @@
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useSelector } from 'react-redux';
@ -23,6 +24,7 @@ import {
conferenceScreenOptions,
inviteScreenOptions,
lobbyScreenOptions,
navigationContainerTheme,
participantsScreenOptions,
sharedDocumentScreenOptions
} from './ConferenceNavigatorScreenOptions';
@ -30,6 +32,7 @@ import { screen } from './routes';
const ConferenceStack = createStackNavigator();
const ConferenceNavigationContainer = () => {
const isPollsDisabled = useSelector(getDisablePolls);
const ChatScreen
@ -40,56 +43,52 @@ const ConferenceNavigationContainer = () => {
= isPollsDisabled
? screen.conference.chat
: screen.conference.chatandpolls.main;
const { t } = useTranslation();
return (
<SafeAreaProvider>
<NavigationContainer
independent = { true }
ref = { conferenceNavigationRef }
theme = {{
colors: {
background: '#fff'
}
}}>
theme = { navigationContainerTheme }>
<ConferenceStack.Navigator
initialRouteName = { screen.conference.main }
mode = 'modal'>
<ConferenceStack.Screen
component = { Conference }
name = { screen.conference.main }
options = {{
...conferenceScreenOptions
}} />
options = { conferenceScreenOptions } />
<ConferenceStack.Screen
/* eslint-disable-next-line react/jsx-no-bind */
component = { ChatScreen }
name = { chatScreenName }
options = {{
...chatScreenOptions
...chatScreenOptions,
title: t('chat.title')
}} />
<ConferenceStack.Screen
component = { ParticipantsPane }
name = { screen.conference.participants }
options = {{
...participantsScreenOptions
...participantsScreenOptions,
title: t('participantsPane.header')
}} />
<ConferenceStack.Screen
component = { LobbyScreen }
name = { screen.lobby }
options = {{
...lobbyScreenOptions
}} />
options = { lobbyScreenOptions } />
<ConferenceStack.Screen
component = { AddPeopleDialog }
name = { screen.conference.invite }
options = {{
...inviteScreenOptions
...inviteScreenOptions,
title: t('addPeople.add')
}} />
<ConferenceStack.Screen
component = { SharedDocument }
name = { screen.conference.sharedDocument }
options = {{
...sharedDocumentScreenOptions
...sharedDocumentScreenOptions,
title: t('documentSharing.title')
}} />
</ConferenceStack.Navigator>
</NavigationContainer>

View File

@ -1,13 +1,31 @@
// @flow
/* eslint-disable react/no-multi-comp */
import { TransitionPresets } from '@react-navigation/stack';
import React from 'react';
import { Platform } from 'react-native';
import { IconClose } from '../../../base/icons';
import {
Icon,
IconClose,
IconHelp,
IconHome,
IconInfo,
IconSettings
} from '../../../base/icons';
import BaseTheme from '../../../base/ui/components/BaseTheme';
import { goBack } from './ConferenceNavigationContainerRef';
import HeaderNavigationButton from './HeaderNavigationButton';
/**
* Navigation container theme.
*/
export const navigationContainerTheme = {
colors: {
background: BaseTheme.palette.ui12
}
};
/**
* Default modal transition for the current platform.
@ -20,24 +38,126 @@ export const conferenceModalPresentation = Platform.select({
/**
* Screen options and transition types.
*/
export const screenOptions = {
export const fullScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: false,
headerShown: false
};
/**
* Dial-IN Info screen options and transition types.
*/
export const dialInSummaryScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: true,
headerShown: true,
headerStyle: {
backgroundColor: BaseTheme.palette.screen01Header
},
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Drawer navigator screens options and transition types.
*/
export const drawerNavigatorScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: true,
headerShown: false
};
/**
* Drawer screen options and transition types.
*/
export const drawerScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: true,
headerShown: true,
headerStyle: {
backgroundColor: BaseTheme.palette.screen01Header
}
};
/**
* Screen options for welcome page.
*/
export const welcomeScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconHome } />
),
headerTitleStyle: {
color: BaseTheme.palette.screen01Header
}
};
/**
* Screen options for settings screen.
*/
export const settingsScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconSettings } />
),
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for terms/privacy screens.
*/
export const termsAndPrivacyScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconInfo } />
),
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for help screen.
*/
export const helpScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconHelp } />
),
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for conference.
*/
export const conferenceScreenOptions = {
...screenOptions
...fullScreenOptions
};
/**
* Screen options for lobby modal.
*/
export const lobbyScreenOptions = {
...screenOptions
...fullScreenOptions
};
/**

View File

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { TouchableWithoutFeedback } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { Icon } from '../../../base/icons';
@ -22,17 +22,18 @@ type Props = {
/**
* The component's external style.
*/
style: Object
style?: Object
}
const HeaderNavigationButton = ({ onPress, src, style }: Props) => (
<TouchableWithoutFeedback
onPress = { onPress } >
<TouchableOpacity
onPress = { onPress }
style = { styles.headerNavigationButton } >
<Icon
size = { 20 }
src = { src }
style = { [ styles.headerNavigationButton, style ] } />
</TouchableWithoutFeedback>
style = { [ styles.headerNavigationIcon, style ] } />
</TouchableOpacity>
);

View File

@ -1,4 +1,12 @@
export const screen = {
welcome: {
main: 'Home',
settings: 'Settings',
terms: 'Terms',
privacy: 'Privacy',
help: 'Help'
},
dialInSummary: 'Dial-In Info',
conference: {
main: 'Conference',
chat: 'Chat',

View File

@ -1,7 +1,8 @@
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel, ColorPalette, fixAndroidViewClipping } from '../../../base/styles';
import { BoxModel, fixAndroidViewClipping } from '../../../base/styles';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export const INSECURE_ROOM_NAME_LABEL_COLOR = ColorPalette.warning;
export const INSECURE_ROOM_NAME_LABEL_COLOR = BaseTheme.palette.warning03;
const NAVBAR_BUTTON_SIZE = 24;
@ -15,7 +16,7 @@ export default {
*/
conference: fixAndroidViewClipping({
alignSelf: 'stretch',
backgroundColor: '#040404',
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1
}),
@ -23,10 +24,16 @@ export default {
margin: 10
},
headerNavigationButton: {
headerNavigationIcon: {
marginLeft: 12
},
headerNavigationButton: {
height: BaseTheme.spacing[6],
marginTop: BaseTheme.spacing[3],
width: BaseTheme.spacing[6]
},
/**
* View that contains the indicators.
*/
@ -45,17 +52,17 @@ export default {
inviteButton: {
iconStyle: {
padding: 10,
color: ColorPalette.white,
color: BaseTheme.palette.icon01,
fontSize: NAVBAR_BUTTON_SIZE
},
underlayColor: ColorPalette.buttonUnderlay
underlayColor: BaseTheme.spacing.underlay01
},
lonelyButton: {
alignItems: 'center',
borderRadius: 24,
flexDirection: 'row',
height: 48,
height: BaseTheme.spacing[6],
justifyContent: 'space-around',
paddingHorizontal: 12
},
@ -84,10 +91,10 @@ export default {
pipButton: {
iconStyle: {
padding: 10,
color: ColorPalette.white,
color: BaseTheme.palette.icon01,
fontSize: NAVBAR_BUTTON_SIZE
},
underlayColor: ColorPalette.buttonUnderlay
underlayColor: BaseTheme.palette.underlay01
},
navBarSafeView: {
@ -107,7 +114,7 @@ export default {
},
roomTimer: {
color: ColorPalette.white,
color: BaseTheme.palette.text01,
fontSize: 12,
fontWeight: '400',
paddingHorizontal: 8
@ -123,7 +130,7 @@ export default {
},
roomName: {
color: ColorPalette.white,
color: BaseTheme.palette.text01,
fontSize: 14,
fontWeight: '400'
},

View File

@ -15,7 +15,8 @@ export * from './functions.any';
* @returns {boolean}.
*/
export function getDisablePolls(stateful: Object) {
const state = toState(stateful['features/base/config']);
const state = toState(stateful)['features/base/config'];
return state.disablePolls;
}

View File

@ -89,7 +89,6 @@ class SharedDocument extends PureComponent<Props> {
return (
<JitsiScreen
addHeaderHeightValue = { true }
hasTabNavigator = { false }
style = { styles.sharedDocContainer }>
<WebView
renderLoading = { this._renderLoading }

View File

@ -1,55 +0,0 @@
// @flow
import React, { PureComponent } from 'react';
import WebView from 'react-native-webview';
import { JitsiModal } from '../../base/modal';
import { connect } from '../../base/redux';
import { HELP_VIEW_MODAL_ID } from '../constants';
const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
type Props = {
/**
* The URL to display in the Help Centre.
*/
_url: string
}
/**
* Implements a page that renders the help content for the app.
*/
class HelpView extends PureComponent<Props> {
/**
* Implements {@code PureComponent#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<JitsiModal
headerProps = {{
headerLabelKey: 'helpView.header'
}}
modalId = { HELP_VIEW_MODAL_ID }>
<WebView source = {{ uri: this.props._url }} />
</JitsiModal>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_url: state['features/base/config'].helpCentreURL || DEFAULT_HELP_CENTRE_URL
};
}
export default connect(_mapStateToProps)(HelpView);

View File

@ -1,3 +0,0 @@
// @flow
export { default as HelpView } from './HelpView';

View File

@ -1,3 +0,0 @@
// @flow
export const HELP_VIEW_MODAL_ID = 'helpView';

View File

@ -1,4 +0,0 @@
// @flow
export * from './components';
export * from './constants';

View File

@ -210,7 +210,6 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
return (
<JitsiScreen
footerComponent = { this._renderShareMeetingButton }
hasTabNavigator = { false }
style = { styles.addPeopleContainer }>
<ClearableInput
autoFocus = { false }

View File

@ -7,10 +7,11 @@ import { type Dispatch } from 'redux';
import { openDialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { JitsiModal, setActiveModalId } from '../../../../base/modal';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { LoadingIndicator } from '../../../../base/react';
import { connect } from '../../../../base/redux';
import { DIAL_IN_SUMMARY_VIEW_ID } from '../../../constants';
import { screen } from '../../../../conference/components/native/routes';
import { renderArrowBackButton } from '../../../../welcome/functions.native';
import { getDialInfoPageURLForURIString } from '../../../functions';
import DialInSummaryErrorDialog from './DialInSummaryErrorDialog';
@ -18,12 +19,17 @@ import styles, { INDICATOR_COLOR } from './styles';
type Props = {
/**
* The URL to display the summary for.
*/
_summaryUrl: ?string,
dispatch: Dispatch<any>,
dispatch: Dispatch<any>
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object,
/**
* Default prop for navigating between screen components(React Navigation).
*/
route: Object
};
/**
@ -44,29 +50,46 @@ class DialInSummary extends Component<Props> {
this._renderLoading = this._renderLoading.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after mounting occurs.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const {
navigation
} = this.props;
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
navigation.navigate(screen.welcome.main))
});
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { _summaryUrl } = this.props;
const { route } = this.props;
const summaryUrl = route.params?.summaryUrl;
return (
<JitsiModal
headerProps = {{
headerLabelKey: 'info.label'
}}
modalId = { DIAL_IN_SUMMARY_VIEW_ID }
<JitsiScreen
style = { styles.backDrop }>
<WebView
onError = { this._onError }
onShouldStartLoadWithRequest = { this._onNavigate }
renderLoading = { this._renderLoading }
source = {{ uri: getDialInfoPageURLForURIString(_summaryUrl) }}
setSupportMultipleWindows = { false }
source = {{ uri: getDialInfoPageURLForURIString(summaryUrl) }}
startInLoadingState = { true }
style = { styles.webView } />
</JitsiModal>
</JitsiScreen>
);
}
@ -78,7 +101,6 @@ class DialInSummary extends Component<Props> {
* @returns {void}
*/
_onError() {
this.props.dispatch(setActiveModalId());
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
}
@ -94,14 +116,14 @@ class DialInSummary extends Component<Props> {
*/
_onNavigate(request) {
const { url } = request;
const { route } = this.props;
const summaryUrl = route.params?.summaryUrl;
if (url.startsWith('tel:')) {
Linking.openURL(url);
this.props.dispatch(setActiveModalId());
}
return url === getDialInfoPageURLForURIString(this.props._summaryUrl);
return url === getDialInfoPageURLForURIString(summaryUrl);
}
_renderLoading: () => React$Component<any>;
@ -122,18 +144,4 @@ class DialInSummary extends Component<Props> {
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _summaryUrl: ?string
* }}
*/
function _mapStateToProps(state) {
return {
_summaryUrl: (state['features/base/modal'].modalProps || {}).summaryUrl
};
}
export default translate(connect(_mapStateToProps)(DialInSummary));
export default translate(connect()(DialInSummary));

View File

@ -9,7 +9,8 @@ const WV_BACKGROUND = 'rgb(71, 71, 71)';
export default {
backDrop: {
backgroundColor: WV_BACKGROUND
backgroundColor: WV_BACKGROUND,
flex: 1
},
indicatorWrapper: {

View File

@ -28,7 +28,6 @@ class LobbyScreen extends AbstractLobbyScreen {
return (
<JitsiScreen
hasTabNavigator = { false }
style = { styles.contentWrapper }>
<SafeAreaView>
<Text style = { styles.dialogTitle }>

View File

@ -35,7 +35,6 @@ const ParticipantsPane = () => {
return (
<JitsiScreen
hasTabNavigator = { false }
style = { styles.participantsPane }>
<ScrollView bounces = { false }>
<LobbyParticipantList />

View File

@ -8,7 +8,6 @@ import { useSelector } from 'react-redux';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { BUTTON_MODES } from '../../../chat/constants';
import { screen } from '../../../conference/components/native/routes';
import { getUnreadPollCount } from '../../functions';
import AbstractPollsPane from '../AbstractPollsPane';
import type { AbstractProps } from '../AbstractPollsPane';
@ -31,7 +30,7 @@ const PollsPane = (props: AbstractProps) => {
useEffect(() => {
navigation.setOptions({
tabBarLabel: `${screen.conference.chatandpolls.tab.polls} ${nrUnreadPolls}`
tabBarLabel: `${t('chat.tabs.polls')} ${nrUnreadPolls}`
});
}, [ nrUnreadPolls ]);

View File

@ -2,10 +2,10 @@
import { translate } from '../../base/i18n';
import { IconInfo } from '../../base/icons';
import { setActiveModalId } from '../../base/modal';
import { connect } from '../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { DIAL_IN_SUMMARY_VIEW_ID } from '../../invite/constants';
import { screen } from '../../conference/components/native/routes';
import { navigate } from '../../welcome/components/RootNavigationContainerRef';
export type Props = AbstractButtonProps & {
@ -40,9 +40,11 @@ class ShowDialInInfoButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch, itemId } = this.props;
const { itemId } = this.props;
dispatch(setActiveModalId(DIAL_IN_SUMMARY_VIEW_ID, { summaryUrl: itemId.url }));
navigate(screen.dialInSummary, {
summaryUrl: itemId.url
});
}
}

View File

@ -1 +0,0 @@
export * from './native';

View File

@ -13,7 +13,7 @@ import { isRecentListEnabled } from '../../recent-list/functions';
/**
* {@code AbstractWelcomePage}'s React {@code Component} prop types.
*/
type Props = {
export type Props = {
/**
* Whether the calendar functionality is enabled or not.
@ -56,7 +56,7 @@ type Props = {
*
* @abstract
*/
export class AbstractWelcomePage extends Component<Props, *> {
export class AbstractWelcomePage<P: Props> extends Component<P, *> {
_mounted: ?boolean;
/**
@ -64,7 +64,7 @@ export class AbstractWelcomePage extends Component<Props, *> {
*
* @inheritdoc
*/
static getDerivedStateFromProps(props: Props, state: Object) {
static getDerivedStateFromProps(props: P, state: Object) {
return {
room: props._room || state.room
};
@ -99,7 +99,7 @@ export class AbstractWelcomePage extends Component<Props, *> {
* @param {Props} props - The React {@code Component} props to initialize
* the new {@code AbstractWelcomePage} instance with.
*/
constructor(props: Props) {
constructor(props: P) {
super(props);
// Bind event handlers so they are only bound once per instance.

View File

@ -0,0 +1,67 @@
// @flow
import { DrawerItemList } from '@react-navigation/drawer';
import React from 'react';
import { ScrollView, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Avatar } from '../../base/avatar';
import {
getLocalParticipant, getParticipantDisplayName
} from '../../base/participants';
import { connect } from '../../base/redux';
import styles, { DRAWER_AVATAR_SIZE } from './styles';
type Props = {
/**
* Local participant name to be displayed.
*/
displayName: string,
/**
* The ID of the local participant.
*/
localParticipantId: string
};
const CustomDrawerContent = (props: Props) => (
<ScrollView bounces = { false }>
<View style = { styles.drawerHeader }>
<Avatar
participantId = { props.localParticipantId }
size = { DRAWER_AVATAR_SIZE } />
<Text style = { styles.displayName }>
{ props.displayName }
</Text>
</View>
<SafeAreaView
edges = { [
'left',
'right'
] }>
<DrawerItemList { ...props } />
</SafeAreaView>
</ScrollView>
);
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Props}
*/
function mapStateToProps(state: Object) {
const localParticipant = getLocalParticipant(state);
const localParticipantId = localParticipant?.id;
const displayName = localParticipant && getParticipantDisplayName(state, localParticipantId);
return {
displayName,
localParticipantId
};
}
export default connect(mapStateToProps)(CustomDrawerContent);

View File

@ -0,0 +1,74 @@
// @flow
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { connect } from '../../base/redux';
import {
dialInSummaryScreenOptions,
drawerNavigatorScreenOptions,
navigationContainerTheme
} from '../../conference/components/native/ConferenceNavigatorScreenOptions';
import { screen } from '../../conference/components/native/routes';
import { DialInSummary } from '../../invite';
import { isWelcomePageAppEnabled } from '../functions.native';
import BlankPage from './BlankPage';
import { rootNavigationRef } from './RootNavigationContainerRef';
import WelcomePageNavigationContainer from './WelcomePageNavigationContainer';
const RootStack = createStackNavigator();
type Props = {
/**
* Is welcome page available?
*/
isWelcomePageAvailable: boolean
}
const RootNavigationContainer = ({ isWelcomePageAvailable }: Props) => (
<SafeAreaProvider>
<NavigationContainer
independent = { true }
ref = { rootNavigationRef }
theme = { navigationContainerTheme }>
<RootStack.Navigator
initialRouteName = { screen.welcome.main }>
{
isWelcomePageAvailable
? <RootStack.Screen
component = { WelcomePageNavigationContainer }
name = { screen.welcome.main }
options = { drawerNavigatorScreenOptions } />
: <RootStack.Screen
component = { BlankPage }
name = { screen.welcome.main } />
}
<RootStack.Screen
component = { DialInSummary }
name = { screen.dialInSummary }
options = { dialInSummaryScreenOptions } />
</RootStack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
/**
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function mapStateToProps(state: Object) {
return {
isWelcomePageAvailable: isWelcomePageAppEnabled(state)
};
}
export default connect(mapStateToProps)(RootNavigationContainer);

View File

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

View File

@ -1,101 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Linking, Text, TouchableOpacity, View } from 'react-native';
import { translate } from '../../base/i18n';
import { Icon } from '../../base/icons';
import styles from './styles';
type Props = {
/**
* The icon of the item.
*/
icon: Object,
/**
* The i18n label of the item.
*/
label: string,
/**
* The function to be invoked when the item is pressed
* if the item is a button.
*/
onPress: Function,
/**
* The translate function.
*/
t: Function,
/**
* The URL of the link, if this item is a link.
*/
url: string
};
/**
* A component rendering an item in the system sidebar.
*/
class SideBarItem extends Component<Props> {
/**
* Initializes a new {@code SideBarItem} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onOpenURL = this._onOpenURL.bind(this);
}
/**
* Implements React's {@link Component#render()}, renders the sidebar item.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { label, onPress, t } = this.props;
const onPressCalculated
= typeof onPress === 'function' ? onPress : this._onOpenURL;
return (
<TouchableOpacity
onPress = { onPressCalculated }
style = { styles.sideBarItem }>
<View style = { styles.sideBarItemButtonContainer }>
<Icon
src = { this.props.icon }
style = { styles.sideBarItemIcon } />
<Text style = { styles.sideBarItemText }>
{ t(label) }
</Text>
</View>
</TouchableOpacity>
);
}
_onOpenURL: () => void;
/**
* Opens the URL if one is provided.
*
* @private
* @returns {void}
*/
_onOpenURL() {
const { url } = this.props;
if (typeof url === 'string') {
Linking.openURL(url);
}
}
}
export default translate(SideBarItem);

View File

@ -1,7 +1,9 @@
// @flow
import { DrawerActions } from '@react-navigation/native';
import React from 'react';
import {
Animated,
Keyboard,
SafeAreaView,
TextInput,
TouchableHighlight,
@ -14,7 +16,8 @@ import { ColorSchemeRegistry } from '../../base/color-scheme';
import { translate } from '../../base/i18n';
import { Icon, IconMenu, IconWarning } from '../../base/icons';
import { MEDIA_TYPE } from '../../base/media';
import { Header, LoadingIndicator, Text } from '../../base/react';
import JitsiStatusBar from '../../base/modal/components/JitsiStatusBar';
import { LoadingIndicator, Text } from '../../base/react';
import { connect } from '../../base/redux';
import { ColorPalette } from '../../base/styles';
import {
@ -22,41 +25,63 @@ import {
destroyLocalDesktopTrackIfExists,
destroyLocalTracks
} from '../../base/tracks';
import { HelpView } from '../../help';
import { DialInSummary } from '../../invite';
import { SettingsView } from '../../settings/components';
import { setSideBarVisible } from '../actions';
import {
AbstractWelcomePage,
_mapStateToProps as _abstractMapStateToProps
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from './AbstractWelcomePage';
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
import VideoSwitch from './VideoSwitch';
import WelcomePageLists from './WelcomePageLists';
import WelcomePageSideBar from './WelcomePageSideBar';
import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
type Props = AbstractProps & {
/**
* The color schemed style of the Header component.
*/
_headerStyles: Object,
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object,
/**
* Default prop for navigating between screen components(React Navigation).
*/
route: Object,
/**
* The translate function.
*/
t: Function
};
/**
* The native container rendering the welcome page.
*
* @augments AbstractWelcomePage
*/
class WelcomePage extends AbstractWelcomePage {
class WelcomePage extends AbstractWelcomePage<*> {
/**
* Constructor of the Component.
*
* @inheritdoc
*/
constructor(props) {
constructor(props: Props) {
super(props);
// $FlowExpectedError
this.state._fieldFocused = false;
// $FlowExpectedError
this.state.hintBoxAnimation = new Animated.Value(0);
// Bind event handlers so they are only bound once per instance.
this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
this._onShowSideBar = this._onShowSideBar.bind(this);
this._renderHintBox = this._renderHintBox.bind(this);
// Specially bind functions to avoid function definition on render.
@ -64,6 +89,16 @@ class WelcomePage extends AbstractWelcomePage {
this._onFieldFocus = this._onFieldFocusChange.bind(this, true);
}
_onFieldBlur: () => void;
_onFieldFocus: () => void;
_onJoin: () => void;
_onRoomChange: (string) => void;
_updateRoomname: () => void;
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after mounting occurs. Creates a local video track if none
@ -77,7 +112,29 @@ class WelcomePage extends AbstractWelcomePage {
this._updateRoomname();
const { dispatch } = this.props;
const {
_headerStyles,
dispatch,
navigation
} = this.props;
navigation.setOptions({
headerLeft: () => (
<TouchableOpacity
/* eslint-disable-next-line react/jsx-no-bind */
onPress = { () =>
navigation.dispatch(DrawerActions.openDrawer()) }
style = { styles.drawerNavigationIcon }>
<Icon
size = { 20 }
src = { IconMenu }
style = { _headerStyles.headerButtonIcon } />
</TouchableOpacity>
),
// eslint-disable-next-line react/no-multi-comp
headerRight: () =>
<VideoSwitch />
});
if (this.props._settings.startAudioOnly) {
dispatch(destroyLocalTracks());
@ -153,11 +210,14 @@ class WelcomePage extends AbstractWelcomePage {
styles.messageContainer,
styles.hintContainer,
{
// $FlowExpectedError
opacity: this.state.hintBoxAnimation
}
];
}
_onFieldFocusChange: (boolean) => void;
/**
* Callback for when the room field's focus changes so the hint box
* must be rendered or removed.
@ -169,6 +229,7 @@ class WelcomePage extends AbstractWelcomePage {
_onFieldFocusChange(focused) {
if (focused) {
// Stop placeholder animation.
// $FlowExpectedError
this._clearTimeouts();
this.setState({
_fieldFocused: true,
@ -180,29 +241,28 @@ class WelcomePage extends AbstractWelcomePage {
}
Animated.timing(
// $FlowExpectedError
this.state.hintBoxAnimation,
// $FlowExpectedError
{
duration: 300,
toValue: focused ? 1 : 0
})
.start(animationState =>
// $FlowExpectedError
animationState.finished
&& !focused
// $FlowExpectedError
&& !focused
&& this.setState({
_fieldFocused: false
}));
}
/**
* Toggles the side bar.
*
* @private
* @returns {void}
*/
_onShowSideBar() {
Keyboard.dismiss();
this.props.dispatch(setSideBarVisible(true));
}
_renderHintBox: () => React$Element<any>;
/**
* Renders the hint box if necessary.
@ -211,9 +271,10 @@ class WelcomePage extends AbstractWelcomePage {
* @returns {React$Node}
*/
_renderHintBox() {
if (this.state._fieldFocused) {
const { t } = this.props;
const { t } = this.props;
// $FlowExpectedError
if (this.state._fieldFocused) {
return (
<Animated.View style = { this._getHintBoxStyle() }>
<View style = { styles.hintTextContainer } >
@ -283,51 +344,48 @@ class WelcomePage extends AbstractWelcomePage {
const { _headerStyles, t } = this.props;
return (
<LocalVideoTrackUnderlay style = { styles.welcomePage }>
<View style = { _headerStyles.page }>
<Header style = { styles.header }>
<TouchableOpacity onPress = { this._onShowSideBar } >
<Icon
src = { IconMenu }
style = { _headerStyles.headerButtonIcon } />
</TouchableOpacity>
<VideoSwitch />
</Header>
<SafeAreaView style = { styles.roomContainer } >
<View style = { styles.joinControls } >
<Text style = { styles.enterRoomText }>
{ t('welcomepage.roomname') }
</Text>
<TextInput
accessibilityLabel = { t(roomnameAccLabel) }
autoCapitalize = 'none'
autoComplete = 'off'
autoCorrect = { false }
autoFocus = { false }
onBlur = { this._onFieldBlur }
onChangeText = { this._onRoomChange }
onFocus = { this._onFieldFocus }
onSubmitEditing = { this._onJoin }
placeholder = { this.state.roomPlaceholder }
placeholderTextColor = { PLACEHOLDER_TEXT_COLOR }
returnKeyType = { 'go' }
spellCheck = { false }
style = { styles.textInput }
underlineColorAndroid = 'transparent'
value = { this.state.room } />
{
this._renderInsecureRoomNameWarning()
}
{
this._renderHintBox()
}
</View>
</SafeAreaView>
<WelcomePageLists disabled = { this.state._fieldFocused } />
</View>
<WelcomePageSideBar />
{ this._renderWelcomePageModals() }
</LocalVideoTrackUnderlay>
<>
<JitsiStatusBar />
<LocalVideoTrackUnderlay style = { styles.welcomePage }>
<View style = { _headerStyles.page }>
<SafeAreaView style = { styles.roomContainer } >
<View style = { styles.joinControls } >
<Text style = { styles.enterRoomText }>
{ t('welcomepage.roomname') }
</Text>
{/* // $FlowExpectedError*/}
<TextInput
accessibilityLabel = { t(roomnameAccLabel) }
autoCapitalize = { 'none' }
autoComplete = { 'off' }
autoCorrect = { false }
autoFocus = { false }
onBlur = { this._onFieldBlur }
onChangeText = { this._onRoomChange }
onFocus = { this._onFieldFocus }
onSubmitEditing = { this._onJoin }
placeholder = { this.state.roomPlaceholder }
placeholderTextColor = { PLACEHOLDER_TEXT_COLOR }
returnKeyType = { 'go' }
spellCheck = { false }
style = { styles.textInput }
underlineColorAndroid = 'transparent'
value = { this.state.room } />
{
// $FlowExpectedError
this._renderInsecureRoomNameWarning()
}
{
this._renderHintBox()
}
</View>
</SafeAreaView>
{/* $FlowExpectedError*/}
<WelcomePageLists disabled = { this.state._fieldFocused } />
</View>
</LocalVideoTrackUnderlay>
</>
);
}
@ -347,19 +405,6 @@ class WelcomePage extends AbstractWelcomePage {
</View>
);
}
/**
* Renders JitsiModals that are supposed to be on the welcome page.
*
* @returns {Array<ReactElement>}
*/
_renderWelcomePageModals() {
return [
<HelpView key = 'helpView' />,
<DialInSummary key = 'dialInSummary' />,
<SettingsView key = 'settings' />
];
}
}
/**

View File

@ -0,0 +1,73 @@
// @flow
import { createDrawerNavigator } from '@react-navigation/drawer';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
helpScreenOptions,
settingsScreenOptions,
termsAndPrivacyScreenOptions,
welcomeScreenOptions
} from '../../conference/components/native/ConferenceNavigatorScreenOptions';
import { screen } from '../../conference/components/native/routes';
import HelpView from '../components/help/components/HelpView';
import PrivacyView from '../components/privacy/components/PrivacyView';
import SettingsView from '../components/settings/components/SettingsView';
import TermsView from '../components/terms/components/TermsView';
import CustomDrawerContent from './CustomDrawerContent';
import WelcomePage from './WelcomePage.native';
import { drawerContentOptions } from './constants';
import styles from './styles';
const DrawerStack = createDrawerNavigator();
const WelcomePageNavigationContainer = () => {
const { t } = useTranslation();
return (
<DrawerStack.Navigator
/* eslint-disable-next-line react/jsx-no-bind */
drawerContent = { props => <CustomDrawerContent { ...props } /> }
drawerContentOptions = { drawerContentOptions }
drawerStyle = { styles.drawerStyle }>
<DrawerStack.Screen
component = { WelcomePage }
name = { screen.welcome.main }
options = { welcomeScreenOptions } />
<DrawerStack.Screen
component = { SettingsView }
name = { screen.welcome.settings }
options = {{
...settingsScreenOptions,
title: t('settingsView.header')
}} />
<DrawerStack.Screen
component = { TermsView }
name = { screen.welcome.terms }
options = {{
...termsAndPrivacyScreenOptions,
title: t('termsView.header')
}} />
<DrawerStack.Screen
component = { PrivacyView }
name = { screen.welcome.privacy }
options = {{
...termsAndPrivacyScreenOptions,
title: t('privacyView.header')
}} />
<DrawerStack.Screen
component = { HelpView }
name = { screen.welcome.help }
options = {{
...helpScreenOptions,
title: t('helpView.header')
}} />
</DrawerStack.Navigator>
);
};
export default WelcomePageNavigationContainer;

View File

@ -1,182 +0,0 @@
// @flow
import React, { Component } from 'react';
import { SafeAreaView, ScrollView, Text } from 'react-native';
import { Avatar } from '../../base/avatar';
import { IconInfo, IconSettings, IconHelp } from '../../base/icons';
import { setActiveModalId } from '../../base/modal';
import {
getLocalParticipant,
getParticipantDisplayName
} from '../../base/participants';
import {
Header,
SlidingView
} from '../../base/react';
import { connect } from '../../base/redux';
import { HELP_VIEW_MODAL_ID } from '../../help';
import { SETTINGS_VIEW_ID } from '../../settings/constants';
import { setSideBarVisible } from '../actions';
import SideBarItem from './SideBarItem';
import styles, { SIDEBAR_AVATAR_SIZE } from './styles';
/**
* The URL at which the privacy policy is available to the user.
*/
const PRIVACY_URL = 'https://jitsi.org/meet/privacy';
/**
* The URL at which the terms (of service/use) are available to the user.
*/
const TERMS_URL = 'https://jitsi.org/meet/terms';
type Props = {
/**
* Redux dispatch action.
*/
dispatch: Function,
/**
* Display name of the local participant.
*/
_displayName: ?string,
/**
* ID of the local participant.
*/
_localParticipantId: ?string,
/**
* Sets the side bar visible or hidden.
*/
_visible: boolean
};
/**
* A component rendering a welcome page sidebar.
*/
class WelcomePageSideBar extends Component<Props> {
/**
* Constructs a new SideBar instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onHideSideBar = this._onHideSideBar.bind(this);
this._onOpenHelpPage = this._onOpenHelpPage.bind(this);
this._onOpenSettings = this._onOpenSettings.bind(this);
}
/**
* Implements React's {@link Component#render()}, renders the sidebar.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<SlidingView
onHide = { this._onHideSideBar }
show = { this.props._visible }
style = { styles.sideBar } >
<Header style = { styles.sideBarHeader }>
<Avatar
participantId = { this.props._localParticipantId }
size = { SIDEBAR_AVATAR_SIZE } />
<Text style = { styles.displayName }>
{ this.props._displayName }
</Text>
</Header>
<SafeAreaView style = { styles.sideBarBody }>
<ScrollView
style = { styles.itemContainer }>
<SideBarItem
icon = { IconSettings }
label = 'settings.title'
onPress = { this._onOpenSettings } />
<SideBarItem
icon = { IconInfo }
label = 'welcomepage.terms'
url = { TERMS_URL } />
<SideBarItem
icon = { IconInfo }
label = 'welcomepage.privacy'
url = { PRIVACY_URL } />
<SideBarItem
icon = { IconHelp }
label = 'welcomepage.getHelp'
onPress = { this._onOpenHelpPage } />
</ScrollView>
</SafeAreaView>
</SlidingView>
);
}
_onHideSideBar: () => void;
/**
* Invoked when the sidebar has closed itself (e.g. Overlay pressed).
*
* @private
* @returns {void}
*/
_onHideSideBar() {
this.props.dispatch(setSideBarVisible(false));
}
_onOpenHelpPage: () => void;
/**
* Shows the {@link HelpView}.
*
* @returns {void}
*/
_onOpenHelpPage() {
const { dispatch } = this.props;
dispatch(setSideBarVisible(false));
dispatch(setActiveModalId(HELP_VIEW_MODAL_ID));
}
_onOpenSettings: () => void;
/**
* Shows the {@link SettingsView}.
*
* @private
* @returns {void}
*/
_onOpenSettings() {
const { dispatch } = this.props;
dispatch(setSideBarVisible(false));
dispatch(setActiveModalId(SETTINGS_VIEW_ID));
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props.
*
* @param {Object} state - The redux state.
* @protected
* @returns {Props}
*/
function _mapStateToProps(state: Object) {
const _localParticipant = getLocalParticipant(state);
const _localParticipantId = _localParticipant?.id;
const _displayName = _localParticipant && getParticipantDisplayName(state, _localParticipantId);
return {
_displayName,
_localParticipantId,
_visible: state['features/welcome'].sideBarVisible
};
}
export default connect(_mapStateToProps)(WelcomePageSideBar);

View File

@ -0,0 +1,12 @@
// @flow
import BaseTheme from '../../base/ui/components/BaseTheme';
export const drawerContentOptions = {
activeBackgroundColor: BaseTheme.palette.ui12,
activeTintColor: BaseTheme.palette.screen01Header,
labelStyle: {
marginLeft: BaseTheme.spacing[2]
}
};

View File

@ -0,0 +1,83 @@
// @flow
import React, { PureComponent } from 'react';
import JitsiScreenWebView from '../../../../base/modal/components/JitsiScreenWebView';
import JitsiStatusBar from '../../../../base/modal/components/JitsiStatusBar';
import { connect } from '../../../../base/redux';
import { screen } from '../../../../conference/components/native/routes';
import { renderArrowBackButton } from '../../../../welcome/functions.native';
import styles from './styles';
const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
type Props = {
/**
* The URL to display in the Help Centre.
*/
_url: string,
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object
}
/**
* Implements a page that renders the help content for the app.
*/
class HelpView extends PureComponent<Props> {
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after mounting occurs.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const {
navigation
} = this.props;
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
navigation.jumpTo(screen.welcome.main))
});
}
/**
* Implements {@code PureComponent#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<>
<JitsiStatusBar />
<JitsiScreenWebView
source = { this.props._url }
style = { styles.helpViewContainer } />
</>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_url: state['features/base/config'].helpCentreURL || DEFAULT_HELP_CENTRE_URL
};
}
export default connect(_mapStateToProps)(HelpView);

View File

@ -0,0 +1,12 @@
/**
* The styles of the native components of the feature {@code settings}.
*/
export default {
/**
* Style for screen container.
*/
helpViewContainer: {
flex: 1
}
};

View File

@ -0,0 +1,46 @@
// @flow
import React, { useEffect } from 'react';
import JitsiScreenWebView from '../../../../base/modal/components/JitsiScreenWebView';
import JitsiStatusBar from '../../../../base/modal/components/JitsiStatusBar';
import { screen } from '../../../../conference/components/native/routes';
import { renderArrowBackButton } from '../../../../welcome/functions.native';
import styles from './styles';
type Props = {
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object
}
/**
* The URL at which the privacy policy is available to the user.
*/
const PRIVACY_URL = 'https://jitsi.org/meet/privacy';
const PrivacyView = ({ navigation }: Props) => {
useEffect(() => {
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
navigation.jumpTo(screen.welcome.main))
});
});
return (
<>
<JitsiStatusBar />
<JitsiScreenWebView
source = { PRIVACY_URL }
style = { styles.privacyViewContainer } />
</>
);
};
export default PrivacyView;

View File

@ -0,0 +1,12 @@
/**
* The styles of the native components of the feature {@code privacy}.
*/
export default {
/**
* Style for screen container.
*/
privacyViewContainer: {
flex: 1
}
};

View File

@ -3,7 +3,7 @@
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { translate } from '../../../base/i18n';
import { translate } from '../../../../base/i18n';
import styles, { ANDROID_UNDERLINE_COLOR, PLACEHOLDER_COLOR } from './styles';

View File

@ -3,8 +3,8 @@
import React, { useCallback, useState } from 'react';
import { List } from 'react-native-paper';
import { translate } from '../../../base/i18n';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import { translate } from '../../../../base/i18n';
import { Icon, IconArrowDown, IconArrowUp } from '../../../../base/icons';
import styles from './styles';

View File

@ -1,19 +1,25 @@
// @flow
import React from 'react';
import { Alert, NativeModules, ScrollView, Text } from 'react-native';
import {
Alert,
NativeModules,
ScrollView,
Text
} from 'react-native';
import { Divider, Switch, TextInput, withTheme } from 'react-native-paper';
import { translate } from '../../../base/i18n';
import { JitsiModal } from '../../../base/modal';
import { connect } from '../../../base/redux';
import { SETTINGS_VIEW_ID } from '../../constants';
import { normalizeUserInputURL, isServerURLChangeEnabled } from '../../functions';
import { translate } from '../../../../base/i18n';
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
import { connect } from '../../../../base/redux';
import { screen } from '../../../../conference/components/native/routes';
import {
AbstractSettingsView,
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from '../AbstractSettingsView';
} from '../../../../settings/components/AbstractSettingsView';
import { normalizeUserInputURL, isServerURLChangeEnabled } from '../../../../settings/functions';
import { renderArrowBackButton } from '../../../../welcome/functions.native';
import FormRow from './FormRow';
import FormSectionAccordion from './FormSectionAccordion';
@ -80,6 +86,11 @@ type Props = AbstractProps & {
*/
_serverURLChangeEnabled: boolean,
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object,
/**
* Theme used for styles.
*/
@ -133,6 +144,25 @@ class SettingsView extends AbstractSettingsView<Props, State> {
this._showURLAlert = this._showURLAlert.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}. Invoked
* immediately after mounting occurs.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const {
navigation
} = this.props;
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
navigation.jumpTo(screen.welcome.main))
});
}
/**
* Implements React's {@link Component#render()}, renders the settings page.
*
@ -153,12 +183,8 @@ class SettingsView extends AbstractSettingsView<Props, State> {
const { palette } = this.props.theme;
return (
<JitsiModal
headerProps = {{
headerLabelKey: 'settingsView.header'
}}
modalId = { SETTINGS_VIEW_ID }
onClose = { this._onClose }>
<JitsiScreen
style = { styles.settingsViewContainer }>
<ScrollView>
<FormSectionAccordion
accordion = { false }
@ -175,7 +201,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
textContentType = { 'name' } // iOS only
theme = {{
colors: {
primary: palette.action01Active,
primary: palette.screen01Header,
underlineColor: 'transparent'
}
}}
@ -194,7 +220,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
textContentType = { 'emailAddress' } // iOS only
theme = {{
colors: {
primary: palette.action01Active,
primary: palette.screen01Header,
underlineColor: 'transparent'
}
}}
@ -219,7 +245,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
textContentType = { 'URL' } // iOS only
theme = {{
colors: {
primary: palette.action01Active,
primary: palette.screen01Header,
underlineColor: 'transparent'
}
}}
@ -230,7 +256,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
<Switch
onValueChange = { this._onStartAudioMutedChange }
thumbColor = { THUMB_COLOR }
trackColor = {{ true: palette.action01Active }}
trackColor = {{ true: palette.screen01Header }}
value = { startWithAudioMuted } />
</FormRow>
<Divider style = { styles.fieldSeparator } />
@ -238,7 +264,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
<Switch
onValueChange = { this._onStartVideoMutedChange }
thumbColor = { THUMB_COLOR }
trackColor = {{ true: palette.action01Active }}
trackColor = {{ true: palette.screen01Header }}
value = { startWithVideoMuted } />
</FormRow>
</FormSectionAccordion>
@ -262,7 +288,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
<Switch
onValueChange = { this._onDisableCallIntegration }
thumbColor = { THUMB_COLOR }
trackColor = {{ true: palette.action01Active }}
trackColor = {{ true: palette.screen01Header }}
value = { disableCallIntegration } />
</FormRow>
<Divider style = { styles.fieldSeparator } />
@ -271,7 +297,7 @@ class SettingsView extends AbstractSettingsView<Props, State> {
<Switch
onValueChange = { this._onDisableP2P }
thumbColor = { THUMB_COLOR }
trackColor = {{ true: palette.action01Active }}
trackColor = {{ true: palette.screen01Header }}
value = { disableP2P } />
</FormRow>
<Divider style = { styles.fieldSeparator } />
@ -282,13 +308,13 @@ class SettingsView extends AbstractSettingsView<Props, State> {
<Switch
onValueChange = { this._onDisableCrashReporting }
thumbColor = { THUMB_COLOR }
trackColor = {{ true: palette.action01Active }}
trackColor = {{ true: palette.screen01Header }}
value = { disableCrashReporting } />
</FormRow>
)}
</FormSectionAccordion>
</ScrollView>
</JitsiModal>
</JitsiScreen>
);
}

View File

@ -1,4 +1,4 @@
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
export const ANDROID_UNDERLINE_COLOR = 'transparent';
export const PLACEHOLDER_COLOR = BaseTheme.palette.action02Focus;
export const THUMB_COLOR = BaseTheme.palette.field02;
@ -9,6 +9,14 @@ const TEXT_SIZE = 14;
* The styles of the native components of the feature {@code settings}.
*/
export default {
/**
* Style for screen container.
*/
settingsViewContainer: {
flex: 1
},
/**
* Standardized style for a field container {@code View}.
*/
@ -80,7 +88,7 @@ export default {
},
formSectionTitleActive: {
color: BaseTheme.palette.section01Active
color: BaseTheme.palette.screen01Header
},
formSectionTitleInActive: {
@ -93,7 +101,7 @@ export default {
},
sectionOpen: {
color: BaseTheme.palette.section01Active,
color: BaseTheme.palette.screen01Header,
fontSize: 14
},

View File

@ -1,23 +1,25 @@
// @flow
import { Dimensions, StyleSheet } from 'react-native';
import { StyleSheet } from 'react-native';
import { BoxModel, ColorPalette } from '../../base/styles';
import { BoxModel } from '../../base/styles';
import BaseTheme from '../../base/ui/components/BaseTheme.native';
export const PLACEHOLDER_TEXT_COLOR = 'rgba(255, 255, 255, 0.5)';
export const SIDEBAR_AVATAR_SIZE = 100;
export const PLACEHOLDER_TEXT_COLOR = BaseTheme.palette.text01;
const SIDEBAR_HEADER_HEIGHT = 150;
export const DRAWER_AVATAR_SIZE = 104;
export const SWITCH_THUMB_COLOR = ColorPalette.blueHighlight;
const DRAWER_HEADER_HEIGHT = 220;
export const SWITCH_THUMB_COLOR = BaseTheme.palette.action04;
export const SWITCH_UNDER_COLOR = 'rgba(0, 0, 0, 0.4)';
/**
* The default color of text on the WelcomePage.
*/
const TEXT_COLOR = ColorPalette.white;
const TEXT_COLOR = BaseTheme.palette.text01;
/**
* The styles of the React {@code Components} of the feature welcome including
@ -37,7 +39,8 @@ export default {
*/
audioVideoSwitchContainer: {
alignItems: 'center',
flexDirection: 'row'
flexDirection: 'row',
marginRight: BaseTheme.spacing[2]
},
/**
@ -55,8 +58,8 @@ export default {
* Join button style.
*/
button: {
backgroundColor: ColorPalette.blue,
borderColor: ColorPalette.blue,
backgroundColor: BaseTheme.palette.screen01Header,
borderColor: BaseTheme.palette.screen01Header,
borderRadius: 4,
borderWidth: 1,
height: 30,
@ -69,15 +72,23 @@ export default {
*/
buttonText: {
alignSelf: 'center',
color: ColorPalette.white,
color: BaseTheme.palette.text01,
fontSize: 14
},
/**
* Drawer style.
*/
drawerStyle: {
backgroundColor: BaseTheme.palette.ui12,
width: '54%'
},
/**
* The style of the display name label in the side bar.
*/
displayName: {
color: ColorPalette.white,
color: BaseTheme.palette.text01,
fontSize: 16,
marginTop: BoxModel.margin,
textAlign: 'center'
@ -89,13 +100,6 @@ export default {
marginBottom: BoxModel.margin
},
/**
* The welcome screen header style.
*/
header: {
justifyContent: 'space-between'
},
/**
* Container for the button on the hint box.
*/
@ -142,8 +146,8 @@ export default {
},
messageContainer: {
backgroundColor: ColorPalette.white,
borderColor: ColorPalette.white,
backgroundColor: BaseTheme.palette.ui12,
borderColor: BaseTheme.palette.field02,
borderRadius: 4,
borderWidth: 1,
marginVertical: 5,
@ -174,7 +178,7 @@ export default {
*/
reducedUIContainer: {
alignItems: 'center',
backgroundColor: ColorPalette.blue,
backgroundColor: BaseTheme.palette.screen01Header,
flex: 1,
justifyContent: 'center'
},
@ -192,64 +196,22 @@ export default {
flexDirection: 'column'
},
/**
* Container of the side bar.
*/
sideBar: {
width: 250,
height: Dimensions.get('window').height
},
/**
* The body of the side bar where the items are.
*/
sideBarBody: {
backgroundColor: ColorPalette.white,
flex: 1
},
/**
* The style of the side bar header.
*/
sideBarHeader: {
drawerHeader: {
alignItems: 'center',
backgroundColor: BaseTheme.palette.screen01Header,
flexDirection: 'column',
height: SIDEBAR_HEADER_HEIGHT,
justifyContent: 'center',
padding: BoxModel.padding
height: DRAWER_HEADER_HEIGHT,
justifyContent: 'center'
},
/**
* Style of the menu items in the side bar.
*/
sideBarItem: {
padding: 13
},
/**
* The View inside the side bar buttons (icon + text).
*/
sideBarItemButtonContainer: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'flex-start'
},
/**
* The icon in the side bar item touchables.
*/
sideBarItemIcon: {
color: ColorPalette.blueHighlight,
fontSize: 20,
marginRight: 15
},
/**
* The label of the side bar item touchables.
*/
sideBarItemText: {
color: ColorPalette.black,
fontWeight: 'bold'
drawerNavigationIcon: {
height: BaseTheme.spacing[6],
marginLeft: BaseTheme.spacing[1],
marginTop: BaseTheme.spacing[1],
width: BaseTheme.spacing[6]
},
/**
@ -264,7 +226,7 @@ export default {
*/
textInput: {
backgroundColor: 'transparent',
borderColor: ColorPalette.white,
borderColor: BaseTheme.palette.field02,
borderRadius: 4,
borderWidth: 1,
color: TEXT_COLOR,
@ -291,13 +253,13 @@ export default {
},
insecureRoomNameWarningIcon: {
color: ColorPalette.warning,
color: BaseTheme.palette.warning03,
fontSize: 24,
marginRight: 10
},
insecureRoomNameWarningText: {
color: ColorPalette.warning,
color: BaseTheme.palette.warning03,
flex: 1
},
@ -305,7 +267,7 @@ export default {
* The style of the top-level container of {@code WelcomePage}.
*/
welcomePage: {
backgroundColor: ColorPalette.blue,
backgroundColor: BaseTheme.palette.screen01Header,
overflow: 'hidden'
}
};

View File

@ -0,0 +1,46 @@
// @flow
import React, { useEffect } from 'react';
import JitsiScreenWebView from '../../../../base/modal/components/JitsiScreenWebView';
import JitsiStatusBar from '../../../../base/modal/components/JitsiStatusBar';
import { screen } from '../../../../conference/components/native/routes';
import { renderArrowBackButton } from '../../../../welcome/functions.native';
import styles from './styles';
type Props = {
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object
}
/**
* The URL at which the terms (of service/use) are available to the user.
*/
const TERMS_URL = 'https://jitsi.org/meet/terms';
const TermsView = ({ navigation }: Props) => {
useEffect(() => {
navigation.setOptions({
headerLeft: () =>
renderArrowBackButton(() =>
navigation.jumpTo(screen.welcome.main))
});
});
return (
<>
<JitsiStatusBar />
<JitsiScreenWebView
source = { TERMS_URL }
style = { styles.termsViewContainer } />
</>
);
};
export default TermsView;

View File

@ -0,0 +1,12 @@
/**
* The styles of the native components of the feature {@code terms}.
*/
export default {
/**
* Style for screen container.
*/
termsViewContainer: {
flex: 1
}
};

View File

@ -1,35 +1,9 @@
// @flow
import { WELCOME_PAGE_ENABLED, getFeatureFlag } from '../base/flags';
import { toState } from '../base/redux';
declare var APP: Object;
/**
* 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) {
if (navigator.product === 'ReactNative') {
// We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
// for Android and iOS. There isn't a strong reason not to introduce it
// on Web but there're a few considerations to be taken before I go
// there among which:
// - Enabling/disabling the Welcome page on Web historically
// automatically redirects to a random room and that does not make sense
// on mobile (right now).
return Boolean(getFeatureFlag(stateful, WELCOME_PAGE_ENABLED));
}
return true;
}
/**
* Determines whether the {@code WelcomePage} is enabled by the user either
* herself or through her deployment config(uration). Not to be confused with
@ -46,3 +20,5 @@ export function isWelcomePageUserEnabled(stateful: Function | Object) {
? true
: toState(stateful)['features/base/config'].enableWelcomePage);
}

View File

@ -0,0 +1,37 @@
// @flow
import React from 'react';
import { getFeatureFlag, WELCOME_PAGE_ENABLED } from '../base/flags';
import { IconArrowBack } from '../base/icons';
import HeaderNavigationButton
from '../conference/components/native/HeaderNavigationButton';
/**
* 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.
*
* @param {Function} onPress - Callback for when the button is pressed
* function.
* @returns {ReactElement}
*/
export function renderArrowBackButton(onPress: Function) {
return (
<HeaderNavigationButton
onPress = { onPress }
src = { IconArrowBack } />
);
}