feat(thumbnails, rn) Native thumbnails redesign (#10954)

This commit is contained in:
Robert Pintilii 2022-02-14 12:13:18 +02:00 committed by GitHub
parent ac8ae50cf0
commit 59065d10f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 95 additions and 177 deletions

View File

@ -26,11 +26,6 @@ type Props = {
*/ */
icon: string, icon: string,
/**
* Size of icon.
*/
iconSize: ?number,
/** /**
* Additional style to be applied to the icon element. * Additional style to be applied to the icon element.
*/ */
@ -48,19 +43,12 @@ export default class BaseIndicator extends Component<Props> {
* @inheritdoc * @inheritdoc
*/ */
render() { render() {
const { highlight, icon, iconStyle, backgroundColor, iconSize } = this.props; const { icon, iconStyle } = this.props;
const highlightedIndicator = { ...styles.highlightedIndicator };
if (backgroundColor) {
highlightedIndicator.backgroundColor = backgroundColor;
}
return ( return (
<View <View
style = { [ BASE_INDICATOR, style = { BASE_INDICATOR }>
highlight ? highlightedIndicator : null ] }>
<Icon <Icon
size = { iconSize }
src = { icon } src = { icon }
style = { [ style = { [
styles.indicator, styles.indicator,

View File

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

View File

@ -1,22 +1,18 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { View } from 'react-native';
import { IconSignalLevel0, IconSignalLevel1, IconSignalLevel2 } from '../../../base/icons'; import { IconConnectionActive } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react'; import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import indicatorStyles from '../../../filmstrip/components/native/styles';
import AbstractConnectionIndicator, { import AbstractConnectionIndicator, {
type Props, type Props,
type State type State
} from '../AbstractConnectionIndicator'; } from '../AbstractConnectionIndicator';
import { CONNECTOR_INDICATOR_COLORS } from './styles'; import { CONNECTOR_INDICATOR_COLORS, iconStyle } from './styles';
const ICONS = [
IconSignalLevel0,
IconSignalLevel1,
IconSignalLevel2
];
/** /**
* Implements an indicator to show the quality of the connection of a participant. * Implements an indicator to show the quality of the connection of a participant.
@ -55,11 +51,15 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
const signalLevel = Math.floor(percent / 33.4); const signalLevel = Math.floor(percent / 33.4);
return ( return (
<BaseIndicator <View
icon = { ICONS[signalLevel] } style = {{
iconStyle = {{ ...indicatorStyles.indicatorContainer,
color: CONNECTOR_INDICATOR_COLORS[signalLevel] backgroundColor: CONNECTOR_INDICATOR_COLORS[signalLevel]
}} /> }}>
<BaseIndicator
icon = { IconConnectionActive }
iconStyle = { iconStyle } />
</View>
); );
} }

View File

@ -7,3 +7,7 @@ export const CONNECTOR_INDICATOR_COLORS = [
ColorPalette.Y200, ColorPalette.Y200,
ColorPalette.green ColorPalette.green
]; ];
export const iconStyle = {
fontSize: 14
};

View File

@ -4,10 +4,8 @@ import React, { Component } from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { import {
getLocalParticipant,
getParticipantById, getParticipantById,
getParticipantDisplayName, getParticipantDisplayName
shouldRenderParticipantVideo
} from '../../../base/participants'; } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -25,6 +23,11 @@ type Props = {
*/ */
_render: boolean, _render: boolean,
/**
* Whether ot not the name is in a container.
*/
contained?: boolean,
/** /**
* The ID of the participant to render the label for. * The ID of the participant to render the label for.
*/ */
@ -46,8 +49,10 @@ class DisplayNameLabel extends Component<Props> {
} }
return ( return (
<View style = { styles.displayNameBackdrop }> <View style = { this.props.contained ? styles.displayNamePadding : styles.displayNameBackdrop }>
<Text style = { styles.displayNameText }> <Text
numberOfLines = { 1 }
style = { styles.displayNameText }>
{ this.props._participantName } { this.props._participantName }
</Text> </Text>
</View> </View>
@ -65,7 +70,6 @@ class DisplayNameLabel extends Component<Props> {
*/ */
function _mapStateToProps(state: Object, ownProps: Props) { function _mapStateToProps(state: Object, ownProps: Props) {
const { participantId } = ownProps; const { participantId } = ownProps;
const localParticipant = getLocalParticipant(state);
const participant = getParticipantById(state, participantId); const participant = getParticipantById(state, participantId);
const isFakeParticipant = participant && participant.isFakeParticipant; const isFakeParticipant = participant && participant.isFakeParticipant;
@ -73,8 +77,6 @@ function _mapStateToProps(state: Object, ownProps: Props) {
// participant and there is no video rendered for // participant and there is no video rendered for
// them. // them.
const _render = Boolean(participantId) const _render = Boolean(participantId)
&& localParticipant?.id !== participantId
&& !shouldRenderParticipantVideo(state, participantId)
&& !isFakeParticipant; && !isFakeParticipant;
return { return {

View File

@ -11,8 +11,13 @@ export default {
paddingVertical: 4 paddingVertical: 4
}, },
displayNamePadding: {
padding: 2
},
displayNameText: { displayNameText: {
color: ColorPalette.white, color: ColorPalette.white,
fontSize: 14 fontSize: 14,
fontWeight: 'bold'
} }
}; };

View File

@ -16,9 +16,7 @@ export default class AudioMutedIndicator extends Component<{}> {
*/ */
render() { render() {
return ( return (
<BaseIndicator <BaseIndicator icon = { IconMicDisabled } />
highlight = { false }
icon = { IconMicDisabled } />
); );
} }
} }

View File

@ -1,25 +0,0 @@
// @flow
import React, { Component } from 'react';
import { IconDominantSpeaker } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react';
/**
* Thumbnail badge showing that the participant is the dominant speaker in
* the conference.
*/
export default class DominantSpeakerIndicator extends Component<{}> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
return (
<BaseIndicator
highlight = { true }
icon = { IconDominantSpeaker } />
);
}
}

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { IconModerator } from '../../../base/icons'; import { IconCrown } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react'; import { BaseIndicator } from '../../../base/react';
/** /**
@ -16,9 +16,7 @@ export default class ModeratorIndicator extends Component<{}> {
*/ */
render() { render() {
return ( return (
<BaseIndicator <BaseIndicator icon = { IconCrown } />
highlight = { false }
icon = { IconModerator } />
); );
} }
} }

View File

@ -1,16 +1,18 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { View } from 'react-native';
import { IconRaisedHand } from '../../../base/icons'; import { IconRaisedHand } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react'; import { BaseIndicator } from '../../../base/react';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import AbstractRaisedHandIndicator, { import AbstractRaisedHandIndicator, {
type Props, type Props,
_mapStateToProps _mapStateToProps
} from '../AbstractRaisedHandIndicator'; } from '../AbstractRaisedHandIndicator';
import styles from './styles';
/** /**
* Thumbnail badge showing that the participant would like to speak. * Thumbnail badge showing that the participant would like to speak.
* *
@ -24,12 +26,11 @@ class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
*/ */
_renderIndicator() { _renderIndicator() {
return ( return (
<BaseIndicator <View style = { styles.raisedHandIndicator }>
backgroundColor = { BaseTheme.palette.warning02 } <BaseIndicator
highlight = { true } icon = { IconRaisedHand }
icon = { IconRaisedHand } iconStyle = { styles.raisedHandIcon } />
iconSize = { 16 } </View>
iconStyle = {{ color: BaseTheme.palette.uiBackground }} />
); );
} }
} }

View File

@ -12,8 +12,6 @@ import { BaseIndicator } from '../../../base/react';
*/ */
export default function ScreenShareIndicator() { export default function ScreenShareIndicator() {
return ( return (
<BaseIndicator <BaseIndicator icon = { IconShareDesktop } />
highlight = { false }
icon = { IconShareDesktop } />
); );
} }

View File

@ -30,11 +30,9 @@ import { toggleToolboxVisible } from '../../../toolbox/actions.native';
import { SQUARE_TILE_ASPECT_RATIO } from '../../constants'; import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
import AudioMutedIndicator from './AudioMutedIndicator'; import AudioMutedIndicator from './AudioMutedIndicator';
import DominantSpeakerIndicator from './DominantSpeakerIndicator';
import ModeratorIndicator from './ModeratorIndicator'; import ModeratorIndicator from './ModeratorIndicator';
import RaisedHandIndicator from './RaisedHandIndicator'; import RaisedHandIndicator from './RaisedHandIndicator';
import ScreenShareIndicator from './ScreenShareIndicator'; import ScreenShareIndicator from './ScreenShareIndicator';
import VideoMutedIndicator from './VideoMutedIndicator';
import styles, { AVATAR_SIZE } from './styles'; import styles, { AVATAR_SIZE } from './styles';
/** /**
@ -105,11 +103,6 @@ type Props = {
*/ */
_styles: StyleType, _styles: StyleType,
/**
* Indicates whether the participant is video muted.
*/
_videoMuted: boolean,
/** /**
* If true, there will be no color overlay (tint) on the thumbnail * If true, there will be no color overlay (tint) on the thumbnail
* indicating the participant associated with the thumbnail is displayed on * indicating the participant associated with the thumbnail is displayed on
@ -207,21 +200,12 @@ class Thumbnail extends PureComponent<Props> {
_audioMuted: audioMuted, _audioMuted: audioMuted,
_isScreenShare: isScreenShare, _isScreenShare: isScreenShare,
_isFakeParticipant, _isFakeParticipant,
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator, _renderModeratorIndicator: renderModeratorIndicator,
_participantId: participantId, _participantId: participantId,
_videoMuted: videoMuted renderDisplayName
} = this.props; } = this.props;
const indicators = []; const indicators = [];
if (renderModeratorIndicator) {
indicators.push(<View
key = 'moderator-indicator'
style = { styles.moderatorIndicatorContainer }>
<ModeratorIndicator />
</View>);
}
if (!_isFakeParticipant) { if (!_isFakeParticipant) {
indicators.push(<View indicators.push(<View
key = 'top-left-indicators' key = 'top-left-indicators'
@ -229,23 +213,22 @@ class Thumbnail extends PureComponent<Props> {
styles.thumbnailTopIndicatorContainer, styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopLeftIndicatorContainer styles.thumbnailTopLeftIndicatorContainer
] }> ] }>
<RaisedHandIndicator participantId = { participantId } />
{ renderDominantSpeakerIndicator && <DominantSpeakerIndicator /> }
</View>);
indicators.push(<View
key = 'top-right-indicators'
style = { [
styles.thumbnailTopIndicatorContainer,
styles.thumbnailTopRightIndicatorContainer
] }>
<ConnectionIndicator participantId = { participantId } /> <ConnectionIndicator participantId = { participantId } />
<RaisedHandIndicator participantId = { participantId } />
{isScreenShare && (
<View style = { styles.indicatorContainer }>
<ScreenShareIndicator />
</View>
)}
</View>); </View>);
indicators.push(<Container indicators.push(<Container
key = 'bottom-indicators' key = 'bottom-indicators'
style = { styles.thumbnailIndicatorContainer }> style = { styles.thumbnailIndicatorContainer }>
{ audioMuted && <AudioMutedIndicator /> } { audioMuted && <AudioMutedIndicator /> }
{ videoMuted && <VideoMutedIndicator /> } { renderModeratorIndicator && <ModeratorIndicator />}
{ isScreenShare && <ScreenShareIndicator /> } {renderDisplayName && <DisplayNameLabel
contained = { true }
participantId = { participantId } />}
</Container>); </Container>);
} }
@ -266,10 +249,10 @@ class Thumbnail extends PureComponent<Props> {
_participantInLargeVideo: participantInLargeVideo, _participantInLargeVideo: participantInLargeVideo,
_pinned, _pinned,
_raisedHand, _raisedHand,
_renderDominantSpeakerIndicator,
_styles, _styles,
disableTint, disableTint,
height, height,
renderDisplayName,
tileView tileView
} = this.props; } = this.props;
const styleOverrides = tileView ? { const styleOverrides = tileView ? {
@ -289,23 +272,17 @@ class Thumbnail extends PureComponent<Props> {
styles.thumbnail, styles.thumbnail,
_pinned && !tileView ? _styles.thumbnailPinned : null, _pinned && !tileView ? _styles.thumbnailPinned : null,
styleOverrides, styleOverrides,
_raisedHand ? styles.thumbnailRaisedHand : null _raisedHand ? styles.thumbnailRaisedHand : null,
_renderDominantSpeakerIndicator ? styles.thumbnailDominantSpeaker : null
] } ] }
touchFeedback = { false }> touchFeedback = { false }>
<ParticipantView <ParticipantView
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE } avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
disableVideo = { isScreenShare || _isFakeParticipant } disableVideo = { isScreenShare || _isFakeParticipant }
participantId = { participantId } participantId = { participantId }
style = { _styles.participantViewStyle }
tintEnabled = { participantInLargeVideo && !disableTint } tintEnabled = { participantInLargeVideo && !disableTint }
tintStyle = { _styles.activeThumbnailTint } tintStyle = { _styles.activeThumbnailTint }
zOrder = { 1 } /> zOrder = { 1 } />
{
renderDisplayName
&& <Container style = { styles.displayNameContainer }>
<DisplayNameLabel participantId = { participantId } />
</Container>
}
{ {
this._renderIndicators() this._renderIndicators()
} }
@ -336,7 +313,6 @@ function _mapStateToProps(state, ownProps) {
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id); = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
const videoTrack const videoTrack
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id); = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
const videoMuted = videoTrack?.muted ?? true;
const isScreenShare = videoTrack?.videoType === VIDEO_TYPE.DESKTOP; const isScreenShare = videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
const participantCount = getParticipantCount(state); const participantCount = getParticipantCount(state);
const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2; const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
@ -357,8 +333,7 @@ function _mapStateToProps(state, ownProps) {
_raisedHand: hasRaisedHand(participant), _raisedHand: hasRaisedHand(participant),
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator, _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator, _renderModeratorIndicator: renderModeratorIndicator,
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'), _styles: ColorSchemeRegistry.get(state, 'Thumbnail')
_videoMuted: videoMuted
}; };
} }

View File

@ -1,24 +0,0 @@
// @flow
import React, { Component } from 'react';
import { IconCameraDisabled } from '../../../base/icons';
import { BaseIndicator } from '../../../base/react';
/**
* Thumbnail badge for displaying the video mute status of a participant.
*/
export default class VideoMutedIndicator extends Component<{}> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
return (
<BaseIndicator
highlight = { false }
icon = { IconCameraDisabled } />
);
}
}

View File

@ -1,7 +1,6 @@
// @flow // @flow
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme'; import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { ColorPalette } from '../../../base/styles';
import BaseTheme from '../../../base/ui/components/BaseTheme.native'; import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import { SMALL_THUMBNAIL_SIZE } from '../../constants'; import { SMALL_THUMBNAIL_SIZE } from '../../constants';
@ -10,6 +9,13 @@ import { SMALL_THUMBNAIL_SIZE } from '../../constants';
*/ */
export const AVATAR_SIZE = 50; export const AVATAR_SIZE = 50;
const indicatorContainer = {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderRadius: 4,
margin: 2,
padding: 2
};
/** /**
* The styles of the feature filmstrip. * The styles of the feature filmstrip.
*/ */
@ -28,11 +34,7 @@ export default {
* The display name container. * The display name container.
*/ */
displayNameContainer: { displayNameContainer: {
alignSelf: 'center', padding: 2
bottom: 0,
flex: 1,
margin: 4,
position: 'absolute'
}, },
/** /**
@ -96,21 +98,15 @@ export default {
flexDirection: 'row' flexDirection: 'row'
}, },
moderatorIndicatorContainer: {
bottom: 4,
position: 'absolute',
right: 4
},
/** /**
* The style of a participant's Thumbnail which renders either the video or * The style of a participant's Thumbnail which renders either the video or
* the avatar of the associated participant. * the avatar of the associated participant.
*/ */
thumbnail: { thumbnail: {
alignItems: 'stretch', alignItems: 'stretch',
backgroundColor: ColorPalette.appBackground, backgroundColor: BaseTheme.palette.ui02,
borderColor: '#424242', borderColor: '#424242',
borderRadius: 3, borderRadius: 4,
borderStyle: 'solid', borderStyle: 'solid',
borderWidth: 1, borderWidth: 1,
flex: 1, flex: 1,
@ -124,35 +120,51 @@ export default {
width: SMALL_THUMBNAIL_SIZE width: SMALL_THUMBNAIL_SIZE
}, },
indicatorContainer,
/** /**
* The thumbnails indicator container. * The thumbnails indicator container.
*/ */
thumbnailIndicatorContainer: { thumbnailIndicatorContainer: {
alignSelf: 'stretch', alignSelf: 'center',
bottom: 4, bottom: 4,
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
left: 4, left: 4,
position: 'absolute' position: 'absolute',
maxWidth: '95%',
overflow: 'hidden',
...indicatorContainer
}, },
thumbnailTopIndicatorContainer: { thumbnailTopIndicatorContainer: {
padding: 4, padding: 4,
position: 'absolute', position: 'absolute',
top: 0 top: 0,
flexDirection: 'row'
}, },
thumbnailTopLeftIndicatorContainer: { thumbnailTopLeftIndicatorContainer: {
left: 0 left: 0
}, },
thumbnailTopRightIndicatorContainer: { raisedHandIndicator: {
right: 0 ...indicatorContainer,
backgroundColor: BaseTheme.palette.warning02
},
raisedHandIcon: {
color: BaseTheme.palette.uiBackground
}, },
thumbnailRaisedHand: { thumbnailRaisedHand: {
borderWidth: 2, borderWidth: 2,
borderColor: BaseTheme.palette.warning02 borderColor: BaseTheme.palette.warning02
},
thumbnailDominantSpeaker: {
borderWidth: 4,
borderColor: BaseTheme.palette.action01Hover
} }
}; };
@ -168,13 +180,6 @@ ColorSchemeRegistry.register('Thumbnail', {
backgroundColor: schemeColor('activeParticipantTint') backgroundColor: schemeColor('activeParticipantTint')
}, },
/**
* Coloring if the thumbnail background.
*/
participantViewStyle: {
backgroundColor: schemeColor('background')
},
/** /**
* Pinned video thumbnail style. * Pinned video thumbnail style.
*/ */