From ff7acf99320a872a7ec2f5e6a0e65be9e50a64f4 Mon Sep 17 00:00:00 2001 From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> Date: Thu, 29 Feb 2024 10:30:23 -0500 Subject: [PATCH] localvideo: refactor preview component device control Change-Id: Ibcd88c5a3c73a0e67f94d70bc420845aa7b8c822 --- src/app/commoncomponents/LocalVideo.qml | 2 +- src/app/currentcall.cpp | 45 +--- src/app/currentconversation.cpp | 60 ++--- .../mainview/components/InCallLocalVideo.qml | 211 +++++++++++++++++ .../mainview/components/OngoingCallPage.qml | 224 +----------------- .../components/VideoSettingsPage.qml | 7 +- src/app/videodevices.cpp | 16 +- src/libclient/api/call.h | 42 ++++ src/libclient/api/conversationmodel.h | 2 +- src/libclient/conversationmodel.cpp | 4 +- tests/qml/src/tst_OngoingCallPage.qml | 11 + 11 files changed, 318 insertions(+), 306 deletions(-) create mode 100644 src/app/mainview/components/InCallLocalVideo.qml diff --git a/src/app/commoncomponents/LocalVideo.qml b/src/app/commoncomponents/LocalVideo.qml index 88e28125a..c746f6d93 100644 --- a/src/app/commoncomponents/LocalVideo.qml +++ b/src/app/commoncomponents/LocalVideo.qml @@ -33,7 +33,7 @@ VideoView { stop(); return; } - const forceRestart = rendererId === id; + const forceRestart = rendererId === id || force; if (!forceRestart) { // Stop previous device VideoDevices.stopDevice(rendererId); diff --git a/src/app/currentcall.cpp b/src/app/currentcall.cpp index 7ef2af43b..4238abc30 100644 --- a/src/app/currentcall.cpp +++ b/src/app/currentcall.cpp @@ -223,43 +223,14 @@ CurrentCall::updateCallInfo() set_isGrid(callInfo.layout == call::Layout::GRID); set_isAudioOnly(callInfo.isAudioOnly); - bool isAudioMuted {}; - bool isVideoMuted {}; - bool isSharing {}; - QString sharingSource {}; - bool isCapturing {}; - QString previewId {}; - using namespace libjami::Media; - if (callInfo.status != lrc::api::call::Status::ENDED) { - for (const auto& media : callInfo.mediaList) { - if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) { - if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY) - || media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) { - isSharing = true; - sharingSource = media[MediaAttributeKey::SOURCE]; - } - if (media[MediaAttributeKey::ENABLED] == TRUE_STR - && media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) { - previewId = media[libjami::Media::MediaAttributeKey::SOURCE]; - } - if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith( - libjami::Media::VideoProtocolPrefix::CAMERA)) { - isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR; - isCapturing = media[MediaAttributeKey::MUTED] == FALSE_STR; - } - } else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) { - if (media[MediaAttributeKey::LABEL] == "audio_0") { - isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR; - } - } - } - } - set_previewId(previewId); - set_isAudioMuted(isAudioMuted); - set_isVideoMuted(isVideoMuted); - set_isSharing(isSharing); - set_sharingSource(sharingSource); - set_isCapturing(isCapturing); + auto callInfoEx = callInfo.getCallInfoEx(); + set_previewId(callInfoEx["preview_id"].toString()); + set_isAudioMuted(callInfoEx["is_audio_muted"].toBool()); + set_isVideoMuted(callInfoEx["is_video_muted"].toBool()); + set_isSharing(callInfoEx["is_sharing"].toBool()); + set_sharingSource(isSharing_ ? callInfoEx["preview_id"].toString() : QString()); + set_isCapturing(callInfoEx["is_capturing"].toBool()); + set_isHandRaised(callModel->isHandRaised(id_)); set_isModerator(callModel->isModerator(id_)); diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp index bc90057e2..44d7ddeaf 100644 --- a/src/app/currentconversation.cpp +++ b/src/app/currentconversation.cpp @@ -18,6 +18,8 @@ #include "currentconversation.h" +#include "global.h" + #include <api/conversationmodel.h> #include <api/contact.h> @@ -264,51 +266,39 @@ void CurrentConversation::connectModel() { membersModel_->setMembers({}, {}, {}); - auto convModel = lrcInstance_->getCurrentConversationModel(); - if (!convModel) + + auto currentConversationModel = lrcInstance_->getCurrentConversationModel(); + auto currentCallModel = lrcInstance_->getCurrentCallModel(); + if (!currentConversationModel || !currentCallModel) { + C_DBG << "CurrentConversation: can't connect to unavailable models"; return; + } auto connectObjectSignal = [this](auto obj, auto signal, auto slot) { connect(obj, signal, this, slot, Qt::UniqueConnection); }; - connectObjectSignal(convModel, + connectObjectSignal(currentConversationModel, &ConversationModel::conversationUpdated, &CurrentConversation::onConversationUpdated); - connectObjectSignal(convModel, + connectObjectSignal(currentConversationModel, &ConversationModel::profileUpdated, &CurrentConversation::updateProfile); - - connect(lrcInstance_->getCurrentConversationModel(), - &ConversationModel::profileUpdated, - this, - &CurrentConversation::updateProfile, - Qt::UniqueConnection); - connect(lrcInstance_->getCurrentConversationModel(), - &ConversationModel::onConversationErrorsUpdated, - this, - &CurrentConversation::updateErrors, - Qt::UniqueConnection); - connect(lrcInstance_->getCurrentConversationModel(), - &ConversationModel::activeCallsChanged, - this, - &CurrentConversation::updateActiveCalls, - Qt::UniqueConnection); - connect(lrcInstance_->getCurrentConversationModel(), - &ConversationModel::conversationPreferencesUpdated, - this, - &CurrentConversation::updateConversationPreferences, - Qt::UniqueConnection); - connect(lrcInstance_->getCurrentConversationModel(), - &ConversationModel::needsHost, - this, - &CurrentConversation::onNeedsHost, - Qt::UniqueConnection); - connect(lrcInstance_->getCurrentCallModel(), - &CallModel::callStatusChanged, - this, - &CurrentConversation::onCallStatusChanged, - Qt::UniqueConnection); + connectObjectSignal(currentConversationModel, + &ConversationModel::conversationErrorsUpdated, + &CurrentConversation::updateErrors); + connectObjectSignal(currentConversationModel, + &ConversationModel::activeCallsChanged, + &CurrentConversation::updateActiveCalls); + connectObjectSignal(currentConversationModel, + &ConversationModel::conversationPreferencesUpdated, + &CurrentConversation::updateConversationPreferences); + connectObjectSignal(currentConversationModel, + &ConversationModel::needsHost, + &CurrentConversation::onNeedsHost); + connectObjectSignal(currentCallModel, + &CallModel::callStatusChanged, + &CurrentConversation::onCallStatusChanged); } void diff --git a/src/app/mainview/components/InCallLocalVideo.qml b/src/app/mainview/components/InCallLocalVideo.qml new file mode 100644 index 000000000..1b244b691 --- /dev/null +++ b/src/app/mainview/components/InCallLocalVideo.qml @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick +import QtQuick.Controls +import Qt5Compat.GraphicalEffects + +import net.jami.Enums 1.1 +import net.jami.Constants 1.1 +import net.jami.Adapters 1.1 + +import "../../commoncomponents" + +// This component uses anchors and they are set within this component. +LocalVideo { + id: localPreview + + required property var container + required property real opacityModifier + + readonly property int previewMargin: 15 + readonly property int previewMarginYTop: previewMargin + 42 + readonly property int previewMarginYBottom: previewMargin + 84 + + anchors.bottomMargin: previewMarginYBottom + anchors.leftMargin: sideMargin + anchors.rightMargin: sideMargin + anchors.topMargin: previewMarginYTop + + visibilityCondition: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) && + !CurrentCall.isConference + height: width * invAspectRatio + width: Math.max(container.width / 5, JamiTheme.minimumPreviewWidth) + flip: CurrentCall.flipSelf && !CurrentCall.isSharing + blurRadius: hidden ? 25 : 0 + + opacity: hidden ? opacityModifier : 1 + + // Allow hiding the preview (available when anchored) + readonly property bool hovered: hoverHandler.hovered + readonly property bool anchored: state !== "unanchored" + property bool hidden: false + readonly property real hiddenHandleSize: 32 + // Compute the margin as a function of the preview width in order to + // apply a negative margin and expose a constant width handle. + // If not hidden, return the previewMargin. + property real sideMargin: !hidden ? previewMargin : -(width - hiddenHandleSize) + // Animate the hiddenSize with a Behavior. + Behavior on sideMargin { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }} + readonly property bool onLeft: state.indexOf("left") !== -1 + PushButton { + id: hidePreviewButton + objectName: "hidePreviewButton" + + width: localPreview.hiddenHandleSize + state: localPreview.onLeft ? + (localPreview.hidden ? "right" : "left") : + (localPreview.hidden ? "left" : "right") + states: [ + State { + name: "left" + AnchorChanges { + target: hidePreviewButton + anchors.left: parent.left + } + }, + State { + name: "right" + AnchorChanges { + target: hidePreviewButton + anchors.right: parent.right + } + } + ] + anchors.top: parent.top + anchors.bottom: parent.bottom + opacity: (localPreview.anchored && localPreview.hovered) || localPreview.hidden + Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }} + visible: opacity > 0 + background: Rectangle { + readonly property color normalColor: JamiTheme.mediumGrey + color: JamiTheme.mediumGrey + opacity: hidePreviewButton.hovered ? 0.7 : 0.5 + Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }} + } + normalImageSource: hidePreviewButton.state === "left" ? + JamiResources.chevron_left_black_24dp_svg : + JamiResources.chevron_right_black_24dp_svg + imageColor: JamiTheme.darkGreyColor + onClicked: localPreview.hidden = !localPreview.hidden + toolTipText: localPreview.hidden ? + JamiStrings.showLocalVideo : + JamiStrings.hideLocalVideo + } + + state: "anchor_top_right" + states: [ + State { + name: "unanchored" + AnchorChanges { + target: localPreview + anchors.top: undefined + anchors.right: undefined + anchors.bottom: undefined + anchors.left: undefined + } + }, + State { + name: "anchor_top_left" + AnchorChanges { + target: localPreview + anchors.top: localPreview.container.top + anchors.left: localPreview.container.left + } + }, + State { + name: "anchor_top_right" + AnchorChanges { + target: localPreview + anchors.top: localPreview.container.top + anchors.right: localPreview.container.right + } + }, + State { + name: "anchor_bottom_right" + AnchorChanges { + target: localPreview + anchors.bottom: localPreview.container.bottom + anchors.right: localPreview.container.right + } + }, + State { + name: "anchor_bottom_left" + AnchorChanges { + target: localPreview + anchors.bottom: localPreview.container.bottom + anchors.left: localPreview.container.left + } + } + ] + + transitions: Transition { + AnchorAnimation { + duration: 250 + easing.type: Easing.OutBack + easing.overshoot: 1.5 + } + } + + HoverHandler { + id: hoverHandler + } + + DragHandler { + id: dragHandler + readonly property var container: localPreview.container + target: parent + dragThreshold: 4 + enabled: !localPreview.hidden + xAxis.maximum: container.width - parent.width - previewMargin + xAxis.minimum: previewMargin + yAxis.maximum: container.height - parent.height - previewMarginYBottom + yAxis.minimum: previewMarginYTop + onActiveChanged: { + if (active) { + localPreview.state = "unanchored"; + } else { + const center = Qt.point(target.x + target.width / 2, + target.y + target.height / 2); + const containerCenter = Qt.point(container.x + container.width / 2, + container.y + container.height / 2); + if (center.x >= containerCenter.x) { + if (center.y >= containerCenter.y) { + localPreview.state = "anchor_bottom_right"; + } else { + localPreview.state = "anchor_top_right"; + } + } else { + if (center.y >= containerCenter.y) { + localPreview.state = "anchor_bottom_left"; + } else { + localPreview.state = "anchor_top_left"; + } + } + } + } + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: localPreview.width + height: localPreview.height + radius: JamiTheme.primaryRadius + } + } +} diff --git a/src/app/mainview/components/OngoingCallPage.qml b/src/app/mainview/components/OngoingCallPage.qml index 429ca7d92..8c5f401fb 100644 --- a/src/app/mainview/components/OngoingCallPage.qml +++ b/src/app/mainview/components/OngoingCallPage.qml @@ -30,11 +30,6 @@ import "../../commoncomponents" Rectangle { id: root - // Constraints for the preview component. - property int previewMargin: 15 - property int previewMarginYTop: previewMargin + 42 - property int previewMarginYBottom: previewMargin + 84 - property alias chatViewContainer: chatViewContainer property string callPreviewId @@ -166,222 +161,15 @@ Rectangle { } } - LocalVideo { + // Note: this component should not be used within a layout, as + // it implements anchor management itself. + InCallLocalVideo { id: localPreview objectName: "localPreview" - readonly property var container: parent - readonly property string callPreviewId: root.callPreviewId - - visibilityCondition: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) && - !CurrentCall.isConference - height: width * invAspectRatio - - // Keep the area of the preview a proportion of the screen size plus a - // modifier to allow the user to scale it. - readonly property real containerArea: container.width * container.height - property real scalingFactor: 1 - width: Math.sqrt(containerArea / 16) * scalingFactor - flip: CurrentCall.flipSelf && !CurrentCall.isSharing - blurRadius: hidden ? 25 : 0 - onCallPreviewIdChanged: startWithId(callPreviewId) - onVisibleChanged: if (!visible) stop() - - anchors.topMargin: previewMarginYTop - anchors.leftMargin: sideMargin - anchors.rightMargin: sideMargin - anchors.bottomMargin: previewMarginYBottom - - opacity: hidden ? callOverlay.mainOverlayOpacity : 1 - - // Allow hiding the preview (available when anchored) - readonly property bool hovered: hoverHandler.hovered - readonly property bool anchored: state !== "unanchored" - property bool hidden: false - readonly property real hiddenHandleSize: 32 - // Compute the margin as a function of the preview width in order to - // apply a negative margin and expose a constant width handle. - // If not hidden, return the previewMargin. - property real sideMargin: !hidden ? previewMargin : -(width - hiddenHandleSize) - // Animate the hiddenSize with a Behavior. - Behavior on sideMargin { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }} - readonly property bool onLeft: state.indexOf("left") !== -1 - PushButton { - id: hidePreviewButton - objectName: "hidePreviewButton" - - width: localPreview.hiddenHandleSize - state: { - if (!localPreview.anchored) { - return "none"; - } - return localPreview.onLeft ? - (localPreview.hidden ? "right" : "left") : - (localPreview.hidden ? "left" : "right") - } - states: [ - State { - name: "none" - // Override visible to false when the localPreview isn't anchored. - PropertyChanges { - target: hidePreviewButton - visible: false - } - }, - State { - name: "left" - AnchorChanges { - target: hidePreviewButton - anchors.left: parent.left - } - }, - State { - name: "right" - AnchorChanges { - target: hidePreviewButton - anchors.right: parent.right - } - } - ] - anchors.top: parent.top - anchors.bottom: parent.bottom - opacity: (localPreview.anchored && localPreview.hovered) || localPreview.hidden - Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }} - visible: opacity > 0 - background: Rectangle { - readonly property color normalColor: JamiTheme.mediumGrey - color: JamiTheme.mediumGrey - opacity: hidePreviewButton.hovered ? 0.7 : 0.5 - Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }} - } - normalImageSource: hidePreviewButton.state === "left" ? - JamiResources.chevron_left_black_24dp_svg : - JamiResources.chevron_right_black_24dp_svg - imageColor: JamiTheme.darkGreyColor - onClicked: localPreview.hidden = !localPreview.hidden - toolTipText: localPreview.hidden ? - JamiStrings.showLocalVideo : - JamiStrings.hideLocalVideo - } - - state: "anchor_top_right" - states: [ - State { - name: "unanchored" - AnchorChanges { - target: localPreview - anchors.top: undefined - anchors.right: undefined - anchors.bottom: undefined - anchors.left: undefined - } - }, - State { - name: "anchor_top_left" - AnchorChanges { - target: localPreview - anchors.top: localPreview.container.top - anchors.left: localPreview.container.left - } - }, - State { - name: "anchor_top_right" - AnchorChanges { - target: localPreview - anchors.top: localPreview.container.top - anchors.right: localPreview.container.right - } - }, - State { - name: "anchor_bottom_right" - AnchorChanges { - target: localPreview - anchors.bottom: localPreview.container.bottom - anchors.right: localPreview.container.right - } - }, - State { - name: "anchor_bottom_left" - AnchorChanges { - target: localPreview - anchors.bottom: localPreview.container.bottom - anchors.left: localPreview.container.left - } - } - ] - - transitions: Transition { - AnchorAnimation { - duration: 250 - easing.type: Easing.OutBack - easing.overshoot: 1.5 - } - } - - HoverHandler { - id: hoverHandler - } - - WheelHandler { - onWheel: function(event) { - const delta = event.angleDelta.y / 120 * 0.1; - parent.opacity = JamiQmlUtils.clamp(parent.opacity + delta, 0.25, 1); - } - acceptedModifiers: Qt.CTRL - } - - WheelHandler { - onWheel: function(event) { - const delta = event.angleDelta.y / 120 * 0.1; - localPreview.scalingFactor = JamiQmlUtils.clamp(localPreview.scalingFactor + delta, 0.5, 4); - } - acceptedModifiers: Qt.NoModifier - enabled: !localPreview.hidden - } - - DragHandler { - id: dragHandler - readonly property var container: localPreview.container - target: parent - dragThreshold: 4 - enabled: !localPreview.hidden - xAxis.maximum: container.width - parent.width - previewMargin - xAxis.minimum: previewMargin - yAxis.maximum: container.height - parent.height - previewMarginYBottom - yAxis.minimum: previewMarginYTop - onActiveChanged: { - if (active) { - localPreview.state = "unanchored"; - } else { - const center = Qt.point(target.x + target.width / 2, - target.y + target.height / 2); - const containerCenter = Qt.point(container.x + container.width / 2, - container.y + container.height / 2); - if (center.x >= containerCenter.x) { - if (center.y >= containerCenter.y) { - localPreview.state = "anchor_bottom_right"; - } else { - localPreview.state = "anchor_top_right"; - } - } else { - if (center.y >= containerCenter.y) { - localPreview.state = "anchor_bottom_left"; - } else { - localPreview.state = "anchor_top_left"; - } - } - } - } - } - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: localPreview.width - height: localPreview.height - radius: JamiTheme.primaryRadius - } - } + container: parent + rendererId: CurrentCall.previewId + opacityModifier: callOverlay.mainOverlayOpacity } CallOverlay { diff --git a/src/app/settingsview/components/VideoSettingsPage.qml b/src/app/settingsview/components/VideoSettingsPage.qml index 7ce709e10..712b07ab4 100644 --- a/src/app/settingsview/components/VideoSettingsPage.qml +++ b/src/app/settingsview/components/VideoSettingsPage.qml @@ -80,13 +80,10 @@ SettingsPageBase { Component.onCompleted: { flipControl.checked = UtilsAdapter.getAppValue(Settings.FlipSelf); hardwareAccelControl.checked = AvAdapter.getHardwareAcceleration(); - if (previewWidget.visible) - startPreviewing(true); + startPreviewing(true); } - Component.onDestruction: { - previewWidget.startWithId(""); - } + Component.onDestruction: previewWidget.stop() // video Preview Rectangle { diff --git a/src/app/videodevices.cpp b/src/app/videodevices.cpp index 86eb9eed3..013d502f7 100644 --- a/src/app/videodevices.cpp +++ b/src/app/videodevices.cpp @@ -256,13 +256,15 @@ VideoDevices::startDevice(const QString& id, bool force) void VideoDevices::stopDevice(const QString& id) { - if (!id.isEmpty()) { - qInfo() << "Stopping device" << id; - if (lrcInstance_->avModel().stopPreview(id)) { - deviceOpen_ = false; - } else { - qWarning() << "Failed to stop device" << id; - } + if (id.isEmpty()) { + return; + } + + qInfo() << "Stopping device" << id; + if (lrcInstance_->avModel().stopPreview(id)) { + deviceOpen_ = false; + } else { + qWarning() << "Failed to stop device" << id; } } diff --git a/src/libclient/api/call.h b/src/libclient/api/call.h index 7328df4f6..d386e72a7 100644 --- a/src/libclient/api/call.h +++ b/src/libclient/api/call.h @@ -149,6 +149,48 @@ struct Info return true; return false; } + + // Extract some common meta data for this call including: + // - the video preview ID + // - audio/video muted status + // - if the call is sharing (indicating that the preview is a screen share) + QVariantMap getCallInfoEx() const + { + bool isAudioMuted = false; + bool isVideoMuted = false; + QString previewId; + QVariantMap callInfo; + using namespace libjami::Media; + if (status == lrc::api::call::Status::ENDED) { + return {}; + } + for (const auto& media : mediaList) { + if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) { + if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY) + || media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) { + callInfo["is_sharing"] = true; + callInfo["preview_id"] = media[MediaAttributeKey::SOURCE]; + } + if (media[MediaAttributeKey::ENABLED] == TRUE_STR + && media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) { + previewId = media[libjami::Media::MediaAttributeKey::SOURCE]; + } + if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith( + libjami::Media::VideoProtocolPrefix::CAMERA)) { + isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR; + callInfo["is_capturing"] = media[MediaAttributeKey::MUTED] == FALSE_STR; + } + } else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) { + if (media[MediaAttributeKey::LABEL] == "audio_0") { + isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR; + } + } + } + callInfo["preview_id"] = previewId; + callInfo["is_audio_muted"] = isAudioMuted; + callInfo["is_video_muted"] = isVideoMuted; + return callInfo; + } }; static inline bool diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h index 414ba3fbf..707ac3a75 100644 --- a/src/libclient/api/conversationmodel.h +++ b/src/libclient/api/conversationmodel.h @@ -464,7 +464,7 @@ Q_SIGNALS: * Emitted when a conversation detects an error * @param uid */ - void onConversationErrorsUpdated(const QString& uid) const; + void conversationErrorsUpdated(const QString& uid) const; /** * Emitted when conversation's preferences has been updated * @param uid diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp index 525c07d49..63d644dae 100644 --- a/src/libclient/conversationmodel.cpp +++ b/src/libclient/conversationmodel.cpp @@ -1089,7 +1089,7 @@ ConversationModel::popFrontError(const QString& conversationId) auto& conversation = conversationOpt->get(); conversation.errors.pop_front(); - Q_EMIT onConversationErrorsUpdated(conversationId); + Q_EMIT conversationErrorsUpdated(conversationId); } void @@ -2706,7 +2706,7 @@ ConversationModelPimpl::slotOnConversationError(const QString& accountId, try { auto& conversation = getConversationForUid(conversationId).get(); conversation.errors.push_back({code, what}); - Q_EMIT linked.onConversationErrorsUpdated(conversationId); + Q_EMIT linked.conversationErrorsUpdated(conversationId); } catch (...) { } } diff --git a/tests/qml/src/tst_OngoingCallPage.qml b/tests/qml/src/tst_OngoingCallPage.qml index 473d3af87..31b58d1f9 100644 --- a/tests/qml/src/tst_OngoingCallPage.qml +++ b/tests/qml/src/tst_OngoingCallPage.qml @@ -180,6 +180,17 @@ TestWrapper { compare(localPreview.hidden, false); }); } + + function test_localPreviewRemainsVisibleWhenOngoingCallPageIsToggled() { + localPreviewTestWrapper(function(localPreview) { + // The local preview should remain visible when the OngoingCallPage is toggled. + compare(localPreview.visible, true); + uut.visible = false; + compare(localPreview.visible, false); + uut.visible = true; + compare(localPreview.visible, true); + }); + } } } } -- GitLab