Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit c47a05c

Browse files
authored
Merge pull request #6121 from SimonBrandner/feature/call-event-tile
Add VoIP event tiles
2 parents d8c4ab5 + 9e5b149 commit c47a05c

File tree

16 files changed

+803
-123
lines changed

16 files changed

+803
-123
lines changed

res/css/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
@import "./views/messages/_CreateEvent.scss";
163163
@import "./views/messages/_DateSeparator.scss";
164164
@import "./views/messages/_EventTileBubble.scss";
165+
@import "./views/messages/_CallEvent.scss";
165166
@import "./views/messages/_MEmoteBody.scss";
166167
@import "./views/messages/_MFileBody.scss";
167168
@import "./views/messages/_MImageBody.scss";

res/css/views/elements/_InfoTooltip.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,12 @@ limitations under the License.
3030
mask-position: center;
3131
content: '';
3232
vertical-align: middle;
33+
}
34+
35+
.mx_InfoTooltip_icon_info::before {
3336
mask-image: url('$(res)/img/element-icons/info.svg');
3437
}
38+
39+
.mx_InfoTooltip_icon_warning::before {
40+
mask-image: url('$(res)/img/element-icons/warning.svg');
41+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
Copyright 2021 Šimon Brandner <[email protected]>
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
.mx_CallEvent {
18+
display: flex;
19+
flex-direction: row;
20+
align-items: center;
21+
justify-content: space-between;
22+
23+
background-color: $dark-panel-bg-color;
24+
border-radius: 8px;
25+
margin: 10px auto;
26+
max-width: 75%;
27+
box-sizing: border-box;
28+
height: 60px;
29+
30+
&.mx_CallEvent_voice {
31+
.mx_CallEvent_type_icon::before,
32+
.mx_CallEvent_content_button_callBack span::before,
33+
.mx_CallEvent_content_button_answer span::before {
34+
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
35+
}
36+
}
37+
38+
&.mx_CallEvent_video {
39+
.mx_CallEvent_type_icon::before,
40+
.mx_CallEvent_content_button_callBack span::before,
41+
.mx_CallEvent_content_button_answer span::before {
42+
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
43+
}
44+
}
45+
46+
.mx_CallEvent_info {
47+
display: flex;
48+
flex-direction: row;
49+
align-items: center;
50+
margin-left: 12px;
51+
52+
.mx_CallEvent_info_basic {
53+
display: flex;
54+
flex-direction: column;
55+
margin-left: 10px; // To match mx_CallEvent
56+
57+
.mx_CallEvent_sender {
58+
font-weight: 600;
59+
font-size: 1.5rem;
60+
line-height: 1.8rem;
61+
margin-bottom: 3px;
62+
}
63+
64+
.mx_CallEvent_type {
65+
font-weight: 400;
66+
color: $secondary-fg-color;
67+
font-size: 1.2rem;
68+
line-height: $font-13px;
69+
display: flex;
70+
align-items: center;
71+
72+
.mx_CallEvent_type_icon {
73+
height: 13px;
74+
width: 13px;
75+
margin-right: 5px;
76+
77+
&::before {
78+
content: '';
79+
position: absolute;
80+
height: 13px;
81+
width: 13px;
82+
background-color: $tertiary-fg-color;
83+
mask-repeat: no-repeat;
84+
mask-size: contain;
85+
}
86+
}
87+
}
88+
}
89+
}
90+
91+
.mx_CallEvent_content {
92+
display: flex;
93+
flex-direction: row;
94+
align-items: center;
95+
color: $secondary-fg-color;
96+
margin-right: 16px;
97+
98+
.mx_CallEvent_content_button {
99+
height: 24px;
100+
padding: 0px 12px;
101+
margin-left: 8px;
102+
103+
span {
104+
padding: 8px 0;
105+
display: flex;
106+
align-items: center;
107+
108+
&::before {
109+
content: '';
110+
display: inline-block;
111+
background-color: $button-fg-color;
112+
mask-position: center;
113+
mask-repeat: no-repeat;
114+
mask-size: 16px;
115+
width: 16px;
116+
height: 16px;
117+
margin-right: 8px;
118+
}
119+
}
120+
}
121+
122+
.mx_CallEvent_content_button_reject span::before {
123+
mask-image: url('$(res)/img/element-icons/call/hangup.svg');
124+
}
125+
126+
.mx_CallEvent_content_tooltip {
127+
margin-right: 5px;
128+
}
129+
130+
.mx_CallEvent_iconButton {
131+
display: inline-flex;
132+
margin-right: 8px;
133+
134+
&::before {
135+
content: '';
136+
137+
height: 16px;
138+
width: 16px;
139+
background-color: $tertiary-fg-color;
140+
mask-repeat: no-repeat;
141+
mask-size: contain;
142+
mask-position: center;
143+
}
144+
}
145+
146+
.mx_CallEvent_silence::before {
147+
mask-image: url('$(res)/img/voip/silence.svg');
148+
}
149+
150+
.mx_CallEvent_unSilence::before {
151+
mask-image: url('$(res)/img/voip/un-silence.svg');
152+
}
153+
}
154+
}

res/css/views/rooms/_EventTile.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,13 @@ $hover-select-border: 4px;
333333
.mx_EventTile_msgOption {
334334
grid-column: 2;
335335
}
336+
337+
&:hover {
338+
.mx_EventTile_line {
339+
// To avoid bubble events being highlighted
340+
background-color: inherit !important;
341+
}
342+
}
336343
}
337344

338345
.mx_EventTile_readAvatars {

res/img/element-icons/warning.svg

Lines changed: 3 additions & 0 deletions
Loading

src/CallHandler.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const CHECK_PROTOCOLS_ATTEMPTS = 3;
9999
// (and store the ID of their native room)
100100
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
101101

102-
export enum AudioID {
102+
enum AudioID {
103103
Ring = 'ringAudio',
104104
Ringback = 'ringbackAudio',
105105
CallEnd = 'callendAudio',
@@ -142,6 +142,7 @@ export enum PlaceCallType {
142142
export enum CallHandlerEvent {
143143
CallsChanged = "calls_changed",
144144
CallChangeRoom = "call_change_room",
145+
SilencedCallsChanged = "silenced_calls_changed",
145146
}
146147

147148
export default class CallHandler extends EventEmitter {
@@ -164,6 +165,8 @@ export default class CallHandler extends EventEmitter {
164165
// do the async lookup when we get new information and then store these mappings here
165166
private assertedIdentityNativeUsers = new Map<string, string>();
166167

168+
private silencedCalls = new Set<string>(); // callIds
169+
167170
static sharedInstance() {
168171
if (!window.mxCallHandler) {
169172
window.mxCallHandler = new CallHandler();
@@ -224,6 +227,33 @@ export default class CallHandler extends EventEmitter {
224227
}
225228
}
226229

230+
public silenceCall(callId: string) {
231+
this.silencedCalls.add(callId);
232+
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
233+
234+
// Don't pause audio if we have calls which are still ringing
235+
if (this.areAnyCallsUnsilenced()) return;
236+
this.pause(AudioID.Ring);
237+
}
238+
239+
public unSilenceCall(callId: string) {
240+
this.silencedCalls.delete(callId);
241+
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
242+
this.play(AudioID.Ring);
243+
}
244+
245+
public isCallSilenced(callId: string): boolean {
246+
return this.silencedCalls.has(callId);
247+
}
248+
249+
/**
250+
* Returns true if there is at least one unsilenced call
251+
* @returns {boolean}
252+
*/
253+
private areAnyCallsUnsilenced(): boolean {
254+
return this.calls.size > this.silencedCalls.size;
255+
}
256+
227257
private async checkProtocols(maxTries) {
228258
try {
229259
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
@@ -301,6 +331,13 @@ export default class CallHandler extends EventEmitter {
301331
}, true);
302332
};
303333

334+
public getCallById(callId: string): MatrixCall {
335+
for (const call of this.calls.values()) {
336+
if (call.callId === callId) return call;
337+
}
338+
return null;
339+
}
340+
304341
getCallForRoom(roomId: string): MatrixCall {
305342
return this.calls.get(roomId) || null;
306343
}
@@ -441,6 +478,10 @@ export default class CallHandler extends EventEmitter {
441478
break;
442479
}
443480

481+
if (newState !== CallState.Ringing) {
482+
this.silencedCalls.delete(call.callId);
483+
}
484+
444485
switch (newState) {
445486
case CallState.Ringing:
446487
this.play(AudioID.Ring);

src/TextForEvent.tsx

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616
import React from 'react';
17-
import { MatrixClientPeg } from './MatrixClientPeg';
1817
import { _t } from './languageHandler';
1918
import * as Roles from './Roles';
2019
import { isValid3pidInvite } from "./RoomInvite";
@@ -318,90 +317,6 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
318317
});
319318
}
320319

321-
function textForCallAnswerEvent(event: MatrixEvent): () => string | null {
322-
return () => {
323-
const senderName = event.sender ? event.sender.name : _t('Someone');
324-
const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)');
325-
return _t('%(senderName)s answered the call.', { senderName }) + ' ' + supported;
326-
};
327-
}
328-
329-
function textForCallHangupEvent(event: MatrixEvent): () => string | null {
330-
const getSenderName = () => event.sender ? event.sender.name : _t('Someone');
331-
const eventContent = event.getContent();
332-
let getReason = () => "";
333-
if (!MatrixClientPeg.get().supportsVoip()) {
334-
getReason = () => _t('(not supported by this browser)');
335-
} else if (eventContent.reason) {
336-
if (eventContent.reason === "ice_failed") {
337-
// We couldn't establish a connection at all
338-
getReason = () => _t('(could not connect media)');
339-
} else if (eventContent.reason === "ice_timeout") {
340-
// We established a connection but it died
341-
getReason = () => _t('(connection failed)');
342-
} else if (eventContent.reason === "user_media_failed") {
343-
// The other side couldn't open capture devices
344-
getReason = () => _t("(their device couldn't start the camera / microphone)");
345-
} else if (eventContent.reason === "unknown_error") {
346-
// An error code the other side doesn't have a way to express
347-
// (as opposed to an error code they gave but we don't know about,
348-
// in which case we show the error code)
349-
getReason = () => _t("(an error occurred)");
350-
} else if (eventContent.reason === "invite_timeout") {
351-
getReason = () => _t('(no answer)');
352-
} else if (eventContent.reason === "user hangup" || eventContent.reason === "user_hangup") {
353-
// workaround for https://github.com/vector-im/element-web/issues/5178
354-
// it seems Android randomly sets a reason of "user hangup" which is
355-
// interpreted as an error code :(
356-
// https://github.com/vector-im/riot-android/issues/2623
357-
// Also the correct hangup code as of VoIP v1 (with underscore)
358-
getReason = () => '';
359-
} else {
360-
getReason = () => _t('(unknown failure: %(reason)s)', { reason: eventContent.reason });
361-
}
362-
}
363-
return () => _t('%(senderName)s ended the call.', { senderName: getSenderName() }) + ' ' + getReason();
364-
}
365-
366-
function textForCallRejectEvent(event: MatrixEvent): () => string | null {
367-
return () => {
368-
const senderName = event.sender ? event.sender.name : _t('Someone');
369-
return _t('%(senderName)s declined the call.', { senderName });
370-
};
371-
}
372-
373-
function textForCallInviteEvent(event: MatrixEvent): () => string | null {
374-
const getSenderName = () => event.sender ? event.sender.name : _t('Someone');
375-
// FIXME: Find a better way to determine this from the event?
376-
let isVoice = true;
377-
if (event.getContent().offer && event.getContent().offer.sdp &&
378-
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
379-
isVoice = false;
380-
}
381-
const isSupported = MatrixClientPeg.get().supportsVoip();
382-
383-
// This ladder could be reduced down to a couple string variables, however other languages
384-
// can have a hard time translating those strings. In an effort to make translations easier
385-
// and more accurate, we break out the string-based variables to a couple booleans.
386-
if (isVoice && isSupported) {
387-
return () => _t("%(senderName)s placed a voice call.", {
388-
senderName: getSenderName(),
389-
});
390-
} else if (isVoice && !isSupported) {
391-
return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", {
392-
senderName: getSenderName(),
393-
});
394-
} else if (!isVoice && isSupported) {
395-
return () => _t("%(senderName)s placed a video call.", {
396-
senderName: getSenderName(),
397-
});
398-
} else if (!isVoice && !isSupported) {
399-
return () => _t("%(senderName)s placed a video call. (not supported by this browser)", {
400-
senderName: getSenderName(),
401-
});
402-
}
403-
}
404-
405320
function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
406321
const senderName = event.sender ? event.sender.name : event.getSender();
407322

@@ -652,10 +567,6 @@ interface IHandlers {
652567

653568
const handlers: IHandlers = {
654569
'm.room.message': textForMessageEvent,
655-
'm.call.invite': textForCallInviteEvent,
656-
'm.call.answer': textForCallAnswerEvent,
657-
'm.call.hangup': textForCallHangupEvent,
658-
'm.call.reject': textForCallRejectEvent,
659570
};
660571

661572
const stateHandlers: IHandlers = {

0 commit comments

Comments
 (0)