fix(rn,ParticipantPane) optimize
Use a FlatList to avoid loading all participants at once.
This commit is contained in:
parent
221cf67d0a
commit
dfd53f4041
|
@ -29,28 +29,28 @@ export function showContextMenuReject(participant: Object) {
|
||||||
* @param {string} participantID - The selected meeting participant id.
|
* @param {string} participantID - The selected meeting participant id.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function showConnectionStatus(participantID: String) {
|
export function showConnectionStatus(participantID: string) {
|
||||||
return openDialog(ConnectionStatusComponent, { participantID });
|
return openDialog(ConnectionStatusComponent, { participantID });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the context menu for the selected meeting participant.
|
* 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}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function showContextMenuDetails(participant: Object) {
|
export function showContextMenuDetails(participantId: string) {
|
||||||
return openDialog(RemoteVideoMenu, { participant });
|
return openDialog(RemoteVideoMenu, { participantId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the shared video menu.
|
* Displays the shared video menu.
|
||||||
*
|
*
|
||||||
* @param {Object} participant - The selected meeting participant.
|
* @param {string} participantId - The ID of the selected meeting participant.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function showSharedVideoMenu(participant: Object) {
|
export function showSharedVideoMenu(participantId: string) {
|
||||||
return openDialog(SharedVideoMenu, { participant });
|
return openDialog(SharedVideoMenu, { participantId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { Button, withTheme } from 'react-native-paper';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ const LobbyParticipantList = ({ theme }: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
|
<ScrollView>
|
||||||
{
|
{
|
||||||
participants.map(p => (
|
participants.map(p => (
|
||||||
<LobbyParticipantItem
|
<LobbyParticipantItem
|
||||||
|
@ -61,6 +62,7 @@ const LobbyParticipantList = ({ theme }: Props) => {
|
||||||
participant = { p } />)
|
participant = { p } />)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { translate } from '../../../base/i18n';
|
import { translate } from '../../../base/i18n';
|
||||||
import {
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
getParticipantByIdOrUndefined,
|
getParticipantByIdOrUndefined,
|
||||||
getParticipantDisplayName
|
getParticipantDisplayName
|
||||||
} from '../../../base/participants';
|
} from '../../../base/participants';
|
||||||
|
@ -12,6 +13,7 @@ import {
|
||||||
isParticipantAudioMuted,
|
isParticipantAudioMuted,
|
||||||
isParticipantVideoMuted
|
isParticipantVideoMuted
|
||||||
} from '../../../base/tracks';
|
} from '../../../base/tracks';
|
||||||
|
import { showConnectionStatus, showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
|
||||||
import { MEDIA_STATE } from '../../constants';
|
import { MEDIA_STATE } from '../../constants';
|
||||||
import type { MediaState } from '../../constants';
|
import type { MediaState } from '../../constants';
|
||||||
import { getParticipantAudioMediaState } from '../../functions';
|
import { getParticipantAudioMediaState } from '../../functions';
|
||||||
|
@ -31,6 +33,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_displayName: string,
|
_displayName: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the participant is fake.
|
||||||
|
*/
|
||||||
|
_isFakeParticipant: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the participant is video muted.
|
* True if the participant is video muted.
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +48,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_local: boolean,
|
_local: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared video local participant owner.
|
||||||
|
*/
|
||||||
|
_localVideoOwner: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The participant ID.
|
* The participant ID.
|
||||||
*/
|
*/
|
||||||
|
@ -52,9 +64,9 @@ type Props = {
|
||||||
_raisedHand: boolean,
|
_raisedHand: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to invoke when item is pressed.
|
* The redux dispatch function.
|
||||||
*/
|
*/
|
||||||
onPress: Function,
|
dispatch: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the participant.
|
* The ID of the participant.
|
||||||
|
@ -64,30 +76,75 @@ type Props = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the MeetingParticipantItem component.
|
* Implements the MeetingParticipantItem component.
|
||||||
|
*/
|
||||||
|
class MeetingParticipantItem extends PureComponent<Props> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new MeetingParticipantItem instance.
|
||||||
*
|
*
|
||||||
* @param {Props} props - The props of the component.
|
* @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}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
function MeetingParticipantItem({
|
render() {
|
||||||
|
const {
|
||||||
_audioMediaState,
|
_audioMediaState,
|
||||||
_displayName,
|
_displayName,
|
||||||
_isVideoMuted,
|
_isVideoMuted,
|
||||||
_local,
|
_local,
|
||||||
_participantID,
|
_participantID,
|
||||||
_raisedHand,
|
_raisedHand
|
||||||
onPress
|
} = this.props;
|
||||||
}: Props) {
|
|
||||||
return (
|
return (
|
||||||
<ParticipantItem
|
<ParticipantItem
|
||||||
audioMediaState = { _audioMediaState }
|
audioMediaState = { _audioMediaState }
|
||||||
displayName = { _displayName }
|
displayName = { _displayName }
|
||||||
isKnockingParticipant = { false }
|
isKnockingParticipant = { false }
|
||||||
local = { _local }
|
local = { _local }
|
||||||
onPress = { onPress }
|
onPress = { this._onPress }
|
||||||
participantID = { _participantID }
|
participantID = { _participantID }
|
||||||
raisedHand = { _raisedHand }
|
raisedHand = { _raisedHand }
|
||||||
videoMediaState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
|
videoMediaState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,19 +157,21 @@ function MeetingParticipantItem({
|
||||||
*/
|
*/
|
||||||
function mapStateToProps(state, ownProps): Object {
|
function mapStateToProps(state, ownProps): Object {
|
||||||
const { participantID } = ownProps;
|
const { participantID } = ownProps;
|
||||||
|
const { ownerId } = state['features/shared-video'];
|
||||||
|
const localParticipantId = getLocalParticipant(state).id;
|
||||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||||
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
const _isAudioMuted = isParticipantAudioMuted(participant, state);
|
||||||
const isVideoMuted = isParticipantVideoMuted(participant, state);
|
const isVideoMuted = isParticipantVideoMuted(participant, state);
|
||||||
const audioMediaState = getParticipantAudioMediaState(
|
const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
|
||||||
participant, _isAudioMuted, state
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_audioMediaState: audioMediaState,
|
_audioMediaState: audioMediaState,
|
||||||
_displayName: getParticipantDisplayName(state, participant?.id),
|
_displayName: getParticipantDisplayName(state, participant?.id),
|
||||||
_isAudioMuted,
|
_isAudioMuted,
|
||||||
|
_isFakeParticipant: Boolean(participant?.isFakeParticipant),
|
||||||
_isVideoMuted: isVideoMuted,
|
_isVideoMuted: isVideoMuted,
|
||||||
_local: Boolean(participant?.local),
|
_local: Boolean(participant?.local),
|
||||||
|
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||||
_participantID: participant?.id,
|
_participantID: participant?.id,
|
||||||
_raisedHand: Boolean(participant?.raisedHand)
|
_raisedHand: Boolean(participant?.raisedHand)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { FlatList, Text, View } from 'react-native';
|
||||||
import { Text, View } from 'react-native';
|
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
|
|
||||||
|
import { translate } from '../../../base/i18n';
|
||||||
import { Icon, IconInviteMore } from '../../../base/icons';
|
import { Icon, IconInviteMore } from '../../../base/icons';
|
||||||
import {
|
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
|
||||||
getLocalParticipant,
|
|
||||||
getParticipantCountWithFake,
|
|
||||||
getSortedParticipants
|
|
||||||
} from '../../../base/participants';
|
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { doInvitePeople } from '../../../invite/actions.native';
|
import { doInvitePeople } from '../../../invite/actions.native';
|
||||||
import {
|
|
||||||
showConnectionStatus,
|
|
||||||
showContextMenuDetails,
|
|
||||||
showSharedVideoMenu
|
|
||||||
} from '../../actions.native';
|
|
||||||
import { shouldRenderInviteButton } from '../../functions';
|
import { shouldRenderInviteButton } from '../../functions';
|
||||||
|
|
||||||
import MeetingParticipantItem from './MeetingParticipantItem';
|
import MeetingParticipantItem from './MeetingParticipantItem';
|
||||||
|
@ -28,74 +17,150 @@ import styles from './styles';
|
||||||
type Props = {
|
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<string, string>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The redux dispatch function.
|
||||||
|
*/
|
||||||
|
dispatch: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation function.
|
||||||
|
*/
|
||||||
|
t: Function
|
||||||
}
|
}
|
||||||
|
|
||||||
const MeetingParticipantList = ({ _localVideoOwner }: Props) => {
|
/**
|
||||||
const dispatch = useDispatch();
|
* The meeting participant list component.
|
||||||
const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
|
*/
|
||||||
const participantsCount = useSelector(getParticipantCountWithFake);
|
class MeetingParticipantList extends PureComponent<Props> {
|
||||||
const sortedParticipants = useSelector(getSortedParticipants);
|
|
||||||
const showInviteButton = useSelector(shouldRenderInviteButton);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-multi-comp
|
/**
|
||||||
const renderParticipant = p => {
|
* Creates new MeetingParticipantList instance.
|
||||||
if (p.isFakeParticipant) {
|
*
|
||||||
if (_localVideoOwner) {
|
* @param {Props} props - The props of the component.
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<MeetingParticipantItem
|
<Icon
|
||||||
key = { p.id }
|
size = { 20 }
|
||||||
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
|
src = { IconInviteMore } />
|
||||||
onPress = { () => dispatch(showSharedVideoMenu(p)) }
|
|
||||||
participantID = { p.id } />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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 (
|
return (
|
||||||
<MeetingParticipantItem
|
<MeetingParticipantItem
|
||||||
key = { p.id }
|
key = { item }
|
||||||
participantID = { p.id } />
|
participantID = { item } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
/**
|
||||||
<MeetingParticipantItem
|
* Implements React's {@link Component#render()}.
|
||||||
key = { p.id }
|
*
|
||||||
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
|
* @inheritdoc
|
||||||
onPress = { () => p.local
|
* @returns {ReactElement}
|
||||||
? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
|
*/
|
||||||
participantID = { p.id } />
|
render() {
|
||||||
);
|
const {
|
||||||
};
|
_localParticipantId,
|
||||||
|
_participantsCount,
|
||||||
|
_showInviteButton,
|
||||||
|
_sortedRemoteParticipants,
|
||||||
|
t
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style = { styles.meetingList }>
|
<View style = { styles.meetingList }>
|
||||||
<Text style = { styles.meetingListDescription }>
|
<Text style = { styles.meetingListDescription }>
|
||||||
{t('participantsPane.headings.participantsList',
|
{t('participantsPane.headings.participantsList',
|
||||||
{ count: participantsCount })}
|
{ count: _participantsCount })}
|
||||||
</Text>
|
</Text>
|
||||||
{
|
{
|
||||||
showInviteButton
|
_showInviteButton
|
||||||
&& <Button
|
&& <Button
|
||||||
children = { t('participantsPane.actions.invite') }
|
children = { t('participantsPane.actions.invite') }
|
||||||
/* eslint-disable-next-line react/jsx-no-bind */
|
icon = { this._renderInviteMoreIcon }
|
||||||
icon = { () =>
|
|
||||||
(<Icon
|
|
||||||
size = { 20 }
|
|
||||||
src = { IconInviteMore } />)
|
|
||||||
}
|
|
||||||
labelStyle = { styles.inviteLabel }
|
labelStyle = { styles.inviteLabel }
|
||||||
mode = 'contained'
|
mode = 'contained'
|
||||||
onPress = { onInvite }
|
onPress = { this._onInvite }
|
||||||
style = { styles.inviteButton } />
|
style = { styles.inviteButton } />
|
||||||
}
|
}
|
||||||
{ sortedParticipants.map(renderParticipant) }
|
<FlatList
|
||||||
|
bounces = { false }
|
||||||
|
data = { [ _localParticipantId, ..._sortedRemoteParticipants ] }
|
||||||
|
horizontal = { false }
|
||||||
|
keyExtractor = { this._keyExtractor }
|
||||||
|
renderItem = { this._renderParticipant }
|
||||||
|
showsHorizontalScrollIndicator = { false }
|
||||||
|
windowSize = { 2 } />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps (parts of) the redux state to the associated props for this component.
|
* Maps (parts of) the redux state to the associated props for this component.
|
||||||
|
@ -105,12 +170,16 @@ const MeetingParticipantList = ({ _localVideoOwner }: Props) => {
|
||||||
* @returns {Props}
|
* @returns {Props}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state): Object {
|
function _mapStateToProps(state): Object {
|
||||||
const { ownerId } = state['features/shared-video'];
|
const _participantsCount = getParticipantCountWithFake(state);
|
||||||
const localParticipantId = getLocalParticipant(state).id;
|
const { remoteParticipants } = state['features/filmstrip'];
|
||||||
|
const _showInviteButton = shouldRenderInviteButton(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_localVideoOwner: Boolean(ownerId === localParticipantId)
|
_participantsCount,
|
||||||
|
_showInviteButton,
|
||||||
|
_sortedRemoteParticipants: remoteParticipants,
|
||||||
|
_localParticipantId: getLocalParticipant(state)?.id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(_mapStateToProps)(MeetingParticipantList);
|
export default translate(connect(_mapStateToProps)(MeetingParticipantList));
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ScrollView, View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
@ -45,10 +45,8 @@ const ParticipantsPane = () => {
|
||||||
}}
|
}}
|
||||||
onClose = { closePane }
|
onClose = { closePane }
|
||||||
style = { styles.participantsPane }>
|
style = { styles.participantsPane }>
|
||||||
<ScrollView>
|
|
||||||
<LobbyParticipantList />
|
<LobbyParticipantList />
|
||||||
<MeetingParticipantList />
|
<MeetingParticipantList />
|
||||||
</ScrollView>
|
|
||||||
{
|
{
|
||||||
isLocalModerator
|
isLocalModerator
|
||||||
&& <View style = { styles.footer }>
|
&& <View style = { styles.footer }>
|
||||||
|
|
Loading…
Reference in New Issue