diff --git a/src/qml/Component/HoverActions.qml b/src/qml/Component/HoverActions.qml new file mode 100644 index 000000000..aebf5ce43 --- /dev/null +++ b/src/qml/Component/HoverActions.qml @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2023 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.15 as Kirigami + +/** + * @brief A component that provides a set of actions when a message is hovered in the timeline. + * + * There is also an icon to show that a message has come from a verified device in + * encrypted chats. + */ +QQC2.Control { + id: root + + /** + * @brief Whether the actions should be shown. + */ + property bool showActions: false + + /** + * @brief Whether the message has been sent from a verified matrix session. + */ + property bool verified: false + + /** + * @brief Whether the edit button should be shown. + */ + property bool editable: false + + /** + * @brief The react button has been clicked. + */ + signal reactClicked(string emoji) + + /** + * @brief The edit button has been clicked. + */ + signal editClicked() + + /** + * @brief The reply button has been clicked. + */ + signal replyClicked() + + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + + visible: (root.hovered || root.showActions || showActionsTimer.running) && !Kirigami.Settings.isMobile + onVisibleChanged: { + if (visible) { + // HACK: delay disapearing by 200ms, otherwise this can create some glitches + // See https://invent.kde.org/network/neochat/-/issues/333 + showActionsTimer.restart() + } + } + Timer { + id: showActionsTimer + interval: 200 + } + + contentItem: RowLayout { + Item { + Layout.fillWidth: true + } + Kirigami.Icon { + source: "security-high" + width: height + height: root.height + visible: root.verified + HoverHandler { + id: hover + } + QQC2.ToolTip.text: i18n("This message was sent from a verified device") + QQC2.ToolTip.visible: hover.hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + Kirigami.ActionToolBar { + Layout.maximumWidth: maximumContentWidth + Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + alignment: Qt.AlignRight + flat: false + display: QQC2.Button.IconOnly + + actions: [ + Kirigami.Action { + text: i18n("React") + icon.name: "preferences-desktop-emoticons" + onTriggered: emojiDialog.open() + }, + Kirigami.Action { + text: i18n("Edit") + icon.name: "document-edit" + onTriggered: root.editClicked() + visible: root.editable + }, + Kirigami.Action { + text: i18n("Reply") + icon.name: "mail-replied-symbolic" + onTriggered: root.replyClicked() + } + ] + + EmojiDialog { + id: emojiDialog + showQuickReaction: true + onChosen: (emoji) => root.reactClicked(emoji) + } + } + } +} diff --git a/src/qml/Component/Timeline/TimelineContainer.qml b/src/qml/Component/Timeline/TimelineContainer.qml index 3138bf3b6..bd3b435e1 100644 --- a/src/qml/Component/Timeline/TimelineContainer.qml +++ b/src/qml/Component/Timeline/TimelineContainer.qml @@ -12,6 +12,19 @@ import org.kde.neochat 1.0 ColumnLayout { id: root + property string eventId: model.eventId + + property var author: model.author + + property int delegateType: model.delegateType + + property bool verified: model.verified + + readonly property real bubbleX: bubble.x + bubble.anchors.leftMargin + readonly property alias bubbleY: mainContainer.y + readonly property alias bubbleWidth: bubble.width + readonly property alias hovered: bubble.hovered + signal openContextMenu signal openExternally() signal replyClicked(string eventID) @@ -99,21 +112,7 @@ ColumnLayout { // show hover actions onHoveredChanged: { if (hovered && !Kirigami.Settings.isMobile) { - updateHoverComponent(); - } - } - - - // Show hover actions by updating the global hover component to this delegate - function updateHoverComponent() { - if (!hoverComponent) { - return; - } - if (hovered && !Kirigami.Settings.isMobile) { - hoverComponent.delegate = root - hoverComponent.bubble = bubble - hoverComponent.event = model - hoverComponent.updateFunction = updateHoverComponent; + root.setHoverActionsToDelegate() } } @@ -280,7 +279,6 @@ ColumnLayout { } } - background: Item { Kirigami.ShadowedRectangle { id: bubbleBackground @@ -382,4 +380,8 @@ ColumnLayout { }); contextMenu.open(); } + + function setHoverActionsToDelegate() { + ListView.view.setHoverActionsToDelegate(root) + } } diff --git a/src/qml/Component/TimelineView.qml b/src/qml/Component/TimelineView.qml index e6c8f9d8f..cccbdad93 100644 --- a/src/qml/Component/TimelineView.qml +++ b/src/qml/Component/TimelineView.qml @@ -297,108 +297,39 @@ QQC2.ScrollView { itemAtIndex(index).isTemporaryHighlighted = true } - Item { + HoverActions { id: hoverActions - property var event: null - property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId - property bool showEdit: event && userMsg && (event.delegateType === MessageEventModel.Emote || event.delegateType === MessageEventModel.Message) + property var delegate: null - property var bubble: null - property var hovered: bubble && bubble.hovered - property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile - property var updateFunction - onVisibleDelayedChanged: if (visibleDelayed) { - visible = true; - } else { - // HACK: delay disapearing by 200ms, otherwise this can create some glitches - // See https://invent.kde.org/network/neochat/-/issues/333 - hoverActionsTimer.restart(); + + x: delegate ? delegate.x + delegate.bubbleX : 0 + y: delegate ? delegate.mapToItem(parent, 0, 0).y + delegate.bubbleY - height + Kirigami.Units.smallSpacing : 0 + width: delegate.bubbleWidth + + showActions: delegate && delegate.hovered + verified: delegate && delegate.verified + editable: delegate && delegate.author.isLocalUser && (delegate.delegateType === MessageEventModel.Emote || delegate.delegateType === MessageEventModel.Message) + + onReactClicked: (emoji) => { + root.currentRoom.toggleReaction(delegate.eventId, emoji); + if (!Kirigami.Settings.isMobile) { + root.focusChatBox(); + } } - Timer { - id: hoverActionsTimer - interval: 200 - onTriggered: hoverActions.visible = hoverActions.visibleDelayed - ; + onEditClicked: { + root.currentRoom.chatBoxEditId = delegate.eventId; + root.currentRoom.chatBoxReplyId = ""; } - - property int childOffset: userMsg && Config.showLocalMessagesOnRight && !Config.compactLayout ? (bubble ? bubble.width : 0) - childWidth : Math.max((bubble ? bubble.width : 0) - childWidth, 0) - x: delegate && bubble ? (delegate.x + bubble.x + Kirigami.Units.largeSpacing + childOffset - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 : 0) - (userMsg && !Config.compactLayout ? Kirigami.Units.gridUnit : 0)) : 0 - y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing : 0 - ; - - visible: false - - property alias childWidth: hoverActionsRow.width - property alias childHeight: hoverActionsRow.height - - RowLayout { - id: hoverActionsRow - z: 4 - spacing: 0 - HoverHandler { - id: hoverHandler - margin: Kirigami.Units.smallSpacing - } - Kirigami.Icon { - source: "security-high" - width: height - height: parent.height - visible: hoverActions.event ? hoverActions.event.verified : false - HoverHandler { - id: hover - } - QQC2.ToolTip.text: i18n("This message was sent from a verified device") - QQC2.ToolTip.visible: hover.hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - - QQC2.Button { - QQC2.ToolTip.text: i18n("React") - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - icon.name: "preferences-desktop-emoticons" - - onClicked: emojiDialog.open() - ; - EmojiDialog { - id: emojiDialog - showQuickReaction: true - onChosen: { - root.currentRoom.toggleReaction(hoverActions.event.eventId, emoji); - if (!Kirigami.Settings.isMobile) { - root.focusChatBox(); - } - } - } - } - QQC2.Button { - QQC2.ToolTip.text: i18n("Edit") - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - visible: hoverActions.showEdit - icon.name: "document-edit" - onClicked: { - root.currentRoom.chatBoxEditId = hoverActions.event.eventId; - root.currentRoom.chatBoxReplyId = ""; - } - } - QQC2.Button { - QQC2.ToolTip.text: i18n("Reply") - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - icon.name: "mail-replied-symbolic" - onClicked: { - root.currentRoom.chatBoxReplyId = hoverActions.event.eventId; - root.currentRoom.chatBoxEditId = ""; - root.focusChatBox(); - } - } + onReplyClicked: { + root.currentRoom.chatBoxReplyId = delegate.eventId; + root.currentRoom.chatBoxEditId = ""; + root.focusChatBox(); } } onContentYChanged: { - if (hoverActions.updateFunction) { - hoverActions.updateFunction(); + if (hoverActions.delegate) { + hoverActions.delegate.setHoverActionsToDelegate(); } } @@ -512,6 +443,10 @@ QQC2.ScrollView { root.currentRoom.markAllMessagesAsRead() } } + + function setHoverActionsToDelegate(delegate) { + hoverActions.delegate = delegate + } } function goToLastMessage() { diff --git a/src/res.qrc b/src/res.qrc index 851330f3b..9bddd3a25 100644 --- a/src/res.qrc +++ b/src/res.qrc @@ -32,6 +32,7 @@ qml/Component/TypingPane.qml qml/Component/ShimmerGradient.qml qml/Component/QuickSwitcher.qml + qml/Component/HoverActions.qml qml/Component/ChatBox/ChatBox.qml qml/Component/ChatBox/ChatBar.qml qml/Component/ChatBox/AttachmentPane.qml