From 3380a26788a482f27522c167e522ea870b2dd1f4 Mon Sep 17 00:00:00 2001 From: agsantos <aline.gondimsantos@savoirfairelinux.com> Date: Wed, 10 Nov 2021 19:55:01 -0500 Subject: [PATCH] conference: improve participant overlay - In another participant video, if I am moderator, I will see local state at bottom and moderator state on top; - In another participant video, if I am NOT moderator, I will see only one state at bottom representing both local and moderator; - In my own video, if I am NOT moderator, I will have my local state at bottom left and moderator state top left with a tooltip but no action; - In my own video, if I am moderator, I will have my local state at bottom left and moderator state top left with an action. Change-Id: I649d4aeefdd15aa3b554d78948849804ad94a9cd GitLab: #593 --- src/constant/JamiStrings.qml | 13 +- src/constant/JamiTheme.qml | 1 + src/mainview/components/CallActionBar.qml | 13 +- src/mainview/components/MainOverlay.qml | 43 +++ .../components/ParticipantControlLayout.qml | 36 +- .../components/ParticipantOverlay.qml | 307 ++++++++++++------ .../components/ParticipantOverlayMenu.qml | 58 +--- 7 files changed, 300 insertions(+), 171 deletions(-) diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index a72008c53..0d9ffe254 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -205,8 +205,6 @@ Item { property string areRecording: qsTr("are recording") property string peerStoppedRecording: qsTr("Peer stopped recording") property string isCallingYou: qsTr("is calling you") - - // CallOverlay property string mute: qsTr("Mute") property string unmute: qsTr("Unmute") property string hangup: qsTr("End call") @@ -218,6 +216,15 @@ Item { property string chat: qsTr("Chat") property string moreOptions: qsTr("More options") property string mosaic: qsTr("Mosaic") + property string participantMicIsStillMuted: qsTr("Participant is still muted on his local machine") + property string mutedLocally: qsTr("You are still muted on your local machine") + property string participantModIsStillMuted: qsTr("You are still muted by moderator") + property string mutedByModerator: qsTr("You are muted by a moderator") + property string moderator: qsTr("Moderator") + property string host: qsTr("Host") + property string bothMuted: qsTr("Local and Moderator muted") + property string moderatorMuted: qsTr("Moderator muted") + property string notMuted: qsTr("Not muted") // LineEditContextMenu property string copy: qsTr("Copy") @@ -540,7 +547,7 @@ Item { property string maximizeParticipant: qsTr("Maximize") property string minimizeParticipant: qsTr("Minimize") property string hangupParticipant: qsTr("Hangup") - property string localMuted: qsTr("local muted") + property string localMuted: qsTr("Local muted") // Settings moderation property string conferenceModeration: qsTr("Conference moderation") diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml index b5b6ab7e1..74e28bea3 100644 --- a/src/constant/JamiTheme.qml +++ b/src/constant/JamiTheme.qml @@ -94,6 +94,7 @@ Item { property color refuseRedTransparent: rgba256(204, 0, 34, 56) property color mosaicButtonNormalColor: "#272727" property color whiteColorTransparent: rgba256(255, 255, 255, 50) + property color raiseHandColor: rgba256(0, 184, 255, 77) property color closeButtonLighterBlack: "#4c4c4c" diff --git a/src/mainview/components/CallActionBar.qml b/src/mainview/components/CallActionBar.qml index bdea63204..7b4ef46da 100644 --- a/src/mainview/components/CallActionBar.qml +++ b/src/mainview/components/CallActionBar.qml @@ -156,7 +156,16 @@ Control { property list<Action> primaryActions: [ Action { id: muteAudioAction - onTriggered: CallAdapter.muteThisCallToggle(!isAudioMuted) + onTriggered: { + var muteState = CallAdapter.getMuteState(CurrentAccount.uri) + var modMuted = muteState === CallAdapter.MODERATOR_MUTED + || muteState === CallAdapter.BOTH_MUTED + if (isAudioMuted && modMuted) { + muteAlertActive = true + muteAlertMessage = JamiStrings.participantModIsStillMuted + } + CallAdapter.muteThisCallToggle(!isAudioMuted) + } checkable: true icon.source: checked ? JamiResources.micro_off_black_24dp_svg : @@ -263,7 +272,7 @@ Control { onTriggered: CallAdapter.setHandRaised("", !CallAdapter.isHandRaised()) checkable: true icon.source: JamiResources.hand_black_24dp_svg - icon.color: checked ? "red" : "white" + icon.color: checked ? JamiTheme.raiseHandColor : "white" text: checked ? JamiStrings.lowerHand : JamiStrings.raiseHand diff --git a/src/mainview/components/MainOverlay.qml b/src/mainview/components/MainOverlay.qml index bd306ba10..cdcd20b3d 100644 --- a/src/mainview/components/MainOverlay.qml +++ b/src/mainview/components/MainOverlay.qml @@ -42,6 +42,15 @@ Item { callActionBar.subMenuOpen || participantCallInStatusView.visible + property string muteAlertMessage: "" + property bool muteAlertActive: false + + onMuteAlertActiveChanged: { + if (muteAlertActive) { + alertTimer.restart() + } + } + opacity: 0 // (un)subscribe to an app-wide mouse move event trap filtered @@ -227,6 +236,40 @@ Item { anchors.bottomMargin: 20 } + Rectangle { + id: alertMessage + + anchors.bottom: __callActionBar.top + anchors.bottomMargin: 16 + anchors.horizontalCenter: __callActionBar.horizontalCenter + width: alertMessageTxt.width + 16 + height: alertMessageTxt.contentHeight + 16 + radius: 5 + visible: root.muteAlertActive + color: JamiTheme.darkGreyColorOpacity + + Text { + id: alertMessageTxt + text: root.muteAlertMessage + anchors.centerIn: parent + width: Math.min(root.width, contentWidth) + color: JamiTheme.whiteColor + font.pointSize: JamiTheme.textFontSize + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + // Timer to decide when ParticipantOverlay fade out + Timer { + id: alertTimer + interval: JamiTheme.overlayFadeDelay + onTriggered: { + root.muteAlertActive = false + } + } + } + CallActionBar { id: __callActionBar diff --git a/src/mainview/components/ParticipantControlLayout.qml b/src/mainview/components/ParticipantControlLayout.qml index b3ee3d476..146836c31 100644 --- a/src/mainview/components/ParticipantControlLayout.qml +++ b/src/mainview/components/ParticipantControlLayout.qml @@ -24,7 +24,7 @@ import net.jami.Constants 1.1 import "../../commoncomponents" RowLayout { - id: buttonsRect + id: root property int visibleButtons: toggleModerator.visible + toggleMute.visible @@ -55,9 +55,25 @@ RowLayout { source: showModeratorMute ? JamiResources.micro_black_24dp_svg : JamiResources.micro_off_black_24dp_svg - onClicked: CallAdapter.muteParticipant(uri, showModeratorMute) - toolTipText: showModeratorMute? JamiStrings.muteParticipant - : JamiStrings.unmuteParticipant + checkable: meModerator + onClicked: { + if (participantIsModeratorMuted && isLocalMuted) { + if (isMe) + muteAlertMessage = JamiStrings.mutedLocally + else + muteAlertMessage = JamiStrings.participantMicIsStillMuted + muteAlertActive = true + } + CallAdapter.muteParticipant(uri, showModeratorMute) + } + toolTipText: { + if (!checkable && participantIsModeratorMuted) + return JamiStrings.mutedByModerator + if (showModeratorMute) + return JamiStrings.muteParticipant + else + return JamiStrings.unmuteParticipant + } } ParticipantOverlayButton { @@ -84,18 +100,6 @@ RowLayout { toolTipText: JamiStrings.minimizeParticipant } - ParticipantOverlayButton { - id: lowerHandParticipant - - visible: showLowerHand - preferredSize: iconButtonPreferredSize - Layout.preferredHeight: buttonPreferredSize - Layout.preferredWidth: buttonPreferredSize - source: JamiResources.hand_black_24dp_svg - onClicked: CallAdapter.setHandRaised(uri, false) - toolTipText: JamiStrings.lowerHand - } - ParticipantOverlayButton { id: hangupParticipant diff --git a/src/mainview/components/ParticipantOverlay.qml b/src/mainview/components/ParticipantOverlay.qml index 1b3002c53..6a080fd6d 100644 --- a/src/mainview/components/ParticipantOverlay.qml +++ b/src/mainview/components/ParticipantOverlay.qml @@ -31,8 +31,8 @@ Item { id: root // svg path for the participant indicators background shape - property int shapeWidth: indicatorsRowLayout.width + 8 - property int shapeHeight: 16 + property int shapeWidth: participantFootInfo.width + 8 + property int shapeHeight: 30 property int shapeRadius: 6 property string pathShape: "M0,0 h%1 q%2,0 %2,%2 v%3 h-%4 z" .arg(shapeWidth - shapeRadius) @@ -41,6 +41,7 @@ Item { .arg(shapeWidth) property string uri: overlayMenu.uri + property string bestName: "" property bool participantIsActive: false property bool participantIsHost: false property bool participantIsModerator: false @@ -48,6 +49,18 @@ Item { property bool participantIsModeratorMuted: false property bool participantHandIsRaised: false + property bool meModerator: false + property bool isMe: false + + property string muteAlertMessage: "" + property bool muteAlertActive: false + + onMuteAlertActiveChanged: { + if (muteAlertActive) { + alertTimer.restart() + } + } + z: 1 function setAvatar(show, uri, isLocal) { @@ -63,10 +76,11 @@ Item { function setMenu(newUri, bestName, isLocal, isActive, showMax) { overlayMenu.uri = newUri - overlayMenu.bestName = bestName + root.bestName = bestName + isMe = overlayMenu.uri === CurrentAccount.uri var isHost = CallAdapter.isCurrentHost() - var isModerator = CallAdapter.isCurrentModerator() + meModerator = CallAdapter.isCurrentModerator() participantIsHost = CallAdapter.participantIsHost(overlayMenu.uri) participantIsModerator = CallAdapter.isModerator(overlayMenu.uri) participantIsActive = isActive @@ -77,104 +91,22 @@ Item { var muteState = CallAdapter.getMuteState(overlayMenu.uri) overlayMenu.isLocalMuted = muteState === CallAdapter.LOCAL_MUTED || muteState === CallAdapter.BOTH_MUTED - var isModeratorMuted = muteState === CallAdapter.MODERATOR_MUTED + participantIsModeratorMuted = muteState === CallAdapter.MODERATOR_MUTED || muteState === CallAdapter.BOTH_MUTED - participantIsMuted = overlayMenu.isLocalMuted || isModeratorMuted + participantIsMuted = overlayMenu.isLocalMuted || participantIsModeratorMuted - overlayMenu.showModeratorMute = isModerator && !isModeratorMuted - overlayMenu.showModeratorUnmute = isModerator && isModeratorMuted - overlayMenu.showMaximize = isModerator && showMax - overlayMenu.showMinimize = isModerator && participantIsActive - overlayMenu.showHangup = isModerator && !isLocal && !participantIsHost - overlayMenu.showLowerHand = isModerator && participantHandIsRaised + overlayMenu.showModeratorMute = meModerator && !participantIsModeratorMuted + overlayMenu.showModeratorUnmute = (meModerator || isMe) && participantIsModeratorMuted + overlayMenu.showMaximize = meModerator && showMax + overlayMenu.showMinimize = meModerator && participantIsActive + overlayMenu.showHangup = meModerator && !isLocal && !participantIsHost } - // Participant header with host, moderator and mute indicators - Rectangle { - id: participantIndicators - width: indicatorsRowLayout.width - height: shapeHeight - visible: participantIsHost || participantIsModerator || participantIsMuted - color: "transparent" - anchors.bottom: parent.bottom - - Shape { - id: backgroundShape - ShapePath { - id: backgroundShapePath - strokeColor: "transparent" - fillColor: JamiTheme.darkGreyColorOpacity - capStyle: ShapePath.RoundCap - PathSvg { path: pathShape } - } - } - - RowLayout { - id: indicatorsRowLayout - height: parent.height - anchors.verticalCenter: parent.verticalCenter - - ResponsiveImage { - id: isHostIndicator - - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: 6 - - containerHeight: 12 - containerWidth: 12 - - visible: participantIsHost - - source: JamiResources.star_outline_24dp_svg - color: JamiTheme.whiteColor - } - - ResponsiveImage { - id: isModeratorIndicator - - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: 6 - - containerHeight: 12 - containerWidth: 12 - - visible: participantIsModerator - - source: JamiResources.moderator_svg - color: JamiTheme.whiteColor - } - - ResponsiveImage { - id: isMutedIndicator - - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: 6 - - containerHeight: 12 - containerWidth: 12 - - visible: participantIsMuted - - source: JamiResources.micro_off_black_24dp_svg - color: JamiTheme.whiteColor - } - - ResponsiveImage { - id: isHandRaisedIndicator - - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: 6 - - containerHeight: 12 - containerWidth: 12 - - visible: participantHandIsRaised - - source: JamiResources.hand_black_24dp_svg - color: JamiTheme.whiteColor - } - } + TextMetrics { + id: nameTextMetrics + text: bestName + font.pointSize: JamiTheme.participantFontSize } Loader { @@ -242,8 +174,187 @@ Item { } } - ParticipantOverlayMenu { id: overlayMenu } + ParticipantOverlayMenu { + id: overlayMenu + visible: isMe || meModerator + } + + // Participant footer with host, moderator and mute indicators + // Mute indicator is as follow: + // - In another participant, if i am not moderator, the mute state is isLocalMuted || participantIsModeratorMuted + // - In another participant, if i am moderator, the mute state is isLocalMuted + // - In my video, the mute state is isLocalMuted + Rectangle { + id: participantIndicators + width: participantRect.width + height: shapeHeight + color: "transparent" + anchors.bottom: parent.bottom + + Shape { + id: backgroundShape + ShapePath { + id: backgroundShapePath + strokeColor: "transparent" + fillColor: JamiTheme.darkGreyColorOpacity + capStyle: ShapePath.RoundCap + PathSvg { path: pathShape } + } + } + + RowLayout { + id: participantFootInfo + height: parent.height + anchors.verticalCenter: parent.verticalCenter + Text { + id: bestNameLabel + + Layout.leftMargin: 8 + Layout.preferredWidth: Math.min(nameTextMetrics.boundingRect.width + 8, + participantIndicators.width - indicatorsRowLayout.width - 16) + Layout.preferredHeight: shapeHeight + + text: bestName + elide: Text.ElideRight + color: JamiTheme.whiteColor + font.pointSize: JamiTheme.participantFontSize + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + HoverHandler { id: hoverName } + MaterialToolTip { + visible: hoverName.hovered && (text.length > 0) + text: bestNameLabel.truncated ? bestName : "" + } + } + + RowLayout { + id: indicatorsRowLayout + height: parent.height + Layout.alignment: Qt.AlignVCenter + + ResponsiveImage { + id: isHostIndicator + + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 6 + + containerHeight: 12 + containerWidth: 12 + + visible: participantIsHost + + source: JamiResources.star_outline_24dp_svg + color: JamiTheme.whiteColor + + HoverHandler { id: hoverHost } + MaterialToolTip { + visible: hoverHost.hovered + text: JamiStrings.host + } + } + + ResponsiveImage { + id: isModeratorIndicator + + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 6 + + containerHeight: 12 + containerWidth: 12 + + visible: !participantIsHost && participantIsModerator + + source: JamiResources.moderator_svg + color: JamiTheme.whiteColor + + HoverHandler { id: hoverModerator } + MaterialToolTip { + visible: hoverModerator.hovered + text: JamiStrings.moderator + } + } + + ResponsiveImage { + id: isMutedIndicator + + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 6 + + containerHeight: 12 + containerWidth: 12 + + visible: (!isMe && !meModerator) ? participantIsMuted : overlayMenu.isLocalMuted + + source: JamiResources.micro_off_black_24dp_svg + color: "red" + + HoverHandler { id: hoverMicrophone } + MaterialToolTip { + visible: hoverMicrophone.hovered + text: { + if (!isMe && !meModerator && participantIsModeratorMuted && overlayMenu.isLocalMuted) + return JamiStrings.bothMuted + if (overlayMenu.isLocalMuted) + return JamiStrings.localMuted + if (!isMe && !meModerator && participantIsModeratorMuted) + return JamiStrings.moderatorMuted + return JamiStrings.notMuted + } + } + } + } + } + } Behavior on opacity { NumberAnimation { duration: JamiTheme.shortFadeDuration }} } + + PushButton { + id: isRaiseHandIndicator + source: JamiResources.hand_black_24dp_svg + imageColor: JamiTheme.whiteColor + preferredSize: shapeHeight + visible: root.participantHandIsRaised + anchors.right: parent.right + checkable: root.meModerator + pressedColor: JamiTheme.raiseHandColor + hoveredColor: JamiTheme.raiseHandColor + normalColor: JamiTheme.raiseHandColor + z: participantRect.z + 1 + toolTipText: root.meModerator ? JamiStrings.lowerHand : "" + onClicked: CallAdapter.setHandRaised(uri, false) + radius: 5 + } + + Rectangle { + id: alertMessage + + anchors.centerIn: parent + width: alertMessageTxt.width + 16 + height: alertMessageTxt.contentHeight + 16 + radius: 5 + visible: root.muteAlertActive + color: JamiTheme.darkGreyColorOpacity + + Text { + id: alertMessageTxt + text: root.muteAlertMessage + anchors.centerIn: parent + width: Math.min(participantRect.width, contentWidth) + color: JamiTheme.whiteColor + font.pointSize: JamiTheme.textFontSize + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + // Timer to decide when ParticipantOverlay fade out + Timer { + id: alertTimer + interval: JamiTheme.overlayFadeDelay + onTriggered: { + root.muteAlertActive = false + } + } + } } diff --git a/src/mainview/components/ParticipantOverlayMenu.qml b/src/mainview/components/ParticipantOverlayMenu.qml index a69608716..b3e82741c 100644 --- a/src/mainview/components/ParticipantOverlayMenu.qml +++ b/src/mainview/components/ParticipantOverlayMenu.qml @@ -31,7 +31,6 @@ Item { id: root property string uri: "" - property string bestName: "" property bool isLocalMuted: true property bool showSetModerator: false property bool showUnsetModerator: false @@ -40,7 +39,6 @@ Item { property bool showMaximize: false property bool showMinimize: false property bool showHangup: false - property bool showLowerHand: false property int shapeHeight: 30 property int shapeRadius: 8 @@ -57,12 +55,6 @@ Item { Loader { sourceComponent: isBarLayout ? barComponent : rectComponent } - TextMetrics { - id: nameTextMetrics - text: bestName - font.pointSize: JamiTheme.participantFontSize - } - Component { id: rectComponent @@ -86,24 +78,8 @@ Item { } ColumnLayout { - anchors.centerIn: parent - Text { - id: bestNameLabel - - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - Layout.preferredWidth: Math.min(nameTextMetrics.boundingRect.width + 8, - root.width - 16) - Layout.preferredHeight: shapeHeight - - text: bestName - elide: Text.ElideRight - color: JamiTheme.whiteColor - font.pointSize: JamiTheme.participantFontSize - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } ParticipantControlLayout { id: buttonsRect @@ -119,7 +95,7 @@ Item { id: barComponent Control { - width: rowLayout.implicitWidth + width: buttonsRect.implicitWidth height: shapeHeight onHoveredChanged: root.hovered = hovered @@ -137,34 +113,12 @@ Item { } } - RowLayout { - id: rowLayout - - spacing: 8 - - Text { - id: bestNameLabel + ParticipantControlLayout { + id: buttonsRect - Layout.leftMargin: 8 - Layout.preferredWidth: Math.min(nameTextMetrics.boundingRect.width + 8, - root.width - buttonsRect.implicitWidth - 16) - Layout.preferredHeight: shapeHeight - - text: bestName - elide: Text.ElideRight - color: JamiTheme.whiteColor - font.pointSize: JamiTheme.participantFontSize - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - ParticipantControlLayout { - id: buttonsRect - - Layout.rightMargin: 8 - Layout.preferredWidth: implicitWidth - Layout.preferredHeight: shapeHeight - } + Layout.rightMargin: 8 + Layout.preferredWidth: implicitWidth + Layout.preferredHeight: shapeHeight } } } -- GitLab