diff --git a/CMakeLists.txt b/CMakeLists.txt index a91d2ed1b939cfc07fff50fb4a440169ede6d0ee..c7c6675448189312e529b1a4c05e574ed50df0d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,8 @@ set(COMMON_SOURCES ${APP_SRC_DIR}/messageparser.cpp ${APP_SRC_DIR}/previewengine.cpp ${APP_SRC_DIR}/imagedownloader.cpp - ${APP_SRC_DIR}/pluginversionmanager.cpp) + ${APP_SRC_DIR}/pluginversionmanager.cpp + ${APP_SRC_DIR}/connectioninfolistmodel.cpp) set(COMMON_HEADERS ${APP_SRC_DIR}/avatarimageprovider.h @@ -310,8 +311,8 @@ set(COMMON_HEADERS ${APP_SRC_DIR}/messageparser.h ${APP_SRC_DIR}/htmlparser.h ${APP_SRC_DIR}/imagedownloader.h - ${APP_SRC_DIR}/pluginversionmanager.h) - + ${APP_SRC_DIR}/pluginversionmanager.h + ${APP_SRC_DIR}/connectioninfolistmodel.h) # For libavutil/avframe. set(LIBJAMI_CONTRIB_DIR "${DAEMON_DIR}/contrib") diff --git a/resources/icons/Connected_Black_24dp.svg b/resources/icons/Connected_Black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..70325ffa54f31949e0a76b9be3fedc60cd8f9629 --- /dev/null +++ b/resources/icons/Connected_Black_24dp.svg @@ -0,0 +1,12 @@ +<?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 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<path id="noun-connection-5025318" d="M18,6.3c1.2,0,2.2-1,2.2-2.1C20.1,3,19.2,2,18,2c-1.2,0-2.2,1-2.2,2.1c0,0.5,0.2,1.1,0.6,1.5 + l-3.3,3.8c-0.8-0.6-2-0.5-2.7,0.2L7.3,7.1c0.5-0.8,0.2-1.9-0.6-2.4C5.9,4.2,4.8,4.5,4.3,5.3C3.8,6.1,4,7.2,4.9,7.7 + C5.6,8.1,6.4,8,7,7.5l3.1,2.5c-0.5,0.8-0.4,1.9,0.3,2.6l-2.9,2.9c-1.1-0.7-2.5-0.4-3.2,0.7c-0.7,1.1-0.4,2.5,0.7,3.2 + c1.1,0.7,2.5,0.4,3.2-0.7c0.6-0.9,0.5-2.1-0.3-2.9l2.9-2.9c0.3,0.2,0.7,0.3,1.2,0.3c0,0,0.1,0,0.1,0l0.8,4.8 + c-1.1,0.3-1.7,1.4-1.4,2.5c0.3,1.1,1.4,1.7,2.5,1.4c1.1-0.3,1.7-1.4,1.4-2.5c-0.3-0.9-1-1.4-1.9-1.4c0,0-0.1,0-0.1,0l-0.8-4.8 + c0.4-0.1,0.7-0.3,0.9-0.6l3.6,2.7c-0.4,0.8-0.2,1.8,0.6,2.2c0.8,0.4,1.8,0.2,2.2-0.6c0.4-0.8,0.2-1.8-0.6-2.2 + c-0.6-0.4-1.4-0.3-1.9,0.2l-3.6-2.7c0.2-0.3,0.3-0.7,0.3-1.1c0-0.5-0.2-1.1-0.6-1.5l3.3-3.8C17.1,6.2,17.5,6.3,18,6.3L18,6.3z"/> +</svg> diff --git a/resources/icons/Connecting_Black_24dp.svg b/resources/icons/Connecting_Black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..b35b6e7bc7225007ba29f5f2386bbe78f54c2208 --- /dev/null +++ b/resources/icons/Connecting_Black_24dp.svg @@ -0,0 +1,23 @@ +<?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 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<g id="noun-waiting-3611673" transform="translate(-13.64 -30.48)"> + <path id="Path_278" d="M25.9,34.5c0.5,0,0.8-0.4,0.8-0.8c0-0.5-0.4-0.8-0.8-0.8c-0.5,0-0.8,0.4-0.8,0.8c0,0.2,0.1,0.4,0.2,0.6 + C25.4,34.4,25.6,34.5,25.9,34.5z"/> + <path id="Path_279" d="M32.7,36.9c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0c-0.4,0.4-0.4,1.1,0,1.5c0.2,0.2,0.5,0.3,0.8,0.3 + C32.2,37.2,32.5,37.1,32.7,36.9z"/> + <path id="Path_280" d="M34.5,41c-0.7,0-1.2,0.5-1.2,1.2s0.5,1.2,1.2,1.2c0.7,0,1.2-0.5,1.2-1.2c0-0.3-0.1-0.6-0.3-0.8 + C35.1,41.1,34.8,41,34.5,41z"/> + <path id="Path_281" d="M31.9,47c-0.7,0-1.3,0.6-1.3,1.3c0,0.7,0.6,1.3,1.3,1.3c0.7,0,1.3-0.6,1.3-1.3c0-0.3-0.1-0.7-0.4-0.9 + C32.6,47.1,32.3,47,31.9,47z"/> + <path id="Path_282" d="M25.9,49.4c-0.8,0-1.4,0.6-1.4,1.4c0,0.8,0.6,1.4,1.4,1.4c0.8,0,1.4-0.6,1.4-1.4c0-0.4-0.1-0.7-0.4-1 + C26.6,49.5,26.2,49.4,25.9,49.4z"/> + <path id="Path_283" d="M18.7,47.2L18.7,47.2c-0.6,0.6-0.6,1.6,0,2.2c0.6,0.6,1.6,0.6,2.2,0c0.6-0.6,0.6-1.6,0-2.2 + c-0.3-0.3-0.7-0.5-1.1-0.5C19.4,46.7,19,46.9,18.7,47.2z"/> + <path id="Path_284" d="M18.9,42.2c0-0.9-0.7-1.7-1.6-1.7s-1.7,0.7-1.7,1.6c0,0.9,0.7,1.7,1.6,1.7c0.4,0,0.9-0.2,1.2-0.5 + C18.8,43,18.9,42.6,18.9,42.2z"/> + <path id="Path_285" d="M21.1,34.9c-0.7-0.3-1.5-0.1-1.9,0.6c-0.3,0.7-0.1,1.5,0.6,1.9c0.7,0.3,1.5,0.1,1.9-0.6 + c0.1-0.2,0.2-0.5,0.1-0.7C21.8,35.5,21.5,35.1,21.1,34.9z"/> +</g> +</svg> diff --git a/src/app/connectioninfolistmodel.cpp b/src/app/connectioninfolistmodel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99f1a808b5aecb150aa80247d2abe1746ff2c1bd --- /dev/null +++ b/src/app/connectioninfolistmodel.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "connectioninfolistmodel.h" + +ConnectionInfoListModel::ConnectionInfoListModel(LRCInstance* instance, QObject* parent) + : AbstractListModelBase(parent) +{ + lrcInstance_ = instance; + connect(lrcInstance_, + &LRCInstance::currentAccountIdChanged, + this, + &ConnectionInfoListModel::resetData); +} + +int +ConnectionInfoListModel::rowCount(const QModelIndex& parent) const +{ + return peerIds_.size(); +} + +QVariant +ConnectionInfoListModel::data(const QModelIndex& index, int role) const +{ + const auto accountId = lrcInstance_->get_currentAccountId(); + + if (accountId.isEmpty()) { + qWarning() << "ConnectionInfoListModel::data: accountId or peerID is empty"; + return {}; + } + const auto peerId = peerIds_[index.row()]; + const auto peerData = peerData_[peerId]; + + switch (role) { + case ConnectionInfoList::ChannelsMap: { + QVariantMap channelsMapMap; + int i = 0; + for (const auto& device : peerData.keys()) { + QString channelsId = peerData[device]["id"].toString(); + QVariantMap channelsMap; + const auto channelInfoList = lrcInstance_->getChannelList(accountId, channelsId); + for (const auto& channelInfo : channelInfoList) { + channelsMap.insert(channelInfo["id"], channelInfo["name"]); + } + channelsMapMap.insert(QString::number(i++), channelsMap); + } + return QVariant(channelsMapMap); + } + case ConnectionInfoList::ConnectionDatas: { + QString peerString; + peerString += "Peer:" + peerId; + for (const auto& device : peerData.keys()) { + peerString += "{"; + peerString += "Device:" + device + ","; + peerString += "Status:" + peerData[device]["status"].toString() + ","; + peerString += "Channels:" + peerData[device]["channels"].toString() + ","; + peerString += "Remote Address" + peerData[device]["remoteAddress"].toString(); + peerString += "}"; + } + return peerString; + } + case ConnectionInfoList::PeerId: + return peerId; + case ConnectionInfoList::RemoteAddress: { + QVariantMap remoteAddressMap; + int i = 0; + for (const auto& device : peerData.keys()) { + remoteAddressMap.insert(QString::number(i++), peerData[device]["remoteAddress"]); + } + return QVariant(remoteAddressMap); + } + case ConnectionInfoList::DeviceId: { + QVariantMap deviceMap; + int i = 0; + for (const auto& device : peerData.keys()) { + deviceMap.insert(QString::number(i++), device); + } + return QVariant(deviceMap); + } + case ConnectionInfoList::Status: { + QVariantMap statusMap; + int i = 0; + for (const auto& device : peerData.keys()) { + statusMap.insert(QString::number(i++), peerData[device]["status"]); + } + return QVariantMap(statusMap); + } + case ConnectionInfoList::Channels: { + QVariantMap channelsMap; + int i = 0; + for (const auto& device : peerData.keys()) { + channelsMap.insert(QString::number(i++), peerData[device]["channels"]); + } + return QVariant(channelsMap); + } + case ConnectionInfoList::Count: + return peerData.size(); + } + return {}; +} + +QHash<int, QByteArray> +ConnectionInfoListModel::roleNames() const +{ + using namespace ConnectionInfoList; + QHash<int, QByteArray> roles; +#define X(role) roles[role] = #role; + CONNECTONINFO_ROLES +#undef X + return roles; +} + +void +ConnectionInfoListModel::update() +{ + const auto accountId = lrcInstance_->get_currentAccountId(); + if (accountId.isEmpty()) { + return; + } + aggregateData(); +} + +template<typename T> +std::tuple<QVector<T>, QVector<T>> +getSetDiff(QVector<T> u, QVector<T> v) +{ + using namespace std; + QVector<T> a, r; + set_difference(v.begin(), v.end(), u.begin(), u.end(), inserter(a, a.begin())); + set_difference(u.begin(), u.end(), v.begin(), v.end(), inserter(r, r.begin())); + return {a, r}; +} + +void +ConnectionInfoListModel::aggregateData() +{ + const auto accountId = lrcInstance_->get_currentAccountId(); + if (accountId.isEmpty()) { + return; + } + + connectionInfoList_ = lrcInstance_->getConnectionList(accountId); + + peerData_ = {}; + + QSet<QString> newPeerIds; + + for (const auto& connectionInfo : connectionInfoList_) { + if (!connectionInfo["peer"].isEmpty()) { + newPeerIds.insert(connectionInfo["peer"]); + } + const auto channelInfoList = lrcInstance_->getChannelList(accountId, connectionInfo["id"]); + peerData_[connectionInfo["peer"]][connectionInfo["device"]] = {}; + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["status"] + = connectionInfo["status"]; + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["channels"] = channelInfoList + .size(); + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["id"] = connectionInfo["id"]; + peerData_[connectionInfo["peer"]][connectionInfo["device"]]["remoteAddress"] + = connectionInfo["remoteAddress"]; + } + + QVector<QString> oldVector; + for (const auto& peerId : peerIds_) { + oldVector << peerId; + } + QVector<QString> newVector; + for (const auto& peerId : newPeerIds) { + newVector << peerId; + } + + std::sort(oldVector.begin(), oldVector.end()); + std::sort(newVector.begin(), newVector.end()); + + QVector<QString> removed, added; + std::tie(added, removed) = getSetDiff(oldVector, newVector); + Q_FOREACH (const auto& key, added) { + auto index = std::distance(newVector.begin(), + std::find(newVector.begin(), newVector.end(), key)); + beginInsertRows(QModelIndex(), index, index); + peerIds_.insert(index, key); + endInsertRows(); + } + Q_FOREACH (const auto& key, removed) { + auto index = std::distance(oldVector.begin(), + std::find(oldVector.begin(), oldVector.end(), key)); + beginRemoveRows(QModelIndex(), index, index); + if (peerIds_.size() > index) { + peerIds_.remove(index); + } else { + qWarning() << "ConnectionInfoListModel::aggregateData: index out of range"; + qWarning() << "index: " << index; + qWarning() << "key: " << key; + } + endRemoveRows(); + } + + // HACK: loop through all the peerIds_ and update the data for each one. + // This is not efficient, but it works. + Q_FOREACH (const auto& peerId, peerIds_) { + auto index = std::distance(peerIds_.begin(), + std::find(peerIds_.begin(), peerIds_.end(), peerId)); + Q_EMIT dataChanged(this->index(index), this->index(index)); + } +} + +void +ConnectionInfoListModel::resetData() +{ + beginResetModel(); + peerIds_.clear(); + peerData_.clear(); + endResetModel(); +} \ No newline at end of file diff --git a/src/app/connectioninfolistmodel.h b/src/app/connectioninfolistmodel.h new file mode 100644 index 0000000000000000000000000000000000000000..74daa7a24b35b6f5f3fcbdee86212e854745340c --- /dev/null +++ b/src/app/connectioninfolistmodel.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "abstractlistmodelbase.h" + +#define CONNECTONINFO_ROLES \ + X(ConnectionDatas) \ + X(ChannelsMap) \ + X(PeerName) \ + X(PeerId) \ + X(DeviceId) \ + X(Status) \ + X(Channels) \ + X(RemoteAddress) \ + X(Count) // this is the number of connections (convenience) + +namespace ConnectionInfoList { +Q_NAMESPACE +enum Role { + DummyRole = Qt::UserRole + 1, +#define X(role) role, + CONNECTONINFO_ROLES +#undef X +}; +Q_ENUM_NS(Role) +} // namespace ConnectionInfoList + +class ConnectionInfoListModel : public AbstractListModelBase +{ +public: + explicit ConnectionInfoListModel(LRCInstance* instance, QObject* parent = nullptr); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QHash<int, QByteArray> roleNames() const override; + + Q_INVOKABLE void update(); + +private: + using Role = ConnectionInfoList::Role; + + VectorMapStringString connectionInfoList_; + + QVector<QString> peerIds_; + QMap<QString, QMap<QString, QMap<QString, QVariant>>> peerData_; + void aggregateData(); + void resetData(); +}; diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml index 6bec14fbfbe6272bfa5028e5b5e75531dd13968f..f3c9d145f2d3de4e5d18014feee1d724c9c4d821 100644 --- a/src/app/constant/JamiStrings.qml +++ b/src/app/constant/JamiStrings.qml @@ -850,4 +850,16 @@ Item { property string shiftEnter: qsTr("SHIFT+ENTER") property string textFormattingDescription: qsTr("ENTER or SHIFT+ENTER to insert a new line") property string textFormatting: qsTr("Text formatting") + + //Connection monitoring + property string connected: qsTr("Connected") + property string connectingTLS: qsTr("Connecting TLS") + property string connectingICE: qsTr("Connecting ICE") + property string connecting: qsTr("Connecting") + property string waiting: qsTr("Waiting") + property string contact: qsTr("Contact") + property string connection: qsTr("Connection") + property string channels: qsTr("Channels") + property string copyAllData: qsTr("Copy all data") + property string remote: qsTr("Remote: ") } diff --git a/src/app/constant/JamiTheme.qml b/src/app/constant/JamiTheme.qml index e29815fdc896c9213c5c111c4f9bcba5b42f2974..ed15e162da0b23463d7f5b8b2664436f78a1dcd0 100644 --- a/src/app/constant/JamiTheme.qml +++ b/src/app/constant/JamiTheme.qml @@ -664,6 +664,11 @@ Item { property color donationBackgroundColor: "#D5E4EF" property string donationUrl: "https://jami.net/donate/" + //Connection monitoring + property color connectionMonitoringTableColor1: "#f0efef" + property color connectionMonitoringTableColor2: "#f6f5f5" + property color connectionMonitoringHeaderColor: "#d1d1d1" + function setTheme(dark) { darkTheme = dark; } diff --git a/src/app/contactadapter.cpp b/src/app/contactadapter.cpp index fb49f41da9d6f0235d1faa44e5a3e6a0e684ad83..7585f346616e4cea0c3c579684575c798894dcb7 100644 --- a/src/app/contactadapter.cpp +++ b/src/app/contactadapter.cpp @@ -21,10 +21,16 @@ #include "contactadapter.h" #include "lrcinstance.h" +#include "qmlregister.h" ContactAdapter::ContactAdapter(LRCInstance* instance, QObject* parent) : QmlAdapterBase(instance, parent) + , connectionInfoListModel_(new ConnectionInfoListModel(lrcInstance_, this)) { + QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, + connectionInfoListModel_.get(), + "ConnectionInfoListModel"); + selectableProxyModel_.reset(new SelectableProxyModel(this)); if (lrcInstance_) { connectSignals(); @@ -246,6 +252,12 @@ ContactAdapter::removeContact(const QString& peerUri, bool banContact) accInfo.contactModel->removeContact(peerUri, banContact); } +void +ContactAdapter::updateConnectionInfo() +{ + connectionInfoListModel_->update(); +} + void ContactAdapter::connectSignals() { diff --git a/src/app/contactadapter.h b/src/app/contactadapter.h index c3666eec3eaf0504766a883eb5bf11b79e9c5b55..ca92a6690fe2967f61b2c817e68014df67c1da37 100644 --- a/src/app/contactadapter.h +++ b/src/app/contactadapter.h @@ -21,6 +21,7 @@ #include "qmladapterbase.h" #include "smartlistmodel.h" #include "conversationlistmodel.h" +#include "connectioninfolistmodel.h" #include <QObject> #include <QSortFilterProxyModel> @@ -90,6 +91,7 @@ public: Q_INVOKABLE void setSearchFilter(const QString& filter); Q_INVOKABLE void contactSelected(int index); Q_INVOKABLE void removeContact(const QString& peerUri, bool banContact); + Q_INVOKABLE void updateConnectionInfo(); void connectSignals(); @@ -104,6 +106,7 @@ private: SmartListModel::Type listModeltype_; QScopedPointer<SmartListModel> smartListModel_; QScopedPointer<SelectableProxyModel> selectableProxyModel_; + QScopedPointer<ConnectionInfoListModel> connectionInfoListModel_; QStringList defaultModerators_; diff --git a/src/app/lrcinstance.cpp b/src/app/lrcinstance.cpp index 02bafe4fdf91bc6cffb85a64aafa705ac1d3c432..0cd74c325f797be8fda559246db9c21a0cefe948 100644 --- a/src/app/lrcinstance.cpp +++ b/src/app/lrcinstance.cpp @@ -458,3 +458,15 @@ LRCInstance::set_selectedConvUid(QString selectedConvUid) Q_EMIT selectedConvUidChanged(); } } + +VectorMapStringString +LRCInstance::getConnectionList(const QString& accountId, const QString& uid) +{ + return Lrc::getConnectionList(accountId, uid); +} + +VectorMapStringString +LRCInstance::getChannelList(const QString& accountId, const QString& uid) +{ + return Lrc::getChannelList(accountId, uid); +} \ No newline at end of file diff --git a/src/app/lrcinstance.h b/src/app/lrcinstance.h index 08075930d35e241a3e1b351bc3152b2c958891ef..502165c3fe0188f2cd9dcc9b587f9e9c1afdadd9 100644 --- a/src/app/lrcinstance.h +++ b/src/app/lrcinstance.h @@ -135,6 +135,10 @@ public: return debugMode_; } + VectorMapStringString getConnectionList(const QString& accountId, const QString& uid = {}); + + VectorMapStringString getChannelList(const QString& accountId, const QString& uid = {}); + Q_SIGNALS: void accountListChanged(); void selectedConvUidChanged(); diff --git a/src/app/settingsview/components/ChannelsPopup.qml b/src/app/settingsview/components/ChannelsPopup.qml new file mode 100644 index 0000000000000000000000000000000000000000..4ef7ffa3192c606987999d851d3a9318425bb1f9 --- /dev/null +++ b/src/app/settingsview/components/ChannelsPopup.qml @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +import QtQuick +import QtQuick.Controls +import Qt5Compat.GraphicalEffects +import QtQuick.Layouts +import net.jami.Models 1.1 +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 + +Popup { + id: popup + width: textComponent.contentWidth + 40 < popup.maxWidth - 20 ? textComponent.contentWidth + 40 : popup.maxWidth - 20 + height: textComponent.contentHeight + 40 < 350 ? textComponent.contentHeight + 40 : 350 + property string text: "" + property int maxWidth: 0 + x: -1 * (popup.width - 20) + + Rectangle { + anchors.fill: parent + color: JamiTheme.transparentColor + + Flickable { + anchors.fill: parent + contentHeight: textComponent.contentHeight + 10 + contentWidth: textComponent.contentWidth + 20 + clip: true + ScrollBar.vertical: ScrollBar { + active: contentHeight > height + } + ScrollBar.horizontal: ScrollBar { + active: contentWidth > width + } + contentX: 10 + contentY: 10 + + Text { + id: textComponent + width: popup.maxWidth - 20 + elide: Text.ElideRight + horizontalAlignment: Text.AlignLeft + text: popup.text + } + } + } +} diff --git a/src/app/settingsview/components/ConnectionMonitoringTable.qml b/src/app/settingsview/components/ConnectionMonitoringTable.qml new file mode 100644 index 0000000000000000000000000000000000000000..c770495f3eae71a2bef42fc1b6872c1e72858844 --- /dev/null +++ b/src/app/settingsview/components/ConnectionMonitoringTable.qml @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2023 Savoir-faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import net.jami.Adapters 1.1 +import net.jami.Constants 1.1 +import net.jami.Enums 1.1 +import net.jami.Models 1.1 +import "../../commoncomponents" +import "../js/logviewwindowcreation.js" as LogViewWindowCreation + +ListView { + id: listview + height: contentItem.childrenRect.height + anchors.top: parent.top + anchors.topMargin: 10 + + spacing: 5 + cacheBuffer: 10 + + property int rota: 0 + + header: Rectangle { + height: 55 + width: connectionMonitoringTable.width + Rectangle { + color: JamiTheme.connectionMonitoringHeaderColor + anchors.top: parent.top + height: 50 + width: connectionMonitoringTable.width + + RowLayout { + anchors.fill: parent + Rectangle { + id: profile + height: 50 + Layout.leftMargin: 5 + Layout.preferredWidth: 210 + color: JamiTheme.transparentColor + Text { + id: textImage + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + text: JamiStrings.contact + } + } + + Rectangle { + id: device + Layout.fillWidth: true + height: 50 + color: JamiTheme.transparentColor + Text { + id: deviceText + anchors.verticalCenter: parent.verticalCenter + text: JamiStrings.device + } + } + + Rectangle { + id: connection + width: 130 + height: 50 + radius: 5 + color: JamiTheme.transparentColor + Text { + id: connectionText + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 10 + text: JamiStrings.connection + } + } + + Rectangle { + id: channel + height: 50 + width: 70 + color: JamiTheme.transparentColor + Text { + anchors.verticalCenter: parent.verticalCenter + text: JamiStrings.channels + } + } + } + } + } + + model: ConnectionInfoListModel + Timer { + interval: 1000 + running: root.visible + repeat: true + onTriggered: { + ContactAdapter.updateConnectionInfo(); + listview.rota = listview.rota + 5; + } + } + + delegate: Rectangle { + id: delegate + height: Count == 0 ? 0 : 10 + 40 * Count + width: connectionMonitoringTable.width + color: index % 2 === 0 ? JamiTheme.connectionMonitoringTableColor1 : JamiTheme.connectionMonitoringTableColor2 + + ListView { + id: listView2 + height: 40 * Count + + anchors.top: delegate.top + + spacing: 0 + + model: Count + + delegate: RowLayout { + id: rowLayoutDelegate + height: 40 + width: connectionMonitoringTable.width + + Rectangle { + id: profile + height: 50 + Layout.leftMargin: 5 + Layout.preferredWidth: 210 + color: JamiTheme.transparentColor + Avatar { + id: avatar + visible: index == 0 + anchors.left: parent.left + height: 40 + width: 40 + anchors.verticalCenter: parent.verticalCenter + imageId: PeerId + mode: Avatar.Mode.Contact + } + Rectangle { + id: usernameRect + anchors.left: avatar.right + anchors.verticalCenter: parent.verticalCenter + width: profile.width - 50 + height: 40 + color: JamiTheme.transparentColor + + Rectangle { + id: usernameRect2 + visible: index == 0 + width: profile.width - 50 + height: 20 + anchors.leftMargin: 10 + anchors.top: parent.top + anchors.left: parent.left + color: JamiTheme.transparentColor + + Text { + id: usernameText + text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, PeerId) + elide: Text.ElideRight + } + } + + Rectangle { + width: profile.width - 50 + height: 20 + anchors.leftMargin: 10 + anchors.top: usernameRect2.bottom + anchors.left: parent.left + visible: usernameRect2.visible && (UtilsAdapter.getBestIdForUri(CurrentAccount.id, PeerId) != UtilsAdapter.getBestNameForUri(CurrentAccount.id, PeerId)) + color: JamiTheme.transparentColor + + Text { + id: idText + anchors.fill: parent + text: UtilsAdapter.getBestIdForUri(CurrentAccount.id, PeerId) + font.pixelSize: 12 + font.underline: usernameText.font.underline + elide: Text.ElideRight + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + usernameText.font.underline = true; + tooltipContact.text = JamiStrings.copyAllData; + } + onExited: { + usernameText.font.underline = false; + tooltipContact.text = JamiStrings.copyAllData; + } + + ToolTip { + id: tooltipContact + visible: usernameText.font.underline + text: JamiStrings.copyAllData + } + onClicked: { + tooltipContact.text = JamiStrings.logsViewCopied; + UtilsAdapter.setClipboardText(ConnectionDatas); + } + } + } + } + + Rectangle { + height: 40 + Layout.fillWidth: true + color: delegate.color + Text { + id: delegateDeviceText + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + text: { + if (DeviceId[index] != undefined) { + return DeviceId[index]; + } else { + return ""; + } + } + elide: Text.ElideMiddle + width: parent.width - 10 + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + delegateDeviceText.font.underline = true; + } + onExited: { + delegateDeviceText.font.underline = false; + tooltipDevice.text = delegateDeviceText.text; + } + + ToolTip { + id: tooltipDevice + visible: delegateDeviceText.font.underline + text: delegateDeviceText.text + } + onClicked: { + tooltipDevice.text = delegateDeviceText.text + " (" + JamiStrings.logsViewCopied + ")"; + UtilsAdapter.setClipboardText(delegateDeviceText.text); + } + } + } + } + + Rectangle { + id: connectionRectangle + color: delegate.color + height: 40 + Layout.preferredWidth: 130 + property var status: Status[index] + ResponsiveImage { + id: connectionImage + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + rotation: connectionRectangle.status == 0 ? 0 : listview.rota + source: { + if (connectionRectangle.status == 0) { + return JamiResources.connected_black_24dp_svg; + } else { + return JamiResources.connecting_black_24dp_svg; + } + } + color: { + if (connectionRectangle.status == 0) { + return "#009c7f"; + } else { + if (connectionRectangle.status == 4) { + return "red"; + } else { + return "#ff8100"; + } + } + } + } + Text { + id: connectionText + anchors.left: connectionImage.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: if (connectionRectangle.status == 0) { + return JamiStrings.connected; + } else { + if (connectionRectangle.status == 1) { + return JamiStrings.connectingTLS; + } else { + if (connectionRectangle.status == 2) { + return JamiStrings.connectingICE; + } else { + if (connectionRectangle.status == 3) { + return JamiStrings.connecting; + } else { + return JamiStrings.waiting; + } + } + } + } + color: connectionImage.color + property var tooltipText: JamiStrings.remote + RemoteAddress[index] + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + connectionText.font.underline = true; + } + onExited: { + connectionText.font.underline = false; + } + + ToolTip { + visible: connectionText.font.underline + text: connectionText.tooltipText + } + } + } + } + + Rectangle { + id: channelDelegateRectangle + height: 40 + Layout.preferredWidth: 70 + color: delegate.color + Text { + id: channelText + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 10 + anchors.left: parent.left + text: { + if (Channels[index] != undefined) { + return Channels[index]; + } else { + return ""; + } + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onExited: { + channelText.font.underline = false; + } + + onEntered: { + channelText.font.underline = true; + } + + onClicked: { + var output = ""; + var channelMap = ChannelsMap[index]; + for (var key in channelMap) { + var value = channelMap[key]; + var keyHexa = parseInt(key, 16).toString(); + output += keyHexa + " : " + value + "\n"; + } + viewCoordinator.presentDialog(parent, "settingsview/components/ChannelsPopup.qml", { + "text": output, + "maxWidth": connectionMonitoringTable.width + }); + } + } + } + } + } + } + } +} diff --git a/src/app/settingsview/components/SettingsPageBase.qml b/src/app/settingsview/components/SettingsPageBase.qml index 69cab4ac7a84ca35ab0e8dbc83d22ae59c535306..cc982f3866b55616dfc6fc9f648a5fc697b8586f 100644 --- a/src/app/settingsview/components/SettingsPageBase.qml +++ b/src/app/settingsview/components/SettingsPageBase.qml @@ -27,6 +27,7 @@ JamiSplitView { id: root required property Item flickableContent property real contentFlickableWidth: Math.min(JamiTheme.maximumWidthSettingsView, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize) + property real tableWidth: Math.min(JamiTheme.maximumWidthSettingsView * 2, settingsPage.width - 2 * JamiTheme.preferredSettingsMarginSize) property alias title: settingsPage.title property color backgroundColor: JamiTheme.secondaryBackgroundColor property alias pageContainer: settingsPage diff --git a/src/app/settingsview/components/TroubleshootSettingsPage.qml b/src/app/settingsview/components/TroubleshootSettingsPage.qml index 63f5adedc6b21571ee317943372cad9ef6f73125..6e1be6d4bdd0301da022db130d5d9f8aa2daa4b0 100644 --- a/src/app/settingsview/components/TroubleshootSettingsPage.qml +++ b/src/app/settingsview/components/TroubleshootSettingsPage.qml @@ -29,11 +29,18 @@ import "../js/logviewwindowcreation.js" as LogViewWindowCreation SettingsPageBase { id: root + Layout.fillWidth: true + + readonly property string baseProviderPrefix: 'image://avatarImage' + + property string typePrefix: 'contact' + property string divider: '_' + property int itemWidth title: JamiStrings.troubleshootTitle - flickableContent: ColumnLayout { + flickableContent: Column { id: troubleshootSettingsColumnLayout width: contentFlickableWidth @@ -42,7 +49,7 @@ SettingsPageBase { anchors.leftMargin: JamiTheme.preferredSettingsMarginSize RowLayout { - + id: rawLayout Text { Layout.fillWidth: true Layout.preferredHeight: 30 @@ -85,5 +92,15 @@ SettingsPageBase { } } } + + Rectangle { + id: connectionMonitoringTable + height: listview.childrenRect.height + 60 + width: tableWidth + + ConnectionMonitoringTable { + id: listview + } + } } } diff --git a/src/libclient/api/lrc.h b/src/libclient/api/lrc.h index 1151c0bee614f0cd22091df19bb99fdc23e9787e..03c61727ce052030438f2178a2ec10cb9e66c2ed 100644 --- a/src/libclient/api/lrc.h +++ b/src/libclient/api/lrc.h @@ -109,6 +109,16 @@ public: */ static VectorString getConferences(const QString& accountId = ""); + /** + * Get connection list from daemon + */ + static VectorMapStringString getConnectionList(const QString& accountId, const QString& uid); + + /** + * Get channel list from daemon + */ + static VectorMapStringString getChannelList(const QString& accountId, const QString& uid); + /** * Preference */ diff --git a/src/libclient/lrc.cpp b/src/libclient/lrc.cpp index c2fb60af0b6051e159e54c3eabd14e4ac8862ec9..330b94750ae9d8029cf9347c21897ca22019abec 100644 --- a/src/libclient/lrc.cpp +++ b/src/libclient/lrc.cpp @@ -211,6 +211,18 @@ Lrc::getConferences(const QString& accountId) return result; } +VectorMapStringString +Lrc::getConnectionList(const QString& accountId, const QString& uid) +{ + return ConfigurationManager::instance().getConnectionList(accountId, uid); +} + +VectorMapStringString +Lrc::getChannelList(const QString& accountId, const QString& uid) +{ + return ConfigurationManager::instance().getChannelList(accountId, uid); +} + bool isFinished(const QString& callState) { diff --git a/src/libclient/qtwrapper/configurationmanager_wrap.h b/src/libclient/qtwrapper/configurationmanager_wrap.h index 2eb19ef9ca49015e362518cf2870e46ff4cd8915..2ecafd2a214b49ac91735c8754833d1abf51dd96 100644 --- a/src/libclient/qtwrapper/configurationmanager_wrap.h +++ b/src/libclient/qtwrapper/configurationmanager_wrap.h @@ -1016,6 +1016,24 @@ public Q_SLOTS: // METHODS libjami::setAllModerators(accountID.toStdString(), allModerators); } + VectorMapStringString getConnectionList(const QString& accountId, const QString& uid) + { + VectorMapStringString temp; + for (auto x : libjami::getConnectionList(accountId.toStdString(), uid.toStdString())) { + temp.push_back(convertMap(x)); + } + return temp; + } + + VectorMapStringString getChannelList(const QString& accountId, const QString& uid) + { + VectorMapStringString temp; + for (auto x : libjami::getChannelList(accountId.toStdString(), uid.toStdString())) { + temp.push_back(convertMap(x)); + } + return temp; + } + bool isAllModerators(const QString& accountID) { return libjami::isAllModerators(accountID.toStdString());