From 5759f7d82bf70879b20ff89a1d379df5f3b83e3e Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 14 Jan 2026 19:06:47 -0500 Subject: [PATCH] Add a way to view server support information This is useful if your server has said information, for matrix.org includes abuse and administrator e-mails. See #707 --- src/app/CMakeLists.txt | 3 + src/app/qml/AccountMenu.qml | 10 +++ src/app/qml/SupportDialog.qml | 121 ++++++++++++++++++++++++++++++++++ src/app/supportcontroller.cpp | 66 +++++++++++++++++++ src/app/supportcontroller.h | 48 ++++++++++++++ 5 files changed, 248 insertions(+) create mode 100644 src/app/qml/SupportDialog.qml create mode 100644 src/app/supportcontroller.cpp create mode 100644 src/app/supportcontroller.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index ef8830a0c..17495419f 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -37,6 +37,8 @@ qt_add_library(neochat STATIC texttospeechhelper.cpp models/limitermodel.cpp models/limitermodel.h + supportcontroller.cpp + supportcontroller.h ) set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES @@ -107,6 +109,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE qml/UserMenu.qml qml/MeetingDialog.qml qml/SeenByDialog.qml + qml/SupportDialog.qml DEPENDENCIES QtCore QtQuick diff --git a/src/app/qml/AccountMenu.qml b/src/app/qml/AccountMenu.qml index a121a375c..66d7f5315 100644 --- a/src/app/qml/AccountMenu.qml +++ b/src/app/qml/AccountMenu.qml @@ -118,6 +118,16 @@ KirigamiComponents.ConvergentContextMenu { } } + Kirigami.Action { + text: i18nc("@action:inmenu Open support dialog", "Support") + icon.name: "help-contents-symbolic" + onTriggered: { + Qt.createComponent("org.kde.neochat", "SupportDialog").createObject(QQC2.Overlay.overlay, { + connection: root.connection, + }).open(); + } + } + Kirigami.Action { text: i18nc("@action:inmenu", "Logout…") icon.name: "im-kick-user" diff --git a/src/app/qml/SupportDialog.qml b/src/app/qml/SupportDialog.qml new file mode 100644 index 000000000..896f9ab95 --- /dev/null +++ b/src/app/qml/SupportDialog.qml @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2026 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-only + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.formcard as FormCard + +import org.kde.neochat + +Kirigami.Dialog { + id: root + + required property NeoChatConnection connection + + readonly property SupportController supportController: SupportController { + connection: root.connection + } + readonly property bool hasSupportResources: supportController.supportPage.length > 0 && supportController.contacts.length > 0 + + title: i18nc("@title Support information", "Support") + width: Math.min(Kirigami.Units.gridUnit * 30, QQC2.ApplicationWindow.window.width) + + ColumnLayout { + spacing: 0 + + FormCard.FormTextDelegate { + id: explanationTextDelegate + + text: root.hasSupportResources ? + i18nc("@info:label %1 is the domain of the server", "Offical support resources provided by %1:", root.connection.domain) + : i18nc("@info:label %1 is the domain of the server", "%1 has no support resources.", root.connection.domain) + } + + FormCard.FormDelegateSeparator { + above: explanationTextDelegate + below: openSupportPageDelegate + visible: openSupportPageDelegate.visible + } + + FormCard.FormLinkDelegate { + id: openSupportPageDelegate + + icon.name: "help-contents-symbolic" + text: i18nc("@action:button Open support webpage", "Open Support") + url: root.supportController.supportPage + visible: root.supportController.supportPage.length > 0 + } + + FormCard.FormDelegateSeparator { + above: openSupportPageDelegate + visible: root.supportController.contacts.length > 0 + } + + Repeater { + model: root.supportController.contacts + + delegate: FormCard.AbstractFormDelegate { + id: contactDelegate + + required property string role + required property string matrixId + required property string emailAddress + + background: null + + Layout.fillWidth: true + + contentItem: RowLayout { + spacing: Kirigami.Units.largeSpacing + + Kirigami.Icon { + source: "user" + } + + QQC2.Label { + text: { + // Translate known keys + if (contactDelegate.role === "m.role.admin") { + return i18nc("@info:label Adminstrator contact", "Admin") + } else if (contactDelegate.role === "m.role.security") { + return i18nc("@info:label Security contact", "Security") + } + return contactDelegate.role; + } + elide: Text.ElideRight + + Layout.fillWidth: true + } + + QQC2.ToolButton { + visible: contactDelegate.matrixId.length > 0 + icon.name: "document-send-symbolic" + + onClicked: { + root.close(); + root.connection.requestDirectChat(contactDelegate.matrixId); + } + + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.text: i18nc("@info:tooltip %1 is a Matrix ID", "Contact via Matrix (%1)", contactDelegate.matrixId) + } + + QQC2.ToolButton { + visible: contactDelegate.emailAddress.length > 0 + icon.name: "mail-sent-symbolic" + + onClicked: Qt.openUrlExternally("mailto:%1".arg(contactDelegate.emailAddress)) + + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.text: i18nc("@info:tooltip %1 is an e-mail address", "Contact via e-mail (%1)", contactDelegate.emailAddress) + } + } + } + } + } +} diff --git a/src/app/supportcontroller.cpp b/src/app/supportcontroller.cpp new file mode 100644 index 000000000..9c6a28ad1 --- /dev/null +++ b/src/app/supportcontroller.cpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2026 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-only + +#include "supportcontroller.h" + +#include + +#include + +using namespace Quotient; + +void SupportController::setConnection(NeoChatConnection *connection) +{ + if (m_connection != connection) { + m_connection = connection; + Q_EMIT connectionChanged(); + + load(); + } +} + +NeoChatConnection *SupportController::connection() const +{ + return m_connection; +} + +QString SupportController::supportPage() const +{ + return m_supportPage; +} + +QList SupportController::contacts() const +{ + return m_contacts; +} + +void SupportController::load() +{ + if (!m_connection) { + qWarning() << "Tried to load support information without a valid connection?"; + return; + } + + m_connection->callApi() + .onResult([this](const auto &job) { + m_supportPage = job->supportPage(); + m_contacts.reserve(job->contacts().size()); + for (const auto &contact : job->contacts()) { + m_contacts.push_back(SupportContact{ + .role = contact.role, + .matrixId = contact.matrixId, + .emailAddress = contact.emailAddress, + }); + } + + Q_EMIT loaded(); + }) + .onFailure([this](const auto &job) { + Q_UNUSED(job) + + // Just do nothing, our properties will be empty. + Q_EMIT loaded(); + }); +} + +#include "moc_supportcontroller.cpp" diff --git a/src/app/supportcontroller.h b/src/app/supportcontroller.h new file mode 100644 index 000000000..9b044ff38 --- /dev/null +++ b/src/app/supportcontroller.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2026 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include "neochatconnection.h" + +class SupportContact +{ + Q_GADGET + + Q_PROPERTY(QString role MEMBER role) + Q_PROPERTY(QString matrixId MEMBER matrixId) + Q_PROPERTY(QString emailAddress MEMBER emailAddress) + +public: + QString role; + QString matrixId; + QString emailAddress; +}; + +class SupportController : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged REQUIRED) + Q_PROPERTY(QString supportPage READ supportPage NOTIFY loaded) + Q_PROPERTY(QList contacts READ contacts NOTIFY loaded) + +public: + void setConnection(NeoChatConnection *connection); + NeoChatConnection *connection() const; + + QString supportPage() const; + QList contacts() const; + +Q_SIGNALS: + void connectionChanged(); + void loaded(); + +private: + void load(); + + QPointer m_connection = nullptr; + QList m_contacts; + QString m_supportPage; +};