init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1 @@
export const _ROOT_NAVIGATION_READY = '_ROOT_NAVIGATION_READY';

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SafeAreaView, Text, View, ViewStyle } from 'react-native';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
import { TEXT_COLOR, navigationStyles } from './styles';
const ConnectingPage = () => {
const { t } = useTranslation();
return (
<JitsiScreen style = { navigationStyles.connectingScreenContainer }>
<View style = { navigationStyles.connectingScreenContent as ViewStyle }>
<SafeAreaView>
<LoadingIndicator
color = { TEXT_COLOR }
size = 'large' />
<Text style = { navigationStyles.connectingScreenText }>
{ t('connectingOverlay.joiningRoom') }
</Text>
</SafeAreaView>
</View>
</JitsiScreen>
);
};
export default ConnectingPage;

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { GestureResponderEvent } from 'react-native';
import { StyleType } from '../../../base/styles/functions.native';
import Button from '../../../base/ui/components/native/Button';
import IconButton from '../../../base/ui/components/native/IconButton';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import { navigationStyles } from './styles';
interface IProps {
/**
* Icon button color.
*/
color?: string;
/**
* Is the button disabled?
*/
disabled?: boolean;
/**
* ID of the header navigation button.
*/
id?: string;
/**
* Label of the button.
*/
label?: string;
/**
* Callback to invoke when the {@code HeaderNavigationButton} is clicked/pressed.
*/
onPress?: (e?: GestureResponderEvent | React.MouseEvent) => void;
/**
* The ImageSource to be rendered as image.
*/
src?: any;
/**
* Style of the button.
*/
style?: StyleType;
/**
* Header has two actions.
*/
twoActions?: boolean;
}
const HeaderNavigationButton = ({ color, id, disabled, label, onPress, src, style, twoActions }: IProps) => {
let btnStyle;
let labelStyle;
if (disabled) {
btnStyle = navigationStyles.headerNavigationButtonDisabled;
labelStyle = twoActions
? navigationStyles.headerNavigationButtonLabelBoldDisabled
: navigationStyles.headerNavigationButtonLabelDisabled;
} else {
btnStyle = navigationStyles.headerNavigationButton;
labelStyle = twoActions
? navigationStyles.headerNavigationButtonLabelBold
: navigationStyles.headerNavigationButtonLabel;
}
return (
<>
{
src ? (
<IconButton
color = { color }
id = { id }
onPress = { onPress }
size = { 24 }
src = { src }
style = { [
navigationStyles.headerNavigationButtonIcon,
style
] } />
) : (
<Button
disabled = { disabled }
id = { id }
labelKey = { label }
labelStyle = { labelStyle }
onClick = { onPress }
style = { [
btnStyle,
style
] }
type = { BUTTON_TYPES.TERTIARY } />
)}
</>
);
};
export default HeaderNavigationButton;

View File

@@ -0,0 +1,136 @@
import { NavigationContainer, Theme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React, { useCallback } from 'react';
import { StatusBar } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import DialInSummary from '../../../invite/components/dial-in-summary/native/DialInSummary';
import Prejoin from '../../../prejoin/components/native/Prejoin';
import UnsafeRoomWarning from '../../../prejoin/components/native/UnsafeRoomWarning';
import { isUnsafeRoomWarningEnabled } from '../../../prejoin/functions';
import VisitorsQueue from '../../../visitors/components/native/VisitorsQueue';
// eslint-disable-next-line
// @ts-ignore
import WelcomePage from '../../../welcome/components/WelcomePage';
import { isWelcomePageEnabled } from '../../../welcome/functions';
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
import { rootNavigationRef } from '../rootNavigationContainerRef';
import { screen } from '../routes';
import {
conferenceNavigationContainerScreenOptions,
connectingScreenOptions,
dialInSummaryScreenOptions,
navigationContainerTheme,
preJoinScreenOptions,
unsafeMeetingScreenOptions,
visitorsScreenOptions,
welcomeScreenOptions
} from '../screenOptions';
import ConnectingPage from './ConnectingPage';
import ConferenceNavigationContainer
from './conference/components/ConferenceNavigationContainer';
const RootStack = createStackNavigator();
interface IProps {
/**
* Redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
* Is unsafe room warning available?
*/
isUnsafeRoomWarningAvailable: boolean;
/**
* Is welcome page available?
*/
isWelcomePageAvailable: boolean;
}
const RootNavigationContainer = ({ dispatch, isUnsafeRoomWarningAvailable, isWelcomePageAvailable }: IProps) => {
const initialRouteName = isWelcomePageAvailable
? screen.welcome.main : screen.connecting;
const onReady = useCallback(() => {
dispatch({
type: _ROOT_NAVIGATION_READY,
ready: true
});
}, [ dispatch ]);
return (
<NavigationContainer
independent = { true }
onReady = { onReady }
ref = { rootNavigationRef }
theme = { navigationContainerTheme as Theme }>
<StatusBar
animated = { true }
backgroundColor = 'transparent'
barStyle = { 'light-content' }
translucent = { true } />
<RootStack.Navigator
initialRouteName = { initialRouteName }>
{
isWelcomePageAvailable
&& <>
<RootStack.Screen // @ts-ignore
component = { WelcomePage }
name = { screen.welcome.main }
options = { welcomeScreenOptions } />
<RootStack.Screen
// @ts-ignore
component = { DialInSummary }
name = { screen.dialInSummary }
options = { dialInSummaryScreenOptions } />
</>
}
<RootStack.Screen
component = { ConnectingPage }
name = { screen.connecting }
options = { connectingScreenOptions } />
<RootStack.Screen
component = { Prejoin }
name = { screen.preJoin }
options = { preJoinScreenOptions } />
{
isUnsafeRoomWarningAvailable
&& <RootStack.Screen
component = { UnsafeRoomWarning }
name = { screen.unsafeRoomWarning }
options = { unsafeMeetingScreenOptions } />
}
<RootStack.Screen
component = { VisitorsQueue }
name = { screen.visitorsQueue }
options = { visitorsScreenOptions } />
<RootStack.Screen
component = { ConferenceNavigationContainer }
name = { screen.conference.root }
options = { conferenceNavigationContainerScreenOptions } />
</RootStack.Navigator>
</NavigationContainer>
);
};
/**
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {IProps}
*/
function mapStateToProps(state: IReduxState) {
return {
isUnsafeRoomWarningAvailable: isUnsafeRoomWarningEnabled(state),
isWelcomePageAvailable: isWelcomePageEnabled(state)
};
}
export default connect(mapStateToProps)(RootNavigationContainer);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { StyleProp, Text, TextStyle, View } from 'react-native';
import { navigationStyles } from './styles';
interface ITabBarLabelCounterProps {
activeUnreadNr: boolean;
isFocused: boolean;
label: string;
nbUnread?: number;
}
export const TabBarLabelCounter = ({ activeUnreadNr, isFocused, label, nbUnread }: ITabBarLabelCounterProps) => {
const labelStyles = isFocused
? navigationStyles.unreadCounterDescriptionFocused
: navigationStyles.unreadCounterDescription;
return (
<View
style = {
navigationStyles.unreadCounterContainer as StyleProp<TextStyle> }>
<Text
style = { labelStyles }>
{ label && label }
</Text>
{
activeUnreadNr && (
<View
style = { navigationStyles.unreadCounterCircle as StyleProp<TextStyle> }>
<Text
style = { navigationStyles.unreadCounter as StyleProp<TextStyle> }>
{ nbUnread }
</Text>
</View>
)
}
</View>
);
};

View File

@@ -0,0 +1,62 @@
/* eslint-disable lines-around-comment */
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../../../app/types';
import {
getClientHeight,
getClientWidth
} from '../../../../../base/modal/components/functions';
import { setFocusedTab } from '../../../../../chat/actions.any';
import Chat from '../../../../../chat/components/native/Chat';
import { ChatTabs } from '../../../../../chat/constants';
import { resetNbUnreadPollsMessages } from '../../../../../polls/actions';
import PollsPane from '../../../../../polls/components/native/PollsPane';
import { screen } from '../../../routes';
import { chatTabBarOptions } from '../../../screenOptions';
const ChatTab = createMaterialTopTabNavigator();
const ChatAndPolls = () => {
const clientHeight = useSelector(getClientHeight);
const clientWidth = useSelector(getClientWidth);
const dispatch = useDispatch();
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
const initialRouteName = focusedTab === ChatTabs.POLLS
? screen.conference.chatandpolls.tab.polls
: screen.conference.chatandpolls.tab.chat;
return (
// @ts-ignore
<ChatTab.Navigator
backBehavior = 'none'
initialLayout = {{
height: clientHeight,
width: clientWidth
}}
initialRouteName = { initialRouteName }
screenOptions = { chatTabBarOptions }>
<ChatTab.Screen
component = { Chat }
listeners = {{
tabPress: () => {
dispatch(setFocusedTab(ChatTabs.CHAT));
}
}}
name = { screen.conference.chatandpolls.tab.chat } />
<ChatTab.Screen
component = { PollsPane }
listeners = {{
tabPress: () => {
dispatch(setFocusedTab(ChatTabs.POLLS));
dispatch(resetNbUnreadPollsMessages);
}
}}
name = { screen.conference.chatandpolls.tab.polls } />
</ChatTab.Navigator>
);
};
export default ChatAndPolls;

View File

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

View File

@@ -0,0 +1,232 @@
/* eslint-disable lines-around-comment */
import { NavigationContainer, Theme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import BreakoutRooms
// @ts-ignore
from '../../../../../breakout-rooms/components/native/BreakoutRooms';
// @ts-ignore
import Chat from '../../../../../chat/components/native/Chat';
// @ts-ignore
import Conference from '../../../../../conference/components/native/Conference';
// @ts-ignore
import CarMode from '../../../../../conference/components/native/carmode/CarMode';
// @ts-ignore
import { arePollsDisabled } from '../../../../../conference/functions';
// @ts-ignore
import SharedDocument from '../../../../../etherpad/components/native/SharedDocument';
// @ts-ignore
import GifsMenu from '../../../../../gifs/components/native/GifsMenu';
import AddPeopleDialog
// @ts-ignore
from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
// @ts-ignore
import ParticipantsPane from '../../../../../participants-pane/components/native/ParticipantsPane';
// @ts-ignore
import StartLiveStreamDialog from '../../../../../recording/components/LiveStream/native/StartLiveStreamDialog';
import StartRecordingDialog
// @ts-ignore
from '../../../../../recording/components/Recording/native/StartRecordingDialog';
import SalesforceLinkDialog
// @ts-ignore
from '../../../../../salesforce/components/native/SalesforceLinkDialog';
import SecurityDialog
// @ts-ignore
from '../../../../../security/components/security-dialog/native/SecurityDialog';
import SpeakerStats
// @ts-ignore
from '../../../../../speaker-stats/components/native/SpeakerStats';
import LanguageSelectorDialog
// @ts-ignore
from '../../../../../subtitles/components/native/LanguageSelectorDialog';
import Whiteboard from '../../../../../whiteboard/components/native/Whiteboard';
// @ts-ignore
import { screen } from '../../../routes';
import {
breakoutRoomsScreenOptions,
carmodeScreenOptions,
chatScreenOptions,
conferenceScreenOptions,
gifsMenuOptions,
inviteScreenOptions,
liveStreamScreenOptions,
lobbyNavigationContainerScreenOptions,
navigationContainerTheme,
participantsScreenOptions,
recordingScreenOptions,
salesforceScreenOptions,
securityScreenOptions,
settingsNavigationContainerScreenOptions,
sharedDocumentScreenOptions,
speakerStatsScreenOptions,
subtitlesScreenOptions,
whiteboardScreenOptions
// @ts-ignore
} from '../../../screenOptions';
// @ts-ignore
import ChatAndPollsNavigator from '../../chat/components/ChatAndPollsNavigator';
// @ts-ignore
import LobbyNavigationContainer from '../../lobby/components/LobbyNavigationContainer';
// @ts-ignore
import SettingsNavigationContainer from '../../settings/components/SettingsNavigationContainer';
import {
conferenceNavigationRef
// @ts-ignore
} from '../ConferenceNavigationContainerRef';
const ConferenceStack = createStackNavigator();
const ConferenceNavigationContainer = () => {
const isPollsDisabled = useSelector(arePollsDisabled);
let ChatScreen;
let chatScreenName;
let chatTitleString;
if (isPollsDisabled) {
ChatScreen = Chat;
chatScreenName = screen.conference.chat;
chatTitleString = 'chat.title';
} else {
ChatScreen = ChatAndPollsNavigator;
chatScreenName = screen.conference.chatandpolls.main;
chatTitleString = 'chat.titleWithPolls';
}
const { t } = useTranslation();
return (
<NavigationContainer
independent = { true }
ref = { conferenceNavigationRef }
theme = { navigationContainerTheme as Theme }>
<ConferenceStack.Navigator
screenOptions = {{
presentation: 'modal'
}}>
<ConferenceStack.Screen
component = { Conference }
name = { screen.conference.main }
options = { conferenceScreenOptions } />
<ConferenceStack.Screen
component = { ChatScreen }
name = { chatScreenName }
options = {{
...chatScreenOptions,
title: t(chatTitleString)
}} />
<ConferenceStack.Screen
component = { ParticipantsPane }
name = { screen.conference.participants }
options = {{
...participantsScreenOptions,
title: t('participantsPane.title')
}} />
<ConferenceStack.Screen
component = { SecurityDialog }
name = { screen.conference.security }
options = {{
...securityScreenOptions,
title: t('security.title')
}} />
<ConferenceStack.Screen
component = { StartRecordingDialog }
name = { screen.conference.recording }
options = {{
...recordingScreenOptions,
title: t('recording.title')
}} />
<ConferenceStack.Screen
component = { StartLiveStreamDialog }
name = { screen.conference.liveStream }
options = {{
...liveStreamScreenOptions,
title: t('liveStreaming.title')
}} />
<ConferenceStack.Screen
component = { SpeakerStats }
name = { screen.conference.speakerStats }
options = {{
...speakerStatsScreenOptions,
title: t('speakerStats.speakerStats')
}} />
<ConferenceStack.Screen
component = { SalesforceLinkDialog }
name = { screen.conference.salesforce }
options = {{
...salesforceScreenOptions,
title: t('notify.linkToSalesforce')
}} />
<ConferenceStack.Screen
component = { GifsMenu }
name = { screen.conference.gifsMenu }
options = {{
...gifsMenuOptions,
title: t('notify.gifsMenu')
}} />
<ConferenceStack.Screen
component = { LobbyNavigationContainer }
name = { screen.lobby.root }
options = {{
...lobbyNavigationContainerScreenOptions,
title: t('lobby.title')
}} />
<ConferenceStack.Screen
component = { AddPeopleDialog }
name = { screen.conference.invite }
options = {{
...inviteScreenOptions,
title: t('addPeople.add')
}} />
<ConferenceStack.Screen
component = { SharedDocument }
name = { screen.conference.sharedDocument }
options = {{
...sharedDocumentScreenOptions,
title: t('documentSharing.title')
}} />
<ConferenceStack.Screen
// @ts-ignore
component = { SettingsNavigationContainer }
name = { screen.settings.main }
options = { settingsNavigationContainerScreenOptions } />
<ConferenceStack.Screen
// @ts-ignore
component = { CarMode }
name = { screen.conference.carmode }
options = {{
...carmodeScreenOptions,
title: t('carmode.labels.title')
}} />
<ConferenceStack.Screen
component = { LanguageSelectorDialog }
name = { screen.conference.subtitles }
options = {{
...subtitlesScreenOptions,
title: t('transcribing.subtitles')
}} />
<ConferenceStack.Screen
component = { BreakoutRooms }
name = { screen.conference.breakoutRooms }
options = {{
...breakoutRoomsScreenOptions,
title: t('breakoutRooms.title')
}} />
<ConferenceStack.Screen
// @ts-ignore
component = { Whiteboard }
name = { screen.conference.whiteboard }
options = {{
...whiteboardScreenOptions,
title: t('whiteboard.screenTitle')
}} />
</ConferenceStack.Navigator>
</NavigationContainer>
);
};
export default ConferenceNavigationContainer;

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,91 @@
import { NavigationContainer, Theme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import LanguageSelectView from '../../../../../settings/components/native/LanguageSelectView';
import ProfileView from '../../../../../settings/components/native/ProfileView';
import SettingsView
from '../../../../../settings/components/native/SettingsView';
import { screen } from '../../../routes';
import {
languageSelectScreenOptions,
navigationContainerTheme,
profileSettingsScreenOptions,
settingsScreenOptions,
welcomeScreenOptions
} from '../../../screenOptions';
import {
settingsNavigationContainerRef
} from '../SettingsNavigationContainerRef';
const SettingsStack = createStackNavigator();
/**
* The type of the React {@code Component} props of {@link SettingsNavigationContainer}.
*/
interface IProps {
/**
* Is the navigator part of Welcome page?
*/
isInWelcomePage?: boolean | undefined;
}
const SettingsNavigationContainer = ({ isInWelcomePage }: IProps) => {
const baseSettingsScreenOptions = isInWelcomePage ? welcomeScreenOptions : settingsScreenOptions;
const { t } = useTranslation();
const SettingsScreen = useCallback(() =>
(
<SettingsView
isInWelcomePage = { isInWelcomePage } />
), []);
const ProfileScreen = useCallback(() =>
(<ProfileView
isInWelcomePage = { isInWelcomePage } />)
, []);
const LanguageSelectScreen = useCallback(() =>
(<LanguageSelectView
isInWelcomePage = { isInWelcomePage } />)
, []);
return (
<NavigationContainer
independent = { true }
ref = { settingsNavigationContainerRef }
theme = { navigationContainerTheme as Theme }>
<SettingsStack.Navigator
initialRouteName = { screen.settings.main }>
<SettingsStack.Screen
name = { screen.settings.main }
options = {{
...baseSettingsScreenOptions,
title: t('settings.title')
}}>
{ SettingsScreen }
</SettingsStack.Screen>
<SettingsStack.Screen
component = { ProfileScreen }
name = { screen.settings.profile }
options = {{
...profileSettingsScreenOptions,
title: t('settingsView.profileSection')
}} />
<SettingsStack.Screen
component = { LanguageSelectScreen }
name = { screen.settings.language }
options = {{
...languageSelectScreenOptions,
title: t('settings.language')
}} />
</SettingsStack.Navigator>
</NavigationContainer>
);
};
export default SettingsNavigationContainer;

View File

@@ -0,0 +1,114 @@
import { BoxModel } from '../../../base/styles/components/styles/BoxModel';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export const TEXT_COLOR = BaseTheme.palette.text01;
const unreadCounterDescription = {
...BaseTheme.typography.bodyShortBoldLarge,
color: BaseTheme.palette.text03
};
const HEADER_ACTION_BUTTON_SIZE = 16;
const headerNavigationButtonLabel = {
color: BaseTheme.palette.link01,
fontSize: HEADER_ACTION_BUTTON_SIZE,
lineHeight: BaseTheme.spacing[3]
};
const headerNavigationButton = {
borderRadius: BaseTheme.shape.borderRadius,
height: BaseTheme.spacing[6],
marginLeft: BaseTheme.spacing[3]
};
/**
* Styles of the navigation feature.
*/
export const navigationStyles = {
connectingScreenContainer: {
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1
},
connectingScreenContent: {
alignItems: 'center',
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
},
connectingScreenIndicator: {
margin: BoxModel.margin
},
connectingScreenText: {
color: TEXT_COLOR
},
headerNavigationButton: {
...headerNavigationButton
},
headerNavigationButtonIcon: {
...headerNavigationButton,
padding: BaseTheme.spacing[2]
},
headerNavigationButtonDisabled: {
backgroundColor: 'transparent',
marginLeft: BaseTheme.spacing[2]
},
headerNavigationButtonLabel: {
...headerNavigationButtonLabel
},
headerNavigationButtonLabelDisabled: {
...headerNavigationButtonLabel,
color: BaseTheme.palette.text03
},
headerNavigationButtonLabelBold: {
...headerNavigationButtonLabel,
...BaseTheme.typography.bodyShortRegularLarge
},
headerNavigationButtonLabelBoldDisabled: {
...headerNavigationButtonLabel,
...BaseTheme.typography.bodyShortRegularLarge,
color: BaseTheme.palette.text03
},
unreadCounterContainer: {
alignItems: 'center',
display: 'flex',
flexDirection: 'row'
},
unreadCounterDescription: {
...unreadCounterDescription
},
unreadCounterDescriptionFocused: {
...unreadCounterDescription,
color: BaseTheme.palette.text01
},
unreadCounterCircle: {
backgroundColor: BaseTheme.palette.warning01,
borderRadius: BaseTheme.spacing[4] / 2,
height: BaseTheme.spacing[4],
justifyContent: 'center',
marginLeft: BaseTheme.spacing[2],
width: BaseTheme.spacing[4]
},
unreadCounter: {
...BaseTheme.typography.bodyShortBold,
alignSelf: 'center',
color: BaseTheme.palette.text04
}
};

View File

@@ -0,0 +1,120 @@
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { GestureResponderEvent } from 'react-native';
import { useSelector } from 'react-redux';
import CalendarList from '../../../../../calendar-sync/components/CalendarList.native';
import { isCalendarEnabled } from '../../../../../calendar-sync/functions.native';
import RecentList from '../../../../../recent-list/components/RecentList.native';
import {
calendarListTabBarOptions,
recentListTabBarOptions,
settingsTabBarOptions,
tabBarOptions
} from '../../../../../welcome/constants';
import { screen } from '../../../routes';
import SettingsNavigationContainer
from '../../settings/components/SettingsNavigationContainer';
const WelcomePage = createBottomTabNavigator();
/**
* The type of the React {@code Component} props of {@link WelcomePageTabs}.
*/
interface IProps {
/**
* Renders the lists disabled.
*/
disabled: boolean;
/**
* Callback to be invoked when pressing the list container.
*/
onListContainerPress?: (e?: GestureResponderEvent) => void;
/**
* Callback to be invoked when settings screen is focused.
*/
onSettingsScreenFocused: Function;
}
const WelcomePageTabs = ({ disabled, onListContainerPress, onSettingsScreenFocused }: IProps) => {
const { t } = useTranslation();
const RecentListScreen = useCallback(() =>
(
<RecentList
disabled = { disabled }
onListContainerPress = { onListContainerPress } />
), []);
const calendarEnabled = useSelector(isCalendarEnabled);
const CalendarListScreen = useCallback(() =>
(
<CalendarList
disabled = { disabled } />
), []);
const SettingsScreen = useCallback(() =>
(
<SettingsNavigationContainer
isInWelcomePage = { true } />
), []);
return (
<WelcomePage.Navigator
backBehavior = { 'none' }
screenOptions = {{
...tabBarOptions,
headerShown: false
}}>
<WelcomePage.Screen
listeners = {{
tabPress: () => {
onSettingsScreenFocused(false);
}
}}
name = { screen.welcome.tabs.recent }
options = {{
...recentListTabBarOptions,
title: t('welcomepage.recentList')
}}>
{ RecentListScreen }
</WelcomePage.Screen>
{
calendarEnabled
&& <WelcomePage.Screen
listeners = {{
tabPress: () => {
onSettingsScreenFocused(false);
}
}}
name = { screen.welcome.tabs.calendar }
options = {{
...calendarListTabBarOptions,
title: t('welcomepage.calendar')
}}>
{ CalendarListScreen }
</WelcomePage.Screen>
}
<WelcomePage.Screen
listeners = {{
tabPress: () => {
onSettingsScreenFocused(true);
}
}}
name = { screen.settings.main }
options = {{
...settingsTabBarOptions,
title: t('welcomepage.settings')
}}>
{ SettingsScreen }
</WelcomePage.Screen>
</WelcomePage.Navigator>
);
};
export default WelcomePageTabs;

View File

@@ -0,0 +1,88 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { GestureResponderEvent, Platform } from 'react-native';
import { useDispatch } from 'react-redux';
import { appNavigate } from '../../app/actions.native';
import { IStateful } from '../../base/app/types';
import { PREJOIN_PAGE_ENABLED } from '../../base/flags/constants';
import { getFeatureFlag } from '../../base/flags/functions';
import { IconCloseLarge } from '../../base/icons/svg';
import { toState } from '../../base/redux/functions';
import { cancelKnocking } from '../../lobby/actions.native';
import { isPrejoinEnabledInConfig } from '../../prejoin/functions.native';
import HeaderNavigationButton from './components/HeaderNavigationButton';
/**
* Close icon/text button based on platform.
*
* @param {Function} goBack - Goes back to the previous screen function.
* @returns {React.Component}
*/
export function screenHeaderCloseButton(goBack: (e?: GestureResponderEvent | React.MouseEvent) => void) {
const { t } = useTranslation();
if (Platform.OS === 'ios') {
return (
<HeaderNavigationButton
id = { 'close-screen-button' }
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
id = { 'close-screen-button' }
onPress = { goBack }
src = { IconCloseLarge } />
);
}
/**
* 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: IStateful) {
const state = toState(stateful);
return getFeatureFlag(state, PREJOIN_PAGE_ENABLED, isPrejoinEnabledInConfig(state));
}
/**
* 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
id = { 'close-screen-button' }
label = { t('dialog.close') }
onPress = { goBack } />
);
}
return (
<HeaderNavigationButton
id = { 'close-screen-button' }
onPress = { goBack }
src = { IconCloseLarge } />
);
}

View File

@@ -0,0 +1,38 @@
import { AnyAction } from 'redux';
import { appNavigate } from '../../app/actions.native';
import { IStore } from '../../app/types';
import { CONFERENCE_FAILED } from '../../base/conference/actionTypes';
import { JitsiConferenceErrors } from '../../base/lib-jitsi-meet';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case CONFERENCE_FAILED:
return _conferenceFailed(store, next, action);
}
return next(action);
});
/**
* Function to handle the conference failed event and navigate the user to the lobby screen
* based on the failure reason.
*
* @param {Object} store - The Redux store.
* @param {Function} next - The Redux next function.
* @param {Object} action - The Redux action.
* @returns {Object}
*/
function _conferenceFailed({ dispatch }: IStore, next: Function, action: AnyAction) {
const { error } = action;
// We need to cover the case where knocking participant
// is rejected from entering the conference
if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
dispatch(appNavigate(undefined));
}
return next(action);
}

View File

@@ -0,0 +1,51 @@
import { NavigationContainerRef } from '@react-navigation/native';
import React from 'react';
import { IStore } from '../../app/types';
import { IStateful } from '../../base/app/types';
import { toState } from '../../base/redux/functions';
import { isWelcomePageEnabled } from '../../welcome/functions';
import { _sendReadyToClose } from '../external-api/functions';
import { screen } from './routes';
export const rootNavigationRef = React.createRef<NavigationContainerRef<any>>();
/**
* 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 navigateRoot(name: string, params?: Object) {
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: IStateful, dispatch: IStore['dispatch']) {
const state = toState(stateful);
if (isWelcomePageEnabled(state)) {
navigateRoot(screen.welcome.main);
} else {
// For JitsiSDK, WelcomePage is not available
_sendReadyToClose(dispatch);
}
}

View File

@@ -0,0 +1,55 @@
export const screen = {
conference: {
breakoutRooms: 'Breakout Rooms',
carmode: 'Car Mode',
chat: 'Chat',
chatandpolls: {
main: 'Chat and Polls',
tab: {
chat: 'Chat',
polls: 'Polls'
}
},
container: 'Conference container',
gifsMenu: 'GIPHY',
invite: 'Invite',
liveStream: 'Live stream',
main: 'Conference',
participants: 'Participants',
root: 'Conference root',
recording: 'Recording',
salesforce: 'Link to Salesforce',
security: 'Security Options',
sharedDocument: 'Shared document',
speakerStats: 'Speaker Stats',
subtitles: 'Subtitles',
whiteboard: 'Whiteboard'
},
connecting: 'Connecting',
dialInSummary: 'Dial-In Info',
preJoin: 'Pre-Join',
lobby: {
chat: 'Lobby chat',
main: 'Lobby',
root: 'Lobby root'
},
settings: {
language: 'Language',
links: {
help: 'Help',
privacy: 'Privacy',
terms: 'Terms'
},
main: 'Settings',
profile: 'Profile'
},
unsafeRoomWarning: 'Unsafe Room Warning',
visitorsQueue: 'Visitors Queue',
welcome: {
main: 'Welcome',
tabs: {
calendar: 'Calendar',
recent: 'Recent'
}
}
};

View File

@@ -0,0 +1,260 @@
import { TransitionPresets } from '@react-navigation/stack';
import { Platform } from 'react-native';
import BaseTheme from '../../base/ui/components/BaseTheme.native';
import { goBack } from './components/conference/ConferenceNavigationContainerRef';
import { goBack as goBackToLobbyScreen } from './components/lobby/LobbyNavigationContainerRef';
import { lobbyScreenHeaderCloseButton, screenHeaderCloseButton } from './functions';
import { goBack as goBackToWelcomeScreen } from './rootNavigationContainerRef';
/**
* Default modal transition for the current platform.
*/
export const modalPresentation = Platform.select({
ios: TransitionPresets.ModalPresentationIOS,
default: TransitionPresets.DefaultTransition
});
/**
* Screen options and transition types.
*/
export const fullScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: false,
headerShown: false
};
/**
* Navigation container theme.
*/
export const navigationContainerTheme = {
colors: {
background: BaseTheme.palette.uiBackground
}
};
/**
* Screen options for welcome page.
*/
export const welcomeScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: false,
headerShown: true,
headerStyle: {
backgroundColor: BaseTheme.palette.ui01
},
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for conference.
*/
export const conferenceScreenOptions = fullScreenOptions;
/**
* Screen options for visitors queue.
*/
export const visitorsScreenOptions = fullScreenOptions;
/**
* Tab bar options for chat screen.
*/
export const chatTabBarOptions = {
swipeEnabled: false,
tabBarIndicatorStyle: {
backgroundColor: BaseTheme.palette.link01Active
},
tabBarStyle: {
backgroundColor: BaseTheme.palette.ui01,
borderBottomColor: BaseTheme.palette.ui06,
borderBottomWidth: 0.4
}
};
/**
* Screen options for presentation type modals.
*/
export const presentationScreenOptions = {
...modalPresentation,
headerBackTitleVisible: false,
headerLeft: () => screenHeaderCloseButton(goBack),
headerStatusBarHeight: 0,
headerStyle: {
backgroundColor: BaseTheme.palette.ui01
},
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for breakout rooms screen.
*/
export const breakoutRoomsScreenOptions = presentationScreenOptions;
/**
* Screen options for car mode.
*/
export const carmodeScreenOptions = presentationScreenOptions;
/**
* Screen options for chat.
*/
export const chatScreenOptions = presentationScreenOptions;
/**
* Dial-IN Info screen options and transition types.
*/
export const dialInSummaryScreenOptions = {
...presentationScreenOptions,
headerLeft: () => screenHeaderCloseButton(goBackToWelcomeScreen)
};
/**
* Screen options for invite modal.
*/
export const inviteScreenOptions = presentationScreenOptions;
/**
* Screen options for live stream modal.
*/
export const liveStreamScreenOptions = presentationScreenOptions;
/**
* Screen options for participants modal.
*/
export const participantsScreenOptions = presentationScreenOptions;
/**
* Screen options for speaker stats modal.
*/
export const speakerStatsScreenOptions = presentationScreenOptions;
/**
* Screen options for security options modal.
*/
export const securityScreenOptions = presentationScreenOptions;
/**
* Screen options for recording modal.
*/
export const recordingScreenOptions = presentationScreenOptions;
/**
* Screen options for subtitles modal.
*/
export const subtitlesScreenOptions = presentationScreenOptions;
/**
* Screen options for lobby modal.
*/
export const lobbyScreenOptions = {
...presentationScreenOptions,
headerLeft: () => lobbyScreenHeaderCloseButton()
};
/**
* Screen options for lobby chat modal.
*/
export const lobbyChatScreenOptions = {
...presentationScreenOptions,
headerLeft: () => screenHeaderCloseButton(goBackToLobbyScreen)
};
/**
* Screen options for salesforce link modal.
*/
export const salesforceScreenOptions = presentationScreenOptions;
/**
* Screen options for GIPHY integration modal.
*/
export const gifsMenuOptions = presentationScreenOptions;
/**
* Screen options for shared document.
*/
export const sharedDocumentScreenOptions = presentationScreenOptions;
/**
* Screen options for settings modal.
*/
export const settingsScreenOptions = presentationScreenOptions;
/**
* Screen options for connecting screen.
*/
export const connectingScreenOptions = {
gestureEnabled: false,
headerShown: false
};
/**
* Screen options for the whiteboard screen.
*/
export const whiteboardScreenOptions = presentationScreenOptions;
/**
* Screen options for pre-join screen.
*/
export const preJoinScreenOptions = {
gestureEnabled: false,
headerStyle: {
backgroundColor: BaseTheme.palette.ui01
},
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for profile setting.
*/
export const profileSettingsScreenOptions = {
headerStyle: {
backgroundColor: BaseTheme.palette.ui01
},
headerTitleStyle: {
color: BaseTheme.palette.text01
},
headerBackTitleVisible: false
};
/**
* Screen options for language select screen.
*/
export const languageSelectScreenOptions = profileSettingsScreenOptions;
/**
* Screen options for pre-join screen.
*/
export const unsafeMeetingScreenOptions = preJoinScreenOptions;
/**
* 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
};
/**
* Screen options for settings navigation container screen.
*/
export const settingsNavigationContainerScreenOptions = {
...modalPresentation,
gestureEnabled: true,
headerShown: false
};