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

Add VoIP event tiles #6121

Merged
merged 83 commits into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
73c66c3
Add basic CallEvent
SimonBrandner May 29, 2021
eaa3645
Hook up CallEvent
SimonBrandner May 29, 2021
cd67d50
Add basic CallEvent styling
SimonBrandner May 29, 2021
3ac63b0
Use styling for CallEvent
SimonBrandner May 29, 2021
320ceb5
Add POC TimelineCallEventStore
SimonBrandner May 30, 2021
4ae92d8
Hook up TimelineCallEventStore and add Avatar
SimonBrandner May 30, 2021
31d16d4
Fix ignoring events
SimonBrandner May 30, 2021
8dc0e2a
Add CallEventGrouper as a replacement for TimeLineCallEventStore
SimonBrandner May 30, 2021
85bcf8e
Hook up CallEventGrouper
SimonBrandner May 30, 2021
5e8df03
Fix styling a bit
SimonBrandner May 30, 2021
f94230c
Fix css
SimonBrandner May 30, 2021
20c5735
Add getCallById()
SimonBrandner May 30, 2021
d05b179
Add callId
SimonBrandner May 30, 2021
5e4a10a
Reorganize HTML
SimonBrandner Jun 1, 2021
8eb24d0
Rename callState to timelineCallState
SimonBrandner Jun 1, 2021
dac741d
Another rewrite
SimonBrandner Jun 1, 2021
30365ca
Allow picking up calls from the timeline
SimonBrandner Jun 1, 2021
86402e9
Add some styling
SimonBrandner Jun 1, 2021
6b72c13
Add some call states
SimonBrandner Jun 1, 2021
f96e25d
Simply use call states
SimonBrandner Jun 1, 2021
c1fcadb
Manage some more call states
SimonBrandner Jun 1, 2021
8c67b96
Save all events
SimonBrandner Jun 1, 2021
67a052e
Reorganize things and do some fixes
SimonBrandner Jun 1, 2021
79ec655
Fix translations
SimonBrandner Jun 1, 2021
5b3967a
Add handling for invite
SimonBrandner Jun 1, 2021
0785997
Handle missed calls
SimonBrandner Jun 1, 2021
79f51ad
Delete old call tile handlers that are replaced by CallEventGrouper a…
SimonBrandner Jun 1, 2021
527723c
Remove unused import
SimonBrandner Jun 1, 2021
91288ab
i18n
SimonBrandner Jun 1, 2021
14605e4
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jun 1, 2021
9b904cd
Remove empty line
SimonBrandner Jun 1, 2021
f1e780e
Improved missed calls
SimonBrandner Jun 1, 2021
795dfa7
Allow custom classes
SimonBrandner Jun 1, 2021
3a0b6eb
Add a warning icon
SimonBrandner Jun 1, 2021
2a22f03
Support InfoTooltip kinds
SimonBrandner Jun 1, 2021
70a5715
Support hangup reasons
SimonBrandner Jun 1, 2021
c03f0fb
i18n
SimonBrandner Jun 1, 2021
9db280b
Listen for CallsChanged
SimonBrandner Jun 1, 2021
6b9e204
Use a Set instead of an Array
SimonBrandner Jun 1, 2021
3bf28e3
Remove Ended from SUPPORTED_STATES
SimonBrandner Jun 1, 2021
521b244
Refactoring and fixes
SimonBrandner Jun 2, 2021
78229a2
Support user busy
SimonBrandner Jun 2, 2021
b202f99
Refactor setState()
SimonBrandner Jun 2, 2021
3331e7b
Use enums where possible
SimonBrandner Jun 2, 2021
466a52e
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jun 2, 2021
37c379c
Merge branch 'develop' into feature/call-event-tile
SimonBrandner Jun 2, 2021
e0572ac
Write tests for CallEventGrouper
SimonBrandner Jun 2, 2021
1c92e31
Add missing license header
SimonBrandner Jun 2, 2021
ae54a8f
Return null
SimonBrandner Jun 4, 2021
3b2d6d4
Translate unknown call state
SimonBrandner Jun 4, 2021
6eb33ac
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jun 4, 2021
8c533c7
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jun 4, 2021
fdda534
Merge branch 'develop' into feature/call-event-tile
SimonBrandner Jun 9, 2021
22567a1
i18n
SimonBrandner Jun 9, 2021
949532c
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jun 17, 2021
7b6c3ae
Change some styling to better match the designs
SimonBrandner Jun 17, 2021
02e6559
Set text color to secondary-fg-color
SimonBrandner Jun 17, 2021
512c054
Add call type icon
SimonBrandner Jun 17, 2021
a781d6f
Adjust padding and line-height a bit
SimonBrandner Jun 17, 2021
9b61953
Improve padding
SimonBrandner Jun 18, 2021
62de75a
Increase height
SimonBrandner Jun 18, 2021
707ecd8
Don't highlight bubble events
SimonBrandner Jun 18, 2021
f96c366
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jun 18, 2021
ccfc7fe
Make call silencing more flexible
SimonBrandner Jun 19, 2021
401fe1d
Add call silencing to CallEvent
SimonBrandner Jun 19, 2021
c4e4dad
Migrate from FormButton to AccessibleButton
SimonBrandner Jun 21, 2021
202cb0f
Fix styling of buttons
SimonBrandner Jun 21, 2021
b014763
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jul 2, 2021
85399e8
Match code style
SimonBrandner Jul 2, 2021
9383ecc
Delint
SimonBrandner Jul 2, 2021
297116a
MORE DELINT!
SimonBrandner Jul 2, 2021
9c67679
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jul 8, 2021
6f1fc3f
Fix call button spacing issues
SimonBrandner Jul 8, 2021
9ec3d93
Better handling of call types
SimonBrandner Jul 8, 2021
2615ea7
Add icons to buttons
SimonBrandner Jul 8, 2021
722c360
Use the correct color for silence button
SimonBrandner Jul 8, 2021
8f0d723
Rework call silencing once again
SimonBrandner Jul 8, 2021
16ed5c6
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jul 16, 2021
313964e
Fix call event tile not behaving
SimonBrandner Jul 17, 2021
5c5b79a
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jul 17, 2021
dd6379c
Merge remote-tracking branch 'upstream/develop' into feature/call-eve…
SimonBrandner Jul 20, 2021
6cb1c5d
Delint
SimonBrandner Jul 20, 2021
9e5b149
Fix event highlighthing
SimonBrandner Jul 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
@import "./views/messages/_CreateEvent.scss";
@import "./views/messages/_DateSeparator.scss";
@import "./views/messages/_EventTileBubble.scss";
@import "./views/messages/_CallEvent.scss";
@import "./views/messages/_MEmoteBody.scss";
@import "./views/messages/_MFileBody.scss";
@import "./views/messages/_MImageBody.scss";
Expand Down
7 changes: 7 additions & 0 deletions res/css/views/elements/_InfoTooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,12 @@ limitations under the License.
mask-position: center;
content: '';
vertical-align: middle;
}

.mx_InfoTooltip_icon_info::before {
mask-image: url('$(res)/img/element-icons/info.svg');
}

.mx_InfoTooltip_icon_warning::before {
mask-image: url('$(res)/img/element-icons/warning.svg');
}
154 changes: 154 additions & 0 deletions res/css/views/messages/_CallEvent.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
Copyright 2021 Šimon Brandner <[email protected]>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_CallEvent {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;

background-color: $dark-panel-bg-color;
border-radius: 8px;
margin: 10px auto;
max-width: 75%;
box-sizing: border-box;
height: 60px;

&.mx_CallEvent_voice {
.mx_CallEvent_type_icon::before,
.mx_CallEvent_content_button_callBack span::before,
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
}
}

&.mx_CallEvent_video {
.mx_CallEvent_type_icon::before,
.mx_CallEvent_content_button_callBack span::before,
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
}
}

.mx_CallEvent_info {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 12px;

.mx_CallEvent_info_basic {
display: flex;
flex-direction: column;
margin-left: 10px; // To match mx_CallEvent

.mx_CallEvent_sender {
font-weight: 600;
font-size: 1.5rem;
line-height: 1.8rem;
margin-bottom: 3px;
}

.mx_CallEvent_type {
font-weight: 400;
color: $secondary-fg-color;
font-size: 1.2rem;
line-height: $font-13px;
display: flex;
align-items: center;

.mx_CallEvent_type_icon {
height: 13px;
width: 13px;
margin-right: 5px;

&::before {
content: '';
position: absolute;
height: 13px;
width: 13px;
background-color: $tertiary-fg-color;
mask-repeat: no-repeat;
mask-size: contain;
}
}
}
}
}

.mx_CallEvent_content {
display: flex;
flex-direction: row;
align-items: center;
color: $secondary-fg-color;
margin-right: 16px;

.mx_CallEvent_content_button {
height: 24px;
padding: 0px 12px;
margin-left: 8px;

span {
padding: 8px 0;
display: flex;
align-items: center;

&::before {
content: '';
display: inline-block;
background-color: $button-fg-color;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 16px;
width: 16px;
height: 16px;
margin-right: 8px;
}
}
}

.mx_CallEvent_content_button_reject span::before {
mask-image: url('$(res)/img/element-icons/call/hangup.svg');
}

.mx_CallEvent_content_tooltip {
margin-right: 5px;
}

.mx_CallEvent_iconButton {
display: inline-flex;
margin-right: 8px;

&::before {
content: '';

height: 16px;
width: 16px;
background-color: $tertiary-fg-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}
}

.mx_CallEvent_silence::before {
mask-image: url('$(res)/img/voip/silence.svg');
}

.mx_CallEvent_unSilence::before {
mask-image: url('$(res)/img/voip/un-silence.svg');
}
}
}
8 changes: 8 additions & 0 deletions res/css/views/rooms/_EventTile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ $hover-select-border: 4px;
margin-right: 10px;
}

&:hover {
.mx_EventTile_line {
// To avoid bubble events being highlighted
background-color: inherit !important;
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
}
}


.mx_EventTile_msgOption a {
text-decoration: none;
}
Expand Down
3 changes: 3 additions & 0 deletions res/img/element-icons/warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 42 additions & 1 deletion src/CallHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const CHECK_PROTOCOLS_ATTEMPTS = 3;
// (and store the ID of their native room)
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';

export enum AudioID {
enum AudioID {
Ring = 'ringAudio',
Ringback = 'ringbackAudio',
CallEnd = 'callendAudio',
Expand Down Expand Up @@ -142,6 +142,7 @@ export enum PlaceCallType {
export enum CallHandlerEvent {
CallsChanged = "calls_changed",
CallChangeRoom = "call_change_room",
SilencedCallsChanged = "silenced_calls_changed",
}

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

private silencedCalls = new Set<string>(); // callIds

static sharedInstance() {
if (!window.mxCallHandler) {
window.mxCallHandler = new CallHandler();
Expand Down Expand Up @@ -224,6 +227,33 @@ export default class CallHandler extends EventEmitter {
}
}

public silenceCall(callId: string) {
this.silencedCalls.add(callId);
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);

// Don't pause audio if we have calls which are still ringing
if (this.areAnyCallsUnsilenced()) return;
this.pause(AudioID.Ring);
}

public unSilenceCall(callId: string) {
this.silencedCalls.delete(callId);
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
this.play(AudioID.Ring);
}

public isCallSilenced(callId: string): boolean {
return this.silencedCalls.has(callId);
}

/**
* Returns true if there is at least one unsilenced call
* @returns {boolean}
*/
private areAnyCallsUnsilenced(): boolean {
return this.calls.size > this.silencedCalls.size;
}

private async checkProtocols(maxTries) {
try {
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
Expand Down Expand Up @@ -301,6 +331,13 @@ export default class CallHandler extends EventEmitter {
}, true);
};

public getCallById(callId: string): MatrixCall {
for (const call of this.calls.values()) {
if (call.callId === callId) return call;
}
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

getCallForRoom(roomId: string): MatrixCall {
return this.calls.get(roomId) || null;
}
Expand Down Expand Up @@ -441,6 +478,10 @@ export default class CallHandler extends EventEmitter {
break;
}

if (newState !== CallState.Ringing) {
this.silencedCalls.delete(call.callId);
}

switch (newState) {
case CallState.Ringing:
this.play(AudioID.Ring);
Expand Down
89 changes: 0 additions & 89 deletions src/TextForEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { MatrixClientPeg } from './MatrixClientPeg';
import { _t } from './languageHandler';
import * as Roles from './Roles';
import { isValid3pidInvite } from "./RoomInvite";
Expand Down Expand Up @@ -318,90 +317,6 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
});
}

function textForCallAnswerEvent(event: MatrixEvent): () => string | null {
return () => {
const senderName = event.sender ? event.sender.name : _t('Someone');
const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)');
return _t('%(senderName)s answered the call.', { senderName }) + ' ' + supported;
};
}

function textForCallHangupEvent(event: MatrixEvent): () => string | null {
const getSenderName = () => event.sender ? event.sender.name : _t('Someone');
const eventContent = event.getContent();
let getReason = () => "";
if (!MatrixClientPeg.get().supportsVoip()) {
getReason = () => _t('(not supported by this browser)');
} else if (eventContent.reason) {
if (eventContent.reason === "ice_failed") {
// We couldn't establish a connection at all
getReason = () => _t('(could not connect media)');
} else if (eventContent.reason === "ice_timeout") {
// We established a connection but it died
getReason = () => _t('(connection failed)');
} else if (eventContent.reason === "user_media_failed") {
// The other side couldn't open capture devices
getReason = () => _t("(their device couldn't start the camera / microphone)");
} else if (eventContent.reason === "unknown_error") {
// An error code the other side doesn't have a way to express
// (as opposed to an error code they gave but we don't know about,
// in which case we show the error code)
getReason = () => _t("(an error occurred)");
} else if (eventContent.reason === "invite_timeout") {
getReason = () => _t('(no answer)');
} else if (eventContent.reason === "user hangup" || eventContent.reason === "user_hangup") {
// workaround for https://github.com/vector-im/element-web/issues/5178
// it seems Android randomly sets a reason of "user hangup" which is
// interpreted as an error code :(
// https://github.com/vector-im/riot-android/issues/2623
// Also the correct hangup code as of VoIP v1 (with underscore)
getReason = () => '';
} else {
getReason = () => _t('(unknown failure: %(reason)s)', { reason: eventContent.reason });
}
}
return () => _t('%(senderName)s ended the call.', { senderName: getSenderName() }) + ' ' + getReason();
}

function textForCallRejectEvent(event: MatrixEvent): () => string | null {
return () => {
const senderName = event.sender ? event.sender.name : _t('Someone');
return _t('%(senderName)s declined the call.', { senderName });
};
}

function textForCallInviteEvent(event: MatrixEvent): () => string | null {
const getSenderName = () => event.sender ? event.sender.name : _t('Someone');
// FIXME: Find a better way to determine this from the event?
let isVoice = true;
if (event.getContent().offer && event.getContent().offer.sdp &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
isVoice = false;
}
const isSupported = MatrixClientPeg.get().supportsVoip();

// This ladder could be reduced down to a couple string variables, however other languages
// can have a hard time translating those strings. In an effort to make translations easier
// and more accurate, we break out the string-based variables to a couple booleans.
if (isVoice && isSupported) {
return () => _t("%(senderName)s placed a voice call.", {
senderName: getSenderName(),
});
} else if (isVoice && !isSupported) {
return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", {
senderName: getSenderName(),
});
} else if (!isVoice && isSupported) {
return () => _t("%(senderName)s placed a video call.", {
senderName: getSenderName(),
});
} else if (!isVoice && !isSupported) {
return () => _t("%(senderName)s placed a video call. (not supported by this browser)", {
senderName: getSenderName(),
});
}
}

function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
const senderName = event.sender ? event.sender.name : event.getSender();

Expand Down Expand Up @@ -652,10 +567,6 @@ interface IHandlers {

const handlers: IHandlers = {
'm.room.message': textForMessageEvent,
'm.call.invite': textForCallInviteEvent,
'm.call.answer': textForCallAnswerEvent,
'm.call.hangup': textForCallHangupEvent,
'm.call.reject': textForCallRejectEvent,
};

const stateHandlers: IHandlers = {
Expand Down
Loading