[RN] Add connection indicator

This commit is contained in:
Bettenbuk Zoltan 2019-04-15 18:23:28 +02:00 committed by Zoltan Bettenbuk
parent 3217ef2bb4
commit 2b4ace75ae
34 changed files with 368 additions and 198 deletions

View File

@ -25,6 +25,15 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-signal_cellular_0:before {
content: "\e901";
}
.icon-signal_cellular_1:before {
content: "\e902";
}
.icon-signal_cellular_2:before {
content: "\e907";
}
.icon-phone:before { .icon-phone:before {
content: "\e0cd"; content: "\e0cd";
} }

Binary file not shown.

View File

@ -28,10 +28,13 @@
<glyph unicode="&#xe8b3;" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" /> <glyph unicode="&#xe8b3;" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
<glyph unicode="&#xe8b6;" glyph-name="search" d="M406 426c106 0 192 86 192 192s-86 192-192 192-192-86-192-192 86-192 192-192zM662 426l212-212-64-64-212 212v34l-12 12c-48-42-112-66-180-66-154 0-278 122-278 276s124 278 278 278 276-124 276-278c0-68-24-132-66-180l12-12h34z" /> <glyph unicode="&#xe8b6;" glyph-name="search" d="M406 426c106 0 192 86 192 192s-86 192-192 192-192-86-192-192 86-192 192-192zM662 426l212-212-64-64-212 212v34l-12 12c-48-42-112-66-180-66-154 0-278 122-278 276s124 278 278 278 276-124 276-278c0-68-24-132-66-180l12-12h34z" />
<glyph unicode="&#xe900;" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" /> <glyph unicode="&#xe900;" glyph-name="AUD" d="M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512c282.77 0 512-229.23 512-512s-229.23-512-512-512zM308.25 387.3h57.225l-87.675 252.525h-62.125l-87.675-252.525h53.025l19.425 60.2h88.725l19.075-60.2zM461.9 639.825h-52.85v-165.375c0-56 41.125-93.625 105.7-93.625 64.75 0 105.875 37.625 105.875 93.625v165.375h-52.85v-159.95c0-31.85-19.075-52.15-53.025-52.15-33.775 0-52.85 20.3-52.85 52.15v159.95zM682.225 640v-252.7h99.4c75.6 0 118.475 46.025 118.475 128.1 0 79.1-43.4 124.6-118.475 124.6h-99.4zM735.075 594.85v-162.4h38.15c46.725 0 72.975 28.7 72.975 82.075 0 51.1-27.125 80.325-72.975 80.325h-38.15zM243.5 587.325l-31.675-99.050h66.15l-31.325 99.050h-3.15z" />
<glyph unicode="&#xe901;" glyph-name="signal_cellular_0" d="M938 938v-852h-852zM854 732l-562-562h562v562z" />
<glyph unicode="&#xe902;" glyph-name="signal_cellular_1" d="M86 86l852 852v-256h-170v-596h-682zM854 86v84h84v-84h-84zM854 256v342h84v-342h-84z" />
<glyph unicode="&#xe903;" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" /> <glyph unicode="&#xe903;" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />
<glyph unicode="&#xe904;" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" /> <glyph unicode="&#xe904;" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
<glyph unicode="&#xe905;" glyph-name="hangup" d="M512 640c-68 0-134-10-196-30v-132c0-16-10-34-24-40-42-20-80-46-114-78-8-8-18-12-30-12s-22 4-30 12l-106 106c-8 8-12 18-12 30s4 22 12 30c130 124 306 200 500 200s370-76 500-200c8-8 12-18 12-30s-4-22-12-30l-106-106c-8-8-18-12-30-12s-22 4-30 12c-34 32-72 58-114 78-14 6-24 20-24 38v132c-62 20-128 32-196 32z" /> <glyph unicode="&#xe905;" glyph-name="hangup" d="M512 640c-68 0-134-10-196-30v-132c0-16-10-34-24-40-42-20-80-46-114-78-8-8-18-12-30-12s-22 4-30 12l-106 106c-8 8-12 18-12 30s4 22 12 30c130 124 306 200 500 200s370-76 500-200c8-8 12-18 12-30s-4-22-12-30l-106-106c-8-8-18-12-30-12s-22 4-30 12c-34 32-72 58-114 78-14 6-24 20-24 38v132c-62 20-128 32-196 32z" />
<glyph unicode="&#xe906;" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" /> <glyph unicode="&#xe906;" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
<glyph unicode="&#xe907;" glyph-name="signal_cellular_2" d="M86 86l852 852v-852h-852z" />
<glyph unicode="&#xe908;" glyph-name="share-doc" d="M554 640h236l-236 234v-234zM682 426v86h-340v-86h340zM682 256v86h-340v-86h340zM598 938l256-256v-512c0-46-40-84-86-84h-512c-46 0-86 38-86 84l2 684c0 46 38 84 84 84h342z" /> <glyph unicode="&#xe908;" glyph-name="share-doc" d="M554 640h236l-236 234v-234zM682 426v86h-340v-86h340zM682 256v86h-340v-86h340zM598 938l256-256v-512c0-46-40-84-86-84h-512c-46 0-86 38-86 84l2 684c0 46 38 84 84 84h342z" />
<glyph unicode="&#xe909;" glyph-name="ninja" d="M330.667 469.333c-0.427 14.933 6.4 29.44 17.92 39.253 32-6.827 61.867-20.053 88.747-39.253 0-29.013-23.893-52.907-53.333-52.907s-52.907 23.467-53.333 52.907zM586.667 469.333c26.88 18.773 56.747 32 88.747 38.827 11.52-9.813 18.347-24.32 17.92-38.827 0-29.867-23.893-53.76-53.333-53.76s-53.333 23.893-53.333 53.76v0zM512 640c-118.187 1.707-234.667-27.733-338.347-85.333l-2.987-42.667c0-52.48 12.373-104.107 35.84-151.040 101.12 15.36 203.093 23.040 305.493 23.040s204.373-7.68 305.493-23.040c23.467 46.933 35.84 98.56 35.84 151.040l-2.987 42.667c-103.68 57.6-220.16 87.040-338.347 85.333zM512 938.667c235.641 0 426.667-191.025 426.667-426.667s-191.025-426.667-426.667-426.667c-235.641 0-426.667 191.025-426.667 426.667s191.025 426.667 426.667 426.667z" /> <glyph unicode="&#xe909;" glyph-name="ninja" d="M330.667 469.333c-0.427 14.933 6.4 29.44 17.92 39.253 32-6.827 61.867-20.053 88.747-39.253 0-29.013-23.893-52.907-53.333-52.907s-52.907 23.467-53.333 52.907zM586.667 469.333c26.88 18.773 56.747 32 88.747 38.827 11.52-9.813 18.347-24.32 17.92-38.827 0-29.867-23.893-53.76-53.333-53.76s-53.333 23.893-53.333 53.76v0zM512 640c-118.187 1.707-234.667-27.733-338.347-85.333l-2.987-42.667c0-52.48 12.373-104.107 35.84-151.040 101.12 15.36 203.093 23.040 305.493 23.040s204.373-7.68 305.493-23.040c23.467 46.933 35.84 98.56 35.84 151.040l-2.987 42.667c-103.68 57.6-220.16 87.040-338.347 85.333zM512 938.667c235.641 0 426.667-191.025 426.667-426.667s-191.025-426.667-426.667-426.667c-235.641 0-426.667 191.025-426.667 426.667s191.025 426.667 426.667 426.667z" />
<glyph unicode="&#xe90b;" glyph-name="full-screen" d="M598 810h212v-212h-84v128h-128v84zM726 298v128h84v-212h-212v84h128zM214 598v212h212v-84h-128v-128h-84zM298 426v-128h128v-84h-212v212h84z" /> <glyph unicode="&#xe90b;" glyph-name="full-screen" d="M598 810h212v-212h-84v128h-128v84zM726 298v128h84v-212h-212v84h128zM214 598v212h212v-84h-128v-128h-84zM298 426v-128h128v-84h-212v212h84z" />

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -836,9 +836,9 @@ SmallVideo.prototype.updateIndicators = function() {
isLocalVideo = { this.isLocal } isLocalVideo = { this.isLocal }
enableStatsDisplay enableStatsDisplay
= { !interfaceConfig.filmStripOnly } = { !interfaceConfig.filmStripOnly }
participantId = { this.id }
statsPopoverPosition statsPopoverPosition
= { statsPopoverPosition } = { statsPopoverPosition } />
userID = { this.id } />
: null } : null }
<RaisedHandIndicator <RaisedHandIndicator
iconSize = { iconSize } iconSize = { iconSize }

View File

@ -3,9 +3,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { Icon } from '../../../base/font-icons'; import { Icon } from '../../../font-icons';
import { type StyleType } from '../../../styles';
import styles from './styles'; import styles from './indicatorstyles';
type Props = { type Props = {
@ -17,7 +18,12 @@ type Props = {
/** /**
* The name of the icon to be used as the indicator. * The name of the icon to be used as the indicator.
*/ */
icon: string icon: string,
/**
* Additional style to be applied to the icon element.
*/
iconStyle: StyleType
}; };
/** /**
@ -31,13 +37,16 @@ export default class BaseIndicator extends Component<Props> {
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { highlight, icon } = this.props; const { highlight, icon, iconStyle } = this.props;
return ( return (
<View style = { highlight ? styles.highlightedIndicator : null }> <View style = { highlight ? styles.highlightedIndicator : null }>
<Icon <Icon
name = { icon } name = { icon }
style = { styles.indicator } /> style = { [
styles.indicator,
iconStyle
] } />
</View> </View>
); );
} }

View File

@ -2,6 +2,7 @@
export { default as AvatarListItem } from './AvatarListItem'; export { default as AvatarListItem } from './AvatarListItem';
export { default as BackButton } from './BackButton'; export { default as BackButton } from './BackButton';
export { default as BaseIndicator } from './BaseIndicator';
export { default as Button } from './Button'; export { default as Button } from './Button';
export { default as Container } from './Container'; export { default as Container } from './Container';
export { default as ForwardButton } from './ForwardButton'; export { default as ForwardButton } from './ForwardButton';

View File

@ -0,0 +1,28 @@
// @flow
import { ColorPalette } from '../../../styles';
export default {
/**
* Highlighted indicator additional style.
*/
highlightedIndicator: {
backgroundColor: ColorPalette.blue,
borderRadius: 16,
padding: 4
},
/**
* Base indicator style.
*/
indicator: {
backgroundColor: ColorPalette.transparent,
color: ColorPalette.white,
fontSize: 12,
textShadowColor: ColorPalette.black,
textShadowOffset: {
height: -1,
width: 0
}
}
};

View File

@ -3,7 +3,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import Tooltip from '@atlaskit/tooltip'; import Tooltip from '@atlaskit/tooltip';
import { translate } from '../../../base/i18n'; import { translate } from '../../../i18n';
/** /**
* The type of the React {@code Component} props of {@link BaseIndicator}. * The type of the React {@code Component} props of {@link BaseIndicator}.

View File

@ -1,3 +1,6 @@
// @flow
export { default as BaseIndicator } from './BaseIndicator';
export { default as Button } from './Button'; export { default as Button } from './Button';
export { default as Container } from './Container'; export { default as Container } from './Container';
export { default as Image } from './Image'; export { default as Image } from './Image';

View File

@ -0,0 +1,173 @@
// @flow
import { Component } from 'react';
import statsEmitter from '../statsEmitter';
declare var interfaceConfig: Object;
/**
* The connection quality percentage that must be reached to be considered of
* good quality and can result in the connection indicator being hidden.
*
* @type {number}
*/
export const INDICATOR_DISPLAY_THRESHOLD = 30;
/**
* The type of the React {@code Component} props of {@link ConnectionIndicator}.
*/
export type Props = {
/**
* The ID of the participant associated with the displayed connection indication and
* stats.
*/
participantId: string
};
/**
* The type of the React {@code Component} state of {@link ConnectionIndicator}.
*/
export type State = {
/**
* Whether or not a CSS class should be applied to the root for hiding the
* connection indicator. By default the indicator should start out hidden
* because the current connection status is not known at mount.
*/
showIndicator: boolean,
/**
* Cache of the stats received from subscribing to stats emitting. The keys
* should be the name of the stat. With each stat update, updates stats are
* mixed in with cached stats and a new stats object is set in state.
*/
stats: Object
};
/**
* Implements a React {@link Component} which displays the current connection
* quality.
*
* @extends {Component}
*/
export default class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
/**
* The timeout for automatically hiding the indicator.
*/
autoHideTimeout: ?TimeoutID
/**
* Initializes a new {@code ConnectionIndicator} instance.
*
* @param {P} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: P) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._onStatsUpdated = this._onStatsUpdated.bind(this);
}
/**
* Starts listening for stat updates.
*
* @inheritdoc
* returns {void}
*/
componentDidMount() {
statsEmitter.subscribeToClientStats(
this.props.participantId, this._onStatsUpdated);
}
/**
* Updates which user's stats are being listened to.
*
* @inheritdoc
* returns {void}
*/
componentDidUpdate(prevProps: Props) {
if (prevProps.participantId !== this.props.participantId) {
statsEmitter.unsubscribeToClientStats(
prevProps.participantId, this._onStatsUpdated);
statsEmitter.subscribeToClientStats(
this.props.participantId, this._onStatsUpdated);
}
}
/**
* Cleans up any queued processes, which includes listening for new stats
* and clearing any timeout to hide the indicator.
*
* @private
* @returns {void}
*/
componentWillUnmount() {
statsEmitter.unsubscribeToClientStats(
this.props.participantId, this._onStatsUpdated);
clearTimeout(this.autoHideTimeout);
}
_onStatsUpdated: (Object) => void;
/**
* Callback invoked when new connection stats associated with the passed in
* user ID are available. Will update the component's display of current
* statistics.
*
* @param {Object} stats - Connection stats from the library.
* @private
* @returns {void}
*/
_onStatsUpdated(stats = {}) {
// Rely on React to batch setState actions.
const { connectionQuality } = stats;
const newPercentageState = typeof connectionQuality === 'undefined'
? {} : { percent: connectionQuality };
const newStats = Object.assign(
{},
this.state.stats,
stats,
newPercentageState);
this.setState({
stats: newStats
});
this._updateIndicatorAutoHide(newStats.percent);
}
/**
* Updates the internal state for automatically hiding the indicator.
*
* @param {number} percent - The current connection quality percentage
* between the values 0 and 100.
* @private
* @returns {void}
*/
_updateIndicatorAutoHide(percent) {
if (percent < INDICATOR_DISPLAY_THRESHOLD) {
clearTimeout(this.autoHideTimeout);
this.autoHideTimeout = undefined;
this.setState({
showIndicator: true
});
} else if (this.autoHideTimeout) {
// This clause is intentionally left blank because no further action
// is needed if the percent is below the threshold and there is an
// autoHideTimeout set.
} else {
this.autoHideTimeout = setTimeout(() => {
this.setState({
showIndicator: false
});
}, typeof interfaceConfig === 'undefined'
? 5000
: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT);
}
}
}

View File

@ -0,0 +1,3 @@
// @flow
export * from './native';

View File

@ -0,0 +1,3 @@
// @flow
export * from './web';

View File

@ -0,0 +1,62 @@
// @flow
import React from 'react';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux';
import AbstractConnectionIndicator, {
type Props,
type State
} from '../AbstractConnectionIndicator';
import { CONNECTOR_INDICATOR_COLORS } from './styles';
/**
* Implements an indicator to show the quality of the connection of a participant.
*/
class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
/**
* Initializes a new {@code ConnectionIndicator} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.state = {
autoHideTimeout: undefined,
showIndicator: false,
stats: {}
};
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { showIndicator, stats } = this.state;
const { percent } = stats;
if (!showIndicator || typeof percent === 'undefined') {
return null;
}
// Signal level on a scale 0..2
const signalLevel = Math.floor(percent / 33.4);
return (
<BaseIndicator
icon = { `signal_cellular_${signalLevel}` }
iconStyle = {{
color: CONNECTOR_INDICATOR_COLORS[signalLevel]
}} />
);
}
}
export default connect()(ConnectionIndicator);

View File

@ -1 +1,3 @@
// @flow
export { default as ConnectionIndicator } from './ConnectionIndicator'; export { default as ConnectionIndicator } from './ConnectionIndicator';

View File

@ -0,0 +1,9 @@
// @flow
import { ColorPalette } from '../../../base/styles';
export const CONNECTOR_INDICATOR_COLORS = [
ColorPalette.red,
ColorPalette.Y200,
ColorPalette.green
];

View File

@ -1,24 +1,20 @@
/* @flow */ // @flow
import React, { Component } from 'react'; import React from 'react';
import { translate } from '../../base/i18n'; import { translate } from '../../../base/i18n';
import { JitsiParticipantConnectionStatus } from '../../base/lib-jitsi-meet'; import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
import { Popover } from '../../base/popover'; import { Popover } from '../../../base/popover';
import { ConnectionStatsTable } from '../../connection-stats'; import { ConnectionStatsTable } from '../../../connection-stats';
import statsEmitter from '../statsEmitter'; import AbstractConnectionIndicator, {
INDICATOR_DISPLAY_THRESHOLD,
type Props as AbstractProps,
type State as AbstractState
} from '../AbstractConnectionIndicator';
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
/**
* The connection quality percentage that must be reached to be considered of
* good quality and can result in the connection indicator being hidden.
*
* @type {number}
*/
const INDICATOR_DISPLAY_THRESHOLD = 30;
/** /**
* An array of display configurations for the connection indicator and its bars. * An array of display configurations for the connection indicator and its bars.
* The ordering is done specifically for faster iteration to find a matching * The ordering is done specifically for faster iteration to find a matching
@ -58,7 +54,7 @@ const QUALITY_TO_WIDTH: Array<Object> = [
/** /**
* The type of the React {@code Component} props of {@link ConnectionIndicator}. * The type of the React {@code Component} props of {@link ConnectionIndicator}.
*/ */
type Props = { type Props = AbstractProps & {
/** /**
* Whether or not the component should ignore setting a visibility class for * Whether or not the component should ignore setting a visibility class for
@ -97,43 +93,18 @@ type Props = {
/** /**
* Invoked to obtain translated strings. * Invoked to obtain translated strings.
*/ */
t: Function, t: Function
/**
* The user ID associated with the displayed connection indication and
* stats.
*/
userID: string
}; };
/** /**
* The type of the React {@code Component} state of {@link ConnectionIndicator}. * The type of the React {@code Component} state of {@link ConnectionIndicator}.
*/ */
type State = { type State = AbstractState & {
/**
* The timeout for automatically hiding the indicator.
*/
autoHideTimeout: TimeoutID | null,
/**
* Whether or not a CSS class should be applied to the root for hiding the
* connection indicator. By default the indicator should start out hidden
* because the current connection status is not known at mount.
*/
showIndicator: boolean,
/** /**
* Whether or not the popover content should display additional statistics. * Whether or not the popover content should display additional statistics.
*/ */
showMoreStats: boolean, showMoreStats: boolean
/**
* Cache of the stats received from subscribing to stats emitting. The keys
* should be the name of the stat. With each stat update, updates stats are
* mixed in with cached stats and a new stats object is set in state.
*/
stats: Object
}; };
/** /**
@ -142,7 +113,7 @@ type State = {
* *
* @extends {Component} * @extends {Component}
*/ */
class ConnectionIndicator extends Component<Props, State> { class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
/** /**
* Initializes a new {@code ConnectionIndicator} instance. * Initializes a new {@code ConnectionIndicator} instance.
* *
@ -153,59 +124,16 @@ class ConnectionIndicator extends Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
autoHideTimeout: null, autoHideTimeout: undefined,
showIndicator: false, showIndicator: false,
showMoreStats: false, showMoreStats: false,
stats: {} stats: {}
}; };
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once for every instance.
this._onStatsUpdated = this._onStatsUpdated.bind(this);
this._onToggleShowMore = this._onToggleShowMore.bind(this); this._onToggleShowMore = this._onToggleShowMore.bind(this);
} }
/**
* Starts listening for stat updates.
*
* @inheritdoc
* returns {void}
*/
componentDidMount() {
statsEmitter.subscribeToClientStats(
this.props.userID, this._onStatsUpdated);
}
/**
* Updates which user's stats are being listened to.
*
* @inheritdoc
* returns {void}
*/
componentDidUpdate(prevProps) {
if (prevProps.userID !== this.props.userID) {
statsEmitter.unsubscribeToClientStats(
prevProps.userID, this._onStatsUpdated);
statsEmitter.subscribeToClientStats(
this.props.userID, this._onStatsUpdated);
}
}
/**
* Cleans up any queued processes, which includes listening for new stats
* and clearing any timeout to hide the indicator.
*
* @private
* @returns {void}
*/
componentWillUnmount() {
statsEmitter.unsubscribeToClientStats(
this.props.userID, this._onStatsUpdated);
if (this.state.autoHideTimeout) {
clearTimeout(this.state.autoHideTimeout);
}
}
/** /**
* Implements React's {@link Component#render()}. * Implements React's {@link Component#render()}.
* *
@ -331,35 +259,6 @@ class ConnectionIndicator extends Component<Props, State> {
? 'show-connection-indicator' : 'hide-connection-indicator'; ? 'show-connection-indicator' : 'hide-connection-indicator';
} }
_onStatsUpdated: (Object) => void;
/**
* Callback invoked when new connection stats associated with the passed in
* user ID are available. Will update the component's display of current
* statistics.
*
* @param {Object} stats - Connection stats from the library.
* @private
* @returns {void}
*/
_onStatsUpdated(stats = {}) {
// Rely on React to batch setState actions.
const { connectionQuality } = stats;
const newPercentageState = typeof connectionQuality === 'undefined'
? {} : { percent: connectionQuality };
const newStats = Object.assign(
{},
this.state.stats,
stats,
newPercentageState);
this.setState({
stats: newStats
});
this._updateIndicatorAutoHide(newStats.percent);
}
_onToggleShowMore: () => void; _onToggleShowMore: () => void;
/** /**
@ -459,39 +358,6 @@ class ConnectionIndicator extends Component<Props, State> {
transport = { transport } /> transport = { transport } />
); );
} }
/**
* Updates the internal state for automatically hiding the indicator.
*
* @param {number} percent - The current connection quality percentage
* between the values 0 and 100.
* @private
* @returns {void}
*/
_updateIndicatorAutoHide(percent) {
if (percent < INDICATOR_DISPLAY_THRESHOLD) {
if (this.state.autoHideTimeout) {
clearTimeout(this.state.autoHideTimeout);
}
this.setState({
autoHideTimeout: null,
showIndicator: true
});
} else if (this.state.autoHideTimeout) {
// This clause is intentionally left blank because no further action
// is needed if the percent is below the threshold and there is an
// autoHideTimeout set.
} else {
this.setState({
autoHideTimeout: setTimeout(() => {
this.setState({
showIndicator: false
});
}, interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT)
});
}
}
} }
export default translate(ConnectionIndicator); export default translate(ConnectionIndicator);

View File

@ -0,0 +1,3 @@
// @flow
export { default as ConnectionIndicator } from './ConnectionIndicator';

View File

@ -1,3 +1,5 @@
// @flow
export * from './components'; export * from './components';
export { default as statsEmitter } from './statsEmitter'; export { default as statsEmitter } from './statsEmitter';

View File

@ -7,8 +7,6 @@ import {
JitsiE2ePingEvents JitsiE2ePingEvents
} from '../base/lib-jitsi-meet'; } from '../base/lib-jitsi-meet';
declare var APP: Object;
/** /**
* Contains all the callbacks to be notified when stats are updated. * Contains all the callbacks to be notified when stats are updated.
* *

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* Thumbnail badge for displaying the audio mute status of a participant. * Thumbnail badge for displaying the audio mute status of a participant.

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* Thumbnail badge showing that the participant is the dominant speaker in * Thumbnail badge showing that the participant is the dominant speaker in

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* Thumbnail badge showing that the participant is a conference moderator. * Thumbnail badge showing that the participant is a conference moderator.

View File

@ -2,6 +2,7 @@
import React from 'react'; import React from 'react';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import AbstractRaisedHandIndicator, { import AbstractRaisedHandIndicator, {
@ -9,8 +10,6 @@ import AbstractRaisedHandIndicator, {
_mapStateToProps _mapStateToProps
} from '../AbstractRaisedHandIndicator'; } from '../AbstractRaisedHandIndicator';
import BaseIndicator from './BaseIndicator';
/** /**
* Thumbnail badge showing that the participant would like to speak. * Thumbnail badge showing that the participant would like to speak.
* *

View File

@ -17,6 +17,7 @@ import { Container } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles'; import { StyleType } from '../../../base/styles';
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks'; import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
import { ConnectionIndicator } from '../../../connection-indicator';
import { DisplayNameLabel } from '../../../display-name'; import { DisplayNameLabel } from '../../../display-name';
import { RemoteVideoMenu } from '../../../remote-video-menu'; import { RemoteVideoMenu } from '../../../remote-video-menu';
@ -176,12 +177,24 @@ class Thumbnail extends Component<Props> {
<ModeratorIndicator /> <ModeratorIndicator />
</View> } </View> }
<View style = { styles.thumbnailTopIndicatorContainer }> <View
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopLeftIndicatorContainer
] }>
<RaisedHandIndicator participantId = { participant.id } /> <RaisedHandIndicator participantId = { participant.id } />
{ participant.dominantSpeaker { participant.dominantSpeaker
&& <DominantSpeakerIndicator /> } && <DominantSpeakerIndicator /> }
</View> </View>
<View
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopRightIndicatorContainer
] }>
<ConnectionIndicator participantId = { participant.id } />
</View>
<Container style = { styles.thumbnailIndicatorContainer }> <Container style = { styles.thumbnailIndicatorContainer }>
{ audioMuted { audioMuted
&& <AudioMutedIndicator /> } && <AudioMutedIndicator /> }

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* Thumbnail badge for displaying the video mute status of a participant. * Thumbnail badge for displaying the video mute status of a participant.

View File

@ -14,28 +14,6 @@ export const AVATAR_SIZE = 50;
* The styles of the feature filmstrip. * The styles of the feature filmstrip.
*/ */
export default { export default {
/**
* Highlighted indicator additional style.
*/
highlightedIndicator: {
backgroundColor: ColorPalette.blue,
borderRadius: 16,
padding: 4
},
/**
* Dominant speaker indicator style.
*/
indicator: {
backgroundColor: ColorPalette.transparent,
color: ColorPalette.white,
fontSize: 12,
textShadowColor: ColorPalette.black,
textShadowOffset: {
height: -1,
width: 0
}
},
/** /**
* The style of the narrow {@link Filmstrip} version which displays * The style of the narrow {@link Filmstrip} version which displays
@ -121,12 +99,19 @@ export default {
}, },
thumbnailTopIndicatorContainer: { thumbnailTopIndicatorContainer: {
left: 0,
padding: 4, padding: 4,
position: 'absolute', position: 'absolute',
top: 0 top: 0
}, },
thumbnailTopLeftIndicatorContainer: {
left: 0
},
thumbnailTopRightIndicatorContainer: {
right: 0
},
tileView: { tileView: {
alignSelf: 'center' alignSelf: 'center'
}, },

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* The type of the React {@code Component} props of {@link AudioMutedIndicator}. * The type of the React {@code Component} props of {@link AudioMutedIndicator}.

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* The type of the React {@code Component} props of * The type of the React {@code Component} props of

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* The type of the React {@code Component} props of {@link ModeratorIndicator}. * The type of the React {@code Component} props of {@link ModeratorIndicator}.

View File

@ -2,6 +2,7 @@
import React from 'react'; import React from 'react';
import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import AbstractRaisedHandIndicator, { import AbstractRaisedHandIndicator, {
@ -9,8 +10,6 @@ import AbstractRaisedHandIndicator, {
_mapStateToProps _mapStateToProps
} from '../AbstractRaisedHandIndicator'; } from '../AbstractRaisedHandIndicator';
import BaseIndicator from './BaseIndicator';
/** /**
* The type of the React {@code Component} props of {@link RaisedHandIndicator}. * The type of the React {@code Component} props of {@link RaisedHandIndicator}.
*/ */

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BaseIndicator from './BaseIndicator'; import { BaseIndicator } from '../../../base/react';
/** /**
* The type of the React {@code Component} props of {@link VideoMutedIndicator}. * The type of the React {@code Component} props of {@link VideoMutedIndicator}.