diff --git a/jami-qt.pro b/jami-qt.pro index 57df532278f94b69c837ccc27824a8eba4f3cea2..d57fd245fd805bb48af3471e60ada85e6268dd77 100644 --- a/jami-qt.pro +++ b/jami-qt.pro @@ -138,6 +138,7 @@ unix { # Input HEADERS += \ src/avatarimageprovider.h \ + src/moderatorlistmodel.h \ src/networkmanager.h \ src/smartlistmodel.h \ src/updatemanager.h \ @@ -187,6 +188,7 @@ HEADERS += \ SOURCES += \ src/bannedlistmodel.cpp \ src/accountlistmodel.cpp \ + src/moderatorlistmodel.cpp \ src/networkmanager.cpp \ src/runguard.cpp \ src/updatemanager.cpp \ diff --git a/qml.qrc b/qml.qrc index b8e16a65cfeaf2fdc0ac77cdd51bfbcb34d53b70..0181fbc43a2cf8e1816b4d3444901bc74bf63706 100644 --- a/qml.qrc +++ b/qml.qrc @@ -47,7 +47,7 @@ <file>src/settingsview/components/PluginItemDelegate.qml</file> <file>src/mainview/components/MediaHandlerItemDelegate.qml</file> <file>src/commoncomponents/PreferenceItemDelegate.qml</file> - <file>src/settingsview/components/BannedItemDelegate.qml</file> + <file>src/settingsview/components/ContactItemDelegate.qml</file> <file>src/settingsview/components/MediaCodecDelegate.qml</file> <file>src/settingsview/components/NameRegistrationDialog.qml</file> <file>src/settingsview/components/LinkDeviceDialog.qml</file> @@ -92,7 +92,6 @@ <file>src/mainview/js/incomingcallpagecreation.js</file> <file>src/mainview/components/ContactSearchBar.qml</file> <file>src/mainview/components/VideoCallPage.qml</file> - <file>src/mainview/components/CallAdvancedOptions.qml</file> <file>src/mainview/components/ParticipantOverlay.qml</file> <file>src/mainview/components/ProjectCreditsScrollView.qml</file> <file>src/mainview/components/AccountComboBoxPopup.qml</file> diff --git a/src/accountadapter.cpp b/src/accountadapter.cpp index 53918936be747d9ab7c63c59c1b7730d6a994058..fa3d3b52fdbfa80b39fe8ce03ba3c49bdefadcd4 100644 --- a/src/accountadapter.cpp +++ b/src/accountadapter.cpp @@ -356,18 +356,9 @@ AccountAdapter::connectAccount(const QString& accountId) QObject::disconnect(accountStatusChangedConnection_); QObject::disconnect(contactAddedConnection_); - QObject::disconnect(accountProfileChangedConnection_); QObject::disconnect(addedToConferenceConnection_); QObject::disconnect(contactUnbannedConnection_); - accountProfileChangedConnection_ - = QObject::connect(&LRCInstance::accountModel(), - &lrc::api::NewAccountModel::profileUpdated, - [this](const QString& accountId) { - if (LRCInstance::getCurrAccId() == accountId) - emit accountStatusChanged(accountId); - }); - accountStatusChangedConnection_ = QObject::connect(accInfo.accountModel, &lrc::api::NewAccountModel::accountStatusChanged, diff --git a/src/accountadapter.h b/src/accountadapter.h index a6e55df427e1c0dc000505896a67ca3c78b50154..ac950a34d355790ea11e2cc7604d43cc6984218f 100644 --- a/src/accountadapter.h +++ b/src/accountadapter.h @@ -108,6 +108,7 @@ signals: * Trigger other components to reconnect account related signals. */ void accountStatusChanged(QString accountId = {}); + void updateConversationForAddedContact(); /* * send report failure to QML to make it show the right UI state . @@ -144,7 +145,6 @@ private: QMetaObject::Connection accountStatusChangedConnection_; QMetaObject::Connection contactAddedConnection_; QMetaObject::Connection addedToConferenceConnection_; - QMetaObject::Connection accountProfileChangedConnection_; QMetaObject::Connection contactUnbannedConnection_; QMetaObject::Connection registeredNameSavedConnection_; }; diff --git a/src/audiooutputdevicemodel.h b/src/audiooutputdevicemodel.h index 376e3636acea40ec0f00fbfa2f9528c46109242a..a615869d4cbb63ac51f63cb02cfb03e03c7fd253 100644 --- a/src/audiooutputdevicemodel.h +++ b/src/audiooutputdevicemodel.h @@ -30,7 +30,6 @@ class AudioOutputDeviceModel : public QAbstractListModel { Q_OBJECT -public: public: enum Role { Device_ID = Qt::UserRole + 1, ID_UTF8 }; Q_ENUM(Role) diff --git a/src/bannedlistmodel.h b/src/bannedlistmodel.h index 28a2951e417f7a94b9a717175dedf6a519e7f52c..a44abc8838e674ce632f04557eabf5bea073b352 100644 --- a/src/bannedlistmodel.h +++ b/src/bannedlistmodel.h @@ -24,8 +24,6 @@ class BannedListModel : public QAbstractListModel { Q_OBJECT - BannedListModel(const BannedListModel& cpy); - public: enum Role { ContactName = Qt::UserRole + 1, ContactID }; Q_ENUM(Role) diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index dbb213c0d9c6793d2bcfdfad42cc58e8c67f8ec6..97553b0eea41a8b63cde41dfc5e7eb072389c5a1 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -413,6 +413,14 @@ Item { property string maximizeParticipant: qsTr("Maximize") property string minimizeParticipant: qsTr("Minimize") property string hangupParticipant: qsTr("Hangup") + property string localMuted: qsTr("local muted") + + // Settings moderation + property string conferenceModeration: qsTr("Conference moderation") + property string defaultModerators: qsTr("Default moderators") + property string enableLocalModerators: qsTr("Enable local moderators") + property string addDefaultModerator: qsTr("Add default moderator") + property string removeDefaultModerator: qsTr("Remove default moderator") // Daemon reconnection property string reconnectDaemon: qsTr("Trying to reconnect to the Jami daemon (dring)…") diff --git a/src/contactadapter.cpp b/src/contactadapter.cpp index 00d7055d0c1b1f3b7163329f4dcc02e829a89268..85c15ddc3f3ff9a2e7cde707622dc25e5f46a825 100644 --- a/src/contactadapter.cpp +++ b/src/contactadapter.cpp @@ -35,15 +35,31 @@ ContactAdapter::getContactSelectableModel(int type) * Called from qml every time contact picker refreshes. */ listModeltype_ = static_cast<SmartListModel::Type>(type); - smartListModel_.reset(new SmartListModel(this, listModeltype_)); + + + if (listModeltype_ == SmartListModel::Type::CONVERSATION) { + defaultModerators_ = + LRCInstance::accountModel().getDefaultModerators(LRCInstance::getCurrAccId()); + smartListModel_.reset(new SmartListModel(this, listModeltype_)); + smartListModel_->fillConversationsList(); + } else { + smartListModel_.reset(new SmartListModel(this, listModeltype_)); + } selectableProxyModel_->setSourceModel(smartListModel_.get()); /* * Adjust filter. */ switch (listModeltype_) { + case SmartListModel::Type::CONVERSATION: + selectableProxyModel_->setPredicate([this] + (const QModelIndex& index, const QRegExp&) { + return !defaultModerators_.contains(index.data(SmartListModel::URI).toString()); + }); + break; + case SmartListModel::Type::CONFERENCE: - selectableProxyModel_->setPredicate([this](const QModelIndex& index, const QRegExp&) { + selectableProxyModel_->setPredicate([](const QModelIndex& index, const QRegExp&) { return index.data(SmartListModel::Presence).toBool(); }); break; @@ -77,9 +93,16 @@ ContactAdapter::setSearchFilter(const QString& filter) { if (listModeltype_ == SmartListModel::Type::CONFERENCE) { smartListModel_->setConferenceableFilter(filter); + } else if (listModeltype_ == SmartListModel::Type::CONVERSATION) { + selectableProxyModel_->setPredicate([this, filter]( + const QModelIndex& index, + const QRegExp&) { + return (!defaultModerators_.contains(index.data(SmartListModel::URI).toString()) + && index.data(SmartListModel::DisplayName).toString().contains(filter)); + }); } selectableProxyModel_->setFilterRegExp( - QRegExp(filter, Qt::CaseInsensitive, QRegExp::FixedString)); + QRegExp(filter, Qt::CaseInsensitive, QRegExp::FixedString)); } void @@ -157,6 +180,17 @@ ContactAdapter::contactSelected(int index) callModel->hangUp(destCallId); } } break; + case SmartListModel::Type::CONVERSATION: { + const auto contactUri = contactIndex.data(SmartListModel::Role::URI).value<QString>(); + if (contactUri.isEmpty()) { + return; + } + + LRCInstance::accountModel().setDefaultModerator( + LRCInstance::getCurrAccId(), contactUri, true); + emit defaultModeratorsUpdated(); + + } break; default: break; } diff --git a/src/contactadapter.h b/src/contactadapter.h index 09e67a8ce8ee14efd49a0e2ee2bf39f7a81d7085..2ece5e916a91d0a75a1ae733dfb6e50e2c244d09 100644 --- a/src/contactadapter.h +++ b/src/contactadapter.h @@ -95,4 +95,10 @@ private: */ std::unique_ptr<SmartListModel> smartListModel_; std::unique_ptr<SelectableProxyModel> selectableProxyModel_; + + QStringList defaultModerators_; + +signals: + void defaultModeratorsUpdated(); + }; diff --git a/src/mainview/components/AboutPopUp.qml b/src/mainview/components/AboutPopUp.qml index 94c96de7e2d9fbf91bbee88c201b3677c3864cdc..8b684d6b664bd1749bf938d0f97ed20a94a4ed52 100644 --- a/src/mainview/components/AboutPopUp.qml +++ b/src/mainview/components/AboutPopUp.qml @@ -19,7 +19,7 @@ import QtQuick 2.14 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 -import net.jami.Models 1.0 //Albert +import net.jami.Models 1.0 import net.jami.Adapters 1.0 import net.jami.Constants 1.0 diff --git a/src/mainview/components/CallAdvancedOptions.qml b/src/mainview/components/CallAdvancedOptions.qml deleted file mode 100644 index d67f03616428eb9ab960ec423e75ae972bcd7281..0000000000000000000000000000000000000000 --- a/src/mainview/components/CallAdvancedOptions.qml +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 - * 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 QtQuick.Layouts 1.14 -import QtQuick.Controls.Universal 2.14 -import net.jami.Models 1.0 -import net.jami.Constants 1.0 - -import "../../commoncomponents" - -Popup { - id: contactPickerPopup - - property int type: ContactPicker.ContactPickerType.JAMICONFERENCE - - - // Important to keep it one, since enum in c++ starts at one for conferences. - enum ContactPickerType { - JAMICONFERENCE = 1, - SIPTRANSFER - } - - contentWidth: 250 - contentHeight: contactPickerPopupRectColumnLayout.height + 50 - - padding: 0 - - modal: true - - contentItem: Rectangle { - id: contactPickerPopupRect - - width: 250 - - PushButton { - id: closeButton - - anchors.top: contactPickerPopupRect.top - anchors.topMargin: 5 - anchors.right: contactPickerPopupRect.right - anchors.rightMargin: 5 - - source: "qrc:/images/icons/ic_close_black_24dp.png" - - onClicked: { - contactPickerPopup.close() - } - } - - ColumnLayout { - id: contactPickerPopupRectColumnLayout - - anchors.top: contactPickerPopupRect.top - anchors.topMargin: 15 - - Text { - id: contactPickerTitle - - Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: contactPickerPopupRect.width - Layout.preferredHeight: 30 - - font.pointSize: JamiTheme.textFontSize - font.bold: true - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - text: type === ContactPicker.ContactPickerType.JAMICONFERENCE ? qsTr("Add to conference") : qsTr("Transfer this call") - } - - ContactSearchBar { - id: contactPickerContactSearchBar - - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 5 - Layout.bottomMargin: 5 - Layout.preferredWidth: contactPickerPopupRect.width - 10 - Layout.preferredHeight: 35 - - onContactSearchBarTextChanged: { - ContactAdapter.setSearchFilter(text) - } - - Component.onCompleted: { - contactPickerContactSearchBar.setPlaceholderString( - qsTr("Search contacts")) - } - } - - ListView { - id: contactPickerListView - - Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: contactPickerPopupRect.width - Layout.preferredHeight: 200 - - model: ContactAdapter.getContactSelectableModel(type) - - clip: true - - delegate: ContactPickerItemDelegate { - id: contactPickerItemDelegate - } - - ScrollIndicator.vertical: ScrollIndicator {} - } - } - - radius: 10 - color: "white" - } - - onAboutToShow: { - // Reset the model on each show. - contactPickerListView.model = ContactAdapter.getContactSelectableModel( - type) - } - - background: Rectangle { - color: "transparent" - } -} diff --git a/src/mainview/components/CallOverlay.qml b/src/mainview/components/CallOverlay.qml index 84dc5bd9f24b34721aa21777770f9c774b5ceccf..d5fe292e7e0dd10576dfd7a2f8455d7923d1bcd1 100644 --- a/src/mainview/components/CallOverlay.qml +++ b/src/mainview/components/CallOverlay.qml @@ -440,8 +440,6 @@ Rectangle { ContactPickerCreation.createContactPickerObjects( ContactPicker.ContactPickerType.JAMICONFERENCE, callOverlayRect) - ContactPickerCreation.calculateCurrentGeo( - callOverlayRect.width / 2, callOverlayRect.height / 2) ContactPickerCreation.openContactPicker() } @@ -551,20 +549,6 @@ Rectangle { color: "transparent" - onWidthChanged: { - ContactPickerCreation.calculateCurrentGeo(callOverlayRect.width / 2, - callOverlayRect.height / 2) - MediaHandlerPickerCreation.calculateCurrentGeo(callOverlayRect.width / 2, - callOverlayRect.height / 2) - } - - onHeightChanged: { - ContactPickerCreation.calculateCurrentGeo(callOverlayRect.width / 2, - callOverlayRect.height / 2) - MediaHandlerPickerCreation.calculateCurrentGeo(callOverlayRect.width / 2, - callOverlayRect.height / 2) - } - CallViewContextMenu { id: callViewContextMenu @@ -573,16 +557,12 @@ Rectangle { ContactPickerCreation.createContactPickerObjects( ContactPicker.ContactPickerType.SIPTRANSFER, callOverlayRect) - ContactPickerCreation.calculateCurrentGeo( - callOverlayRect.width / 2, callOverlayRect.height / 2) ContactPickerCreation.openContactPicker() } onPluginItemClicked: { // Create media handler picker - PLUGINS MediaHandlerPickerCreation.createMediaHandlerPickerObjects(callOverlayRect) - MediaHandlerPickerCreation.calculateCurrentGeo( - callOverlayRect.width / 2, callOverlayRect.height / 2) MediaHandlerPickerCreation.openMediaHandlerPicker() } } diff --git a/src/mainview/components/ContactPicker.qml b/src/mainview/components/ContactPicker.qml index 9d767d07993178e08603fe990bd061668040d04c..3f9f2ce85332497f2fb86452810f2a08006c74d8 100644 --- a/src/mainview/components/ContactPicker.qml +++ b/src/mainview/components/ContactPicker.qml @@ -33,7 +33,8 @@ Popup { // Important to keep it one, since enum in c++ starts at one for conferences. enum ContactPickerType { - JAMICONFERENCE = 1, + CONVERSATION = 0, + JAMICONFERENCE, SIPTRANSFER } @@ -84,7 +85,16 @@ Popup { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - text: type === ContactPicker.ContactPickerType.JAMICONFERENCE ? qsTr("Add to conference") : qsTr("Transfer this call") + text: { + switch(type) { + case ContactPicker.ContactPickerType.JAMICONFERENCE: + return qsTr("Add to conference") + case ContactPicker.ContactPickerType.SIPTRANSFER: + return qsTr("Transfer this call") + default: + return qsTr("Add default moderator") + } + } } ContactSearchBar { @@ -127,9 +137,8 @@ Popup { } onAboutToShow: { - // Reset the model on each show. - contactPickerListView.model = ContactAdapter.getContactSelectableModel( - type) + contactPickerListView.model = + ContactAdapter.getContactSelectableModel(type) } background: Rectangle { diff --git a/src/mainview/components/ParticipantOverlay.qml b/src/mainview/components/ParticipantOverlay.qml index 04e987c49a934b354c5ca3db43b683a80a46f3f3..f801d89d71c9162bc51f737b3e78376310073c85 100644 --- a/src/mainview/components/ParticipantOverlay.qml +++ b/src/mainview/components/ParticipantOverlay.qml @@ -70,12 +70,12 @@ Rectangle { overlayMenu.showUnsetModerator = isHost && !isLocal && participantIsModerator var muteState = CallAdapter.getMuteState(overlayMenu.uri) - var isLocalMuted = muteState === CallAdapter.LOCAL_MUTED + overlayMenu.isLocalMuted = muteState === CallAdapter.LOCAL_MUTED || muteState === CallAdapter.BOTH_MUTED var isModeratorMuted = muteState === CallAdapter.MODERATOR_MUTED || muteState === CallAdapter.BOTH_MUTED - participantIsMuted = isLocalMuted || isModeratorMuted + participantIsMuted = overlayMenu.isLocalMuted || isModeratorMuted overlayMenu.showModeratorMute = isModerator && !isModeratorMuted overlayMenu.showModeratorUnmute = isModerator && isModeratorMuted diff --git a/src/mainview/components/ParticipantOverlayMenu.qml b/src/mainview/components/ParticipantOverlayMenu.qml index 55818ff5f8114603d6a1e26adc137073bf64533d..270994096838ede98e6eca7d768fe9c1977b64fa 100644 --- a/src/mainview/components/ParticipantOverlayMenu.qml +++ b/src/mainview/components/ParticipantOverlayMenu.qml @@ -41,6 +41,7 @@ Rectangle { property string uri: "" property string bestName: "" + property bool isLocalMuted: false property bool showSetModerator: false property bool showUnsetModerator: false property bool showModeratorMute: false @@ -150,7 +151,10 @@ Rectangle { : JamiTheme.whiteColor onClicked: CallAdapter.muteParticipant(uri, showModeratorMute) - onHoveredChanged: toggleParticipantToolTip.visible = hovered + onHoveredChanged: { + toggleParticipantToolTip.visible = hovered + localMutedText.visible = hovered && isLocalMuted + } Text { id: toggleParticipantToolTip @@ -167,6 +171,22 @@ Rectangle { color: JamiTheme.whiteColor font.pointSize: JamiTheme.tinyFontSize } + + Text { + id: localMutedText + + visible: false + width: parent.width + text: "(" + JamiStrings.localMuted + ")" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + + anchors.top: parent.bottom + anchors.topMargin: 16 + color: JamiTheme.whiteColor + font.pointSize: JamiTheme.tinyFontSize + } + } PushButton { diff --git a/src/mainview/js/contactpickercreation.js b/src/mainview/js/contactpickercreation.js index 7ce7b62e2cb535fd3e8707543f8390564ecbf6df..6cbf70b66a021f6e85580622ea5317c07d3afaae 100644 --- a/src/mainview/js/contactpickercreation.js +++ b/src/mainview/js/contactpickercreation.js @@ -1,4 +1,3 @@ - /* * Copyright (C) 2020 by Savoir-faire Linux * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> @@ -27,7 +26,6 @@ var contactPickerObject function createContactPickerObjects(type, parent) { if (contactPickerObject) { - /* * If already created, reset parameters, since object cannot be destroyed. */ @@ -49,23 +47,15 @@ function finishCreation(type, parent) { "type": type }) if (contactPickerObject === null) { - - /* * Error Handling. */ console.log("Error creating object for contact picker") - } -} - - -/* - * Put contact picker in the middle of container. - */ -function calculateCurrentGeo(containerX, containerY) { - if (contactPickerObject) { - contactPickerObject.x = containerX - contactPickerObject.width / 2 - contactPickerObject.y = containerY - contactPickerObject.height / 2 + } else { + contactPickerObject.x = Qt.binding(function(){ + return parent.width/2 - contactPickerObject.width / 2}) + contactPickerObject.y = Qt.binding(function(){ + return parent.height/2 - contactPickerObject.height / 2}) } } diff --git a/src/mainview/js/mediahandlerpickercreation.js b/src/mainview/js/mediahandlerpickercreation.js index 1a14eca22776448d48e9b7aff07a873281b70e79..b35346c0b0b13b0e2b39505e31bf7993b031f463 100644 --- a/src/mainview/js/mediahandlerpickercreation.js +++ b/src/mainview/js/mediahandlerpickercreation.js @@ -47,17 +47,11 @@ function finishCreation(parent) { * Error Handling. */ console.log("Error creating object for mediahandler picker") - } -} - - -/* - * Put mediahandler picker in the middle of container. - */ -function calculateCurrentGeo(containerX, containerY) { - if (mediahandlerPickerObject) { - mediahandlerPickerObject.x = containerX - mediahandlerPickerObject.width / 2 - mediahandlerPickerObject.y = containerY - mediahandlerPickerObject.height / 2 + } else { + mediahandlerPickerObject.x = Qt.binding(function(){ + return parent.width/2 - mediahandlerPickerObject.width / 2}) + mediahandlerPickerObject.y = Qt.binding(function(){ + return parent.height/2 - mediahandlerPickerObject.height / 2}) } } diff --git a/src/moderatorlistmodel.cpp b/src/moderatorlistmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..41d135476d1ba1ef9c88b5a40ca0fed032f699bb --- /dev/null +++ b/src/moderatorlistmodel.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019-2020 by Savoir-faire Linux + * Author: Albert Babà Oller <albert.babi@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 <http://www.gnu.org/licenses/>. + */ + +#include "moderatorlistmodel.h" +#include "lrcinstance.h" + +ModeratorListModel::ModeratorListModel(QObject* parent) + : QAbstractListModel(parent) +{} + +ModeratorListModel::~ModeratorListModel() {} + +int +ModeratorListModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return LRCInstance::accountModel().getDefaultModerators( + LRCInstance::getCurrentAccountInfo().id).size(); + } + return 0; +} + +int +ModeratorListModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + /* + * Only need one column. + */ + return 1; +} + +QVariant +ModeratorListModel::data(const QModelIndex& index, int role) const +{ + QStringList list = LRCInstance::accountModel().getDefaultModerators( + LRCInstance::getCurrAccId()); + if (!index.isValid() || list.size() <= index.row()) { + return QVariant(); + } + auto contactInfo = LRCInstance::getCurrentAccountInfo().contactModel->getContact( + list.at(index.row())); + + switch (role) { + case Role::ContactName: { + QString str = LRCInstance::getCurrentAccountInfo().contactModel-> + bestNameForContact(list.at(index.row())); + return QVariant(str); + } + case Role::ContactID: + return QVariant(contactInfo.profileInfo.uri); + } + return QVariant(); +} + +QHash<int, QByteArray> +ModeratorListModel::roleNames() const +{ + QHash<int, QByteArray> roles; + roles[ContactName] = "ContactName"; + roles[ContactID] = "ContactID"; + return roles; +} + +QModelIndex +ModeratorListModel::index(int row, int column, const QModelIndex& parent) const +{ + Q_UNUSED(parent); + if (column != 0) { + return QModelIndex(); + } + + if (row >= 0 && row < rowCount()) { + return createIndex(row, column); + } + return QModelIndex(); +} + +QModelIndex +ModeratorListModel::parent(const QModelIndex& child) const +{ + Q_UNUSED(child); + return QModelIndex(); +} + +Qt::ItemFlags +ModeratorListModel::flags(const QModelIndex& index) const +{ + auto flags = QAbstractItemModel::flags(index) | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable; + if (!index.isValid()) { + return QAbstractItemModel::flags(index); + } + return flags; +} + +void +ModeratorListModel::reset() +{ + beginResetModel(); + endResetModel(); +} diff --git a/src/moderatorlistmodel.h b/src/moderatorlistmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..060f376c799301440f07bd61b3298c2d3ceba8aa --- /dev/null +++ b/src/moderatorlistmodel.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019-2020 by Savoir-faire Linux + * Author: Albert Babà Oller <albert.babi@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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QAbstractListModel> + +class ModeratorListModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Role { ContactName = Qt::UserRole + 1, ContactID }; + Q_ENUM(Role) + + explicit ModeratorListModel(QObject* parent = nullptr); + ~ModeratorListModel(); + + /* + * QAbstractListModel override. + */ + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + /* + * Override role name as access point in qml. + */ + QHash<int, QByteArray> roleNames() const override; + QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& child) const; + Qt::ItemFlags flags(const QModelIndex& index) const; + + /* + * This function is to reset the model when there's new account added. + */ + Q_INVOKABLE void reset(); +}; diff --git a/src/qmlregister.cpp b/src/qmlregister.cpp index e1bea27c4bce762c8ab03dd14e091838b7d41176..73ad895dc921b3edbf249e3f591b2cfbb57e0ad5 100644 --- a/src/qmlregister.cpp +++ b/src/qmlregister.cpp @@ -26,6 +26,7 @@ #include "audiooutputdevicemodel.h" #include "avadapter.h" #include "bannedlistmodel.h" +#include "moderatorlistmodel.h" #include "calladapter.h" #include "contactadapter.h" #include "conversationsadapter.h" @@ -103,6 +104,7 @@ registerTypes() QML_REGISTERTYPE("net.jami.Models", MediaHandlerItemListModel, 1, 0); QML_REGISTERTYPE("net.jami.Models", PreferenceItemListModel, 1, 0); QML_REGISTERTYPE("net.jami.Models", BannedListModel, 1, 0); + QML_REGISTERTYPE("net.jami.Models", ModeratorListModel, 1, 0); QML_REGISTERTYPE("net.jami.Models", MediaCodecListModel, 1, 0); QML_REGISTERTYPE("net.jami.Models", AccountsToMigrateListModel, 1, 0); QML_REGISTERTYPE("net.jami.Models", AudioInputDeviceModel, 1, 0); diff --git a/src/settingsadapter.cpp b/src/settingsadapter.cpp index 8dbc9adc723de8aba4f7a636bfa975898b5b4692..b444653a9f95ea7270c02d745f6aff928ac65a71 100644 --- a/src/settingsadapter.cpp +++ b/src/settingsadapter.cpp @@ -1016,3 +1016,30 @@ SettingsAdapter::set_FilePrivateKey(QString text) confProps.TLS.privateKeyFile = text; LRCInstance::accountModel().setAccountConfig(LRCInstance::getCurrAccId(), confProps); } + +void +SettingsAdapter::setDefaultModerator(const QString& accountId, + const QString& peerURI, + const bool& state) +{ + return LRCInstance::accountModel().setDefaultModerator(accountId, peerURI, state); +} + +QStringList +SettingsAdapter::getDefaultModerators(const QString& accountId) +{ + return LRCInstance::accountModel().getDefaultModerators(accountId); +} + +void +SettingsAdapter::enableLocalModerators(const QString& accountId, + const bool& isModEnabled) +{ + return LRCInstance::accountModel().enableLocalModerators(accountId, isModEnabled); +} + +bool +SettingsAdapter::isLocalModeratorsEnabled(const QString& accountId) +{ + return LRCInstance::accountModel().isLocalModeratorsEnabled(accountId); +} diff --git a/src/settingsadapter.h b/src/settingsadapter.h index 53e1f1569b5419905edbd3ce1cc05ae6d7042192..cabf798c508eeb7a3017d4ae7cf2a985b8aabdaa 100644 --- a/src/settingsadapter.h +++ b/src/settingsadapter.h @@ -227,5 +227,14 @@ public: Q_INVOKABLE void set_FileCACert(QString text); Q_INVOKABLE void set_FileUserCert(QString text); Q_INVOKABLE void set_FilePrivateKey(QString text); + + Q_INVOKABLE void setDefaultModerator(const QString& accountID, + const QString& peerURI, + const bool& state); + Q_INVOKABLE QStringList getDefaultModerators(const QString& accId); + Q_INVOKABLE void enableLocalModerators(const QString& accountID, + const bool& isModEnabled); + Q_INVOKABLE bool isLocalModeratorsEnabled(const QString& accountId); + }; Q_DECLARE_METATYPE(SettingsAdapter*) diff --git a/src/settingsview/SettingsView.qml b/src/settingsview/SettingsView.qml index 3369d5a4ac12d299822110f6b10578b190596f85..87af87a07c7ce2723cfdc5e3a602123e4d5003b0 100644 --- a/src/settingsview/SettingsView.qml +++ b/src/settingsview/SettingsView.qml @@ -27,6 +27,8 @@ import net.jami.Adapters 1.0 import net.jami.Constants 1.0 import "components" +import "../mainview/js/contactpickercreation.js" as ContactPickerCreation + Rectangle { id: root diff --git a/src/settingsview/components/AdvancedCallSettings.qml b/src/settingsview/components/AdvancedCallSettings.qml index d396227c8434c2df8120771cd18ee954e353fcae..e389581b6c493efa19e1d0364c6a0c61aad6fe2c 100644 --- a/src/settingsview/components/AdvancedCallSettings.qml +++ b/src/settingsview/components/AdvancedCallSettings.qml @@ -1,6 +1,7 @@ /* * Copyright (C) 2020 by Savoir-faire Linux * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Albert Babà Oller <albert.babi@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 @@ -28,6 +29,8 @@ import net.jami.Adapters 1.0 import net.jami.Constants 1.0 import "../../commoncomponents" +import "../../mainview/components" +import "../../mainview/js/contactpickercreation.js" as ContactPickerCreation ColumnLayout { id: root @@ -43,6 +46,7 @@ ColumnLayout { btnRingtone.setEnabled(SettingsAdapter.getAccountConfig_Ringtone_RingtoneEnabled()) btnRingtone.setText(UtilsAdapter.toFileInfoName(SettingsAdapter.getAccountConfig_Ringtone_RingtonePath())) + updateAndShowModeratorsSlot() } function changeRingtonePath(url) { @@ -54,6 +58,21 @@ ColumnLayout { } } + function updateAndShowModeratorsSlot() { + toggleLocalModerators.checked = SettingsAdapter.isLocalModeratorsEnabled( + AccountAdapter.currentAccountId) + moderatorListWidget.model.reset() + moderatorListWidget.visible = (moderatorListWidget.model.rowCount() > 0) + } + + Connections { + target: ContactAdapter + + function onDefaultModeratorsUpdated() { + updateAndShowModeratorsSlot() + } + } + JamiFileDialog { id: ringtonePath_Dialog @@ -142,5 +161,78 @@ ColumnLayout { SettingsAdapter.setIsRendezVous(checked) } } + + ToggleSwitch { + id: toggleLocalModerators + + labelText: JamiStrings.enableLocalModerators + fontPointSize: JamiTheme.settingsFontSize + + onSwitchToggled: SettingsAdapter.enableLocalModerators( + AccountAdapter.currentAccountId, checked) + } + + ElidedTextLabel { + Layout.fillWidth: true + + eText: JamiStrings.defaultModerators + fontSize: JamiTheme.settingsFontSize + maxWidth: root.width - JamiTheme.preferredFieldHeight + - JamiTheme.preferredMarginSize * 4 + } + + ListViewJami { + id: moderatorListWidget + + Layout.fillWidth: true + Layout.preferredHeight: 160 + + model: ModeratorListModel {} + + delegate: ContactItemDelegate { + id: moderatorListDelegate + + width: moderatorListWidget.width + height: 74 + + contactName: ContactName + contactID: ContactID + + btnImgSource: "qrc:/images/icons/round-remove_circle-24px.svg" + btnToolTip: JamiStrings.removeDefaultModerator + + onClicked: moderatorListWidget.currentIndex = index + onBtnContactClicked: { + SettingsAdapter.setDefaultModerator( + AccountAdapter.currentAccountId, contactID, false) + updateAndShowModeratorsSlot() + } + } + } + + MaterialButton { + id: addDefaultModeratorPushButton + + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: JamiTheme.preferredFieldWidth + Layout.preferredHeight: JamiTheme.preferredFieldHeight + + color: JamiTheme.buttonTintedBlack + hoveredColor: JamiTheme.buttonTintedBlackHovered + pressedColor: JamiTheme.buttonTintedBlackPressed + outlined: true + toolTipText: JamiStrings.addDefaultModerator + + source: "qrc:/images/icons/round-add-24px.svg" + + text: JamiStrings.addDefaultModerator + + onClicked: { + ContactPickerCreation.createContactPickerObjects( + ContactPicker.ContactPickerType.CONVERSATION, + mainView) + ContactPickerCreation.openContactPicker() + } + } } } diff --git a/src/settingsview/components/BannedContacts.qml b/src/settingsview/components/BannedContacts.qml index 26fa8bb021d09649e0542ee65f5e4487bfcd28a5..2364f3dd8c66efaa10982ebf6d663c96624aba72 100644 --- a/src/settingsview/components/BannedContacts.qml +++ b/src/settingsview/components/BannedContacts.qml @@ -30,7 +30,7 @@ import net.jami.Constants 1.0 import "../../commoncomponents" ColumnLayout { - id:root + id: root property bool isSIP @@ -120,18 +120,20 @@ ColumnLayout { model: BannedListModel {} - delegate: BannedItemDelegate { + delegate: ContactItemDelegate { id: bannedListDelegate width: bannedListWidget.width height: 74 - contactName : ContactName + contactName: ContactName contactID: ContactID - onClicked: bannedListWidget.currentIndex = index + btnImgSource: "qrc:/images/icons/round-remove_circle-24px.svg" + btnToolTip: JamiStrings.reinstateContact - onBtnReAddContactClicked: unban(index) + onClicked: bannedListWidget.currentIndex = index + onBtnContactClicked: unban(index) } } } diff --git a/src/settingsview/components/BannedItemDelegate.qml b/src/settingsview/components/ContactItemDelegate.qml similarity index 89% rename from src/settingsview/components/BannedItemDelegate.qml rename to src/settingsview/components/ContactItemDelegate.qml index d83e88946cbbe57dbec99e88859d942ea1c2b931..029a764f6ec0ed2e777681d7ea3814ec30628324 100644 --- a/src/settingsview/components/BannedItemDelegate.qml +++ b/src/settingsview/components/ContactItemDelegate.qml @@ -1,6 +1,7 @@ /* * Copyright (C) 2019-2020 by Savoir-faire Linux - * Author: Yang Wang <yang.wang@savoirfairelinux.com> + * Author: Yang Wang <yang.wang@savoirfairelinux.com> + * Author: Albert Babà Oller <albert.babi@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 @@ -32,8 +33,10 @@ ItemDelegate { property string contactName : "" property string contactID: "" + property string btnImgSource: "" + property string btnToolTip: "" - signal btnReAddContactClicked + signal btnContactClicked highlighted: ListView.isCurrentItem background: Rectangle { @@ -74,7 +77,7 @@ ItemDelegate { radius: { var size = ((avatarImg.width <= avatarImg.height) ? avatarImg.width:avatarImg.height) - return size /2 + return size / 2 } } } @@ -82,12 +85,12 @@ ItemDelegate { } } - ColumnLayout{ + ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignVCenter - Label{ + Label { id: labelContactName Layout.fillWidth: true @@ -104,7 +107,7 @@ ItemDelegate { color: JamiTheme.textColor } - Label{ + Label { id: labelContactId Layout.fillWidth: true @@ -124,21 +127,21 @@ ItemDelegate { } } - PushButton{ - id: btnReAddContact + PushButton { + id: btnContact Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.rightMargin: 16 Layout.preferredWidth: JamiTheme.preferredFieldHeight Layout.preferredHeight: JamiTheme.preferredFieldHeight - source: "qrc:/images/icons/person_add-24px.svg" + source: btnImgSource imageColor: JamiTheme.textColor normalColor: highlighted? JamiTheme.selectedColor : JamiTheme.editBackgroundColor - toolTipText: JamiStrings.reinstateContact + toolTipText: btnToolTip - onClicked: btnReAddContactClicked() + onClicked: btnContactClicked() } } } diff --git a/src/settingsview/components/NameRegistrationDialog.qml b/src/settingsview/components/NameRegistrationDialog.qml index d6ca3e17b71bbcf8f6a886ae284f65eb049abb70..9c39a07510a2756bc72b70fea9d360d51c2ae927 100644 --- a/src/settingsview/components/NameRegistrationDialog.qml +++ b/src/settingsview/components/NameRegistrationDialog.qml @@ -1,7 +1,7 @@ /* * Copyright (C) 2020 by Savoir-faire Linux * Author: Yang Wang <yang.wang@savoirfairelinux.com> - * Author: Albert Babà <yang.wang@savoirfairelinux.com> + * Author: Albert Babà <albert.babi@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