mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 03:00:46 +03:00
Working text messages in delegate rework
This commit is contained in:
parent
4d8b8c3b81
commit
76b40f452b
5 changed files with 191 additions and 35 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue