feat(native-participants-pane) context menu for more btn and reject lobby participant

This commit is contained in:
Calin Chitu 2021-06-03 19:23:18 +03:00 committed by Hristo Terezov
parent cd05c34d19
commit ba64d3e0c8
10 changed files with 275 additions and 65 deletions

View File

@ -25,11 +25,21 @@ const GESTURE_SPEED_THRESHOLD = 0.2;
*/
type Props = {
/**
* The height of the screen.
*/
_height: number,
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Whether to add padding to scroll view.
*/
addScrollViewPadding?: boolean,
/**
* The children to be displayed within this component.
*/
@ -57,9 +67,14 @@ type Props = {
renderFooter: ?Function,
/**
* The height of the screen.
*/
_height: number
* Whether to show sliding view or not.
*/
showSlidingView?: boolean,
/**
* The component's external style
*/
style: Object
};
/**
@ -68,6 +83,16 @@ type Props = {
class BottomSheet extends PureComponent<Props> {
panResponder: Object;
/**
* Default values for {@code BottomSheet} component's properties.
*
* @static
*/
static defaultProps = {
addScrollViewPadding: true,
showSlidingView: true
};
/**
* Instantiates a new component.
*
@ -90,7 +115,15 @@ class BottomSheet extends PureComponent<Props> {
* @returns {ReactElement}
*/
render() {
const { _styles, renderHeader, renderFooter, _height } = this.props;
const {
_height,
_styles,
addScrollViewPadding,
renderHeader,
renderFooter,
showSlidingView,
style
} = this.props;
return (
<SlidingView
@ -98,7 +131,7 @@ class BottomSheet extends PureComponent<Props> {
accessibilityViewIsModal = { true }
onHide = { this.props.onCancel }
position = 'bottom'
show = { true }>
show = { showSlidingView }>
<View
pointerEvents = 'box-none'
style = { styles.sheetContainer }>
@ -110,6 +143,7 @@ class BottomSheet extends PureComponent<Props> {
style = { [
styles.sheetItemContainer,
_styles.sheet,
style,
{
maxHeight: _height - 100
}
@ -118,7 +152,7 @@ class BottomSheet extends PureComponent<Props> {
<ScrollView
bounces = { false }
showsVerticalScrollIndicator = { false }
style = { styles.scrollView } >
style = { addScrollViewPadding && styles.scrollView } >
{ this.props.children }
</ScrollView>
{ renderFooter && renderFooter() }

View File

@ -0,0 +1,13 @@
import { openDialog } from '../base/dialog';
import { ContextMenuReject } from './components/native/ContextMenuReject';
/**
* Displays the context menu for the selected lobby participant.
*
* @param {string} participant - The selected participant's id.
* @returns {Function}
*/
export function showContextMenuReject(participant) {
return openDialog(ContextMenuReject, { participant });
}

View File

@ -0,0 +1,60 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity } from 'react-native';
import { Text } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { hideDialog } from '../../../base/dialog';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import {
Icon, IconMicDisabledHollow,
IconVideoOff
} from '../../../base/icons';
import { MEDIA_TYPE } from '../../../base/media';
import { muteAllParticipants } from '../../../video-menu/actions.any';
import styles from './styles';
type Props = {
/**
* Array of participant IDs to not mute
*/
exclude: Array<string>,
/**
* Participant reference
*/
participant: Object
};
export const ContextMenuMore = ({ exclude }: Props) => {
const dispatch = useDispatch();
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const muteEveryoneVideo = useCallback(() => dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO)), [ dispatch ]);
const { t } = useTranslation();
return (
<BottomSheet
onCancel = { cancel }
style = { styles.contextMenuMore }>
<TouchableOpacity
onPress = { muteEveryoneVideo }
style = { styles.contextMenuItemMuteVideo }>
<Icon
size = { 24 }
src = { IconVideoOff } />
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
</TouchableOpacity>
<TouchableOpacity
style = { styles.contextMenuItemDontAllowUnmute }>
<Icon
size = { 24 }
src = { IconMicDisabledHollow }
style = { styles.contextMenuIcon } />
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.dontAllowUnmute')}</Text>
</TouchableOpacity>
</BottomSheet>
);
};

View File

@ -0,0 +1,59 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native';
import { Text } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { Avatar } from '../../../base/avatar';
import { hideDialog } from '../../../base/dialog';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import {
Icon, IconClose
} from '../../../base/icons';
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
import styles from './styles';
type Props = {
/**
* Participant reference
*/
participant: Object
};
export const ContextMenuReject = ({ participant: p }: Props) => {
const dispatch = useDispatch();
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const displayName = p.name;
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
const { t } = useTranslation();
return (
<BottomSheet
onCancel = { cancel }
style = { styles.contextMenuMore }>
<View
style = { styles.contextMenuItemDetails }>
<Avatar
className = 'participant-avatar'
participantId = { p.id }
size = { 32 } />
<View style = { styles.contextMenuItemText }>
<Text style = { styles.contextMenuItemParticipantName }>
{ displayName }
</Text>
</View>
</View>
<TouchableOpacity
onPress = { reject }
style = { styles.contextMenuItemReject }>
<Icon
size = { 24 }
src = { IconClose } />
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
</TouchableOpacity>
</BottomSheet>
);
};

View File

@ -2,11 +2,11 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { View } from 'react-native';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
import { showContextMenuReject } from '../../actions.native';
import { MediaState } from '../../constants';
import ParticipantItem from './ParticipantItem';
@ -23,31 +23,23 @@ type Props = {
export const LobbyParticipantItem = ({ participant: p }: Props) => {
const dispatch = useDispatch();
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
const { t } = useTranslation();
return (
<ParticipantItem
audioMuteState = { MediaState.Muted }
name = { p.name }
onPress = { openContextMenuReject }
participant = { p }
videoMuteState = { MediaState.ForceMuted }>
<View style = { styles.lobbyParticipantItem }>
<Button
children = { t('lobby.admit') }
contentStyle = { styles.participantActionsButtonContent }
labelStyle = { styles.participantActionsButtonText }
mode = 'contained'
onPress = { admit }
style = { styles.participantActionsButtonAdmit } />
<Button
children = { t('lobby.reject') }
contentStyle = { styles.participantActionsButtonContent }
labelStyle = { styles.participantActionsButtonText }
mode = 'contained'
onPress = { reject }
style = { styles.participantActionsButtonReject } />
</View>
<Button
children = { t('lobby.admit') }
contentStyle = { styles.participantActionsButtonContent }
labelStyle = { styles.participantActionsButtonText }
mode = 'contained'
onPress = { admit }
style = { styles.participantActionsButtonAdmit } />
</ParticipantItem>
);
};

View File

@ -19,18 +19,11 @@ export const LobbyParticipantList = () => {
{t('participantsPane.headings.lobby',
{ count: participants.length })}
</Text>
<View style = { styles.lobbyListActions }>
<Button
labelStyle = { styles.allParticipantActionsButton }
mode = 'text'>
{t('lobby.admitAll')}
</Button>
<Button
labelStyle = { styles.allParticipantActionsButton }
mode = 'text'>
{t('lobby.rejectAll')}
</Button>
</View>
<Button
labelStyle = { styles.allParticipantActionsButton }
mode = 'text'>
{t('lobby.admitAll')}
</Button>
</View>
{ participants.map(p => (
<LobbyParticipantItem

View File

@ -3,7 +3,7 @@
import React from 'react';
import type { Node } from 'react';
import { useTranslation } from 'react-i18next';
import { View } from 'react-native';
import { TouchableOpacity, View } from 'react-native';
import { Text } from 'react-native-paper';
import { useSelector } from 'react-redux';
@ -40,6 +40,11 @@ type Props = {
*/
onLeave?: Function,
/**
* Callback to be invoked on pressing the participant item.
*/
onPress?: Function,
/**
* Participant reference
*/
@ -60,19 +65,24 @@ function ParticipantItem({
audioMuteState = MediaState.None,
children,
name,
onPress,
participant: p,
videoMuteState = MediaState.None
}: Props) {
const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
const { t } = useTranslation();
return (
<View style = { styles.participantContainer } >
<Avatar
className = 'participant-avatar'
participantId = { p.id }
size = { 32 } />
<View style = { styles.participantContent }>
<TouchableOpacity
/* eslint-disable-next-line react/jsx-no-bind */
onPress = { onPress }
style = { styles.participantContent }>
<Avatar
className = 'participant-avatar'
participantId = { p.id }
size = { 32 } />
<View style = { styles.participantNameContainer }>
<Text style = { styles.participantName }>
{ displayName }
@ -84,7 +94,7 @@ function ParticipantItem({
<View style = { styles.participantStateVideo }>{VideoStateIcons[videoMuteState]}</View>
<View style = { styles.participantStateAudio }>{AudioStateIcons[audioMuteState]}</View>
</View>
</View>
</TouchableOpacity>
{ children }
</View>
);

View File

@ -14,6 +14,7 @@ import MuteEveryoneDialog
import { LobbyParticipantList } from './LobbyParticipantList';
import { MeetingParticipantList } from './MeetingParticipantList';
import { ContextMenuMore } from './ContextMenuMore';
import styles from './styles';
/**
@ -23,6 +24,7 @@ import styles from './styles';
*/
export function ParticipantsPane() {
const dispatch = useDispatch();
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)));
const closePane = useCallback(
() => dispatch(hideDialog()),
[ dispatch ]);
@ -68,6 +70,7 @@ export function ParticipantsPane() {
src = { IconHorizontalPoints } />)
}
mode = 'contained'
onPress = { openMoreMenu }
style = { styles.moreButton } />
</View>
</JitsiModal>

View File

@ -93,7 +93,7 @@ export const participants = [
isJigasi: undefined,
loadableAvatarUrl: undefined,
local: true,
name: 'Carlin',
name: 'Carlin Teodor',
pinned: false,
presence: undefined,
raisedHand: true,

View File

@ -69,22 +69,28 @@ const buttonContent = {
justifyContent: 'center'
};
/**
* The style of the context menu pane items.
*/
const contextMenuItem = {
flexDirection: 'row',
paddingBottom: 16,
paddingTop: 16
};
/**
* The styles of the native components of the feature {@code participants}.
*/
export default {
lobbyParticipantItem: {
flexDirection: 'row',
position: 'absolute',
right: 0,
zIndex: 1
},
participantActionsButtonAdmit: {
backgroundColor: BaseTheme.palette.action01,
borderRadius: BaseTheme.shape.borderRadius,
height: BaseTheme.spacing[5]
flexDirection: 'row',
height: BaseTheme.spacing[5],
position: 'absolute',
right: 0,
zIndex: 1
},
participantActionsButtonReject: {
@ -105,10 +111,10 @@ export default {
},
allParticipantActionsButton: {
...BaseTheme.typography.labelRegular,
color: BaseTheme.palette.action01,
...BaseTheme.typography.heading6,
color: BaseTheme.palette.link01,
marginRight: 'auto',
textTransform: 'capitalize'
},
participantContainer: {
@ -144,7 +150,9 @@ export default {
},
isLocal: {
color: BaseTheme.palette.text01
alignSelf: 'center',
color: BaseTheme.palette.text01,
marginLeft: 4
},
participantsPane: {
@ -154,7 +162,8 @@ export default {
participantStatesContainer: {
display: 'flex',
flexDirection: 'row',
marginLeft: BaseTheme.spacing[3]
marginLeft: 'auto',
width: 72
},
participantStateAudio: {
@ -191,6 +200,7 @@ export default {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
overflow: 'hidden',
position: 'relative',
width: '100%'
@ -204,10 +214,6 @@ export default {
...participantListDescription
},
lobbyListActions: {
flexDirection: 'row'
},
header: {
alignItems: 'center',
backgroundColor: BaseTheme.palette.ui01,
@ -243,10 +249,6 @@ export default {
left: BaseTheme.spacing[2]
},
moreButton: {
...smallButton
},
inviteButton: {
backgroundColor: BaseTheme.palette.action01,
marginTop: BaseTheme.spacing[2]
@ -257,11 +259,20 @@ export default {
textTransform: 'capitalize'
},
moreButton: {
...smallButton
},
moreIcon: {
...buttonContent,
left: BaseTheme.spacing[2]
},
contextMenuMore: {
backgroundColor: BaseTheme.palette.action02,
borderRadius: BaseTheme.shape.borderRadius
},
muteAllButton: {
...button,
left: BaseTheme.spacing[10] + BaseTheme.spacing[2]
@ -274,5 +285,40 @@ export default {
muteAllLabel: {
color: BaseTheme.palette.text01,
textTransform: 'capitalize'
},
contextMenuItemMuteVideo: {
...contextMenuItem
},
contextMenuItemDontAllowUnmute: {
...contextMenuItem
},
contextMenuItemDetails: {
...contextMenuItem,
borderBottomColor: BaseTheme.palette.section01,
borderBottomWidth: 1
},
contextMenuItemReject: {
...contextMenuItem
},
contextMenuItemText: {
...BaseTheme.typography.bodyShortRegularLarge,
alignSelf: 'center',
color: BaseTheme.palette.text01,
flexDirection: 'row',
marginLeft: 8
},
contextMenuItemParticipantName: {
...BaseTheme.typography.bodyShortRegularLarge,
color: BaseTheme.palette.text01
},
contextMenuIcon: {
color: BaseTheme.palette.actionDanger
}
};