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:
parent
9ee75038b6
commit
d22fc88ae3
|
@ -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
|
||||
},
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
const renderMenuHeader = () => (
|
||||
<View
|
||||
style = { styles.contextMenuItemSectionAvatar }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
participantId = { p.id }
|
||||
size = { 24 } />
|
||||
<Text style = { styles.contextMenuItemName }>
|
||||
{ displayName }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
addScrollViewPadding = { false }
|
||||
onCancel = { cancel }
|
||||
/* eslint-disable-next-line react/jsx-no-bind */
|
||||
renderHeader = { renderMenuHeader }
|
||||
showSlidingView = { Boolean(knockParticipantIsAvailable) }
|
||||
style = { styles.contextMenuMore }>
|
||||
<View
|
||||
style = { styles.contextMenuItemSectionAvatar }>
|
||||
<Avatar
|
||||
className = 'participant-avatar'
|
||||
participantId = { p.id }
|
||||
size = { 20 } />
|
||||
<View style = { styles.contextMenuItemAvatarText }>
|
||||
<Text style = { styles.contextMenuItemName }>
|
||||
{ displayName }
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Divider style = { styles.divider } />
|
||||
<TouchableOpacity
|
||||
onPress = { reject }
|
||||
style = { styles.contextMenuItem }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
size = { 24 }
|
||||
src = { IconClose } />
|
||||
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
|
||||
</TouchableOpacity>
|
||||
|
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 } />
|
||||
);
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
||||
/**
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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));
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue