diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index a72008c537fe8f1106147b5510ed3b03d857b640..0d9ffe254608a03242f31f06a5807b0f6c0c10c7 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 b5b6ab7e1e10eaf73a7e3af03e6c96b274b6b2a2..74e28bea3331683719ad42df9b54b3cd5fca3b9b 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 bdea63204453312b2161aa764936ace537075e88..7b4ef46da41f250639258d7fd77add96003e8f78 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 bd306ba1022f4c71427e3b3aa4c2fedda6b910da..cdcd20b3dae4a0aee88cf46dd67707d969edce71 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 b3ee3d4762218e3e5d6af217f59ab6bcfb601dc3..146836c317d0403bb956ae47f3e75c5f3e82fb6e 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 1b3002c53e3cf5957ef7dcc0fca5d4ca0b3f62eb..6a080fd6d1836fde2842a96acfc7fa8abab1d7aa 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 a69608716ce1d04998f76791a84c88ac49cdd429..b3e82741ce89d53b8129d52bc2d75753e5c33ebd 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 } } }