diff --git a/react/features/base/dialog/components/native/BottomSheet.js b/react/features/base/dialog/components/native/BottomSheet.js index 546558ff0..1e5a03c12 100644 --- a/react/features/base/dialog/components/native/BottomSheet.js +++ b/react/features/base/dialog/components/native/BottomSheet.js @@ -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 { 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 { * @returns {ReactElement} */ render() { - const { _styles, renderHeader, renderFooter, _height } = this.props; + const { + _height, + _styles, + addScrollViewPadding, + renderHeader, + renderFooter, + showSlidingView, + style + } = this.props; return ( { accessibilityViewIsModal = { true } onHide = { this.props.onCancel } position = 'bottom' - show = { true }> + show = { showSlidingView }> @@ -110,6 +143,7 @@ class BottomSheet extends PureComponent { style = { [ styles.sheetItemContainer, _styles.sheet, + style, { maxHeight: _height - 100 } @@ -118,7 +152,7 @@ class BottomSheet extends PureComponent { + style = { addScrollViewPadding && styles.scrollView } > { this.props.children } { renderFooter && renderFooter() } diff --git a/react/features/participants-pane/actions.native.js b/react/features/participants-pane/actions.native.js new file mode 100644 index 000000000..43f276011 --- /dev/null +++ b/react/features/participants-pane/actions.native.js @@ -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 }); +} diff --git a/react/features/participants-pane/components/native/ContextMenuMore.js b/react/features/participants-pane/components/native/ContextMenuMore.js new file mode 100644 index 000000000..734446649 --- /dev/null +++ b/react/features/participants-pane/components/native/ContextMenuMore.js @@ -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, + + /** + * 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 ( + + + + {t('participantsPane.actions.stopEveryonesVideo')} + + + + {t('participantsPane.actions.dontAllowUnmute')} + + + ); +}; diff --git a/react/features/participants-pane/components/native/ContextMenuReject.js b/react/features/participants-pane/components/native/ContextMenuReject.js new file mode 100644 index 000000000..3429b3cda --- /dev/null +++ b/react/features/participants-pane/components/native/ContextMenuReject.js @@ -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 ( + + + + + + { displayName } + + + + + + { t('lobby.reject') } + + + ); +}; diff --git a/react/features/participants-pane/components/native/LobbyParticipantItem.js b/react/features/participants-pane/components/native/LobbyParticipantItem.js index b6382bc0a..b91338e09 100644 --- a/react/features/participants-pane/components/native/LobbyParticipantItem.js +++ b/react/features/participants-pane/components/native/LobbyParticipantItem.js @@ -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 ( - - - - + { participants.map(p => ( - - + + { displayName } @@ -84,7 +94,7 @@ function ParticipantItem({ {VideoStateIcons[videoMuteState]} {AudioStateIcons[audioMuteState]} - + { children } ); diff --git a/react/features/participants-pane/components/native/ParticipantsPane.js b/react/features/participants-pane/components/native/ParticipantsPane.js index c1a09aff8..73a0122dc 100644 --- a/react/features/participants-pane/components/native/ParticipantsPane.js +++ b/react/features/participants-pane/components/native/ParticipantsPane.js @@ -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 } /> diff --git a/react/features/participants-pane/components/native/participants.js b/react/features/participants-pane/components/native/participants.js index 2f06a84fc..388f69bd3 100644 --- a/react/features/participants-pane/components/native/participants.js +++ b/react/features/participants-pane/components/native/participants.js @@ -93,7 +93,7 @@ export const participants = [ isJigasi: undefined, loadableAvatarUrl: undefined, local: true, - name: 'Carlin', + name: 'Carlin Teodor', pinned: false, presence: undefined, raisedHand: true, diff --git a/react/features/participants-pane/components/native/styles.js b/react/features/participants-pane/components/native/styles.js index 6790c7753..8f2518ace 100644 --- a/react/features/participants-pane/components/native/styles.js +++ b/react/features/participants-pane/components/native/styles.js @@ -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 } };