From 76b40f452b8313db972e44f5eca30f59b7fdf4d3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 25 Jun 2023 02:40:44 +0200 Subject: [PATCH] Working text messages in delegate rework --- resources/qml/MatrixText.qml | 32 ++--- resources/qml/MessageView.qml | 148 ++++++++++++++++++++++-- resources/qml/delegates/TextMessage.qml | 15 +-- src/timeline/EventDelegateChooser.cpp | 26 ++++- src/timeline/EventDelegateChooser.h | 5 + 5 files changed, 191 insertions(+), 35 deletions(-) diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 94b8bb98..de15e078 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -2,24 +2,24 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -import QtQuick 2.5 -import QtQuick.Controls 2.3 -import im.nheko 1.0 +import QtQuick +import QtQuick.Controls +import im.nheko -TextEdit { +TextArea { id: r property alias cursorShape: cs.cursorShape - //leftInset: 0 - //bottomInset: 0 - //rightInset: 0 - //topInset: 0 - //leftPadding: 0 - //bottomPadding: 0 - //rightPadding: 0 - //topPadding: 0 - //background: null + leftInset: 0 + bottomInset: 0 + rightInset: 0 + topInset: 0 + leftPadding: 0 + bottomPadding: 0 + rightPadding: 0 + topPadding: 0 + background: null ToolTip.text: hoveredLink ToolTip.visible: hoveredLink || false @@ -39,9 +39,9 @@ TextEdit { } onLinkActivated: Nheko.openLink(link) - //// propagate events up - //onPressAndHold: (event) => event.accepted = false - //onPressed: (event) => event.accepted = (event.button == Qt.LeftButton) + // propagate events up + onPressAndHold: (event) => event.accepted = false + onPressed: (event) => event.accepted = (event.button == Qt.LeftButton) NhekoCursorShape { id: cs diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index fde7ee57..2852e5f7 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -63,28 +63,160 @@ Item { id: wrapper ListView.delayRemove: true width: chat.delegateMaxWidth - height: main?.height ?? 10 + height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight, 10) + anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter room: chatRoot.roommodel + required property var day + required property bool isSender + required property bool isStateEvent + //required property var previousMessageDay + //required property bool previousMessageIsStateEvent + //required property string previousMessageUserId + required property int index + property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day) + property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent) + property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId) + required property date timestamp + required property string userId + required property string userName + required property string threadId + + data: [ + Loader { + id: section + + property var day: wrapper.day + property bool isSender: wrapper.isSender + property bool isStateEvent: wrapper.isStateEvent + property int parentWidth: wrapper.width + property var previousMessageDay: wrapper.previousMessageDay + property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent + property string previousMessageUserId: wrapper.previousMessageUserId + property date timestamp: wrapper.timestamp + property string userId: wrapper.userId + property string userName: wrapper.userName + + active: previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent + //asynchronous: true + sourceComponent: sectionHeader + visible: status == Loader.Ready + z: 4 + }, + GridLayout { + id: gridContainer + + width: wrapper.width + y: section.visible && section.active ? section.y + section.height : 0 + + ColumnLayout { + id: contentColumn + Layout.fillWidth: true + Layout.leftMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) + (wrapper.threadId ? 6 : 0) // align bubble with section header + + AbstractButton { + id: replyRow + visible: wrapper.reply + Layout.fillWidth: true + Layout.maximumHeight: timelineView.height / 8 + Layout.preferredWidth: replyRowLay.implicitWidth + Layout.preferredHeight: replyRowLay.implicitHeight + + property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) + + clip: true + + contentItem: RowLayout { + id: replyRowLay + + anchors.fill: parent + + + Rectangle { + id: replyLine + Layout.fillHeight: true + color: replyRow.userColor + Layout.preferredWidth: 4 + } + + ColumnLayout { + AbstractButton { + id: replyUserButton + Layout.fillWidth: true + contentItem: ElidedLabel { + id: userName_ + fullText: wrapper.reply?.userName ?? '' + color: replyRow.userColor + textFormat: Text.RichText + width: parent.width + elideWidth: width + } + onClicked: room.openUserProfile(wrapper.reply?.userId) + } + data: [ + replyUserButton, + wrapper.reply, + ] + } + } + + background: Rectangle { + width: replyRow.implicitContentWidth + color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) + } + } + + data: [ + replyRow, wrapper.main, + ] + } + + Rectangle { + color: 'yellow' + Layout.preferredWidth: 100 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignRight | Qt.AlignTop + } + }, + + Rectangle { + width: Math.min(contentColumn.implicitWidth, contentColumn.width) + height: contentColumn.implicitHeight + color: "blue" + opacity: 0.2 + } + ] + EventDelegateChoice { roleValues: [ MtxEvent.TextMessage, MtxEvent.NoticeMessage, ] - TextArea { - required property string body + TextMessage { + id: textMes + + keepFullText: true + required property string userId + required property string userName + + Layout.fillWidth: true + //Layout.maximumWidth: implicitWidth - width: parent.width - text: body } } EventDelegateChoice { roleValues: [ ] - TextArea { - width: parent.width - text: "Unsupported" + MatrixText { + Layout.fillWidth: true + + required property string typeString + + text: "Unsupported: " + typeString + + required property string userId + required property string userName } } } diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 1eb5e2c0..d4b72965 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -3,17 +3,19 @@ // SPDX-License-Identifier: GPL-3.0-or-later import ".." -import QtQuick.Controls 2.3 -import im.nheko 1.0 +import QtQuick.Controls +import QtQuick.Layouts +import im.nheko MatrixText { required property string body required property bool isOnlyEmoji required property bool isReply required property bool keepFullText - required property string formatted + required property string formattedBody + property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body - property int metadataWidth + property int metadataWidth: 100 property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4) // table border-collapse doesn't seem to work @@ -38,9 +40,8 @@ MatrixText { background-color: " + palette.text + "; }" : "") + // TODO(Nico): Figure out how to support mobile " - " + formatted.replace(//g, "").replace(/<\/del>/g, "").replace(//g, "").replace(/<\/strike>/g, "") - width: parent?.width ?? 0 - height: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight + " + formattedBody.replace(//g, "").replace(/<\/del>/g, "").replace(//g, "").replace(/<\/strike>/g, "") + Layout.maximumHeight: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight clip: !keepFullText selectByMouse: !Settings.mobileMode && !isReply enabled: !Settings.mobileMode diff --git a/src/timeline/EventDelegateChooser.cpp b/src/timeline/EventDelegateChooser.cpp index 7618e20b..5e6ee37e 100644 --- a/src/timeline/EventDelegateChooser.cpp +++ b/src/timeline/EventDelegateChooser.cpp @@ -85,6 +85,7 @@ EventDelegateChooser::componentComplete() { QQuickItem::componentComplete(); // eventIncubator.reset(eventIndex); + // eventIncubator.forceCompletion(); } void @@ -104,6 +105,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) QHash roleToPropIdx; std::vector roles; + bool isReplyNeeded = false; // Workaround for https://bugreports.qt.io/browse/QTBUG-98846 QHash requiredProperties; @@ -121,7 +123,10 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) if (!prop.isRequired() && !requiredProperties.contains(prop.name())) continue; - if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) { + if (prop.name() == std::string_view("isReply")) { + isReplyNeeded = true; + roleToPropIdx.insert(-1, i); + } else if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) { roleToPropIdx.insert(*role, i); roles.emplace_back(*role); @@ -134,13 +139,26 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) nhlog::ui()->debug("Querying data for id {}", currentId.toStdString()); chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles); - QVariantMap rolesToSet; for (const auto &role : roles) { const auto &roleName = roleNames[role.role()]; - nhlog::ui()->critical("Setting role {}, {}", role.role(), roleName.toStdString()); + nhlog::ui()->critical("Setting role {}, {} to {}", + role.role(), + roleName.toStdString(), + role.data().toString().toStdString()); + nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[role.role()]).name()); mo->property(roleToPropIdx[role.role()]).write(obj, role.data()); - rolesToSet.insert(roleName, role.data()); + + if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) + QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); + } + + if (isReplyNeeded) { + const auto roleName = QByteArray("isReply"); + nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply); + + nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name()); + mo->property(roleToPropIdx[-1]).write(obj, forReply); if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); diff --git a/src/timeline/EventDelegateChooser.h b/src/timeline/EventDelegateChooser.h index ce22ca3a..b627b383 100644 --- a/src/timeline/EventDelegateChooser.h +++ b/src/timeline/EventDelegateChooser.h @@ -54,6 +54,7 @@ class EventDelegateChooser : public QQuickItem public: Q_PROPERTY(QQmlListProperty choices READ choices CONSTANT FINAL) Q_PROPERTY(QQuickItem *main READ main NOTIFY mainChanged FINAL) + Q_PROPERTY(QQuickItem *reply READ reply NOTIFY replyChanged FINAL) Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL) Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged REQUIRED FINAL) Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL) @@ -64,6 +65,10 @@ public: { return qobject_cast(eventIncubator.object()); } + [[nodiscard]] QQuickItem *reply() const + { + return qobject_cast(replyIncubator.object()); + } void setRoom(TimelineModel *m) {