From 4e27b00cf370e2e4bbf42da9aee48823dd1c4daa Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 13:46:39 -0700 Subject: [PATCH 1/3] Move call buttons to the room header This is to make some room in the composer for voice messages. The hangup behaviour is intentionally lost by this change as the VOIP UX is intended to rely on dedicated hangup buttons instead. --- res/css/views/rooms/_MessageComposer.scss | 12 -- res/css/views/rooms/_RoomHeader.scss | 13 ++ src/components/structures/RoomView.tsx | 11 +- src/components/views/rooms/MessageComposer.js | 121 ------------------ src/components/views/rooms/RoomHeader.js | 20 +++ src/settings/Settings.ts | 2 + 6 files changed, 45 insertions(+), 134 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 71c0db947e..69e8b50501 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -227,18 +227,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/composer/attach.svg'); } -.mx_MessageComposer_hangup::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); -} - -.mx_MessageComposer_voicecall::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); -} - -.mx_MessageComposer_videocall::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); -} - .mx_MessageComposer_emoji::before { mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a23a44906f..387d1588a3 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -252,6 +252,19 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } +.mx_RoomHeader_voiceCallButton::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + + // The call button SVG is padded slightly differently, so match it up to the size + // of the other icons + mask-size: 20px; + mask-position: center; +} + +.mx_RoomHeader_videoCallButton::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); +} + .mx_RoomHeader_showPanel { height: 16px; } diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 68ab3c6e0c..6c8560f42c 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -34,7 +34,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier'; import ContentMessages from '../../ContentMessages'; import Modal from '../../Modal'; import * as sdk from '../../index'; -import CallHandler from '../../CallHandler'; +import CallHandler, { PlaceCallType } from '../../CallHandler'; import dis from '../../dispatcher/dispatcher'; import Tinter from '../../Tinter'; import rateLimitedFunc from '../../ratelimitedfunc'; @@ -1352,6 +1352,14 @@ export default class RoomView extends React.Component { SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); }; + private onCallPlaced = (type: PlaceCallType) => { + dis.dispatch({ + action: 'place_call', + type: type, + room_id: this.state.room.roomId, + }); + }; + private onSettingsClick = () => { dis.dispatch({ action: "open_room_settings" }); }; @@ -2031,6 +2039,7 @@ export default class RoomView extends React.Component { e2eStatus={this.state.e2eStatus} onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null} appsShown={this.state.showApps} + onCallPlaced={this.onCallPlaced} />
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 6a867386f7..c38d40020f 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -50,97 +50,6 @@ ComposerAvatar.propTypes = { me: PropTypes.object.isRequired, }; -function CallButton(props) { - const onVoiceCallClick = (ev) => { - dis.dispatch({ - action: 'place_call', - type: PlaceCallType.Voice, - room_id: props.roomId, - }); - }; - - return (); -} - -CallButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -function VideoCallButton(props) { - const onCallClick = (ev) => { - dis.dispatch({ - action: 'place_call', - type: ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video, - room_id: props.roomId, - }); - }; - - return ; -} - -VideoCallButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -function HangupButton(props) { - const onHangupClick = () => { - if (props.isConference) { - dis.dispatch({ - action: props.canEndConference ? 'end_conference' : 'hangup_conference', - room_id: props.roomId, - }); - return; - } - - const call = CallHandler.sharedInstance().getCallForRoom(props.roomId); - if (!call) { - return; - } - - const action = call.state === CallState.Ringing ? 'reject' : 'hangup'; - - dis.dispatch({ - action, - // hangup the call for this room. NB. We use the room in props as the room ID - // as call.roomId may be the 'virtual room', and the dispatch actions always - // use the user-facing room (there was a time when we deliberately used - // call.roomId and *not* props.roomId, but that was for the old - // style Freeswitch conference calls and those times are gone.) - room_id: props.roomId, - }); - }; - - let tooltip = _t("Hangup"); - if (props.isConference && props.canEndConference) { - tooltip = _t("End conference"); - } - - const canLeaveConference = !props.isConference ? true : props.isInConference; - return ( - - ); -} - -HangupButton.propTypes = { - roomId: PropTypes.string.isRequired, - isConference: PropTypes.bool.isRequired, - canEndConference: PropTypes.bool, - isInConference: PropTypes.bool, -}; - const EmojiButton = ({addEmoji}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -265,7 +174,6 @@ export default class MessageComposer extends React.Component { this.state = { tombstone: this._getRoomTombstone(), canSendMessages: this.props.room.maySendMessage(), - showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), }; @@ -405,12 +313,7 @@ export default class MessageComposer extends React.Component { ]; if (!this.state.tombstone && this.state.canSendMessages) { - // This also currently includes the call buttons. Really we should - // check separately for whether we can call, but this is slightly - // complex because of conference calls. - const SendMessageComposer = sdk.getComponent("rooms.SendMessageComposer"); - const callInProgress = this.props.callState && this.props.callState !== 'ended'; controls.push( ); } - - if (this.state.showCallButtons) { - if (this.state.hasConference) { - const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId); - controls.push( - , - ); - } else if (callInProgress) { - controls.push( - , - ); - } else { - controls.push( - , - , - ); - } - } } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 93055c69f5..6736600bc8 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -31,6 +31,7 @@ import {DefaultTagID} from "../../../stores/room-list/models"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import RoomTopic from "../elements/RoomTopic"; import RoomName from "../elements/RoomName"; +import {PlaceCallType} from "../../../CallHandler"; export default class RoomHeader extends React.Component { static propTypes = { @@ -45,6 +46,7 @@ export default class RoomHeader extends React.Component { e2eStatus: PropTypes.string, onAppsClick: PropTypes.func, appsShown: PropTypes.bool, + onCallPlaced: PropTypes.func, // (PlaceCallType) => void; }; static defaultProps = { @@ -226,8 +228,26 @@ export default class RoomHeader extends React.Component { title={_t("Search")} />; } + let voiceCallButton; + let videoCallButton; + if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) { + voiceCallButton = + this.props.onCallPlaced(PlaceCallType.Voice)} + title={_t("Voice call")} />; + videoCallButton = + this.props.onCallPlaced( + ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video)} + title={_t("Video call")} />; + } + const rightRow =
+ { videoCallButton } + { voiceCallButton } { pinnedEventsButton } { forgetButton } { appsButton } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b452f10e73..43210021e5 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -632,6 +632,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: 3000, }, "showCallButtonsInComposer": { + // Dev note: This is no longer "in composer" but is instead "in room header". + // TODO: Rename with settings v3 supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: true, controller: new UIFeatureController(UIFeature.Voip), From 855ee068c3c26d37488cf1fe47e6ae62bdb74bc9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 13:50:03 -0700 Subject: [PATCH 2/3] Appease the linter --- src/components/views/rooms/MessageComposer.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index c38d40020f..eea6a6b802 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -1,7 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 New Vector Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2015-2018, 2020, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +17,6 @@ import React, {createRef} from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; -import CallHandler from '../../../CallHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -33,11 +30,8 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import ReplyPreview from "./ReplyPreview"; import {UIFeature} from "../../../settings/UIFeature"; import WidgetStore from "../../../stores/WidgetStore"; -import WidgetUtils from "../../../utils/WidgetUtils"; import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; -import { PlaceCallType } from "../../../CallHandler"; -import { CallState } from 'matrix-js-sdk/src/webrtc/call'; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); From 01da8633d48a2c04029d5b4bbf80d9cf2a86e63f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 13:50:22 -0700 Subject: [PATCH 3/3] i18n --- src/i18n/strings/en_EN.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f443c4961b..0be7e6e02b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1416,9 +1416,6 @@ "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", - "Voice call": "Voice call", - "Video call": "Video call", - "Hangup": "Hangup", "Emoji picker": "Emoji picker", "Upload file": "Upload file", "Send an encrypted reply…": "Send an encrypted reply…", @@ -1476,6 +1473,8 @@ "Hide Widgets": "Hide Widgets", "Show Widgets": "Show Widgets", "Search": "Search", + "Voice call": "Voice call", + "Video call": "Video call", "Start a Conversation": "Start a Conversation", "Open dial pad": "Open dial pad", "Invites": "Invites",