From d2888bc479e6fcc6c4eb9c4d838469059a04098b Mon Sep 17 00:00:00 2001 From: James Graham Date: Tue, 11 Oct 2022 20:36:15 +0000 Subject: [PATCH] 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) --- .../Component/Timeline/MessageDelegate.qml | 1 + .../Component/Timeline/MimeComponent.qml | 46 +++++ .../Component/Timeline/ReplyComponent.qml | 181 ++++++++++-------- .../NeoChat/Component/Timeline/RichLabel.qml | 2 - .../Component/Timeline/TimelineContainer.qml | 12 +- imports/NeoChat/Component/Timeline/qmldir | 1 + res.qrc | 1 + 7 files changed, 162 insertions(+), 82 deletions(-) create mode 100644 imports/NeoChat/Component/Timeline/MimeComponent.qml diff --git a/imports/NeoChat/Component/Timeline/MessageDelegate.qml b/imports/NeoChat/Component/Timeline/MessageDelegate.qml index 6987871b9..1d8b58630 100644 --- a/imports/NeoChat/Component/Timeline/MessageDelegate.qml +++ b/imports/NeoChat/Component/Timeline/MessageDelegate.qml @@ -23,6 +23,7 @@ TimelineContainer { Layout.maximumWidth: messageDelegate.contentMaxWidth RichLabel { id: label + Layout.fillWidth: true isEmote: messageDelegate.isEmote } Loader { diff --git a/imports/NeoChat/Component/Timeline/MimeComponent.qml b/imports/NeoChat/Component/Timeline/MimeComponent.qml new file mode 100644 index 000000000..c569668f0 --- /dev/null +++ b/imports/NeoChat/Component/Timeline/MimeComponent.qml @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2022 James Graham +// 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 + } + } +} diff --git a/imports/NeoChat/Component/Timeline/ReplyComponent.qml b/imports/NeoChat/Component/Timeline/ReplyComponent.qml index acf4170b5..ccf9c6505 100644 --- a/imports/NeoChat/Component/Timeline/ReplyComponent.qml +++ b/imports/NeoChat/Component/Timeline/ReplyComponent.qml @@ -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 } } } diff --git a/imports/NeoChat/Component/Timeline/RichLabel.qml b/imports/NeoChat/Component/Timeline/RichLabel.qml index 8457d7013..8b42c38d9 100644 --- a/imports/NeoChat/Component/Timeline/RichLabel.qml +++ b/imports/NeoChat/Component/Timeline/RichLabel.qml @@ -50,8 +50,6 @@ TextEdit { ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage)) - Layout.fillWidth: true - persistentSelection: true text: "