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