diff --git a/images/icons/mozaic_black_24dp.svg b/images/icons/mozaic_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..114bc3f9397dcf3f0e877e2413e2410df4588547 --- /dev/null +++ b/images/icons/mozaic_black_24dp.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#231F20;} +</style> +<path class="st0" d="M20.5,1.5h-17c-1.1,0-2,0.9-2,2v17c0,1.1,0.9,2,2,2h17c1.1,0,2-0.9,2-2v-17C22.5,2.4,21.6,1.5,20.5,1.5z + M21.1,3.5v7.8h-8.4V2.9h7.8C20.8,2.9,21.1,3.2,21.1,3.5z M3.5,2.9h7.8v8.4H2.9V3.5C2.9,3.2,3.2,2.9,3.5,2.9z M2.9,20.5v-7.8h8.4 + v8.4H3.5C3.2,21.1,2.9,20.8,2.9,20.5z M20.5,21.1h-7.8v-8.4h8.4v7.8C21.1,20.8,20.8,21.1,20.5,21.1z"/> +</svg> diff --git a/resources.qrc b/resources.qrc index e5e4217a58e35ad2bdb53fe6e5f014f04b1d0831..1b86aec0c01bb7f9a4f36a3036f812de6aa681e8 100644 --- a/resources.qrc +++ b/resources.qrc @@ -6,6 +6,7 @@ <file>images/jami_rolling_spinner.gif</file> <file>images/icons/baseline-close-24px.svg</file> <file>images/icons/cross_black_24dp.svg</file> + <file>images/icons/mozaic_black_24dp.svg</file> <file>images/icons/baseline-done-24px.svg</file> <file>images/icons/baseline-error_outline-24px.svg</file> <file>projectcredits.html</file> diff --git a/src/calladapter.cpp b/src/calladapter.cpp index cd5f72b6506bf580ecdeb6fd3de48d21f8ba141e..b0a6d7ef276179527921b866f20766d0e098e252 100644 --- a/src/calladapter.cpp +++ b/src/calladapter.cpp @@ -629,6 +629,9 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo) bool isAudioMuted = call->audioMuted && (call->status != lrc::api::call::Status::PAUSED); bool isVideoMuted = call->videoMuted && !isPaused && !call->isAudioOnly; bool isRecording = isRecordingThisCall(); + bool isConferenceCall = !convInfo.confId.isEmpty() + || (convInfo.confId.isEmpty() && call->participantsInfos.size() != 0); + bool isGrid = call->layout == lrc::api::call::Layout::GRID; auto bestName = convInfo.participants.isEmpty() ? QString() : accInfo.contactModel->bestNameForContact(convInfo.participants[0]); @@ -639,6 +642,8 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo) isVideoMuted, isRecording, accInfo.profileInfo.type == lrc::api::profile::Type::SIP, + isConferenceCall, + isGrid, bestName); } @@ -712,6 +717,20 @@ CallAdapter::minimizeParticipant(const QString& uri) } } +void +CallAdapter::showGridConferenceLayout() +{ + auto* callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get(); + const auto& convInfo + = lrcInstance_->getConversationFromConvUid(lrcInstance_->get_selectedConvUid(), accountId_); + + auto confId = convInfo.confId; + if (confId.isEmpty()) + confId = convInfo.callId; + + callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); +} + void CallAdapter::hangUpThisCall() { diff --git a/src/calladapter.h b/src/calladapter.h index 1e56d3df8de652686e7fbd3d6be002e1c15c650d..2b181b7f0b8fe961ce3bae7c1267644ccc2559c4 100644 --- a/src/calladapter.h +++ b/src/calladapter.h @@ -59,6 +59,7 @@ public: Q_INVOKABLE void hangUpCall(const QString& callId); Q_INVOKABLE void maximizeParticipant(const QString& uri); Q_INVOKABLE void minimizeParticipant(const QString& uri); + Q_INVOKABLE void showGridConferenceLayout(); Q_INVOKABLE void hangUpThisCall(); Q_INVOKABLE bool isCurrentHost() const; Q_INVOKABLE bool participantIsHost(const QString& uri) const; @@ -95,6 +96,8 @@ Q_SIGNALS: bool isVideoMuted, bool isRecording, bool isSIP, + bool isConferenceCall, + bool isGrid, const QString& bestName); void remoteRecordingChanged(const QStringList& peers, bool state); void eraseRemoteRecording(); diff --git a/src/commoncomponents/PhotoboothView.qml b/src/commoncomponents/PhotoboothView.qml index 19434d548a50857f87dadb3cf2411e09a183b684..b8edc565eca1435f8199337023a77e9004f97bc1 100644 --- a/src/commoncomponents/PhotoboothView.qml +++ b/src/commoncomponents/PhotoboothView.qml @@ -240,9 +240,6 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter - text: "" - font.pointSize: 10 - font.kerning: true imageColor: JamiTheme.textColor toolTipText: JamiStrings.takePhoto @@ -284,10 +281,6 @@ ColumnLayout { Layout.preferredHeight: JamiTheme.preferredFieldHeight Layout.alignment: Qt.AlignHCenter - text: "" - font.pointSize: 10 - font.kerning: true - radius: height / 6 source: "qrc:/images/icons/round-folder-24px.svg" diff --git a/src/commoncomponents/PushButton.qml b/src/commoncomponents/PushButton.qml index 255fb853a20acc99f1f12a2fb107f76b31a9ba33..f9481e5b7e67389bcb71e10a53ba9a3314691860 100644 --- a/src/commoncomponents/PushButton.qml +++ b/src/commoncomponents/PushButton.qml @@ -37,17 +37,18 @@ AbstractButton { // Shape will default to a 15px circle // but can be sized accordingly. property int preferredSize: 30 - property int preferredWidth: preferredSize - property int preferredHeight: preferredSize property int preferredMargin: 16 - property alias textHAlign: textContent.horizontalAlignment // Note the radius will default to preferredSize property alias radius: background.radius // Text properties property alias buttonText: textContent.text + property alias buttonTextHeight: textContent.height + readonly property alias buttonTextWidth: textContent.width + property alias buttonTextFont: textContent.font property alias buttonTextColor: textContent.color - property alias fontPointSize: textContent.font.pointSize + property alias textHAlign: textContent.horizontalAlignment + property bool buttonTextEnableElide: false property string toolTipText: "" @@ -61,6 +62,8 @@ AbstractButton { property int duration: JamiTheme.shortFadeDuration // Image properties + property alias imageContainerWidth: image.containerWidth + property alias imageContainerHeight: image.containerHeight property alias source: image.source property var imageColor: null property string normalImageSource @@ -81,6 +84,63 @@ AbstractButton { ToolTip.visible: hovered && (toolTipText.length > 0) ToolTip.text: toolTipText + ResponsiveImage { + id: image + + anchors.centerIn: textContent.text ? undefined : root + anchors.left: textContent.text ? root.left : undefined + anchors.leftMargin: textContent.text ? preferredMargin : 0 + anchors.verticalCenter: root.verticalCenter + + containerHeight: preferredSize + containerWidth: preferredSize + + source: { + if (checkable && checkedImageSource) + return checked ? checkedImageSource : normalImageSource + else + return normalImageSource + } + + layer { + enabled: imageColor || checkedColor + effect: ColorOverlay { + id: overlay + color: { + if (checked && checkedImageColor) + return checkedImageColor + else if (imageColor) + return imageColor + else + return JamiTheme.transparentColor + } + } + // Mipmap does not render correctly on linux + mipmap: false + smooth: true + } + } + + Text { + id: textContent + + anchors.left: image.right + anchors.leftMargin: preferredMargin + anchors.verticalCenter: root.verticalCenter + + anchors.right: buttonTextEnableElide ? root.right : undefined + anchors.rightMargin: preferredMargin + + visible: text ? true : false + + horizontalAlignment: Text.AlignHCenter + + color: JamiTheme.primaryForegroundColor + font.kerning: true + font.pointSize: 9 + elide: Qt.ElideRight + } + background: Rectangle { id: background @@ -119,64 +179,5 @@ AbstractButton { ColorAnimation { duration: root.duration } } ] - - ResponsiveImage { - id: image - - containerWidth: preferredSize - containerHeight: preferredSize - - anchors.verticalCenter: background.verticalCenter - anchors.horizontalCenter: textContent.text ? undefined : parent.horizontalCenter - anchors.left: textContent.text ? parent.left : undefined - anchors.leftMargin: preferredMargin - - source: { - if (checkable && checkedImageSource) - return checked ? checkedImageSource : normalImageSource - else - return normalImageSource - } - - layer { - enabled: imageColor || checkedColor - effect: ColorOverlay { - id: overlay - color: { - if (checked && checkedImageColor) - return checkedImageColor - else if (imageColor) - return imageColor - else - return JamiTheme.transparentColor - } - } - // Mipmap does not render correctly on linux - mipmap: false - smooth: true - } - } - - Text { - id: textContent - - anchors.left: { - if (image.source.toString() !== '') - return image.right - else - return background.left - } - anchors.leftMargin: preferredMargin - anchors.right: background.right - anchors.rightMargin: preferredMargin - anchors.verticalCenter: background.verticalCenter - - horizontalAlignment: Text.AlignHCenter - - color: JamiTheme.primaryForegroundColor - font.kerning: true - font.pointSize: 9 - elide: Qt.ElideRight - } } } diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index 208c73b7c7bc5657f4e07e27b7150d9bdfa1e4eb..bd3853aa5056edfaad39b06f2b7fdd7dce776c74 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -197,6 +197,7 @@ Item { property string addParticipants: qsTr("Add participants") property string chat: qsTr("Chat") property string moreOptions: qsTr("More options") + property string mozaic: qsTr("Mozaic") // CallViewContextMenu property string hold: qsTr("Hold") diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml index a3357ba03017c9a6af2d73eec66c5c46f4244955..7bc536985979c8a0dbf786cad15081ef1814f464 100644 --- a/src/constant/JamiTheme.qml +++ b/src/constant/JamiTheme.qml @@ -91,6 +91,7 @@ Item { property color acceptGreenTransparency: rgba256(11, 130, 113, 56) property color refuseRed: rgba256(204, 0, 34, 100) property color refuseRedTransparent: rgba256(204, 0, 34, 56) + property color mozaicButtonNormalColor: "#272727" property color closeButtonLighterBlack: "#4c4c4c" @@ -204,6 +205,14 @@ Item { property int participantCallInStatusFontSize: 9 property int participantCallInStatusTextWidthLimit: 100 property int participantCallInStatusTextWidth: 68 + property int mozaicButtonRadius: 5 + property int mozaicButtonPreferredMargin: 5 + property real mozaicButtonOpacity: 0.77 + property int mozaicButtonTextPreferredWidth: 40 + property int mozaicButtonTextPreferredHeight: 16 + property int mozaicButtonTextPointSize: 8 + property int mozaicButtonPreferredWidth: 70 + property int mozaicButtonMaxWidth: 100 property real maximumWidthSettingsView: 600 property real settingsHeaderpreferredHeight: 64 diff --git a/src/mainview/components/CallOverlay.qml b/src/mainview/components/CallOverlay.qml index 8e99e8e9027bac96d2ed4316853ae1d431999e94..175930788198eb82f320e12201c0e6ee94c10978 100644 --- a/src/mainview/components/CallOverlay.qml +++ b/src/mainview/components/CallOverlay.qml @@ -47,12 +47,17 @@ Item { property bool isRecording property bool isSIP property bool isModerator + property bool isConferenceCall + property bool isGrid property string bestName: "" signal chatButtonClicked - onVisibleChanged: if (!visible) callViewContextMenu.close() + onVisibleChanged: { + if (!visible) + callViewContextMenu.close() + } ParticipantsLayer { id: __participantsLayer @@ -66,7 +71,8 @@ Item { } function updateUI(isPaused, isAudioOnly, isAudioMuted, - isVideoMuted, isRecording, isSIP) { + isVideoMuted, isRecording, isSIP, + isConferenceCall, isGrid) { if (isPaused !== undefined) { root.isPaused = isPaused root.isAudioOnly = isAudioOnly @@ -74,9 +80,10 @@ Item { root.isVideoMuted = isVideoMuted root.isRecording = isRecording root.isSIP = isSIP + root.isConferenceCall = isConferenceCall + root.isGrid = isGrid mainOverlay.recordingVisible = isRecording } - root.isModerator = CallAdapter.isCurrentModerator() } @@ -110,13 +117,10 @@ Item { label += ", " i += 1 } - label += " " + ((peers.length > 1)? JamiStrings.areRecording - : JamiStrings.isRecording) + label += " " + ((peers.length > 1) ? JamiStrings.areRecording : JamiStrings.isRecording) } - mainOverlay.remoteRecordingLabel = state ? - label : - JamiStrings.peerStoppedRecording + mainOverlay.remoteRecordingLabel = state ? label : JamiStrings.peerStoppedRecording callViewContextMenu.peerIsRecording = state mainOverlay.recordingVisible = callViewContextMenu.localIsRecording || callViewContextMenu.peerIsRecording diff --git a/src/mainview/components/MainOverlay.qml b/src/mainview/components/MainOverlay.qml index d47de09985b79d24017c54d1ad809037c2c3c8a1..a7faeefe291f39b740ec6d6cdced411d02bf24e8 100644 --- a/src/mainview/components/MainOverlay.qml +++ b/src/mainview/components/MainOverlay.qml @@ -51,9 +51,10 @@ Item { // (un)subscribe to an app-wide mouse move event trap filtered // for the overlay's geometry - onVisibleChanged: visible ? - CallOverlayModel.registerFilter(appWindow, this) : - CallOverlayModel.unregisterFilter(appWindow, this) + onVisibleChanged: visible ? CallOverlayModel.registerFilter( + appWindow, + this) : CallOverlayModel.unregisterFilter( + appWindow, this) Connections { target: CallOverlayModel @@ -104,80 +105,117 @@ Item { RowLayout { anchors.fill: parent + spacing: 0 + Text { id: jamiBestNameText Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.preferredWidth: overlayUpperPartRect.width / 3 + Layout.preferredWidth: overlayUpperPartRect.width / 2 Layout.preferredHeight: 50 - leftPadding: 16 - font.pointSize: JamiTheme.textFontSize + leftPadding: 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - text: textMetricsjamiBestNameText.elidedText - color: "white" - - TextMetrics { - id: textMetricsjamiBestNameText - font: jamiBestNameText.font - text: { - if (!root.isAudioOnly) { - if (remoteRecordingLabel === "") { - return root.bestName - } else { - return remoteRecordingLabel - } + font.pointSize: JamiTheme.textFontSize + text: { + if (!root.isAudioOnly) { + if (remoteRecordingLabel === "") { + return root.bestName + } else { + return remoteRecordingLabel } - return "" } - elideWidth: overlayUpperPartRect.width / 3 - elide: Qt.ElideRight + return "" } + color: JamiTheme.whiteColor + elide: Qt.ElideRight + } + + PushButton { + id: mozaicButton + + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.preferredWidth: JamiTheme.mozaicButtonPreferredWidth + Layout.preferredHeight: 30 + Layout.rightMargin: 5 + + visible: isConferenceCall && !isGrid + + preferredMargin: JamiTheme.mozaicButtonPreferredMargin + radius: JamiTheme.mozaicButtonRadius + opacity: JamiTheme.mozaicButtonOpacity + + buttonText: JamiStrings.mozaic + buttonTextColor: JamiTheme.whiteColor + buttonTextHeight: JamiTheme.mozaicButtonTextPreferredHeight + buttonTextFont.weight: Font.DemiBold + buttonTextFont.pointSize: JamiTheme.mozaicButtonTextPointSize + textHAlign: Text.AlignLeft + + imageColor: JamiTheme.whiteColor + imageContainerHeight: 20 + imageContainerWidth: 20 + source: "qrc:/images/icons/mozaic_black_24dp.svg" + + normalColor: JamiTheme.mozaicButtonNormalColor + onButtonTextWidthChanged: { + if (buttonTextWidth > JamiTheme.mozaicButtonTextPreferredWidth) { + if (mozaicButton.Layout.preferredWidth + buttonTextWidth + - JamiTheme.mozaicButtonTextPreferredWidth + > JamiTheme.mozaicButtonMaxWidth) { + mozaicButton.Layout.preferredWidth = JamiTheme.mozaicButtonMaxWidth + buttonTextEnableElide = true + } else + mozaicButton.Layout.preferredWidth += buttonTextWidth + - JamiTheme.mozaicButtonTextPreferredWidth + } + } + + onClicked: CallAdapter.showGridConferenceLayout() } Text { id: callTimerText + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.preferredWidth: 64 - Layout.minimumWidth: 64 Layout.preferredHeight: 48 - Layout.rightMargin: recordingRect.visible? - 0 : JamiTheme.preferredMarginSize + Layout.rightMargin: recordingRect.visible ? 0 : JamiTheme.preferredMarginSize + font.pointSize: JamiTheme.textFontSize horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter - text: textMetricscallTimerText.elidedText - color: "white" - TextMetrics { - id: textMetricscallTimerText - font: callTimerText.font - text: timeText - elideWidth: overlayUpperPartRect.width / 4 - elide: Qt.ElideRight - } + + text: timeText + color: JamiTheme.whiteColor + elide: Qt.ElideRight } Rectangle { id: recordingRect + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: JamiTheme.preferredMarginSize + height: 16 width: 16 + radius: height / 2 - color: "red" + color: JamiTheme.recordIconColor SequentialAnimation on color { loops: Animation.Infinite running: true ColorAnimation { - from: "red"; to: "transparent"; + from: JamiTheme.recordIconColor + to: "transparent" duration: JamiTheme.recordBlinkDuration } ColorAnimation { - from: "transparent"; to: "red"; + from: "transparent" + to: JamiTheme.recordIconColor duration: JamiTheme.recordBlinkDuration } } @@ -206,5 +244,9 @@ Item { height: 55 } - Behavior on opacity { NumberAnimation { duration: JamiTheme.overlayFadeDuration }} + Behavior on opacity { + NumberAnimation { + duration: JamiTheme.overlayFadeDuration + } + } } diff --git a/src/mainview/components/OngoingCallPage.qml b/src/mainview/components/OngoingCallPage.qml index a68f2f42d928a8c672632a8727480ae9ba8c1194..8ecd59cd9a0a70bc41d3e9b8dac0711e3dee7db7 100644 --- a/src/mainview/components/OngoingCallPage.qml +++ b/src/mainview/components/OngoingCallPage.qml @@ -30,7 +30,7 @@ import net.jami.Constants 1.0 import "../../commoncomponents" -Rectangle { +Rectangle { id: root property var accountPeerPair: ["", ""] @@ -50,15 +50,17 @@ Rectangle { onAccountPeerPairChanged: { if (accountPeerPair[0] === "" || accountPeerPair[1] === "") - return; + return contactImage.updateImage(accountPeerPair[1]) callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos()) - bestName = UtilsAdapter.getBestName(accountPeerPair[0], accountPeerPair[1]) + bestName = UtilsAdapter.getBestName(accountPeerPair[0], + accountPeerPair[1]) var id = UtilsAdapter.getBestId(accountPeerPair[0], accountPeerPair[1]) bestId = (bestName !== id) ? id : "" - root.callId = UtilsAdapter.getCallId(accountPeerPair[0], accountPeerPair[1]) + root.callId = UtilsAdapter.getCallId(accountPeerPair[0], + accountPeerPair[1]) } function setLinkedWebview(webViewId) { @@ -262,11 +264,10 @@ Rectangle { onPositionChanged: { // Calculate mouse position relative change. var delta = Qt.point(mouse.x - clickPos.x, - mouse.y - clickPos.y) + mouse.y - clickPos.y) var deltaW = previewRenderer.x + delta.x + previewRenderer.width var deltaH = previewRenderer.y + delta.y + previewRenderer.height - // Check if the previewRenderer exceeds the border of callPageMainRect. if (deltaW < callPageMainRect.width && previewRenderer.x + delta.x > 1) @@ -303,11 +304,14 @@ Rectangle { target: CallAdapter function onUpdateOverlay(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, - isRecording, isSIP, bestName) { + isRecording, isSIP, isConferenceCall, isGrid, + bestName) { callOverlay.showOnHoldImage(isPaused) audioCallPageRectCentralRect.visible = !isPaused && root.isAudioOnly - callOverlay.updateUI(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, - isRecording, isSIP) + callOverlay.updateUI(isPaused, isAudioOnly, + isAudioMuted, isVideoMuted, + isRecording, isSIP, + isConferenceCall, isGrid) root.bestName = bestName callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos()) } diff --git a/src/settingsview/components/SettingsMenu.qml b/src/settingsview/components/SettingsMenu.qml index cb820a968cb4cf6fdf0b33fb7cabc78c33b4c38f..0a2eb8e5e48d5d6108681f8cda92f371d83ae58c 100644 --- a/src/settingsview/components/SettingsMenu.qml +++ b/src/settingsview/components/SettingsMenu.qml @@ -78,7 +78,7 @@ Rectangle { pressedColor: Qt.lighter(JamiTheme.pressedButtonColor, 1.25) checkedColor: JamiTheme.selectedColor hoveredColor: JamiTheme.hoverColor - fontPointSize: JamiTheme.textFontSize + 2 + buttonTextFont.pointSize: JamiTheme.textFontSize + 2 duration: 0 textHAlign: Text.AlignLeft preferredMargin: 24