Refactor carmnode
- open as a modal - add long press to talk - add long press analytics - video mute keeps the previous state after carmode closes - and more
This commit is contained in:
parent
ac965b86bc
commit
698ab32a62
|
@ -83,6 +83,7 @@
|
|||
"selectSoundDevice": "Select sound device"
|
||||
},
|
||||
"labels": {
|
||||
"buttonLabel": "Car mode",
|
||||
"title": "Safe driving mode",
|
||||
"videoStopped": "Your video is stopped"
|
||||
}
|
||||
|
@ -1050,6 +1051,7 @@
|
|||
"muteEveryoneElse": "Mute everyone else",
|
||||
"muteEveryoneElsesVideoStream": "Stop everyone else's video",
|
||||
"muteEveryonesVideoStream": "Stop everyone's video",
|
||||
"carmode": "Carmode",
|
||||
"participants": "Participants",
|
||||
"pip": "Toggle Picture-in-Picture mode",
|
||||
"privateMessage": "Send private message",
|
||||
|
|
|
@ -641,18 +641,20 @@ export function createSharedVideoEvent(action, attributes = {}) {
|
|||
* of ACTION_SHORTCUT_PRESSED, ACTION_SHORTCUT_RELEASED
|
||||
* or ACTION_SHORTCUT_TRIGGERED).
|
||||
* @param {Object} attributes - Attributes to attach to the event.
|
||||
* @param {string} source - The event's source.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createShortcutEvent(
|
||||
shortcut,
|
||||
action = ACTION_SHORTCUT_TRIGGERED,
|
||||
attributes = {}) {
|
||||
attributes = {},
|
||||
source = 'keyboard.shortcut') {
|
||||
return {
|
||||
action,
|
||||
actionSubjectId: shortcut,
|
||||
attributes,
|
||||
source: 'keyboard.shortcut',
|
||||
source,
|
||||
type: TYPE_UI
|
||||
};
|
||||
}
|
||||
|
@ -901,7 +903,7 @@ export function createBreakoutRoomsEvent(actionSubject) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates and event which indicates a GIF was sent.
|
||||
* Creates an event which indicates a GIF was sent.
|
||||
*
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
|
|
|
@ -166,7 +166,7 @@ export function setVideoAvailable(available: boolean) {
|
|||
*/
|
||||
export function setVideoMuted(
|
||||
muted: boolean,
|
||||
mediaType: MediaType = MEDIA_TYPE.VIDEO,
|
||||
mediaType: string = MEDIA_TYPE.VIDEO,
|
||||
authority: number = VIDEO_MUTISM_AUTHORITY.USER,
|
||||
ensureTrack: boolean = false) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
|
|
|
@ -45,7 +45,8 @@ export const SCREENSHARE_MUTISM_AUTHORITY = {
|
|||
export const VIDEO_MUTISM_AUTHORITY = {
|
||||
AUDIO_ONLY: 1 << 0,
|
||||
BACKGROUND: 1 << 1,
|
||||
USER: 1 << 2
|
||||
USER: 1 << 2,
|
||||
CAR_MODE: 1 << 3
|
||||
};
|
||||
|
||||
/* eslint-enable no-bitwise */
|
||||
|
|
|
@ -16,7 +16,6 @@ import { PollsPane } from '../../../polls/components';
|
|||
|
||||
const ChatTab = createMaterialTopTabNavigator();
|
||||
|
||||
|
||||
const ChatAndPolls = () => {
|
||||
const clientHeight = useSelector(getClientHeight);
|
||||
const clientWidth = useSelector(getClientWidth);
|
||||
|
|
|
@ -1,176 +0,0 @@
|
|||
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;
|
|
@ -1,13 +1,11 @@
|
|||
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 { useDispatch, useSelector } from 'react-redux';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
|
||||
import { setAudioOnly } from '../../../../base/audio-only';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../../base/react';
|
||||
import { screen } from '../../../../mobile/navigation/routes';
|
||||
import { LoadingIndicator, TintedView } from '../../../../base/react';
|
||||
import { setIsCarmode } from '../../../../video-layout/actions';
|
||||
import ConferenceTimer from '../../ConferenceTimer';
|
||||
import { isConnecting } from '../../functions';
|
||||
|
@ -17,42 +15,34 @@ 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
|
||||
|
||||
}
|
||||
import { isLocalVideoTrackDesktop } from '../../../../base/tracks';
|
||||
import { setPictureInPictureDisabled } from '../../../../mobile/picture-in-picture/functions';
|
||||
|
||||
/**
|
||||
* 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 CarmodeTab = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const connecting = useSelector(isConnecting);
|
||||
const isSharing = useSelector(isLocalVideoTrackDesktop);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
batch(() => {
|
||||
dispatch(setAudioOnly(true));
|
||||
dispatch(setIsCarmode(true));
|
||||
});
|
||||
dispatch(setIsCarmode(true));
|
||||
setPictureInPictureDisabled(true);
|
||||
|
||||
onFocused(screen.car);
|
||||
return () => {
|
||||
dispatch(setIsCarmode(false));
|
||||
if (!isSharing) {
|
||||
setPictureInPictureDisabled(false);
|
||||
}
|
||||
}
|
||||
}, [ isFocused ]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container style = { styles.conference }>
|
||||
<JitsiScreen style = { styles.conference }>
|
||||
{/*
|
||||
* The activity/loading indicator goes above everything, except
|
||||
* the toolbox/toolbars and the dialogs.
|
||||
|
@ -62,22 +52,21 @@ const CarmodeTab = ({ onFocused }: Props): JSX.Element => {
|
|||
<LoadingIndicator />
|
||||
</TintedView>
|
||||
}
|
||||
<SafeAreaView
|
||||
<View
|
||||
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
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.microphoneContainer }>
|
||||
<MicrophoneButton />
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.bottomContainer }>
|
||||
<Text style = { styles.videoStoppedLabel }>
|
||||
|
@ -85,8 +74,8 @@ const CarmodeTab = ({ onFocused }: Props): JSX.Element => {
|
|||
</Text>
|
||||
<SoundDeviceButton />
|
||||
<EndMeetingButton />
|
||||
</SafeAreaView>
|
||||
</Container>
|
||||
</View>
|
||||
</JitsiScreen>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { View, TouchableOpacity } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
createShortcutEvent,
|
||||
sendAnalytics,
|
||||
ACTION_SHORTCUT_PRESSED as PRESSED,
|
||||
ACTION_SHORTCUT_RELEASED as RELEASED
|
||||
} from '../../../../analytics';
|
||||
|
||||
import { getFeatureFlag, AUDIO_MUTE_BUTTON_ENABLED } from '../../../../base/flags';
|
||||
import { Icon, IconMicrophone, IconMicrophoneEmptySlash } from '../../../../base/icons';
|
||||
|
@ -11,6 +18,8 @@ import { muteLocal } from '../../../../video-menu/actions';
|
|||
|
||||
import styles from './styles';
|
||||
|
||||
const LONG_PRESS = 'long.press';
|
||||
|
||||
/**
|
||||
* Implements a round audio mute/unmute button of a custom size.
|
||||
*
|
||||
|
@ -21,18 +30,45 @@ const MicrophoneButton = () : JSX.Element => {
|
|||
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));
|
||||
const [ longPress, setLongPress ] = useState(false);
|
||||
|
||||
if (!enabledFlag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toggleMute = useCallback(() => {
|
||||
const onPressIn = useCallback(() => {
|
||||
!disabled && dispatch(muteLocal(!audioMuted, MEDIA_TYPE.AUDIO));
|
||||
}, [ audioMuted, disabled ]);
|
||||
|
||||
const onLongPress = useCallback(() => {
|
||||
if ( !disabled && !audioMuted) {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'push.to.talk',
|
||||
PRESSED,
|
||||
{},
|
||||
LONG_PRESS));
|
||||
setLongPress(true);
|
||||
}
|
||||
}, [audioMuted, disabled, setLongPress]);
|
||||
|
||||
const onPressOut = useCallback(() => {
|
||||
if (longPress) {
|
||||
setLongPress(false);
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'push.to.talk',
|
||||
RELEASED,
|
||||
{},
|
||||
LONG_PRESS
|
||||
));
|
||||
dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
}, [longPress, setLongPress]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress = { toggleMute }>
|
||||
onPressIn = { onPressIn }
|
||||
onLongPress={ onLongPress }
|
||||
onPressOut={ onPressOut } >
|
||||
<View
|
||||
style = { [
|
||||
styles.microphoneStyles.container,
|
||||
|
|
|
@ -4,8 +4,7 @@ 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 { connect } from '../../../../base/redux';;
|
||||
import { RecordingLabel } from '../../../../recording';
|
||||
import { VideoQualityLabel } from '../../../../video-quality';
|
||||
|
||||
|
@ -40,9 +39,6 @@ const TitleBar = (props: Props) : JSX.Element => (<>
|
|||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.roomNameWrapper }>
|
||||
<View style = { styles.headerLabels }>
|
||||
<PictureInPictureButton styles = { styles.pipButton } />
|
||||
</View>
|
||||
<View style = { styles.headerLabels }>
|
||||
<VideoQualityLabel />
|
||||
</View>
|
||||
|
|
|
@ -5,8 +5,6 @@ import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
|||
*/
|
||||
const MICROPHONE_SIZE = 180;
|
||||
|
||||
const TITLE_BAR_BUTTON_SIZE = 24;
|
||||
|
||||
/**
|
||||
* Base button style.
|
||||
*/
|
||||
|
@ -59,11 +57,9 @@ export default {
|
|||
* {@code Conference} Style.
|
||||
*/
|
||||
conference: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
microphoneStyles: {
|
||||
|
@ -100,14 +96,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
pipButton: {
|
||||
iconStyle: {
|
||||
color: BaseTheme.palette.icon01,
|
||||
fontSize: TITLE_BAR_BUTTON_SIZE
|
||||
},
|
||||
underlayColor: BaseTheme.spacing.underlay01
|
||||
},
|
||||
|
||||
roomTimer: {
|
||||
color: BaseTheme.palette.text01,
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
|
@ -165,6 +153,12 @@ export default {
|
|||
backgroundColor: BaseTheme.palette.uiBackground
|
||||
},
|
||||
|
||||
microphoneContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
titleBarWrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
|
|
|
@ -9,7 +9,7 @@ export const conferenceNavigationRef = React.createRef();
|
|||
* @param {Object} params - Params to pass to the destination route.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function navigate(name: string, params: Object) {
|
||||
export function navigate(name: string, params?: Object) {
|
||||
return conferenceNavigationRef.current?.navigate(name, params);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,67 +1,165 @@
|
|||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||
import { NavigationContainer, TypedNavigator } from '@react-navigation/native';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
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 ConferenceTab from '../../../../../conference/components/native/ConferenceTab';
|
||||
import { Chat, ChatAndPolls } from '../../../../../chat';
|
||||
|
||||
import Conference from '../../../../../conference/components/native/Conference';
|
||||
import CarmodeTab from '../../../../../conference/components/native/carmode/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 { navigationContainerTheme } from '../../../screenOptions';
|
||||
import {
|
||||
carmodeScreenOptions,
|
||||
chatScreenOptions,
|
||||
conferenceScreenOptions,
|
||||
gifsMenuOptions,
|
||||
inviteScreenOptions,
|
||||
liveStreamScreenOptions,
|
||||
navigationContainerTheme,
|
||||
participantsScreenOptions,
|
||||
recordingScreenOptions,
|
||||
salesforceScreenOptions,
|
||||
securityScreenOptions,
|
||||
sharedDocumentScreenOptions,
|
||||
speakerStatsScreenOptions
|
||||
} from '../../../screenOptions';
|
||||
import LobbyNavigationContainer
|
||||
from '../../lobby/components/LobbyNavigationContainer';
|
||||
import {
|
||||
conferenceNavigationRef
|
||||
} from '../ConferenceNavigationContainerRef';
|
||||
|
||||
import NavigationThumb, { THUMBS } from './NavigationThumb';
|
||||
import styles from './styles';
|
||||
const ConferenceStack = createStackNavigator();
|
||||
|
||||
const ConferenceTabs: TypedNavigator = createMaterialTopTabNavigator();
|
||||
const ConferenceNavigationContainer = () => {
|
||||
const isPollsDisabled = useSelector(getDisablePolls);
|
||||
let ChatScreen;
|
||||
let chatScreenName;
|
||||
let chatTitleString;
|
||||
|
||||
/**
|
||||
* 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 } />
|
||||
), []);
|
||||
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();
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
independent = { true }
|
||||
ref = { conferenceNavigationRef }
|
||||
theme = { navigationContainerTheme }>
|
||||
<ConferenceTabs.Navigator
|
||||
backBehavior = 'none'
|
||||
screenOptions = { styles.tabBarOptions }>
|
||||
<ConferenceTabs.Screen
|
||||
<ConferenceStack.Navigator
|
||||
screenOptions = {{
|
||||
presentation: 'modal'
|
||||
}}>
|
||||
<ConferenceStack.Screen
|
||||
component = { Conference }
|
||||
name = { screen.conference.container } />
|
||||
<ConferenceTabs.Screen
|
||||
component = { Carmode }
|
||||
headerShown = { false }
|
||||
name = { screen.car } />
|
||||
</ConferenceTabs.Navigator>
|
||||
<NavigationThumb selectedThumb = { selectedThumb } />
|
||||
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.Screen
|
||||
component = { CarmodeTab }
|
||||
name = { screen.conference.carmode }
|
||||
options = {{
|
||||
...carmodeScreenOptions,
|
||||
title: t('carmode.labels.title')
|
||||
}}/>
|
||||
</ConferenceStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
// @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;
|
|
@ -1,31 +0,0 @@
|
|||
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,12 +11,12 @@ export const screen = {
|
|||
privacy: 'Privacy',
|
||||
help: 'Help'
|
||||
},
|
||||
car: 'Car Mode',
|
||||
dialInSummary: 'Dial-In Info',
|
||||
connecting: 'Connecting',
|
||||
conference: {
|
||||
root: 'Conference root',
|
||||
main: 'Conference',
|
||||
carmode: 'Car Mode',
|
||||
chat: 'Chat',
|
||||
chatandpolls: {
|
||||
main: 'Chat and Polls',
|
||||
|
|
|
@ -198,6 +198,11 @@ export const presentationScreenOptions = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for car mode.
|
||||
*/
|
||||
export const carmodeScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for chat.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { translate } from '../../../base/i18n';
|
||||
import { IconCar } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the carmode.
|
||||
*/
|
||||
class OpenCarmodeButton extends AbstractButton<AbstractButtonProps, any, any> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.carmode';
|
||||
icon = IconCar;
|
||||
label = 'carmode.labels.buttonLabel';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the carmode mode.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
return navigate(screen.conference.carmode);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(OpenCarmodeButton));
|
|
@ -24,6 +24,7 @@ import HelpButton from '../HelpButton';
|
|||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
import LinkToSalesforceButton from './LinkToSalesforceButton';
|
||||
import OpenCarmodeButton from './OpenCarmodeButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ScreenSharingButton from './ScreenSharingButton.js';
|
||||
import ToggleCameraButton from './ToggleCameraButton';
|
||||
|
@ -143,6 +144,7 @@ class OverflowMenu extends PureComponent<Props, State> {
|
|||
? this._renderReactionMenu
|
||||
: null }>
|
||||
<ParticipantsPaneButton { ...topButtonProps } />
|
||||
<OpenCarmodeButton { ...topButtonProps } />
|
||||
<AudioOnlyButton { ...buttonProps } />
|
||||
{!_reactionsEnabled && !toolbarButtons.has('raisehand') && <RaiseHandButton { ...buttonProps } />}
|
||||
<Divider style = { styles.divider } />
|
||||
|
|
|
@ -1 +1,24 @@
|
|||
import { MEDIA_TYPE, setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../base/media';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { SET_CAR_MODE } from './actionTypes';
|
||||
import './middleware.any';
|
||||
|
||||
/**
|
||||
* Middleware which intercepts actions and updates the legacy component.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
const { dispatch } = store;
|
||||
|
||||
switch (action.type) {
|
||||
case SET_CAR_MODE:
|
||||
dispatch(setVideoMuted(action.enabled, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.CAR_MODE));
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue