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 }