feat(carmode) Add carmode screen
This commit is contained in:
parent
8e67c8e74f
commit
ac965b86bc
|
@ -77,6 +77,16 @@
|
|||
"refresh": "Refresh calendar",
|
||||
"today": "Today"
|
||||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": " Leave meeting",
|
||||
"selectSoundDevice": "Select sound device"
|
||||
},
|
||||
"labels": {
|
||||
"title": "Safe driving mode",
|
||||
"videoStopped": "Your video is stopped"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Enter room",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="12"/>
|
||||
</svg>
|
After Width: | Height: | Size: 124 B |
|
@ -28,6 +28,7 @@ export { default as IconChatSend } from './send.svg';
|
|||
export { default as IconChatUnread } from './chat-unread.svg';
|
||||
export { default as IconCheck } from './check.svg';
|
||||
export { default as IconCheckSolid } from './check-solid.svg';
|
||||
export { default as IconCircle } from './circle.svg';
|
||||
export { default as IconClose } from './close.svg';
|
||||
export { default as IconCloseCircle } from './close-circle.svg';
|
||||
export { default as IconCloseSolid } from './close-solid.svg';
|
||||
|
|
|
@ -7,6 +7,7 @@ import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
|
|||
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
||||
import {
|
||||
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||
SET_CAR_MODE,
|
||||
SET_TILE_VIEW,
|
||||
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
|
||||
} from '../../video-layout/actionTypes';
|
||||
|
@ -68,7 +69,7 @@ const _updateLastN = debounce(({ dispatch, getState }) => {
|
|||
if (typeof appState !== 'undefined' && appState !== 'active') {
|
||||
lastNSelected = isLocalVideoTrackDesktop(state) ? 1 : 0;
|
||||
} else if (audioOnly) {
|
||||
const { remoteScreenShares, tileViewEnabled } = state['features/video-layout'];
|
||||
const { remoteScreenShares, tileViewEnabled, carMode } = state['features/video-layout'];
|
||||
const largeVideoParticipantId = state['features/large-video'].participantId;
|
||||
const largeVideoParticipant
|
||||
= largeVideoParticipantId ? getParticipantById(state, largeVideoParticipantId) : undefined;
|
||||
|
@ -76,7 +77,7 @@ const _updateLastN = debounce(({ dispatch, getState }) => {
|
|||
// Use tileViewEnabled state from redux here instead of determining if client should be in tile
|
||||
// view since we make an exception only for screenshare when in audio-only mode. If the user unpins
|
||||
// the screenshare, lastN will be set to 0 here. It will be set to 1 if screenshare has been auto pinned.
|
||||
if (!tileViewEnabled && largeVideoParticipant && !largeVideoParticipant.local) {
|
||||
if (!carMode && !tileViewEnabled && largeVideoParticipant && !largeVideoParticipant.local) {
|
||||
lastNSelected = (remoteScreenShares || []).includes(largeVideoParticipantId) ? 1 : 0;
|
||||
} else {
|
||||
lastNSelected = 0;
|
||||
|
@ -101,6 +102,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
|
||||
case SELECT_LARGE_VIDEO_PARTICIPANT:
|
||||
case SET_AUDIO_ONLY:
|
||||
case SET_CAR_MODE:
|
||||
case SET_FILMSTRIP_ENABLED:
|
||||
case SET_TILE_VIEW:
|
||||
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED:
|
||||
|
|
|
@ -4,20 +4,20 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { Chat } from '../..';
|
||||
import {
|
||||
getClientHeight,
|
||||
getClientWidth
|
||||
} from '../../../../../base/modal/components/functions.native';
|
||||
import BaseTheme from '../../../../../base/ui/components/BaseTheme.native';
|
||||
import { Chat } from '../../../../../chat';
|
||||
import { PollsPane } from '../../../../../polls/components';
|
||||
import { screen } from '../../../routes';
|
||||
import { chatTabBarOptions } from '../../../screenOptions';
|
||||
} from '../../../base/modal/components/functions.native';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { chatTabBarOptions } from '../../../mobile/navigation/screenOptions';
|
||||
import { PollsPane } from '../../../polls/components';
|
||||
|
||||
const ChatTab = createMaterialTopTabNavigator();
|
||||
|
||||
|
||||
const ChatAndPollsNavigationContainer = () => {
|
||||
const ChatAndPolls = () => {
|
||||
const clientHeight = useSelector(getClientHeight);
|
||||
const clientWidth = useSelector(getClientWidth);
|
||||
|
||||
|
@ -44,4 +44,4 @@ const ChatAndPollsNavigationContainer = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default ChatAndPollsNavigationContainer;
|
||||
export default ChatAndPolls;
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
|
||||
export { default as Chat } from './Chat';
|
||||
export { default as ChatAndPolls } from './ChatAndPolls';
|
||||
export { default as ChatButton } from './ChatButton';
|
||||
export { default as ChatPrivacyDialog } from './ChatPrivacyDialog';
|
||||
|
|
|
@ -1 +1,30 @@
|
|||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
* Returns whether the conference is in connecting state.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} Whether conference is connecting.
|
||||
*/
|
||||
export const isConnecting = (state: Object) => {
|
||||
const { connecting, connection } = state['features/base/connection'];
|
||||
const {
|
||||
conference,
|
||||
joining,
|
||||
membersOnly,
|
||||
leaving
|
||||
} = state['features/base/conference'];
|
||||
|
||||
// XXX There is a window of time between the successful establishment of the
|
||||
// XMPP connection and the subsequent commencement of joining the MUC during
|
||||
// which the app does not appear to be doing anything according to the redux
|
||||
// state. In order to not toggle the _connecting props during the window of
|
||||
// time in question, define _connecting as follows:
|
||||
// - the XMPP connection is connecting, or
|
||||
// - the XMPP connection is connected and the conference is joining, or
|
||||
// - the XMPP connection is connected and we have no conference yet, nor we
|
||||
// are leaving one.
|
||||
return Boolean(
|
||||
connecting || (connection && (!membersOnly && (joining || (!conference && !leaving))))
|
||||
);
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
abstractMapStateToProps
|
||||
} from '../AbstractConference';
|
||||
import type { AbstractProps } from '../AbstractConference';
|
||||
import { isConnecting } from '../functions';
|
||||
|
||||
import AlwaysOnLabels from './AlwaysOnLabels';
|
||||
import ExpandedLabelPopup from './ExpandedLabelPopup';
|
||||
|
@ -496,34 +497,15 @@ class Conference extends AbstractConference<Props, State> {
|
|||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { connecting, connection } = state['features/base/connection'];
|
||||
const {
|
||||
conference,
|
||||
joining,
|
||||
membersOnly,
|
||||
leaving
|
||||
} = state['features/base/conference'];
|
||||
const { isOpen } = state['features/participants-pane'];
|
||||
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
|
||||
const participantCount = getParticipantCount(state);
|
||||
|
||||
// XXX There is a window of time between the successful establishment of the
|
||||
// XMPP connection and the subsequent commencement of joining the MUC during
|
||||
// which the app does not appear to be doing anything according to the redux
|
||||
// state. In order to not toggle the _connecting props during the window of
|
||||
// time in question, define _connecting as follows:
|
||||
// - the XMPP connection is connecting, or
|
||||
// - the XMPP connection is connected and the conference is joining, or
|
||||
// - the XMPP connection is connected and we have no conference yet, nor we
|
||||
// are leaving one.
|
||||
const connecting_
|
||||
= connecting || (connection && (!membersOnly && (joining || (!conference && !leaving))));
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_aspectRatio: aspectRatio,
|
||||
_calendarEnabled: isCalendarEnabled(state),
|
||||
_connecting: Boolean(connecting_),
|
||||
_connecting: isConnecting(state),
|
||||
_filmstripVisible: isFilmstripVisible(state),
|
||||
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
|
||||
_isOneToOneConference: Boolean(participantCount === 2),
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
import { TypedNavigator, useIsFocused } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { Chat, ChatAndPolls } from '../../../chat';
|
||||
import { SharedDocument } from '../../../etherpad';
|
||||
import { GifsMenu } from '../../../gifs/components';
|
||||
import AddPeopleDialog
|
||||
from '../../../invite/components/add-people-dialog/native/AddPeopleDialog';
|
||||
import LobbyNavigationContainer
|
||||
from '../../../mobile/navigation/components/lobby/components/LobbyNavigationContainer';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import {
|
||||
chatScreenOptions,
|
||||
conferenceScreenOptions,
|
||||
gifsMenuOptions,
|
||||
inviteScreenOptions,
|
||||
liveStreamScreenOptions,
|
||||
participantsScreenOptions,
|
||||
recordingScreenOptions,
|
||||
salesforceScreenOptions,
|
||||
securityScreenOptions,
|
||||
sharedDocumentScreenOptions,
|
||||
speakerStatsScreenOptions
|
||||
} from '../../../mobile/navigation/screenOptions';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components/native';
|
||||
import { StartLiveStreamDialog } from '../../../recording';
|
||||
import { StartRecordingDialog }
|
||||
from '../../../recording/components/Recording/native';
|
||||
import SalesforceLinkDialog
|
||||
from '../../../salesforce/components/native/SalesforceLinkDialog';
|
||||
import SecurityDialog
|
||||
from '../../../security/components/security-dialog/native/SecurityDialog';
|
||||
import SpeakerStats
|
||||
from '../../../speaker-stats/components/native/SpeakerStats';
|
||||
import { setIsCarmode } from '../../../video-layout/actions';
|
||||
import { getDisablePolls } from '../../functions';
|
||||
|
||||
import Conference from './Conference';
|
||||
|
||||
const ConferenceStack : TypedNavigator = createStackNavigator();
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback on component focused.
|
||||
* Passes the route name to the embedder.
|
||||
*/
|
||||
onFocused: Function
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The main conference screen navigator.
|
||||
*
|
||||
* @param {Props} props - The React props passed to this component.
|
||||
* @returns {JSX.Element} - The conference tab.
|
||||
*/
|
||||
const ConferenceTab = ({ onFocused }: Props) : JSX.Element => {
|
||||
const isFocused = useIsFocused();
|
||||
const dispatch = useDispatch();
|
||||
const isPollsDisabled = useSelector(getDisablePolls);
|
||||
let ChatScreen;
|
||||
let chatScreenName;
|
||||
let chatTitleString;
|
||||
|
||||
if (isPollsDisabled) {
|
||||
ChatScreen = Chat;
|
||||
chatScreenName = screen.conference.chat;
|
||||
chatTitleString = 'chat.title';
|
||||
} else {
|
||||
ChatScreen = ChatAndPolls;
|
||||
chatScreenName = screen.conference.chatandpolls.main;
|
||||
chatTitleString = 'chat.titleWithPolls';
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
dispatch(setIsCarmode(false));
|
||||
onFocused(screen.conference.container);
|
||||
}
|
||||
}, [ isFocused ]);
|
||||
|
||||
return (
|
||||
<ConferenceStack.Navigator
|
||||
initialRouteName = { screen.conference.main }
|
||||
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.header')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { SecurityDialog }
|
||||
name = { screen.conference.security }
|
||||
options = {{
|
||||
...securityScreenOptions,
|
||||
title: t('security.header')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { StartRecordingDialog }
|
||||
name = { screen.conference.recording }
|
||||
options = {{
|
||||
...recordingScreenOptions
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { StartLiveStreamDialog }
|
||||
name = { screen.conference.liveStream }
|
||||
options = {{
|
||||
...liveStreamScreenOptions
|
||||
}} />
|
||||
<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 = {{
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
}} />
|
||||
<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.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConferenceTab;
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Icon, IconVolumeEmpty } from '../../../../base/icons';
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/**
|
||||
* React component for Audio icon.
|
||||
*
|
||||
* @returns {JSX.Element} - the Audio icon.
|
||||
*
|
||||
*/
|
||||
const AudioIcon = () : JSX.Element => (<Icon
|
||||
color = { BaseTheme.palette.text06 }
|
||||
size = { 20 }
|
||||
src = { IconVolumeEmpty } />);
|
||||
|
||||
export default AudioIcon;
|
|
@ -0,0 +1,93 @@
|
|||
import { useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, SafeAreaView, View } from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { batch, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { setAudioOnly } from '../../../../base/audio-only';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../../base/react';
|
||||
import { screen } from '../../../../mobile/navigation/routes';
|
||||
import { setIsCarmode } from '../../../../video-layout/actions';
|
||||
import ConferenceTimer from '../../ConferenceTimer';
|
||||
import { isConnecting } from '../../functions';
|
||||
|
||||
import EndMeetingButton from './EndMeetingButton';
|
||||
import MicrophoneButton from './MicrophoneButton';
|
||||
import SoundDeviceButton from './SoundDeviceButton';
|
||||
import TitleBar from './TitleBar';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback on component focused.
|
||||
* Passes the route name to the embedder.
|
||||
*/
|
||||
onFocused: Function
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the carmode tab.
|
||||
*
|
||||
* @param { Props } - - The component's props.
|
||||
* @returns { JSX.Element} - The carmode tab.
|
||||
*/
|
||||
const CarmodeTab = ({ onFocused }: Props): JSX.Element => {
|
||||
const isFocused = useIsFocused();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const connecting = useSelector(isConnecting);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
batch(() => {
|
||||
dispatch(setAudioOnly(true));
|
||||
dispatch(setIsCarmode(true));
|
||||
});
|
||||
|
||||
onFocused(screen.car);
|
||||
}
|
||||
}, [ isFocused ]);
|
||||
|
||||
return (
|
||||
<Container style = { styles.conference }>
|
||||
{/*
|
||||
* The activity/loading indicator goes above everything, except
|
||||
* the toolbox/toolbars and the dialogs.
|
||||
*/
|
||||
connecting
|
||||
&& <TintedView>
|
||||
<LoadingIndicator />
|
||||
</TintedView>
|
||||
}
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.titleBarSafeViewColor }>
|
||||
<View style = { styles.titleView }>
|
||||
<Text style = { styles.title }>
|
||||
{t('carmode.labels.title')}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style = { styles.titleBar }>
|
||||
<TitleBar />
|
||||
</View>
|
||||
<ConferenceTimer textStyle = { styles.roomTimer } />
|
||||
</SafeAreaView>
|
||||
<MicrophoneButton />
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.bottomContainer }>
|
||||
<Text style = { styles.videoStoppedLabel }>
|
||||
{t('carmode.labels.videoStopped')}
|
||||
</Text>
|
||||
<SoundDeviceButton />
|
||||
<EndMeetingButton />
|
||||
</SafeAreaView>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default withSafeAreaInsets(CarmodeTab);
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../../analytics';
|
||||
import { appNavigate } from '../../../../app/actions';
|
||||
|
||||
import EndMeetingIcon from './EndMeetingIcon';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Button for ending meeting from carmode.
|
||||
*
|
||||
* @returns {JSX.Element} - The end meeting button.
|
||||
*/
|
||||
const EndMeetingButton = () : JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
dispatch(appNavigate(undefined));
|
||||
}, [ dispatch ]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { t('carmode.actions.leaveMeeting') }
|
||||
children = { t('carmode.actions.leaveMeeting') }
|
||||
icon = { EndMeetingIcon }
|
||||
labelStyle = { styles.endMeetingButtonLabel }
|
||||
mode = 'contained'
|
||||
onPress = { onSelect }
|
||||
style = { styles.endMeetingButton } />
|
||||
);
|
||||
};
|
||||
|
||||
export default EndMeetingButton;
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Icon, IconHangup } from '../../../../base/icons';
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/**
|
||||
* Implements an end meeting icon.
|
||||
*
|
||||
* @returns {JSX.Element} - the end meeting icon.
|
||||
*/
|
||||
const EndMeetingIcon = () : JSX.Element => (<Icon
|
||||
color = { BaseTheme.palette.icon01 }
|
||||
size = { 20 }
|
||||
src = { IconHangup } />);
|
||||
|
||||
export default EndMeetingIcon;
|
|
@ -0,0 +1,52 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { View, TouchableOpacity } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { getFeatureFlag, AUDIO_MUTE_BUTTON_ENABLED } from '../../../../base/flags';
|
||||
import { Icon, IconMicrophone, IconMicrophoneEmptySlash } from '../../../../base/icons';
|
||||
import { MEDIA_TYPE } from '../../../../base/media';
|
||||
import { isLocalTrackMuted } from '../../../../base/tracks';
|
||||
import { isAudioMuteButtonDisabled } from '../../../../toolbox/functions.any';
|
||||
import { muteLocal } from '../../../../video-menu/actions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Implements a round audio mute/unmute button of a custom size.
|
||||
*
|
||||
* @returns {JSX.Element} - The audio mute round button.
|
||||
*/
|
||||
const MicrophoneButton = () : JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const audioMuted = useSelector(state => isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO));
|
||||
const disabled = useSelector(isAudioMuteButtonDisabled);
|
||||
const enabledFlag = useSelector(state => getFeatureFlag(state, AUDIO_MUTE_BUTTON_ENABLED, true));
|
||||
|
||||
if (!enabledFlag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggleMute = useCallback(() => {
|
||||
!disabled && dispatch(muteLocal(!audioMuted, MEDIA_TYPE.AUDIO));
|
||||
}, [ audioMuted, disabled ]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress = { toggleMute }>
|
||||
<View
|
||||
style = { [
|
||||
styles.microphoneStyles.container,
|
||||
!audioMuted && styles.microphoneStyles.unmuted
|
||||
] }>
|
||||
<View
|
||||
style = { styles.microphoneStyles.iconContainer }>
|
||||
<Icon
|
||||
src = { audioMuted ? IconMicrophoneEmptySlash : IconMicrophone }
|
||||
style = { styles.microphoneStyles.icon } />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default MicrophoneButton;
|
|
@ -0,0 +1,37 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openDialog } from '../../../../base/dialog/actions';
|
||||
import AudioRoutePickerDialog from '../../../../mobile/audio-mode/components/AudioRoutePickerDialog';
|
||||
|
||||
import AudioIcon from './AudioIcon';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Button for selecting sound device in carmode.
|
||||
*
|
||||
* @returns {JSX.Element} - The sound device button.
|
||||
*/
|
||||
const SelectSoundDevice = () : JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() =>
|
||||
dispatch(openDialog(AudioRoutePickerDialog))
|
||||
, [ dispatch ]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { t('carmode.actions.selectSoundDevice') }
|
||||
children = { t('carmode.actions.selectSoundDevice') }
|
||||
icon = { AudioIcon }
|
||||
labelStyle = { styles.soundDeviceButtonLabel }
|
||||
mode = 'contained'
|
||||
onPress = { onSelect }
|
||||
style = { styles.soundDeviceButton } />
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectSoundDevice;
|
|
@ -0,0 +1,85 @@
|
|||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { getConferenceName } from '../../../../base/conference/functions';
|
||||
import { getFeatureFlag, MEETING_NAME_ENABLED } from '../../../../base/flags';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { PictureInPictureButton } from '../../../../mobile/picture-in-picture';
|
||||
import { RecordingLabel } from '../../../../recording';
|
||||
import { VideoQualityLabel } from '../../../../video-quality';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Name of the meeting we're currently in.
|
||||
*/
|
||||
_meetingName: string,
|
||||
|
||||
/**
|
||||
* Whether displaying the current meeting name is enabled or not.
|
||||
*/
|
||||
_meetingNameEnabled: boolean,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a navigation bar component that is rendered on top of the
|
||||
* carmode screen.
|
||||
*
|
||||
* @param {Props} props - The React props passed to this component.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const TitleBar = (props: Props) : JSX.Element => (<>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.titleBarWrapper }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.roomNameWrapper }>
|
||||
<View style = { styles.headerLabels }>
|
||||
<PictureInPictureButton styles = { styles.pipButton } />
|
||||
</View>
|
||||
<View style = { styles.headerLabels }>
|
||||
<VideoQualityLabel />
|
||||
</View>
|
||||
|
||||
<View style = { styles.headerLabels }>
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
|
||||
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
|
||||
</View>
|
||||
|
||||
{
|
||||
props._meetingNameEnabled
|
||||
&& <View style = { styles.roomNameView }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.roomName }>
|
||||
{props._meetingName}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
</>);
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const { hideConferenceSubject } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_meetingName: getConferenceName(state),
|
||||
_meetingNameEnabled:
|
||||
getFeatureFlag(state, MEETING_NAME_ENABLED, true) && !hideConferenceSubject
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(TitleBar);
|
|
@ -0,0 +1,204 @@
|
|||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/**
|
||||
* The size of the microphone icon.
|
||||
*/
|
||||
const MICROPHONE_SIZE = 180;
|
||||
|
||||
const TITLE_BAR_BUTTON_SIZE = 24;
|
||||
|
||||
/**
|
||||
* Base button style.
|
||||
*/
|
||||
const baseButton = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BaseTheme.spacing[7],
|
||||
marginTop: BaseTheme.spacing[3],
|
||||
marginLeft: BaseTheme.spacing[10],
|
||||
marginRight: BaseTheme.spacing[10],
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
width: 300
|
||||
};
|
||||
|
||||
/**
|
||||
* Base label style.
|
||||
*/
|
||||
const baseLabel = {
|
||||
display: 'flex',
|
||||
fontSize: 16,
|
||||
textTransform: 'capitalize'
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the safe area view that contains the title bar.
|
||||
*/
|
||||
const titleBarSafeView = {
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the native components of Carmode.
|
||||
*/
|
||||
export default {
|
||||
|
||||
bottomContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
position: 'absolute'
|
||||
},
|
||||
|
||||
/**
|
||||
* {@code Conference} Style.
|
||||
*/
|
||||
conference: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
|
||||
microphoneStyles: {
|
||||
container: {
|
||||
borderRadius: MICROPHONE_SIZE / 2,
|
||||
height: MICROPHONE_SIZE,
|
||||
maxHeight: MICROPHONE_SIZE,
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
width: MICROPHONE_SIZE,
|
||||
maxWidth: MICROPHONE_SIZE,
|
||||
flex: 1,
|
||||
zIndex: 1,
|
||||
elevation: 1
|
||||
},
|
||||
|
||||
icon: {
|
||||
color: BaseTheme.palette.text01,
|
||||
fontSize: MICROPHONE_SIZE * 0.45,
|
||||
fontWeight: '100'
|
||||
},
|
||||
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
alignSelf: 'stretch',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui03
|
||||
},
|
||||
|
||||
unmuted: {
|
||||
borderWidth: 4,
|
||||
borderColor: BaseTheme.palette.success01
|
||||
}
|
||||
},
|
||||
|
||||
pipButton: {
|
||||
iconStyle: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: TITLE_BAR_BUTTON_SIZE
|
||||
},
|
||||
underlayColor: BaseTheme.spacing.underlay01
|
||||
},
|
||||
|
||||
roomTimer: {
|
||||
color: BaseTheme.palette.text01,
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
titleView: {
|
||||
width: 152,
|
||||
height: 28,
|
||||
backgroundColor: BaseTheme.palette.ui02,
|
||||
borderRadius: 12,
|
||||
alignSelf: 'center'
|
||||
},
|
||||
|
||||
title: {
|
||||
margin: 'auto',
|
||||
textAlign: 'center',
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 16,
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
soundDeviceButtonLabel: {
|
||||
...baseLabel,
|
||||
color: BaseTheme.palette.text06
|
||||
},
|
||||
|
||||
soundDeviceButton: {
|
||||
...baseButton,
|
||||
backgroundColor: BaseTheme.palette.section01
|
||||
},
|
||||
|
||||
endMeetingButton: {
|
||||
...baseButton,
|
||||
backgroundColor: BaseTheme.palette.actionDanger,
|
||||
marginBottom: 60
|
||||
},
|
||||
|
||||
endMeetingButtonLabel: {
|
||||
...baseLabel,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
headerLabels: {
|
||||
borderBottomLeftRadius: 3,
|
||||
borderTopLeftRadius: 3,
|
||||
flexShrink: 1,
|
||||
paddingHorizontal: 2
|
||||
},
|
||||
|
||||
titleBarSafeViewColor: {
|
||||
...titleBarSafeView,
|
||||
backgroundColor: BaseTheme.palette.uiBackground
|
||||
},
|
||||
|
||||
titleBarWrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
roomNameWrapper: {
|
||||
flexDirection: 'row',
|
||||
marginRight: 10,
|
||||
flexShrink: 1,
|
||||
flexGrow: 1
|
||||
},
|
||||
|
||||
roomNameView: {
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
flexShrink: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 5,
|
||||
marginBottom: 8
|
||||
},
|
||||
|
||||
roomName: {
|
||||
color: BaseTheme.palette.text01,
|
||||
...BaseTheme.typography.bodyShortBold
|
||||
},
|
||||
|
||||
titleBar: {
|
||||
alignSelf: 'center'
|
||||
},
|
||||
|
||||
videoStoppedLabel: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginBottom: 32,
|
||||
...BaseTheme.typography.bodyShortRegularLarge
|
||||
}
|
||||
};
|
|
@ -1,160 +0,0 @@
|
|||
import { NavigationContainer } 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 { Chat } from '../../../../../chat';
|
||||
import Conference from '../../../../../conference/components/native/Conference';
|
||||
import { getDisablePolls } from '../../../../../conference/functions';
|
||||
import { SharedDocument } from '../../../../../etherpad';
|
||||
import { GifsMenu } from '../../../../../gifs/components';
|
||||
import AddPeopleDialog
|
||||
from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
|
||||
import { ParticipantsPane } from '../../../../../participants-pane/components/native';
|
||||
import { StartLiveStreamDialog } from '../../../../../recording';
|
||||
import { StartRecordingDialog }
|
||||
from '../../../../../recording/components/Recording/native';
|
||||
import SalesforceLinkDialog
|
||||
from '../../../../../salesforce/components/native/SalesforceLinkDialog';
|
||||
import SecurityDialog
|
||||
from '../../../../../security/components/security-dialog/native/SecurityDialog';
|
||||
import SpeakerStats
|
||||
from '../../../../../speaker-stats/components/native/SpeakerStats';
|
||||
import { screen } from '../../../routes';
|
||||
import {
|
||||
chatScreenOptions,
|
||||
conferenceScreenOptions,
|
||||
gifsMenuOptions,
|
||||
inviteScreenOptions,
|
||||
liveStreamScreenOptions,
|
||||
navigationContainerTheme,
|
||||
participantsScreenOptions,
|
||||
recordingScreenOptions,
|
||||
salesforceScreenOptions,
|
||||
securityScreenOptions,
|
||||
sharedDocumentScreenOptions,
|
||||
speakerStatsScreenOptions
|
||||
} from '../../../screenOptions';
|
||||
import ChatAndPollsNavigationContainer
|
||||
from '../../chat/components/ChatAndPollsNavigationContainer';
|
||||
import LobbyNavigationContainer
|
||||
from '../../lobby/components/LobbyNavigationContainer';
|
||||
import {
|
||||
conferenceNavigationRef
|
||||
} from '../ConferenceNavigationContainerRef';
|
||||
|
||||
const ConferenceStack = createStackNavigator();
|
||||
|
||||
|
||||
const ConferenceNavigationContainer = () => {
|
||||
const isPollsDisabled = useSelector(getDisablePolls);
|
||||
let ChatScreen;
|
||||
let chatScreenName;
|
||||
let chatTitleString;
|
||||
|
||||
if (isPollsDisabled) {
|
||||
ChatScreen = Chat;
|
||||
chatScreenName = screen.conference.chat;
|
||||
chatTitleString = 'chat.title';
|
||||
} else {
|
||||
ChatScreen = ChatAndPollsNavigationContainer;
|
||||
chatScreenName = screen.conference.chatandpolls.main;
|
||||
chatTitleString = 'chat.titleWithPolls';
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
independent = { true }
|
||||
ref = { conferenceNavigationRef }
|
||||
theme = { navigationContainerTheme }>
|
||||
<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.header')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { SecurityDialog }
|
||||
name = { screen.conference.security }
|
||||
options = {{
|
||||
...securityScreenOptions,
|
||||
title: t('security.header')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { StartRecordingDialog }
|
||||
name = { screen.conference.recording }
|
||||
options = {{
|
||||
...recordingScreenOptions
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { StartLiveStreamDialog }
|
||||
name = { screen.conference.liveStream }
|
||||
options = {{
|
||||
...liveStreamScreenOptions
|
||||
}} />
|
||||
<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 = {{
|
||||
gestureEnabled: false,
|
||||
headerShown: false
|
||||
}} />
|
||||
<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.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConferenceNavigationContainer;
|
|
@ -0,0 +1,69 @@
|
|||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||
import { NavigationContainer, TypedNavigator } from '@react-navigation/native';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import ConferenceTab from '../../../../../conference/components/native/ConferenceTab';
|
||||
import CarmodeTab from '../../../../../conference/components/native/carmode/Conference';
|
||||
import { screen } from '../../../routes';
|
||||
import { navigationContainerTheme } from '../../../screenOptions';
|
||||
import {
|
||||
conferenceNavigationRef
|
||||
} from '../ConferenceNavigationContainerRef';
|
||||
|
||||
import NavigationThumb, { THUMBS } from './NavigationThumb';
|
||||
import styles from './styles';
|
||||
|
||||
const ConferenceTabs: TypedNavigator = createMaterialTopTabNavigator();
|
||||
|
||||
/**
|
||||
* Navigation container component for the conference.
|
||||
*
|
||||
* @returns {JSX.Element} - the container.
|
||||
*/
|
||||
const ConferenceNavigationContainer = () : JSX.Element => {
|
||||
const [ selectedThumb , setSelectedThumb ] = useState(THUMBS.CONFERENCE_VIEW);
|
||||
|
||||
/**
|
||||
* Lights up the correct bottom navigation circle
|
||||
* in regards with the focused screen.
|
||||
*/
|
||||
const onFocused = useCallback(selected => {
|
||||
if (selected === screen.car) {
|
||||
setSelectedThumb(THUMBS.CAR_VIEW);
|
||||
} else {
|
||||
setSelectedThumb(THUMBS.CONFERENCE_VIEW);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Carmode = useCallback(() => (
|
||||
<CarmodeTab
|
||||
onFocused = { onFocused } />
|
||||
), []);
|
||||
|
||||
const Conference = useCallback(() => (
|
||||
<ConferenceTab
|
||||
onFocused = { onFocused } />
|
||||
), []);
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
independent = { true }
|
||||
ref = { conferenceNavigationRef }
|
||||
theme = { navigationContainerTheme }>
|
||||
<ConferenceTabs.Navigator
|
||||
backBehavior = 'none'
|
||||
screenOptions = { styles.tabBarOptions }>
|
||||
<ConferenceTabs.Screen
|
||||
component = { Conference }
|
||||
name = { screen.conference.container } />
|
||||
<ConferenceTabs.Screen
|
||||
component = { Carmode }
|
||||
headerShown = { false }
|
||||
name = { screen.car } />
|
||||
</ConferenceTabs.Navigator>
|
||||
<NavigationThumb selectedThumb = { selectedThumb } />
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConferenceNavigationContainer;
|
|
@ -0,0 +1,46 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { SafeAreaView } from 'react-native';
|
||||
|
||||
import { Icon, IconCircle } from '../../../../../base/icons';
|
||||
|
||||
import styles, { ICON_ACTIVE_COLOR, ICON_INACTIVE_COLOR } from './styles';
|
||||
|
||||
export const enum THUMBS {
|
||||
CONFERENCE_VIEW = 'CONFERENCE_VIEW ',
|
||||
CAR_VIEW = 'CAR_VIEW'
|
||||
}
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Which thumb is selected.
|
||||
*/
|
||||
selectedThumb: THUMBS
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom tab navigation screen indicator.
|
||||
*
|
||||
* @returns {JSX.Element} - The tab navigation indicator.
|
||||
*/
|
||||
const NavigationThumb = ({ selectedThumb }: Props): JSX.Element => (
|
||||
<SafeAreaView
|
||||
style = { styles.navigationThumbContainer }>
|
||||
{
|
||||
Object.values(THUMBS)
|
||||
.map(value =>
|
||||
(<Icon
|
||||
key = { `thumb-${value.toLowerCase()}` }
|
||||
size = { 8 }
|
||||
color = { value === selectedThumb ? ICON_ACTIVE_COLOR : ICON_INACTIVE_COLOR}
|
||||
src = { IconCircle }
|
||||
style = { styles.navigationThumbIcon } />)
|
||||
)
|
||||
}
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
export default NavigationThumb;
|
|
@ -0,0 +1,31 @@
|
|||
import BaseTheme from '../../../../../base/ui/components/BaseTheme';
|
||||
|
||||
export const ICON_ACTIVE_COLOR = BaseTheme.palette.icon01;
|
||||
|
||||
export const ICON_INACTIVE_COLOR = BaseTheme.palette.icon03;
|
||||
|
||||
/**
|
||||
* The styles navigation components.
|
||||
*/
|
||||
export default {
|
||||
|
||||
navigationThumbContainer: {
|
||||
alignSelf: 'center',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
bottom: 13,
|
||||
height: 8,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
navigationThumbIcon: {
|
||||
marginRight: 10
|
||||
},
|
||||
|
||||
tabBarOptions: {
|
||||
tabBarStyle: {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
};
|
|
@ -11,6 +11,7 @@ export const screen = {
|
|||
privacy: 'Privacy',
|
||||
help: 'Help'
|
||||
},
|
||||
car: 'Car Mode',
|
||||
dialInSummary: 'Dial-In Info',
|
||||
connecting: 'Connecting',
|
||||
conference: {
|
||||
|
@ -24,6 +25,7 @@ export const screen = {
|
|||
polls: 'Polls'
|
||||
}
|
||||
},
|
||||
container: 'Conference container',
|
||||
security: 'Security Options',
|
||||
recording: 'Recording',
|
||||
liveStream: 'Live stream',
|
||||
|
|
|
@ -30,3 +30,13 @@ export const VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED = 'VIRTUAL_SCREENSH
|
|||
* }}
|
||||
*/
|
||||
export const SET_TILE_VIEW = 'SET_TILE_VIEW';
|
||||
|
||||
/**
|
||||
* The type of the action which tells whether we are in carmode.
|
||||
*
|
||||
* @returns {{
|
||||
* type: SET_CAR_MODE,
|
||||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export const SET_CAR_MODE = ' SET_CAR_MODE';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { SET_CAR_MODE } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Creates a (redux) action which tells whether we are in carmode.
|
||||
*
|
||||
* @param {boolean} enabled - Whether we are in carmode.
|
||||
* @returns {{
|
||||
* type: SET_CAR_MODE,
|
||||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setIsCarmode(enabled) {
|
||||
return {
|
||||
type: SET_CAR_MODE,
|
||||
enabled
|
||||
};
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './actions.any';
|
|
@ -4,6 +4,7 @@ import { ReducerRegistry } from '../base/redux';
|
|||
|
||||
import {
|
||||
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||
SET_CAR_MODE,
|
||||
SET_TILE_VIEW,
|
||||
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
|
||||
} from './actionTypes';
|
||||
|
@ -21,7 +22,15 @@ const DEFAULT_STATE = {
|
|||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
tileViewEnabled: undefined
|
||||
tileViewEnabled: undefined,
|
||||
|
||||
/**
|
||||
* Whether we are in carmode.
|
||||
*
|
||||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
carMode: false
|
||||
};
|
||||
|
||||
const STORE_NAME = 'features/video-layout';
|
||||
|
@ -29,18 +38,23 @@ const STORE_NAME = 'features/video-layout';
|
|||
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
|
||||
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED: {
|
||||
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED:
|
||||
return {
|
||||
...state,
|
||||
remoteScreenShares: action.participantIds
|
||||
};
|
||||
}
|
||||
|
||||
case SET_TILE_VIEW:
|
||||
return {
|
||||
...state,
|
||||
tileViewEnabled: action.enabled
|
||||
};
|
||||
|
||||
case SET_CAR_MODE:
|
||||
return {
|
||||
...state,
|
||||
carMode: action.enabled
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
Loading…
Reference in New Issue