Reply Refactor

The aim here is to tidy up the reply component and make it so it sizes automatically without any need to manually set widths or heights. This is achieved by putting the layout in a grid layout. The implicitwidth and height variables are also simplified meaning that the margins are no longer prone to being slightly off because the calculation didn't add up the margins and spacing right.

Also added here is a mime component which is used to provide a nicer representation for files, videos and audio in the reply because trying to put the full component in wouldn't look good.

This also fixes the situation in right to left mode where the layout now mirror properly and everything sits where it should.

New reply elements
![image](/uploads/bfdc81040b3477b064fe8a06d30bdb6e/image.png)
This commit is contained in:
James Graham
2022-10-11 20:36:15 +00:00
parent d97448eb07
commit d2888bc479
7 changed files with 162 additions and 82 deletions

View File

@@ -23,6 +23,7 @@ TimelineContainer {
Layout.maximumWidth: messageDelegate.contentMaxWidth Layout.maximumWidth: messageDelegate.contentMaxWidth
RichLabel { RichLabel {
id: label id: label
Layout.fillWidth: true
isEmote: messageDelegate.isEmote isEmote: messageDelegate.isEmote
} }
Loader { Loader {

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
RowLayout {
property alias mimeIconSource: icon.source
property alias label: nameLabel.text
property alias subLabel: subLabel.text
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
id: icon
fallback: "unknown"
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
spacing: 0
QQC2.Label {
id: nameLabel
Layout.fillWidth: true
Layout.alignment: subLabel.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter
elide: Text.ElideRight
}
QQC2.Label {
id: subLabel
Layout.fillWidth: true
elide: Text.ElideRight
visible: text.length > 0
opacity: 0.7
}
}
}

View File

@@ -11,93 +11,118 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
import NeoChat.Component.Timeline 1.0 import NeoChat.Component.Timeline 1.0
MouseArea { Item {
id: replyButton id: replyComponent
Layout.fillWidth: true
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
implicitWidth: Math.min(contentMaxWidth, Math.max((loader.item ? loader.item.width : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
Component.onCompleted: {
parent.Layout.fillWidth = true;
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
parent.Layout.maximumWidth = Qt.binding(function() { return contentMaxWidth + Kirigami.Units.largeSpacing * 2; })
}
Rectangle {
id: replyLeftBorder
width: Kirigami.Units.smallSpacing
height: parent.height
x: Config.compactLayout ? Kirigami.Units.largeSpacing : 0
color: Kirigami.Theme.highlightColor
}
Kirigami.Avatar { signal replyClicked()
id: replyAvatar
anchors.left: replyLeftBorder.right
anchors.leftMargin: Kirigami.Units.smallSpacing
width: visible ? Kirigami.Units.gridUnit : 0
height: Kirigami.Units.gridUnit
sourceSize.width: width
sourceSize.height: height
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
source: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
name: reply.author.name || ""
color: reply.author.color
}
QQC2.Label { property var name
id: replyName property alias avatar: replyAvatar.source
anchors { property var color
left: replyAvatar.right
leftMargin: Kirigami.Units.smallSpacing implicitWidth: mainLayout.implicitWidth
right: parent.right implicitHeight: mainLayout.implicitHeight
rightMargin: Kirigami.Units.smallSpacing
GridLayout {
id: mainLayout
anchors.fill: parent
rows: 2
columns: 3
rowSpacing: Kirigami.Units.smallSpacing
columnSpacing: Kirigami.Units.largeSpacing
Rectangle {
id: verticalBorder
Layout.fillHeight: true
Layout.rowSpan: 2
implicitWidth: Kirigami.Units.smallSpacing
color: replyComponent.color
} }
text: currentRoom.htmlSafeMemberName(reply.author.id) Kirigami.Avatar {
color: reply.author.color id: replyAvatar
elide: Text.ElideRight
}
Loader { implicitWidth: Kirigami.Units.iconSizes.small
id: loader implicitHeight: Kirigami.Units.iconSizes.small
anchors.top: replyName.bottom
sourceComponent: { name: replyComponent.name || ""
switch (reply.type) { color: replyComponent.color
case "image": }
case "sticker": QQC2.Label {
return imageComponent; Layout.fillWidth: true
case "message":
return textComponent; color: replyComponent.color
// TODO support more types text: replyComponent.name
default: elide: Text.ElideRight
return textComponent; }
Loader {
id: loader
Layout.fillWidth: true
Layout.columnSpan: 2
sourceComponent: {
switch (reply.type) {
case "image":
case "sticker":
return imageComponent;
case "message":
case "notice":
return textComponent;
case "file":
case "video":
case "audio":
return mimeComponent;
case "encrypted":
return encryptedComponent;
default:
return textComponent;
}
} }
} }
}
Component { MouseArea {
id: textComponent anchors.fill: parent
RichLabel { onClicked: {
id: replyText replyComponent.replyClicked()
textMessage: reply.display
textFormat: Text.RichText
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
}
} }
}
Component { Component {
id: imageComponent id: textComponent
Image { RichLabel {
readonly property var content: reply.content textMessage: reply.display
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null) textFormat: Text.RichText
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info }
readonly property var info: content.info }
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId Component {
source: "image://mxc/" + mediaId id: imageComponent
Image {
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width id: image
height: reply.content.info.h / reply.content.info.w * width readonly property var content: reply.content
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
} readonly property var info: content.info
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
source: "image://mxc/" + mediaId
}
}
Component {
id: mimeComponent
MimeComponent {
mimeIconSource: reply.content.info.mimetype.replace("/", "-")
label: reply.display
subLabel: reply.type === "file" ? Controller.formatByteSize(reply.content.info ? reply.content.info.size : 0) : Controller.formatDuration(reply.content.info.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
} }
} }
} }

View File

@@ -50,8 +50,6 @@ TextEdit {
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage)) ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
Layout.fillWidth: true
persistentSelection: true persistentSelection: true
text: "<style> text: "<style>

View File

@@ -252,13 +252,21 @@ QQC2.ItemDelegate {
} }
Loader { Loader {
id: replyLoader id: replyLoader
Layout.maximumWidth: contentMaxWidth
active: model.reply !== undefined active: model.reply !== undefined
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
visible: active visible: active
sourceComponent: ReplyComponent {
name: currentRoom.htmlSafeMemberName(reply.author.id)
avatar: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
color: reply.author.color
}
Connections { Connections {
target: replyLoader.item target: replyLoader.item
function onClicked() { function onReplyClicked() {
replyClicked(reply.eventId) replyClicked(reply.eventId)
} }
} }

View File

@@ -13,3 +13,4 @@ EventDelegate 1.0 EventDelegate.qml
MessageDelegate 1.0 MessageDelegate.qml MessageDelegate 1.0 MessageDelegate.qml
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml
MimeComponent 1.0 MimeComponent.qml

View File

@@ -48,6 +48,7 @@
<file>imports/NeoChat/Component/Timeline/EventDelegate.qml</file> <file>imports/NeoChat/Component/Timeline/EventDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/MessageDelegate.qml</file> <file>imports/NeoChat/Component/Timeline/MessageDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml</file> <file>imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/MimeComponent.qml</file>
<file>imports/NeoChat/Component/Login/qmldir</file> <file>imports/NeoChat/Component/Login/qmldir</file>
<file>imports/NeoChat/Component/Login/LoginStep.qml</file> <file>imports/NeoChat/Component/Login/LoginStep.qml</file>
<file>imports/NeoChat/Component/Login/Login.qml</file> <file>imports/NeoChat/Component/Login/Login.qml</file>