feat(participants-pane) context menu ui fixes

- Fixed background color for all participants context menus
- Removed connection status from ReactVideoMenu and added it for local participants
- Removed AVModeration comments on mobile
- Show on stage option visible only when participants pane is closed
This commit is contained in:
Calinteodor 2021-07-30 12:46:49 +03:00 committed by GitHub
parent 9ee75038b6
commit d22fc88ae3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 169 additions and 360 deletions

View File

@ -171,7 +171,7 @@ ColorSchemeRegistry.register('BottomSheet', {
*/
labelStyle: {
...brandedDialogLabelStyle,
marginLeft: 32
marginLeft: 16
},
/**
@ -179,7 +179,6 @@ ColorSchemeRegistry.register('BottomSheet', {
*/
style: {
...brandedDialogItemContainerStyle,
backgroundColor: ColorPalette.darkBackground,
paddingHorizontal: MD_ITEM_MARGIN_PADDING
},

View File

@ -18,6 +18,7 @@ export const colors = {
primary08: '#99BBF3',
primary09: '#CCDDF9',
surface00: '#111111',
surface01: '#040404',
surface02: '#141414',
surface03: '#292929',
@ -29,6 +30,7 @@ export const colors = {
surface09: '#C2C2C2',
surface10: '#E0E0E0',
surface11: '#FFF',
surface12: '#AAAAAA',
success04: '#189B55',
success05: '#1EC26A',
@ -109,6 +111,9 @@ export const colorMap = {
// Disabled state for danger buttons
actionDangerDisabled: 'error03',
// Bottom sheet background
bottomSheet: 'surface00',
// Primary text default color for body copy & headers
text01: 'surface11',
@ -118,6 +123,9 @@ export const colorMap = {
// Tertiary text with low contrast placeholders, disabled actions, label for disabled buttons
text03: 'surface07',
// Text for bottom sheet items
text04: 'surface12',
// error messages
textError: 'error06',
@ -149,6 +157,9 @@ export const colorMap = {
// Background for high-contrast input fields
field02: 'surface11',
// Color for the section divider
dividerColor: 'surface12',
// Background for high-contrast input fields on hover
field02Hover: 'primary09',

View File

@ -1,10 +1,12 @@
// @flow
import { openDialog } from '../base/dialog';
import ConnectionStatusComponent
from '../video-menu/components/native/ConnectionStatusComponent';
import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
import { SET_VOLUME } from './actionTypes';
import {
ContextMenuMeetingParticipantDetails,
ContextMenuLobbyParticipantReject
} from './components/native';
export * from './actions.any';
@ -21,13 +23,23 @@ export function showContextMenuReject(participant: Object) {
/**
* Displays the context menu for the selected meeting participant.
* Displays the connection status for the local meeting participant.
*
* @param {string} participantID - The selected meeting participant id.
* @returns {Function}
*/
export function showContextMenuDetails(participantID: String) {
return openDialog(ContextMenuMeetingParticipantDetails, { participantID });
export function showConnectionStatus(participantID: String) {
return openDialog(ConnectionStatusComponent, { participantID });
}
/**
* Displays the context menu for the selected meeting participant.
*
* @param {Object} participant - The selected meeting participant.
* @returns {Function}
*/
export function showContextMenuDetails(participant: Object) {
return openDialog(RemoteVideoMenu, { participant });
}
/**

View File

@ -3,7 +3,7 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native';
import { Divider, Text } from 'react-native-paper';
import { Text } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { Avatar } from '../../../base/avatar';
@ -33,30 +33,33 @@ const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
const { t } = useTranslation();
return (
<BottomSheet
addScrollViewPadding = { false }
onCancel = { cancel }
showSlidingView = { Boolean(knockParticipantIsAvailable) }
style = { styles.contextMenuMore }>
// eslint-disable-next-line react/no-multi-comp
const renderMenuHeader = () => (
<View
style = { styles.contextMenuItemSectionAvatar }>
<Avatar
className = 'participant-avatar'
participantId = { p.id }
size = { 20 } />
<View style = { styles.contextMenuItemAvatarText }>
size = { 24 } />
<Text style = { styles.contextMenuItemName }>
{ displayName }
</Text>
</View>
</View>
<Divider style = { styles.divider } />
);
return (
<BottomSheet
addScrollViewPadding = { false }
onCancel = { cancel }
/* eslint-disable-next-line react/jsx-no-bind */
renderHeader = { renderMenuHeader }
showSlidingView = { Boolean(knockParticipantIsAvailable) }
style = { styles.contextMenuMore }>
<TouchableOpacity
onPress = { reject }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
size = { 24 }
src = { IconClose } />
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
</TouchableOpacity>

View File

@ -1,264 +0,0 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native';
import { Divider, Text } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { Avatar } from '../../../base/avatar';
import { hideDialog, openDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import {
Icon, IconCloseCircle, IconMessage,
IconMicrophoneEmptySlash,
IconMuteEveryoneElse, IconVideoOff
} from '../../../base/icons';
import {
getLocalParticipant,
getParticipantByIdOrUndefined,
getParticipantDisplayName, getRemoteParticipants,
isLocalParticipantModerator
} from '../../../base/participants/functions';
import { connect } from '../../../base/redux';
import {
isParticipantAudioMuted,
isParticipantVideoMuted
} from '../../../base/tracks/functions';
import { openChat } from '../../../chat/actions.native';
import {
KickRemoteParticipantDialog,
MuteEveryoneDialog,
MuteRemoteParticipantDialog,
MuteRemoteParticipantsVideoDialog
} from '../../../video-menu';
import VolumeSlider from '../../../video-menu/components/native/VolumeSlider';
import styles from './styles';
type Props = {
/**
* The display name of the participant.
*/
_displayName: string,
/**
* True if the local participant is moderator and false otherwise.
*/
_isLocalModerator: boolean,
/**
* True if the participant is moderator and false otherwise.
*/
_isParticipantModerator: boolean,
/**
* True if the participant is video muted and false otherwise.
*/
_isParticipantVideoMuted: boolean,
/**
* True if the participant is audio muted and false otherwise.
*/
_isParticipantAudioMuted: boolean,
/**
* Whether the participant is present in the room or not.
*/
_isParticipantIDAvailable?: boolean,
/**
* Participant reference
*/
_participant: Object,
/**
* The ID of the participant.
*/
participantID: string,
};
const ContextMenuMeetingParticipantDetails = (
{
_displayName,
_isLocalModerator,
_isParticipantVideoMuted,
_isParticipantAudioMuted,
_participant,
_isParticipantIDAvailable,
participantID
}: Props) => {
const dispatch = useDispatch();
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const kickRemoteParticipant = useCallback(() => {
dispatch(openDialog(KickRemoteParticipantDialog, {
participantID
}));
}, [ dispatch, participantID ]);
const muteAudio = useCallback(() => {
dispatch(openDialog(MuteRemoteParticipantDialog, {
participantID
}));
}, [ dispatch, participantID ]);
const muteEveryoneElse = useCallback(() => {
dispatch(openDialog(MuteEveryoneDialog, {
exclude: [ participantID ]
}));
}, [ dispatch, participantID ]);
const muteVideo = useCallback(() => {
dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
participantID
}));
}, [ dispatch, participantID ]);
const sendPrivateMessage = useCallback(() => {
dispatch(hideDialog());
dispatch(openChat(_participant));
}, [ dispatch, _participant ]);
const { t } = useTranslation();
return (
<BottomSheet
addScrollViewPadding = { false }
onCancel = { cancel }
showSlidingView = { _isParticipantIDAvailable }
style = { styles.contextMenuMeetingParticipantDetails }>
<View
style = { styles.contextMenuItemSectionAvatar }>
<Avatar
className = 'participant-avatar'
participantId = { participantID }
size = { 20 } />
<View style = { styles.contextMenuItemAvatarText }>
<Text style = { styles.contextMenuItemName }>
{ _displayName }
</Text>
</View>
</View>
<Divider style = { styles.divider } />
{
_isLocalModerator && (
<>
{
!_isParticipantAudioMuted
&& <TouchableOpacity
onPress = { muteAudio }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconMicrophoneEmptySlash } />
<Text style = { styles.contextMenuItemText }>
{ t('participantsPane.actions.mute') }
</Text>
</TouchableOpacity>
}
<TouchableOpacity
onPress = { muteEveryoneElse }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconMuteEveryoneElse } />
<Text style = { styles.contextMenuItemText }>
{ t('participantsPane.actions.muteEveryoneElse') }
</Text>
</TouchableOpacity>
</>
)
}
<Divider style = { styles.divider } />
{
_isLocalModerator && (
<>
{
!_isParticipantVideoMuted
&& <TouchableOpacity
onPress = { muteVideo }
style = { styles.contextMenuItemSection }>
<Icon
size = { 20 }
src = { IconVideoOff } />
<Text style = { styles.contextMenuItemText }>
{ t('participantsPane.actions.stopVideo') }
</Text>
</TouchableOpacity>
}
<TouchableOpacity
onPress = { kickRemoteParticipant }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconCloseCircle } />
<Text style = { styles.contextMenuItemText }>
{ t('videothumbnail.kick') }
</Text>
</TouchableOpacity>
</>
)
}
<TouchableOpacity
onPress = { sendPrivateMessage }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconMessage } />
<Text style = { styles.contextMenuItemText }>
{ t('toolbar.accessibilityLabel.privateMessage') }
</Text>
</TouchableOpacity>
{/* We need design specs for this*/}
{/* <TouchableOpacity*/}
{/* style = { styles.contextMenuItemSection }>*/}
{/* <Icon*/}
{/* size = { 20 }*/}
{/* src = { IconConnectionActive }*/}
{/* style = { styles.contextMenuItemIcon } />*/}
{/* <Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.networkStats') }</Text>*/}
{/* </TouchableOpacity>*/}
<Divider style = { styles.divider } />
<VolumeSlider participantID = { participantID } />
</BottomSheet>
);
};
/**
* Maps (parts of) the redux state to the associated props for this component.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The own props of the component.
* @private
* @returns {Props}
*/
function _mapStateToProps(state, ownProps): Object {
const { participantID } = ownProps;
const participantIDS = [];
const participant = getParticipantByIdOrUndefined(state, participantID);
const _isLocalModerator = isLocalParticipantModerator(state);
const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state);
const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state);
const localParticipant = getLocalParticipant(state);
const remoteParticipants = getRemoteParticipants(state);
localParticipant && participantIDS.push(localParticipant?.id);
remoteParticipants.forEach(p => {
participantIDS.push(p?.id);
});
const isParticipantIDAvailable = participantIDS.find(partID => partID === participantID);
return {
_displayName: getParticipantDisplayName(state, participantID),
_isLocalModerator,
_isParticipantAudioMuted,
_isParticipantIDAvailable: Boolean(isParticipantIDAvailable),
_isParticipantVideoMuted,
_participant: participant
};
}
export default connect(_mapStateToProps)(ContextMenuMeetingParticipantDetails);

View File

@ -9,14 +9,13 @@ import { useDispatch, useSelector } from 'react-redux';
import { openDialog, hideDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import {
Icon, IconMicDisabledHollow,
Icon,
IconVideoOff
} from '../../../base/icons';
import {
getLocalParticipant,
getParticipantCount
} from '../../../base/participants';
import { BlockAudioVideoDialog } from '../../../video-menu';
import MuteEveryonesVideoDialog
from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
@ -24,7 +23,6 @@ import styles from './styles';
export const ContextMenuMore = () => {
const dispatch = useDispatch();
const blockAudioVideo = useCallback(() => dispatch(openDialog(BlockAudioVideoDialog)), [ dispatch ]);
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const { id } = useSelector(getLocalParticipant);
const participantsCount = useSelector(getParticipantCount);
@ -45,21 +43,10 @@ export const ContextMenuMore = () => {
onPress = { muteAllVideo }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
size = { 24 }
src = { IconVideoOff } />
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
</TouchableOpacity>
<TouchableOpacity
onPress = { blockAudioVideo }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconMicDisabledHollow }
style = { styles.contextMenuIcon } />
<Text style = { styles.contextMenuItemText }>
{t('participantsPane.actions.blockEveryoneMicCamera')}
</Text>
</TouchableOpacity>
</BottomSheet>
);
};

View File

@ -13,7 +13,7 @@ import {
getRemoteParticipants
} from '../../../base/participants';
import { doInvitePeople } from '../../../invite/actions.native';
import { showContextMenuDetails } from '../../actions.native';
import { showConnectionStatus, showContextMenuDetails } from '../../actions.native';
import { shouldRenderInviteButton } from '../../functions';
import MeetingParticipantItem from './MeetingParticipantItem';
@ -31,11 +31,11 @@ export const MeetingParticipantList = () => {
// eslint-disable-next-line react/no-multi-comp
const renderParticipant = p => (
<MeetingParticipantItem
key = { p.id }
/* eslint-disable-next-line react/jsx-no-bind */
onPress = { () => !p.local && dispatch(showContextMenuDetails(p.id)) }
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
onPress = { () => p.local
? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
participantID = { p.id } />
);

View File

@ -3,4 +3,3 @@
export { default as ParticipantsPane } from './ParticipantsPane';
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';
export { default as ContextMenuMeetingParticipantDetails } from './ContextMenuMeetingParticipantDetails';

View File

@ -145,7 +145,7 @@ export default {
display: 'flex',
flexDirection: 'row',
overflow: 'hidden',
paddingLeft: BaseTheme.spacing[2],
paddingLeft: BaseTheme.spacing[3],
width: '63%'
},
@ -187,6 +187,7 @@ export default {
...flexContent,
top: BaseTheme.spacing[1]
},
lobbyList: {
position: 'relative'
},
@ -277,12 +278,7 @@ export default {
},
contextMenuMore: {
backgroundColor: BaseTheme.palette.action02,
borderRadius: BaseTheme.shape.borderRadius
},
contextMenuMeetingParticipantDetails: {
backgroundColor: BaseTheme.palette.action02,
backgroundColor: BaseTheme.palette.bottomSheet,
borderRadius: BaseTheme.shape.borderRadius
},
@ -303,8 +299,6 @@ export default {
marginHorizontal: BaseTheme.spacing[0],
paddingTop: 12,
paddingBottom: 12,
paddingRight: BaseTheme.spacing[3],
paddingLeft: BaseTheme.spacing[3],
textTransform: 'capitalize',
width: 94
},
@ -318,13 +312,15 @@ export default {
},
contextMenuItemSectionAvatar: {
...contextMenuItem,
marginLeft: BaseTheme.spacing[3]
},
contextMenuItemAvatarText: {
...contextMenuItemText,
marginLeft: BaseTheme.spacing[3]
alignItems: 'center',
backgroundColor: BaseTheme.palette.bottomSheet,
borderBottomColor: BaseTheme.palette.dividerColor,
borderBottomWidth: 1,
borderTopLeftRadius: BaseTheme.spacing[3],
borderTopRightRadius: BaseTheme.spacing[3],
flexDirection: 'row',
height: BaseTheme.spacing[7],
paddingLeft: BaseTheme.spacing[3]
},
contextMenuItemText: {
@ -333,15 +329,14 @@ export default {
},
contextMenuItemName: {
...BaseTheme.typography.bodyShortRegularLarge,
color: BaseTheme.palette.text01
},
contextMenuIcon: {
color: BaseTheme.palette.actionDanger
color: BaseTheme.palette.text04,
flexShrink: 1,
fontSize: BaseTheme.spacing[3],
marginLeft: BaseTheme.spacing[3],
opacity: 0.90
},
divider: {
backgroundColor: BaseTheme.palette.section01
backgroundColor: BaseTheme.palette.dividerColor
}
};

View File

@ -1,7 +1,7 @@
// @flow
import { openDialog } from '../../base/dialog';
import { IconKick } from '../../base/icons';
import { IconCloseCircle } from '../../base/icons';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { KickRemoteParticipantDialog } from '.';
@ -29,7 +29,7 @@ export type Props = AbstractButtonProps & {
*/
export default class AbstractKickButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.kick';
icon = IconKick;
icon = IconCloseCircle;
label = 'videothumbnail.kick';
/**

View File

@ -5,7 +5,7 @@ import {
sendAnalytics
} from '../../analytics';
import { openDialog } from '../../base/dialog';
import { IconCameraDisabled } from '../../base/icons';
import { IconVideoOff } from '../../base/icons';
import { MEDIA_TYPE } from '../../base/media';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { isRemoteTrackMuted } from '../../base/tracks';
@ -42,7 +42,7 @@ export type Props = AbstractButtonProps & {
*/
export default class AbstractMuteVideoButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.remoteVideoMute';
icon = IconCameraDisabled;
icon = IconVideoOff;
label = 'videothumbnail.domuteVideo';
toggledLabel = 'videothumbnail.videoMuted';

View File

@ -2,6 +2,8 @@
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { withTheme } from 'react-native-paper';
import { Avatar } from '../../../base/avatar';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
@ -11,7 +13,7 @@ import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons';
import { getParticipantDisplayName } from '../../../base/participants';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux';
import { StyleType, ColorPalette } from '../../../base/styles';
import { StyleType } from '../../../base/styles';
import statsEmitter from '../../../connection-indicator/statsEmitter';
import styles from './styles';
@ -57,7 +59,12 @@ export type Props = {
/**
* The function to be used to translate i18n labels.
*/
t: Function
t: Function,
/**
* Theme used for styles.
*/
theme: Object
}
/**
@ -116,7 +123,8 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @returns {React$Node}
*/
render(): React$Node {
const { t } = this.props;
const { t, theme } = this.props;
const { palette } = theme;
return (
<BottomSheet
@ -138,7 +146,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
<BaseIndicator
icon = { IconArrowDownLarge }
iconStyle = {{
color: ColorPalette.darkGrey
color: palette.icon03
}} />
<Text style = { styles.statsInfoText }>
{ this.state.downloadString }
@ -146,7 +154,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
<BaseIndicator
icon = { IconArrowUpLarge }
iconStyle = {{
color: ColorPalette.darkGrey
color: palette.icon03
}} />
<Text style = { styles.statsInfoText }>
{ `${this.state.uploadString} Kbps` }
@ -159,7 +167,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
<BaseIndicator
icon = { IconArrowDownLarge }
iconStyle = {{
color: ColorPalette.darkGrey
color: palette.icon03
}} />
<Text style = { styles.statsInfoText }>
{ this.state.packetLostDownloadString }
@ -167,7 +175,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
<BaseIndicator
icon = { IconArrowUpLarge }
iconStyle = {{
color: ColorPalette.darkGrey
color: palette.icon03
}} />
<Text style = { styles.statsInfoText }>
{ this.state.packetLostUploadString }
@ -426,6 +434,6 @@ function _mapStateToProps(state, ownProps) {
};
}
ConnectionStatusComponent_ = translate(connect(_mapStateToProps)(ConnectionStatusComponent));
ConnectionStatusComponent_ = translate(connect(_mapStateToProps)(withTheme(ConnectionStatusComponent)));
export default ConnectionStatusComponent_;

View File

@ -0,0 +1,22 @@
// @flow
import { translate } from '../../../base/i18n';
import { isLocalParticipantModerator } from '../../../base/participants';
import { connect } from '../../../base/redux';
import AbstractMuteVideoButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractMuteVideoButton';
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - Properties of component.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
return {
..._abstractMapStateToProps(state, ownProps),
visible: isLocalParticipantModerator(state)
};
}
export default translate(connect(_mapStateToProps)(AbstractMuteVideoButton));

View File

@ -59,8 +59,10 @@ class PinButton extends AbstractButton<Props, *> {
* @returns {Props}
*/
function _mapStateToProps(state) {
const { isOpen } = state['features/participants-pane'];
return {
visible: shouldDisplayTileView(state)
visible: !isOpen && shouldDisplayTileView(state)
};
}

View File

@ -2,29 +2,37 @@
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { Divider } from 'react-native-paper';
import { Avatar } from '../../../base/avatar';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { BottomSheet, isDialogOpen } from '../../../base/dialog';
import { KICK_OUT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { getParticipantDisplayName } from '../../../base/participants';
import {
getParticipantById,
getParticipantDisplayName
} from '../../../base/participants';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import { PrivateMessageButton } from '../../../chat';
import { hideRemoteVideoMenu } from '../../actions.native';
import ConnectionStatusButton from '../native/ConnectionStatusButton';
import ConnectionStatusButton from './ConnectionStatusButton';
import GrantModeratorButton from './GrantModeratorButton';
import KickButton from './KickButton';
import MuteButton from './MuteButton';
import MuteEveryoneElseButton from './MuteEveryoneElseButton';
import MuteVideoButton from './MuteVideoButton';
import PinButton from './PinButton';
import styles from './styles';
// import VolumeSlider from './VolumeSlider';
/**
* Size of the rendered avatar in the menu.
*/
const AVATAR_SIZE = 25;
const AVATAR_SIZE = 24;
type Props = {
@ -63,10 +71,20 @@ type Props = {
*/
_isOpen: boolean,
/**
* Whether the participant is present in the room or not.
*/
_isParticipantAvailable?: boolean,
/**
* Display name of the participant retrieved from Redux.
*/
_participantDisplayName: string
_participantDisplayName: string,
/**
* The ID of the participant.
*/
_participantID: ?string,
}
// eslint-disable-next-line prefer-const
@ -94,7 +112,13 @@ class RemoteVideoMenu extends PureComponent<Props> {
* @inheritdoc
*/
render() {
const { _disableKick, _disableRemoteMute, _disableGrantModerator, participant } = this.props;
const {
_disableKick,
_disableRemoteMute,
_disableGrantModerator,
_isParticipantAvailable,
participant
} = this.props;
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
@ -105,14 +129,19 @@ class RemoteVideoMenu extends PureComponent<Props> {
return (
<BottomSheet
onCancel = { this._onCancel }
renderHeader = { this._renderMenuHeader }>
renderHeader = { this._renderMenuHeader }
showSlidingView = { _isParticipantAvailable }>
{ !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
<MuteEveryoneElseButton { ...buttonProps } />
{ !_disableRemoteMute && <MuteVideoButton { ...buttonProps } /> }
<Divider style = { styles.divider } />
{ !_disableKick && <KickButton { ...buttonProps } /> }
{ !_disableGrantModerator && <GrantModeratorButton { ...buttonProps } /> }
<PinButton { ...buttonProps } />
<PrivateMessageButton { ...buttonProps } />
<MuteEveryoneElseButton { ...buttonProps } />
<ConnectionStatusButton { ...buttonProps } />
{/* <Divider style = { styles.divider } />*/}
{/* <VolumeSlider participantID = { _participantID } />*/}
</BottomSheet>
);
}
@ -173,6 +202,7 @@ function _mapStateToProps(state, ownProps) {
const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true);
const { participant } = ownProps;
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];
const isParticipantAvailable = getParticipantById(state, participant.id);
let { disableKick } = remoteVideoMenu;
disableKick = disableKick || !kickOutEnabled;
@ -182,7 +212,9 @@ function _mapStateToProps(state, ownProps) {
_disableKick: Boolean(disableKick),
_disableRemoteMute: Boolean(disableRemoteMute),
_isOpen: isDialogOpen(state, RemoteVideoMenu_),
_participantDisplayName: getParticipantDisplayName(state, participant.id)
_isParticipantAvailable: Boolean(isParticipantAvailable),
_participantDisplayName: getParticipantDisplayName(state, participant.id),
_participantID: participant.id
};
}

View File

@ -7,7 +7,6 @@ import { View } from 'react-native';
import { withTheme } from 'react-native-paper';
import { Icon, IconVolumeEmpty } from '../../../base/icons';
import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { setVolume } from '../../../participants-pane/actions.native';
import { VOLUME_SLIDER_SCALE } from '../../constants';
@ -102,7 +101,7 @@ class VolumeSlider extends PureComponent<Props, State> {
return (
<View style = { styles.volumeSliderContainer } >
<Icon
size = { 20 }
size = { 24 }
src = { IconVolumeEmpty } />
<Slider
maximumTrackTintColor = { palette.field02 }
@ -145,12 +144,10 @@ function mapStateToProps(state, ownProps): Object {
const { participantID } = ownProps;
const { participantsVolume } = state['features/participants-pane'];
const { startSilent } = state['features/base/config'];
const localParticipant = getLocalParticipant(state);
return {
_startSilent: Boolean(startSilent),
_volume: localParticipant ? undefined : participantID
? participantsVolume[participantID] : undefined
_volume: participantID && participantsVolume[participantID]
};
}

View File

@ -11,8 +11,8 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export default createStyleSheet({
participantNameContainer: {
alignItems: 'center',
borderBottomColor: ColorPalette.lightGrey,
borderBottomWidth: 1,
borderBottomColor: BaseTheme.palette.dividerColor,
borderBottomWidth: 0.4,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
flexDirection: 'row',
@ -29,12 +29,14 @@ export default createStyleSheet({
},
statsTitleText: {
color: BaseTheme.palette.text01,
fontSize: 16,
fontWeight: 'bold',
marginRight: 3
},
statsInfoText: {
color: BaseTheme.palette.text01,
fontSize: 16,
marginRight: 2,
marginLeft: 2
@ -48,18 +50,22 @@ export default createStyleSheet({
},
statsWrapper: {
marginVertical: 10
margin: BaseTheme.spacing[3]
},
volumeSliderContainer: {
alignItems: 'center',
flexDirection: 'row',
marginLeft: BaseTheme.spacing[3],
marginTop: BaseTheme.spacing[3]
marginHorizontal: BaseTheme.spacing[3],
marginVertical: BaseTheme.spacing[2]
},
sliderContainer: {
marginLeft: BaseTheme.spacing[3],
minWidth: '84%'
minWidth: '80%'
},
divider: {
backgroundColor: BaseTheme.palette.dividerColor
}
});