From d059195e9202abf3457dfeb5fad05f9f4f5ebbee Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 15 May 2025 15:01:52 -0400 Subject: [PATCH] Add user menu that is opened via right-clicking/long-tapped on avatar We can un-clutter our message context menu, which we had to share with user actions. (Even though we only had one so far.) I added one new user-specific action which allows you to quickly mention the user in chat. Otherwise you would've had to copy their username or use the completion menu. It's convergent on mobile, it still has the hover indicator and it also is available through the AuthorComponent. BUG: 486252 --- src/app/CMakeLists.txt | 1 + src/app/qml/UserMenu.qml | 35 ++++++++++++++ src/chatbar/ChatBar.qml | 13 ++++++ src/libneochat/chatbarcache.h | 1 + src/timeline/AuthorComponent.qml | 52 ++++++++++++--------- src/timeline/DelegateContextMenu.qml | 8 ---- src/timeline/FileDelegateContextMenu.qml | 2 - src/timeline/MessageDelegate.qml | 32 ++++++++++++- src/timeline/MessageDelegateContextMenu.qml | 1 - 9 files changed, 111 insertions(+), 34 deletions(-) create mode 100644 src/app/qml/UserMenu.qml diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 8d6413958..bcc79b4ed 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -101,6 +101,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE qml/AvatarNotification.qml qml/ReasonDialog.qml qml/NewPollDialog.qml + qml/UserMenu.qml DEPENDENCIES QtCore QtQuick diff --git a/src/app/qml/UserMenu.qml b/src/app/qml/UserMenu.qml new file mode 100644 index 000000000..3986c821a --- /dev/null +++ b/src/app/qml/UserMenu.qml @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Joshua Goins +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts + +import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.components as KirigamiComponents + +import org.kde.neochat +import org.kde.neochat.settings +import org.kde.neochat.devtools + +KirigamiComponents.ConvergentContextMenu { + id: root + + required property NeoChatConnection connection + required property Kirigami.ApplicationWindow window + required property var author + + QQC2.Action { + text: i18nc("@action:button", "Open Profile") + icon.name: "im-user-symbolic" + onTriggered: RoomManager.resolveResource(root.author.uri) + } + + QQC2.Action { + text: i18nc("@action:button", "Mention") + icon.name: "username-copy-symbolic" + onTriggered: { + RoomManager.currentRoom.mainCache.mentionAdded(root.author.id); + } + } +} diff --git a/src/chatbar/ChatBar.qml b/src/chatbar/ChatBar.qml index 93af4be30..80abde9a0 100644 --- a/src/chatbar/ChatBar.qml +++ b/src/chatbar/ChatBar.qml @@ -55,6 +55,19 @@ QQC2.Control { } } + Connections { + target: currentRoom.mainCache + + function onMentionAdded(mention: string): void { + // add mention text + textField.append(mention + " "); + // move cursor to the end + textField.cursorPosition = textField.text.length; + // move the focus back to the chat bar + textField.forceActiveFocus(Qt.OtherFocusReason); + } + } + /** * @brief The list of actions in the ChatBar. * diff --git a/src/libneochat/chatbarcache.h b/src/libneochat/chatbarcache.h index dd369351d..be89e36f1 100644 --- a/src/libneochat/chatbarcache.h +++ b/src/libneochat/chatbarcache.h @@ -195,6 +195,7 @@ Q_SIGNALS: void relationIdChanged(const QString &oldEventId, const QString &newEventId); void threadIdChanged(const QString &oldThreadId, const QString &newThreadId); void attachmentPathChanged(); + void mentionAdded(const QString &mention); private: QString m_text = QString(); diff --git a/src/timeline/AuthorComponent.qml b/src/timeline/AuthorComponent.qml index e8df80f21..7f2a4aacc 100644 --- a/src/timeline/AuthorComponent.qml +++ b/src/timeline/AuthorComponent.qml @@ -41,21 +41,43 @@ RowLayout { implicitHeight: Math.max(nameButton.implicitHeight, timeLabel.implicitHeight) - QQC2.AbstractButton { + QQC2.Label { id: nameButton - contentItem: QQC2.Label { - text: root.author.disambiguatedName - color: root.author.color - textFormat: Text.PlainText - font.weight: Font.Bold - elide: Text.ElideRight + + text: root.author.disambiguatedName + color: root.author.color + textFormat: Text.PlainText + font.weight: Font.Bold + elide: Text.ElideRight + + function openUserMenu(): void { + const menu = Qt.createComponent("org.kde.neochat", "UserMenu").createObject(root, { + connection: root.connection, + window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow, + author: root.author, + }); + menu.popup(root); } - Accessible.name: contentItem.text - onClicked: RoomManager.resolveResource(root.author.uri) HoverHandler { cursorShape: Qt.PointingHandCursor } + + // tapping to open profile + TapHandler { + onTapped: RoomManager.resolveResource(root.author.uri) + } + + // right-clicking/long-press for context menu + TapHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus + acceptedButtons: Qt.RightButton + onTapped: nameButton.openUserMenu() + } + TapHandler { + acceptedDevices: PointerDevice.TouchScreen + onTapped: nameButton.openUserMenu() + } } Item { Layout.fillWidth: true @@ -73,16 +95,4 @@ RowLayout { id: timeHoverHandler } } - - TapHandler { - acceptedButtons: Qt.LeftButton - acceptedDevices: PointerDevice.TouchScreen - onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink); - } - TapHandler { - acceptedButtons: Qt.RightButton - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus - gesturePolicy: TapHandler.WithinBounds - onTapped: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink); - } } diff --git a/src/timeline/DelegateContextMenu.qml b/src/timeline/DelegateContextMenu.qml index 3fb132f8f..bc405554b 100644 --- a/src/timeline/DelegateContextMenu.qml +++ b/src/timeline/DelegateContextMenu.qml @@ -142,14 +142,6 @@ KirigamiComponents.ConvergentContextMenu { } } - component ShowUserAction: Kirigami.Action { - text: i18nc("@action:inmenu", "Show User") - icon.name: "username-copy" - onTriggered: { - RoomManager.resolveResource(author.id) - } - } - component PinMessageAction: Kirigami.Action { readonly property bool pinned: currentRoom.isEventPinned(root.eventId) diff --git a/src/timeline/FileDelegateContextMenu.qml b/src/timeline/FileDelegateContextMenu.qml index d29fc7965..994eb4d56 100644 --- a/src/timeline/FileDelegateContextMenu.qml +++ b/src/timeline/FileDelegateContextMenu.qml @@ -94,8 +94,6 @@ DelegateContextMenu { DelegateContextMenu.ReportMessageAction {} - DelegateContextMenu.ShowUserAction {} - Kirigami.Action { separator: true visible: viewSourceAction.visible diff --git a/src/timeline/MessageDelegate.qml b/src/timeline/MessageDelegate.qml index 12a7d03de..cf3e1927c 100644 --- a/src/timeline/MessageDelegate.qml +++ b/src/timeline/MessageDelegate.qml @@ -149,6 +149,7 @@ MessageDelegateBase { TapHandler { acceptedButtons: Qt.RightButton + gesturePolicy: TapHandler.ReleaseWithinBounds onTapped: _private.showMessageMenu() } @@ -159,7 +160,7 @@ MessageDelegateBase { } } - avatarComponent: KirigamiComponents.AvatarButton { + avatarComponent: KirigamiComponents.Avatar { id: avatar implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 implicitHeight: width @@ -170,7 +171,34 @@ MessageDelegateBase { asynchronous: true QQC2.ToolTip.text: root.author.htmlSafeDisambiguatedName - onClicked: RoomManager.resolveResource(root.author.uri) + function openUserMenu(): void { + const menu = Qt.createComponent("org.kde.neochat", "UserMenu").createObject(root, { + connection: root.connection, + window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow, + author: root.author, + }); + menu.popup(root); + } + + HoverHandler { + cursorShape: Qt.PointingHandCursor + } + + // tapping to open profile + TapHandler { + onTapped: RoomManager.resolveResource(root.author.uri) + } + + // right-clicking/long-press for context menu + TapHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus + acceptedButtons: Qt.RightButton + onTapped: avatar.openUserMenu() + } + TapHandler { + acceptedDevices: PointerDevice.TouchScreen + onTapped: avatar.openUserMenu() + } } sectionComponent: SectionDelegate { diff --git a/src/timeline/MessageDelegateContextMenu.qml b/src/timeline/MessageDelegateContextMenu.qml index d7adf2a70..c22e35001 100644 --- a/src/timeline/MessageDelegateContextMenu.qml +++ b/src/timeline/MessageDelegateContextMenu.qml @@ -102,7 +102,6 @@ DelegateContextMenu { } DelegateContextMenu.PinMessageAction {} DelegateContextMenu.ReportMessageAction {} - DelegateContextMenu.ShowUserAction {} Kirigami.Action { separator: true visible: viewSourceAction.visible