feat(RN-filmtrip) stop reordering small meetings
This commit is contained in:
parent
61abf0d882
commit
adef5095da
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue