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 }