Working text messages in delegate rework

This commit is contained in:
Nicolas Werner 2023-06-25 02:40:44 +02:00
parent 4d8b8c3b81
commit 76b40f452b
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
5 changed files with 191 additions and 35 deletions

View file

@ -2,24 +2,24 @@
// //
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick
import QtQuick.Controls 2.3 import QtQuick.Controls
import im.nheko 1.0 import im.nheko
TextEdit { TextArea {
id: r id: r
property alias cursorShape: cs.cursorShape property alias cursorShape: cs.cursorShape
//leftInset: 0 leftInset: 0
//bottomInset: 0 bottomInset: 0
//rightInset: 0 rightInset: 0
//topInset: 0 topInset: 0
//leftPadding: 0 leftPadding: 0
//bottomPadding: 0 bottomPadding: 0
//rightPadding: 0 rightPadding: 0
//topPadding: 0 topPadding: 0
//background: null background: null
ToolTip.text: hoveredLink ToolTip.text: hoveredLink
ToolTip.visible: hoveredLink || false ToolTip.visible: hoveredLink || false
@ -39,9 +39,9 @@ TextEdit {
} }
onLinkActivated: Nheko.openLink(link) onLinkActivated: Nheko.openLink(link)
//// propagate events up // propagate events up
//onPressAndHold: (event) => event.accepted = false onPressAndHold: (event) => event.accepted = false
//onPressed: (event) => event.accepted = (event.button == Qt.LeftButton) onPressed: (event) => event.accepted = (event.button == Qt.LeftButton)
NhekoCursorShape { NhekoCursorShape {
id: cs id: cs

View file

@ -63,28 +63,160 @@ Item {
id: wrapper id: wrapper
ListView.delayRemove: true ListView.delayRemove: true
width: chat.delegateMaxWidth 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 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 { EventDelegateChoice {
roleValues: [ roleValues: [
MtxEvent.TextMessage, MtxEvent.TextMessage,
MtxEvent.NoticeMessage, MtxEvent.NoticeMessage,
] ]
TextArea { TextMessage {
required property string body id: textMes
keepFullText: true
required property string userId
required property string userName
Layout.fillWidth: true
//Layout.maximumWidth: implicitWidth
width: parent.width
text: body
} }
} }
EventDelegateChoice { EventDelegateChoice {
roleValues: [ roleValues: [
] ]
TextArea { MatrixText {
width: parent.width Layout.fillWidth: true
text: "Unsupported"
required property string typeString
text: "Unsupported: " + typeString
required property string userId
required property string userName
} }
} }
} }

View file

@ -3,17 +3,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import ".." import ".."
import QtQuick.Controls 2.3 import QtQuick.Controls
import im.nheko 1.0 import QtQuick.Layouts
import im.nheko
MatrixText { MatrixText {
required property string body required property string body
required property bool isOnlyEmoji required property bool isOnlyEmoji
required property bool isReply required property bool isReply
required property bool keepFullText required property bool keepFullText
required property string formatted required property string formattedBody
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body 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) property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
// table border-collapse doesn't seem to work // table border-collapse doesn't seem to work
@ -38,9 +40,8 @@ MatrixText {
background-color: " + palette.text + "; background-color: " + palette.text + ";
}" : "") + // TODO(Nico): Figure out how to support mobile }" : "") + // TODO(Nico): Figure out how to support mobile
"</style> "</style>
" + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>") " + formattedBody.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
width: parent?.width ?? 0 Layout.maximumHeight: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
height: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
clip: !keepFullText clip: !keepFullText
selectByMouse: !Settings.mobileMode && !isReply selectByMouse: !Settings.mobileMode && !isReply
enabled: !Settings.mobileMode enabled: !Settings.mobileMode

View file

@ -85,6 +85,7 @@ EventDelegateChooser::componentComplete()
{ {
QQuickItem::componentComplete(); QQuickItem::componentComplete();
// eventIncubator.reset(eventIndex); // eventIncubator.reset(eventIndex);
// eventIncubator.forceCompletion();
} }
void void
@ -104,6 +105,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
QHash<int, int> roleToPropIdx; QHash<int, int> roleToPropIdx;
std::vector<QModelRoleData> roles; std::vector<QModelRoleData> roles;
bool isReplyNeeded = false;
// Workaround for https://bugreports.qt.io/browse/QTBUG-98846 // Workaround for https://bugreports.qt.io/browse/QTBUG-98846
QHash<QString, RequiredPropertyKey> requiredProperties; QHash<QString, RequiredPropertyKey> requiredProperties;
@ -121,7 +123,10 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
if (!prop.isRequired() && !requiredProperties.contains(prop.name())) if (!prop.isRequired() && !requiredProperties.contains(prop.name()))
continue; 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); roleToPropIdx.insert(*role, i);
roles.emplace_back(*role); roles.emplace_back(*role);
@ -134,13 +139,26 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
nhlog::ui()->debug("Querying data for id {}", currentId.toStdString()); nhlog::ui()->debug("Querying data for id {}", currentId.toStdString());
chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles); chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles);
QVariantMap rolesToSet;
for (const auto &role : roles) { for (const auto &role : roles) {
const auto &roleName = roleNames[role.role()]; 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()); 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()) if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end())
QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req);

View file

@ -54,6 +54,7 @@ class EventDelegateChooser : public QQuickItem
public: public:
Q_PROPERTY(QQmlListProperty<EventDelegateChoice> choices READ choices CONSTANT FINAL) Q_PROPERTY(QQmlListProperty<EventDelegateChoice> choices READ choices CONSTANT FINAL)
Q_PROPERTY(QQuickItem *main READ main NOTIFY mainChanged 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(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 eventId READ eventId WRITE setEventId NOTIFY eventIdChanged REQUIRED FINAL)
Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL) Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL)
@ -64,6 +65,10 @@ public:
{ {
return qobject_cast<QQuickItem *>(eventIncubator.object()); return qobject_cast<QQuickItem *>(eventIncubator.object());
} }
[[nodiscard]] QQuickItem *reply() const
{
return qobject_cast<QQuickItem *>(replyIncubator.object());
}
void setRoom(TimelineModel *m) void setRoom(TimelineModel *m)
{ {