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 
This commit is contained in:
@@ -23,6 +23,7 @@ TimelineContainer {
|
||||
Layout.maximumWidth: messageDelegate.contentMaxWidth
|
||||
RichLabel {
|
||||
id: label
|
||||
Layout.fillWidth: true
|
||||
isEmote: messageDelegate.isEmote
|
||||
}
|
||||
Loader {
|
||||
|
||||
46
imports/NeoChat/Component/Timeline/MimeComponent.qml
Normal file
46
imports/NeoChat/Component/Timeline/MimeComponent.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,93 +11,118 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Timeline 1.0
|
||||
|
||||
MouseArea {
|
||||
id: replyButton
|
||||
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
|
||||
}
|
||||
Item {
|
||||
id: replyComponent
|
||||
|
||||
Kirigami.Avatar {
|
||||
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
|
||||
}
|
||||
signal replyClicked()
|
||||
|
||||
QQC2.Label {
|
||||
id: replyName
|
||||
anchors {
|
||||
left: replyAvatar.right
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
right: parent.right
|
||||
rightMargin: Kirigami.Units.smallSpacing
|
||||
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
|
||||
}
|
||||
text: currentRoom.htmlSafeMemberName(reply.author.id)
|
||||
color: reply.author.color
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Kirigami.Avatar {
|
||||
id: replyAvatar
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.top: replyName.bottom
|
||||
sourceComponent: {
|
||||
switch (reply.type) {
|
||||
case "image":
|
||||
case "sticker":
|
||||
return imageComponent;
|
||||
case "message":
|
||||
return textComponent;
|
||||
// TODO support more types
|
||||
default:
|
||||
return textComponent;
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
|
||||
name: replyComponent.name || ""
|
||||
color: replyComponent.color
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: replyComponent.color
|
||||
text: replyComponent.name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
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 {
|
||||
id: textComponent
|
||||
RichLabel {
|
||||
id: replyText
|
||||
textMessage: reply.display
|
||||
textFormat: Text.RichText
|
||||
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
replyComponent.replyClicked()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
Image {
|
||||
readonly property var content: reply.content
|
||||
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 string mediaId: isThumbnail ? content.thumbnailMediaId : content.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
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
Component {
|
||||
id: textComponent
|
||||
RichLabel {
|
||||
textMessage: reply.display
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: imageComponent
|
||||
Image {
|
||||
id: image
|
||||
readonly property var content: reply.content
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ TextEdit {
|
||||
|
||||
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
persistentSelection: true
|
||||
|
||||
text: "<style>
|
||||
|
||||
@@ -252,13 +252,21 @@ QQC2.ItemDelegate {
|
||||
}
|
||||
Loader {
|
||||
id: replyLoader
|
||||
|
||||
Layout.maximumWidth: contentMaxWidth
|
||||
|
||||
active: model.reply !== undefined
|
||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||
visible: active
|
||||
|
||||
sourceComponent: ReplyComponent {
|
||||
name: currentRoom.htmlSafeMemberName(reply.author.id)
|
||||
avatar: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||
color: reply.author.color
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
function onClicked() {
|
||||
function onReplyClicked() {
|
||||
replyClicked(reply.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,3 +13,4 @@ EventDelegate 1.0 EventDelegate.qml
|
||||
MessageDelegate 1.0 MessageDelegate.qml
|
||||
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
|
||||
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml
|
||||
MimeComponent 1.0 MimeComponent.qml
|
||||
|
||||
1
res.qrc
1
res.qrc
@@ -48,6 +48,7 @@
|
||||
<file>imports/NeoChat/Component/Timeline/EventDelegate.qml</file>
|
||||
<file>imports/NeoChat/Component/Timeline/MessageDelegate.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/LoginStep.qml</file>
|
||||
<file>imports/NeoChat/Component/Login/Login.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user