From 8a149b6c4f1a0a4957ce6e4f1e10e7daa9021237 Mon Sep 17 00:00:00 2001
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
Date: Mon, 8 Apr 2024 18:15:06 -0400
Subject: [PATCH] callview: use dynamic loading for call views

This commit replaces a StackLayout with a Loader allowing us to load initial and ongoing call views dynamically based on the current conversation's call state.

This may fix several issues related to conversation loading including a possible uncaught binding loop based on observing CurrentConversation.id changes.

- small header clean up

Change-Id: Idfc723d8b39f19aafb026c19f26590910b5c26cd
---
 src/app/calloverlaymodel.cpp                  | 46 +++++++++----------
 src/app/calloverlaymodel.h                    |  3 +-
 src/app/commoncomponents/LocalVideo.qml       |  2 +
 src/app/mainview/ConversationView.qml         | 28 ++++++-----
 src/app/mainview/components/CallStackView.qml | 37 +++++++++------
 src/app/mainview/components/MainOverlay.qml   | 14 ++----
 src/app/videodevices.cpp                      |  5 +-
 src/libclient/avmodel.cpp                     | 22 ++++-----
 src/libclient/lrc.cpp                         |  2 -
 9 files changed, 78 insertions(+), 81 deletions(-)

diff --git a/src/app/calloverlaymodel.cpp b/src/app/calloverlaymodel.cpp
index 5e6555216..8ace2b795 100644
--- a/src/app/calloverlaymodel.cpp
+++ b/src/app/calloverlaymodel.cpp
@@ -362,36 +362,32 @@ CallOverlayModel::clearControls()
 }
 
 void
-CallOverlayModel::registerFilter(QObject* object, QQuickItem* item)
+CallOverlayModel::setEventFilterActive(QObject* object, QQuickItem* item, bool isActive)
 {
     QQuickWindow* window = qobject_cast<QQuickWindow*>(object);
     if (!window || !item) {
-        C_WARN << "Attempting to register an invalid object or item" << object << item;
+        C_WARN << "Attempting to" << (isActive ? "register" : "unregister")
+               << "an invalid object or item" << window << item;
         return;
     }
-    if (watchedItems_.contains(item)) {
-        C_DBG << "Item already registered" << item;
-    }
-    watchedItems_.push_back(item);
-    if (watchedItems_.size() == 1) {
-        window->installEventFilter(this);
-    }
-}
-
-void
-CallOverlayModel::unregisterFilter(QObject* object, QQuickItem* item)
-{
-    QQuickWindow* window = qobject_cast<QQuickWindow*>(object);
-    if (!window || !item) {
-        C_WARN << "Attempting to unregister an invalid object or item" << object << item;
-        return;
-    }
-    if (!watchedItems_.contains(item)) {
-        C_DBG << "Item not registered" << item;
-    }
-    watchedItems_.removeOne(item);
-    if (watchedItems_.size() == 0) {
-        window->removeEventFilter(this);
+    if (isActive) {
+        if (watchedItems_.contains(item)) {
+            C_DBG << "Item already registered" << item;
+        } else {
+            watchedItems_.push_back(item);
+            if (watchedItems_.size() == 1) {
+                window->installEventFilter(this);
+            }
+        }
+    } else {
+        if (!watchedItems_.contains(item)) {
+            C_DBG << "Item not registered" << item;
+        } else {
+            watchedItems_.removeOne(item);
+            if (watchedItems_.size() == 0) {
+                window->removeEventFilter(this);
+            }
+        }
     }
 }
 
diff --git a/src/app/calloverlaymodel.h b/src/app/calloverlaymodel.h
index c940896ad..231293569 100644
--- a/src/app/calloverlaymodel.h
+++ b/src/app/calloverlaymodel.h
@@ -137,8 +137,7 @@ public:
     Q_INVOKABLE QVariant overflowHiddenModel();
     Q_INVOKABLE QVariant pendingConferenceesModel();
 
-    Q_INVOKABLE void registerFilter(QObject* object, QQuickItem* item);
-    Q_INVOKABLE void unregisterFilter(QObject* object, QQuickItem* item);
+    Q_INVOKABLE void setEventFilterActive(QObject* object, QQuickItem* item, bool isActive);
     bool eventFilter(QObject* object, QEvent* event) override;
 
 Q_SIGNALS:
diff --git a/src/app/commoncomponents/LocalVideo.qml b/src/app/commoncomponents/LocalVideo.qml
index c746f6d93..cf5346470 100644
--- a/src/app/commoncomponents/LocalVideo.qml
+++ b/src/app/commoncomponents/LocalVideo.qml
@@ -28,6 +28,8 @@ VideoView {
     crop: true
     visible: isRendering && visibilityCondition
 
+    Component.onDestruction: VideoDevices.stopDevice(rendererId);
+
     function startWithId(id, force = false) {
         if (id !== undefined && id.length === 0) {
             stop();
diff --git a/src/app/mainview/ConversationView.qml b/src/app/mainview/ConversationView.qml
index 9b8b7f0da..299602464 100644
--- a/src/app/mainview/ConversationView.qml
+++ b/src/app/mainview/ConversationView.qml
@@ -48,10 +48,10 @@ ListSelectionView {
     leftPaneItem: viewCoordinator.getView("SidePanel", true)
 
     rightPaneItem: StackLayout {
+        id: conversationStackLayout
         objectName: "ConversationLayout"
 
-        currentIndex: !CurrentConversation.hasCall ? 0 : 1
-        onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer
+        currentIndex: CurrentConversation.hasCall ? 1 : 0
 
         anchors.fill: parent
 
@@ -64,24 +64,28 @@ ListSelectionView {
             ChatView {
                 id: chatView
                 anchors.fill: parent
-                inCallView: parent == callStackView.chatViewContainer
+
+                // Parent the chat view to the call stack view when in call.
+                parent: callStackView.chatViewContainer ? callStackView.chatViewContainer : chatViewContainer
+                inCallView: parent === callStackView.chatViewContainer
 
                 readonly property string currentConvId: CurrentConversation.id
                 onCurrentConvIdChanged: {
-                    if (!CurrentConversation.hasCall) {
-                        Qt.callLater(focusChatView);
-                    } else {
-                        dismiss();
-                        callStackView.contentView.forceActiveFocus();
-                    }
+                    Qt.callLater(function() {
+                        if (CurrentConversation.hasCall) {
+                            callStackView.contentView.forceActiveFocus();
+                        } else {
+                            focusChatView();
+                        }
+                    });
                 }
 
                 onDismiss: {
-                    if (!inCallView) {
-                        viewNode.dismiss();
-                    } else {
+                    if (inCallView) {
                         callStackView.chatViewContainer.visible = false;
                         callStackView.contentView.forceActiveFocus();
+                    } else {
+                        viewNode.dismiss();
                     }
                 }
 
diff --git a/src/app/mainview/components/CallStackView.qml b/src/app/mainview/components/CallStackView.qml
index 88c651243..bd5d23dd8 100644
--- a/src/app/mainview/components/CallStackView.qml
+++ b/src/app/mainview/components/CallStackView.qml
@@ -25,7 +25,11 @@ import "../../commoncomponents"
 
 Item {
     id: root
-    property alias chatViewContainer: ongoingCallPage.chatViewContainer
+    property var chatViewContainer: {
+        if (callStackMainView.item instanceof OngoingCallPage)
+            return callStackMainView.item.chatViewContainer;
+        return undefined;
+    }
     property alias contentView: callStackMainView
 
     property var sipKeys: ["1", "2", "3", "A", "4", "5", "6", "B", "7", "8", "9", "C", "*", "0", "#", "D"]
@@ -61,44 +65,49 @@ Item {
     // TODO: this should all be done by listening to
     // parent visibility change or parent `Component.onDestruction`
     function needToCloseInCallConversationAndPotentialWindow() {
-        ongoingCallPage.closeInCallConversation();
-        ongoingCallPage.closeContextMenuAndRelatedWindows();
+        if (callStackMainView.item instanceof OngoingCallPage) {
+            callStackMainView.item.closeInCallConversation();
+            callStackMainView.item.closeContextMenuAndRelatedWindows();
+        }
     }
 
     function toggleFullScreen() {
         if (!layoutManager.isCallFullscreen) {
-            layoutManager.pushFullScreenItem(callStackMainView.currentItem, callStackMainView, null, null);
+            layoutManager.pushFullScreenItem(callStackMainView.item, callStackMainView, null, null);
         } else {
-            layoutManager.removeFullScreenItem(callStackMainView.currentItem);
+            layoutManager.removeFullScreenItem(callStackMainView.item);
         }
     }
 
-    StackLayout {
+    Loader {
         id: callStackMainView
 
         anchors.fill: parent
 
-        property Item currentItem: itemAt(currentIndex)
-
-        currentIndex: {
+        sourceComponent: {
             switch (CurrentCall.status) {
             case Call.Status.IN_PROGRESS:
             case Call.Status.CONNECTED:
             case Call.Status.PAUSED:
-                return 1;
+                return ongoingCallPageComponent;
             case Call.Status.SEARCHING:
             case Call.Status.CONNECTING:
             case Call.Status.INCOMING_RINGING:
             case Call.Status.OUTGOING_RINGING:
+                return initialCallPageComponent;
             default:
-                return 0;
+                return null;
             }
         }
 
-        InitialCallPage {
+        Component {
+            id: initialCallPageComponent
+            InitialCallPage {}
         }
-        OngoingCallPage {
-            id: ongoingCallPage
+
+        Component {
+            id: ongoingCallPageComponent
+            OngoingCallPage {}
         }
     }
 }
diff --git a/src/app/mainview/components/MainOverlay.qml b/src/app/mainview/components/MainOverlay.qml
index 9bc7863ba..4a48d745c 100644
--- a/src/app/mainview/components/MainOverlay.qml
+++ b/src/app/mainview/components/MainOverlay.qml
@@ -61,17 +61,9 @@ Item {
 
     opacity: 0
 
-    // (un)subscribe to an app-wide mouse move event trap filtered
-    // for the overlay's geometry
-    function setupFilter() {
-        if (visible) {
-            CallOverlayModel.registerFilter(appWindow, this);
-        } else {
-            CallOverlayModel.unregisterFilter(appWindow, this);
-        }
-    }
-    Component.onCompleted: setupFilter()
-    onVisibleChanged: setupFilter()
+    Component.onCompleted: CallOverlayModel.setEventFilterActive(appWindow, this, true)
+    Component.onDestruction: CallOverlayModel.setEventFilterActive(appWindow, this, false)
+    onVisibleChanged: CallOverlayModel.setEventFilterActive(appWindow, this, visible)
 
     Connections {
         target: CallOverlayModel
diff --git a/src/app/videodevices.cpp b/src/app/videodevices.cpp
index 013d502f7..f3003e43f 100644
--- a/src/app/videodevices.cpp
+++ b/src/app/videodevices.cpp
@@ -18,7 +18,7 @@
 
 #include "videodevices.h"
 
-#include "api/devicemodel.h"
+#include "global.h"
 
 VideoInputDeviceModel::VideoInputDeviceModel(LRCInstance* lrcInstance,
                                              VideoDevices* videoDeviceInstance)
@@ -260,11 +260,10 @@ VideoDevices::stopDevice(const QString& id)
         return;
     }
 
-    qInfo() << "Stopping device" << id;
     if (lrcInstance_->avModel().stopPreview(id)) {
         deviceOpen_ = false;
     } else {
-        qWarning() << "Failed to stop device" << id;
+        C_DBG << "Failed to stop device" << id;
     }
 }
 
diff --git a/src/libclient/avmodel.cpp b/src/libclient/avmodel.cpp
index 3712bfcb1..56285fce8 100644
--- a/src/libclient/avmodel.cpp
+++ b/src/libclient/avmodel.cpp
@@ -25,6 +25,8 @@
 #include "directrenderer.h"
 #else
 #include "shmrenderer.h"
+#include <csignal>
+#include <thread>
 #endif
 #include "callbackshandler.h"
 #include "dbus/callmanager.h"
@@ -42,11 +44,7 @@
 
 #include <algorithm> // std::sort
 #include <chrono>
-#include <csignal>
 #include <iomanip> // for std::put_time
-#include <fstream>
-#include <mutex>
-#include <thread>
 #include <string>
 #include <sstream>
 
@@ -328,7 +326,7 @@ void
 AVModel::setDeviceSettings(video::Settings& settings)
 {
     MapStringString newSettings;
-    auto rate = QString::number(settings.rate, 'f', 7);
+    auto rate = QString::number(static_cast<double>(settings.rate), 'f', 7);
     rate = rate.left(rate.length() - 1);
     newSettings["channel"] = settings.channel;
     newSettings["name"] = settings.name;
@@ -357,7 +355,7 @@ AVModel::getDeviceIdFromName(const QString& deviceName) const
         return settings.name == deviceName;
     });
     if (iter == devices.end()) {
-        qWarning() << "Couldn't find device: " << deviceName;
+        LC_WARN << "Couldn't find device: " << deviceName;
         return {};
     }
     return *iter;
@@ -491,7 +489,7 @@ void
 AVModel::stopLocalRecorder(const QString& path) const
 {
     if (path.isEmpty()) {
-        qWarning("stopLocalRecorder: can't stop non existing recording");
+        LC_WARN << "stopLocalRecorder: can't stop non existing recording";
         return;
     }
 
@@ -664,7 +662,7 @@ AVModel::getListWindows() const
                                                                      });
 
     if (xcb_connection_has_error(c.get())) {
-        qDebug() << "xcb connection has error";
+        LC_DBG << "xcb connection has error";
         return ret;
     }
 
@@ -690,7 +688,7 @@ AVModel::getListWindows() const
     propertyPtr replyPropList(xcb_get_property_reply(c.get(), propCookieList, &e),
                               [](auto* ptr) { free(ptr); });
     if (e) {
-        qDebug() << "Error: " << e->error_code;
+        LC_DBG << "Error: " << e->error_code;
         free(e);
     }
     if (replyPropList.get()) {
@@ -710,7 +708,7 @@ AVModel::getListWindows() const
                                            free(ptr);
                                        }};
                 if (e) {
-                    qDebug() << "Error: " << e->error_code;
+                    LC_DBG << "Error: " << e->error_code;
                     free(e);
                 }
                 if (replyProp.get()) {
@@ -987,7 +985,7 @@ AVModelPimpl::getDevice(int type) const
         if (deviceIdx < devices.size())
             result = devices.at(deviceIdx);
     } catch (std::bad_alloc& ba) {
-        qWarning() << "bad_alloc caught: " << ba.what();
+        LC_WARN << "bad_alloc caught: " << ba.what();
         return "";
     }
     return result;
@@ -1060,7 +1058,7 @@ AVModelPimpl::removeRenderer(const QString& id)
     QWriteLocker lk(&renderersMutex_);
     auto it = renderers_.find(id);
     if (it == renderers_.end()) {
-        qWarning() << "Cannot remove renderer. " << id << "not found";
+        LC_DBG << "Cannot remove renderer. " << id << " not found";
         return {};
     }
     auto removed = std::move(it->second);
diff --git a/src/libclient/lrc.cpp b/src/libclient/lrc.cpp
index e486e5cbf..46fc8c227 100644
--- a/src/libclient/lrc.cpp
+++ b/src/libclient/lrc.cpp
@@ -19,8 +19,6 @@
  ***************************************************************************/
 #include "api/lrc.h"
 
-#include <locale>
-
 #if !defined(_MSC_VER)
 #include <unistd.h>
 #endif
-- 
GitLab