Message Content Rework

For now everything should look identical. However this moves to using a model for the content of the message and is intended to lay the foundation for improved message content representation, e.g. splitting up a text message in multiple sections and using different delegates for things like code and quotes.
This commit is contained in:
James Graham
2024-02-18 09:53:08 +00:00
parent 0ebcacce69
commit b598584aea
52 changed files with 2515 additions and 2186 deletions

View File

@@ -5,6 +5,7 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.coreaddons
import org.kde.kirigami as Kirigami
@@ -23,6 +24,16 @@ import org.kde.neochat
RowLayout {
id: root
/**
* @brief The matrix ID of the reply event.
*/
required property var replyComponentType
/**
* @brief The matrix ID of the reply event.
*/
required property var replyEventId
/**
* @brief The reply author.
*
@@ -39,17 +50,12 @@ RowLayout {
*
* @sa Quotient::User
*/
required property var author
required property var replyAuthor
/**
* @brief The delegate type of the reply message.
* @brief The display text of the message replied to.
*/
required property int type
/**
* @brief The display text of the message.
*/
required property string display
required property string replyDisplay
/**
* @brief The media info for the reply event.
@@ -66,15 +72,19 @@ RowLayout {
* - height - The height in pixels of the audio media (image/video only).
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads (image/video only).
*/
required property var mediaInfo
required property var replyMediaInfo
property real contentMaxWidth
/**
* @brief The maximum width that the bubble's content can be.
*/
property real maxContentWidth: -1
/**
* @brief The reply has been clicked.
*/
signal replyClicked
signal replyClicked(string eventID)
implicitHeight: contentColumn.implicitHeight
spacing: Kirigami.Units.largeSpacing
Rectangle {
@@ -82,12 +92,17 @@ RowLayout {
Layout.fillHeight: true
implicitWidth: Kirigami.Units.smallSpacing
color: root.author.color
color: root.replyAuthor.color
}
ColumnLayout {
id: contentColumn
implicitHeight: headerRow.implicitHeight + (root.replyComponentType != MessageComponentType.Other ? contentRepeater.itemAt(0).implicitHeight + spacing : 0)
spacing: Kirigami.Units.smallSpacing
RowLayout {
id: headerRow
implicitHeight: Math.max(replyAvatar.implicitHeight, replyName.implicitHeight)
Layout.maximumWidth: root.maxContentWidth
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
@@ -96,42 +111,87 @@ RowLayout {
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: Kirigami.Units.iconSizes.small
source: root.author.avatarSource
name: root.author.displayName
color: root.author.color
source: root.replyAuthor.avatarSource
name: root.replyAuthor.displayName
color: root.replyAuthor.color
}
QQC2.Label {
id: replyName
Layout.fillWidth: true
color: root.author.color
text: root.author.displayName
color: root.replyAuthor.color
text: root.replyAuthor.displayName
elide: Text.ElideRight
}
}
Loader {
id: loader
Repeater {
id: contentRepeater
model: [root.replyComponentType]
delegate: DelegateChooser {
role: "modelData"
Layout.fillWidth: true
Layout.maximumHeight: loader.item && (root.type == DelegateType.Image || root.type == DelegateType.Sticker) ? loader.item.height : loader.item.implicitHeight
Layout.columnSpan: 2
DelegateChoice {
roleValue: MessageComponentType.Text
delegate: TextComponent {
display: root.replyDisplay
maxContentWidth: _private.availableContentWidth
sourceComponent: {
switch (root.type) {
case DelegateType.Image:
case DelegateType.Sticker:
return imageComponent;
case DelegateType.Message:
case DelegateType.Notice:
return textComponent;
case DelegateType.File:
case DelegateType.Video:
case DelegateType.Audio:
return mimeComponent;
case DelegateType.Encrypted:
return encryptedComponent;
default:
return textComponent;
HoverHandler {
enabled: !hoveredLink
cursorShape: Qt.PointingHandCursor
}
TapHandler {
enabled: !hoveredLink
acceptedButtons: Qt.LeftButton
onTapped: root.replyClicked(root.replyEventId)
}
}
}
DelegateChoice {
roleValue: MessageComponentType.Image
delegate: Image {
id: image
Layout.maximumWidth: mediaSizeHelper.currentSize.width
Layout.maximumHeight: mediaSizeHelper.currentSize.height
source: root?.replyMediaInfo.source ?? ""
MediaSizeHelper {
id: mediaSizeHelper
contentMaxWidth: _private.availableContentWidth
mediaWidth: root?.replyMediaInfo.width ?? -1
mediaHeight: root?.replyMediaInfo.height ?? -1
}
}
}
DelegateChoice {
roleValue: MessageComponentType.File
delegate: MimeComponent {
mimeIconSource: root.replyMediaInfo.mimeIcon
label: root.replyDisplay
subLabel: root.replyComponentType === DelegateType.File ? Format.formatByteSize(root.replyMediaInfo.size) : Format.formatDuration(root.replyMediaInfo.duration)
}
}
DelegateChoice {
roleValue: MessageComponentType.Video
delegate: MimeComponent {
mimeIconSource: root.replyMediaInfo.mimeIcon
label: root.replyDisplay
subLabel: root.replyComponentType === DelegateType.File ? Format.formatByteSize(root.replyMediaInfo.size) : Format.formatDuration(root.replyMediaInfo.duration)
}
}
DelegateChoice {
roleValue: MessageComponentType.Audio
delegate: MimeComponent {
mimeIconSource: root.replyMediaInfo.mimeIcon
label: root.replyDisplay
subLabel: root.replyComponentType === DelegateType.File ? Format.formatByteSize(root.replyMediaInfo.size) : Format.formatDuration(root.replyMediaInfo.duration)
}
}
DelegateChoice {
roleValue: MessageComponentType.Encrypted
delegate: TextComponent {
display: i18n("This message is encrypted and the sender has not shared the key with this device.")
}
}
}
}
@@ -141,55 +201,11 @@ RowLayout {
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: root.replyClicked()
onTapped: root.replyClicked(root.replyEventId)
}
Component {
id: textComponent
RichLabel {
textMessage: root.display
HoverHandler {
enabled: !hoveredLink
cursorShape: Qt.PointingHandCursor
}
TapHandler {
enabled: !hoveredLink
acceptedButtons: Qt.LeftButton
onTapped: root.replyClicked()
}
}
}
Component {
id: imageComponent
Image {
id: image
width: mediaSizeHelper.currentSize.width
height: mediaSizeHelper.currentSize.height
fillMode: Image.PreserveAspectFit
source: root?.mediaInfo.source ?? ""
MediaSizeHelper {
id: mediaSizeHelper
contentMaxWidth: root.contentMaxWidth - verticalBorder.width - root.spacing
mediaWidth: root?.mediaInfo.width ?? -1
mediaHeight: root?.mediaInfo.height ?? -1
}
}
}
Component {
id: mimeComponent
MimeComponent {
mimeIconSource: root.mediaInfo.mimeIcon
label: root.display
subLabel: root.type === DelegateType.File ? Format.formatByteSize(root.mediaInfo.size) : Format.formatDuration(root.mediaInfo.duration)
}
}
Component {
id: encryptedComponent
RichLabel {
textMessage: i18n("This message is encrypted and the sender has not shared the key with this device.")
textFormat: Text.RichText
}
QtObject {
id: _private
// The space available for the component after taking away the border
readonly property real availableContentWidth: root.maxContentWidth - verticalBorder.implicitWidth - root.spacing
}
}