Implement fancy reply rendering

This currently assumes the event, that is replied to, is already
fetched. If it isn't, it will render an empty reply. In the future we
should fetch replies before rendering them.
This commit is contained in:
Nicolas Werner 2020-01-11 14:07:51 +01:00
parent b130b85df8
commit 2b3dc3d8b9
10 changed files with 159 additions and 87 deletions

View File

@ -14,23 +14,70 @@ RowLayout {
anchors.left: parent.left
anchors.right: parent.right
height: Math.max(contentItem.height, 16)
//height: Math.max(model.replyTo ? reply.height + contentItem.height + 4 : contentItem.height, 16)
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 4
//property var replyTo: model.replyTo
// fancy reply, if this is a reply
Rectangle {
visible: model.replyTo
width: parent.width
height: replyContainer.height
//Text {
// property int idx: timelineManager.timeline.idToIndex(replyTo)
// text: "" + (idx != -1 ? timelineManager.timeline.data(timelineManager.timeline.index(idx, 0), 2) : "nothing")
//}
Rectangle {
id: colorLine
height: replyContainer.height
width: 4
color: chat.model.userColor(reply.modelData.userId, colors.window)
}
Column {
id: replyContainer
anchors.left: colorLine.right
anchors.leftMargin: 4
width: parent.width - 8
Text {
id: userName
text: chat.model.escapeEmoji(reply.modelData.userName)
color: chat.model.userColor(reply.modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor
}
}
MessageDelegate {
id: reply
width: parent.width
modelData: chat.model.getDump(model.replyTo)
}
}
color: { var col = chat.model.userColor(reply.modelData.userId, colors.window); col.a = 0.2; return col }
MouseArea {
anchors.fill: parent
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(model.replyTo), ListView.Contain)
cursorShape: Qt.PointingHandCursor
}
}
// actual message content
MessageDelegate {
id: contentItem
width: parent.width
height: childrenRect.height
modelData: model
}
}

View File

@ -31,7 +31,7 @@ Rectangle {
}
MouseArea {
anchors.fill: parent
onClicked: timelineManager.timeline.saveMedia(model.id)
onClicked: timelineManager.timeline.saveMedia(model.data.id)
cursorShape: Qt.PointingHandCursor
}
}
@ -40,14 +40,14 @@ Rectangle {
Text {
Layout.fillWidth: true
text: model.body
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.filesize
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text

View File

@ -3,26 +3,26 @@ import QtQuick 2.6
import im.nheko 1.0
Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.width)
property double tempHeight: tempWidth * model.proportionalHeight
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property bool tooHigh: tempHeight > chat.height - 40
height: tooHigh ? chat.height - 40 : tempHeight
width: tooHigh ? (chat.height - 40) / model.proportionalHeight : tempWidth
width: tooHigh ? (chat.height - 40) / model.data.proportionalHeight : tempWidth
Image {
id: img
anchors.fill: parent
source: model.url.replace("mxc://", "image://MxcImage/")
source: model.data.url.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
MouseArea {
enabled: model.type == MtxEvent.ImageMessage
enabled: model.data.type == MtxEvent.ImageMessage
anchors.fill: parent
onClicked: timelineManager.openImageOverlay(model.url, model.id)
onClicked: timelineManager.openImageOverlay(model.data.url, model.data.id)
}
}
}

View File

@ -1,9 +1,22 @@
import QtQuick 2.6
import im.nheko 1.0
Item {
// Workaround to have an assignable global property
Item {
id: model
property var data;
}
property alias modelData: model.data
height: chooser.childrenRect.height
DelegateChooser {
id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.type
roleValue: model.data.type
anchors.fill: parent
DelegateChoice {
roleValue: MtxEvent.TextMessage
@ -52,16 +65,17 @@ DelegateChooser {
DelegateChoice {
roleValue: MtxEvent.Name
NoticeMessage {
notice: model.roomName ? qsTr("room name changed to: %1").arg(model.roomName) : qsTr("removed room name")
notice: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
}
}
DelegateChoice {
roleValue: MtxEvent.Topic
NoticeMessage {
notice: model.roomTopic ? qsTr("topic changed to: %1").arg(model.roomTopic) : qsTr("removed topic")
notice: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
}
}
DelegateChoice {
Placeholder {}
}
}
}

View File

@ -1,7 +1,7 @@
import ".."
MatrixText {
property string notice: model.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
property string notice: model.data.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
text: notice
width: parent ? parent.width : undefined
font.italic: true

View File

@ -1,7 +1,7 @@
import ".."
MatrixText {
text: qsTr("unimplemented event: ") + model.type
text: qsTr("unimplemented event: ") + model.data.type
width: parent ? parent.width : undefined
color: inactiveColors.text
}

View File

@ -19,12 +19,12 @@ Rectangle {
Rectangle {
id: videoContainer
visible: model.type == MtxEvent.VideoMessage
width: Math.min(parent.width, model.width ? model.width : 400) // some media has 0 as size...
height: width*model.proportionalHeight
visible: model.data.type == MtxEvent.VideoMessage
width: Math.min(parent.width, model.data.width ? model.data.width : 400) // some media has 0 as size...
height: width*model.data.proportionalHeight
Image {
anchors.fill: parent
source: model.thumbnailUrl.replace("mxc://", "image://MxcImage/")
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
@ -97,7 +97,7 @@ Rectangle {
anchors.fill: parent
onClicked: {
switch (button.state) {
case "": timelineManager.timeline.cacheMedia(model.id); break;
case "": timelineManager.timeline.cacheMedia(model.data.id); break;
case "stopped":
media.play(); console.log("play");
button.state = "playing"
@ -120,7 +120,7 @@ Rectangle {
Connections {
target: timelineManager.timeline
onMediaCached: {
if (mxcUrl == model.url) {
if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl
button.state = "stopped"
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
@ -145,14 +145,14 @@ Rectangle {
Text {
Layout.fillWidth: true
text: model.body
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.filesize
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text

View File

@ -1,6 +1,6 @@
import ".."
MatrixText {
text: model.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
text: model.data.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
width: parent ? parent.width : undefined
}

View File

@ -212,6 +212,14 @@ TimelineModel::rowCount(const QModelIndex &parent) const
return (int)this->eventOrder.size();
}
QVariantMap
TimelineModel::getDump(QString eventId) const
{
if (events.contains(eventId))
return data(index(idToIndex(eventId), 0), Dump).toMap();
return {};
}
QVariant
TimelineModel::data(const QModelIndex &index, int role) const
{
@ -263,11 +271,13 @@ TimelineModel::data(const QModelIndex &index, int role) const
return QVariant(toRoomEventType(event));
case Body:
return QVariant(utils::replaceEmoji(QString::fromStdString(body(event))));
case FormattedBody:
case FormattedBody: {
const static QRegularExpression replyFallback(
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
return QVariant(
utils::replaceEmoji(utils::linkifyMessage(formattedBodyWithFallback(event)))
.remove("<mx-reply>")
.remove("</mx-reply>"));
.remove(replyFallback));
}
case Url:
return QVariant(QString::fromStdString(url(event)));
case ThumbnailUrl:

View File

@ -181,6 +181,7 @@ public slots:
void setCurrentIndex(int index);
int currentIndex() const { return idToIndex(currentId); }
void markEventsAsRead(const std::vector<QString> &event_ids);
QVariantMap getDump(QString eventId) const;
private slots:
// Add old events at the top of the timeline.