From 7a34209583e863e083b9051d3e6a30c09ccb5517 Mon Sep 17 00:00:00 2001
From: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
Date: Thu, 13 Oct 2022 14:41:28 -0400
Subject: [PATCH] feature: documents flow in the swarmDetailsPanel

Change-Id: I24a94b9ced0ec3930a0b9e20f3fa0440e2d8fd00
Signed-off-by: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com>
---
 src/app/constant/JamiTheme.qml                |   7 +-
 .../components/DocumentsScrollview.qml        |  84 +++++++
 src/app/mainview/components/FilePreview.qml   | 113 ++++++++++
 src/app/mainview/components/MediaPreview.qml  | 205 ++++++++++++++++++
 .../mainview/components/SwarmDetailsPanel.qml |  18 +-
 src/app/messagesadapter.cpp                   |  49 +++++
 src/app/messagesadapter.h                     |   7 +
 src/app/qml.qrc                               |   3 +
 src/libclient/api/conversationmodel.h         |  15 ++
 src/libclient/callbackshandler.cpp            |  14 +-
 src/libclient/callbackshandler.h              |   8 +
 src/libclient/conversationmodel.cpp           |  59 +++++
 .../qtwrapper/configurationmanager_wrap.h     |  34 +++
 13 files changed, 610 insertions(+), 6 deletions(-)
 create mode 100644 src/app/mainview/components/DocumentsScrollview.qml
 create mode 100644 src/app/mainview/components/FilePreview.qml
 create mode 100644 src/app/mainview/components/MediaPreview.qml

diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 7712b000f..421367575 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -311,7 +311,6 @@ Item {
     property real preferredFieldHeight: 32
     property real preferredMarginSize: 16
     property real settingsMarginSize: 8
-    property real swarmDetailsPageTopMargin: 32
     property real preferredDialogWidth: 400
     property real preferredDialogHeight: 300
     property real minimumPreviewWidth: 120
@@ -337,6 +336,12 @@ Item {
     property real timestampFont: calcSize(12)
     property int timestampIntervalTime: 120
 
+    //swarmDetailsPage
+    property real swarmDetailsPageTopMargin: 32
+    property real swarmDetailsPageDocumentsMargins: 5
+    property real swarmDetailsPageDocumentsMediaRadius: 15
+    property real swarmDetailsPageDocumentsPaperClipSize: 24
+    property real swarmDetailsPageDocumentsMediaSize: 175
 
     // Jami switch
     property real switchIndicatorRadius: 30
diff --git a/src/app/mainview/components/DocumentsScrollview.qml b/src/app/mainview/components/DocumentsScrollview.qml
new file mode 100644
index 000000000..df516961d
--- /dev/null
+++ b/src/app/mainview/components/DocumentsScrollview.qml
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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 QtQuick.Layouts
+import Qt.labs.platform
+import Qt5Compat.GraphicalEffects
+import QtWebEngine
+
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+import "../../commoncomponents"
+import "../../settingsview/components"
+
+Flickable {
+    id: root
+
+    contentHeight: flow.implicitHeight
+    contentWidth: width
+
+    property int spacingFlow: JamiTheme.swarmDetailsPageDocumentsMargins
+    property int numberElementsPerRow: {
+        var sizeW = flow.width
+        var breakSize = JamiTheme.swarmDetailsPageDocumentsMediaSize
+        return Math.floor(sizeW / breakSize)
+    }
+    property int spacingLength: spacingFlow * (numberElementsPerRow - 1)
+
+    onVisibleChanged: {
+        if (visible) {
+            MessagesAdapter.getConvMedias()
+        } else {
+            MessagesAdapter.mediaMessageListModel = null
+        }
+    }
+    Flow {
+        id: flow
+
+        width: parent.width
+        spacing: spacingFlow
+        anchors.horizontalCenter: parent.horizontalCenter
+
+        Repeater {
+            model: MessagesAdapter.mediaMessageListModel
+
+            delegate: Loader {
+                id: loaderRoot
+
+                sourceComponent: {
+                    if(Status === Interaction.Status.TRANSFER_FINISHED || Status === Interaction.Status.SUCCESS ){
+                        if (Object.keys(MessagesAdapter.getMediaInfo(Body)).length !== 0 && WITH_WEBENGINE)
+                            return localMediaMsgComp
+
+                        return fileMsgComp
+                    }
+                }
+
+                FilePreview {
+                    id: fileMsgComp
+                }
+                MediaPreview {
+                    id: localMediaMsgComp
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/mainview/components/FilePreview.qml b/src/app/mainview/components/FilePreview.qml
new file mode 100644
index 000000000..57b1ec8b7
--- /dev/null
+++ b/src/app/mainview/components/FilePreview.qml
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 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 QtQuick.Layouts
+import Qt.labs.platform
+import Qt5Compat.GraphicalEffects
+import QtWebEngine
+
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+import "../../commoncomponents"
+import "../../settingsview/components"
+
+Component {
+    id: root
+
+    Rectangle {
+        id: dataTransferRect
+
+        clip: true
+        width: (documents.width - spacingLength ) / numberElementsPerRow
+        height: width
+        color: "transparent"
+
+        ColumnLayout{
+            anchors.fill: parent
+            anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins
+
+            Text {
+                id: myText
+
+                text: TransferName
+                color: JamiTheme.textColor
+                elide: Text.ElideRight
+                Layout.fillWidth: true
+                horizontalAlignment: Text.AlignHCenter
+            }
+            Rectangle {
+                Layout.preferredHeight: parent.height - myText.height - JamiTheme.swarmDetailsPageDocumentsMargins
+                Layout.preferredWidth: parent.width
+                Layout.rightMargin: JamiTheme.swarmDetailsPageDocumentsMargins
+                Layout.bottomMargin: JamiTheme.swarmDetailsPageDocumentsMargins
+                color: "transparent"
+
+                Rectangle {
+                    id: rectContent
+
+                    anchors.fill: parent
+                    anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins
+                    color: "transparent"
+                    border.color: CurrentConversation.color
+                    border.width: 2
+                    radius: JamiTheme.swarmDetailsPageDocumentsMediaRadius
+                    layer.enabled: true
+
+                    ResponsiveImage {
+                        id: paperClipImage
+
+                        source: JamiResources.link_black_24dp_svg
+                        width: parent.width / 2
+                        height: parent.height / 2
+                        anchors.centerIn: parent
+                        color: JamiTheme.textColor
+
+                        MouseArea {
+                            anchors.fill: parent
+                            hoverEnabled: true
+                            acceptedButtons: Qt.LeftButton | Qt.RightButton
+                            onEntered: {
+                                cursorShape = Qt.PointingHandCursor
+                            }
+
+                            onClicked: function (mouse) {
+                                if (mouse.button === Qt.RightButton) {
+                                    ctxMenu.x = mouse.x
+                                    ctxMenu.y = mouse.y
+                                    ctxMenu.openMenu()
+                                } else {
+                                    Qt.openUrlExternally("file://" + Body)
+                                }
+                            }
+                        }
+                        SBSContextMenu {
+                            id: ctxMenu
+
+                            msgId: Id
+                            location: Body
+                            transferId: Id
+                            transferName: TransferName
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/mainview/components/MediaPreview.qml b/src/app/mainview/components/MediaPreview.qml
new file mode 100644
index 000000000..0c33bf3e4
--- /dev/null
+++ b/src/app/mainview/components/MediaPreview.qml
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 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 QtQuick.Layouts
+import Qt.labs.platform
+import Qt5Compat.GraphicalEffects
+import QtWebEngine
+
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+import "../../commoncomponents"
+import "../../settingsview/components"
+
+Component {
+    id: root
+
+    Rectangle {
+        id: localMediaRect
+
+        width: (documents.width - spacingLength) /  numberElementsPerRow
+        height: width
+        color: "transparent"
+
+        ColumnLayout {
+            anchors.fill: parent
+            anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins
+
+            Text {
+                id: mediaName
+
+                text: TransferName
+                color: JamiTheme.textColor
+                elide: Text.ElideRight
+                Layout.fillWidth: true
+                horizontalAlignment: Text.AlignHCenter
+            }
+            Rectangle {
+                Layout.preferredHeight: parent.height - mediaName.height - JamiTheme.swarmDetailsPageDocumentsMargins
+                Layout.preferredWidth: parent.width
+                Layout.rightMargin: JamiTheme.swarmDetailsPageDocumentsMargins
+                Layout.bottomMargin: JamiTheme.swarmDetailsPageDocumentsMargins
+                color: "transparent"
+
+                Rectangle {
+                    id: rectContent
+
+                    anchors.fill: parent
+                    anchors.margins: JamiTheme.swarmDetailsPageDocumentsMargins
+                    color: CurrentConversation.color
+                    layer.enabled: true
+                    layer.effect: OpacityMask {
+                        maskSource: Item {
+                            width: localMediaCompLoader.width
+                            height: localMediaCompLoader.height
+                            Rectangle {
+                                anchors.centerIn: parent
+                                width:  localMediaCompLoader.width
+                                height: localMediaCompLoader.height
+                                radius: JamiTheme.swarmDetailsPageDocumentsMediaRadius
+                            }
+                        }
+                    }
+
+                    Loader {
+                        id: localMediaCompLoader
+
+                        property var mediaInfo: MessagesAdapter.getMediaInfo(Body)
+                        anchors.fill: parent
+                        anchors.margins: 2
+                        sourceComponent: {
+                            if (mediaInfo.isImage || mediaInfo.isAnimatedImage )
+                                return imageMediaComp
+                            else if (WITH_WEBENGINE)
+                                return avMediaComp
+                        }
+                        Component {
+                            id: avMediaComp
+
+                            Loader {
+                                property real msgRadius: 20
+
+                                Rectangle {
+                                    id: videoAudioRect
+                                    color: JamiTheme.secondaryBackgroundColor
+                                    anchors.fill: parent
+
+                                    WebEngineView {
+                                        id: wev
+
+                                        property bool isVideo: mediaInfo.isVideo
+                                        property string html: mediaInfo.html
+
+                                        anchors.fill: parent
+                                        anchors.verticalCenter: videoAudioRect.verticalCenter
+                                        backgroundColor: JamiTheme.secondaryBackgroundColor
+                                        anchors.topMargin: isVideo? 0 :  wev.implicitHeight / 2
+                                        settings.fullScreenSupportEnabled: isVideo
+                                        settings.javascriptCanOpenWindows: false
+                                        Component.onCompleted: loadHtml(html, 'file://')
+                                        onFullScreenRequested: function(request) {
+                                            if (request.toggleOn) {
+                                                layoutManager.pushFullScreenItem(
+                                                            this,
+                                                            videoAudioRect,
+                                                            null,
+                                                            function() { wev.fullScreenCancelled() })
+                                            } else if (!request.toggleOn) {
+                                                layoutManager.removeFullScreenItem(this)
+                                            }
+                                            request.accept()
+                                        }
+                                    }
+
+                                    layer.enabled: true
+                                    layer.effect: OpacityMask {
+                                        maskSource: Item {
+                                            width: videoAudioRect.width
+                                            height: videoAudioRect.height
+                                            Rectangle {
+                                                anchors.centerIn: parent
+                                                width:  videoAudioRect.width
+                                                height: videoAudioRect.height
+                                                radius: JamiTheme.swarmDetailsPageDocumentsMediaRadius
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        Component {
+                            id: imageMediaComp
+
+                            Image {
+                                id: fileImage
+
+                                anchors.fill: parent
+                                fillMode: Image.PreserveAspectCrop
+                                source: "file://" + Body
+                                layer.enabled: true
+                                layer.effect: OpacityMask {
+                                    maskSource: Item {
+                                        width: fileImage.width
+                                        height: fileImage.height
+                                        Rectangle {
+                                            anchors.centerIn: parent
+                                            width:  fileImage.width
+                                            height: fileImage.height
+                                            radius: JamiTheme.swarmDetailsPageDocumentsMediaRadius
+                                        }
+                                    }
+                                }
+                                MouseArea {
+                                    anchors.fill: parent
+                                    hoverEnabled: true
+                                    acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+                                    onEntered: {
+                                        cursorShape = Qt.PointingHandCursor
+                                    }
+
+                                    onClicked: function(mouse)  {
+                                        if (mouse.button === Qt.RightButton) {
+                                            ctxMenu.x = mouse.x
+                                            ctxMenu.y = mouse.y
+                                            ctxMenu.openMenu()
+                                        } else {
+                                            MessagesAdapter.openUrl(fileImage.source)
+                                        }
+                                    }
+                                }
+
+                                SBSContextMenu {
+                                    id: ctxMenu
+
+                                    msgId: Id
+                                    location: Body
+                                    transferId: Id
+                                    transferName: TransferName
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/mainview/components/SwarmDetailsPanel.qml b/src/app/mainview/components/SwarmDetailsPanel.qml
index 049a3cda4..0dd93bc29 100644
--- a/src/app/mainview/components/SwarmDetailsPanel.qml
+++ b/src/app/mainview/components/SwarmDetailsPanel.qml
@@ -20,6 +20,8 @@ import QtQuick
 import QtQuick.Controls
 import QtQuick.Layouts
 import Qt.labs.platform
+import Qt5Compat.GraphicalEffects
+import QtWebEngine
 
 import net.jami.Models 1.1
 import net.jami.Adapters 1.1
@@ -205,7 +207,7 @@ Rectangle {
                     }
                 }
 
-                /*FilterTabButton {
+                FilterTabButton {
                     id: documentsTabButton
                     backgroundColor: CurrentConversation.color
                     hoverColor: CurrentConversation.color
@@ -216,12 +218,12 @@ Rectangle {
 
                     textColorHovered: UtilsAdapter.luma(root.color) ? JamiTheme.placeholderTextColorWhite : JamiTheme.placeholderTextColor
                     textColor: UtilsAdapter.luma(root.color) ?
-                            JamiTheme.chatviewTextColorLight :
-                            JamiTheme.chatviewTextColorDark
+                                   JamiTheme.chatviewTextColorLight :
+                                   JamiTheme.chatviewTextColorDark
 
                     down: tabBar.currentIndex === 2
                     labelText: JamiStrings.documents
-                }*/
+                }
             }
         }
 
@@ -523,6 +525,14 @@ Rectangle {
                     }
                 }
             }
+
+            DocumentsScrollview {
+                id: documents
+
+                clip: true
+                visible: tabBar.currentIndex === 2
+                anchors.fill: parent
+            }
         }
     }
 }
diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp
index 3d9f70ba6..5f8c0ae99 100644
--- a/src/app/messagesadapter.cpp
+++ b/src/app/messagesadapter.cpp
@@ -49,6 +49,7 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager,
     : QmlAdapterBase(instance, parent)
     , settingsManager_(settingsManager)
     , previewEngine_(previewEngine)
+    , mediaInteractions_(std::make_unique<MessageListModel>())
 {
     connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() {
         set_replyToId("");
@@ -136,6 +137,12 @@ MessagesAdapter::connectConversationModel()
                      this,
                      &MessagesAdapter::onComposingStatusChanged,
                      Qt::UniqueConnection);
+
+    QObject::connect(currentConversationModel,
+                     &ConversationModel::messagesFoundProcessed,
+                     this,
+                     &MessagesAdapter::onMessagesFoundProcessed,
+                     Qt::UniqueConnection);
 }
 
 void
@@ -517,6 +524,33 @@ MessagesAdapter::onComposingStatusChanged(const QString& convId,
     }
 }
 
+void
+MessagesAdapter::onMessagesFoundProcessed(const QString& accountId,
+                                          const VectorMapStringString& messageIds,
+                                          const QVector<interaction::Info>& messageInformations)
+{
+    if (lrcInstance_->get_currentAccountId() != accountId) {
+        return;
+    }
+    bool isSearchInProgress = messageIds.length();
+    if (isSearchInProgress) {
+        int index = -1;
+        Q_FOREACH (const MapStringString& msg, messageIds) {
+            index++;
+            try {
+                std::pair<QString, interaction::Info> message(msg["id"],
+                                                              messageInformations.at(index));
+                mediaInteractions_->insert(message);
+            } catch (...) {
+                qWarning() << "error in onMessagesFoundProcessed, message insertion on index: "
+                           << index;
+            }
+        }
+    } else {
+        set_mediaMessageListModel(QVariant::fromValue(mediaInteractions_.get()));
+    }
+}
+
 QList<QString>
 MessagesAdapter::conversationTypersUrlToName(const QSet<QString>& typersSet)
 {
@@ -637,3 +671,18 @@ MessagesAdapter::getFormattedDay(const quint64 timestamp)
 
     return dateLocale;
 }
+
+void
+MessagesAdapter::getConvMedias()
+{
+    auto accountId = lrcInstance_->get_currentAccountId();
+    auto convId = lrcInstance_->get_selectedConvUid();
+
+    mediaInteractions_.reset(new MessageListModel(this));
+
+    try {
+        lrcInstance_->getCurrentConversationModel()->getConvMediasInfos(accountId, convId);
+    } catch (...) {
+        qDebug() << "Exception during getConvMedia:";
+    }
+}
diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h
index b059fc8e6..446f8b61e 100644
--- a/src/app/messagesadapter.h
+++ b/src/app/messagesadapter.h
@@ -34,6 +34,7 @@ class MessagesAdapter final : public QmlAdapterBase
     QML_PROPERTY(QString, replyToId)
     QML_PROPERTY(QString, editId)
     QML_RO_PROPERTY(QList<QString>, currentConvComposingList)
+    QML_PROPERTY(QVariant, mediaMessageListModel)
 
 public:
     explicit MessagesAdapter(AppSettingsManager* settingsManager,
@@ -94,6 +95,7 @@ protected:
     Q_INVOKABLE QVariantMap getTransferStats(const QString& messageId, int);
     Q_INVOKABLE QVariant dataForInteraction(const QString& interactionId,
                                             int role = Qt::DisplayRole) const;
+    Q_INVOKABLE void getConvMedias();
 
     // Run corrsponding js functions, c++ to qml.
     void setMessagesImageContent(const QString& path, bool isBased64 = false);
@@ -110,6 +112,9 @@ private Q_SLOTS:
     void onComposingStatusChanged(const QString& convId,
                                   const QString& contactUri,
                                   bool isComposing);
+    void onMessagesFoundProcessed(const QString& accountId,
+                                  const VectorMapStringString& messageIds,
+                                  const QVector<interaction::Info>& messageInformations);
 
 private:
     QList<QString> conversationTypersUrlToName(const QSet<QString>& typersSet);
@@ -118,4 +123,6 @@ private:
     PreviewEngine* previewEngine_;
 
     static constexpr const int loadChunkSize_ {20};
+
+    std::unique_ptr<MessageListModel> mediaInteractions_;
 };
diff --git a/src/app/qml.qrc b/src/app/qml.qrc
index 3560ee0bb..8f558284d 100644
--- a/src/app/qml.qrc
+++ b/src/app/qml.qrc
@@ -209,5 +209,8 @@
         <file>commoncomponents/MaterialTextField.qml</file>
         <file>commoncomponents/ModalTextEdit.qml</file>
         <file>commoncomponents/UsernameTextEdit.qml</file>
+        <file>mainview/components/DocumentsScrollview.qml</file>
+        <file>mainview/components/FilePreview.qml</file>
+        <file>mainview/components/MediaPreview.qml</file>
     </qresource>
 </RCC>
diff --git a/src/libclient/api/conversationmodel.h b/src/libclient/api/conversationmodel.h
index d0871caac..13e158431 100644
--- a/src/libclient/api/conversationmodel.h
+++ b/src/libclient/api/conversationmodel.h
@@ -302,6 +302,11 @@ public:
     void getTransferInfo(const QString& conversationId,
                          const QString& interactionId,
                          api::datatransfer::Info& info) const;
+
+    /**
+     * Starts a search of all medias and files in a conversation
+     */
+    void getConvMediasInfos(const QString& accountId, const QString& conversationId);
     /**
      * @param convUid, uid of the conversation
      * @return the number of unread messages for the conversation
@@ -584,6 +589,16 @@ Q_SIGNALS:
      */
     void dataChanged(int position) const;
 
+    /**
+     * Emitted once a message search has been done and processed
+     * @param accountId
+     * @param messageIds ids of all the messages found by the search
+     * @param messageInformations message datas
+     */
+    void messagesFoundProcessed(const QString& accountId,
+                                const VectorMapStringString& messageIds,
+                                const QVector<interaction::Info>& messageInformations) const;
+
 private:
     std::unique_ptr<ConversationModelPimpl> pimpl_;
 };
diff --git a/src/libclient/callbackshandler.cpp b/src/libclient/callbackshandler.cpp
index 416842a2d..19e4a4b5b 100644
--- a/src/libclient/callbackshandler.cpp
+++ b/src/libclient/callbackshandler.cpp
@@ -294,7 +294,6 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             this,
             &CallbacksHandler::slotAudioDeviceEvent,
             Qt::QueuedConnection);
-
     connect(&ConfigurationManager::instance(),
             &ConfigurationManagerInterface::audioMeter,
             this,
@@ -305,6 +304,11 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             this,
             &CallbacksHandler::slotConversationLoaded,
             Qt::QueuedConnection);
+    connect(&ConfigurationManager::instance(),
+            &ConfigurationManagerInterface::messagesFound,
+            this,
+            &CallbacksHandler::slotMessagesFound,
+            Qt::QueuedConnection);
     connect(&ConfigurationManager::instance(),
             &ConfigurationManagerInterface::messageReceived,
             this,
@@ -749,6 +753,14 @@ CallbacksHandler::slotConversationLoaded(uint32_t requestId,
 {
     Q_EMIT conversationLoaded(requestId, accountId, conversationId, messages);
 }
+void
+CallbacksHandler::slotMessagesFound(uint32_t requestId,
+                                    const QString& accountId,
+                                    const QString& conversationId,
+                                    const VectorMapStringString& messages)
+{
+    Q_EMIT messagesFound(requestId, accountId, conversationId, messages);
+}
 
 void
 CallbacksHandler::slotMessageReceived(const QString& accountId,
diff --git a/src/libclient/callbackshandler.h b/src/libclient/callbackshandler.h
index d14704231..4f3ad2711 100644
--- a/src/libclient/callbackshandler.h
+++ b/src/libclient/callbackshandler.h
@@ -350,6 +350,10 @@ Q_SIGNALS:
                             const QString& accountId,
                             const QString& conversationId,
                             const VectorMapStringString& messages);
+    void messagesFound(uint32_t requestId,
+                       const QString& accountId,
+                       const QString& conversationId,
+                       const VectorMapStringString& messages);
     void messageReceived(const QString& accountId,
                          const QString& conversationId,
                          const MapStringString& message);
@@ -668,6 +672,10 @@ private Q_SLOTS:
                                 const QString& accountId,
                                 const QString& conversationId,
                                 const VectorMapStringString& messages);
+    void slotMessagesFound(uint32_t requestId,
+                           const QString& accountId,
+                           const QString& conversationId,
+                           const VectorMapStringString& messages);
     void slotMessageReceived(const QString& accountId,
                              const QString& conversationId,
                              const MapStringString& message);
diff --git a/src/libclient/conversationmodel.cpp b/src/libclient/conversationmodel.cpp
index c904ad1df..5f1bb59b4 100644
--- a/src/libclient/conversationmodel.cpp
+++ b/src/libclient/conversationmodel.cpp
@@ -246,6 +246,7 @@ public:
 
     std::map<QString, std::mutex> interactionsLocks; ///< {convId, mutex}
     MapStringString transfIdToDbIntId;
+    uint32_t currentMsgRequestId;
 
 public Q_SLOTS:
     /**
@@ -355,6 +356,18 @@ public Q_SLOTS:
                                 const QString& accountId,
                                 const QString& conversationId,
                                 const VectorMapStringString& messages);
+    /**
+     * Listen messageFound signal.
+     * Is the search response from MessagesAdapter::getConvMedias()
+     * @param requestId token of the request
+     * @param accountId
+     * @param conversationId
+     * @param messages Id of all the messages
+     */
+    void slotMessagesFound(uint32_t requestId,
+                           const QString& accountId,
+                           const QString& conversationId,
+                           const VectorMapStringString& messages);
     void slotMessageReceived(const QString& accountId,
                              const QString& conversationId,
                              const MapStringString& message);
@@ -1865,6 +1878,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
             &CallbacksHandler::conversationLoaded,
             this,
             &ConversationModelPimpl::slotConversationLoaded);
+    connect(&callbacksHandler,
+            &CallbacksHandler::messagesFound,
+            this,
+            &ConversationModelPimpl::slotMessagesFound);
     connect(&callbacksHandler,
             &CallbacksHandler::messageReceived,
             this,
@@ -2013,6 +2030,10 @@ ConversationModelPimpl::~ConversationModelPimpl()
                &CallbacksHandler::conversationLoaded,
                this,
                &ConversationModelPimpl::slotConversationLoaded);
+    disconnect(&callbacksHandler,
+               &CallbacksHandler::messagesFound,
+               this,
+               &ConversationModelPimpl::slotMessagesFound);
     disconnect(&callbacksHandler,
                &CallbacksHandler::messageReceived,
                this,
@@ -2420,6 +2441,37 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
     }
 }
 
+void
+ConversationModelPimpl::slotMessagesFound(uint32_t requestId,
+                                          const QString& accountId,
+                                          const QString& conversationId,
+                                          const VectorMapStringString& messageIds)
+{
+    if (requestId != currentMsgRequestId) {
+        return;
+    }
+    QVector<interaction::Info> messageDetailedinformations;
+    Q_FOREACH (const MapStringString& msg, messageIds) {
+        auto intInfo = interaction::Info(msg, "");
+        if (intInfo.type == interaction::Type::DATA_TRANSFER) {
+            auto fileId = msg["fileId"];
+
+            QString path;
+            qlonglong bytesProgress, totalSize;
+            linked.owner.dataTransferModel->fileTransferInfo(accountId,
+                                                             conversationId,
+                                                             fileId,
+                                                             path,
+                                                             totalSize,
+                                                             bytesProgress);
+            intInfo.body = path;
+        }
+        messageDetailedinformations.append(intInfo);
+    }
+
+    Q_EMIT linked.messagesFoundProcessed(accountId, messageIds, messageDetailedinformations);
+}
+
 void
 ConversationModelPimpl::slotMessageReceived(const QString& accountId,
                                             const QString& conversationId,
@@ -3786,6 +3838,13 @@ ConversationModel::sendFile(const QString& convUid, const QString& path, const Q
     }
 }
 
+void
+ConversationModel::getConvMediasInfos(const QString& accountId, const QString& conversationId)
+{
+    pimpl_->currentMsgRequestId = ConfigurationManager::instance().searchConversation(
+        accountId, conversationId, "", "", "", "application/data-transfer+json", 0, 0, 0);
+};
+
 void
 ConversationModel::acceptTransfer(const QString& convUid, const QString& interactionId)
 {
diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h
index ad7328bf0..b280cea69 100644
--- a/src/libclient/qtwrapper/configurationmanager_wrap.h
+++ b/src/libclient/qtwrapper/configurationmanager_wrap.h
@@ -280,6 +280,16 @@ public:
                                                  QString(conversationId.c_str()),
                                                  convertVecMap(messages));
                    }),
+               exportable_callback<ConversationSignal::MessagesFound>(
+                   [this](uint32_t id,
+                          const std::string& accountId,
+                          const std::string& conversationId,
+                          const std::vector<std::map<std::string, std::string>>& messages) {
+                       Q_EMIT messagesFound(id,
+                                            QString(accountId.c_str()),
+                                            QString(conversationId.c_str()),
+                                            convertVecMap(messages));
+                   }),
                exportable_callback<ConversationSignal::MessageReceived>(
                    [this](const std::string& accountId,
                           const std::string& conversationId,
@@ -1151,6 +1161,26 @@ public Q_SLOTS: // METHODS
                                         fromId.toStdString(),
                                         authorUri.toStdString());
     }
+    uint32_t searchConversation(const QString& accountId,
+                                const QString& conversationId,
+                                const QString& author,
+                                const QString& lastId,
+                                const QString& regexSearch,
+                                const QString& type,
+                                const int64_t& after,
+                                const int64_t& before,
+                                const uint32_t& maxResult)
+    {
+        return DRing::searchConversation(accountId.toStdString(),
+                                         conversationId.toStdString(),
+                                         author.toStdString(),
+                                         lastId.toStdString(),
+                                         regexSearch.toStdString(),
+                                         type.toStdString(),
+                                         after,
+                                         before,
+                                         maxResult);
+    }
 Q_SIGNALS: // SIGNALS
     void volumeChanged(const QString& device, double value);
     void accountsChanged();
@@ -1224,6 +1254,10 @@ Q_SIGNALS: // SIGNALS
     void messageReceived(const QString& accountId,
                          const QString& conversationId,
                          const MapStringString& message);
+    void messagesFound(uint32_t requestId,
+                       const QString& accountId,
+                       const QString& conversationId,
+                       const VectorMapStringString& messages);
     void conversationProfileUpdated(const QString& accountId,
                                     const QString& conversationId,
                                     const MapStringString& profile);
-- 
GitLab