diff --git a/lang/main.json b/lang/main.json index edcec2f30..76032190a 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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", diff --git a/react/features/analytics/AnalyticsEvents.js b/react/features/analytics/AnalyticsEvents.js index 99ab51275..6eb97a8f0 100644 --- a/react/features/analytics/AnalyticsEvents.js +++ b/react/features/analytics/AnalyticsEvents.js @@ -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. diff --git a/react/features/base/media/actions.js b/react/features/base/media/actions.js index b53481ba6..76f2e75f0 100644 --- a/react/features/base/media/actions.js +++ b/react/features/base/media/actions.js @@ -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, getState: Function) => { diff --git a/react/features/base/media/constants.js b/react/features/base/media/constants.js index 4846e99be..67d9a3093 100644 --- a/react/features/base/media/constants.js +++ b/react/features/base/media/constants.js @@ -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 */ diff --git a/react/features/chat/components/native/ChatAndPolls.js b/react/features/chat/components/native/ChatAndPolls.js index 30400c836..526fb1ad3 100644 --- a/react/features/chat/components/native/ChatAndPolls.js +++ b/react/features/chat/components/native/ChatAndPolls.js @@ -16,7 +16,6 @@ import { PollsPane } from '../../../polls/components'; const ChatTab = createMaterialTopTabNavigator(); - const ChatAndPolls = () => { const clientHeight = useSelector(getClientHeight); const clientWidth = useSelector(getClientWidth); diff --git a/react/features/conference/components/native/ConferenceTab.tsx b/react/features/conference/components/native/ConferenceTab.tsx deleted file mode 100644 index 04463e9a5..000000000 --- a/react/features/conference/components/native/ConferenceTab.tsx +++ /dev/null @@ -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 ( - - - - - - - - - - - - - - - ); -}; - -export default ConferenceTab; diff --git a/react/features/conference/components/native/carmode/Conference.tsx b/react/features/conference/components/native/carmode/Conference.tsx index 5a9a12db9..f78f2c17d 100644 --- a/react/features/conference/components/native/carmode/Conference.tsx +++ b/react/features/conference/components/native/carmode/Conference.tsx @@ -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 ( - + {/* * The activity/loading indicator goes above everything, except * the toolbox/toolbars and the dialogs. @@ -62,22 +52,21 @@ const CarmodeTab = ({ onFocused }: Props): JSX.Element => { } - - - - {t('carmode.labels.title')} - - - - - + + + + @@ -85,8 +74,8 @@ const CarmodeTab = ({ onFocused }: Props): JSX.Element => { - - + + ); }; diff --git a/react/features/conference/components/native/carmode/MicrophoneButton.tsx b/react/features/conference/components/native/carmode/MicrophoneButton.tsx index 4f3c05fc1..64f341a2f 100644 --- a/react/features/conference/components/native/carmode/MicrophoneButton.tsx +++ b/react/features/conference/components/native/carmode/MicrophoneButton.tsx @@ -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 ( + onPressIn = { onPressIn } + onLongPress={ onLongPress } + onPressOut={ onPressOut } > (<> - - - diff --git a/react/features/conference/components/native/carmode/styles.js b/react/features/conference/components/native/carmode/styles.js index ee3298138..fb72afc2e 100644 --- a/react/features/conference/components/native/carmode/styles.js +++ b/react/features/conference/components/native/carmode/styles.js @@ -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, diff --git a/react/features/mobile/navigation/components/conference/ConferenceNavigationContainerRef.js b/react/features/mobile/navigation/components/conference/ConferenceNavigationContainerRef.js index 892da4851..70a5fc047 100644 --- a/react/features/mobile/navigation/components/conference/ConferenceNavigationContainerRef.js +++ b/react/features/mobile/navigation/components/conference/ConferenceNavigationContainerRef.js @@ -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); } diff --git a/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx b/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx index f0b43895d..b4b0fdf53 100644 --- a/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx +++ b/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx @@ -1,69 +1,167 @@ -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(() => ( - - ), []); - - const Conference = useCallback(() => ( - - ), []); + 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 ( - - + - - - + name = { screen.conference.main } + options = { conferenceScreenOptions } /> + + + + + + + + + + + + + ); }; -export default ConferenceNavigationContainer; +export default ConferenceNavigationContainer; \ No newline at end of file diff --git a/react/features/mobile/navigation/components/conference/components/NavigationThumb.tsx b/react/features/mobile/navigation/components/conference/components/NavigationThumb.tsx deleted file mode 100644 index 4d5618a87..000000000 --- a/react/features/mobile/navigation/components/conference/components/NavigationThumb.tsx +++ /dev/null @@ -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 => ( - - { - Object.values(THUMBS) - .map(value => - () - ) - } - -); - -export default NavigationThumb; diff --git a/react/features/mobile/navigation/components/conference/components/styles.js b/react/features/mobile/navigation/components/conference/components/styles.js deleted file mode 100644 index f5ed5d928..000000000 --- a/react/features/mobile/navigation/components/conference/components/styles.js +++ /dev/null @@ -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' - } - } - -}; diff --git a/react/features/mobile/navigation/routes.js b/react/features/mobile/navigation/routes.js index d5453c51d..09a605097 100644 --- a/react/features/mobile/navigation/routes.js +++ b/react/features/mobile/navigation/routes.js @@ -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', diff --git a/react/features/mobile/navigation/screenOptions.js b/react/features/mobile/navigation/screenOptions.js index 539b863db..f0e34a2ff 100644 --- a/react/features/mobile/navigation/screenOptions.js +++ b/react/features/mobile/navigation/screenOptions.js @@ -198,6 +198,11 @@ export const presentationScreenOptions = { } }; +/** + * Screen options for car mode. + */ +export const carmodeScreenOptions = presentationScreenOptions; + /** * Screen options for chat. */ diff --git a/react/features/toolbox/components/native/OpenCarmodeButton.tsx b/react/features/toolbox/components/native/OpenCarmodeButton.tsx new file mode 100644 index 000000000..b09c29cad --- /dev/null +++ b/react/features/toolbox/components/native/OpenCarmodeButton.tsx @@ -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 { + 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)); diff --git a/react/features/toolbox/components/native/OverflowMenu.js b/react/features/toolbox/components/native/OverflowMenu.js index cf095e0b3..3d0a3484a 100644 --- a/react/features/toolbox/components/native/OverflowMenu.js +++ b/react/features/toolbox/components/native/OverflowMenu.js @@ -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 { ? this._renderReactionMenu : null }> + {!_reactionsEnabled && !toolbarButtons.has('raisehand') && } diff --git a/react/features/video-layout/middleware.native.js b/react/features/video-layout/middleware.native.js index fefd329e8..d7f6466b0 100644 --- a/react/features/video-layout/middleware.native.js +++ b/react/features/video-layout/middleware.native.js @@ -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; +});