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
}
signal replyClicked()
property var name
property alias avatar: replyAvatar.source
property var color
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
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
}
Kirigami.Avatar { Kirigami.Avatar {
id: replyAvatar 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 { implicitWidth: Kirigami.Units.iconSizes.small
id: replyName implicitHeight: Kirigami.Units.iconSizes.small
anchors {
left: replyAvatar.right name: replyComponent.name || ""
leftMargin: Kirigami.Units.smallSpacing color: replyComponent.color
right: parent.right
rightMargin: Kirigami.Units.smallSpacing
} }
text: currentRoom.htmlSafeMemberName(reply.author.id) QQC2.Label {
color: reply.author.color Layout.fillWidth: true
color: replyComponent.color
text: replyComponent.name
elide: Text.ElideRight elide: Text.ElideRight
} }
Loader { Loader {
id: loader id: loader
anchors.top: replyName.bottom
Layout.fillWidth: true
Layout.columnSpan: 2
sourceComponent: { sourceComponent: {
switch (reply.type) { switch (reply.type) {
case "image": case "image":
case "sticker": case "sticker":
return imageComponent; return imageComponent;
case "message": case "message":
case "notice":
return textComponent; return textComponent;
// TODO support more types case "file":
case "video":
case "audio":
return mimeComponent;
case "encrypted":
return encryptedComponent;
default: default:
return textComponent; return textComponent;
} }
} }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
replyComponent.replyClicked()
}
}
Component { Component {
id: textComponent id: textComponent
RichLabel { RichLabel {
id: replyText
textMessage: reply.display textMessage: reply.display
textFormat: Text.RichText textFormat: Text.RichText
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
} }
} }
Component { Component {
id: imageComponent id: imageComponent
Image { Image {
id: image
readonly property var content: reply.content readonly property var content: reply.content
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null) readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
readonly property var info: content.info readonly property var info: content.info
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
source: "image://mxc/" + mediaId source: "image://mxc/" + mediaId
}
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width }
height: reply.content.info.h / reply.content.info.w * width Component {
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width 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>