Group Calling: blocking participants

Co-authored-by: Evan Hahn <evanhahn@signal.org>
This commit is contained in:
Josh Perez 2020-12-01 20:30:25 -05:00 committed by GitHub
parent f3f2cb2b5e
commit 81cc8a1211
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 146 additions and 20 deletions

View file

@ -1031,7 +1031,7 @@
"description": "Label for back button in a conversation" "description": "Label for back button in a conversation"
}, },
"moreInfo": { "moreInfo": {
"message": "More Info...", "message": "More Info",
"description": "Shown on the drop-down menu for an individual message, takes you to message detail screen" "description": "Shown on the drop-down menu for an individual message, takes you to message detail screen"
}, },
"retrySend": { "retrySend": {
@ -1280,6 +1280,20 @@
} }
} }
}, },
"calling__you-have-blocked": {
"message": "You have blocked $name$",
"description": "when you block someone and cannot view their video",
"placeholders": {
"name": {
"content": "$1",
"example": "Henry Richard"
}
}
},
"calling__block-info": {
"message": "You won't receive their audio or video and they won't receive yours.",
"description": "Shown in the modal dialog to describe how blocking works in a gorup call"
},
"alwaysRelayCallsDescription": { "alwaysRelayCallsDescription": {
"message": "Always relay calls", "message": "Always relay calls",
"description": "Description of the always relay calls setting" "description": "Description of the always relay calls setting"

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>block-24</title><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,1.5a9.448,9.448,0,0,1,6.159,2.281L4.781,18.159A9.488,9.488,0,0,1,12,2.5Zm0,19a9.448,9.448,0,0,1-6.159-2.281L19.219,5.841A9.488,9.488,0,0,1,12,21.5Z"/></svg>

After

Width:  |  Height:  |  Size: 315 B

View file

@ -6301,6 +6301,30 @@ button.module-image__border-overlay:focus {
background-color: $color-gray-75; background-color: $color-gray-75;
} }
&__blocked {
@include color-svg('../images/icons/v2/block-24.svg', $color-white);
height: 24px;
margin-bottom: 16px;
width: 24px;
&--info {
@include button-reset;
background-color: $color-gray-75;
border-radius: 16px;
color: $color-white;
line-height: 16px;
padding: 3px 10px;
}
&--modal-title {
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
}
}
&--title { &--title {
@include font-caption; @include font-caption;
background: linear-gradient( background: linear-gradient(

View file

@ -191,6 +191,7 @@ story.add('Group call - 1', () => (
demuxId: 0, demuxId: 0,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isBlocked: false,
isSelf: false, isSelf: false,
title: 'Tyler', title: 'Tyler',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -209,6 +210,7 @@ story.add('Group call - Many', () => (
demuxId: 0, demuxId: 0,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isBlocked: false,
isSelf: false, isSelf: false,
title: 'Amy', title: 'Amy',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -217,6 +219,7 @@ story.add('Group call - Many', () => (
demuxId: 1, demuxId: 1,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isBlocked: false,
isSelf: true, isSelf: true,
title: 'Bob', title: 'Bob',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -225,6 +228,7 @@ story.add('Group call - Many', () => (
demuxId: 2, demuxId: 2,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isBlocked: true,
isSelf: false, isSelf: false,
title: 'Alice', title: 'Alice',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,
@ -246,6 +250,7 @@ story.add('Group call - reconnecting', () => (
demuxId: 0, demuxId: 0,
hasRemoteAudio: true, hasRemoteAudio: true,
hasRemoteVideo: true, hasRemoteVideo: true,
isBlocked: false,
isSelf: false, isSelf: false,
title: 'Tyler', title: 'Tyler',
videoAspectRatio: 1.3, videoAspectRatio: 1.3,

View file

@ -23,6 +23,7 @@ function createParticipant(
demuxId: 2, demuxId: 2,
hasRemoteAudio: Boolean(participantProps.hasRemoteAudio), hasRemoteAudio: Boolean(participantProps.hasRemoteAudio),
hasRemoteVideo: Boolean(participantProps.hasRemoteVideo), hasRemoteVideo: Boolean(participantProps.hasRemoteVideo),
isBlocked: Boolean(participantProps.isBlocked),
isSelf: Boolean(participantProps.isSelf), isSelf: Boolean(participantProps.isSelf),
name: participantProps.name, name: participantProps.name,
profileName: participantProps.title, profileName: participantProps.title,

View file

@ -15,7 +15,10 @@ import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const createProps = (overrideProps: Partial<PropsType> = {}): any => ({ const createProps = (
overrideProps: Partial<PropsType> = {},
isBlocked?: boolean
): any => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
getGroupCallVideoFrameSource: noop as any, getGroupCallVideoFrameSource: noop as any,
i18n, i18n,
@ -23,6 +26,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): any => ({
demuxId: 123, demuxId: 123,
hasRemoteAudio: false, hasRemoteAudio: false,
hasRemoteVideo: true, hasRemoteVideo: true,
isBlocked: Boolean(isBlocked),
isSelf: false, isSelf: false,
title: title:
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso', 'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
@ -52,3 +56,18 @@ story.add('isInPip', () => (
})} })}
/> />
)); ));
story.add('Blocked', () => (
<GroupCallRemoteParticipant
{...createProps(
{
isInPip: false,
height: 120,
left: 0,
top: 0,
width: 120,
},
true
)}
/>
));

View file

@ -18,6 +18,8 @@ import {
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { CallBackgroundBlur } from './CallBackgroundBlur'; import { CallBackgroundBlur } from './CallBackgroundBlur';
import { Avatar, AvatarSize } from './Avatar'; import { Avatar, AvatarSize } from './Avatar';
import { ConfirmationModal } from './ConfirmationModal';
import { Intl } from './Intl';
import { ContactName } from './conversation/ContactName'; import { ContactName } from './conversation/ContactName';
// The max size video frame we'll support (in RGBA) // The max size video frame we'll support (in RGBA)
@ -45,20 +47,22 @@ export type PropsType = BasePropsType & (InPipPropsType | NotInPipPropsType);
export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo( export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
props => { props => {
const { getGroupCallVideoFrameSource } = props; const { getGroupCallVideoFrameSource, i18n } = props;
const { const {
avatarPath, avatarPath,
color, color,
profileName,
title,
demuxId, demuxId,
hasRemoteAudio, hasRemoteAudio,
hasRemoteVideo, hasRemoteVideo,
isBlocked,
profileName,
title,
} = props.remoteParticipant; } = props.remoteParticipant;
const [isWide, setIsWide] = useState(true); const [isWide, setIsWide] = useState(true);
const [hasHover, setHover] = useState(false); const [hasHover, setHover] = useState(false);
const [showBlockInfo, setShowBlockInfo] = useState(false);
const remoteVideoRef = useRef<HTMLCanvasElement | null>(null); const remoteVideoRef = useRef<HTMLCanvasElement | null>(null);
const canvasContextRef = useRef<CanvasRenderingContext2D | null>(null); const canvasContextRef = useRef<CanvasRenderingContext2D | null>(null);
@ -173,6 +177,45 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
} }
const showHover = hasHover && !props.isInPip; const showHover = hasHover && !props.isInPip;
const canShowVideo = hasRemoteVideo && !isBlocked;
if (showBlockInfo) {
return (
<ConfirmationModal
i18n={i18n}
onClose={() => {
setShowBlockInfo(false);
}}
title={
<div className="module-ongoing-call__group-call-remote-participant__blocked--modal-title">
<Intl
i18n={i18n}
id="calling__you-have-blocked"
components={[
<ContactName
key="name"
profileName={profileName}
title={title}
i18n={i18n}
/>,
]}
/>
</div>
}
actions={[
{
text: i18n('ok'),
action: () => {
setShowBlockInfo(false);
},
style: 'affirmative',
},
]}
>
{i18n('calling__block-info')}
</ConfirmationModal>
);
}
return ( return (
<div <div
@ -194,11 +237,11 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
module="module-ongoing-call__group-call-remote-participant--contact-name" module="module-ongoing-call__group-call-remote-participant--contact-name"
profileName={profileName} profileName={profileName}
title={title} title={title}
i18n={props.i18n} i18n={i18n}
/> />
</div> </div>
)} )}
{hasRemoteVideo ? ( {canShowVideo ? (
<canvas <canvas
className="module-ongoing-call__group-call-remote-participant__remote-video" className="module-ongoing-call__group-call-remote-participant__remote-video"
style={canvasStyles} style={canvasStyles}
@ -217,16 +260,31 @@ export const GroupCallRemoteParticipant: React.FC<PropsType> = React.memo(
/> />
) : ( ) : (
<CallBackgroundBlur avatarPath={avatarPath} color={color}> <CallBackgroundBlur avatarPath={avatarPath} color={color}>
<Avatar {isBlocked ? (
avatarPath={avatarPath} <>
color={color || 'ultramarine'} <i className="module-ongoing-call__group-call-remote-participant__blocked" />
noteToSelf={false} <button
conversationType="direct" type="button"
i18n={props.i18n} className="module-ongoing-call__group-call-remote-participant__blocked--info"
profileName={profileName} onClick={() => {
title={title} setShowBlockInfo(true);
size={avatarSize} }}
/> >
{i18n('moreInfo')}
</button>
</>
) : (
<Avatar
avatarPath={avatarPath}
color={color || 'ultramarine'}
noteToSelf={false}
conversationType="direct"
i18n={i18n}
profileName={profileName}
title={title}
size={avatarSize}
/>
)}
</CallBackgroundBlur> </CallBackgroundBlur>
)} )}
</div> </div>

View file

@ -99,6 +99,7 @@ const mapStateToActiveCallProp = (state: StateType) => {
firstName: remoteConversation.firstName, firstName: remoteConversation.firstName,
hasRemoteAudio: remoteParticipant.hasRemoteAudio, hasRemoteAudio: remoteParticipant.hasRemoteAudio,
hasRemoteVideo: remoteParticipant.hasRemoteVideo, hasRemoteVideo: remoteParticipant.hasRemoteVideo,
isBlocked: Boolean(remoteConversation.isBlocked),
isSelf: remoteParticipant.isSelf, isSelf: remoteParticipant.isSelf,
name: remoteConversation.name, name: remoteConversation.name,
profileName: remoteConversation.profileName, profileName: remoteConversation.profileName,
@ -108,6 +109,8 @@ const mapStateToActiveCallProp = (state: StateType) => {
}); });
} }
); );
groupCallParticipants.sort((a, b) => a.title.localeCompare(b.title));
} }
return { return {

View file

@ -75,6 +75,7 @@ export interface GroupCallRemoteParticipantType {
firstName?: string; firstName?: string;
hasRemoteAudio: boolean; hasRemoteAudio: boolean;
hasRemoteVideo: boolean; hasRemoteVideo: boolean;
isBlocked: boolean;
isSelf: boolean; isSelf: boolean;
name?: string; name?: string;
profileName?: string; profileName?: string;

View file

@ -14562,7 +14562,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js", "path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const remoteVideoRef = react_1.useRef(null);", "line": " const remoteVideoRef = react_1.useRef(null);",
"lineNumber": 28, "lineNumber": 31,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-11-11T21:56:04.179Z", "updated": "2020-11-11T21:56:04.179Z",
"reasonDetail": "Needed to render the remote video element." "reasonDetail": "Needed to render the remote video element."
@ -14571,7 +14571,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js", "path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const canvasContextRef = react_1.useRef(null);", "line": " const canvasContextRef = react_1.useRef(null);",
"lineNumber": 29, "lineNumber": 32,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-11-17T23:29:38.698Z", "updated": "2020-11-17T23:29:38.698Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."
@ -14580,7 +14580,7 @@
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/GroupCallRemoteParticipant.js", "path": "ts/components/GroupCallRemoteParticipant.js",
"line": " const frameBufferRef = react_1.useRef(new ArrayBuffer(FRAME_BUFFER_SIZE));", "line": " const frameBufferRef = react_1.useRef(new ArrayBuffer(FRAME_BUFFER_SIZE));",
"lineNumber": 30, "lineNumber": 33,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2020-11-17T16:24:25.480Z", "updated": "2020-11-17T16:24:25.480Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."