Warning, /network/neochat/src/qml/MessageDelegate.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
0002 // SPDX-License-Identifier: GPL-3.0-only
0003 
0004 import QtQuick
0005 import QtQuick.Controls as QQC2
0006 import QtQuick.Layouts
0007 import Qt.labs.qmlmodels
0008 
0009 import org.kde.kirigami as Kirigami
0010 import org.kde.kirigamiaddons.components as KirigamiComponents
0011 
0012 import org.kde.neochat
0013 import org.kde.neochat.config
0014 
0015 /**
0016  * @brief The base delegate for all messages in the timeline.
0017  *
0018  * This supports a message bubble plus sender avatar for each message as well as reactions
0019  * and read markers. A date section can be show for when the message is on a different
0020  * day to the previous one.
0021  *
0022  * The component is designed for all messages, positioning them in the timeline with
0023  * variable padding depending on the window width. Local user messages are highlighted
0024  * and can also be aligned to the right if configured.
0025  *
0026  * This component also supports a compact mode where the padding is adjusted, the
0027  * background is hidden and the delegate spans the full width of the timeline.
0028  */
0029 TimelineDelegate {
0030     id: root
0031 
0032     /**
0033      * @brief The index of the delegate in the model.
0034      */
0035     required property var index
0036 
0037     /**
0038      * @brief The matrix ID of the message event.
0039      */
0040     required property string eventId
0041 
0042     /**
0043      * @brief The timestamp of the message.
0044      */
0045     required property var time
0046 
0047     /**
0048      * @brief The timestamp of the message as a string.
0049      */
0050     required property string timeString
0051 
0052     /**
0053      * @brief The message author.
0054      *
0055      * This should consist of the following:
0056      *  - id - The matrix ID of the author.
0057      *  - isLocalUser - Whether the author is the local user.
0058      *  - avatarSource - The mxc URL for the author's avatar in the current room.
0059      *  - avatarMediaId - The media ID of the author's avatar.
0060      *  - avatarUrl - The mxc URL for the author's avatar.
0061      *  - displayName - The display name of the author.
0062      *  - display - The name of the author.
0063      *  - color - The color for the author.
0064      *  - object - The Quotient::User object for the author.
0065      *
0066      * @sa Quotient::User
0067      */
0068     required property var author
0069 
0070     /**
0071      * @brief Whether the author should be shown.
0072      */
0073     required property bool showAuthor
0074 
0075     /**
0076      * @brief Whether the author should always be shown.
0077      *
0078      * This is primarily used when these delegates are used in a filtered list of
0079      * events rather than a sequential timeline, e.g. the media model view.
0080      *
0081      * @note This setting still respects the avatar configuration settings.
0082      */
0083     property bool alwaysShowAuthor: false
0084 
0085     /**
0086      * @brief The delegate type of the message.
0087      */
0088     required property int delegateType
0089 
0090     /**
0091      * @brief The display text of the message.
0092      */
0093     required property string display
0094 
0095     /**
0096      * @brief The display text of the message as plain text.
0097      */
0098     required property string plainText
0099 
0100     /**
0101      * @brief The date of the event as a string.
0102      */
0103     required property string section
0104 
0105     /**
0106      * @brief Whether the section header should be shown.
0107      */
0108     required property bool showSection
0109 
0110     /**
0111      * @brief A model with the reactions to the message in.
0112      */
0113     required property var reaction
0114 
0115     /**
0116      * @brief Whether the reaction component should be shown.
0117      */
0118     required property bool showReactions
0119 
0120     /**
0121      * @brief A model with the first 5 other user read markers for this message.
0122      */
0123     required property var readMarkers
0124 
0125     /**
0126      * @brief String with the display name and matrix ID of the other user read markers.
0127      */
0128     required property string readMarkersString
0129 
0130     /**
0131      * @brief The number of other users at the event after the first 5.
0132      */
0133     required property var excessReadMarkers
0134 
0135     /**
0136      * @brief Whether the other user read marker component should be shown.
0137      */
0138     required property bool showReadMarkers
0139 
0140     /**
0141      * @brief The matrix ID of the reply event.
0142      */
0143     required property var replyId
0144 
0145     /**
0146      * @brief The reply author.
0147      *
0148      * This should consist of the following:
0149      *  - id - The matrix ID of the reply author.
0150      *  - isLocalUser - Whether the reply author is the local user.
0151      *  - avatarSource - The mxc URL for the reply author's avatar in the current room.
0152      *  - avatarMediaId - The media ID of the reply author's avatar.
0153      *  - avatarUrl - The mxc URL for the reply author's avatar.
0154      *  - displayName - The display name of the reply author.
0155      *  - display - The name of the reply author.
0156      *  - color - The color for the reply author.
0157      *  - object - The Quotient::User object for the reply author.
0158      *
0159      * @sa Quotient::User
0160      */
0161     required property var replyAuthor
0162 
0163     /**
0164      * @brief The delegate type of the message replied to.
0165      */
0166     required property int replyDelegateType
0167 
0168     /**
0169      * @brief The display text of the message replied to.
0170      */
0171     required property string replyDisplay
0172 
0173     /**
0174      * @brief The media info for the reply event.
0175      *
0176      * This could be an image, audio, video or file.
0177      *
0178      * This should consist of the following:
0179      *  - source - The mxc URL for the media.
0180      *  - mimeType - The MIME type of the media.
0181      *  - mimeIcon - The MIME icon name.
0182      *  - size - The file size in bytes.
0183      *  - duration - The length in seconds of the audio media (audio/video only).
0184      *  - width - The width in pixels of the audio media (image/video only).
0185      *  - height - The height in pixels of the audio media (image/video only).
0186      *  - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads (image/video only).
0187      */
0188     required property var replyMediaInfo
0189 
0190     required property bool isThreaded
0191 
0192     required property string threadRoot
0193 
0194     /**
0195      * @brief Whether this message is replying to another.
0196      */
0197     required property bool isReply
0198 
0199     /**
0200      * @brief Whether this message has a local user mention.
0201      */
0202     required property bool isHighlighted
0203 
0204     /**
0205      * @brief Whether an event is waiting to be accepted by the server.
0206      */
0207     required property bool isPending
0208 
0209     /**
0210      * @brief Progress info when downloading files.
0211      *
0212      * @sa Quotient::FileTransferInfo
0213      */
0214     required property var progressInfo
0215 
0216     /**
0217      * @brief Whether an encrypted message is sent in a verified session.
0218      */
0219     required property bool verified
0220 
0221     /**
0222      * @brief The x position of the message bubble.
0223      *
0224      * @note Used for positioning the hover actions.
0225      */
0226     readonly property real bubbleX: bubble.x + bubble.anchors.leftMargin
0227 
0228     /**
0229      * @brief The y position of the message bubble.
0230      *
0231      * @note Used for positioning the hover actions.
0232      */
0233     readonly property alias bubbleY: mainContainer.y
0234 
0235     /**
0236      * @brief The width of the message bubble.
0237      *
0238      * @note Used for sizing the hover actions.
0239      */
0240     readonly property alias bubbleWidth: bubble.width
0241 
0242     /**
0243      * @brief Whether this message is hovered.
0244      */
0245     readonly property alias hovered: bubble.hovered
0246 
0247     required property NeoChatConnection connection
0248 
0249     /**
0250      * @brief Open the context menu for the message.
0251      */
0252     signal openContextMenu
0253 
0254     /**
0255      * @brief Open the any message media externally.
0256      */
0257     signal openExternally()
0258 
0259     /**
0260      * @brief The reply has been clicked.
0261      */
0262     signal replyClicked(string eventID)
0263     onReplyClicked: eventID => ListView.view.goToEvent(eventID)
0264 
0265     /**
0266      * @brief The main delegate content item to show in the bubble.
0267      */
0268     property alias bubbleContent: bubble.content
0269 
0270     /**
0271      * @brief Whether the bubble background is enabled.
0272      */
0273     property bool cardBackground: true
0274 
0275     /**
0276      * @brief Whether the delegate should always stretch to the maximum availabel width.
0277      */
0278     property bool alwaysMaxWidth: false
0279 
0280     /**
0281      * @brief Whether the message should be highlighted.
0282      */
0283     property bool showHighlight: root.isHighlighted || isTemporaryHighlighted
0284 
0285     /**
0286      * @brief Whether the message should temporarily be highlighted.
0287      *
0288      * Normally triggered when jumping to the event in the timeline, e.g. when a reply
0289      * is clicked.
0290      */
0291     property bool isTemporaryHighlighted: false
0292 
0293     onIsTemporaryHighlightedChanged: if (isTemporaryHighlighted) temporaryHighlightTimer.start()
0294 
0295     Timer {
0296         id: temporaryHighlightTimer
0297 
0298         interval: 1500
0299         onTriggered: isTemporaryHighlighted = false
0300     }
0301 
0302     /**
0303      * @brief The width available to the bubble content.
0304      */
0305     property real contentMaxWidth: bubbleSizeHelper.currentWidth - bubble.leftPadding - bubble.rightPadding
0306 
0307     contentItem: ColumnLayout {
0308         spacing: Kirigami.Units.smallSpacing
0309 
0310         SectionDelegate {
0311             id: sectionDelegate
0312             Layout.fillWidth: true
0313             visible: root.showSection
0314             labelText: root.section
0315             colorSet: Config.compactLayout || root.alwaysMaxWidth ? Kirigami.Theme.View : Kirigami.Theme.Window
0316         }
0317         QQC2.ItemDelegate {
0318             id: mainContainer
0319 
0320             Layout.fillWidth: true
0321             Layout.topMargin: root.showAuthor || root.alwaysShowAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
0322             Layout.leftMargin: Kirigami.Units.smallSpacing
0323             Layout.rightMargin: Kirigami.Units.smallSpacing
0324 
0325             implicitHeight: Math.max(root.showAuthor || root.alwaysShowAuthor ? avatar.implicitHeight : 0, bubble.height)
0326 
0327             Component.onCompleted: {
0328                 if (root.isReply && root.replyDelegateType === DelegateType.Other) {
0329                     currentRoom.loadReply(root.eventId, root.replyId)
0330                 }
0331             }
0332 
0333             // show hover actions
0334             onHoveredChanged: {
0335                 if (hovered && !Kirigami.Settings.isMobile) {
0336                     root.setHoverActionsToDelegate()
0337                 }
0338             }
0339 
0340             KirigamiComponents.AvatarButton {
0341                 id: avatar
0342                 width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2: 0
0343                 height: width
0344                 anchors {
0345                     left: parent.left
0346                     leftMargin: Kirigami.Units.smallSpacing
0347                     top: parent.top
0348                     topMargin: Kirigami.Units.smallSpacing
0349                 }
0350 
0351                 visible: (root.showAuthor || root.alwaysShowAuthor) &&
0352                     Config.showAvatarInTimeline &&
0353                     (Config.compactLayout || !_private.showUserMessageOnRight)
0354                 name: root.author.displayName
0355                 source: root.author.avatarSource
0356                 color: root.author.color
0357                 QQC2.ToolTip.text: root.author.escapedDisplayName
0358 
0359                 onClicked: RoomManager.resolveResource(root.author.id, "mention")
0360             }
0361             Bubble {
0362                 id: bubble
0363                 anchors.left: avatar.right
0364                 anchors.leftMargin: Kirigami.Units.largeSpacing
0365                 anchors.rightMargin: Kirigami.Units.largeSpacing
0366                 maxContentWidth: root.contentMaxWidth
0367 
0368                 topPadding: Config.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
0369                 bottomPadding: Config.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
0370                 leftPadding: Config.compactLayout ? 0 : Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0371                 rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0372 
0373                 state: _private.showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
0374                 // states for anchor animations on window resize
0375                 // as setting anchors to undefined did not work reliably
0376                 states: [
0377                     State {
0378                         name: "userMessageOnRight"
0379                         AnchorChanges {
0380                             target: bubble
0381                             anchors.left: undefined
0382                             anchors.right: parent.right
0383                         }
0384                     },
0385                     State {
0386                         name: "userMessageOnLeft"
0387                         AnchorChanges {
0388                             target: bubble
0389                             anchors.left: avatar.right
0390                             anchors.right: undefined
0391                         }
0392                     }
0393                 ]
0394 
0395                 author: root.author
0396                 showAuthor: root.showAuthor || root.alwaysShowAuthor
0397                 time: root.time
0398                 timeString: root.timeString
0399 
0400                 showHighlight: root.showHighlight
0401 
0402                 isReply: root.isReply
0403                 replyId: root.replyId
0404                 replyAuthor: root.replyAuthor
0405                 replyDelegateType: root.replyDelegateType
0406                 replyDisplay: root.replyDisplay
0407                 replyMediaInfo: root.replyMediaInfo
0408 
0409                 onReplyClicked: (eventId) => {root.replyClicked(eventId)}
0410 
0411                 showBackground: root.cardBackground && !Config.compactLayout
0412             }
0413 
0414             background: Rectangle {
0415                 visible: mainContainer.hovered && (Config.compactLayout || root.alwaysMaxWidth)
0416                 color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
0417                 radius: Kirigami.Units.smallSpacing
0418             }
0419 
0420             TapHandler {
0421                 acceptedButtons: Qt.RightButton
0422                 onTapped: root.openContextMenu()
0423             }
0424 
0425             TapHandler {
0426                 acceptedButtons: Qt.LeftButton
0427                 onLongPressed: root.openContextMenu()
0428             }
0429         }
0430 
0431         ReactionDelegate {
0432             Layout.maximumWidth: root.width - Kirigami.Units.largeSpacing * 2
0433             Layout.alignment: _private.showUserMessageOnRight ? Qt.AlignRight : Qt.AlignLeft
0434             Layout.leftMargin: _private.showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
0435             Layout.rightMargin: _private.showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
0436 
0437             visible: root.showReactions
0438             model: root.reaction
0439 
0440             onReactionClicked: (reaction) => currentRoom.toggleReaction(root.eventId, reaction)
0441         }
0442         AvatarFlow {
0443             Layout.alignment: Qt.AlignRight
0444             Layout.rightMargin: Kirigami.Units.largeSpacing
0445             visible: root.showReadMarkers
0446             model: root.readMarkers
0447             toolTipText: root.readMarkersString
0448             excessAvatars: root.excessReadMarkers
0449         }
0450 
0451         DelegateSizeHelper {
0452             id: bubbleSizeHelper
0453             startBreakpoint: Kirigami.Units.gridUnit * 25
0454             endBreakpoint: Kirigami.Units.gridUnit * 40
0455             startPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 90
0456             endPercentWidth: Config.compactLayout || root.alwaysMaxWidth ? 100 : 60
0457 
0458             parentWidth: mainContainer.availableWidth - (Config.showAvatarInTimeline ? avatar.width + bubble.anchors.leftMargin : 0)
0459         }
0460     }
0461 
0462     function isVisibleInTimeline() {
0463             let yoff = Math.round(y - ListView.view.contentY);
0464             return (yoff + height > 0 && yoff < ListView.view.height)
0465     }
0466 
0467     function setHoverActionsToDelegate() {
0468         if (ListView.view.setHoverActionsToDelegate) {
0469             ListView.view.setHoverActionsToDelegate(root)
0470         }
0471     }
0472 
0473     QtObject {
0474         id: _private
0475 
0476         /**
0477          * @brief Whether local user messages should be aligned right.
0478          */
0479         property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalUser && !Config.compactLayout && !root.alwaysMaxWidth
0480     }
0481 }