From dfd53f40418f2bf2c52fd63aca8153bd8133973e Mon Sep 17 00:00:00 2001 From: Hristo Terezov Date: Wed, 1 Sep 2021 18:13:16 -0500 Subject: [PATCH] fix(rn,ParticipantPane) optimize Use a FlatList to avoid loading all participants at once. --- .../participants-pane/actions.native.js | 14 +- .../components/native/LobbyParticipantList.js | 18 +- .../native/MeetingParticipantItem.js | 117 ++++++--- .../native/MeetingParticipantList.js | 223 ++++++++++++------ .../components/native/ParticipantsPane.js | 8 +- 5 files changed, 254 insertions(+), 126 deletions(-) diff --git a/react/features/participants-pane/actions.native.js b/react/features/participants-pane/actions.native.js index 3812e6951..187217d6a 100644 --- a/react/features/participants-pane/actions.native.js +++ b/react/features/participants-pane/actions.native.js @@ -29,28 +29,28 @@ export function showContextMenuReject(participant: Object) { * @param {string} participantID - The selected meeting participant id. * @returns {Function} */ -export function showConnectionStatus(participantID: String) { +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. + * @param {string} participantId - The ID of the selected meeting participant. * @returns {Function} */ -export function showContextMenuDetails(participant: Object) { - return openDialog(RemoteVideoMenu, { participant }); +export function showContextMenuDetails(participantId: string) { + return openDialog(RemoteVideoMenu, { participantId }); } /** * Displays the shared video menu. * - * @param {Object} participant - The selected meeting participant. + * @param {string} participantId - The ID of the selected meeting participant. * @returns {Function} */ -export function showSharedVideoMenu(participant: Object) { - return openDialog(SharedVideoMenu, { participant }); +export function showSharedVideoMenu(participantId: string) { + return openDialog(SharedVideoMenu, { participantId }); } /** diff --git a/react/features/participants-pane/components/native/LobbyParticipantList.js b/react/features/participants-pane/components/native/LobbyParticipantList.js index 7b9948da8..5bde465d8 100644 --- a/react/features/participants-pane/components/native/LobbyParticipantList.js +++ b/react/features/participants-pane/components/native/LobbyParticipantList.js @@ -2,7 +2,7 @@ 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'; @@ -54,13 +54,15 @@ const LobbyParticipantList = ({ theme }: Props) => { ) } - { - participants.map(p => ( - ) - ) - } + + { + participants.map(p => ( + ) + ) + } + ); }; diff --git a/react/features/participants-pane/components/native/MeetingParticipantItem.js b/react/features/participants-pane/components/native/MeetingParticipantItem.js index 86f660d0e..f90ee2f7f 100644 --- a/react/features/participants-pane/components/native/MeetingParticipantItem.js +++ b/react/features/participants-pane/components/native/MeetingParticipantItem.js @@ -1,9 +1,10 @@ // @flow -import React from 'react'; +import React, { PureComponent } from 'react'; import { translate } from '../../../base/i18n'; import { + getLocalParticipant, getParticipantByIdOrUndefined, getParticipantDisplayName } from '../../../base/participants'; @@ -12,6 +13,7 @@ import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks'; +import { showConnectionStatus, showContextMenuDetails, showSharedVideoMenu } from '../../actions.native'; import { MEDIA_STATE } from '../../constants'; import type { MediaState } from '../../constants'; import { getParticipantAudioMediaState } from '../../functions'; @@ -31,6 +33,11 @@ type Props = { */ _displayName: string, + /** + * True if the participant is fake. + */ + _isFakeParticipant: boolean, + /** * True if the participant is video muted. */ @@ -41,6 +48,11 @@ type Props = { */ _local: boolean, + /** + * Shared video local participant owner. + */ + _localVideoOwner: boolean, + /** * The participant ID. */ @@ -52,9 +64,9 @@ type Props = { _raisedHand: boolean, /** - * Callback to invoke when item is pressed. + * The redux dispatch function. */ - onPress: Function, + dispatch: Function, /** * The ID of the participant. @@ -64,30 +76,75 @@ type Props = { /** * Implements the MeetingParticipantItem component. - * - * @param {Props} props - The props of the component. - * @returns {ReactElement} */ -function MeetingParticipantItem({ - _audioMediaState, - _displayName, - _isVideoMuted, - _local, - _participantID, - _raisedHand, - onPress -}: Props) { - return ( - - ); +class MeetingParticipantItem extends PureComponent { + + /** + * Creates new MeetingParticipantItem instance. + * + * @param {Props} props - The props of the component. + */ + constructor(props: Props) { + super(props); + + this._onPress = this._onPress.bind(this); + } + + _onPress: () => void; + + /** + * Handles MeetingParticipantItem press events. + * + * @returns {void} + */ + _onPress() { + const { + _local, + _localVideoOwner, + _isFakeParticipant, + _participantID, + dispatch + } = this.props; + + if (_isFakeParticipant && _localVideoOwner) { + dispatch(showSharedVideoMenu(_participantID)); + } else if (!_isFakeParticipant) { + if (_local) { + dispatch(showConnectionStatus(_participantID)); + } else { + dispatch(showContextMenuDetails(_participantID)); + } + } // else no-op + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { + _audioMediaState, + _displayName, + _isVideoMuted, + _local, + _participantID, + _raisedHand + } = this.props; + + return ( + + ); + } } /** @@ -100,19 +157,21 @@ function MeetingParticipantItem({ */ function mapStateToProps(state, ownProps): Object { const { participantID } = ownProps; + const { ownerId } = state['features/shared-video']; + const localParticipantId = getLocalParticipant(state).id; const participant = getParticipantByIdOrUndefined(state, participantID); const _isAudioMuted = isParticipantAudioMuted(participant, state); const isVideoMuted = isParticipantVideoMuted(participant, state); - const audioMediaState = getParticipantAudioMediaState( - participant, _isAudioMuted, state - ); + const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state); return { _audioMediaState: audioMediaState, _displayName: getParticipantDisplayName(state, participant?.id), _isAudioMuted, + _isFakeParticipant: Boolean(participant?.isFakeParticipant), _isVideoMuted: isVideoMuted, _local: Boolean(participant?.local), + _localVideoOwner: Boolean(ownerId === localParticipantId), _participantID: participant?.id, _raisedHand: Boolean(participant?.raisedHand) }; diff --git a/react/features/participants-pane/components/native/MeetingParticipantList.js b/react/features/participants-pane/components/native/MeetingParticipantList.js index 78cace350..80dea4b75 100644 --- a/react/features/participants-pane/components/native/MeetingParticipantList.js +++ b/react/features/participants-pane/components/native/MeetingParticipantList.js @@ -1,25 +1,14 @@ // @flow -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Text, View } from 'react-native'; +import React, { PureComponent } from 'react'; +import { FlatList, Text, View } from 'react-native'; import { Button } from 'react-native-paper'; -import { useDispatch, useSelector } from 'react-redux'; - +import { translate } from '../../../base/i18n'; import { Icon, IconInviteMore } from '../../../base/icons'; -import { - getLocalParticipant, - getParticipantCountWithFake, - getSortedParticipants -} from '../../../base/participants'; +import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants'; import { connect } from '../../../base/redux'; import { doInvitePeople } from '../../../invite/actions.native'; -import { - showConnectionStatus, - showContextMenuDetails, - showSharedVideoMenu -} from '../../actions.native'; import { shouldRenderInviteButton } from '../../functions'; import MeetingParticipantItem from './MeetingParticipantItem'; @@ -28,74 +17,150 @@ import styles from './styles'; type Props = { /** - * Shared video local participant owner. + * The ID of the local participant. */ - _localVideoOwner: boolean + _localParticipantId: string, + + /** + * The number of participants in the conference. + */ + _participantsCount: number, + + /** + * Whether or not to show the invite button. + */ + _showInviteButton: boolean, + + /** + * The remote participants. + */ + _sortedRemoteParticipants: Map, + + /** + * The redux dispatch function. + */ + dispatch: Function, + + /** + * Translation function. + */ + t: Function } -const MeetingParticipantList = ({ _localVideoOwner }: Props) => { - const dispatch = useDispatch(); - const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]); - const participantsCount = useSelector(getParticipantCountWithFake); - const sortedParticipants = useSelector(getSortedParticipants); - const showInviteButton = useSelector(shouldRenderInviteButton); - const { t } = useTranslation(); +/** + * The meeting participant list component. + */ +class MeetingParticipantList extends PureComponent { - // eslint-disable-next-line react/no-multi-comp - const renderParticipant = p => { - if (p.isFakeParticipant) { - if (_localVideoOwner) { - return ( - dispatch(showSharedVideoMenu(p)) } - participantID = { p.id } /> - ); - } + /** + * Creates new MeetingParticipantList instance. + * + * @param {Props} props - The props of the component. + */ + constructor(props: Props) { + super(props); - return ( - - ); - } + this._keyExtractor = this._keyExtractor.bind(this); + this._onInvite = this._onInvite.bind(this); + this._renderParticipant = this._renderParticipant.bind(this); + } + _keyExtractor: Function; + + /** + * Returns a key for a passed item of the list. + * + * @param {string} item - The user ID. + * @returns {string} - The user ID. + */ + _keyExtractor(item) { + return item; + } + + _onInvite: () => void; + + /** + * Handles ivite button presses. + * + * @returns {void} + */ + _onInvite() { + this.props.dispatch(doInvitePeople()); + } + + /** + * Renders the "invite more" icon. + * + * @returns {ReactElement} + */ + _renderInviteMoreIcon() { + return ( + + ); + } + + _renderParticipant: Object => Object; + + /** + * Renders a participant. + * + * @param {Object} flatListItem - Information about the item to be rendered. + * @param {string} flatListItem.item - The ID of the participant. + * @returns {ReactElement} + */ + _renderParticipant({ item/* , index, separators */ }) { return ( p.local - ? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) } - participantID = { p.id } /> + key = { item } + participantID = { item } /> ); - }; + } - return ( - - - {t('participantsPane.headings.participantsList', - { count: participantsCount })} - - { - showInviteButton - &&