Overhaul user profile UI

Our previous iteration is hitting some limitations as the power of
profiles on Matrix grows. For example, where do we put common rooms or
extra profile fields?

I re-arranged everything to group similar actions together - instead of
throwing it all into one big list. We basically trade vertical for
horizontal space, and gives us more headroom for extra fields when we
want to add more.
This commit is contained in:
Joshua Goins
2025-12-21 13:56:26 -05:00
parent 9bdb9b6a7c
commit 9e7cd0eb09
6 changed files with 392 additions and 221 deletions

View File

@@ -35,6 +35,8 @@ qt_add_library(neochat STATIC
models/commonroomsmodel.h models/commonroomsmodel.h
texttospeechhelper.h texttospeechhelper.h
texttospeechhelper.cpp texttospeechhelper.cpp
models/limitermodel.cpp
models/limitermodel.h
) )
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES

View File

@@ -5,6 +5,7 @@
#include "jobs/neochatgetcommonroomsjob.h" #include "jobs/neochatgetcommonroomsjob.h"
#include <QGuiApplication> #include <QGuiApplication>
#include <Quotient/room.h>
using namespace Quotient; using namespace Quotient;
@@ -39,8 +40,22 @@ void CommonRoomsModel::setUserId(const QString &userId)
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
{ {
Q_UNUSED(index) auto roomId = m_commonRooms[index.row()];
Q_UNUSED(roleName) auto room = connection()->room(roomId);
if (!room) {
return {};
}
switch (roleName) {
case Qt::DisplayRole:
case RoomNameRole:
return room->displayName();
case RoomAvatarRole:
return room->avatarUrl();
case RoomIdRole:
return roomId;
}
return {}; return {};
} }
@@ -50,6 +65,15 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
return m_commonRooms.size(); return m_commonRooms.size();
} }
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
{
return {
{RoomIdRole, "roomId"},
{RoomNameRole, "roomName"},
{RoomAvatarRole, "roomAvatar"},
};
}
void CommonRoomsModel::reload() void CommonRoomsModel::reload()
{ {
if (!m_connection || m_userId.isEmpty()) { if (!m_connection || m_userId.isEmpty()) {

View File

@@ -24,7 +24,9 @@ class CommonRoomsModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
RoomIdRole = Qt::DisplayRole, RoomIdRole = Qt::UserRole,
RoomNameRole,
RoomAvatarRole,
}; };
Q_ENUM(Roles) Q_ENUM(Roles)
@@ -39,6 +41,8 @@ public:
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override; [[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS: Q_SIGNALS:
void connectionChanged(); void connectionChanged();
void userIdChanged(); void userIdChanged();

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "models/limitermodel.h"
LimiterModel::LimiterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
}
int LimiterModel::maximumCount() const
{
return m_maximumCount;
}
void LimiterModel::setMaximumCount(int maximumCount)
{
if (m_maximumCount != maximumCount) {
m_maximumCount = maximumCount;
Q_EMIT maximumCountChanged();
}
}
int LimiterModel::extraCount() const
{
if (sourceModel()) {
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
}
return 0;
}
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
return source_row < maximumCount();
}
#include "moc_limitermodel.cpp"

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
* @class LimiterModel
*
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
*
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
*/
class LimiterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
public:
explicit LimiterModel(QObject *parent = nullptr);
[[nodiscard]] int maximumCount() const;
void setMaximumCount(int maximumCount);
[[nodiscard]] int extraCount() const;
Q_SIGNALS:
void maximumCountChanged();
void extraCountChanged();
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
int m_maximumCount = 0;
};

View File

@@ -23,27 +23,43 @@ Kirigami.Dialog {
property NeoChatConnection connection property NeoChatConnection connection
leftPadding: 0 property CommonRoomsModel model: CommonRoomsModel {
rightPadding: 0 connection: root.connection
topPadding: 0 userId: root.user.id
bottomPadding: 0 }
property LimiterModel limiterModel: LimiterModel {
maximumCount: 5
sourceModel: root.model
}
readonly property bool isSelf: root.user.id === root.connection.localUserId
readonly property bool hasMutualRooms: root.model.count > 0
readonly property bool isRoomProfile: root.room
readonly property string shareUrl: "https://matrix.to/#/" + root.user.id
leftPadding: Kirigami.Units.largeSpacing * 2
rightPadding: Kirigami.Units.largeSpacing * 2
topPadding: Kirigami.Units.largeSpacing * 2
bottomPadding: Kirigami.Units.largeSpacing * 2
standardButtons: Kirigami.Dialog.NoButton standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24) width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title:menu Account details dialog", "Account Details") title: i18nc("@title:menu Account details dialog", "Account Details")
header: null
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: 0 spacing: 0
RowLayout { RowLayout {
id: detailRow id: detailRow
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
Layout.fillWidth: true
KirigamiComponents.Avatar { KirigamiComponents.Avatar {
id: avatar id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge Layout.preferredWidth: Kirigami.Units.iconSizes.huge
@@ -75,242 +91,285 @@ Kirigami.Dialog {
id: idLabel id: idLabel
textFormat: TextEdit.PlainText textFormat: TextEdit.PlainText
text: idLabelTextMetrics.elidedText text: idLabelTextMetrics.elidedText
color: Kirigami.Theme.disabledTextColor
TextMetrics { TextMetrics {
id: idLabelTextMetrics id: idLabelTextMetrics
text: root.user.id text: root.user.id
elide: Qt.ElideRight elide: Qt.ElideRight
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin elideWidth: root.availableWidth - avatar.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
} }
} }
QQC2.Label { Kirigami.ActionToolBar {
property CommonRoomsModel model: CommonRoomsModel {
connection: root.connection
userId: root.user.id
}
text: i18ncp("@info", "One mutual room", "%1 mutual rooms", model.count)
color: Kirigami.Theme.disabledTextColor
visible: model.count > 0
Layout.topMargin: Kirigami.Units.smallSpacing Layout.topMargin: Kirigami.Units.smallSpacing
actions: [
Kirigami.Action {
text: i18nc("@action:intoolbar Message this user directly", "Message")
icon.name: "document-send-symbolic"
onTriggered: {
root.close();
root.connection.requestDirectChat(root.user.id);
}
},
Kirigami.Action {
icon.name: "im-invisible-user-symbolic"
text: root.connection.isIgnored(root.user.id) ? i18nc("@action:intoolbar Unignore or 'unblock' this user", "Unignore") : i18nc("@action:intoolbar Ignore or 'block' this user", "Ignore")
onTriggered: {
root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar Copy shareable link for this user", "Copy Link")
icon.name: "username-copy-symbolic"
onTriggered: Clipboard.saveText(root.shareUrl)
},
Kirigami.Action {
text: i18nc("@action:intoolbar Search for this user's messages.", "Search Messages…")
icon.name: "search-symbolic"
onTriggered: {
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.room,
senderId: root.user.id
}, {
title: i18nc("@action:title", "Search")
});
root.close();
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
onTriggered: {
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: root.shareUrl,
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
subtitle: root.user.id,
avatarColor: root.room?.member(root.user.id).color,
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
}) as QrCodeMaximizeComponent;
root.close();
qrCode.open();
}
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…")
icon.name: "dialog-warning-symbolic"
visible: root.connection.supportsMatrixSpecVersion("v1.13")
onTriggered: {
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Report User"),
placeholder: i18nc("@info:placeholder", "Reason for reporting this user"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report")
}, {
title: i18nc("@title", "Report User"),
width: Kirigami.Units.gridUnit * 25
}) as ReasonDialog;
dialog.accepted.connect(reason => {
root.connection.reportUser(root.user.id, reason);
});
}
}
]
} }
} }
QQC2.AbstractButton { }
id: qrButton
Layout.minimumHeight: avatar.height * 0.75
Layout.maximumHeight: avatar.height * 1.5
Layout.maximumWidth: avatar.height * 1.5
contentItem: Barcode { Kirigami.Heading {
id: barcode text: i18nc("@title Moderation actions for this user", "Moderation")
barcodeType: Barcode.QRCode level: 2
content: "https://matrix.to/#/" + root.user.id visible: root.isRoomProfile && moderationToolbar.actions.filter(function (it) { return it.visible; }).length > 0
}
onClicked: { Layout.topMargin: Kirigami.Units.largeSpacing
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, { }
text: barcode.content,
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName, Kirigami.ActionToolBar {
subtitle: root.user.id, id: moderationToolbar
avatarColor: root.room?.member(root.user.id).color,
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl flat: false
}) as QrCodeMaximizeComponent; visible: root.isRoomProfile
root.close();
qrCode.open(); Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
actions: [
Kirigami.Action {
visible: !root.isSelf && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button Kick the user from the room", "Kick…")
icon.name: "im-kick-user"
onTriggered: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Kick User"),
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
icon: "im-kick-user"
}, {
title: i18nc("@title:dialog", "Kick User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.kickMember(root.user.id, reason);
});
root.close();
}
},
Kirigami.Action {
visible: !root.isSelf && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button Ban this user from the room", "Ban…")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user"
}, {
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
},
Kirigami.Action {
visible: !root.isSelf && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id)
text: i18nc("@action:button Unban the user from this room", "Unban")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
root.room.unban(root.user.id);
root.close();
}
},
Kirigami.Action {
visible: (root.user.id === root.connection.localUserId || root.room.canSendState("redact"))
text: i18nc("@action:button Remove messages from the user in this room", "Remove Messages…")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Messages"),
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.deleteMessagesByUser(root.user.id, reason);
});
root.close();
}
} }
]
}
Kirigami.Heading {
text: i18nc("@title Role such as 'Admin' or 'Moderator' for this user", "Role")
level: 2
visible: root.isRoomProfile
Layout.topMargin: Kirigami.Units.largeSpacing
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
visible: root.isRoomProfile
Layout.topMargin: Kirigami.Units.smallSpacing
QQC2.Label {
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
}
QQC2.Button {
visible: root.room.canSendState("m.room.power_levels")
text: i18nc("@action:button Set the power level (such as 'Admin') for this user", "Set Power Level")
icon.name: "document-edit-symbolic"
display: QQC2.AbstractButton.IconOnly
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: barcode.content QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
Kirigami.Chip { onClicked: {
visible: root.room (powerLevelDialog.createObject(this, {
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : "" room: root.room,
closable: false userId: root.user.id,
checkable: false powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
}) as PowerLevelDialog).open();
root.close();
}
Layout.leftMargin: Kirigami.Units.largeSpacing Component {
Layout.bottomMargin: Kirigami.Units.largeSpacing
}
Kirigami.Separator {
Layout.fillWidth: true
}
FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId && !!root.user
text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user")
icon.name: "im-invisible-user"
onClicked: {
root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Kick this user")
icon.name: "im-kick-user"
onClicked: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Kick User"),
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
icon: "im-kick-user"
}, {
title: i18nc("@title:dialog", "Kick User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.kickMember(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("invite") && !root.room.containsUser(root.user.id)
enabled: root.room && !root.room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Invite this user")
icon.name: "list-add-user"
onClicked: {
root.room.inviteToRoom(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Ban this user")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user"
}, {
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Unban this user")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
root.room.unban(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.room.canSendState("m.room.power_levels")
text: i18nc("@action:button", "Set user power level")
icon.name: "visibility"
onClicked: {
(powerLevelDialog.createObject(this, {
room: root.room,
userId: root.user.id,
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
}) as PowerLevelDialog).open();
root.close();
}
Component {
id: powerLevelDialog
PowerLevelDialog {
id: powerLevelDialog id: powerLevelDialog
PowerLevelDialog {
id: powerLevelDialog
}
} }
} }
} }
FormCard.FormButtonDelegate { Kirigami.Heading {
visible: root.room && (root.user.id === root.connection.localUserId || root.room.canSendState("redact")) text: i18nc("@title The set of common rooms between your current user and the one shown", "Mutual Rooms")
level: 4
visible: !root.isSelf && root.hasMutualRooms
text: i18nc("@action:button", "Remove recent messages by this user") Layout.topMargin: Kirigami.Units.largeSpacing
icon.name: "delete" }
icon.color: Kirigami.Theme.negativeTextColor
onClicked: { RowLayout {
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { spacing: Kirigami.Units.smallSpacing
title: i18nc("@title:dialog", "Remove Messages"), visible: !root.isSelf && root.hasMutualRooms
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"), Layout.topMargin: Kirigami.Units.smallSpacing
icon: "delete"
}, { Repeater {
title: i18nc("@title", "Remove Messages"), model: root.limiterModel
width: Kirigami.Units.gridUnit * 25
}); delegate: KirigamiComponents.AvatarButton {
dialog.accepted.connect(reason => { required property string roomName
root.room.deleteMessagesByUser(root.user.id, reason); required property string roomAvatar
}); required property string roomId
root.close();
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
name: roomName
source: roomAvatar
onClicked: {
root.close();
RoomManager.resolveResource(roomId);
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: name
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
} }
}
FormCard.FormButtonDelegate { QQC2.Label {
visible: root.user.id !== root.connection.localUserId text: i18ncp("@info:label And '%1' more rooms you have in common with this user, but are not shown", "and 1 more…", "and %1 more…", root.limiterModel.extraCount)
text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat") visible: root.limiterModel.extraCount > 0
icon.name: "document-send" color: Kirigami.Theme.disabledTextColor
onClicked: {
root.connection.requestDirectChat(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName))
icon.name: "search-symbolic"
onClicked: {
((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.room,
senderId: root.user.id
}, {
title: i18nc("@action:title", "Search")
});
root.close();
}
}
FormCard.FormButtonDelegate {
text: i18n("Copy link")
icon.name: "username-copy"
onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id)
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…")
icon.name: "dialog-warning-symbolic"
visible: root.connection.supportsMatrixSpecVersion("v1.13")
onClicked: {
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Report User"),
placeholder: i18nc("@info:placeholder", "Reason for reporting this user"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report")
}, {
title: i18nc("@title", "Report User"),
width: Kirigami.Units.gridUnit * 25
}) as ReasonDialog;
dialog.accepted.connect(reason => {
root.connection.reportUser(root.user.id, reason);
});
} }
} }
} }