[network/kaidan] /: Add support for reporting contacts/messages
Melvin Keskin <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit b77c1eccfe2c8f84bcc6e98123a545f5e4303e50 by Melvin Keskin, on behalf of Linus Jahn.
Committed on 06/08/2026 at 12:44.
Pushed by melvo into branch 'master'.
Add support for reporting contacts/messages
This implements XEP-0377: Blocking Command Reports.
Reporting can only be done in combination with blocking a contact.
The option for sending spam/abuse reports is only shown if the
server supports it.
A report consists of a reason (spam/abuse), an optional description,
and, if a message is selected, its reference (while allowing to report
multiple messages once the UI supports that).
The reports are sent to the own server.
In addition, the own server is allowed to forward the report to the
server of the reported contact and to third-party services (e.g.,
blocklist providers) in order to be as effective as possible.
M +2 -0 kirigami-icons.qrc
M +8 -0 misc/doap.xml
M +13 -0 src/Account.cpp
M +6 -0 src/Account.h
M +29 -0 src/Blocking.cpp
M +4 -0 src/Blocking.h
M +2 -0 src/DiscoveryController.cpp
M +1 -0 src/Globals.h
M +11 -0 src/MessageModel.cpp
M +5 -0 src/MessageModel.h
M +9 -0 src/app/main.cpp
M +1 -0 src/qml/ChatPage.qml
M +37 -10 src/qml/details/ContactDetailsContent.qml
A +86 -0 src/qml/elements/BlockingReportContent.qml [License: GPL(v3.0+)]
A +44 -0 src/qml/elements/BlockingReportDialog.qml [License: GPL(v3.0+)]
M +20 -0 src/qml/elements/ChatMessage.qml
M +16 -0 src/qml/elements/ChatMessageContextMenu.qml
M +12 -1 src/qml/elements/ConfirmationFormButtonArea.qml
M +2 -0 src/qml/qml.qrc
https://invent.kde.org/network/kaidan/-/commit/b77c1eccfe2c8f84bcc6e98123a545f5e4303e50
diff --git a/kirigami-icons.qrc b/kirigami-icons.qrc
index 661797f21..393d9ba75 100644
--- a/kirigami-icons.qrc
+++ b/kirigami-icons.qrc
@@ -25,6 +25,8 @@ SPDX-License-Identifier: CC0-1.0
<file alias="resource-group-new.svg">3rdparty/breeze-icons/icons/actions/22/resource-group-new.svg</file>
<file alias="info-symbolic.svg">3rdparty/breeze-icons/icons/actions/22/info-symbolic.svg</file>
<file alias="internet-mail-symbolic.svg">3rdparty/breeze-icons/icons/actions/22/internet-mail-symbolic.svg</file>
+ <file alias="mail-mark-junk-symbolic.svg">3rdparty/breeze-icons/icons/actions/22/mail-mark-junk-symbolic.svg</file>
+ <file alias="mail-mark-notjunk-symbolic.svg">3rdparty/breeze-icons/icons/actions/22/mail-mark-notjunk-symbolic.svg</file>
</qresource>
<qresource prefix="/icons/breeze/actions/32">
diff --git a/misc/doap.xml b/misc/doap.xml
index b707ed252..766735664 100644
--- a/misc/doap.xml
+++ b/misc/doap.xml
@@ -387,6 +387,14 @@ SPDX-License-Identifier: CC0-1.0
<xmpp:since>0.10</xmpp:since>
</xmpp:SupportedXep>
</implements>
+ <implements>
+ <xmpp:SupportedXep>
+ <xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0377.html'/>
+ <xmpp:status>complete</xmpp:status>
+ <xmpp:version>0.4.1</xmpp:version>
+ <xmpp:since>0.17</xmpp:since>
+ </xmpp:SupportedXep>
+ </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource='https://xmpp.org/extensions/xep-0380.html'/>
diff --git a/src/Account.cpp b/src/Account.cpp
index cde35c0fe..cd6b594d1 100644
--- a/src/Account.cpp
+++ b/src/Account.cpp
@@ -201,6 +201,19 @@ void AccountSettings::setInBandRegistrationFeaturesSupported(bool inBandRegistra
}
}
+bool AccountSettings::blockingReportsSupported() const
+{
+ return m_data.blockingReportsSupported;
+}
+
+void AccountSettings::setBlockingReportsSupported(bool blockingReportsSupported)
+{
+ if (m_data.blockingReportsSupported != blockingReportsSupported) {
+ m_data.blockingReportsSupported = blockingReportsSupported;
+ Q_EMIT blockingReportsSupportedChanged();
+ }
+}
+
qint64 AccountSettings::httpUploadLimit() const
{
return m_data.httpUploadLimit;
diff --git a/src/Account.h b/src/Account.h
index e2d74fcdc..576d3ac04 100644
--- a/src/Account.h
+++ b/src/Account.h
@@ -55,6 +55,7 @@ class AccountSettings : public QObject
Q_PROPERTY(QString displayName READ displayName NOTIFY nameChanged)
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
Q_PROPERTY(bool inBandRegistrationFeaturesSupported READ inBandRegistrationFeaturesSupported NOTIFY inBandRegistrationFeaturesSupportedChanged)
+ Q_PROPERTY(bool blockingReportsSupported READ blockingReportsSupported NOTIFY blockingReportsSupportedChanged)
Q_PROPERTY(qint64 httpUploadLimit READ httpUploadLimit NOTIFY httpUploadLimitChanged)
Q_PROPERTY(QString httpUploadLimitText READ httpUploadLimitText NOTIFY httpUploadLimitChanged)
Q_PROPERTY(QList<QString> chatSupportAddresses READ chatSupportAddresses NOTIFY chatSupportAddressesChanged)
@@ -142,6 +143,7 @@ public:
QString latestMessageStanzaId;
QDateTime latestMessageStanzaTimestamp;
bool inBandRegistrationFeaturesSupported = false;
+ bool blockingReportsSupported = false;
qint64 httpUploadLimit = 0;
QList<QString> chatSupportAddresses;
QList<QString> groupChatSupportAddresses;
@@ -207,6 +209,10 @@ public:
void setInBandRegistrationFeaturesSupported(bool inBandRegistrationFeaturesSupported);
Q_SIGNAL void inBandRegistrationFeaturesSupportedChanged();
+ bool blockingReportsSupported() const;
+ void setBlockingReportsSupported(bool blockingReportsSupported);
+ Q_SIGNAL void blockingReportsSupportedChanged();
+
qint64 httpUploadLimit() const;
void setHttpUploadLimit(qint64 httpUploadLimit);
Q_SIGNAL void httpUploadLimitChanged();
diff --git a/src/Blocking.cpp b/src/Blocking.cpp
index 641b30a4c..2fd9a2c33 100644
--- a/src/Blocking.cpp
+++ b/src/Blocking.cpp
@@ -7,6 +7,7 @@
// Qt
#include <QSqlQuery>
// QXmpp
+#include <QXmppSpamReport.h>
#include <QXmppUtils.h>
// Kaidan
#include "Account.h"
@@ -177,6 +178,34 @@ void BlockingController::block(const QString &jid)
});
}
+void BlockingController::blockAndReport(const QString &jid,
+ QXmppSpamReport::Reason reason,
+ const QString &description,
+ const QList<QXmppStanzaId> &messageReferences)
+{
+ qCDebug(KAIDAN_CORE_LOG) << "Blocking and reporting" << jid;
+
+ QXmppSpamReport report;
+ report.setReason(reason);
+ report.setText(description);
+ report.setForwardToOrigin(true);
+ report.setForwardToThirdParty(true);
+ report.setMessageReferences(messageReferences);
+
+ setRunning(m_runningActionCount + 1);
+
+ m_manager->reportAndBlock(jid, std::move(report)).then(this, [this, jid](auto result) {
+ if (auto *error = std::get_if<QXmppError>(&result)) {
+ Q_EMIT blockingFailed(jid, error->description);
+ } else {
+ qCDebug(KAIDAN_CORE_LOG) << "Blocked and reported" << jid;
+ Q_EMIT blocked(jid);
+ }
+
+ setRunning(m_runningActionCount - 1);
+ });
+}
+
void BlockingController::unblock(const QString &jid)
{
qCDebug(KAIDAN_CORE_LOG) << "Unblocking" << jid;
diff --git a/src/Blocking.h b/src/Blocking.h
index 0b34abeac..18ce9d697 100644
--- a/src/Blocking.h
+++ b/src/Blocking.h
@@ -8,6 +8,8 @@
#include <QAbstractListModel>
// QXmpp
#include <QXmppBlockingManager.h>
+#include <QXmppSpamReport.h>
+#include <QXmppStanzaId.h>
// Kaidan
#include "DatabaseComponent.h"
@@ -44,6 +46,8 @@ public:
Q_SIGNAL void blocklistChanged();
Q_INVOKABLE void block(const QString &jid);
+ Q_INVOKABLE void
+ blockAndReport(const QString &jid, QXmppSpamReport::Reason reason, const QString &text, const QList<QXmppStanzaId> &messageReferences = {});
Q_SIGNAL void blocked(const QString &jid);
Q_SIGNAL void blockingFailed(const QString &jid, const QString &errorText);
diff --git a/src/DiscoveryController.cpp b/src/DiscoveryController.cpp
index e788200aa..e18037da8 100644
--- a/src/DiscoveryController.cpp
+++ b/src/DiscoveryController.cpp
@@ -66,6 +66,8 @@ void DiscoveryController::updateData()
void DiscoveryController::handleOwnServerInfo(QXmppDiscoInfo &&info)
{
+ m_accountSettings->setBlockingReportsSupported(info.features().contains(XMLNS_BLOCKING_COMMAND_REPORTS));
+
if (auto contactAddresses = info.dataForm<QXmppContactAddresses>()) {
auto supportAddresses = contactAddresses->supportAddresses();
diff --git a/src/Globals.h b/src/Globals.h
index 4c1fae1ed..99f047467 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -134,3 +134,4 @@ constexpr QStringView ENCRYPTION_KEY_ID_CHARACTER_GROUP_SEPARATOR = u" ";
inline constexpr QStringView XMLNS_SFS = u"urn:xmpp:sfs:0";
inline constexpr QStringView XMLNS_MESSAGE_REPLIES = u"urn:xmpp:reply:0";
const auto XMLNS_OMEMO_2 = QStringLiteral("urn:xmpp:omemo:2");
+const auto XMLNS_BLOCKING_COMMAND_REPORTS = QStringLiteral("urn:xmpp:reporting:1");
diff --git a/src/MessageModel.cpp b/src/MessageModel.cpp
index 25f7464d1..4f3244fc4 100644
--- a/src/MessageModel.cpp
+++ b/src/MessageModel.cpp
@@ -81,6 +81,7 @@ QHash<int, QByteArray> MessageModel::roleNames() const
roles[GroupChatSenderId] = QByteArrayLiteral("groupChatSenderId");
roles[SenderName] = QByteArrayLiteral("senderName");
roles[Id] = QByteArrayLiteral("id");
+ roles[StanzaId] = QByteArrayLiteral("stanzaId");
roles[IsLastReadOwnMessage] = QByteArrayLiteral("isLastReadOwnMessage");
roles[IsLatestOldMessage] = QByteArrayLiteral("isLatestOldMessage");
roles[IsEdited] = QByteArrayLiteral("isEdited");
@@ -157,6 +158,16 @@ QVariant MessageModel::data(const QModelIndex &index, int role) const
}
case Id:
return msg.relevantId();
+ case StanzaId: {
+ if (msg.stanzaId.isEmpty()) {
+ return {};
+ }
+
+ // The stored stanza ID is guaranteed (on receipt) to have been generated by the group chat
+ // JID for group chat messages and by the own account JID otherwise.
+ const auto by = msg.isGroupChatMessage() ? msg.chatJid : msg.accountJid;
+ return QVariant::fromValue(QXmppStanzaId{msg.stanzaId, by});
+ }
case IsLastReadOwnMessage:
// A read marker text is only displayed if the message is the last read own message and
// there is no more recent message from the contact.
diff --git a/src/MessageModel.h b/src/MessageModel.h
index ba6b10a39..d90ee916b 100644
--- a/src/MessageModel.h
+++ b/src/MessageModel.h
@@ -12,9 +12,13 @@
// Qt
#include <QAbstractListModel>
+// QXmpp
+#include <QXmppStanzaId.h>
// Kaidan
#include "Message.h"
+Q_DECLARE_METATYPE(QXmppStanzaId)
+
class AccountSettings;
class AtmController;
class ChatController;
@@ -65,6 +69,7 @@ public:
GroupChatSenderId,
SenderName,
Id,
+ StanzaId,
IsLastReadOwnMessage,
IsLatestOldMessage,
IsEdited,
diff --git a/src/app/main.cpp b/src/app/main.cpp
index a646e72c4..ef9949ea3 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -59,6 +59,7 @@
#include <QXmppMixParticipantItem.h>
#include <QXmppRegisterIq.h>
#include <QXmppResultSet.h>
+#include <QXmppSpamReport.h>
#include <QXmppUri.h>
#include <QXmppVCardIq.h>
#include <QXmppVersionIq.h>
@@ -318,6 +319,8 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
// register qMetaTypes
qRegisterMetaType<Provider>();
+ qRegisterMetaType<QXmppStanzaId>();
+ qRegisterMetaType<QList<QXmppStanzaId>>();
qRegisterMetaType<RosterItem>();
qRegisterMetaType<RosterItemWatcher *>();
qRegisterMetaType<RosterModel *>();
@@ -619,6 +622,12 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QStringLiteral("Cannot create object; only enums defined!"));
qmlRegisterUncreatableMetaObject(Message::staticMetaObject, APPLICATION_ID, 1, 0, "Message", QStringLiteral("Cannot create object; only enums defined!"));
qmlRegisterUncreatableMetaObject(QMimeType::staticMetaObject, APPLICATION_ID, 1, 0, "QMimeType", QStringLiteral("QMimeType type usable"));
+ qmlRegisterUncreatableMetaObject(QXmppSpamReport::staticMetaObject,
+ APPLICATION_ID,
+ 1,
+ 0,
+ "SpamReport",
+ QStringLiteral("Cannot create object; only enums defined!"));
qmlRegisterUncreatableType<ChatStateController>(APPLICATION_ID, 1, 0, "ChatStateController", QStringLiteral("Not creatable from QML"));
// Q_NAMESPACE
diff --git a/src/qml/ChatPage.qml b/src/qml/ChatPage.qml
index 1acd25580..d1f78224f 100644
--- a/src/qml/ChatPage.qml
+++ b/src/qml/ChatPage.qml
@@ -368,6 +368,7 @@ SearchBarPage {
chatController: root.chatController
modelIndex: index
msgId: model.id
+ stanzaId: model.stanzaId
senderJid: model.senderJid
groupChatSenderId: model.groupChatSenderId
senderName: model.senderName
diff --git a/src/qml/details/ContactDetailsContent.qml b/src/qml/details/ContactDetailsContent.qml
index 3a5a39551..9bebe89bb 100644
--- a/src/qml/details/ContactDetailsContent.qml
+++ b/src/qml/details/ContactDetailsContent.qml
@@ -161,18 +161,45 @@ RosterItemDetailsContent {
onToggled: root.chatController.account.rosterController.setReadMarkerSendingEnabled(root.chatController.jid, checked)
}
- FormCard.FormSwitchDelegate {
- text: qsTr("Block")
- description: qsTr("Block all communication including status and notifications")
+ ConfirmationFormButtonArea {
+ id: blockingArea
visible: root.chatController.account.settings.enabled
- enabled: !root.chatController.account.blockingController.busy && root.chatController.account.connection.state === Enums.StateConnected
- checked: blockingWatcher.blocked
- onToggled: {
- if (checked) {
- root.chatController.account.blockingController.block(root.chatController.jid)
- } else {
- root.chatController.account.blockingController.unblock(root.chatController.jid)
+ button {
+ text: {
+ if (blockingWatcher.blocked) {
+ return qsTr("Unblock")
+ }
+
+ return blockingReportContent.reportsSupported ? qsTr("Block and report") : qsTr("Block")
+ }
+ description: {
+ if (blockingWatcher.blocked) {
+ return qsTr("Unblock this contact")
+ }
+
+ return blockingReportContent.reportsSupported ? qsTr("Block and (optionally) report this contact") : qsTr("Block this contact")
}
+ icon.name: blockingWatcher.blocked ? "mail-mark-notjunk-symbolic" : "mail-mark-junk-symbolic"
+ checkable: !blockingWatcher.blocked && blockingReportContent.reportsSupported
+ enabled: !root.chatController.account.blockingController.busy && root.chatController.account.connection.state === Enums.StateConnected
+ onClicked: {
+ if (blockingWatcher.blocked) {
+ root.chatController.account.blockingController.unblock(root.chatController.jid)
+ } else if (!button.checkable) {
+ blockingReportContent.submit()
+ }
+ }
+ }
+ confirmationText: blockingReportContent.reportingEnabled ? qsTr("Block and report") : qsTr("Block")
+ confirmationButton.onClicked: {
+ blockingReportContent.submit()
+ blockingArea.button.checked = false
+ }
+
+ BlockingReportContent {
+ id: blockingReportContent
+ account: root.chatController.account
+ jid: root.chatController.jid
}
BlockingWatcher {
diff --git a/src/qml/elements/BlockingReportContent.qml b/src/qml/elements/BlockingReportContent.qml
new file mode 100644
index 000000000..77cc0300e
--- /dev/null
+++ b/src/qml/elements/BlockingReportContent.qml
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: 2026 Linus Jahn <[email protected]>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick
+import QtQuick.Layouts
+import org.kde.kirigami as Kirigami
+import org.kde.kirigamiaddons.formcard as FormCard
+
+import im.kaidan.kaidan
+
+/**
+ * Form entries for blocking a JID and reporting spam/abuse.
+ *
+ * The entries for reporting are only shown if the server supports blocking reports.
+ *
+ * It is intended to be used within the contentItem of a FormCard.FormCard.
+ */
+ColumnLayout {
+ id: root
+
+ required property Account account
+ property string jid
+ // List of stanza ID references (QXmppStanzaId) of the reported messages.
+ property var messageReferences: []
+ readonly property bool reportsSupported: account.settings.blockingReportsSupported
+ property bool reportingMandatory: false
+ readonly property bool reportingEnabled: reportsSupported && (reportingMandatory || reportSwitch.checked)
+
+ spacing: 0
+
+ FormCard.FormSwitchDelegate {
+ id: reportSwitch
+ text: qsTr("Report")
+ description: qsTr("Report spam or abuse")
+ visible: root.reportsSupported && !root.reportingMandatory
+ checked: root.reportingMandatory
+ onToggled: {
+ if (checked) {
+ root.forceActiveFocus()
+ }
+ }
+ }
+
+ FormComboBoxDelegate {
+ id: reasonDelegate
+ text: qsTr("Reason")
+ currentIndex: 0
+ description: currentIndex < 0 ? "" : model[currentIndex].description
+ visible: root.reportingEnabled
+ textRole: "label"
+ valueRole: "value"
+ model: [
+ {
+ label: qsTr("Spam"),
+ description: qsTr("Unsolicited messages, often sent in bulk"),
+ value: SpamReport.Reason.Spam
+ },
+ {
+ label: qsTr("Abuse"),
+ description: qsTr("Harassing, threatening, or otherwise abusive behavior"),
+ value: SpamReport.Reason.Abuse
+ }
+ ]
+ }
+
+ FormCard.FormTextAreaDelegate {
+ id: descriptionField
+ visible: root.reportingEnabled
+ label: qsTr("Description (optional)")
+ }
+
+ function forceActiveFocus() {
+ if (!Kirigami.Settings.isMobile) {
+ descriptionField.forceActiveFocus()
+ }
+ }
+
+ function submit() {
+ if (reportingEnabled) {
+ account.blockingController.blockAndReport(jid, reasonDelegate.currentValue, descriptionField.text, messageReferences)
+ } else {
+ account.blockingController.block(jid)
+ }
+ }
+}
diff --git a/src/qml/elements/BlockingReportDialog.qml b/src/qml/elements/BlockingReportDialog.qml
new file mode 100644
index 000000000..b66f8bd07
--- /dev/null
+++ b/src/qml/elements/BlockingReportDialog.qml
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: 2026 Linus Jahn <[email protected]>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick
+import QtQuick.Controls as Controls
+import QtQuick.Layouts
+import org.kde.kirigami as Kirigami
+import org.kde.kirigamiaddons.formcard as FormCard
+
+/**
+ * Dialog for reporting a message and blocking its sender.
+ *
+ * It is used for blocking a JID while referencing its messages.
+ * Since it is opened via an explicit report action, reporting is mandatory.
+ * Blocking without reporting is done via the contact details.
+ */
+Dialog {
+ id: root
+
+ property alias account: content.account
+ property alias jid: content.jid
+ property alias messageReferences: content.messageReferences
+
+ title: qsTr("Block and report")
+ onOpened: content.forceActiveFocus()
+
+ ConfirmationArea {
+ confirmationButton.text: qsTr("Block and report")
+ confirmationButton.onClicked: {
+ content.submit()
+ root.close()
+ }
+
+ FormCard.FormTextDelegate {
+ description: qsTr("Report the selected message and block its sender")
+ }
+
+ BlockingReportContent {
+ id: content
+ reportingMandatory: true
+ }
+ }
+}
diff --git a/src/qml/elements/ChatMessage.qml b/src/qml/elements/ChatMessage.qml
index 1a05e26b8..5b888be59 100644
--- a/src/qml/elements/ChatMessage.qml
+++ b/src/qml/elements/ChatMessage.qml
@@ -27,6 +27,8 @@ Controls.ItemDelegate {
property ChatController chatController
property int modelIndex
property string msgId
+ // Stanza ID reference (QXmppStanzaId) or undefined if none is available.
+ property var stanzaId
property string senderJid
property string groupChatSenderId
property string senderName
@@ -595,6 +597,20 @@ Controls.ItemDelegate {
}
}
+ Component {
+ id: blockingReportDialog
+
+ BlockingReportDialog {
+ account: root.chatController.account
+ jid: root.senderJid
+ messageReferences: [root.stanzaId]
+ onClosed: {
+ root.messageListView.restorePreviousCurrentIndex()
+ root.sendingPane.forceActiveFocus()
+ }
+ }
+ }
+
function determineMessageGroupDelimiter(delimitingIndex = 0, indexOffset = -1) {
if (modelIndex < 0 || delimitingIndex < 0 || modelIndex === delimitingIndex) {
return true
@@ -622,6 +638,10 @@ Controls.ItemDelegate {
openOverlay(reactionEmojiPicker)
}
+ function openBlockingReportDialog() {
+ openOverlay(blockingReportDialog)
+ }
+
function openGeoLocationMap() {
if (root.chatController.account.openGeoLocation(geoCoordinate)) {
openPage(geoLocationMapPage)
diff --git a/src/qml/elements/ChatMessageContextMenu.qml b/src/qml/elements/ChatMessageContextMenu.qml
index 2f9b4d9eb..1f0f512d0 100644
--- a/src/qml/elements/ChatMessageContextMenu.qml
+++ b/src/qml/elements/ChatMessageContextMenu.qml
@@ -177,6 +177,22 @@ Kirigami.Dialog {
onClicked: root.message.chatController.messageModel.setMessageMarked(root.message.modelIndex, true)
}
+ ChatMessageContextMenuButton {
+ text: qsTr("Block and report")
+ icon.source: "mail-mark-junk-symbolic"
+ contextMenu: root
+ shown: !root.message.isOwn
+ && !root.message.isGroupChatMessage
+ && !root.message.groupChatInvitationJid
+ && root.message.stanzaId
+ && root.message.chatController.account.settings.blockingReportsSupported
+ onClicked: {
+ contextMenu.close()
+ root.message.openBlockingReportDialog()
+ root.currentIndexResetOnClosing = false
+ }
+ }
+
ChatMessageContextMenuButton {
text: qsTr("Remove from this device")
icon.source: "edit-delete-symbolic"
diff --git a/src/qml/elements/ConfirmationFormButtonArea.qml b/src/qml/elements/ConfirmationFormButtonArea.qml
index d7dc5788d..a517a2d5e 100644
--- a/src/qml/elements/ConfirmationFormButtonArea.qml
+++ b/src/qml/elements/ConfirmationFormButtonArea.qml
@@ -11,11 +11,15 @@ import org.kde.kirigamiaddons.formcard as FormCard
/**
* This area contains a button for an action and a button for confirming the action.
*
- * It is intended to be used within the contentItem of a FormCard.FormCard.
+ * Additional form entries can be added as children.
+ * They are only shown while the action's button is checked (i.e., the content and the confirmation button are expanded).
+ *
+ * It is intended to be used within a FormCard.FormCard.
*/
ColumnLayout {
id: root
+ default property alias __data: contentArea.data
property alias button: button
property alias confirmationButton: confirmationButton
property alias confirmationText: confirmationButton.idleText
@@ -41,6 +45,13 @@ ColumnLayout {
}
}
+ ColumnLayout {
+ id: contentArea
+ spacing: 0
+ visible: root.button.checked
+ Layout.fillWidth: true
+ }
+
BusyIndicatorFormButton {
id: confirmationButton
idleText: qsTr("Confirm")
diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc
index 0cd525b55..edb10b391 100644
--- a/src/qml/qml.qrc
+++ b/src/qml/qml.qrc
@@ -54,6 +54,8 @@
<file>elements/AccountSelectionDialog.qml</file>
<file>elements/Audio.qml</file>
<file>elements/Avatar.qml</file>
+ <file>elements/BlockingReportContent.qml</file>
+ <file>elements/BlockingReportDialog.qml</file>
<file>elements/BusyIndicatorFormButton.qml</file>
<file>elements/Button.qml</file>
<file>elements/CameraArea.qml</file>