feat(RN-filmtrip) stop reordering small meetings

This commit is contained in:
Hristo Terezov 2022-05-06 05:18:57 -05:00 committed by GitHub
parent 61abf0d882
commit adef5095da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 345 additions and 154 deletions

View File

@ -1,6 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import SplashScreen from 'react-native-splash-screen'; import SplashScreen from 'react-native-splash-screen';
import { DialogContainer } from '../../base/dialog'; import { DialogContainer } from '../../base/dialog';
@ -8,7 +9,7 @@ import { updateFlags } from '../../base/flags/actions';
import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED } from '../../base/flags/constants'; import { CALL_INTEGRATION_ENABLED, SERVER_URL_CHANGE_ENABLED } from '../../base/flags/constants';
import { getFeatureFlag } from '../../base/flags/functions'; import { getFeatureFlag } from '../../base/flags/functions';
import { Platform } from '../../base/react'; import { Platform } from '../../base/react';
import { DimensionsDetector, clientResized } from '../../base/responsive-ui'; import { DimensionsDetector, clientResized, setSafeAreaInsets } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings'; import { updateSettings } from '../../base/settings';
import { _getRouteToRender } from '../getRouteToRender.native'; import { _getRouteToRender } from '../getRouteToRender.native';
import logger from '../logger'; import logger from '../logger';
@ -76,6 +77,7 @@ export class App extends AbstractApp {
// Bind event handler so it is only bound once per instance. // Bind event handler so it is only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this); this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
this._onSafeAreaInsetsChanged = this._onSafeAreaInsetsChanged.bind(this);
} }
/** /**
@ -138,10 +140,13 @@ export class App extends AbstractApp {
*/ */
_createMainElement(component, props) { _createMainElement(component, props) {
return ( return (
<DimensionsDetector <SafeAreaProvider>
onDimensionsChanged = { this._onDimensionsChanged }> <DimensionsDetector
{ super._createMainElement(component, props) } onDimensionsChanged = { this._onDimensionsChanged }
</DimensionsDetector> onSafeAreaInsetsChanged = { this._onSafeAreaInsetsChanged }>
{ super._createMainElement(component, props) }
</DimensionsDetector>
</SafeAreaProvider>
); );
} }
@ -198,6 +203,23 @@ export class App extends AbstractApp {
dispatch(clientResized(width, height)); dispatch(clientResized(width, height));
} }
/**
* Updates the safe are insets values.
*
* @param {Object} insets - The insets.
* @param {number} insets.top - The top inset.
* @param {number} insets.right - The right inset.
* @param {number} insets.bottom - The bottom inset.
* @param {number} insets.left - The left inset.
* @private
* @returns {void}
*/
_onSafeAreaInsetsChanged(insets) {
const { dispatch } = this.state.store;
dispatch(setSafeAreaInsets(insets));
}
/** /**
* Renders the platform specific dialog container. * Renders the platform specific dialog container.
* *

View File

@ -7,6 +7,16 @@
*/ */
export const CLIENT_RESIZED = 'CLIENT_RESIZED'; export const CLIENT_RESIZED = 'CLIENT_RESIZED';
/**
* The type of (redux) action which indicates that the insets from the SafeAreaProvider have changed.
*
* {
* type: SAFE_AREA_INSETS_CHANGED,
* insets: Object
* }
*/
export const SAFE_AREA_INSETS_CHANGED = 'SAFE_AREA_INSETS_CHANGED';
/** /**
* The type of (redux) action which sets the aspect ratio of the app's user * The type of (redux) action which sets the aspect ratio of the app's user
* interface. * interface.

View File

@ -7,7 +7,13 @@ import { CHAT_SIZE } from '../../chat/constants';
import { getParticipantsPaneOpen } from '../../participants-pane/functions'; import { getParticipantsPaneOpen } from '../../participants-pane/functions';
import theme from '../components/themes/participantsPaneTheme.json'; import theme from '../components/themes/participantsPaneTheme.json';
import { CLIENT_RESIZED, SET_ASPECT_RATIO, SET_CONTEXT_MENU_OPEN, SET_REDUCED_UI } from './actionTypes'; import {
CLIENT_RESIZED,
SAFE_AREA_INSETS_CHANGED,
SET_ASPECT_RATIO,
SET_CONTEXT_MENU_OPEN,
SET_REDUCED_UI
} from './actionTypes';
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants'; import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
/** /**
@ -123,3 +129,19 @@ export function setParticipantContextMenuOpen(isOpen: boolean) {
isOpen isOpen
}; };
} }
/**
* Sets the insets from the SafeAreaProvider.
*
* @param {Object} insets - The new insets to be set.
* @returns {{
* type: SAFE_AREA_INSETS_CHANGED,
* insets: Object
* }}
*/
export function setSafeAreaInsets(insets) {
return {
type: SAFE_AREA_INSETS_CHANGED,
insets
};
}

View File

@ -1,8 +1,8 @@
// @flow // @flow
import React, { PureComponent } from 'react'; import React, { useCallback, useEffect } from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type Props = { type Props = {
@ -11,6 +11,11 @@ type Props = {
*/ */
onDimensionsChanged: Function, onDimensionsChanged: Function,
/**
* The safe are insets handler.
*/
onSafeAreaInsetsChanged: Function,
/** /**
* Any nested components. * Any nested components.
*/ */
@ -20,21 +25,23 @@ type Props = {
/** /**
* A {@link View} which captures the 'onLayout' event and calls a prop with the * A {@link View} which captures the 'onLayout' event and calls a prop with the
* component size. * component size.
*
* @param {Props} props - The read-only properties with which the new
* instance is to be initialized.
* @returns {Component} - Renders the root view and it's children.
*/ */
export default class DimensionsDetector extends PureComponent<Props> { export default function DimensionsDetector(props: Props) {
/** const { top = 0, right = 0, bottom = 0, left = 0 } = useSafeAreaInsets();
* Initializes a new DimensionsDetector instance. const { children, onDimensionsChanged, onSafeAreaInsetsChanged } = props;
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Object) {
super(props);
this._onLayout = this._onLayout.bind(this); useEffect(() => {
} onSafeAreaInsetsChanged && onSafeAreaInsetsChanged({
top,
_onLayout: (Object) => void; right,
bottom,
left
});
}, [ onSafeAreaInsetsChanged, top, right, bottom, left ]);
/** /**
* Handles the "on layout" View's event and calls the onDimensionsChanged * Handles the "on layout" View's event and calls the onDimensionsChanged
@ -45,24 +52,15 @@ export default class DimensionsDetector extends PureComponent<Props> {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_onLayout({ nativeEvent: { layout: { height, width } } }) { const onLayout = useCallback(({ nativeEvent: { layout: { height, width } } }) => {
const { onDimensionsChanged } = this.props;
onDimensionsChanged && onDimensionsChanged(width, height); onDimensionsChanged && onDimensionsChanged(width, height);
} }, [ onDimensionsChanged ]);
/** return (
* Renders the root view and it's children. <View
* onLayout = { onLayout }
* @returns {Component} style = { StyleSheet.absoluteFillObject } >
*/ { children }
render() { </View>
return ( );
<View
onLayout = { this._onLayout }
style = { StyleSheet.absoluteFillObject } >
{ this.props.children }
</View>
);
}
} }

View File

@ -2,7 +2,13 @@
import { ReducerRegistry, set } from '../redux'; import { ReducerRegistry, set } from '../redux';
import { CLIENT_RESIZED, SET_ASPECT_RATIO, SET_CONTEXT_MENU_OPEN, SET_REDUCED_UI } from './actionTypes'; import {
CLIENT_RESIZED,
SAFE_AREA_INSETS_CHANGED,
SET_ASPECT_RATIO,
SET_CONTEXT_MENU_OPEN,
SET_REDUCED_UI
} from './actionTypes';
import { ASPECT_RATIO_NARROW } from './constants'; import { ASPECT_RATIO_NARROW } from './constants';
const { const {
@ -30,6 +36,13 @@ ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE,
clientHeight: action.clientHeight clientHeight: action.clientHeight
}; };
} }
case SAFE_AREA_INSETS_CHANGED:
return {
...state,
safeAreaInsets: action.insets
};
case SET_ASPECT_RATIO: case SET_ASPECT_RATIO:
return set(state, 'aspectRatio', action.aspectRatio); return set(state, 'aspectRatio', action.aspectRatio);

View File

@ -1,10 +1,12 @@
// @flow // @flow
import { getParticipantCountWithFake } from '../base/participants'; import conferenceStyles from '../conference/components/native/styles';
import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes'; import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes';
import { styles } from './components';
import { SQUARE_TILE_ASPECT_RATIO, TILE_MARGIN } from './constants'; import { SQUARE_TILE_ASPECT_RATIO, TILE_MARGIN } from './constants';
import { getColumnCount } from './functions.native'; import { getColumnCount } from './functions';
import { getTileViewParticipantCount } from './functions.native';
export * from './actions.any'; export * from './actions.any';
@ -18,16 +20,19 @@ export * from './actions.any';
export function setTileViewDimensions() { export function setTileViewDimensions() {
return (dispatch: Function, getState: Function) => { return (dispatch: Function, getState: Function) => {
const state = getState(); const state = getState();
const participantCount = getParticipantCountWithFake(state); const participantCount = getTileViewParticipantCount(state);
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui']; const { clientHeight: height, clientWidth: width, safeAreaInsets = {} } = state['features/base/responsive-ui'];
const { left = 0, right = 0, top = 0, bottom = 0 } = safeAreaInsets;
const columns = getColumnCount(state); const columns = getColumnCount(state);
const heightToUse = height - (TILE_MARGIN * 2); const rows = Math.ceil(participantCount / columns);
const widthToUse = width - (TILE_MARGIN * 2); const conferenceBorder = conferenceStyles.conference.borderWidth || 0;
const heightToUse = height - top - (2 * conferenceBorder);
const widthToUse = width - (TILE_MARGIN * 2) - left - right - (2 * conferenceBorder);
let tileWidth; let tileWidth;
// If there is going to be at least two rows, ensure that at least two // If there is going to be at least two rows, ensure that at least two
// rows display fully on screen. // rows display fully on screen.
if (participantCount / columns > 1) { if (rows / columns > 1) {
tileWidth = Math.min(widthToUse / columns, heightToUse / 2); tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
} else { } else {
tileWidth = Math.min(widthToUse / columns, heightToUse); tileWidth = Math.min(widthToUse / columns, heightToUse);
@ -37,6 +42,9 @@ export function setTileViewDimensions() {
tileWidth = Math.floor(tileWidth); tileWidth = Math.floor(tileWidth);
// Adding safeAreaInsets.bottom to the total height of all thumbnails because we add it as a padding to the
// thumbnails container.
const hasScroll = heightToUse < ((tileHeight + (2 * styles.thumbnail.margin)) * rows) + bottom;
dispatch({ dispatch({
type: SET_TILE_VIEW_DIMENSIONS, type: SET_TILE_VIEW_DIMENSIONS,
@ -45,7 +53,8 @@ export function setTileViewDimensions() {
thumbnailSize: { thumbnailSize: {
height: tileHeight, height: tileHeight,
width: tileWidth width: tileWidth
} },
hasScroll
} }
}); });
}; };

View File

@ -2,7 +2,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView, withSafeAreaInsets } from 'react-native-safe-area-context';
import { getLocalParticipant } from '../../../base/participants'; import { getLocalParticipant } from '../../../base/participants';
import { Platform } from '../../../base/react'; import { Platform } from '../../../base/react';
@ -11,7 +11,12 @@ import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { shouldHideSelfView } from '../../../base/settings/functions.any'; import { shouldHideSelfView } from '../../../base/settings/functions.any';
import { isToolboxVisible } from '../../../toolbox/functions'; import { isToolboxVisible } from '../../../toolbox/functions';
import { setVisibleRemoteParticipants } from '../../actions'; import { setVisibleRemoteParticipants } from '../../actions';
import { isFilmstripVisible, shouldRemoteVideosBeVisible } from '../../functions'; import {
getFilmstripDimensions,
isFilmstripVisible,
shouldDisplayLocalThumbnailSeparately,
shouldRemoteVideosBeVisible
} from '../../functions';
import LocalThumbnail from './LocalThumbnail'; import LocalThumbnail from './LocalThumbnail';
import Thumbnail from './Thumbnail'; import Thumbnail from './Thumbnail';
@ -60,6 +65,11 @@ type Props = {
* Invoked to trigger state changes in Redux. * Invoked to trigger state changes in Redux.
*/ */
dispatch: Function, dispatch: Function,
/**
* Object containing the safe area insets.
*/
insets: Object,
}; };
/** /**
@ -105,7 +115,7 @@ class Filmstrip extends PureComponent<Props> {
// indicators such as moderator, audio and video muted, etc. For now we // indicators such as moderator, audio and video muted, etc. For now we
// do not have much of a choice but to continue rendering LocalThumbnail // do not have much of a choice but to continue rendering LocalThumbnail
// as any other remote Thumbnail on Android. // as any other remote Thumbnail on Android.
this._separateLocalThumbnail = Platform.OS !== 'android'; this._separateLocalThumbnail = shouldDisplayLocalThumbnailSeparately();
this._viewabilityConfig = { this._viewabilityConfig = {
itemVisiblePercentThreshold: 30, itemVisiblePercentThreshold: 30,
@ -136,20 +146,23 @@ class Filmstrip extends PureComponent<Props> {
* @returns {Object} - The width and the height. * @returns {Object} - The width and the height.
*/ */
_getDimensions() { _getDimensions() {
const { _aspectRatio, _clientWidth, _clientHeight } = this.props; const {
const { height, width, margin } = styles.thumbnail; _aspectRatio,
_clientWidth,
_clientHeight,
_disableSelfView,
_localParticipantId,
insets
} = this.props;
const localParticipantVisible = Boolean(_localParticipantId) && !_disableSelfView;
if (_aspectRatio === ASPECT_RATIO_NARROW) { return getFilmstripDimensions({
return { aspectRatio: _aspectRatio,
height, clientHeight: _clientHeight,
width: this._separateLocalThumbnail ? _clientWidth - width - (margin * 2) : _clientWidth clientWidth: _clientWidth,
}; insets,
} localParticipantVisible
});
return {
height: this._separateLocalThumbnail ? _clientHeight - height - (margin * 2) : _clientHeight,
width
};
} }
_getItemLayout: (?Array<string>, number) => {length: number, offset: number, index: number}; _getItemLayout: (?Array<string>, number) => {length: number, offset: number, index: number};
@ -312,7 +325,7 @@ function _mapStateToProps(state) {
const responsiveUI = state['features/base/responsive-ui']; const responsiveUI = state['features/base/responsive-ui'];
return { return {
_aspectRatio: state['features/base/responsive-ui'].aspectRatio, _aspectRatio: responsiveUI.aspectRatio,
_clientHeight: responsiveUI.clientHeight, _clientHeight: responsiveUI.clientHeight,
_clientWidth: responsiveUI.clientWidth, _clientWidth: responsiveUI.clientWidth,
_disableSelfView: disableSelfView, _disableSelfView: disableSelfView,
@ -323,4 +336,4 @@ function _mapStateToProps(state) {
}; };
} }
export default connect(_mapStateToProps)(Filmstrip); export default withSafeAreaInsets(connect(_mapStateToProps)(Filmstrip));

View File

@ -218,7 +218,7 @@ export const SHOW_TOOLBAR_CONTEXT_MENU_AFTER = 600;
/** /**
* The margin for each side of the tile view. Taken away from the available * The margin for each side of the tile view. Taken away from the available
* height and width for the tile container to display in. * width for the tile container to display in.
* *
* NOTE: Mobile specific. * NOTE: Mobile specific.
* *

View File

@ -4,7 +4,7 @@ import { getSourceNameSignalingFeatureFlag } from '../base/config';
import { getVirtualScreenshareParticipantOwnerId } from '../base/participants'; import { getVirtualScreenshareParticipantOwnerId } from '../base/participants';
import { setRemoteParticipants } from './actions'; import { setRemoteParticipants } from './actions';
import { isReorderingEnabled } from './functions'; import { isFilmstripScrollVisible } from './functions';
/** /**
* Computes the reorderd list of the remote participants. * Computes the reorderd list of the remote participants.
@ -118,3 +118,17 @@ export function updateRemoteParticipantsOnLeave(store: Object, participantId: ?s
reorderedParticipants.delete(participantId) reorderedParticipants.delete(participantId)
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants))); && store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
} }
/**
* Returns true if thumbnail reordering is enabled and false otherwise.
* Note: The function will return false if all participants are displayed on the screen.
*
* @param {Object} state - The redux state.
* @returns {boolean} - True if thumbnail reordering is enabled and false otherwise.
*/
export function isReorderingEnabled(state) {
const { testing = {} } = state['features/base/config'];
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
return enableThumbnailReordering && isFilmstripScrollVisible(state);
}

View File

@ -1,9 +1,19 @@
// @flow // @flow
import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags'; import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags';
import { getParticipantCountWithFake, getPinnedParticipant } from '../base/participants'; import {
getLocalParticipant,
getParticipantCountWithFake,
getPinnedParticipant
} from '../base/participants';
import { Platform } from '../base/react';
import { toState } from '../base/redux'; import { toState } from '../base/redux';
import { ASPECT_RATIO_NARROW } from '../base/responsive-ui/constants'; import { ASPECT_RATIO_NARROW } from '../base/responsive-ui/constants';
import { shouldHideSelfView } from '../base/settings/functions.any';
import conferenceStyles from '../conference/components/native/styles';
import { shouldDisplayTileView } from '../video-layout';
import { styles } from './components';
export * from './functions.any'; export * from './functions.any';
@ -61,6 +71,22 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|| disable1On1Mode); || disable1On1Mode);
} }
/**
* Returns the number of participants displayed in tile view.
*
* @param {Object | Function} stateful - The Object or Function that can be
* resolved to a Redux state object with the toState function.
* @returns {number} - The number of participants displayed in tile view.
*/
export function getTileViewParticipantCount(stateful: Object | Function) {
const state = toState(stateful);
const disableSelfView = shouldHideSelfView(state);
const localParticipant = getLocalParticipant(state);
const participantCount = getParticipantCountWithFake(state) - (disableSelfView && localParticipant ? 1 : 0);
return participantCount;
}
/** /**
* Returns how many columns should be displayed for tile view. * Returns how many columns should be displayed for tile view.
* *
@ -71,7 +97,7 @@ export function shouldRemoteVideosBeVisible(state: Object) {
*/ */
export function getColumnCount(stateful: Object | Function) { export function getColumnCount(stateful: Object | Function) {
const state = toState(stateful); const state = toState(stateful);
const participantCount = getParticipantCountWithFake(state); const participantCount = getTileViewParticipantCount(state);
const { aspectRatio } = state['features/base/responsive-ui']; const { aspectRatio } = state['features/base/responsive-ui'];
// For narrow view, tiles should stack on top of each other for a lonely // For narrow view, tiles should stack on top of each other for a lonely
@ -90,16 +116,38 @@ export function getColumnCount(stateful: Object | Function) {
} }
/** /**
* Returns true if thumbnail reordering is enabled and false otherwise. * Returns true if the filmstrip has a scroll and false otherwise.
* *
* @param {Object} state - The redux state. * @param {Object} state - The redux state.
* @returns {boolean} - True if thumbnail reordering is enabled and false otherwise. * @returns {boolean} - True if the scroll is displayed and false otherwise.
*/ */
export function isReorderingEnabled(state) { export function isFilmstripScrollVisible(state) {
const { testing = {} } = state['features/base/config']; if (shouldDisplayTileView(state)) {
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true; return state['features/filmstrip']?.tileViewDimensions?.hasScroll;
}
return enableThumbnailReordering; const { aspectRatio, clientWidth, clientHeight, safeAreaInsets = {} } = state['features/base/responsive-ui'];
const isNarrowAspectRatio = aspectRatio === ASPECT_RATIO_NARROW;
const disableSelfView = shouldHideSelfView(state);
const localParticipant = Boolean(getLocalParticipant(state));
const localParticipantVisible = localParticipant && !disableSelfView;
const participantCount
= getParticipantCountWithFake(state)
- (localParticipant && (shouldDisplayLocalThumbnailSeparately() || disableSelfView) ? 1 : 0);
const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
const { height, width } = getFilmstripDimensions({
aspectRatio,
clientWidth,
clientHeight,
insets: safeAreaInsets,
localParticipantVisible
});
if (isNarrowAspectRatio) {
return width < (thumbnailWidth + (2 * margin)) * participantCount;
}
return height < (thumbnailHeight + (2 * margin)) * participantCount;
} }
/** /**
@ -120,3 +168,68 @@ export function isStageFilmstripAvailable() {
export function isStageFilmstripEnabled() { export function isStageFilmstripEnabled() {
return false; return false;
} }
/**
* Calculates the width and height of the filmstrip based on the screen size and aspect ratio.
*
* @param {Object} options - The screen aspect ratio, width, height and safe are insets.
* @returns {Object} - The width and the height.
*/
export function getFilmstripDimensions({
aspectRatio,
clientWidth,
clientHeight,
insets = {},
localParticipantVisible = true
}) {
const { height, width, margin } = styles.thumbnail;
const conferenceBorder = conferenceStyles.conference.borderWidth || 0;
const { left = 0, right = 0, top = 0, bottom = 0 } = insets;
if (aspectRatio === ASPECT_RATIO_NARROW) {
return {
height,
width:
(shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
? clientWidth - width - (margin * 2) : clientWidth)
- left - right - (styles.filmstripNarrow.margin * 2) - (conferenceBorder * 2)
};
}
return {
height:
(shouldDisplayLocalThumbnailSeparately() && localParticipantVisible
? clientHeight - height - (margin * 2) : clientHeight)
- top - bottom - (conferenceBorder * 2),
width
};
}
/**
* Returns true if the local thumbnail should be displayed separately and false otherwise.
*
* @returns {boolean} - True if the local thumbnail should be displayed separately and flase otherwise.
*/
export function shouldDisplayLocalThumbnailSeparately() {
// XXX Our current design is to have the local participant separate from
// the remote participants. Unfortunately, Android's Video
// implementation cannot accommodate that because remote participants'
// videos appear on top of the local participant's video at times.
// That's because Android's Video utilizes EGL and EGL gives us only two
// practical layers in which we can place our participants' videos:
// layer #0 sits behind the window, creates a hole in the window, and
// there we render the LargeVideo; layer #1 is known as media overlay in
// EGL terms, renders on top of layer #0, and, consequently, is for the
// Filmstrip. With the separate LocalThumbnail, we should have left the
// remote participants' Thumbnails in layer #1 and utilized layer #2 for
// LocalThumbnail. Unfortunately, layer #2 is not practical (that's why
// I said we had two practical layers only) because it renders on top of
// everything which in our case means on top of participant-related
// indicators such as moderator, audio and video muted, etc. For now we
// do not have much of a choice but to continue rendering LocalThumbnail
// as any other remote Thumbnail on Android.
return Platform.OS !== 'android';
}

View File

@ -652,20 +652,6 @@ export function getVerticalViewMaxWidth(state) {
return maxWidth; return maxWidth;
} }
/**
* Returns true if thumbnail reordering is enabled and false otherwise.
* Note: The function will return false if all participants are displayed on the screen.
*
* @param {Object} state - The redux state.
* @returns {boolean} - True if thumbnail reordering is enabled and false otherwise.
*/
export function isReorderingEnabled(state) {
const { testing = {} } = state['features/base/config'];
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
return enableThumbnailReordering && isFilmstripScrollVisible(state);
}
/** /**
* Returns true if the scroll is displayed and false otherwise. * Returns true if the scroll is displayed and false otherwise.
* *

View File

@ -2,7 +2,7 @@
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants'; import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { CLIENT_RESIZED, SET_ASPECT_RATIO } from '../base/responsive-ui'; import { CLIENT_RESIZED, SAFE_AREA_INSETS_CHANGED, SET_ASPECT_RATIO } from '../base/responsive-ui';
import { setTileViewDimensions } from './actions'; import { setTileViewDimensions } from './actions';
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions'; import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
@ -25,6 +25,7 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) { switch (action.type) {
case CLIENT_RESIZED: case CLIENT_RESIZED:
case SAFE_AREA_INSETS_CHANGED:
case SET_ASPECT_RATIO: case SET_ASPECT_RATIO:
store.dispatch(setTileViewDimensions()); store.dispatch(setTileViewDimensions());
break; break;

View File

@ -204,12 +204,15 @@ ReducerRegistry.register(
} }
}; };
case SET_VISIBLE_REMOTE_PARTICIPANTS: { case SET_VISIBLE_REMOTE_PARTICIPANTS: {
const { endIndex, startIndex } = action;
const { remoteParticipants } = state;
const visibleRemoteParticipants = new Set(remoteParticipants.slice(startIndex, endIndex + 1));
return { return {
...state, ...state,
visibleParticipantsStartIndex: action.startIndex, visibleParticipantsStartIndex: startIndex,
visibleParticipantsEndIndex: action.endIndex, visibleParticipantsEndIndex: endIndex,
visibleRemoteParticipants: visibleRemoteParticipants
new Set(state.remoteParticipants.slice(action.startIndex, action.endIndex + 1))
}; };
} }
case PARTICIPANT_LEFT: { case PARTICIPANT_LEFT: {

View File

@ -2,7 +2,7 @@
import { StateListenerRegistry } from '../base/redux'; import { StateListenerRegistry } from '../base/redux';
import { updateRemoteParticipants } from './functions'; import { isFilmstripScrollVisible, updateRemoteParticipants } from './functions';
/** /**
* Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the * Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the
@ -25,3 +25,10 @@ StateListenerRegistry.register(
StateListenerRegistry.register( StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].dominantSpeaker, /* selector */ state => state['features/base/participants'].dominantSpeaker,
/* listener */ (dominantSpeaker, store) => updateRemoteParticipants(store)); /* listener */ (dominantSpeaker, store) => updateRemoteParticipants(store));
/**
* Listens for changes in the filmstrip scroll visibility.
*/
StateListenerRegistry.register(
/* selector */ state => isFilmstripScrollVisible(state),
/* listener */ (_, store) => updateRemoteParticipants(store));

View File

@ -1,25 +1,17 @@
// @flow // @flow
import { getParticipantCountWithFake } from '../base/participants';
import { StateListenerRegistry } from '../base/redux'; import { StateListenerRegistry } from '../base/redux';
import { shouldDisplayTileView } from '../video-layout'; import { shouldDisplayTileView } from '../video-layout';
import { setTileViewDimensions } from './actions'; import { setTileViewDimensions } from './actions';
import { getTileViewParticipantCount } from './functions.native';
import './subscriber.any'; import './subscriber.any';
/** /**
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles. * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
*/ */
StateListenerRegistry.register( StateListenerRegistry.register(
/* selector */ state => { /* selector */ state => getTileViewParticipantCount(state),
const participantCount = getParticipantCountWithFake(state);
if (participantCount < 6) { // the dimensions are updated only when the participant count is lower than 6.
return participantCount;
}
return 5; // make sure we don't update the dimensions.
},
/* listener */ (_, store) => { /* listener */ (_, store) => {
const state = store.getState(); const state = store.getState();

View File

@ -23,9 +23,7 @@ import {
DISPLAY_DRAWER_THRESHOLD DISPLAY_DRAWER_THRESHOLD
} from './constants'; } from './constants';
import { import {
isFilmstripResizable, isFilmstripResizable
isFilmstripScrollVisible,
updateRemoteParticipants
} from './functions'; } from './functions';
import './subscriber.any'; import './subscriber.any';
@ -166,13 +164,6 @@ StateListenerRegistry.register(
store.dispatch(setVerticalViewDimensions()); store.dispatch(setVerticalViewDimensions());
}); });
/**
* Listens for changes in the filmstrip scroll visibility.
*/
StateListenerRegistry.register(
/* selector */ state => isFilmstripScrollVisible(state),
/* listener */ (_, store) => updateRemoteParticipants(store));
/** /**
* Listens for changes to determine the size of the stage filmstrip tiles. * Listens for changes to determine the size of the stage filmstrip tiles.
*/ */

View File

@ -1,7 +1,6 @@
import { NavigationContainer } from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { DialInSummary } from '../../../invite'; import { DialInSummary } from '../../../invite';
@ -36,43 +35,41 @@ const RootNavigationContainer = ({ isWelcomePageAvailable }: Props) => {
? screen.root : screen.connecting; ? screen.root : screen.connecting;
return ( return (
<SafeAreaProvider> <NavigationContainer
<NavigationContainer independent = { true }
independent = { true } ref = { rootNavigationRef }
ref = { rootNavigationRef } theme = { navigationContainerTheme }>
theme = { navigationContainerTheme }> <RootStack.Navigator
<RootStack.Navigator initialRouteName = { initialRouteName }>
initialRouteName = { initialRouteName }> {
{ isWelcomePageAvailable
isWelcomePageAvailable && <>
&& <> <RootStack.Screen
<RootStack.Screen component = { WelcomePageNavigationContainer }
component = { WelcomePageNavigationContainer } name = { screen.root }
name = { screen.root } options = { drawerNavigatorScreenOptions } />
options = { drawerNavigatorScreenOptions } /> <RootStack.Screen
<RootStack.Screen component = { DialInSummary }
component = { DialInSummary } name = { screen.dialInSummary }
name = { screen.dialInSummary } options = { dialInSummaryScreenOptions } />
options = { dialInSummaryScreenOptions } /> </>
</> }
} <RootStack.Screen
<RootStack.Screen component = { ConnectingPage }
component = { ConnectingPage } name = { screen.connecting }
name = { screen.connecting } options = {{
options = {{ gestureEnabled: false,
gestureEnabled: false, headerShown: false
headerShown: false }} />
}} /> <RootStack.Screen
<RootStack.Screen component = { ConferenceNavigationContainer }
component = { ConferenceNavigationContainer } name = { screen.conference.root }
name = { screen.conference.root } options = {{
options = {{ gestureEnabled: false,
gestureEnabled: false, headerShown: false
headerShown: false }} />
}} /> </RootStack.Navigator>
</RootStack.Navigator> </NavigationContainer>
</NavigationContainer>
</SafeAreaProvider>
); );
}; };