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 }