From 968088785d4e1579e1fd32ce71d0d7c63da782a0 Mon Sep 17 00:00:00 2001
From: Ming Rui Zhang <mingrui.zhang@savoirfairelinux.com>
Date: Thu, 27 Aug 2020 12:59:09 -0400
Subject: [PATCH] mainview: make all context menus generated at run time with
 the same style

By giving a base context menu, all context menus are generated at run time
and kept the same style. Some issues are fixed along with the patch.

Gitlab: #8
Gitlab: #35
Change-Id: Ieb812420fcb44c33d161a62c8574f6705dc5e1a9
---
 images/icons/ic_person_add_black_24dp_2x.png  | Bin 323 -> 0 bytes
 images/icons/ic_person_add_white_24dp.png     | Bin 329 -> 0 bytes
 qml.qrc                                       |   2 +
 ressources.qrc                                |   4 +-
 src/commoncomponents/BaseContextMenu.qml      |  47 +++
 src/commoncomponents/GeneralMenuItem.qml      |   9 +-
 .../js/contextmenugenerator.js                | 116 ++++++
 .../components/CallOverlayButtonGroup.qml     |   2 +-
 .../components/CallViewContextMenu.qml        | 345 ++++++------------
 .../ConversationSmartListContextMenu.qml      | 217 ++++-------
 .../components/ConversationSmartListView.qml  |   4 +
 .../ConversationSmartListViewItemDelegate.qml |   7 +-
 .../components/ParticipantContextMenu.qml     | 126 ++-----
 .../components/ParticipantOverlay.qml         |  14 +-
 .../VideoCallPageContextMenuDeviceItem.qml    |   9 +-
 .../js/videodevicecontextmenuitemcreation.js  |  21 +-
 src/messagesadapter.cpp                       |  18 +-
 src/messagesadapter.h                         |   6 +-
 .../components/BannedItemDelegate.qml         |   2 +-
 19 files changed, 421 insertions(+), 528 deletions(-)
 delete mode 100644 images/icons/ic_person_add_black_24dp_2x.png
 delete mode 100644 images/icons/ic_person_add_white_24dp.png
 create mode 100644 src/commoncomponents/BaseContextMenu.qml
 create mode 100644 src/commoncomponents/js/contextmenugenerator.js

diff --git a/images/icons/ic_person_add_black_24dp_2x.png b/images/icons/ic_person_add_black_24dp_2x.png
deleted file mode 100644
index f3e8931b62c359a28c6cdd1151f3c1b49c5d9889..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 323
zcmV-J0lfZ+P)<h;3K|Lk000e1NJLTq001xm001xu1ONa4{R=S+0003BNkl<ZcmeH_
zu}T9$5I|q5oFAdBSjd@w5bVW9Kgrl7;jYLZ2$tGO5Jch!B)R(u4@F4|Z@j`rxy-G*
z(+K-sKW}#!B9VwE19D0>jRhGgrtC09epr)}*o}WcTAW!WrpOjEo88M+Yf^&A5yc5k
z$hU2jYG4^2*r<W^KL`|RU=be3)W9e_kWwpWdZ-Q6bve{ASOgMgNQ?LMZ32*x|5{Yd
zm_Aqq08%C_SvM{iX}i~AHrNMZV2=x~xMM}dXJf^RTZWvo8yXleq(l@8E;+)39%now
zj?OuyhdUhbg#6J*_HpEdH^jqNj@@4Wfq44v@{1cJz-v0Gf&wpeB4IiPVjvQUd;vR7
VdYQP=*mM8@002ovPDHLkV1flSe@p-X

diff --git a/images/icons/ic_person_add_white_24dp.png b/images/icons/ic_person_add_white_24dp.png
deleted file mode 100644
index 7e7c289d4971337ec3693780d13b26c146c58a5f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 329
zcmV-P0k-~$P)<h;3K|Lk000e1NJLTq001xm001xu1ONa4{R=S+0003HNkl<ZcmeIw
zPfEi;97pkVWqSzSmM$db4uX4eqerst+LA1K1Hq;4qzEE<fVRmc42se&oM$#N2$?_0
zFBA&%zWe#W%tWDfg+e4`6l|;Iq{Kw#MKs-jIsa2+^t%EEJW<y|6Ea|qsk93q!JHzN
z0~xkve!yRKK!L559N1!Ovj;$qt)(1Du{DtcF$z<PV>95&=c7)Ex&ZntsOymfDWHiA
zg$@)`eY^zFi<z-vQ*Fsa-@W*e-3!1DfFrKB;ej<JKh;Xs+%x8qLn)9jrodLtH76Fp
zxE>e0;oB?c^qK%8UTE4gLto&GPuldssShyXo3?#1bOU!tx^)93lHT2bFewlK`St_|
bKmdLLcf8r0$|reu00000NkvXXu0mjflV6Dp

diff --git a/qml.qrc b/qml.qrc
index d9e8085f7..619d53da6 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -103,5 +103,7 @@
         <file>src/mainview/components/RecordBox.qml</file>
         <file>src/commoncomponents/ElidedTextLabel.qml</file>
         <file>src/mainview/components/SipInputPanel.qml</file>
+        <file>src/commoncomponents/js/contextmenugenerator.js</file>
+        <file>src/commoncomponents/BaseContextMenu.qml</file>
     </qresource>
 </RCC>
diff --git a/ressources.qrc b/ressources.qrc
index 357fe0bf7..14eb8f575 100644
--- a/ressources.qrc
+++ b/ressources.qrc
@@ -23,6 +23,8 @@
         <file>images/icons/ic_add_black_18dp_2x.png</file>
         <file>images/icons/info-24px.svg</file>
         <file>images/icons/backup-24px.svg</file>
+        <file>images/icons/check_box_outline_blank-24px.svg</file>
+        <file>images/icons/check_box-24px.svg</file>
         <file>images/icons/devices-24px.svg</file>
         <file>images/icons/ic_arrow_back_24px.svg</file>
         <file>images/icons/ic_arrow_back_white_24dp.png</file>
@@ -52,8 +54,6 @@
         <file>images/icons/pause_circle_outline-24px.svg</file>
         <file>images/icons/play_circle_outline-24px.svg</file>
         <file>images/icons/ic_pause_white_100px.png</file>
-        <file>images/icons/ic_person_add_black_24dp_2x.png</file>
-        <file>images/icons/ic_person_add_white_24dp.png</file>
         <file>images/icons/ic_phone_24px.svg</file>
         <file>images/icons/ic_photo_camera_white_24dp_2x.png</file>
         <file>images/icons/ic_baseline-search-24px.svg</file>
diff --git a/src/commoncomponents/BaseContextMenu.qml b/src/commoncomponents/BaseContextMenu.qml
new file mode 100644
index 000000000..05227a5ae
--- /dev/null
+++ b/src/commoncomponents/BaseContextMenu.qml
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@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 2.14
+import QtQuick.Controls 2.14
+import QtGraphicalEffects 1.12
+import net.jami.Models 1.0
+
+Menu {
+    id: root
+
+    property int menuItemsPreferredWidth: 220
+    property int menuItemsPreferredHeight: 48
+    property int generalMenuSeparatorCount: 0
+    property int commonBorderWidth: 1
+    font.pointSize: JamiTheme.menuFontSize
+
+    function openMenu(){
+        visible = true
+        visible = false
+        visible = true
+    }
+
+    background: Rectangle {
+        implicitWidth: menuItemsPreferredWidth
+        implicitHeight: menuItemsPreferredHeight
+                        * (root.count - generalMenuSeparatorCount)
+
+        border.width: commonBorderWidth
+        border.color: JamiTheme.tabbarBorderColor
+    }
+}
diff --git a/src/commoncomponents/GeneralMenuItem.qml b/src/commoncomponents/GeneralMenuItem.qml
index a457f8d88..199f59d2f 100644
--- a/src/commoncomponents/GeneralMenuItem.qml
+++ b/src/commoncomponents/GeneralMenuItem.qml
@@ -35,8 +35,7 @@ MenuItem {
     property string iconSource: ""
     property int preferredWidth: 220
     property int preferredHeight: 48
-    property int topBorderWidth: 0
-    property int bottomBorderWidth: 0
+
     property int leftBorderWidth: 0
     property int rightBorderWidth: 0
 
@@ -99,8 +98,6 @@ MenuItem {
         id: contextMenuBackgroundRect
 
         anchors.fill: parent
-        anchors.topMargin: topBorderWidth
-        anchors.bottomMargin: bottomBorderWidth
         anchors.leftMargin: leftBorderWidth
         anchors.rightMargin: rightBorderWidth
 
@@ -132,8 +129,8 @@ MenuItem {
             commonBorder: false
             lBorderwidth: leftBorderWidth
             rBorderwidth: rightBorderWidth
-            tBorderwidth: topBorderWidth
-            bBorderwidth: bottomBorderWidth
+            tBorderwidth: 0
+            bBorderwidth: 0
             borderColor: JamiTheme.tabbarBorderColor
         }
     }
diff --git a/src/commoncomponents/js/contextmenugenerator.js b/src/commoncomponents/js/contextmenugenerator.js
new file mode 100644
index 000000000..7c55365a1
--- /dev/null
+++ b/src/commoncomponents/js/contextmenugenerator.js
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * Author: Mingrui Zhang <mingrui.zhang@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/>.
+ */
+
+// Global base context menu, object variable for creation.
+var baseContextMenuComponent
+var baseContextMenuObject
+var menuItemList = []
+
+function createBaseContextMenuObjects(parent) {
+    // If already created, return, since object cannot be destroyed.
+    if (baseContextMenuObject)
+        return
+
+    baseContextMenuComponent = Qt.createComponent(
+                "../BaseContextMenu.qml")
+    if (baseContextMenuComponent.status === Component.Ready)
+        finishCreation(parent)
+    else if (baseContextMenuComponent.status === Component.Error)
+        console.log("Error loading component:",
+                    baseContextMenuComponent.errorString())
+}
+
+function finishCreation(parent) {
+    baseContextMenuObject = baseContextMenuComponent.createObject(parent)
+    if (baseContextMenuObject === null) {
+        // Error Handling.
+        console.log("Error creating object for base context menu")
+    }
+
+    baseContextMenuObject.closed.connect(function (){
+        // Remove the menu items when hidden.
+        for (var i = 0; i < menuItemList.length; i++) {
+            baseContextMenuObject.removeItem(menuItemList[i])
+        }
+    })
+
+    baseContextMenuObject.aboutToShow.connect(function (){
+        // Add default separator at the bottom.
+        addMenuSeparator(8, "transparent")
+    })
+}
+
+function addMenuSeparator(separatorHeight, separatorColor) {
+    var menuSeparatorObject
+    var menuSeparatorComponent = Qt.createComponent("../GeneralMenuSeparator.qml");
+    if (menuSeparatorComponent.status === Component.Ready) {
+        baseContextMenuObject.generalMenuSeparatorCount ++
+        menuSeparatorObject = menuSeparatorComponent.createObject(null,
+                              {preferredWidth: baseContextMenuObject.menuItemsPreferredWidth,
+                               preferredHeight: separatorHeight ? separatorHeight : 1})
+        if (separatorColor)
+            menuSeparatorObject.separatorColor = separatorColor
+    } else if (menuSeparatorComponent.status === Component.Error)
+        console.log("Error loading component:",
+                    menuSeparatorComponent.errorString())
+    if (menuSeparatorObject !== null) {
+        baseContextMenuObject.addItem(menuSeparatorObject)
+
+        menuItemList.push(menuSeparatorObject)
+    } else {
+        // Error handling.
+        console.log("Error creating object")
+    }
+}
+
+function addMenuItem(itemName,
+                     iconSource,
+                     onClickedCallback) {
+    if (!baseContextMenuObject.count){
+        // Add default separator at the top.
+        addMenuSeparator(8, "transparent")
+    }
+
+    var menuItemObject
+    var menuItemComponent = Qt.createComponent("../GeneralMenuItem.qml");
+    if (menuItemComponent.status === Component.Ready) {
+        menuItemObject = menuItemComponent.createObject(null,
+                         {itemName: itemName,
+                          iconSource: iconSource,
+                          leftBorderWidth: baseContextMenuObject.commonBorderWidth,
+                          rightBorderWidth: baseContextMenuObject.commonBorderWidth})
+    } else if (menuItemComponent.status === Component.Error)
+        console.log("Error loading component:",
+                    menuItemComponent.errorString())
+    if (menuItemObject !== null) {
+        menuItemObject.clicked.connect(function () {baseContextMenuObject.close()})
+        menuItemObject.clicked.connect(onClickedCallback)
+        menuItemObject.icon.color = "black"
+
+        baseContextMenuObject.addItem(menuItemObject)
+
+        menuItemList.push(menuItemObject)
+    } else {
+        // Error handling.
+        console.log("Error creating object")
+    }
+}
+
+function getMenu() {
+    return baseContextMenuObject
+}
diff --git a/src/mainview/components/CallOverlayButtonGroup.qml b/src/mainview/components/CallOverlayButtonGroup.qml
index 107ab9e25..88a097fac 100644
--- a/src/mainview/components/CallOverlayButtonGroup.qml
+++ b/src/mainview/components/CallOverlayButtonGroup.qml
@@ -230,7 +230,7 @@ Rectangle {
 
             onClicked: {
                 var rectPos = mapToItem(callStackViewWindow, optionsButton.x, optionsButton.y)
-                callViewContextMenu.activate()
+                callViewContextMenu.openMenu()
                 callViewContextMenu.x = rectPos.x + optionsButton.width/2 - callViewContextMenu.width/2
                 callViewContextMenu.y = rectPos.y - 12 - callViewContextMenu.height
             }
diff --git a/src/mainview/components/CallViewContextMenu.qml b/src/mainview/components/CallViewContextMenu.qml
index 2deeeaac2..5b2272a7a 100644
--- a/src/mainview/components/CallViewContextMenu.qml
+++ b/src/mainview/components/CallViewContextMenu.qml
@@ -17,6 +17,7 @@
  * 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 2.14
 import QtQuick.Controls 2.14
 import QtGraphicalEffects 1.12
@@ -24,61 +25,125 @@ import net.jami.Models 1.0
 
 import "../../commoncomponents"
 
+import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
 import "../js/videodevicecontextmenuitemcreation.js" as VideoDeviceContextMenuItemCreation
 import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
+import "../js/screenrubberbandcreation.js" as ScreenRubberBandCreation
 
-Menu {
+Item {
     id: root
 
-    property int generalMenuSeparatorCount: 0
-    property int commonBorderWidth: 1
-
-    signal pluginItemClicked
-
-    font.pointSize: JamiTheme.textFontSize+3
-
     property bool isSIP: false
     property bool isPaused: false
     property bool isAudioOnly: false
     property bool isRecording: false
 
+    signal pluginItemClicked
     signal transferCallButtonClicked
 
-    function activate() {
+    function openMenu(){
+        if (isSIP){
+            ContextMenuGenerator.addMenuItem(isPaused ? qsTr("Resume call") : qsTr("Hold call"),
+                                             isPaused ?
+                                                 "qrc:/images/icons/play_circle_outline-24px.svg" :
+                                                 "qrc:/images/icons/pause_circle_outline-24px.svg",
+                                             function (){
+                                                 CallAdapter.holdThisCallToggle()
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Sip Input Panel"),
+                                             "qrc:/images/icons/ic_keypad.svg",
+                                             function (){
+                                                 sipInputPanel.open()
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Transfer call"),
+                                             "qrc:/images/icons/phone_forwarded-24px.svg",
+                                             function (){
+                                                 root.transferCallButtonClicked()
+                                             })
+
+            ContextMenuGenerator.addMenuSeparator()
+        }
+
+        if (!isAudioOnly) {
+            ContextMenuGenerator.addMenuItem(isRecording ? qsTr("Stop recording") :
+                                                           qsTr("Start recording"),
+                                             "qrc:/images/icons/ic_video_call_24px.svg",
+                                             function (){
+                                                  CallAdapter.recordThisCallToggle()
+                                             })
+            ContextMenuGenerator.addMenuItem(videoCallPage.isFullscreen ? qsTr("Exit full screen") :
+                                                                          qsTr("Full screen mode"),
+                                             videoCallPage.isFullscreen ?
+                                                 "qrc:/images/icons/close_fullscreen-24px.svg" :
+                                                 "qrc:/images/icons/open_in_full-24px.svg",
+                                             function (){
+                                                  videoCallPageRect.needToShowInFullScreen()
+                                             })
+
+            ContextMenuGenerator.addMenuSeparator()
+
+            generateDeviceMenuItem()
+
+            ContextMenuGenerator.addMenuSeparator()
+
+            ContextMenuGenerator.addMenuItem(qsTr("Share entire screen"),
+                                             "qrc:/images/icons/screen_share-24px.svg",
+                                             function (){
+                                                 if (Qt.application.screens.length === 1) {
+                                                     AvAdapter.shareEntireScreen(0)
+                                                 } else {
+                                                     SelectScreenWindowCreation.createSelectScreenWindowObject()
+                                                     SelectScreenWindowCreation.showSelectScreenWindow()
+                                                 }
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Share screen area"),
+                                             "qrc:/images/icons/screen_share-24px.svg",
+                                             function (){
+                                                 if (Qt.application.screens.length === 1) {
+                                                     ScreenRubberBandCreation.createScreenRubberBandWindowObject(
+                                                                 null, 0)
+                                                     ScreenRubberBandCreation.showScreenRubberBandWindow()
+                                                 } else {
+                                                     SelectScreenWindowCreation.createSelectScreenWindowObject(true)
+                                                     SelectScreenWindowCreation.showSelectScreenWindow()
+                                                 }
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Share file"),
+                                             "qrc:/images/icons/insert_photo-24px.svg",
+                                             function (){
+                                                  jamiFileDialog.open()
+                                             })
+        }
+
+        ContextMenuGenerator.addMenuItem(qsTr("Toggle plugin"),
+                                         "qrc:/images/icons/extension_24dp.svg",
+                                         function (){
+                                              root.pluginItemClicked()
+                                         })
+
+        root.height = ContextMenuGenerator.getMenu().height
+        root.width = ContextMenuGenerator.getMenu().width
+        ContextMenuGenerator.getMenu().open()
+    }
+
+    function generateDeviceMenuItem() {
         var deviceContextMenuInfoMap = AvAdapter.populateVideoDeviceContextMenuItem()
+
         /*
          * Somehow, the map size is undefined, so use this instead.
          */
         var mapSize = deviceContextMenuInfoMap["size"]
 
-        var count = 2
+        if (mapSize === 0)
+            VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
+                        qsTr("No video device"), false)
+
         for (var deviceName in deviceContextMenuInfoMap) {
-            if (deviceName === "size" || root.isAudioOnly)
+            if (deviceName === "size")
                 continue
-            if (videoDeviceItem.itemName === "No video device") {
-                videoDeviceItem.checkable = true
-                videoDeviceItem.itemName = deviceName
-                videoDeviceItem.checked = deviceContextMenuInfoMap[deviceName]
-                if (count === mapSize)
-                    root.open()
-            } else {
-                VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
-                            deviceName, deviceContextMenuInfoMap[deviceName],
-                            count === mapSize)
-            }
-            count++
+            VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
+                        deviceName, deviceContextMenuInfoMap[deviceName])
         }
-        root.open()
-    }
-
-    Component.onCompleted: {
-        VideoDeviceContextMenuItemCreation.setVideoContextMenuObject(root)
-    }
-
-
-    onClosed: {
-        videoDeviceItem.itemName = "No video device"
-        VideoDeviceContextMenuItemCreation.removeCreatedItems()
     }
 
     JamiFileDialog {
@@ -92,187 +157,13 @@ Menu {
         }
     }
 
-    /*
-     * All GeneralMenuItems should remain the same width / height.
-     */
-    GeneralMenuItem {
-        id: holdCallButton
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        itemName: isPaused? qsTr("Resume call") : qsTr("Hold call")
-        iconSource: isPaused? "qrc:/images/icons/play_circle_outline-24px.svg" : "qrc:/images/icons/pause_circle_outline-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            CallAdapter.holdThisCallToggle()
-            root.close()
-        }
-    }
-
-    GeneralMenuItem {
-        id: showSipInputPanelButton
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        itemName: qsTr("Sip Input Panel")
-        iconSource: "qrc:/images/icons/ic_keypad.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.close()
-            sipInputPanel.open()
-        }
-    }
-
-    GeneralMenuItem {
-        id: transferCallButton
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        itemName: qsTr("Transfer call")
-        iconSource: "qrc:/images/icons/phone_forwarded-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.transferCallButtonClicked()
-            root.close()
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startRecordingItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: startRecordingItem
-
-        itemName: isRecording? qsTr("Stop recording") : qsTr("Start recording")
-        iconSource: "qrc:/images/icons/ic_video_call_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.close()
-            CallAdapter.recordThisCallToggle()
-        }
-    }
-
-    GeneralMenuItem {
-        id: fullScreenItem
-
-        itemName: videoCallPage.isFullscreen ? qsTr("Exit full screen") : qsTr(
-                                     "Full screen mode")
-        iconSource: videoCallPage.isFullscreen ? "qrc:/images/icons/close_fullscreen-24px.svg" : "qrc:/images/icons/open_in_full-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.close()
-            videoCallPageRect.needToShowInFullScreen()
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startRecordingItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    VideoCallPageContextMenuDeviceItem {
-        id: videoDeviceItem
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        contextMenuPreferredWidth: root.implicitWidth
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startRecordingItem.preferredWidth
-        preferredHeight: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: shareEntireScreenItem
-
-        itemName: qsTr("Share entire screen")
-        iconSource: "qrc:/images/icons/screen_share-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        onClicked: {
-            root.close()
-            if (Qt.application.screens.length === 1) {
-                AvAdapter.shareEntireScreen(0)
-            } else {
-                SelectScreenWindowCreation.createSelectScreenWindowObject()
-                SelectScreenWindowCreation.showSelectScreenWindow()
-            }
-        }
-    }
-
-    GeneralMenuItem {
-        id: shareScreenAreaItem
-
-        itemName: qsTr("Share screen area")
-        iconSource: "qrc:/images/icons/screen_share-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        onClicked: {
-            root.close()
-            if (Qt.application.screens.length === 1) {
-                ScreenRubberBandCreation.createScreenRubberBandWindowObject(
-                            null, 0)
-                ScreenRubberBandCreation.showScreenRubberBandWindow()
-            } else {
-                SelectScreenWindowCreation.createSelectScreenWindowObject(true)
-                SelectScreenWindowCreation.showSelectScreenWindow()
-            }
-        }
-    }
-
-    GeneralMenuItem {
-        id: shareFileItem
-
-        itemName: qsTr("Share file")
-        iconSource: "qrc:/images/icons/insert_photo-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
+    Component.onCompleted: {
+        ContextMenuGenerator.createBaseContextMenuObjects(root)
+        VideoDeviceContextMenuItemCreation.setVideoContextMenuObject(ContextMenuGenerator.getMenu())
 
-        onClicked: {
-            root.close()
-            jamiFileDialog.open()
-        }
+        ContextMenuGenerator.getMenu().closed.connect(function (){
+            VideoDeviceContextMenuItemCreation.removeCreatedItems()
+        })
     }
 
     /* TODO: In the future we want to implement this
@@ -289,31 +180,5 @@ Menu {
             root.close()
         }
     }*/
-
-    GeneralMenuItem {
-        id: pluginItem
-
-        itemName: qsTr("Toggle plugin")
-        iconSource: "qrc:/images/icons/extension_24dp.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.pluginItemClicked()
-            root.close()
-        }
-    }
-
-    background: Rectangle {
-        implicitWidth: startRecordingItem.preferredWidth
-        implicitHeight: startRecordingItem.preferredHeight
-                        * (root.count
-                          - (isSIP? 0 : 2)
-                          - (isAudioOnly? 6 : 0)
-                          - generalMenuSeparatorCount)
-
-        border.width: commonBorderWidth
-        border.color: JamiTheme.tabbarBorderColor
-    }
 }
 
diff --git a/src/mainview/components/ConversationSmartListContextMenu.qml b/src/mainview/components/ConversationSmartListContextMenu.qml
index 26c5406f2..c5aa47138 100644
--- a/src/mainview/components/ConversationSmartListContextMenu.qml
+++ b/src/mainview/components/ConversationSmartListContextMenu.qml
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
@@ -16,6 +15,7 @@
  * 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 2.14
 import QtQuick.Controls 2.14
 import QtGraphicalEffects 1.12
@@ -23,157 +23,88 @@ import net.jami.Models 1.0
 
 import "../../commoncomponents"
 
-Menu {
-    id: contextMenu
+import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
+
+Item {
+    id: root
+
     property string responsibleAccountId: ""
     property string responsibleConvUid: ""
-
-    property int generalMenuSeparatorCount: 0
-    property int commonBorderWidth: 1
-    font.pointSize: JamiTheme.menuFontSize
+    property int contactType: Profile.Type.INVALID
 
     function openMenu(){
-        visible = true
-        visible = false
-        visible = true
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: 8
-        separatorColor: "transparent"
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    /*
-     * All GeneralMenuItems should remain the same width / height.
-     */
-    GeneralMenuItem {
-        id: startVideoCallItem
-
-        itemName: qsTr("Start video call")
-        iconSource: "qrc:/images/icons/ic_video_call_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ConversationsAdapter.selectConversation(responsibleAccountId,
-                                                    responsibleConvUid, false)
-            CallAdapter.placeCall()
-        }
-    }
-
-    GeneralMenuItem {
-        id: startAudioCallItem
-
-        itemName: qsTr("Start audio call")
-        iconSource: "qrc:/images/icons/ic_phone_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ConversationsAdapter.selectConversation(responsibleAccountId,
-                                                    responsibleConvUid, false)
-            CallAdapter.placeAudioOnlyCall()
-        }
-    }
-
-    GeneralMenuItem {
-        id: clearConversationItem
-
-        itemName: qsTr("Clear conversation")
-        iconSource: "qrc:/images/icons/ic_clear_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ClientWrapper.utilsAdaptor.clearConversationHistory(responsibleAccountId,
-                                                  responsibleConvUid)
-        }
-    }
-
-    GeneralMenuItem {
-        id: removeContactItem
-
-        itemName: qsTr("Remove contact")
-        iconSource: "qrc:/images/icons/round-remove_circle-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ClientWrapper.utilsAdaptor.removeConversation(responsibleAccountId,
-                                            responsibleConvUid)
+        ContextMenuGenerator.addMenuItem(qsTr("Start video call"),
+                                         "qrc:/images/icons/ic_video_call_24px.svg",
+                                         function (){
+                                             ConversationsAdapter.selectConversation(
+                                                         responsibleAccountId,
+                                                         responsibleConvUid, false)
+                                             CallAdapter.placeCall()
+                                         })
+        ContextMenuGenerator.addMenuItem(qsTr("Start audio call"),
+                                         "qrc:/images/icons/ic_phone_24px.svg",
+                                         function (){
+                                             ConversationsAdapter.selectConversation(
+                                                         responsibleAccountId,
+                                                         responsibleConvUid, false)
+                                             CallAdapter.placeAudioOnlyCall()
+                                         })
+        ContextMenuGenerator.addMenuItem(qsTr("Clear conversation"),
+                                         "qrc:/images/icons/ic_clear_24px.svg",
+                                         function (){
+                                             ClientWrapper.utilsAdaptor.clearConversationHistory(
+                                                         responsibleAccountId,
+                                                         responsibleConvUid)
+                                         })
+
+        if (contactType === Profile.Type.RING || contactType === Profile.Type.SIP) {
+            ContextMenuGenerator.addMenuItem(qsTr("Remove contact"),
+                                             "qrc:/images/icons/round-remove_circle-24px.svg",
+                                             function (){
+                                                 ClientWrapper.utilsAdaptor.removeConversation(
+                                                             responsibleAccountId,
+                                                             responsibleConvUid)
+                                             })
         }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: commonBorderWidth
 
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
+        if (contactType === Profile.Type.RING || contactType === Profile.Type.PENDING) {
+            ContextMenuGenerator.addMenuSeparator()
+
+            if (contactType === Profile.Type.PENDING) {
+                ContextMenuGenerator.addMenuItem(qsTr("Accept request"),
+                                                 "qrc:/images/icons/person_add-24px.svg",
+                                                 function (){
+                                                     MessagesAdapter.acceptInvitation(
+                                                                 responsibleConvUid)
+                                                 })
+                ContextMenuGenerator.addMenuItem(qsTr("Decline request"),
+                                                 "qrc:/images/icons/round-close-24px.svg",
+                                                 function (){
+                                                     MessagesAdapter.refuseInvitation(
+                                                                 responsibleConvUid)
+                                                 })
+            }
+            ContextMenuGenerator.addMenuItem(qsTr("Block contact"),
+                                             "qrc:/images/icons/ic_block_24px.svg",
+                                             function (){
+                                                 MessagesAdapter.blockConversation(
+                                                             responsibleConvUid)
+                                             })
+
+            ContextMenuGenerator.addMenuSeparator()
+            ContextMenuGenerator.addMenuItem(qsTr("Profile"),
+                                             "qrc:/images/icons/person-24px.svg",
+                                             function (){
+                                                 userProfile.open()
+                                             })
         }
-    }
-
-    GeneralMenuItem {
-        id: blockContactItem
-
-        itemName: qsTr("Block contact")
-        iconSource: "qrc:/images/icons/ic_block_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
 
-        onClicked: {
-            contextMenu.close()
-            ClientWrapper.utilsAdaptor.removeConversation(responsibleAccountId,
-                                            responsibleConvUid, true)
-        }
+        root.height = ContextMenuGenerator.getMenu().height
+        root.width = ContextMenuGenerator.getMenu().width
+        ContextMenuGenerator.getMenu().open()
     }
 
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: profileItem
-
-        itemName: qsTr("Profile")
-        iconSource: "qrc:/images/icons/person-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            userProfile.open()
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: 8
-        separatorColor: "transparent"
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    background: Rectangle {
-        implicitWidth: startVideoCallItem.preferredWidth
-        implicitHeight: startVideoCallItem.preferredHeight
-                        * (contextMenu.count - generalMenuSeparatorCount)
-
-        border.width: commonBorderWidth
-        border.color: JamiTheme.tabbarBorderColor
+    Component.onCompleted: {
+        ContextMenuGenerator.createBaseContextMenuObjects(root)
     }
 }
diff --git a/src/mainview/components/ConversationSmartListView.qml b/src/mainview/components/ConversationSmartListView.qml
index ee787f780..d16b96d81 100644
--- a/src/mainview/components/ConversationSmartListView.qml
+++ b/src/mainview/components/ConversationSmartListView.qml
@@ -82,6 +82,10 @@ ListView {
         conversationSmartListView.model.setAccount(accountId)
     }
 
+    ConversationSmartListContextMenu {
+        id: smartListContextMenu
+    }
+
     Connections {
         target: CallAdapter
 
diff --git a/src/mainview/components/ConversationSmartListViewItemDelegate.qml b/src/mainview/components/ConversationSmartListViewItemDelegate.qml
index 7b564a855..74d07dbc8 100644
--- a/src/mainview/components/ConversationSmartListViewItemDelegate.qml
+++ b/src/mainview/components/ConversationSmartListViewItemDelegate.qml
@@ -180,7 +180,7 @@ ItemDelegate {
                 itemSmartListBackground.color = JamiTheme.releaseColor
             }
             if (mouse.button === Qt.RightButton) {
-
+                smartListContextMenu.parent = mouseAreaSmartListItemDelegate
 
                 /*
                  * Make menu pos at mouse.
@@ -191,6 +191,7 @@ ItemDelegate {
                 smartListContextMenu.y = relativeMousePos.y
                 smartListContextMenu.responsibleAccountId = ClientWrapper.utilsAdaptor.getCurrAccId()
                 smartListContextMenu.responsibleConvUid = UID
+                smartListContextMenu.contactType = ContactType
                 userProfile.responsibleConvUid = UID
                 userProfile.aliasText = DisplayName
                 userProfile.registeredNameText = DisplayID
@@ -225,8 +226,4 @@ ItemDelegate {
             }
         }
     }
-
-    ConversationSmartListContextMenu {
-        id: smartListContextMenu
-    }
 }
diff --git a/src/mainview/components/ParticipantContextMenu.qml b/src/mainview/components/ParticipantContextMenu.qml
index 9397701cf..87d883a5d 100644
--- a/src/mainview/components/ParticipantContextMenu.qml
+++ b/src/mainview/components/ParticipantContextMenu.qml
@@ -1,7 +1,7 @@
-
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ * Author: Mingrui Zhang <mingrui.zhang@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
@@ -16,6 +16,7 @@
  * 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 2.14
 import QtQuick.Controls 2.14
 import QtGraphicalEffects 1.12
@@ -23,105 +24,46 @@ import net.jami.Models 1.0
 
 import "../../commoncomponents"
 
-import "../js/videodevicecontextmenuitemcreation.js" as VideoDeviceContextMenuItemCreation
-import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
+import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
 
-Menu {
+Item {
     id: root
 
-    property int generalMenuSeparatorCount: 0
-    property int commonBorderWidth: 1
-    font.pointSize: JamiTheme.textFontSize + 3
     property var uri: ""
     property var maximized: true
     property var active: true
-
-    function showHangup(show) {
-        if (show) {
-            hangupItem.visible = true
-            hangupItem.height = hangupItem.preferredHeight
-        } else {
-            hangupItem.visible = false
-            hangupItem.height = 0
-        }
-    }
-
-    function showMaximize(show) {
-        if (show) {
-            maximizeItem.visible = true
-            maximizeItem.height = hangupItem.preferredHeight
-        } else {
-            maximizeItem.visible = false
-            maximizeItem.height = 0
-        }
-    }
-
-    function showMinimize(show) {
-        if (show) {
-            minimizeItem.visible = true
-            minimizeItem.height = hangupItem.preferredHeight
-        } else {
-            minimizeItem.visible = false
-            minimizeItem.height = 0
-        }
-    }
-
-    function setHeight(visibleItems) {
-        root.height = hangupItem.preferredHeight * visibleItems;
-    }
-
-    /*
-     * All GeneralMenuItems should remain the same width / height.
-     */
-    GeneralMenuItem {
-        id: hangupItem
-
-        itemName: qsTr("Hangup")
-        iconSource: "qrc:/images/icons/ic_call_end_white_24px.svg"
-        icon.color: "black"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            CallAdapter.hangupCall(uri)
-            root.close()
-        }
-    }
-    GeneralMenuItem {
-        id: maximizeItem
-
-        itemName: qsTr("Maximize participant")
-        iconSource: "qrc:/images/icons/open_in_full-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !maximized
-
-        onClicked: {
-            CallAdapter.maximizeParticipant(uri, active)
-            root.close()
-        }
+    property var showHangup: false
+    property var showMaximize: false
+    property var showMinimize: false
+
+    function openMenu(){
+        if (showHangup)
+            ContextMenuGenerator.addMenuItem(qsTr("Hang up"),
+                                             "qrc:/images/icons/ic_call_end_white_24px.svg",
+                                             function (){
+                                                 CallAdapter.hangupCall(uri)
+                                             })
+
+        if (showMaximize)
+            ContextMenuGenerator.addMenuItem(qsTr("Maximize participant"),
+                                             "qrc:/images/icons/open_in_full-24px.svg",
+                                             function (){
+                                                  CallAdapter.maximizeParticipant(uri, active)
+                                             })
+        if (showMinimize)
+            ContextMenuGenerator.addMenuItem(qsTr("Minimize participant"),
+                                             "qrc:/images/icons/close_fullscreen-24px.svg",
+                                             function (){
+                                                  CallAdapter.minimizeParticipant()
+                                             })
+
+        root.height = ContextMenuGenerator.getMenu().height
+        root.width = ContextMenuGenerator.getMenu().width
+        ContextMenuGenerator.getMenu().open()
     }
-    GeneralMenuItem {
-        id: minimizeItem
-
-        itemName: qsTr("Minimize participant")
-        iconSource: "qrc:/images/icons/close_fullscreen-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: maximized
-
-        onClicked: {
-            CallAdapter.minimizeParticipant()
-            root.close()
-        }
-    }
-
-    background: Rectangle {
-        implicitWidth: hangupItem.preferredWidth
-        implicitHeight: hangupItem.preferredHeight * 3
 
-        border.width: commonBorderWidth
-        border.color: JamiTheme.tabbarBorderColor
+    Component.onCompleted: {
+        ContextMenuGenerator.createBaseContextMenuObjects(root)
     }
 }
 
diff --git a/src/mainview/components/ParticipantOverlay.qml b/src/mainview/components/ParticipantOverlay.qml
index fb49a4687..2f17b0880 100644
--- a/src/mainview/components/ParticipantOverlay.qml
+++ b/src/mainview/components/ParticipantOverlay.qml
@@ -112,18 +112,14 @@ Rectangle {
                         var layout = CallAdapter.getCurrentLayoutType()
                         var showMaximized = layout !== 2
                         var showMinimized = !(layout === 0 || (layout === 1 && !active))
-                        injectedContextMenu.showHangup(!root.isLocal)
-                        injectedContextMenu.showMaximize(showMaximized)
-                        injectedContextMenu.showMinimize(showMinimized)
-                        injectedContextMenu.setHeight(
-                            (root.isLocal ? 0 : 1)
-                            + (showMaximized ? 1 : 0)
-                            + (showMinimized ? 1 : 0))
+                        injectedContextMenu.showHangup = !root.isLocal
+                        injectedContextMenu.showMaximize = showMaximized
+                        injectedContextMenu.showMinimize = showMinimized
                         injectedContextMenu.uri = uri
                         injectedContextMenu.active = active
                         injectedContextMenu.x = mousePos.x
                         injectedContextMenu.y = mousePos.y - injectedContextMenu.height
-                        injectedContextMenu.open()
+                        injectedContextMenu.openMenu()
                     }
                 }
             }
@@ -166,4 +162,4 @@ Rectangle {
             duration: 500
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml b/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml
index 73e692b7d..a6a4f8043 100644
--- a/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml
+++ b/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
@@ -16,24 +15,21 @@
  * 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 2.14
 import QtQuick.Controls 2.14
 import net.jami.Models 1.0
 
 import "../../commoncomponents"
 
-
 /*
- * Take advantage of child can access parent's item (ex: contextMenu, commonBorderWidth).
+ * Menu item wrapper for video device checkable item.
  */
 GeneralMenuItem {
     id: videoCallPageContextMenuDeviceItem
 
     property int contextMenuPreferredWidth: 250
 
-    leftBorderWidth: commonBorderWidth
-    rightBorderWidth: commonBorderWidth
-
     TextMetrics {
         id: textMetrics
         elide: Text.ElideMiddle
@@ -54,7 +50,6 @@ GeneralMenuItem {
 
     onClicked: {
         var deviceName = videoCallPageContextMenuDeviceItem.itemName
-        contextMenu.close()
         AvAdapter.onVideoContextMenuDeviceItemClicked(deviceName)
     }
 }
diff --git a/src/mainview/js/videodevicecontextmenuitemcreation.js b/src/mainview/js/videodevicecontextmenuitemcreation.js
index 02aa29253..ca17fc7cf 100644
--- a/src/mainview/js/videodevicecontextmenuitemcreation.js
+++ b/src/mainview/js/videodevicecontextmenuitemcreation.js
@@ -40,24 +40,28 @@ function setVideoContextMenuObject(obj) {
     videoContextMenuObject = obj
 }
 
-function createVideoDeviceContextMenuItemObjects(deviceName, setChecked, last) {
+function createVideoDeviceContextMenuItemObjects(deviceName, setChecked) {
 
     videoDeviceContextMenuItemComponent = Qt.createComponent(
                 "../components/VideoCallPageContextMenuDeviceItem.qml")
     if (videoDeviceContextMenuItemComponent.status === Component.Ready)
-        finishCreation(deviceName, setChecked, last)
+        finishCreation(deviceName, setChecked)
     else if (videoDeviceContextMenuItemComponent.status === Component.Error)
         console.log("Error loading component:",
                     videoDeviceContextMenuItemComponent.errorString())
 }
 
-function finishCreation(deviceName, setChecked, last) {
+function finishCreation(deviceName, setChecked) {
     videoDeviceContextMenuItemObject = videoDeviceContextMenuItemComponent.createObject()
     if (videoDeviceContextMenuItemObject === null) {
         // Error Handling.
         console.log("Error creating video context menu object")
     }
 
+    videoDeviceContextMenuItemObject.leftBorderWidth =
+            videoContextMenuObject.commonBorderWidth
+    videoDeviceContextMenuItemObject.rightBorderWidth =
+            videoContextMenuObject.commonBorderWidth
     videoDeviceContextMenuItemObject.itemName = deviceName
     videoDeviceContextMenuItemObject.checkable = true
     videoDeviceContextMenuItemObject.checked = setChecked
@@ -68,14 +72,11 @@ function finishCreation(deviceName, setChecked, last) {
      * Push into the storage array, and insert it into context menu.
      */
     itemArray.push(videoDeviceContextMenuItemObject)
-    videoContextMenuObject.insertItem(3 /* The button is at pos 3 in the menu */, videoDeviceContextMenuItemObject)
+    videoContextMenuObject.addItem(videoDeviceContextMenuItemObject)
 
-
-    /*
-     * If it is the last device context menu item, open the context menu.
-     */
-    if (last)
-        videoContextMenuObject.open()
+    videoDeviceContextMenuItemObject.clicked.connect(function () {
+        videoContextMenuObject.close()
+    })
 }
 
 function removeCreatedItems() {
diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp
index 5214d0bd4..37b477f9e 100644
--- a/src/messagesadapter.cpp
+++ b/src/messagesadapter.cpp
@@ -633,24 +633,24 @@ MessagesAdapter::contactIsComposing(const QString &uid, const QString &contactUr
 }
 
 void
-MessagesAdapter::acceptInvitation()
+MessagesAdapter::acceptInvitation(const QString &convUid)
 {
-    const auto convUid = LRCInstance::getCurrentConvUid();
-    LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
+    const auto currentConvUid = convUid.isEmpty() ? LRCInstance::getCurrentConvUid() : convUid;
+    LRCInstance::getCurrentConversationModel()->makePermanent(currentConvUid);
 }
 
 void
-MessagesAdapter::refuseInvitation()
+MessagesAdapter::refuseInvitation(const QString &convUid)
 {
-    auto convUid = LRCInstance::getCurrentConvUid();
-    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
+    const auto currentConvUid = convUid.isEmpty() ? LRCInstance::getCurrentConvUid() : convUid;
+    LRCInstance::getCurrentConversationModel()->removeConversation(currentConvUid, false);
     setInvitation(false);
 }
 
 void
-MessagesAdapter::blockConversation()
+MessagesAdapter::blockConversation(const QString &convUid)
 {
-    auto convUid = LRCInstance::getCurrentConvUid();
-    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
+    const auto currentConvUid = convUid.isEmpty() ? LRCInstance::getCurrentConvUid() : convUid;
+    LRCInstance::getCurrentConversationModel()->removeConversation(currentConvUid, true);
     setInvitation(false);
 }
diff --git a/src/messagesadapter.h b/src/messagesadapter.h
index 6b520d2bd..aaf11da72 100644
--- a/src/messagesadapter.h
+++ b/src/messagesadapter.h
@@ -41,9 +41,9 @@ public:
     /*
      * JS Q_INVOKABLE.
      */
-    Q_INVOKABLE void acceptInvitation();
-    Q_INVOKABLE void refuseInvitation();
-    Q_INVOKABLE void blockConversation();
+    Q_INVOKABLE void acceptInvitation(const QString &convUid = "");
+    Q_INVOKABLE void refuseInvitation(const QString &convUid = "");
+    Q_INVOKABLE void blockConversation(const QString &convUid = "");
     Q_INVOKABLE void setNewMessagesContent(const QString &path);
     Q_INVOKABLE void sendMessage(const QString &message);
     Q_INVOKABLE void sendImage(const QString &message);
diff --git a/src/settingsview/components/BannedItemDelegate.qml b/src/settingsview/components/BannedItemDelegate.qml
index 0322bb01f..999b49446 100644
--- a/src/settingsview/components/BannedItemDelegate.qml
+++ b/src/settingsview/components/BannedItemDelegate.qml
@@ -173,7 +173,7 @@ ItemDelegate {
             buttonImageHeight: height - 8
             buttonImageWidth: width - 8
 
-            source: "qrc:/images/icons/ic_person_add_black_24dp_2x.png"
+            source: "qrc:/images/icons/person_add-24px.svg"
 
             radius: height / 2
             width: 25
-- 
GitLab