From 5530649f073ca39a19c518b151c8ba6a56a376ab Mon Sep 17 00:00:00 2001
From: Xavier Jouslin de Noray <xavier.jouslindenoray@savoirfairelinux.com>
Date: Wed, 28 Jun 2023 10:47:36 -0400
Subject: [PATCH] PluginStore: add view for plugin store

Gitlab: #1163
Change-Id: If9d9a27a296c5810b9f99126bed6453cc6ab6852
---
 resources/icons/plugins_default_icon.svg      |  35 ++
 .../commoncomponents/HeaderToggleSwitch.qml   |  75 ++++
 src/app/commoncomponents/ResponsiveImage.qml  |   1 +
 .../commoncomponents/SpinningAnimation.qml    |   7 +-
 src/app/constant/JamiStrings.qml              |  15 +-
 src/app/constant/JamiTheme.qml                |   4 +
 src/app/mainview/components/CachedImage.qml   |  22 +-
 src/app/pluginadapter.cpp                     |  64 +++-
 src/app/pluginadapter.h                       |  12 +-
 src/app/pluginlistmodel.cpp                   |  40 ++-
 src/app/pluginlistmodel.h                     |  16 +-
 src/app/pluginstorelistmodel.cpp              | 103 ++++--
 src/app/pluginstorelistmodel.h                |   9 +-
 src/app/pluginversionmanager.cpp              |  28 +-
 src/app/pluginversionmanager.h                |   2 +-
 src/app/qmlregister.cpp                       |   1 -
 src/app/settingsview/SettingsView.qml         |   4 +-
 .../components/InstallManuallyView.qml        |  78 +++++
 .../components/PluginAvailableDelagate.qml    | 211 ++++++++----
 .../components/PluginItemDelegate.qml         | 154 +++++----
 .../components/PluginListView.qml             | 181 +++++-----
 .../components/PluginPreferencesListView.qml  |  12 +-
 .../components/PluginPreferencesView.qml      | 322 +++++++++---------
 .../components/PluginSettingsPage.qml         |  89 ++++-
 .../components/PluginStoreListView.qml        | 115 +++----
 .../components/SettingsPageBase.qml           |  61 ++--
 src/app/utils.cpp                             |   6 +
 src/app/utils.h                               |   1 +
 src/libclient/api/pluginmodel.h               |   2 +-
 src/libclient/pluginmodel.cpp                 |  11 +-
 30 files changed, 1071 insertions(+), 610 deletions(-)
 create mode 100644 resources/icons/plugins_default_icon.svg
 create mode 100644 src/app/commoncomponents/HeaderToggleSwitch.qml
 create mode 100644 src/app/settingsview/components/InstallManuallyView.qml

diff --git a/resources/icons/plugins_default_icon.svg b/resources/icons/plugins_default_icon.svg
new file mode 100644
index 000000000..cf331309b
--- /dev/null
+++ b/resources/icons/plugins_default_icon.svg
@@ -0,0 +1,35 @@
+<?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 428 428" style="enable-background:new 0 0 428 428;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);}
+	.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);}
+	.st2{fill:url(#SVGID_3_);}
+	.st3{fill:url(#SVGID_4_);}
+</style>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-12.6549" y1="165.4979" x2="165.4206" y2="343.5734">
+	<stop  offset="0" style="stop-color:#3A3A3A"/>
+	<stop  offset="1" style="stop-color:#818181"/>
+</linearGradient>
+<path class="st0" d="M1.6,154v21.5v1.4c0.2,0,0.4,0,0.6,0c21.8,0,39.5,17.7,39.5,39.5c0,21.8-17.7,39.5-39.5,39.5
+	c-0.2,0-0.4,0-0.6,0V408H101V152c0.2,0.1,0.3,0.1,0.5,0.2V51.4L1.6,154z"/>
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="227.3426" y1="84.8295" x2="405.4313" y2="262.9182">
+	<stop  offset="0" style="stop-color:#3A3A3A"/>
+	<stop  offset="1" style="stop-color:#717171"/>
+</linearGradient>
+<path class="st1" d="M386.9,176.8L386.9,176.8l0-157.8h-93.7v256c-0.1-0.1-0.3-0.1-0.4-0.2v100.8l94.2-96.7v-23.2h0
+	c21.8,0,39.5-17.7,39.5-39.5C426.4,194.5,408.7,176.8,386.9,176.8z"/>
+<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="1.6097" y1="86.4966" x2="395.2845" y2="86.4966">
+	<stop  offset="0" style="stop-color:#818181"/>
+	<stop  offset="1" style="stop-color:#3A3A3A"/>
+</linearGradient>
+<path class="st2" d="M181.3,19C126.4,19,1.6,31.5,1.6,154c0,0,49.3-42.7,136.9-25s160.9-0.7,207.6-49.1
+	c18.6-19.3,32.4-39.6,40.2-60.9H181.3z"/>
+<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="-553.2536" y1="-1072.2665" x2="-159.5788" y2="-1072.2665" gradientTransform="matrix(-1 0 0 -1 -166.3346 -728.8083)">
+	<stop  offset="0" style="stop-color:#818181"/>
+	<stop  offset="1" style="stop-color:#3A3A3A"/>
+</linearGradient>
+<path class="st3" d="M207.2,408c54.9,0,179.7-11.9,179.7-129.1c0,0-49.3,40.8-136.9,23.9c-87.6-16.9-160.9,0.7-207.6,47
+	C23.8,368.3,10.1,387.6,2.2,408H207.2z"/>
+</svg>
\ No newline at end of file
diff --git a/src/app/commoncomponents/HeaderToggleSwitch.qml b/src/app/commoncomponents/HeaderToggleSwitch.qml
new file mode 100644
index 000000000..938e10430
--- /dev/null
+++ b/src/app/commoncomponents/HeaderToggleSwitch.qml
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022-2023 Savoir-faire Linux Inc.
+ * Author: Xavier Jouslin <xavier.jouslindenoray@savoirfairelinux.com>
+ *
+ * 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 net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+RowLayout {
+    id: root
+    property string labelText: ""
+    property int widthOfSwitch: 50
+    property int heightOfSwitch: 10
+
+    property string tooltipText: ""
+
+    property alias toggleSwitch: autoupdate
+    property alias checked: autoupdate.checked
+
+    signal switchToggled
+    Layout.alignment: Qt.AlignRight
+    JamiSwitch {
+        id: autoupdate
+        Layout.alignment: Qt.AlignLeft
+
+        Layout.preferredWidth: widthOfSwitch
+
+        hoverEnabled: true
+        toolTipText: tooltipText
+
+        Accessible.role: Accessible.Button
+        Accessible.name: JamiStrings.autoUpdate
+        Accessible.description: root.tooltipText
+
+        onToggled: switchToggled()
+    }
+    Text {
+        id: description
+        Layout.rightMargin: JamiTheme.preferredMarginSize
+        text: JamiStrings.autoUpdate
+        font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+        visible: labelText !== ""
+        font.kerning: true
+        wrapMode: Text.WordWrap
+        verticalAlignment: Text.AlignVCenter
+
+        color: JamiTheme.textColor
+    }
+    TapHandler {
+        target: parent
+        enabled: parent.visible
+        onTapped: function onTapped(eventPoint) {
+            // switchToggled should be emitted as onToggled is not called (because it's only called if the user click on the switch)
+            autoupdate.toggle();
+            switchToggled();
+        }
+    }
+}
diff --git a/src/app/commoncomponents/ResponsiveImage.qml b/src/app/commoncomponents/ResponsiveImage.qml
index c1fcc63b4..4ba427322 100644
--- a/src/app/commoncomponents/ResponsiveImage.qml
+++ b/src/app/commoncomponents/ResponsiveImage.qml
@@ -68,6 +68,7 @@ Item {
         antialiasing: true
         asynchronous: true
         visible: false
+        mipmap: true
 
         function setSourceSize() {
             sourceSize = undefined;
diff --git a/src/app/commoncomponents/SpinningAnimation.qml b/src/app/commoncomponents/SpinningAnimation.qml
index 740df45d4..d450e0d5c 100644
--- a/src/app/commoncomponents/SpinningAnimation.qml
+++ b/src/app/commoncomponents/SpinningAnimation.qml
@@ -32,9 +32,8 @@ Item {
     property int spinningAnimationWidth: 4
     property real outerCutRadius: root.height / 2
     property int spinningAnimationDuration: 1000
-
+    property color color: "white"
     visible: mode !== SpinningAnimation.Mode.Disabled
-
     ConicalGradient {
         id: conicalGradientOne
 
@@ -48,7 +47,7 @@ Item {
             }
             GradientStop {
                 position: 1.0
-                color: "white"
+                color: mode === SpinningAnimation.Mode.Disabled ? "transparent" : root.color
             }
         }
 
@@ -90,7 +89,7 @@ Item {
             }
             GradientStop {
                 position: 1.0
-                color: "white"
+                color: mode === SpinningAnimation.Mode.Disabled ? "transparent" : root.color
             }
         }
 
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 4278a99d3..06122b9d5 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -166,7 +166,7 @@ Item {
     property string back: qsTr("Back")
     property string accountSettingsMenuTitle: qsTr("Account")
     property string generalSettingsTitle: qsTr("General")
-    property string pluginSettingsTitle: qsTr("Plugins")
+    property string pluginSettingsTitle: qsTr("Extensions")
     property string enableAccountSettingsTitle: qsTr("Enable account")
     property string manageAccountSettingsTitle: qsTr("Manage account")
     property string linkedDevicesSettingsTitle: qsTr("Linked devices")
@@ -659,9 +659,19 @@ Item {
 
     // Plugins
     property string enable: qsTr("Enable")
+    property string autoUpdate: qsTr("Auto update")
+    property string disableAll: qsTr("Disable all")
+    property string installed: qsTr("Installed")
+    property string install: qsTr("Install")
+    property string installing: qsTr("Installing")
+    property string installManually: qsTr("Install manually")
+    property string installMannuallyDescription: qsTr("Install an extension directly from your device.")
+    property string pluginStoreTitle: qsTr("Available")
+    property string pluginStoreNotAvailable: qsTr("Plugins store is not available")
     property string pluginPreferences: qsTr("Preferences")
+    property string installationFailed: qsTr("Installation failed")
+    property string pluginInstallationFailed: qsTr("The installation of the plugin failed")
     property string reset: qsTr("Reset")
-    property string disableAll: qsTr("Disable all")
     property string uninstall: qsTr("Uninstall")
     property string resetPreferences: qsTr("Reset Preferences")
     property string selectPluginInstall: qsTr("Select a plugin to install")
@@ -677,7 +687,6 @@ Item {
     property string chooseImageFile: qsTr("Choose image file")
     property string tipGeneralPluginSettingsDisplay: qsTr("Display or hide General plugin settings")
     property string tipAccountPluginSettingsDisplay: qsTr("Display or hide Account plugin settings")
-    property string installedPlugins: qsTr("Installed plugins")
     property string pluginFiles: qsTr("Plugin Files (*.jpl)")
     property string loadUnload: qsTr("Load/Unload")
     property string selectAnImage: qsTr("Select An Image to %1")
diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml
index 3d39ed34a..4b0f5c8d4 100644
--- a/src/app/constant/JamiTheme.qml
+++ b/src/app/constant/JamiTheme.qml
@@ -383,6 +383,10 @@ Item {
     property real minimumMapWidth: 250
     property real pluginHandlersPopupViewHeight: 200
     property real pluginHandlersPopupViewDelegateHeight: 50
+    property color pluginDefaultBackgroundColor: "#666666"
+    property real remotePluginWidthDelegate: 350
+    property real remotePluginHeightDelegate: 400
+    property color pluginViewBackgroundColor: darkTheme ? "#000000" : "#F0EFEF"
     property real secondaryDialogDimension: 500
 
     property real lineEditContextMenuItemsHeight: 15
diff --git a/src/app/mainview/components/CachedImage.qml b/src/app/mainview/components/CachedImage.qml
index 50382a042..4d770c0b5 100644
--- a/src/app/mainview/components/CachedImage.qml
+++ b/src/app/mainview/components/CachedImage.qml
@@ -42,15 +42,11 @@ Item {
         antialiasing: true
         property bool isGif: getIsGif(this)
 
-        Image {
-            id: default_img
-            objectName: "default_img"
-            anchors.fill: parent
-            source: defaultImage
-            visible: image.status != Image.Ready
-            smooth: true
-            antialiasing: true
-            property bool isGif: getIsGif(this)
+        source: defaultImage
+        onStatusChanged: {
+            if (status === Image.Error) {
+                source = defaultImage;
+            }
         }
     }
 
@@ -70,13 +66,6 @@ Item {
         function onDownloadImageSuccessful(localPath) {
             if (localPath === cachedImage.localPath) {
                 image.source = "file://" + localPath;
-                print("onDownloadImageSuccessful", localPath);
-            }
-        }
-        function onDownloadImageFailed(localPath) {
-            if (localPath === cachedImage.localPath) {
-                print("Failed to download image: " + downloadUrl);
-                image.source = defaultImage;
             }
         }
     }
@@ -99,7 +88,6 @@ Item {
         }
         if (downloadUrl && downloadUrl !== "" && localPath !== "") {
             if (!UtilsAdapter.fileExists(localPath)) {
-                print("ImageDownloader.downloadImage", downloadUrl, localPath);
                 ImageDownloader.downloadImage(downloadUrl, localPath);
             } else {
                 image.source = "file://" + localPath;
diff --git a/src/app/pluginadapter.cpp b/src/app/pluginadapter.cpp
index 8b2a464fa..bbf5ee606 100644
--- a/src/app/pluginadapter.cpp
+++ b/src/app/pluginadapter.cpp
@@ -18,9 +18,13 @@
 
 #include "pluginadapter.h"
 
+#include "pluginversionmanager.h"
+#include "pluginlistmodel.h"
+#include "pluginstorelistmodel.h"
 #include "networkmanager.h"
 #include "lrcinstance.h"
 #include "utilsadapter.h"
+#include "qmlregister.h"
 
 #include <QJsonArray>
 #include <QJsonDocument>
@@ -30,14 +34,16 @@
 
 PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent, QString baseUrl)
     : QmlAdapterBase(instance, parent)
-    , pluginStoreListModel_(new PluginStoreListModel(this))
+    , pluginStoreListModel_(new PluginStoreListModel(instance, this))
     , pluginVersionManager_(new PluginVersionManager(instance, baseUrl, this))
-    , pluginListModel_(new PluginListModel(this))
+    , pluginListModel_(new PluginListModel(instance, this))
     , lrcInstance_(instance)
     , tempPath_(QDir::tempPath())
     , baseUrl_(baseUrl)
 
 {
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, pluginStoreListModel_, "PluginStoreListModel");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, pluginListModel_, "PluginListModel")
     set_isEnabled(lrcInstance_->pluginModel().getPluginsEnabled());
     updateHandlersListCount();
     connect(&lrcInstance_->pluginModel(),
@@ -75,24 +81,34 @@ PluginAdapter::PluginAdapter(LRCInstance* instance, QObject* parent, QString bas
 void
 PluginAdapter::getPluginsFromStore()
 {
-    pluginVersionManager_->sendGetRequest(QUrl(baseUrl_), [this](const QByteArray& data) {
-        auto result = QJsonDocument::fromJson(data).array();
-        auto pluginsInstalled = lrcInstance_->pluginModel().getPluginsId();
-        QList<QVariantMap> plugins;
-        for (const auto& plugin : result) {
-            auto qPlugin = plugin.toVariant().toMap();
-            if (!pluginsInstalled.contains(qPlugin["id"].toString())) {
-                plugins.append(qPlugin);
-            }
-        }
-        pluginStoreListModel_->setPlugins(plugins);
-    });
+    const auto& errorHandler = connect(pluginVersionManager_,
+                                       &PluginVersionManager::errorOccurred,
+                                       this,
+                                       [this](NetworkManager::GetError error, const QString& msg) {
+                                           Q_EMIT storeNotAvailable();
+                                       });
+    pluginVersionManager_
+        ->sendGetRequest(QUrl(baseUrl_ + "?arch=" + Utils::getPlatformString()),
+                         [this, errorHandler](const QByteArray& data) {
+                             auto result = QJsonDocument::fromJson(data).array();
+                             auto pluginsInstalled = lrcInstance_->pluginModel().getPluginsId();
+                             QList<QVariantMap> plugins;
+                             for (const auto& plugin : result) {
+                                 auto qPlugin = plugin.toVariant().toMap();
+                                 if (!pluginsInstalled.contains(qPlugin["name"].toString())) {
+                                     plugins.append(qPlugin);
+                                 }
+                             }
+                             pluginStoreListModel_->setPlugins(plugins);
+                             disconnect(errorHandler);
+                         });
 }
 
 void
 PluginAdapter::getPluginDetails(const QString& pluginId)
 {
-    pluginVersionManager_->sendGetRequest(QUrl(baseUrl_ + "/details/" + pluginId),
+    pluginVersionManager_->sendGetRequest(QUrl(baseUrl_ + "/details/" + pluginId
+                                               + "?arch=" + Utils::getPlatformString()),
                                           [this](const QByteArray& data) {
                                               auto result = QJsonDocument::fromJson(data).object();
                                               // my response is a json object and I want to convert
@@ -114,6 +130,18 @@ PluginAdapter::isAutoUpdaterEnabled()
     return pluginVersionManager_->isAutoUpdaterEnabled();
 }
 
+void
+PluginAdapter::setAutoUpdate(bool state)
+{
+    pluginVersionManager_->setAutoUpdate(state);
+}
+
+void
+PluginAdapter::cancelDownload(const QString& pluginId)
+{
+    pluginVersionManager_->cancelUpdate(pluginId);
+}
+
 QVariant
 PluginAdapter::getMediaHandlerSelectableModel(const QString& callId)
 {
@@ -169,3 +197,9 @@ PluginAdapter::baseUrl() const
 {
     return baseUrl_;
 }
+
+QString
+PluginAdapter::getIconUrl(const QString& pluginId) const
+{
+    return baseUrl_ + "/icons/" + pluginId + "?arch=" + Utils::getPlatformString();
+}
diff --git a/src/app/pluginadapter.h b/src/app/pluginadapter.h
index f994b089f..f168726f6 100644
--- a/src/app/pluginadapter.h
+++ b/src/app/pluginadapter.h
@@ -30,6 +30,9 @@
 #include <QSortFilterProxyModel>
 #include <QString>
 
+class PluginVersionManager;
+class PluginStoreListModel;
+
 class PluginAdapter final : public QmlAdapterBase
 {
     Q_OBJECT
@@ -49,6 +52,9 @@ public:
     Q_INVOKABLE QString baseUrl() const;
     Q_INVOKABLE void checkVersionStatus(const QString& pluginId);
     Q_INVOKABLE bool isAutoUpdaterEnabled();
+    Q_INVOKABLE void cancelDownload(const QString& pluginId);
+    Q_INVOKABLE void setAutoUpdate(bool state);
+    Q_INVOKABLE QString getIconUrl(const QString& pluginId) const;
 
 protected:
     Q_INVOKABLE QVariant getMediaHandlerSelectableModel(const QString& callId);
@@ -57,14 +63,18 @@ protected:
     Q_INVOKABLE QVariant getPluginPreferencesCategories(const QString& pluginId,
                                                         const QString& accountId,
                                                         bool removeLast = false);
+Q_SIGNALS:
+    void storeNotAvailable();
 
 private:
     void updateHandlersListCount();
 
-    std::unique_ptr<PluginHandlerListModel> pluginHandlerListModel_;
     PluginStoreListModel* pluginStoreListModel_;
     PluginVersionManager* pluginVersionManager_;
     PluginListModel* pluginListModel_;
+
+    std::unique_ptr<PluginHandlerListModel> pluginHandlerListModel_;
+
     LRCInstance* lrcInstance_;
     std::mutex mtx_;
     QString tempPath_;
diff --git a/src/app/pluginlistmodel.cpp b/src/app/pluginlistmodel.cpp
index 3dc41db43..670ee5c8c 100644
--- a/src/app/pluginlistmodel.cpp
+++ b/src/app/pluginlistmodel.cpp
@@ -22,9 +22,12 @@
 
 #include "api/pluginmodel.h"
 
-PluginListModel::PluginListModel(QObject* parent)
+PluginListModel::PluginListModel(LRCInstance* lrcInstance, QObject* parent)
     : AbstractListModelBase(parent)
-{}
+    , lrcInstance_(lrcInstance)
+{
+    reset();
+}
 
 PluginListModel::~PluginListModel() {}
 
@@ -55,16 +58,20 @@ PluginListModel::data(const QModelIndex& index, int role) const
     }
 
     auto details = lrcInstance_->pluginModel().getPluginDetails(installedPlugins_.at(index.row()));
-
+    installedPlugins_.at(index.row());
     switch (role) {
     case Role::PluginName:
         return QVariant(details.name);
+    case Role::PluginDescription:
+        return QVariant(details.description);
     case Role::PluginId:
         return QVariant(installedPlugins_.at(index.row()));
     case Role::PluginIcon:
         return QVariant(details.iconPath);
     case Role::IsLoaded:
         return QVariant(details.loaded);
+    case Role::Status:
+        return QVariant(pluginStatus_.value(installedPlugins_.at(index.row())));
     }
     return QVariant();
 }
@@ -77,7 +84,8 @@ PluginListModel::roleNames() const
     roles[PluginId] = "PluginId";
     roles[PluginIcon] = "PluginIcon";
     roles[IsLoaded] = "IsLoaded";
-
+    roles[Status] = "Status";
+    roles[PluginDescription] = "PluginDescription";
     return roles;
 }
 
@@ -87,6 +95,9 @@ PluginListModel::reset()
     beginResetModel();
     installedPlugins_.clear();
     installedPlugins_ = lrcInstance_->pluginModel().getInstalledPlugins();
+    for (auto plugin : installedPlugins_) {
+        pluginStatus_[plugin] = PluginStatus::INSTALLED;
+    }
     filterPlugins(installedPlugins_);
     endResetModel();
 }
@@ -97,6 +108,7 @@ PluginListModel::removePlugin(int index)
     beginRemoveRows(QModelIndex(), index, index);
     installedPlugins_.removeAt(index);
     endRemoveRows();
+    reset();
 }
 
 void
@@ -120,9 +132,18 @@ PluginListModel::addPlugin()
         index++;
     }
 
-    beginInsertRows(QModelIndex(), index, index);
-    installedPlugins_ = newList;
-    endInsertRows();
+    reset();
+}
+
+void
+PluginListModel::disableAllPlugins()
+{
+    for (auto& plugin : installedPlugins_) {
+        auto& pluginModel = lrcInstance_->pluginModel();
+        const auto& details = pluginModel.getPluginDetails(plugin);
+        pluginModel.unloadPlugin(details.path);
+        disabled(details.path);
+    }
 }
 
 void
@@ -165,14 +186,11 @@ PluginListModel::onVersionStatusChanged(const QString& pluginId, PluginStatus::R
     if (pluginIndex == -1) {
         return;
     }
-
+    pluginStatus_[pluginId] = status;
     switch (status) {
     case PluginStatus::INSTALLABLE:
         removePlugin(pluginIndex);
         break;
-    case PluginStatus::FAILED:
-        qWarning() << "Failed to install plugin" << pluginId;
-        break;
     default:
         break;
     }
diff --git a/src/app/pluginlistmodel.h b/src/app/pluginlistmodel.h
index d5c445183..84dd1c010 100644
--- a/src/app/pluginlistmodel.h
+++ b/src/app/pluginlistmodel.h
@@ -28,10 +28,17 @@ class PluginListModel : public AbstractListModelBase
     Q_OBJECT
     QML_PROPERTY(bool, filterAccount)
 public:
-    enum Role { PluginName = Qt::UserRole + 1, PluginId, PluginIcon, IsLoaded };
+    enum Role {
+        PluginName = Qt::UserRole + 1,
+        PluginDescription,
+        PluginId,
+        PluginIcon,
+        IsLoaded,
+        Status
+    };
     Q_ENUM(Role)
 
-    explicit PluginListModel(QObject* parent = nullptr);
+    explicit PluginListModel(LRCInstance* lrcInstance, QObject* parent = nullptr);
     ~PluginListModel();
 
     /*
@@ -52,16 +59,19 @@ public:
     Q_INVOKABLE void removePlugin(int index);
     Q_INVOKABLE void pluginChanged(int index);
     Q_INVOKABLE void addPlugin();
+    Q_INVOKABLE void disableAllPlugins();
 
 Q_SIGNALS:
     void versionCheckRequested(const QString& pluginId);
     void setVersionStatus(const QString& pluginId, PluginStatus::Role status);
     void autoUpdateChanged(bool state);
-
+    void disabled(const QString& pluginId);
 public Q_SLOTS:
     void onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status);
 
 private:
+    LRCInstance* lrcInstance_ = nullptr;
     void filterPlugins(VectorString& list) const;
     VectorString installedPlugins_ {};
+    QMap<QString, PluginStatus::Role> pluginStatus_ {};
 };
diff --git a/src/app/pluginstorelistmodel.cpp b/src/app/pluginstorelistmodel.cpp
index a90b79828..769954a7d 100644
--- a/src/app/pluginstorelistmodel.cpp
+++ b/src/app/pluginstorelistmodel.cpp
@@ -17,10 +17,17 @@
 
 #include "pluginstorelistmodel.h"
 
+#include "lrcinstance.h"
+
+#include "api/pluginmodel.h"
+
 #include <QUrl>
 
-PluginStoreListModel::PluginStoreListModel(QObject* parent)
+#include <algorithm>
+
+PluginStoreListModel::PluginStoreListModel(LRCInstance* lrcInstance, QObject* parent)
     : AbstractListModelBase(parent)
+    , lrcInstance_(lrcInstance)
 {}
 
 int
@@ -41,20 +48,16 @@ PluginStoreListModel::data(const QModelIndex& index, int role) const
     }
     auto plugin = plugins_.at(index.row());
     switch (role) {
-    case Role::Id:
-        return QVariant(plugin["id"].toString());
-    case Role::Title:
+    case Role::Name:
         return QVariant(plugin["name"].toString());
     case Role::IconPath:
         return QVariant(plugin["iconPath"].toString());
-    case Role::Background:
-        return QVariant(plugin["background"].toString());
     case Role::Description:
         return QVariant(plugin["description"].toString());
     case Role::Author:
         return QVariant(plugin["author"].toString());
     case Role::Status:
-        return QVariant(plugin.value("status", PluginStatus::INSTALLABLE).toString());
+        return QVariant(plugin.value("status", PluginStatus::INSTALLABLE));
     }
     return QVariant();
 }
@@ -84,14 +87,16 @@ PluginStoreListModel::addPlugin(const QVariantMap& plugin)
     beginInsertRows(QModelIndex(), plugins_.size(), plugins_.size());
     plugins_.append(plugin);
     endInsertRows();
+    sort();
 }
 
 void
 PluginStoreListModel::setPlugins(const QList<QVariantMap>& plugins)
 {
     beginResetModel();
-    plugins_ = plugins;
+    plugins_ = filterPlugins(plugins);
     endResetModel();
+    sort();
 }
 
 void
@@ -99,7 +104,7 @@ PluginStoreListModel::removePlugin(const QString& pluginId)
 {
     auto index = 0;
     for (auto& plugin : plugins_) {
-        if (plugin["id"].toString() == pluginId) {
+        if (plugin["name"].toString() == pluginId) {
             beginRemoveRows(QModelIndex(), index, index);
             plugins_.removeAt(index);
             endRemoveRows();
@@ -107,6 +112,7 @@ PluginStoreListModel::removePlugin(const QString& pluginId)
         }
         index++;
     }
+    sort();
 }
 
 void
@@ -114,13 +120,14 @@ PluginStoreListModel::updatePlugin(const QVariantMap& plugin)
 {
     auto index = 0;
     for (auto& p : plugins_) {
-        if (p["id"].toString() == plugin["id"].toString()) {
+        if (p["name"].toString() == plugin["name"].toString()) {
             p = plugin;
             Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0));
             return;
         }
         index++;
     }
+    sort();
 }
 
 QColor
@@ -151,7 +158,7 @@ PluginStoreListModel::computeAverageColorOfImage(const QString& file)
                 blue += pixelColor.blue();
             }
         }
-        return QColor(red / nPixels, green / nPixels, blue / nPixels, 70);
+        return QColor(red / nPixels, green / nPixels, blue / nPixels);
     } else {
         // Return an invalid color.
         return QColor();
@@ -161,36 +168,80 @@ PluginStoreListModel::computeAverageColorOfImage(const QString& file)
 void
 PluginStoreListModel::onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status)
 {
-    auto plugin = QVariantMap();
-    for (auto& p : plugins_) {
-        if (p["id"].toString() == pluginId) {
-            plugin = p;
-            break;
-        }
-    }
+    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&pluginId](const QVariantMap& p) {
+        return p["name"].toString() == pluginId;
+    });
+
     switch (status) {
     case PluginStatus::INSTALLABLE:
-        if (!plugin.isEmpty())
+        if (it != plugins_.end()) {
             break;
+        }
         pluginAdded(pluginId);
         break;
-
     default:
         break;
     }
-    if (plugin.isEmpty()) {
+
+    if (it == plugins_.end()) {
         return;
     }
-    plugin["status"] = status;
+    auto& plugin = *it;
 
+    plugin["status"] = status;
+    auto index = createIndex(rowFromPluginId(pluginId), 0);
+    if (index.isValid()) {
+        Q_EMIT dataChanged(index, index, {PluginStoreList::Role::Status});
+    }
     switch (status) {
     case PluginStatus::INSTALLED:
         removePlugin(pluginId);
         break;
-    case PluginStatus::FAILED:
-        qWarning() << "Failed to install plugin" << pluginId;
-        break;
     default:
         break;
     }
-}
\ No newline at end of file
+}
+
+int
+PluginStoreListModel::rowFromPluginId(const QString& pluginId) const
+{
+    const auto it = std::find_if(plugins_.begin(),
+                                 plugins_.end(),
+                                 [&pluginId](const QVariantMap& p) {
+                                     return p["name"].toString() == pluginId;
+                                 });
+    if (it != plugins_.end()) {
+        return std::distance(plugins_.begin(), it);
+    }
+    return -1;
+}
+
+void
+PluginStoreListModel::sort()
+{
+    beginResetModel();
+    std::sort(plugins_.begin(), plugins_.end(), [](const QVariantMap& a, const QVariantMap& b) {
+        return a["timestamp"].toString() < b["timestamp"].toString();
+    });
+    endResetModel();
+}
+
+QList<QVariantMap>
+PluginStoreListModel::filterPlugins(const QList<QVariantMap>& plugins)
+{
+    auto& pluginModel = lrcInstance_->pluginModel();
+    auto installedPlugins = pluginModel.getInstalledPlugins();
+    QList<QVariantMap> filterPluginsNotInstalled;
+    for (auto& remotePlugin : plugins) {
+        if (std::find_if(installedPlugins.begin(),
+                         installedPlugins.end(),
+                         [remotePlugin, &pluginModel, this](const QString& installedPlugin) {
+                             const auto& details = pluginModel.getPluginDetails(installedPlugin);
+                             return remotePlugin["name"].toString() == details.name;
+                         })
+            == installedPlugins.end()) {
+            filterPluginsNotInstalled.append(remotePlugin);
+        }
+    }
+    return filterPluginsNotInstalled;
+}
diff --git a/src/app/pluginstorelistmodel.h b/src/app/pluginstorelistmodel.h
index 7a603c510..53654feb3 100644
--- a/src/app/pluginstorelistmodel.h
+++ b/src/app/pluginstorelistmodel.h
@@ -24,8 +24,7 @@ class QColor;
 class QString;
 
 #define PLUGINSTORE_ROLES \
-    X(Id) \
-    X(Title) \
+    X(Name) \
     X(IconPath) \
     X(Background) \
     X(Description) \
@@ -48,7 +47,7 @@ class PluginStoreListModel : public AbstractListModelBase
     Q_OBJECT
 
 public:
-    explicit PluginStoreListModel(QObject* parent = nullptr);
+    explicit PluginStoreListModel(LRCInstance* lrcInstance, QObject* parent = nullptr);
 
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
@@ -69,6 +68,10 @@ public Q_SLOTS:
     void onVersionStatusChanged(const QString& pluginId, PluginStatus::Role status);
 
 private:
+    QList<QVariantMap> filterPlugins(const QList<QVariantMap>& plugins);
+    int rowFromPluginId(const QString& pluginId) const;
+    void sort();
     using Role = PluginStoreList::Role;
     QList<QVariantMap> plugins_;
+    LRCInstance* lrcInstance_ {};
 };
diff --git a/src/app/pluginversionmanager.cpp b/src/app/pluginversionmanager.cpp
index 28217891f..e508402b9 100644
--- a/src/app/pluginversionmanager.cpp
+++ b/src/app/pluginversionmanager.cpp
@@ -45,7 +45,6 @@ public:
                 return;
             }
             for (const auto& pluginId : qAsConst(pluginsId)) {
-                Q_EMIT parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLING);
                 parent_.pluginRepliesId.remove(pluginId);
             }
         });
@@ -74,6 +73,7 @@ public:
             return;
         }
         parent_.cancelDownload(parent_.pluginRepliesId[pluginId]);
+        parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLABLE);
     };
 
     bool isAutoUpdaterEnabled()
@@ -103,7 +103,8 @@ public:
             return;
         }
 
-        parent_.sendGetRequest(QUrl(parent_.baseUrl + "/versions/" + plugin.id),
+        parent_.sendGetRequest(QUrl(parent_.baseUrl + "/versions/" + plugin.id
+                                    + "?arch=" + Utils::getPlatformString()),
                                [this, plugin](const QByteArray& data) {
                                    // `data` represents the version in this case.
                                    if (plugin.version < data) {
@@ -120,7 +121,7 @@ public:
     void installRemotePlugin(const QString& pluginId)
     {
         parent_.downloadFile(
-            QUrl(parent_.baseUrl + "/download/" + pluginId),
+            QUrl(parent_.baseUrl + "/download/" + Utils::getPlatformString() + "/" + pluginId),
             pluginId,
             0,
             [this, pluginId](bool success, const QString& error) {
@@ -129,14 +130,17 @@ public:
                     parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
                     return;
                 }
-                auto res = lrcInstance_->pluginModel().installPlugin(QDir(tempPath_).filePath(
-                                                                         pluginId + ".jpl"),
-                                                                     true);
-                if (res) {
-                    parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLED);
-                } else {
-                    parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
-                }
+                QThreadPool::globalInstance()->start([this, pluginId] {
+                    auto res = lrcInstance_->pluginModel().installPlugin(QDir(tempPath_).filePath(
+                                                                             pluginId + ".jpl"),
+                                                                         true);
+                    if (res) {
+                        parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLED);
+                    } else {
+                        parent_.versionStatusChanged(pluginId, PluginStatus::Role::FAILED);
+                    }
+                });
+                parent_.versionStatusChanged(pluginId, PluginStatus::Role::INSTALLING);
             },
             tempPath_ + '/');
         Q_EMIT parent_.versionStatusChanged(pluginId, PluginStatus::Role::DOWNLOADING);
@@ -167,7 +171,7 @@ PluginVersionManager::PluginVersionManager(LRCInstance* instance, QString& baseU
 
 PluginVersionManager::~PluginVersionManager()
 {
-    for (const auto& pluginReplyId : qAsConst(pluginRepliesId)) {
+    for (const auto& pluginReplyId : pluginRepliesId.values()) {
         cancelDownload(pluginReplyId);
     }
     pluginRepliesId.clear();
diff --git a/src/app/pluginversionmanager.h b/src/app/pluginversionmanager.h
index 365006bbe..a56a2a079 100644
--- a/src/app/pluginversionmanager.h
+++ b/src/app/pluginversionmanager.h
@@ -72,7 +72,7 @@ Q_SIGNALS:
 private:
     QString baseUrl;
     bool autoUpdateCheck = false;
-    QMap<QString, unsigned int> pluginRepliesId {};
+    QMap<QString, int> pluginRepliesId {};
     struct Impl;
     friend struct Impl;
     std::unique_ptr<Impl> pimpl_;
diff --git a/src/app/qmlregister.cpp b/src/app/qmlregister.cpp
index 4ffec74d0..6e2f3377b 100644
--- a/src/app/qmlregister.cpp
+++ b/src/app/qmlregister.cpp
@@ -174,7 +174,6 @@ registerTypes(QQmlEngine* engine,
     QML_REGISTERTYPE(NS_MODELS, MediaCodecListModel);
     QML_REGISTERTYPE(NS_MODELS, AudioDeviceModel);
     QML_REGISTERTYPE(NS_MODELS, AudioManagerListModel);
-    QML_REGISTERTYPE(NS_MODELS, PluginListModel);
     QML_REGISTERTYPE(NS_MODELS, PreferenceItemListModel);
     QML_REGISTERTYPE(NS_MODELS, PluginListPreferenceModel);
     QML_REGISTERTYPE(NS_MODELS, FilesToSendListModel);
diff --git a/src/app/settingsview/SettingsView.qml b/src/app/settingsview/SettingsView.qml
index 3cc6a12da..375d92e67 100644
--- a/src/app/settingsview/SettingsView.qml
+++ b/src/app/settingsview/SettingsView.qml
@@ -59,14 +59,14 @@ ListSelectionView {
     leftPaneItem: viewCoordinator.getView("SettingsSidePanel")
 
     Component.onCompleted: {
-        leftPaneItem.updateModel()
+        leftPaneItem.updateModel();
     }
 
     Connections {
         target: viewNode
 
         function onIsSinglePaneChanged() {
-            leftPaneItem.isSinglePane = viewNode.isSinglePane
+            leftPaneItem.isSinglePane = viewNode.isSinglePane;
         }
     }
 
diff --git a/src/app/settingsview/components/InstallManuallyView.qml b/src/app/settingsview/components/InstallManuallyView.qml
new file mode 100644
index 000000000..5c0d02eaf
--- /dev/null
+++ b/src/app/settingsview/components/InstallManuallyView.qml
@@ -0,0 +1,78 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.platform
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+import "../../commoncomponents"
+
+ColumnLayout {
+    function installPlugin() {
+        var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/JamiFileDialog.qml", {
+                "title": JamiStrings.selectPluginInstall,
+                "fileMode": JamiFileDialog.OpenFile,
+                "folder": StandardPaths.writableLocation(StandardPaths.DownloadLocation),
+                "nameFilters": [JamiStrings.pluginFiles, JamiStrings.allFiles]
+            });
+        dlg.fileAccepted.connect(function (file) {
+                var url = UtilsAdapter.getAbsPath(file.toString());
+                var isInstall = PluginModel.installPlugin(url, true);
+                if (isInstall) {
+                    PluginListModel.addPlugin();
+                } else {
+                    presentErrorMessage();
+                }
+            });
+    }
+
+    function presentErrorMessage() {
+        viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", {
+                "title": JamiStrings.installationFailed,
+                "infoText": JamiStrings.pluginInstallationFailed,
+                "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue],
+                "buttonTitles": [JamiStrings.optionOk],
+                "buttonCallBacks": []
+            });
+    }
+
+    Label {
+        Layout.fillWidth: true
+        Layout.bottomMargin: 20
+
+        text: JamiStrings.installManually
+        font.pixelSize: JamiTheme.settingsTitlePixelSize
+        font.kerning: true
+        color: JamiTheme.textColor
+
+        horizontalAlignment: Text.AlignLeft
+        verticalAlignment: Text.AlignVCenter
+    }
+    Text {
+        id: descriptionInstallManually
+        Layout.fillWidth: true
+        font.pixelSize: JamiTheme.popuptextSize
+        color: JamiTheme.textColor
+        text: JamiStrings.installMannuallyDescription
+        wrapMode: Text.WordWrap
+        horizontalAlignment: Qt.AlignLeft
+        lineHeight: 1.5
+        textFormat: Text.PlainText
+    }
+    MaterialButton {
+        id: installManually
+        radius: JamiTheme.chatViewHeaderButtonRadius
+        TextMetrics {
+            id: textSize
+            font.weight: Font.Black
+            font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+            font.capitalization: Font.AllUppercase
+            text: installManually.text
+        }
+        primary: true
+        preferredWidth: textSize.width + 2 * JamiTheme.buttontextWizzardPadding
+        text: JamiStrings.install
+        fontSize: JamiTheme.popuptextSize
+        onClicked: installPlugin()
+    }
+}
diff --git a/src/app/settingsview/components/PluginAvailableDelagate.qml b/src/app/settingsview/components/PluginAvailableDelagate.qml
index 0d66d5d98..7907315bf 100644
--- a/src/app/settingsview/components/PluginAvailableDelagate.qml
+++ b/src/app/settingsview/components/PluginAvailableDelagate.qml
@@ -27,114 +27,195 @@ import "../../mainview/components"
 
 ItemDelegate {
     id: root
-    property string pluginId
-    property string pluginTitle
+    property string pluginName
     property string pluginIcon
-    property string pluginBackground
+    property string pluginBackground: JamiTheme.pluginDefaultBackgroundColor
     property string pluginDescription
     property string pluginAuthor
     property string pluginShortDescription
     property int pluginStatus
+    property string installButtonStatus: {
+        switch (pluginStatus) {
+        case PluginStatus.DOWNLOADING:
+            return JamiStrings.cancel;
+        case PluginStatus.INSTALLABLE:
+            return JamiStrings.install;
+        case PluginStatus.INSTALLING:
+            return JamiStrings.installing;
+        default:
+            return JamiStrings.install;
+        }
+    }
+    onPluginStatusChanged: {
+        if (pluginStatus === PluginStatus.FAILED) {
+            presentErrorMessage();
+        }
+    }
+
+    background: null
+
+    function presentErrorMessage() {
+        viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", {
+                "title": JamiStrings.installationFailed,
+                "infoText": JamiStrings.pluginInstallationFailed,
+                "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue],
+                "buttonTitles": [JamiStrings.optionOk],
+                "buttonCallBacks": []
+            });
+    }
 
     Rectangle {
-        id: rect
-        Scaffold {
-        }
-        color: Qt.rgba(0, 0, 0, 1)
+        id: mask
         anchors.fill: parent
-        radius: 15
+        radius: 5
     }
     Page {
         id: plugin
         anchors.fill: parent
+        layer {
+            enabled: true
+            effect: OpacityMask {
+                maskSource: mask
+            }
+        }
         header: Control {
-            padding: 10
+            leftPadding: 20
+            rightPadding: 5
+            bottomPadding: 20
+            topPadding: 5
             background: Rectangle {
-                color: pluginBackground
+                id: headerBackground
+                color: hovered ? Qt.lighter(pluginBackground, 1.9) : Qt.lighter(pluginBackground, 2)
             }
             contentItem: ColumnLayout {
-                RowLayout {
+                SpinningAnimation {
+                    id: buttonContainer
+                    visible: true
                     Layout.alignment: Qt.AlignTop | Qt.AlignRight
+                    Layout.rightMargin: 8
+                    Layout.topMargin: 2
+                    Layout.preferredHeight: install.height
+                    Layout.preferredWidth: install.width
+                    color: "black"
+                    outerCutRadius: install.radius
+                    spinningAnimationDuration: 5000
+                    mode: {
+                        if (pluginStatus === PluginStatus.INSTALLABLE || pluginStatus === PluginStatus.FAILED) {
+                            SpinningAnimation.Mode.Disabled;
+                        } else {
+                            SpinningAnimation.Mode.Radial;
+                        }
+                    }
+
                     MaterialButton {
                         id: install
-                        Layout.alignment: Qt.AlignRight
-                        Layout.rightMargin: 8
-                        Layout.topMargin: 8
-                        Layout.preferredHeight: 20
+                        hoverEnabled: pluginStatus !== PluginStatus.INSTALLING
+                        secHoveredColor: Qt.darker(headerBackground.color, 1.1)
+                        buttontextHeightMargin: 10.0
+                        radius: JamiTheme.chatViewHeaderButtonRadius
                         TextMetrics {
                             id: installTextSize
                             font.weight: Font.Black
                             font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
                             font.capitalization: Font.Medium
-                            text: isDownloading() ? JamiStrings.cancel : JamiStrings.install
+                            text: install.text
                         }
+                        contentColorProvider: "black"
                         onClicked: installPlugin()
                         secondary: true
                         preferredWidth: installTextSize.width + JamiTheme.buttontextWizzardPadding
-                        text: isDownloading() ? JamiStrings.cancel : JamiStrings.install
-                        fontSize: 15
+                        text: {
+                            switch (pluginStatus) {
+                            case PluginStatus.DOWNLOADING:
+                                return JamiStrings.cancel;
+                            case PluginStatus.INSTALLABLE:
+                                return JamiStrings.install;
+                            case PluginStatus.INSTALLING:
+                                return JamiStrings.installing;
+                            default:
+                                return JamiStrings.install;
+                            }
+                        }
                     }
                 }
                 RowLayout {
                     spacing: 10
-
                     CachedImage {
                         id: icon
-                        Component.onCompleted: {
-                            pluginBackground = PluginStoreListModel.computeAverageColorOfImage("file://" + UtilsAdapter.getCachePath() + '/plugins/' + pluginId + '.svg');
+                        defaultImage: JamiResources.plugins_default_icon_svg
+                        onSourceChanged: {
+                            if (source == defaultImage) {
+                                pluginBackground = JamiTheme.pluginDefaultBackgroundColor;
+                                return;
+                            }
+                            pluginBackground = PluginStoreListModel.computeAverageColorOfImage(source);
                         }
-                        width: 50
-                        height: 50
-                        downloadUrl: PluginAdapter.baseUrl + "/icons/" + pluginId
+                        width: 55
+                        height: 55
+                        downloadUrl: PluginAdapter.getIconUrl(pluginName)
                         fileExtension: '.svg'
-                        localPath: UtilsAdapter.getCachePath() + '/plugins/' + pluginId + '.svg'
+                        localPath: UtilsAdapter.getCachePath() + '/plugins/' + pluginName + '.svg'
                     }
                     ColumnLayout {
+                        width: parent.width
                         Label {
-                            text: pluginTitle
+                            Layout.fillWidth: true
+                            Layout.alignment: Qt.AlignHCenter
+                            text: pluginName
                             font.kerning: true
-                            color: JamiTheme.textColor
-                            font.pointSize: JamiTheme.settingsFontSize
-                            verticalAlignment: Text.AlignVCenter
-                        }
-                        Label {
-                            color: JamiTheme.textColor
-                            text: pluginShortDescription
-                            font.kerning: true
-                            font.pointSize: JamiTheme.settingsFontSize
-                            verticalAlignment: Text.AlignVCenter
+                            color: "black"
+                            font.pointSize: JamiTheme.tinyCreditsTextSize
+                            textFormat: Text.PlainText
+                            wrapMode: Text.WrapAnywhere
                         }
+                        //                        Label {
+                        //                            Layout.fillWidth: true
+                        //                            color: "black"
+                        //                            text: pluginShortDescription
+                        //                            font.pointSize: JamiTheme.settingsFontSize
+                        //                            textFormat: Text.PlainText
+                        //                            wrapMode: Text.WordWrap
+                        //                        }
                     }
                 }
             }
         }
         Rectangle {
+            id: contentContainer
             anchors.fill: parent
-            color: JamiTheme.pluginViewBackgroundColor
+            color: hovered ? JamiTheme.smartListHoveredColor : JamiTheme.pluginViewBackgroundColor
         }
-        Flickable {
+        JamiFlickable {
             anchors.fill: parent
-            anchors.margins: 10
-            contentWidth: description.width
+            anchors.margins: 20
             contentHeight: description.height
             clip: true
             flickableDirection: Flickable.VerticalFlick
-            ScrollBar.vertical: ScrollBar {
+            ScrollBar.vertical: JamiScrollBar {
                 id: scrollBar
                 policy: ScrollBar.AsNeeded
             }
-            Text {
-                id: description
+            ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+            ColumnLayout {
                 width: parent.width
-                color: JamiTheme.textColor
-                text: pluginDescription
-                wrapMode: Text.WordWrap
+                Text {
+                    id: description
+                    Layout.preferredWidth: contentContainer.width
+                    font.pixelSize: JamiTheme.popuptextSize
+                    color: JamiTheme.textColor
+                    text: pluginDescription
+                    wrapMode: Text.WordWrap
+                    horizontalAlignment: Qt.AlignLeft
+                    lineHeight: 1.5
+                    textFormat: Text.MarkdownText
+                    rightPadding: 40
+                }
             }
         }
         footer: Control {
-            padding: 10
+            padding: 20
             background: Rectangle {
-                color: JamiTheme.pluginViewBackgroundColor
+                color: hovered ? JamiTheme.smartListHoveredColor : JamiTheme.pluginViewBackgroundColor
             }
             contentItem: Text {
                 Layout.fillWidth: true
@@ -145,32 +226,26 @@ ItemDelegate {
 
                 font.pointSize: JamiTheme.settingsFontSize
                 font.kerning: true
+                font.italic: true
                 text: "By " + pluginAuthor
+                wrapMode: Text.WordWrap
                 verticalAlignment: Text.AlignVCenter
             }
         }
-
-        DropShadow {
-            z: 2
-            visible: hovered
-            width: root.width
-            height: root.height
-            radius: 16
-            color: Qt.rgba(0, 0.34, 0.6, 0.16)
-            source: root
-            transparentBorder: true
-            samples: radius + 1
-            cached: true
-        }
     }
     function installPlugin() {
-        if (isDownloading()) {
-            return;
+        switch (pluginStatus) {
+        case PluginStatus.DOWNLOADING:
+            PluginAdapter.cancelDownload(pluginName);
+            break;
+        case PluginStatus.INSTALLABLE:
+            PluginAdapter.installRemotePlugin(pluginName);
+            break;
+        case PluginStatus.FAILED:
+            PluginAdapter.installRemotePlugin(pluginName);
+            break;
+        case PluginStatus.INSTALLING:
+            break;
         }
-        PluginAdapter.installRemotePlugin(pluginId);
-    }
-
-    function isDownloading() {
-        return pluginStatus === PluginStatus.DOWNLOADING;
     }
 }
diff --git a/src/app/settingsview/components/PluginItemDelegate.qml b/src/app/settingsview/components/PluginItemDelegate.qml
index e273d8772..139a225cd 100644
--- a/src/app/settingsview/components/PluginItemDelegate.qml
+++ b/src/app/settingsview/components/PluginItemDelegate.qml
@@ -20,109 +20,133 @@ import QtQuick.Controls
 import QtQuick.Layouts
 import net.jami.Models 1.1
 import net.jami.Adapters 1.1
+import Qt5Compat.GraphicalEffects
 import net.jami.Constants 1.1
 import "../../commoncomponents"
 
 ItemDelegate {
     id: root
-
     property string pluginName: ""
     property string pluginId: ""
     property string pluginIcon: ""
+    property int pluginStatus
     property bool isLoaded: false
-    property string activeId: ""
-    height: pluginPreferencesView.visible ? implicitHeight + pluginPreferencesView.childrenRect.height : implicitHeight
+    height: implicitHeight
+    Connections {
+        target: PluginListModel
+        function onDisabled(id) {
+            if (root.pluginId === id) {
+                isLoaded = false;
+                loadSwitch.checked = false;
+            }
+        }
+    }
 
-    signal settingsClicked
+    onClicked: {
+        pluginListView.currentIndex = index;
+    }
 
-    onActiveIdChanged: pluginPreferencesView.visible = activeId != pluginId ? false : !pluginPreferencesView.visible
+    Rectangle {
+        id: mask
+        anchors.fill: parent
+        color: {
+            if (pluginHover.hovered && pluginListView.currentIndex !== index) {
+                return JamiTheme.smartListHoveredColor;
+            } else {
+                return JamiTheme.pluginViewBackgroundColor;
+            }
+        }
+        border.width: 2
+        border.color: {
+            if (pluginListView.currentIndex === index) {
+                return JamiTheme.switchHandleCheckedBorderColor;
+            }
+            return "transparent";
+        }
+        radius: 5
+    }
 
     ColumnLayout {
-        width: parent.width
-
-        RowLayout {
+        width: parent.width - 20
+        height: parent.height
+        anchors.centerIn: parent
+        Item {
             Layout.fillWidth: true
-            Layout.preferredHeight: implicitHeight
+            Layout.alignment: Qt.AlignCenter
+            Layout.preferredHeight: childrenRect.height
 
             Label {
                 id: pluginImage
-                Layout.leftMargin: 8
-                Layout.topMargin: 8
-                Layout.alignment: Qt.AlignLeft | Qt.AlingVCenter
+                anchors.left: parent.left
                 width: JamiTheme.preferredFieldHeight
-                Layout.fillHeight: true
+                height: parent.height
 
                 background: Rectangle {
                     color: "transparent"
-                    Image {
+                    ResponsiveImage {
                         anchors.centerIn: parent
                         source: "file:" + pluginIcon
-                        sourceSize: Qt.size(256, 256)
-                        mipmap: true
-                        width: JamiTheme.preferredFieldHeight
-                        height: JamiTheme.preferredFieldHeight
+                        containerWidth: JamiTheme.preferredFieldHeight
+                        containerHeight: JamiTheme.preferredFieldHeight
                     }
                 }
             }
 
             Label {
-                Layout.fillHeight: true
-                Layout.fillWidth: true
-                Layout.topMargin: 8
-                Layout.leftMargin: 8
+                width: contentWidth
+                height: parent.height
+                anchors.left: pluginImage.right
+                anchors.leftMargin: 8
                 color: JamiTheme.textColor
 
-                font.pointSize: JamiTheme.settingsFontSize
+                font.pointSize: JamiTheme.tinyCreditsTextSize
                 font.kerning: true
                 text: pluginName === "" ? pluginId : pluginName
                 verticalAlignment: Text.AlignVCenter
             }
 
-            ToggleSwitch {
-                id: loadSwitch
-                Layout.fillHeight: true
-                property bool isHovering: false
-                Layout.topMargin: 8
-                Layout.rightMargin: 8
-                width: 20
-
-                tooltipText: JamiStrings.loadUnload
-
-                checked: isLoaded
-                onSwitchToggled: {
-                    if (isLoaded)
-                        PluginModel.unloadPlugin(pluginId);
-                    else
-                        PluginModel.loadPlugin(pluginId);
-                    installedPluginsModel.pluginChanged(index);
+            MaterialButton {
+                id: update
+                anchors.right: itemSwitch.left
+                buttontextHeightMargin: 10.0
+                TextMetrics {
+                    id: updateTextSize
+                    font.weight: Font.Bold
+                    font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+                    font.capitalization: Font.AllUppercase
+                    text: JamiStrings.updateDialogTitle
                 }
+                visible: pluginStatus === PluginStatus.UPDATABLE
+                secondary: true
+                preferredWidth: updateTextSize.width
+                text: JamiStrings.updateDialogTitle
+                fontSize: 15
             }
-
-            PushButton {
-                id: btnPreferencesPlugin
-
-                Layout.alignment: Qt.AlingVCenter | Qt.AlignRight
-                Layout.topMargin: 8
-                Layout.rightMargin: 8
-
-                source: JamiResources.round_settings_24dp_svg
-                normalColor: JamiTheme.primaryBackgroundColor
-                imageColor: JamiTheme.textColor
-                toolTipText: JamiStrings.showHidePrefs
-
-                onClicked: settingsClicked()
+            Item {
+                id: itemSwitch
+                height: parent.height
+                anchors.right: parent.right
+                anchors.verticalCenter: parent.verticalCenter
+                width: 78
+                ToggleSwitch {
+                    id: loadSwitch
+                    anchors.topMargin: parent.height / 2
+                    width: parent.width
+                    height: parent.height
+                    property bool isHovering: false
+
+                    tooltipText: JamiStrings.loadUnload
+
+                    checked: isLoaded
+                    onSwitchToggled: {
+                        if (isLoaded)
+                            PluginModel.unloadPlugin(pluginId);
+                        else
+                            PluginModel.loadPlugin(pluginId);
+                        PluginListModel.pluginChanged(index);
+                    }
+                }
             }
         }
-
-        PluginPreferencesView {
-            id: pluginPreferencesView
-
-            pluginId: root.pluginId
-
-            Layout.fillWidth: true
-            Layout.leftMargin: JamiTheme.preferredMarginSize
-            Layout.rightMargin: JamiTheme.preferredMarginSize
-            Layout.preferredHeight: pluginPreferencesView.childrenRect.height
-        }
     }
 }
diff --git a/src/app/settingsview/components/PluginListView.qml b/src/app/settingsview/components/PluginListView.qml
index 1afa2d4cf..081865430 100644
--- a/src/app/settingsview/components/PluginListView.qml
+++ b/src/app/settingsview/components/PluginListView.qml
@@ -26,114 +26,105 @@ import "../../commoncomponents"
 
 Rectangle {
     id: root
-
-    property string activePlugin: ""
-
+    property int count: pluginLoader.item !== undefined ? pluginLoader.item.count : 0
+    property bool isAutoUpdate: PluginAdapter.isAutoUpdaterEnabled()
+    property int currentIndex: {
+        if (pluginLoader.item !== undefined) {
+            return -1;
+        } else {
+            if (pluginListView.currentIndex === null) {
+                return -1;
+            }
+            return pluginListView.currentIndex;
+        }
+    }
+    visible: PluginAdapter.isEnabled && count
     color: JamiTheme.secondaryBackgroundColor
 
     ColumnLayout {
         anchors.left: root.left
         anchors.right: root.right
         anchors.bottomMargin: 20
-
-        Label {
+        RowLayout {
+            Layout.preferredHeight: JamiTheme.settingsHeaderpreferredHeight
             Layout.fillWidth: true
-            Layout.preferredHeight: 25
-
-            text: JamiStrings.installedPlugins
-            font.pointSize: JamiTheme.headerFontSize
-            font.kerning: true
-            color: JamiTheme.textColor
-
-            horizontalAlignment: Text.AlignLeft
-            verticalAlignment: Text.AlignVCenter
-        }
-
-        MaterialButton {
-            id: disableAll
-            TextMetrics {
-                id: disableTextSize
-                font.weight: Font.Bold
-                font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
-                font.capitalization: Font.AllUppercase
-                text: JamiStrings.disableAll
+            Layout.bottomMargin: 20
+            Layout.alignment: Qt.AlignRight
+            Label {
+                Layout.fillWidth: true
+                text: JamiStrings.installed
+                font.pixelSize: JamiTheme.settingsTitlePixelSize
+                font.kerning: true
+                color: JamiTheme.textColor
+
+                horizontalAlignment: Text.AlignLeft
+                verticalAlignment: Text.AlignVCenter
             }
-            secondary: true
-            preferredWidth: disableTextSize.width
-            text: JamiStrings.disableAll
-            fontSize: 15
-        }
-
-        MaterialButton {
-            id: installButton
-
-            Layout.alignment: Qt.AlignCenter
-            Layout.topMargin: JamiTheme.preferredMarginSize / 2
-
-            preferredWidth: JamiTheme.preferredFieldWidth
-            buttontextHeightMargin: JamiTheme.buttontextHeightMargin
-
-            color: JamiTheme.buttonTintedBlack
-            hoveredColor: JamiTheme.buttonTintedBlackHovered
-            pressedColor: JamiTheme.buttonTintedBlackPressed
-            secondary: true
-            toolTipText: JamiStrings.addNewPlugin
-
-            iconSource: JamiResources.round_add_24dp_svg
-
-            text: JamiStrings.installPlugin
-
-            onClicked: {
-                var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/JamiFileDialog.qml", {
-                        "title": JamiStrings.selectPluginInstall,
-                        "fileMode": JamiFileDialog.OpenFile,
-                        "folder": StandardPaths.writableLocation(StandardPaths.DownloadLocation),
-                        "nameFilters": [JamiStrings.pluginFiles, JamiStrings.allFiles]
-                    });
-                dlg.fileAccepted.connect(function (file) {
-                        var url = UtilsAdapter.getAbsPath(file.toString());
-                        PluginModel.installPlugin(url, true);
-                        installedPluginsModel.addPlugin();
-                    });
+            HeaderToggleSwitch {
+                labelText: "auto update"
+                tooltipText: "auto update"
+                checked: isAutoUpdate
+                onSwitchToggled: {
+                    isAutoUpdate = !isAutoUpdate;
+                    PluginAdapter.setAutoUpdate(isAutoUpdate);
+                }
+            }
+            MaterialButton {
+                id: disableAll
+                radius: JamiTheme.chatViewHeaderButtonRadius
+                buttontextHeightMargin: 10.0
+                TextMetrics {
+                    id: disableTextSize
+                    font.weight: Font.Bold
+                    font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+                    font.capitalization: Font.AllUppercase
+                    text: JamiStrings.disableAll
+                }
+                secondary: true
+                preferredWidth: disableTextSize.width + 2
+                text: JamiStrings.disableAll
+                fontSize: JamiTheme.wizardViewButtonFontPixelSize
+                onClicked: PluginListModel.disableAllPlugins()
             }
         }
-
-        ListView {
-            id: pluginList
-
+        Loader {
+            id: pluginLoader
             Layout.fillWidth: true
-            Layout.bottomMargin: 10
-            Layout.preferredHeight: childrenRect.height
-            clip: true
-
-            model: PluginListModel {
-                id: installedPluginsModel
-
-                lrcInstance: LRCInstance
-                onLrcInstanceChanged: {
-                    this.reset();
+            Layout.preferredHeight: pluginLoader.item.contentHeight
+            Layout.topMargin: 10
+            active: true
+            asynchronous: true
+
+            sourceComponent: ListView {
+                id: pluginListView
+                clip: true
+                model: PluginListModel
+                spacing: 10
+                currentIndex: -1
+                onCurrentIndexChanged: {
+                    root.currentIndex = currentIndex;
                 }
-            }
-
-            delegate: PluginItemDelegate {
-                id: pluginItemDelegate
-
-                width: pluginList.width
-                implicitHeight: 50
-
-                pluginName: PluginName
-                pluginId: PluginId
-                pluginIcon: PluginIcon
-                isLoaded: IsLoaded
-                activeId: root.activePlugin
-
-                background: Rectangle {
-                    anchors.fill: parent
-                    color: "transparent"
+                delegate: PluginItemDelegate {
+                    id: pluginItemDelegate
+                    width: pluginLoader.width
+                    implicitHeight: 50
+
+                    pluginName: PluginName
+                    pluginId: PluginId
+                    pluginIcon: PluginIcon
+                    pluginStatus: Status
+                    isLoaded: IsLoaded
+                    HoverHandler {
+                        id: pluginHover
+                        target: parent
+                        enabled: true
+                    }
                 }
-
-                onSettingsClicked: {
-                    root.activePlugin = root.activePlugin === pluginId ? "" : pluginId;
+                Connections {
+                    target: pluginPreferencesView
+                    function onClosed() {
+                        pluginListView.currentIndex = -1;
+                    }
                 }
             }
         }
diff --git a/src/app/settingsview/components/PluginPreferencesListView.qml b/src/app/settingsview/components/PluginPreferencesListView.qml
index ec5aaf823..456a08895 100644
--- a/src/app/settingsview/components/PluginPreferencesListView.qml
+++ b/src/app/settingsview/components/PluginPreferencesListView.qml
@@ -25,9 +25,9 @@ import "../../commoncomponents"
 
 Rectangle {
     id: root
-
     property string accountId: ""
     required property string pluginId
+    width: parent.width
 
     property int count: pluginPreferenceView.count + pluginPreferenceViewCategory.count
 
@@ -40,7 +40,6 @@ Rectangle {
     }
 
     color: "transparent"
-
     Connections {
         target: LRCInstance
 
@@ -56,8 +55,6 @@ Rectangle {
     property var categories: PluginAdapter.getPluginPreferencesCategories(pluginId, accountId)
     property string generalCategory: categories.length <= 1 ? "all" : ""
 
-    visible: false
-
     function setPreference(pluginId, preferenceKey, preferenceNewValue) {
         PluginModel.setPluginPreference(pluginId, accountId, preferenceKey, preferenceNewValue);
     }
@@ -66,7 +63,7 @@ Rectangle {
         anchors.left: root.left
         anchors.right: root.right
 
-        Rectangle {
+        Item {
             id: prefsByCategory
 
             visible: categories.length > 1
@@ -74,7 +71,6 @@ Rectangle {
             Layout.topMargin: 24
             Layout.fillWidth: true
             implicitHeight: childrenRect.height
-            color: JamiTheme.backgroundColor
 
             ColumnLayout {
                 anchors.left: parent.left
@@ -263,7 +259,7 @@ Rectangle {
 
         MaterialButton {
             id: resetButton
-
+            visible: count > 0
             Layout.alignment: Qt.AlignCenter
 
             preferredWidth: JamiTheme.preferredFieldWidth
@@ -274,8 +270,6 @@ Rectangle {
             pressedColor: JamiTheme.buttonTintedBlackPressed
             secondary: true
 
-            iconSource: JamiResources.settings_backup_restore_24dp_svg
-
             text: JamiStrings.reset
 
             onClicked: viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", {
diff --git a/src/app/settingsview/components/PluginPreferencesView.qml b/src/app/settingsview/components/PluginPreferencesView.qml
index f8c12e321..82bf989c5 100644
--- a/src/app/settingsview/components/PluginPreferencesView.qml
+++ b/src/app/settingsview/components/PluginPreferencesView.qml
@@ -19,179 +19,181 @@ import QtQuick
 import QtQuick.Controls
 import QtQuick.Layouts
 import net.jami.Adapters 1.1
+import SortFilterProxyModel 0.2
 import net.jami.Models 1.1
 import net.jami.Constants 1.1
 import "../../commoncomponents"
 
-Rectangle {
+Item {
     id: root
-
-    required property string pluginId
-
-    color: "transparent"
-
-    visible: false
-
-    ColumnLayout {
-        anchors.left: root.left
-        anchors.right: root.right
-        anchors.bottomMargin: 10
-
-        Label {
-            Layout.topMargin: 34
-            Layout.alignment: Qt.AlignHCenter
-            height: 64
-            background: Rectangle {
-                Image {
-                    anchors.centerIn: parent
-                    source: pluginIcon === "" ? JamiResources.plugins_24dp_svg : "file:" + pluginIcon
-                    sourceSize: Qt.size(256, 256)
-                    height: 64
-                    width: 64
-                    mipmap: true
+    required property int currentIndex
+    signal closed
+    ListView {
+        id: pluginPreferenceListView
+        height: parent.height
+        width: parent.width
+        model: SortFilterProxyModel {
+            sourceModel: PluginListModel
+            filters: [
+                ExpressionFilter {
+                    expression: index === currentIndex
+                    enabled: true
                 }
-            }
-        }
-
-        Label {
-            Layout.alignment: Qt.AlignHCenter
-            Layout.topMargin: 24
-            height: JamiTheme.preferredFieldHeight
-
-            text: "%1\n%2".arg(pluginName).arg(JamiStrings.pluginPreferences)
-            font.pointSize: JamiTheme.headerFontSize
-            font.kerning: true
-            color: JamiTheme.textColor
-
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
+            ]
         }
-
-        RowLayout {
-            Layout.fillWidth: true
-
-            Text {
-                Layout.fillWidth: true
-                Layout.preferredHeight: JamiTheme.preferredFieldHeight
-
-                font.pointSize: JamiTheme.headerFontSize
-                font.kerning: true
-
-                horizontalAlignment: Text.AlignLeft
-                verticalAlignment: Text.AlignVCenter
-                color: JamiTheme.textColor
-
-                text: JamiStrings.generalSettingsTitle
-                elide: Text.ElideRight
+        delegate: Page {
+            id: settings
+            width: root.width
+            height: root.height
+            background: Rectangle {
+                color: JamiTheme.pluginViewBackgroundColor
             }
-
-            PushButton {
-                Layout.preferredWidth: JamiTheme.preferredFieldHeight
-                Layout.preferredHeight: JamiTheme.preferredFieldHeight
-                Layout.alignment: Qt.AlignHCenter
-
-                imageColor: JamiTheme.textColor
-                toolTipText: JamiStrings.tipGeneralPluginSettingsDisplay
-
-                preferredSize: 32
-                source: pluginGeneralSettingsView.visible ? JamiResources.expand_less_24dp_svg : JamiResources.expand_more_24dp_svg
-
-                onClicked: {
-                    pluginGeneralSettingsView.visible = !pluginGeneralSettingsView.visible;
+            header: Control {
+                padding: 10
+                background: Rectangle {
+                    color: JamiTheme.pluginViewBackgroundColor
+                }
+                contentItem: ColumnLayout {
+                    width: parent.width
+                    PushButton {
+                        id: closeButton
+                        normalColor: "transparent"
+                        hoveredColor: JamiTheme.smartListHoveredColor
+                        Layout.alignment: Qt.AlignRight
+                        Layout.preferredWidth: JamiTheme.preferredFieldHeight
+                        Layout.preferredHeight: childrenRect.height
+
+                        imageColor: JamiTheme.textColor
+                        toolTipText: JamiStrings.closeSettings
+
+                        preferredSize: 32
+                        source: JamiResources.round_close_24dp_svg
+                        onClicked: {
+                            closed();
+                        }
+                    }
+
+                    RowLayout {
+                        Layout.preferredWidth: parent.width
+                        ResponsiveImage {
+                            Layout.bottomMargin: 10
+                            Layout.rightMargin: 10
+                            containerWidth: 64
+                            containerHeight: 64
+                            source: PluginIcon === "" ? JamiResources.plugins_default_icon_svg : "file:" + PluginIcon
+                        }
+                        Label {
+                            text: PluginName
+                            font.pixelSize: JamiTheme.settingsTitlePixelSize
+                            font.kerning: true
+                            color: JamiTheme.textColor
+                            textFormat: Text.PlainText
+                        }
+
+                        Item {
+                            Layout.fillHeight: true
+                            Layout.fillWidth: true
+                            MaterialButton {
+                                id: update
+                                anchors.right: parent.right
+                                buttontextHeightMargin: 0.0
+                                TextMetrics {
+                                    id: updateTextSize
+                                    font.weight: Font.Bold
+                                    font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
+                                    font.capitalization: Font.AllUppercase
+                                    text: JamiStrings.updateDialogTitle
+                                }
+                                visible: Status === PluginStatus.UPDATABLE
+                                secondary: true
+                                preferredWidth: updateTextSize.width
+                                text: JamiStrings.updateDialogTitle
+                                fontSize: 15
+                            }
+                        }
+                    }
+
+                    JamiFlickable {
+                        Layout.fillWidth: true
+                        Layout.preferredHeight: childrenRect.height
+                        Layout.minimumHeight: childrenRect.height
+                        Layout.maximumHeight: 88
+                        contentWidth: description.width
+                        contentHeight: description.height
+                        clip: true
+                        flickableDirection: Flickable.VerticalFlick
+                        ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+                        ScrollBar.vertical: ScrollBar {
+                            id: scrollBar
+                            policy: ScrollBar.AsNeeded
+                        }
+                        Text {
+                            id: description
+                            width: settings.width - 2 * scrollBar.width
+                            text: PluginDescription
+                            font.pixelSize: JamiTheme.popuptextSize
+                            color: JamiTheme.textColor
+                            wrapMode: Text.WordWrap
+                            textFormat: Text.PlainText
+                        }
+                    }
                 }
             }
-        }
-
-        PluginPreferencesListView {
-            id: pluginGeneralSettingsView
-            visible: false
-            Layout.fillWidth: true
-            pluginId: root.pluginId
-        }
-
-        RowLayout {
-            Layout.fillWidth: true
-            visible: pluginAccountSettingsView.count > 0
-
-            Text {
-                Layout.fillWidth: true
-                Layout.preferredHeight: JamiTheme.preferredFieldHeight
-
-                font.pointSize: JamiTheme.headerFontSize
-                font.kerning: true
-
-                horizontalAlignment: Text.AlignLeft
-                verticalAlignment: Text.AlignVCenter
-                color: JamiTheme.textColor
-
-                text: JamiStrings.accountSettingsMenuTitle
-                elide: Text.ElideRight
+            Rectangle {
+                anchors.fill: parent
+                color: JamiTheme.primaryBackgroundColor
             }
-
-            PushButton {
-                Layout.preferredWidth: JamiTheme.preferredFieldHeight
-                Layout.preferredHeight: JamiTheme.preferredFieldHeight
-                Layout.alignment: Qt.AlignHCenter
-
-                imageColor: JamiTheme.textColor
-                toolTipText: JamiStrings.tipAccountPluginSettingsDisplay
-
-                preferredSize: 32
-                source: pluginAccountSettingsView.visible ? JamiResources.expand_less_24dp_svg : JamiResources.expand_more_24dp_svg
-
-                onClicked: {
-                    pluginAccountSettingsView.visible = !pluginAccountSettingsView.visible;
+            JamiFlickable {
+                anchors.fill: parent
+                contentHeight: contentItem.childrenRect.height
+                topMargin: JamiTheme.preferredSettingsBottomMarginSize
+                bottomMargin: JamiTheme.preferredSettingsBottomMarginSize
+                ScrollBar.horizontal.visible: false
+                contentItem.children: ColumnLayout {
+                    width: root.width
+                    PluginPreferencesListView {
+                        id: pluginGeneralSettingsView
+                        Layout.fillWidth: true
+                        pluginId: PluginId
+                    }
+                    PluginPreferencesListView {
+                        id: pluginAccountSettingsView
+                        Layout.fillWidth: true
+                        accountId: LRCInstance.currentAccountId
+                        pluginId: PluginId
+                    }
+                    MaterialButton {
+                        id: uninstallButton
+
+                        Layout.alignment: Qt.AlignCenter
+
+                        preferredWidth: JamiTheme.preferredFieldWidth
+                        buttontextHeightMargin: JamiTheme.buttontextHeightMargin
+                        contentColorProvider: JamiTheme.buttonTintedRed
+                        color: JamiTheme.buttonTintedBlack
+                        hoveredColor: JamiTheme.buttonTintedBlackHovered
+                        pressedColor: JamiTheme.buttonTintedBlackPressed
+                        tertiary: true
+                        toolTipText: JamiStrings.pluginUninstallConfirmation.arg(PluginId)
+
+                        text: JamiStrings.uninstall
+
+                        onClicked: viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", {
+                                "title": JamiStrings.uninstallPlugin,
+                                "infoText": JamiStrings.pluginUninstallConfirmation.arg(PluginName),
+                                "buttonTitles": [JamiStrings.optionOk, JamiStrings.optionCancel],
+                                "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue, SimpleMessageDialog.ButtonStyle.TintedBlack],
+                                "buttonCallBacks": [function () {
+                                        PluginListModel.setVersionStatus(PluginName, PluginStatus.INSTALLABLE);
+                                        PluginModel.uninstallPlugin(PluginId);
+                                        PluginListModel.removePlugin(index);
+                                        // could not call root from here
+                                        settings.ListView.view.parent.closed();
+                                    }]
+                            })
+                    }
                 }
             }
         }
-
-        PluginPreferencesListView {
-            id: pluginAccountSettingsView
-            visible: false
-            Layout.fillWidth: true
-            accountId: LRCInstance.currentAccountId
-            pluginId: root.pluginId
-        }
-
-        MaterialButton {
-            id: uninstallButton
-
-            Layout.alignment: Qt.AlignCenter
-
-            preferredWidth: JamiTheme.preferredFieldWidth
-            buttontextHeightMargin: JamiTheme.buttontextHeightMargin
-
-            color: JamiTheme.buttonTintedBlack
-            hoveredColor: JamiTheme.buttonTintedBlackHovered
-            pressedColor: JamiTheme.buttonTintedBlackPressed
-            secondary: true
-            toolTipText: JamiStrings.pluginUninstallConfirmation.arg(pluginName)
-            iconSource: JamiResources.delete_24dp_svg
-
-            text: JamiStrings.uninstall
-
-            onClicked: viewCoordinator.presentDialog(appWindow, "commoncomponents/SimpleMessageDialog.qml", {
-                    "title": JamiStrings.uninstallPlugin,
-                    "infoText": JamiStrings.pluginUninstallConfirmation.arg(pluginName),
-                    "buttonTitles": [JamiStrings.optionOk, JamiStrings.optionCancel],
-                    "buttonStyles": [SimpleMessageDialog.ButtonStyle.TintedBlue, SimpleMessageDialog.ButtonStyle.TintedBlack],
-                    "buttonCallBacks": [function () {
-                            pluginPreferencesView.visible = false;
-                            PluginModel.uninstallPlugin(pluginId);
-                            PluginListModel.removePlugin(index);
-                            var pluginPath = pluginId.split('/');
-                            PluginListModel.setVersionStatus(pluginPath[pluginPath.length - 1], PluginStatus.INSTALLABLE);
-                        }]
-                })
-        }
-
-        Rectangle {
-            Layout.bottomMargin: 10
-            height: 2
-            Layout.fillWidth: true
-            color: "transparent"
-            border.width: 1
-            border.color: JamiTheme.separationLine
-        }
     }
 }
diff --git a/src/app/settingsview/components/PluginSettingsPage.qml b/src/app/settingsview/components/PluginSettingsPage.qml
index 6a3a28c67..e9afdc7d1 100644
--- a/src/app/settingsview/components/PluginSettingsPage.qml
+++ b/src/app/settingsview/components/PluginSettingsPage.qml
@@ -25,29 +25,84 @@ import "../../commoncomponents"
 
 SettingsPageBase {
     id: root
-    contentFlickableWidth: Math.min(root.width, root.width - 2 * JamiTheme.preferredSettingsMarginSize)
     title: JamiStrings.pluginSettingsTitle
-
-    flickableContent: ColumnLayout {
-        id: pluginSettingsColumnLayout
-
-        width: contentFlickableWidth
-        spacing: JamiTheme.settingsBlockSpacing
+    onWidthChanged: resolvePanes()
+    flickableContent: RowLayout {
+        width: parent.width
         anchors.left: parent.left
         anchors.leftMargin: JamiTheme.preferredSettingsMarginSize
-
         ColumnLayout {
             id: generalSettings
-            Layout.preferredWidth: root.width
-            spacing: JamiTheme.settingsCategorySpacing
-        }
-        PluginListView {
-            id: pluginListView
-
-            Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
+            Layout.maximumWidth: 3 * (JamiTheme.remotePluginWidthDelegate + 20)
             Layout.preferredWidth: parent.width
-            Layout.minimumHeight: 0
-            Layout.preferredHeight: childrenRect.height
+            Layout.rightMargin: 80
+            spacing: JamiTheme.settingsBlockSpacing
+            // View of installed plugins
+            PluginListView {
+                id: pluginList
+                Layout.fillWidth: true
+                Layout.rightMargin: 20
+                Layout.preferredHeight: childrenRect.height
+                Connections {
+                    target: pluginPreferencesView
+                    function onClosed() {
+                        pluginList.currentIndex = -1;
+                    }
+                }
+            }
+            // View of available plugins in the store
+            PluginStoreListView {
+                Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter
+                Layout.fillWidth: true
+            }
+            InstallManuallyView {
+                Layout.fillWidth: true
+                spacing: 10
+            }
         }
     }
+    property real previousDetailsWidth: 500
+    property real previousWidth: 500
+    // This function governs the visibility of the plugin content and tracks the
+    // the width of the SplitView and the details panel. This function should be
+    // called when the width of the SplitView changes, when the SplitView is shown,
+    // and when the details panel is shown. When called with force=true, it is being
+    // called from a visibleChanged event, and we should not update the previous widths.
+    function resolvePanes(force = false) {
+        // If the details panel is not visible, then show the generalSettings.
+        if (!pluginPreferencesView.visible) {
+            pageContainer.visible = true;
+            return;
+        }
+        // Next we compute whether the SplitView is expanding or shrinking.
+        const isExpanding = width > previousWidth;
+        //  width has a first bad state
+        const preferencePreferredWidth = pluginPreferencesView.width === 0 ? 500 : pluginPreferencesView.width;
+        // If the SplitView is not wide enough to show both the generalSettings
+        // and the details panel, then hide the generalSettings.
+        if (width < 522 + preferencePreferredWidth && (!isExpanding || force) && pageContainer.visible) {
+            if (!force)
+                previousDetailsWidth = pluginPreferencesView.width;
+            pageContainer.visible = false;
+        } else if (width >= JamiTheme.mainViewPaneMinWidth + previousDetailsWidth && (isExpanding || force) && !pageContainer.visible) {
+            pageContainer.visible = true;
+        }
+        if (!force)
+            previousWidth = width;
+    }
+
+    onResizingChanged: if (pageContainer.visible)
+        pluginPreferencesView.previousWidth = pluginPreferencesView.width
+
+    PluginPreferencesView {
+        id: pluginPreferencesView
+        SplitView.maximumWidth: root.width
+        SplitView.minimumWidth: 500
+        SplitView.preferredWidth: 500
+        SplitView.fillHeight: true
+        property int previousWidth: 500
+        currentIndex: pluginList.currentIndex
+        visible: pluginList.currentIndex != -1
+        onVisibleChanged: root.resolvePanes(true)
+    }
 }
diff --git a/src/app/settingsview/components/PluginStoreListView.qml b/src/app/settingsview/components/PluginStoreListView.qml
index a47adc8de..2a32087fc 100644
--- a/src/app/settingsview/components/PluginStoreListView.qml
+++ b/src/app/settingsview/components/PluginStoreListView.qml
@@ -25,80 +25,63 @@ import net.jami.Constants 1.1
 import "../../commoncomponents"
 
 ColumnLayout {
-    function installPlugin() {
-        var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/JamiFileDialog.qml", {
-                "title": JamiStrings.selectPluginInstall,
-                "fileMode": JamiFileDialog.OpenFile,
-                "folder": StandardPaths.writableLocation(StandardPaths.DownloadLocation),
-                "nameFilters": [JamiStrings.pluginFiles, JamiStrings.allFiles]
-            });
-        dlg.fileAccepted.connect(function (file) {
-                var url = UtilsAdapter.getAbsPath(file.toString());
-                PluginModel.installPlugin(url, true);
-                PluginListModel.addPlugin();
-            });
+    property bool storeAvailable: true
+    Component.onCompleted: {
+        PluginAdapter.getPluginsFromStore();
     }
-    RowLayout {
-        Layout.fillWidth: true
-        Layout.fillHeight: true
-        Label {
-            Layout.fillWidth: true
-            Layout.preferredHeight: 25
-
-            text: JamiStrings.pluginStoreTitle
-            font.pointSize: JamiTheme.headerFontSize
-            font.kerning: true
-            color: JamiTheme.textColor
-
-            horizontalAlignment: Text.AlignLeft
-            verticalAlignment: Text.AlignVCenter
+    Connections {
+        target: PluginAdapter
+        function onStoreNotAvailable() {
+            storeAvailable = false;
         }
-        RowLayout {
-            Layout.alignment: Qt.AlignRight
-            MaterialButton {
-                id: installManually
+    }
+    Label {
+        Layout.fillWidth: true
+        Layout.bottomMargin: 20
+        text: JamiStrings.pluginStoreTitle
+        font.pixelSize: JamiTheme.settingsTitlePixelSize
+        font.kerning: true
+        color: JamiTheme.textColor
+        horizontalAlignment: Text.AlignLeft
+        verticalAlignment: Text.AlignVCenter
+    }
+    Loader {
+        active: storeAvailable
+        Layout.fillWidth: true
+        Layout.alignment: Qt.AlignHCenter
+        Layout.preferredHeight: active ? item.height : 0
+        sourceComponent: Flow {
+            id: pluginStoreList
+            height: childrenRect.height
+            spacing: 20
+            Repeater {
+                model: PluginStoreListModel
 
-                TextMetrics {
-                    id: installManuallyTextSize
-                    font.weight: Font.Black
-                    font.pixelSize: JamiTheme.wizardViewButtonFontPixelSize
-                    font.capitalization: Font.Capitalize
-                    text: JamiStrings.installManually
+                delegate: PluginAvailableDelagate {
+                    id: pluginItemDelegate
+                    width: JamiTheme.remotePluginWidthDelegate
+                    height: JamiTheme.remotePluginHeightDelegate
+                    pluginName: Name
+                    pluginIcon: IconPath
+                    pluginDescription: Description
+                    pluginAuthor: Author
+                    pluginShortDescription: ""
+                    pluginStatus: Status
                 }
-                secondary: true
-                preferredWidth: installManuallyTextSize.width
-                text: JamiStrings.installManually
-                toolTipText: JamiStrings.installManually
-                fontSize: 15
-                onClicked: installPlugin()
             }
         }
     }
-
-    Flow {
-        id: pluginStoreList
-
+    Loader {
         Layout.fillWidth: true
-        spacing: 20
-        Layout.preferredHeight: childrenRect.height
-        clip: true
-        Repeater {
-            model: PluginStoreListModel
-
-            delegate: PluginAvailableDelagate {
-                id: pluginItemDelegate
-
-                width: 350
-                height: 400
-                pluginId: Id
-                pluginTitle: Title
-                pluginIcon: IconPath
-                pluginBackground: Background === '' ? JamiTheme.backgroundColor : Background
-                pluginDescription: Description
-                pluginAuthor: Author
-                pluginShortDescription: ""
-                pluginStatus: Status
-            }
+        Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+        Layout.preferredHeight: active ? JamiTheme.bigFontSize : 0
+        active: !storeAvailable
+        sourceComponent: Text {
+            font.bold: true
+            color: JamiTheme.textColor
+            font.pixelSize: JamiTheme.bigFontSize
+            horizontalAlignment: Text.AlignHCenter
+            text: JamiStrings.pluginStoreNotAvailable
         }
     }
 }
diff --git a/src/app/settingsview/components/SettingsPageBase.qml b/src/app/settingsview/components/SettingsPageBase.qml
index e28a0cd30..69cab4ac7 100644
--- a/src/app/settingsview/components/SettingsPageBase.qml
+++ b/src/app/settingsview/components/SettingsPageBase.qml
@@ -23,41 +23,44 @@ import net.jami.Enums 1.1
 import net.jami.Models 1.1
 import "../../commoncomponents"
 
-Page {
+JamiSplitView {
     id: root
-
     required property Item flickableContent
-
-    property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, root.width - 2 * JamiTheme.preferredSettingsMarginSize)
-
+    property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize)
+    property alias title: settingsPage.title
     property color backgroundColor: JamiTheme.secondaryBackgroundColor
+    property alias pageContainer: settingsPage
+    Page {
+        id: settingsPage
+        SplitView.maximumWidth: root.width
+        SplitView.fillWidth: true
+        SplitView.minimumWidth: 500
+        Rectangle {
+            width: parent.width
+            height: parent.height
+            color: backgroundColor
+        }
+        header: Rectangle {
+            height: JamiTheme.settingsHeaderpreferredHeight
+            width: root.preferredWidth
+            color: backgroundColor
 
-    Rectangle {
-        width: parent.width
-        height: parent.height
-        color: backgroundColor
-    }
-
-    header: Rectangle {
-        height: JamiTheme.settingsHeaderpreferredHeight
-        width: root.preferredWidth
-        color: backgroundColor
+            SettingsHeader {
+                id: settingsHeader
+                title: root.title
+                anchors.fill: parent
+                onBackArrowClicked: viewNode.dismiss()
+            }
+        }
 
-        SettingsHeader {
-            id: settingsHeader
-            title: root.title
+        JamiFlickable {
+            id: flickable
             anchors.fill: parent
-            onBackArrowClicked: viewNode.dismiss()
+            contentHeight: contentItem.childrenRect.height
+            contentItem.children: [flickableContent]
+            topMargin: JamiTheme.preferredSettingsBottomMarginSize
+            bottomMargin: JamiTheme.preferredSettingsBottomMarginSize
+            ScrollBar.horizontal.visible: false
         }
     }
-
-    JamiFlickable {
-        id: flickable
-        anchors.fill: parent
-        contentHeight: contentItem.childrenRect.height
-        contentItem.children: [flickableContent]
-        topMargin: JamiTheme.preferredSettingsBottomMarginSize
-        bottomMargin: JamiTheme.preferredSettingsBottomMarginSize
-        ScrollBar.horizontal.visible: false
-    }
 }
diff --git a/src/app/utils.cpp b/src/app/utils.cpp
index 1dfd8cf7e..5d664a85e 100644
--- a/src/app/utils.cpp
+++ b/src/app/utils.cpp
@@ -933,3 +933,9 @@ Utils::generateUid()
 {
     return QUuid::createUuid().toString(QUuid::Id128);
 }
+
+QString
+Utils::getPlatformString()
+{
+    return "desktop";
+}
\ No newline at end of file
diff --git a/src/app/utils.h b/src/app/utils.h
index 0084a9b7d..6f7bed819 100644
--- a/src/app/utils.h
+++ b/src/app/utils.h
@@ -127,4 +127,5 @@ QString generateUid();
 QString humanFileSize(qint64 fileSize);
 QString getDebugFilePath();
 
+QString getPlatformString();
 } // namespace Utils
diff --git a/src/libclient/api/pluginmodel.h b/src/libclient/api/pluginmodel.h
index 65fe7e375..ad2234fef 100644
--- a/src/libclient/api/pluginmodel.h
+++ b/src/libclient/api/pluginmodel.h
@@ -40,6 +40,7 @@ struct PluginDetails
 {
     QString id = "";
     QString name = "";
+    QString description = "";
     QString path = "";
     QString version = "";
     QString iconPath = "";
@@ -201,7 +202,6 @@ public:
      * @return true if preference was succesfully reset
      */
     Q_INVOKABLE bool resetPluginPreferencesValues(const QString& path, const QString& accountId);
-
 Q_SIGNALS:
     void chatHandlerStatusUpdated(bool isVisible);
     void modelUpdated();
diff --git a/src/libclient/pluginmodel.cpp b/src/libclient/pluginmodel.cpp
index 3b22a64ae..7c3232a0e 100644
--- a/src/libclient/pluginmodel.cpp
+++ b/src/libclient/pluginmodel.cpp
@@ -50,6 +50,15 @@ namespace lrc {
 
 using namespace api;
 
+enum pluginInstallResult {
+    SUCCESS = 0,
+    PLUGIN_ALREADY_INSTALLED = 100,      /* Plugin already installed with the same version */
+    PLUGIN_OLD_VERSION = 200,            /* Plugin already installed with a newer version */
+    SIGNATURE_VERIFICATION_FAILED = 300, /* Signature verification failed */
+    CERTIFICATE_VERIFICATION_FAILED = 400,
+    INVALID_PLUGIN = 500,
+};
+
 PluginModel::PluginModel()
     : QObject()
 {
@@ -99,6 +108,7 @@ PluginModel::getPluginDetails(const QString& path)
     if (!details.empty()) {
         result.id = details["id"];
         result.name = details["name"];
+        result.description = details["description"];
         result.path = path;
         result.iconPath = details["iconPath"];
         result.version = details["version"];
@@ -311,5 +321,4 @@ PluginModel::resetPluginPreferencesValues(const QString& path, const QString& ac
     Q_EMIT modelUpdated();
     return result;
 }
-
 } // namespace lrc
-- 
GitLab