feat(participants-pane) separated participants into collapsible lists

This commit is contained in:
Calin Chitu 2022-01-12 18:29:58 +02:00 committed by Calinteodor
parent b1a2ac66b0
commit 73f3409f0d
26 changed files with 260 additions and 157 deletions

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { createBreakoutRoom } from '../../actions';
import { createBreakoutRoom } from '../../../../../breakout-rooms/actions';
import styles from './styles';

View File

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { autoAssignToBreakoutRooms } from '../../actions';
import { autoAssignToBreakoutRooms } from '../../../../../breakout-rooms/actions';
import styles from './styles';

View File

@ -6,17 +6,17 @@ import { TouchableOpacity } from 'react-native';
import { Text } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
import { hideDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../../../analytics';
import { hideDialog } from '../../../../../base/dialog/actions';
import BottomSheet from '../../../../../base/dialog/components/native/BottomSheet';
import {
Icon,
IconClose,
IconRingGroup
} from '../../../base/icons';
import { isLocalParticipantModerator } from '../../../base/participants';
import styles from '../../../participants-pane/components/native/styles';
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
} from '../../../../../base/icons';
import { isLocalParticipantModerator } from '../../../../../base/participants';
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
import styles from '../../../native/styles';
type Props = {

View File

@ -3,8 +3,8 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { isParticipantModerator } from '../../../base/participants';
import ParticipantItem from '../../../participants-pane/components/native/ParticipantItem';
import { isParticipantModerator } from '../../../../../base/participants';
import ParticipantItem from '../../../native/ParticipantItem';
type Props = {

View File

@ -1,17 +1,17 @@
// @flow
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, Text, TouchableOpacity, View } from 'react-native';
import { FlatList } from 'react-native';
import { useDispatch } from 'react-redux';
import { openDialog } from '../../../base/dialog';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import { participantMatchesSearch } from '../../../participants-pane/functions';
import { openDialog } from '../../../../../base/dialog';
import { participantMatchesSearch } from '../../../../functions';
import CollapsibleList from '../../../native/CollapsibleList';
import styles from '../../../native/styles';
import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
import styles from './styles';
type Props = {
@ -39,33 +39,23 @@ function _keyExtractor(item: Object) {
export const CollapsibleRoom = ({ room, searchString }: Props) => {
const dispatch = useDispatch();
const [ collapsed, setCollapsed ] = useState(false);
const { t } = useTranslation();
const _toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [ collapsed ]);
const _openContextMenu = useCallback(() => {
dispatch(openDialog(BreakoutRoomContextMenu, { room }));
}, [ room ]);
const roomParticipantsNr = Object.values(room.participants || {}).length;
const title
= `${room.name
|| t('breakoutRooms.mainRoom')} (${roomParticipantsNr})`;
const containerStyle
= roomParticipantsNr > 3 && styles.collapsibleRoomContainer;
return (
<View>
<TouchableOpacity
<CollapsibleList
containerStyle = { containerStyle }
onLongPress = { _openContextMenu }
onPress = { _toggleCollapsed }
style = { styles.collapsibleRoom }>
<TouchableOpacity
onPress = { _toggleCollapsed }
style = { styles.arrowIcon }>
<Icon
size = { 18 }
src = { collapsed ? IconArrowDown : IconArrowUp } />
</TouchableOpacity>
<Text style = { styles.roomName }>
{`${room.name || t('breakoutRooms.mainRoom')} (${Object.values(room.participants || {}).length})`}
</Text>
</TouchableOpacity>
{!collapsed && <FlatList
title = { title }>
<FlatList
bounces = { false }
data = { Object.values(room.participants || {}) }
horizontal = { false }
@ -73,8 +63,9 @@ export const CollapsibleRoom = ({ room, searchString }: Props) => {
// eslint-disable-next-line react/jsx-no-bind
renderItem = { ({ item: participant }) => participantMatchesSearch(participant, searchString)
&& <BreakoutRoomParticipantItem item = { participant } /> }
scrollEnabled = { true }
showsHorizontalScrollIndicator = { false }
windowSize = { 2 } />}
</View>
windowSize = { 2 } />
</CollapsibleList>
);
};

View File

@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
import { moveToRoom } from '../../actions';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../../../analytics';
import { moveToRoom } from '../../../../../breakout-rooms/actions';
import styles from './styles';

View File

@ -1,4 +1,4 @@
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import BaseTheme from '../../../../../base/ui/components/BaseTheme';
const baseButton = {
borderRadius: BaseTheme.shape.borderRadius,
@ -36,6 +36,16 @@ export default {
alignItems: 'center'
},
collapsibleList: {
alignItems: 'center',
borderRadius: BaseTheme.shape.borderRadius,
display: 'flex',
flexDirection: 'row',
height: BaseTheme.spacing[7],
marginHorizontal: BaseTheme.spacing[3],
marginTop: BaseTheme.spacing[3]
},
arrowIcon: {
backgroundColor: BaseTheme.palette.ui03,
height: BaseTheme.spacing[5],
@ -53,6 +63,13 @@ export default {
marginLeft: BaseTheme.spacing[2]
},
listTile: {
fontSize: 15,
color: BaseTheme.palette.text01,
fontWeight: 'bold',
marginLeft: BaseTheme.spacing[2]
},
transparentButton: {
...baseButton,
backgroundColor: 'transparent'

View File

@ -5,8 +5,8 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import ParticipantPaneBaseButton from '../../../participants-pane/components/web/ParticipantPaneBaseButton';
import { createBreakoutRoom } from '../../actions';
import { createBreakoutRoom } from '../../../../../breakout-rooms/actions';
import ParticipantPaneBaseButton from '../../../web/ParticipantPaneBaseButton';
const useStyles = makeStyles(() => {
return {

View File

@ -5,8 +5,8 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import ParticipantPaneBaseButton from '../../../participants-pane/components/web/ParticipantPaneBaseButton';
import { autoAssignToBreakoutRooms } from '../../actions';
import { autoAssignToBreakoutRooms } from '../../../../../breakout-rooms/actions';
import ParticipantPaneBaseButton from '../../../web/ParticipantPaneBaseButton';
const useStyles = makeStyles(theme => {
return {

View File

@ -6,11 +6,11 @@ import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { ListItem } from '../../../base/components';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import ParticipantItem from '../../../participants-pane/components/web/ParticipantItem';
import { ACTION_TRIGGER } from '../../../participants-pane/constants';
import { participantMatchesSearch } from '../../../participants-pane/functions';
import { ListItem } from '../../../../../base/components';
import { Icon, IconArrowDown, IconArrowUp } from '../../../../../base/icons';
import { ACTION_TRIGGER } from '../../../../constants';
import { participantMatchesSearch } from '../../../../functions';
import ParticipantItem from '../../../web/ParticipantItem';
type Props = {

View File

@ -5,9 +5,9 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
import { QuickActionButton } from '../../../base/components';
import { moveToRoom } from '../../actions';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../../../analytics';
import { QuickActionButton } from '../../../../../base/components';
import { moveToRoom } from '../../../../../breakout-rooms/actions';
type Props = {

View File

@ -5,9 +5,9 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
import ParticipantPaneBaseButton from '../../../participants-pane/components/web/ParticipantPaneBaseButton';
import { moveToRoom } from '../../actions';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../../../analytics';
import { moveToRoom } from '../../../../../breakout-rooms/actions';
import ParticipantPaneBaseButton from '../../../web/ParticipantPaneBaseButton';
const useStyles = makeStyles(theme => {
return {

View File

@ -4,8 +4,8 @@ import { makeStyles } from '@material-ui/styles';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { QuickActionButton } from '../../../base/components';
import { Icon, IconHorizontalPoints } from '../../../base/icons';
import { QuickActionButton } from '../../../../../base/components';
import { Icon, IconHorizontalPoints } from '../../../../../base/icons';
type Props = {

View File

@ -4,15 +4,15 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../analytics';
import { ContextMenu, ContextMenuItemGroup } from '../../../base/components';
import { createBreakoutRoomsEvent, sendAnalytics } from '../../../../../analytics';
import { ContextMenu, ContextMenuItemGroup } from '../../../../../base/components';
import {
IconClose,
IconRingGroup
} from '../../../base/icons';
import { isLocalParticipantModerator } from '../../../base/participants';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
} from '../../../../../base/icons';
import { isLocalParticipantModerator } from '../../../../../base/participants';
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
import { showOverflowDrawer } from '../../../../../toolbox/functions';
type Props = {

View File

@ -3,11 +3,11 @@
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import useContextMenu from '../../../base/components/context-menu/useContextMenu';
import { getParticipantCount, isLocalParticipantModerator } from '../../../base/participants';
import { equals } from '../../../base/redux';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { getBreakoutRooms, isInBreakoutRoom, getCurrentRoomId } from '../../functions';
import useContextMenu from '../../../../../base/components/context-menu/useContextMenu';
import { getParticipantCount, isLocalParticipantModerator } from '../../../../../base/participants';
import { equals } from '../../../../../base/redux';
import { getBreakoutRooms, isInBreakoutRoom, getCurrentRoomId } from '../../../../../breakout-rooms/functions';
import { showOverflowDrawer } from '../../../../../toolbox/functions';
import { AutoAssignButton } from './AutoAssignButton';
import { CollapsibleRoom } from './CollapsibleRoom';

View File

@ -0,0 +1,65 @@
// @flow
import React, { useCallback, useState } from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import { StyleType } from '../../../base/styles';
import styles from '../breakout-rooms/components/native/styles';
type Props = {
/**
* The children to be displayed within this list.
*/
children: React$Node,
/**
* Additional style to be appended to the CollapsibleList container.
*/
containerStyle?: StyleType,
/**
* Callback to invoke when the {@code CollapsibleList} is long pressed.
*/
onLongPress?: Function,
/**
* Collapsable list title.
*/
title: Object
}
const CollapsibleList = ({ children, containerStyle, onLongPress, title }: Props) => {
const [ collapsed, setCollapsed ] = useState(true);
const _toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [ collapsed ]);
return (
<View style = { !collapsed && containerStyle }>
<TouchableOpacity
onLongPress = { onLongPress }
onPress = { _toggleCollapsed }
style = { styles.collapsibleList }>
<TouchableOpacity
onPress = { _toggleCollapsed }
style = { styles.arrowIcon }>
<Icon
size = { 18 }
src = { collapsed ? IconArrowDown : IconArrowUp } />
</TouchableOpacity>
<Text style = { styles.listTile }>
{
title
}
</Text>
</TouchableOpacity>
{
!collapsed && children
}
</View>
);
};
export default CollapsibleList;

View File

@ -2,13 +2,14 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, View } from 'react-native';
import { ScrollView, Text, View } from 'react-native';
import { Button, withTheme } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { admitMultiple } from '../../../lobby/actions.native';
import { getKnockingParticipants, getLobbyEnabled } from '../../../lobby/functions';
import CollapsibleList from './CollapsibleList';
import { LobbyParticipantItem } from './LobbyParticipantItem';
import styles from './styles';
@ -34,10 +35,7 @@ const LobbyParticipantList = ({ theme }: Props) => {
if (!lobbyEnabled || !participants.length) {
return null;
}
return (
<View
style = { styles.lobbyListContainer } >
const title = (
<View style = { styles.lobbyListDetails } >
<Text style = { styles.lobbyListDescription }>
{t('participantsPane.headings.waitingLobby',
@ -55,6 +53,15 @@ const LobbyParticipantList = ({ theme }: Props) => {
)
}
</View>
);
const style = participants.length > 1 && styles.lobbyListContent;
return (
<CollapsibleList
title = { title }>
<ScrollView
bounces = { false }
style = { style } >
{
participants.map(p => (
<LobbyParticipantItem
@ -62,7 +69,8 @@ const LobbyParticipantList = ({ theme }: Props) => {
participant = { p } />)
)
}
</View>
</ScrollView>
</CollapsibleList>
);
};

View File

@ -1,7 +1,7 @@
// @flow
import React, { PureComponent } from 'react';
import { FlatList, Text, View } from 'react-native';
import { FlatList } from 'react-native';
import { Button, withTheme } from 'react-native-paper';
import { translate } from '../../../base/i18n';
@ -13,6 +13,7 @@ import { doInvitePeople } from '../../../invite/actions.native';
import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
import ClearableInput from './ClearableInput';
import CollapsibleList from './CollapsibleList';
import MeetingParticipantItem from './MeetingParticipantItem';
import styles from './styles';
@ -181,17 +182,19 @@ class MeetingParticipantList extends PureComponent<Props> {
_sortedRemoteParticipants,
t
} = this.props;
return (
<View
style = { styles.meetingListContainer }>
<Text style = { styles.meetingListDescription }>
{_currentRoom?.name
const title = _currentRoom?.name
// $FlowExpectedError
? `${_currentRoom.name} (${_participantsCount})`
: t('participantsPane.headings.participantsList', { count: _participantsCount })}
</Text>
: t('participantsPane.headings.participantsList',
{ count: _participantsCount });
const containerStyle
= _participantsCount > 3 && styles.meetingListContainer;
return (
<CollapsibleList
containerStyle = { containerStyle }
title = { title } >
{
_showInviteButton
&& <Button
@ -212,11 +215,10 @@ class MeetingParticipantList extends PureComponent<Props> {
horizontal = { false }
keyExtractor = { this._keyExtractor }
renderItem = { this._renderParticipant }
scrollEnabled = { false }
scrollEnabled = { true }
showsHorizontalScrollIndicator = { false }
style = { styles.meetingList }
windowSize = { 2 } />
</View>
</CollapsibleList>
);
}
}

View File

@ -2,7 +2,7 @@
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, View } from 'react-native';
import { View } from 'react-native';
import { Button } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
@ -13,15 +13,15 @@ import {
isLocalParticipantModerator
} from '../../../base/participants';
import { equals } from '../../../base/redux';
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
import MuteEveryoneDialog
from '../../../video-menu/components/native/MuteEveryoneDialog';
import {
AddBreakoutRoomButton,
AutoAssignButton,
LeaveBreakoutRoomButton
} from '../../../breakout-rooms/components/native';
import { CollapsibleRoom } from '../../../breakout-rooms/components/native/CollapsibleRoom';
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
import MuteEveryoneDialog
from '../../../video-menu/components/native/MuteEveryoneDialog';
} from '../breakout-rooms/components/native';
import { CollapsibleRoom } from '../breakout-rooms/components/native/CollapsibleRoom';
import { ContextMenuMore } from './ContextMenuMore';
import HorizontalDotsIcon from './HorizontalDotsIcon';
@ -54,32 +54,36 @@ const ParticipantsPane = () => {
.sort((p1: Object, p2: Object) => (p1?.name || '').localeCompare(p2?.name || ''));
const inBreakoutRoom = useSelector(isInBreakoutRoom);
const participantsCount = useSelector(getParticipantCount);
const autoAssign = !inBreakoutRoom && isLocalModerator
&& participantsCount > 2 && rooms.length > 1;
const addBreakoutRoom
= _isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator;
return (
<JitsiScreen
style = { styles.participantsPane }>
<ScrollView bounces = { false }>
<JitsiScreen style = { styles.participantsPaneContainer }>
<LobbyParticipantList />
<MeetingParticipantList
searchString = { searchString }
setSearchString = { setSearchString } />
{!inBreakoutRoom
&& isLocalModerator
&& participantsCount > 2
&& rooms.length > 1
&& <AutoAssignButton />}
{inBreakoutRoom && <LeaveBreakoutRoomButton />}
{_isBreakoutRoomsSupported
{
autoAssign && <AutoAssignButton />
}
{
inBreakoutRoom && <LeaveBreakoutRoomButton />
}
{
_isBreakoutRoomsSupported
&& rooms.map(room => (<CollapsibleRoom
key = { room.id }
room = { room }
searchString = { searchString } />))}
{_isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator
&& <AddBreakoutRoomButton />}
</ScrollView>
searchString = { searchString } />))
}
{
addBreakoutRoom && <AddBreakoutRoomButton />
}
{
isLocalModerator
&& <View style = { styles.footer }>
&& <View style = { styles.participantsPaneFooter }>
<Button
children = { t('participantsPane.actions.muteAll') }
labelStyle = { styles.muteAllLabel }

View File

@ -113,7 +113,9 @@ export default {
admitAllParticipantsActionButtonLabel: {
...BaseTheme.typography.heading6,
color: BaseTheme.palette.link01,
textTransform: 'capitalize'
textTransform: 'capitalize',
marginRight: BaseTheme.spacing[5],
marginTop: BaseTheme.spacing[3]
},
participantContainer: {
@ -167,12 +169,6 @@ export default {
paddingTop: BaseTheme.spacing[1]
},
participantsPane: {
backgroundColor: BaseTheme.palette.ui01,
flex: 1,
justifyContent: 'center'
},
participantStatesContainer: {
display: 'flex',
flexDirection: 'row',
@ -199,12 +195,15 @@ export default {
color: BaseTheme.palette.uiBackground
},
lobbyListContainer: {
position: 'relative'
lobbyListContent: {
height: '20%'
},
lobbyListDescription: {
...participantListDescription
fontSize: 15,
color: BaseTheme.palette.text01,
fontWeight: 'bold',
marginTop: BaseTheme.spacing[2]
},
lobbyListDetails: {
@ -213,13 +212,12 @@ export default {
flexDirection: 'row',
justifyContent: 'space-between',
overflow: 'hidden',
paddingLeft: BaseTheme.spacing[3],
position: 'relative',
width: '100%'
},
meetingListContainer: {
flex: 1
height: '60%'
},
meetingListDescription: {
@ -227,11 +225,28 @@ export default {
marginLeft: BaseTheme.spacing[3]
},
footer: {
collapsibleRoomContainer: {
height: '30%'
},
participantsPaneContainer: {
backgroundColor: BaseTheme.palette.ui01,
flex: 1,
justifyContent: 'center'
},
participantsPaneFooter: {
alignItems: 'center',
backgroundColor: BaseTheme.palette.ui01,
bottom: 0,
flexDirection: 'row',
paddingHorizontal: BaseTheme.spacing[3],
paddingVertical: BaseTheme.spacing[2]
height: BaseTheme.spacing[12],
left: 0,
right: 0,
position: 'absolute',
paddingBottom: BaseTheme.spacing[2],
paddingLeft: BaseTheme.spacing[3],
paddingRight: BaseTheme.spacing[3]
},
headerCloseIcon: {
@ -242,9 +257,9 @@ export default {
backgroundColor: BaseTheme.palette.action01,
borderRadius: BaseTheme.shape.borderRadius,
height: BaseTheme.spacing[7],
marginBottom: BaseTheme.spacing[4],
marginLeft: BaseTheme.spacing[3],
marginRight: BaseTheme.spacing[3]
marginRight: BaseTheme.spacing[3],
marginVertical: BaseTheme.spacing[3]
},
inviteLabel: {
@ -335,7 +350,8 @@ export default {
backgroundColor: BaseTheme.palette.uiBackground,
borderRadius: BaseTheme.shape.borderRadius,
marginLeft: BaseTheme.spacing[3],
marginRight: BaseTheme.spacing[3]
marginRight: BaseTheme.spacing[3],
marginBottom: BaseTheme.spacing[4]
},
clearableInputFocus: {

View File

@ -9,11 +9,11 @@ import { translate } from '../../../base/i18n';
import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
import { isLocalParticipantModerator } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AddBreakoutRoomButton } from '../../../breakout-rooms/components/web/AddBreakoutRoomButton';
import { RoomList } from '../../../breakout-rooms/components/web/RoomList';
import { MuteEveryoneDialog } from '../../../video-menu/components/';
import { close } from '../../actions';
import { classList, findAncestorByClass, getParticipantsPaneOpen } from '../../functions';
import { AddBreakoutRoomButton } from '../breakout-rooms/components/web/AddBreakoutRoomButton';
import { RoomList } from '../breakout-rooms/components/web/RoomList';
import FooterButton from './FooterButton';
import { FooterContextMenu } from './FooterContextMenu';