diff --git a/src/app/main.cpp b/src/app/main.cpp index f905da56f..c03df4c2e 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -227,7 +227,6 @@ int main(int argc, char *argv[]) Registration::instance().setAccountManager(accountManager.get()); qml_register_types_org_kde_neochat(); - qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s); QQmlApplicationEngine engine; diff --git a/src/libneochat/models/userfiltermodel.cpp b/src/libneochat/models/userfiltermodel.cpp index 13caa11e6..7082cb3f1 100644 --- a/src/libneochat/models/userfiltermodel.cpp +++ b/src/libneochat/models/userfiltermodel.cpp @@ -11,8 +11,10 @@ bool UserFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceP if (!m_allowEmpty && m_filterText.length() < 1) { return false; } - if (sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::MembershipRole).value() != Quotient::Membership::Join) { - return false; + if (m_membership != Quotient::Membership::Invalid) { + if (sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::MembershipRole).value() != m_membership) { + return false; + } } return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive) || sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::UserIdRole).toString().contains(m_filterText, Qt::CaseInsensitive); @@ -41,4 +43,15 @@ void UserFilterModel::setAllowEmpty(bool allowEmpty) Q_EMIT allowEmptyChanged(); } +Quotient::Membership UserFilterModel::membership() const +{ + return m_membership; +} + +void UserFilterModel::setMembership(const Quotient::Membership state) +{ + m_membership = state; + Q_EMIT membershipChanged(); +} + #include "moc_userfiltermodel.cpp" diff --git a/src/libneochat/models/userfiltermodel.h b/src/libneochat/models/userfiltermodel.h index ad10f0cc4..716dbec68 100644 --- a/src/libneochat/models/userfiltermodel.h +++ b/src/libneochat/models/userfiltermodel.h @@ -3,6 +3,8 @@ #pragma once +#include + #include #include @@ -25,7 +27,10 @@ class UserFilterModel : public QSortFilterProxyModel */ Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged) Q_PROPERTY(bool allowEmpty READ allowEmpty WRITE setAllowEmpty NOTIFY allowEmptyChanged) - + /** + * @brief Only shows users with this membership state. + */ + Q_PROPERTY(Quotient::Membership membership READ membership WRITE setMembership NOTIFY membershipChanged) public: using QSortFilterProxyModel::QSortFilterProxyModel; @@ -42,11 +47,16 @@ public: bool allowEmpty() const; void setAllowEmpty(bool allowEmpty); + Quotient::Membership membership() const; + void setMembership(Quotient::Membership state); + Q_SIGNALS: void filterTextChanged(); void allowEmptyChanged(); + void membershipChanged(); private: QString m_filterText; bool m_allowEmpty = false; + Quotient::Membership m_membership = Quotient::Membership::Invalid; }; diff --git a/src/roominfo/RoomInformation.qml b/src/roominfo/RoomInformation.qml index 47b93088d..d4b721dfc 100644 --- a/src/roominfo/RoomInformation.qml +++ b/src/roominfo/RoomInformation.qml @@ -261,6 +261,7 @@ QQC2.ScrollView { id: userFilterModel sourceModel: root.userListModel allowEmpty: true + membership: JoinRule.Join } clip: true diff --git a/src/rooms/RoomContextMenu.qml b/src/rooms/RoomContextMenu.qml index c62bcf2e6..29f9ec256 100644 --- a/src/rooms/RoomContextMenu.qml +++ b/src/rooms/RoomContextMenu.qml @@ -9,7 +9,7 @@ import QtQuick.Layouts import org.kde.kirigami as Kirigami import org.kde.kirigamiaddons.components as KirigamiComponents -import Quotient +import io.github.quotient_im.libquotient import org.kde.neochat import org.kde.neochat.settings @@ -126,7 +126,7 @@ KirigamiComponents.ConvergentContextMenu { Kirigami.Action { text: i18nc("@action:inmenu", "Copy Room Link") icon.name: "edit-copy" - visible: !root.room.isDirectChat() && root.room.joinRule !== JoinRule.Invite + visible: !root.room.isDirectChat() && root.room.joinRule !== Quotient.Invite onTriggered: { // The canonical alias (if it exists) otherwise the first available alias const firstAlias = root.room.aliases[0]; diff --git a/src/settings/CMakeLists.txt b/src/settings/CMakeLists.txt index 5ce8b9bab..b8a58cf4e 100644 --- a/src/settings/CMakeLists.txt +++ b/src/settings/CMakeLists.txt @@ -52,6 +52,7 @@ ecm_add_qml_module(Settings GENERATE_PLUGIN_SOURCE RoomAdvancedPage.qml KeyboardShortcutsPage.qml Members.qml + MembersList.qml SOURCES colorschemer.cpp threepidaddhelper.cpp diff --git a/src/settings/Members.qml b/src/settings/Members.qml index 0198b83e0..572c5d35c 100644 --- a/src/settings/Members.qml +++ b/src/settings/Members.qml @@ -14,6 +14,7 @@ import org.kde.kirigamiaddons.labs.components as KirigamiComponents import org.kde.kitemmodels import org.kde.neochat +import io.github.quotient_im.libquotient FormCard.FormCardPage { id: root @@ -28,6 +29,63 @@ FormCard.FormCardPage { showMute: false } + Component { + id: bannedMembersPage + MembersList { + title: i18nc("@title", "Banned Members") + membership: Quotient.MembershipMask.Ban + room: root.room + confirmationTitle: i18nc("@title:dialog", "Unban User") + confirmationSubtitle: i18nc("@info %1 is a matrix ID", "Do you really want to unban %1?", currentMemberId) + icon: "checkmark-symbolic" + actionText: i18nc("@action:button", "Unban…") + actionConfirmationText: i18nc("@action:button", "Unban") + actionVisible: root.room.canSendState("ban") + + onActionTaken: memberId => root.room.unban(memberId) + } + } + + Component { + id: invitedMembersPage + MembersList { + title: i18nc("@title", "Invited Members") + membership: Quotient.MembershipMask.Invite + room: root.room + confirmationTitle: i18nc("@title:dialog", "Uninvite User") + confirmationSubtitle: i18nc("@info %1 is a matrix ID", "Do you really want to uninvite %1?", currentMemberId) + icon: "im-ban-kick-user-symbolic" + actionText: i18nc("@action:button", "Uninvite…") + actionConfirmationText: i18nc("@action:button", "Uninvite") + actionVisible: root.room.canSendState("kick") + + onActionTaken: memberId => root.room.kickMember(memberId, "Revoked invite") + } + } + + FormCard.FormCard { + Layout.topMargin: Kirigami.Units.largeSpacing * 4 + + FormCard.FormButtonDelegate { + id: bannedMemberDelegate + + icon.name: "im-ban-user-symbolic" + text: i18nc("@action:button", "Banned Members") + onClicked: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).layers.push(bannedMembersPage) + } + FormCard.FormDelegateSeparator { + above: bannedMemberDelegate + below: inviteMemberDelegate + } + FormCard.FormButtonDelegate { + id: inviteMemberDelegate + + icon.name: "list-add-user-symbolic" + text: i18nc("@action:button", "Invited Members") + onClicked: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).layers.push(invitedMembersPage) + } + } + FormCard.FormHeader { title: i18nc("@title", "Privileged Members") visible: !root.loading @@ -103,6 +161,7 @@ FormCard.FormCardPage { id: userListFilterModel sourceModel: RoomManager.userListModel filterText: userListSearchField.text + membership: Quotient.MembershipMask.Join onFilterTextChanged: { if (filterText.length > 0 && !userListSearchPopup.visible) { diff --git a/src/settings/MembersList.qml b/src/settings/MembersList.qml new file mode 100644 index 000000000..f7ea6e68c --- /dev/null +++ b/src/settings/MembersList.qml @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2026 Joshua Goins +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +pragma ComponentBehavior: Bound + +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.kirigamiaddons.delegates as Delegates +import org.kde.kirigamiaddons.labs.components as KirigamiComponents +import org.kde.kitemmodels + +import org.kde.neochat + + +FormCard.FormCardPage { + id: root + + required property NeoChatRoom room + property alias membership: userFilterModel.membership + property alias confirmationTitle: actionDialog.title + property alias confirmationSubtitle: actionDialog.subtitle + property string currentMemberId + required property string icon + required property string actionText + required property string actionConfirmationText + required property bool actionVisible + + signal actionTaken(memberId: string) + + FormCard.FormCard { + Layout.topMargin: Kirigami.Units.largeSpacing * 4 + Layout.fillWidth: true + + FormCard.FormPlaceholderMessageDelegate { + text: i18nc("@info:placeholder", "No members") + visible: userRepeater.count === 0 + } + + Repeater { + id: userRepeater + + model: UserFilterModel { + id: userFilterModel + sourceModel: RoomManager.userListModel + allowEmpty: true + } + + delegate: FormCard.FormTextDelegate { + id: userDelegate + + required property string userId + + text: userId + textItem.textFormat: Text.PlainText + + contentItem.children: RowLayout { + spacing: Kirigami.Units.largeSpacing + + QQC2.Button { + icon.name: root.icon + visible: root.actionVisible + text: root.actionText + + onClicked: { + root.currentMemberId = userDelegate.userId; + actionDialog.open(); + } + } + } + } + } + } + + Kirigami.PromptDialog { + id: actionDialog + + parent: root.QQC2.Overlay.overlay + footer: QQC2.DialogButtonBox { + standardButtons: QQC2.Dialog.Cancel + + QQC2.Button { + icon.name: root.icon + text: root.actionConfirmationText + + QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole + } + } + + onAccepted: root.actionTaken(root.currentMemberId) + } +} diff --git a/src/settings/RoomSecurityPage.qml b/src/settings/RoomSecurityPage.qml index 401a59d7b..796a05095 100644 --- a/src/settings/RoomSecurityPage.qml +++ b/src/settings/RoomSecurityPage.qml @@ -13,7 +13,7 @@ import org.kde.kirigamiaddons.formcard as FormCard import org.kde.neochat -import Quotient +import io.github.quotient_im.libquotient FormCard.FormCardPage { id: root diff --git a/src/settings/SelectSpacesDialog.qml b/src/settings/SelectSpacesDialog.qml index 394d12712..4da2f2476 100644 --- a/src/settings/SelectSpacesDialog.qml +++ b/src/settings/SelectSpacesDialog.qml @@ -9,7 +9,7 @@ import org.kde.kirigami as Kirigami import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.labs.components as Components -import Quotient +import io.github.quotient_im.libquotient import org.kde.neochat