feat(native-participants-pane) context menu for more btn and reject lobby participant
This commit is contained in:
parent
cd05c34d19
commit
ba64d3e0c8
|
@ -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() }
|
||||
|
|
|
@ -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 });
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -93,7 +93,7 @@ export const participants = [
|
|||
isJigasi: undefined,
|
||||
loadableAvatarUrl: undefined,
|
||||
local: true,
|
||||
name: 'Carlin',
|
||||
name: 'Carlin Teodor',
|
||||
pinned: false,
|
||||
presence: undefined,
|
||||
raisedHand: true,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue