feat(carmode) Add carmode screen
- opens as a modal - lastn is 0, mutes local video while open - long press to talk - and more
This commit is contained in:
parent
e628d99544
commit
61abf0d882
|
@ -77,6 +77,17 @@
|
|||
"refresh": "Refresh calendar",
|
||||
"today": "Today"
|
||||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": " Leave meeting",
|
||||
"selectSoundDevice": "Select sound device"
|
||||
},
|
||||
"labels": {
|
||||
"buttonLabel": "Car mode",
|
||||
"title": "Safe driving mode",
|
||||
"videoStopped": "Your video is stopped"
|
||||
}
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Enter room",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
|
@ -1010,6 +1021,7 @@
|
|||
"boo": "Boo",
|
||||
"breakoutRoom": "Join/leave breakout room",
|
||||
"callQuality": "Manage video quality",
|
||||
"carmode": "Carmode",
|
||||
"cc": "Toggle subtitles",
|
||||
"chat": "Open / Close chat",
|
||||
"clap": "Clap",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -38,6 +38,12 @@ export const CALENDAR_ENABLED = 'calendar.enabled';
|
|||
*/
|
||||
export const CALL_INTEGRATION_ENABLED = 'call-integration.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if car mode should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const CAR_MODE_ENABLED = 'car-mode.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if close captions should be enabled.
|
||||
* Default: enabled (true).
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.44701 5H15.553C16.427 5 17.1997 5.56747 17.4613 6.40136L18.2765 9H18H6H5.7235L6.5387 6.40136C6.8003 5.56747 7.57305 5 8.44701 5ZM3.29779 10.0507L4.6304 5.80272C5.15358 4.13493 6.69908 3 8.44701 3H15.553C17.3009 3 18.8464 4.13494 19.3696 5.80272L20.7022 10.0507C21.4999 10.782 22 11.8326 22 13V17V18V21H20V18H4V21H2V18V17V13C2 11.8326 2.50012 10.782 3.29779 10.0507ZM6 11C4.89543 11 4 11.8954 4 13V16H20V13C20 11.8954 19.1046 11 18 11H6ZM9 13.5C9 14.3284 8.32843 15 7.5 15C6.67157 15 6 14.3284 6 13.5C6 12.6716 6.67157 12 7.5 12C8.32843 12 9 12.6716 9 13.5ZM16.5 15C17.3284 15 18 14.3284 18 13.5C18 12.6716 17.3284 12 16.5 12C15.6716 12 15 12.6716 15 13.5C15 14.3284 15.6716 15 16.5 15Z"/>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.50001 11.25C7.50001 11.9404 6.94036 12.5 6.25001 12.5C5.55965 12.5 5.00001 11.9404 5.00001 11.25C5.00001 10.5596 5.55965 10 6.25001 10C6.94036 10 7.50001 10.5596 7.50001 11.25Z" />
|
||||
<path d="M13.75 12.5C14.4404 12.5 15 11.9404 15 11.25C15 10.5596 14.4404 10 13.75 10C13.0596 10 12.5 10.5596 12.5 11.25C12.5 11.9404 13.0596 12.5 13.75 12.5Z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.74816 8.3756L3.85867 4.8356C4.29466 3.44578 5.58258 2.5 7.03918 2.5H12.9608C14.4174 2.5 15.7054 3.44578 16.1413 4.8356L17.2518 8.3756C17.9166 8.98497 18.3333 9.86048 18.3333 10.8333V17.5H16.6667V15H3.33334V17.5H1.66667V10.8333C1.66667 9.86048 2.08344 8.98497 2.74816 8.3756ZM7.03918 4.16667H12.9608C13.6891 4.16667 14.3331 4.63956 14.5511 5.33447L15.2304 7.5H4.76959L5.44892 5.33447C5.66692 4.63956 6.31088 4.16667 7.03918 4.16667ZM5.00001 9.16667C4.07953 9.16667 3.33334 9.91286 3.33334 10.8333V13.3333H16.6667V10.8333C16.6667 9.91286 15.9205 9.16667 15 9.16667H5.00001Z" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 1.0 KiB |
|
@ -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';
|
||||
|
@ -51,6 +52,7 @@ const _updateLastN = debounce(({ dispatch, getState }) => {
|
|||
const config = state['features/base/config'];
|
||||
const { lastNLimits } = state['features/base/lastn'];
|
||||
const participantCount = getParticipantCount(state);
|
||||
const { carMode } = state['features/video-layout'];
|
||||
|
||||
// Select the (initial) lastN value based on the following preference order.
|
||||
// 1. The last-n value from 'startLastN' if it is specified in config.js
|
||||
|
@ -67,6 +69,8 @@ const _updateLastN = debounce(({ dispatch, getState }) => {
|
|||
|
||||
if (typeof appState !== 'undefined' && appState !== 'active') {
|
||||
lastNSelected = isLocalVideoTrackDesktop(state) ? 1 : 0;
|
||||
} else if (carMode) {
|
||||
lastNSelected = 0;
|
||||
} else if (audioOnly) {
|
||||
const { remoteScreenShares, tileViewEnabled } = state['features/video-layout'];
|
||||
const largeVideoParticipantId = state['features/large-video'].participantId;
|
||||
|
@ -101,6 +105,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:
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -4,20 +4,19 @@ 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 +43,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,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,82 @@
|
|||
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 { useDispatch, useSelector } from 'react-redux';
|
||||
import JitsiScreen from '../../../../base/modal/components/JitsiScreen';
|
||||
|
||||
import { LoadingIndicator, TintedView } from '../../../../base/react';
|
||||
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';
|
||||
import { isLocalVideoTrackDesktop } from '../../../../base/tracks';
|
||||
import { setPictureInPictureDisabled } from '../../../../mobile/picture-in-picture/functions';
|
||||
|
||||
/**
|
||||
* Implements the carmode tab.
|
||||
*
|
||||
* @returns { JSX.Element} - The carmode tab.
|
||||
*/
|
||||
const CarmodeTab = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const connecting = useSelector(isConnecting);
|
||||
const isSharing = useSelector(isLocalVideoTrackDesktop);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setIsCarmode(true));
|
||||
setPictureInPictureDisabled(true);
|
||||
|
||||
return () => {
|
||||
dispatch(setIsCarmode(false));
|
||||
if (!isSharing) {
|
||||
setPictureInPictureDisabled(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<JitsiScreen style = { styles.conference }>
|
||||
{/*
|
||||
* The activity/loading indicator goes above everything, except
|
||||
* the toolbox/toolbars and the dialogs.
|
||||
*/
|
||||
connecting
|
||||
&& <TintedView>
|
||||
<LoadingIndicator />
|
||||
</TintedView>
|
||||
}
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.titleBarSafeViewColor }>
|
||||
<View
|
||||
style = { styles.titleBar }>
|
||||
<TitleBar />
|
||||
</View>
|
||||
<ConferenceTimer textStyle = { styles.roomTimer } />
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.microphoneContainer }>
|
||||
<MicrophoneButton />
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.bottomContainer }>
|
||||
<Text style = { styles.videoStoppedLabel }>
|
||||
{t('carmode.labels.videoStopped')}
|
||||
</Text>
|
||||
<SoundDeviceButton />
|
||||
<EndMeetingButton />
|
||||
</View>
|
||||
</JitsiScreen>
|
||||
);
|
||||
};
|
||||
|
||||
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,88 @@
|
|||
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';
|
||||
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';
|
||||
|
||||
const LONG_PRESS = 'long.press';
|
||||
|
||||
/**
|
||||
* 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));
|
||||
const [ longPress, setLongPress ] = useState(false);
|
||||
|
||||
if (!enabledFlag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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
|
||||
onPressIn = { onPressIn }
|
||||
onLongPress={ onLongPress }
|
||||
onPressOut={ onPressOut } >
|
||||
<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,81 @@
|
|||
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 { 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.qualityLabelContainer }>
|
||||
<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,207 @@
|
|||
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
/**
|
||||
* The size of the microphone icon.
|
||||
*/
|
||||
const MICROPHONE_SIZE = 180;
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
backgroundColor: BaseTheme.palette.uiBackground,
|
||||
flex: 1,
|
||||
justifyContent: '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
|
||||
}
|
||||
},
|
||||
|
||||
qualityLabelContainer: {
|
||||
borderBottomLeftRadius: 3,
|
||||
borderTopLeftRadius: 3,
|
||||
flexShrink: 1,
|
||||
paddingHorizontal: 2,
|
||||
justifyContent: 'center',
|
||||
marginTop: 8
|
||||
},
|
||||
|
||||
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,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
titleBarSafeViewColor: {
|
||||
...titleBarSafeView,
|
||||
backgroundColor: BaseTheme.palette.uiBackground
|
||||
},
|
||||
|
||||
microphoneContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
roomName: {
|
||||
color: BaseTheme.palette.text01,
|
||||
...BaseTheme.typography.bodyShortBold
|
||||
},
|
||||
|
||||
titleBar: {
|
||||
alignSelf: 'center'
|
||||
},
|
||||
|
||||
videoStoppedLabel: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginBottom: 32,
|
||||
...BaseTheme.typography.bodyShortRegularLarge
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ import React from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { Chat } from '../../../../../chat';
|
||||
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';
|
||||
|
@ -23,6 +25,7 @@ import SpeakerStats
|
|||
from '../../../../../speaker-stats/components/native/SpeakerStats';
|
||||
import { screen } from '../../../routes';
|
||||
import {
|
||||
carmodeScreenOptions,
|
||||
chatScreenOptions,
|
||||
conferenceScreenOptions,
|
||||
gifsMenuOptions,
|
||||
|
@ -36,8 +39,6 @@ import {
|
|||
sharedDocumentScreenOptions,
|
||||
speakerStatsScreenOptions
|
||||
} from '../../../screenOptions';
|
||||
import ChatAndPollsNavigationContainer
|
||||
from '../../chat/components/ChatAndPollsNavigationContainer';
|
||||
import LobbyNavigationContainer
|
||||
from '../../lobby/components/LobbyNavigationContainer';
|
||||
import {
|
||||
|
@ -46,7 +47,6 @@ import {
|
|||
|
||||
const ConferenceStack = createStackNavigator();
|
||||
|
||||
|
||||
const ConferenceNavigationContainer = () => {
|
||||
const isPollsDisabled = useSelector(getDisablePolls);
|
||||
let ChatScreen;
|
||||
|
@ -58,7 +58,7 @@ const ConferenceNavigationContainer = () => {
|
|||
chatScreenName = screen.conference.chat;
|
||||
chatTitleString = 'chat.title';
|
||||
} else {
|
||||
ChatScreen = ChatAndPollsNavigationContainer;
|
||||
ChatScreen = ChatAndPolls;
|
||||
chatScreenName = screen.conference.chatandpolls.main;
|
||||
chatTitleString = 'chat.titleWithPolls';
|
||||
}
|
||||
|
@ -152,6 +152,13 @@ const ConferenceNavigationContainer = () => {
|
|||
...sharedDocumentScreenOptions,
|
||||
title: t('documentSharing.title')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { CarmodeTab }
|
||||
name = { screen.conference.carmode }
|
||||
options = {{
|
||||
...carmodeScreenOptions,
|
||||
title: t('carmode.labels.title')
|
||||
}}/>
|
||||
</ConferenceStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
|
@ -16,6 +16,7 @@ export const screen = {
|
|||
conference: {
|
||||
root: 'Conference root',
|
||||
main: 'Conference',
|
||||
carmode: 'Car Mode',
|
||||
chat: 'Chat',
|
||||
chatandpolls: {
|
||||
main: 'Chat and Polls',
|
||||
|
@ -24,6 +25,7 @@ export const screen = {
|
|||
polls: 'Polls'
|
||||
}
|
||||
},
|
||||
container: 'Conference container',
|
||||
security: 'Security Options',
|
||||
recording: 'Recording',
|
||||
liveStream: 'Live stream',
|
||||
|
|
|
@ -183,6 +183,11 @@ export const presentationScreenOptions = {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for car mode.
|
||||
*/
|
||||
export const carmodeScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for chat.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { CAR_MODE_ENABLED, getFeatureFlag } from '../../../base/flags';
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {AbstractButtonProps} ownProps - The properties explicitly passed to the component instance.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: Object, ownProps: AbstractButtonProps): Object {
|
||||
const enabled = getFeatureFlag(state, CAR_MODE_ENABLED, true);
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(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';
|
||||
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 } />
|
||||
|
|
|
@ -11,14 +11,14 @@ export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
|
|||
= 'SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
|
||||
|
||||
/**
|
||||
* The type of the action which sets the list of known remote virtual screen share participant IDs.
|
||||
* The type of the action which tells whether we are in carmode.
|
||||
*
|
||||
* @returns {{
|
||||
* type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||
* participantIds: Array<string>
|
||||
* type: SET_CAR_MODE,
|
||||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export const VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED = 'VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED';
|
||||
export const SET_CAR_MODE = ' SET_CAR_MODE';
|
||||
|
||||
/**
|
||||
* The type of the action which enables or disables the feature for showing
|
||||
|
@ -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 sets the list of known remote virtual screen share participant IDs.
|
||||
*
|
||||
* @returns {{
|
||||
* type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||
* participantIds: Array<string>
|
||||
* }}
|
||||
*/
|
||||
export const VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED = 'VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED';
|
||||
|
|
|
@ -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';
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -4,11 +4,20 @@ import { ReducerRegistry } from '../base/redux';
|
|||
|
||||
import {
|
||||
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||
SET_CAR_MODE,
|
||||
SET_TILE_VIEW,
|
||||
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
/**
|
||||
* Whether we are in carmode.
|
||||
*
|
||||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
carMode: false,
|
||||
|
||||
remoteScreenShares: [],
|
||||
|
||||
/**
|
||||
|
@ -29,12 +38,17 @@ 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_CAR_MODE:
|
||||
return {
|
||||
...state,
|
||||
carMode: action.enabled
|
||||
};
|
||||
|
||||
case SET_TILE_VIEW:
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue