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:
parent
b130b85df8
commit
2b3dc3d8b9
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,81 @@
|
||||
import QtQuick 2.6
|
||||
import im.nheko 1.0
|
||||
|
||||
DelegateChooser {
|
||||
//role: "type" //< not supported in our custom implementation, have to use roleValue
|
||||
roleValue: model.type
|
||||
Item {
|
||||
// Workaround to have an assignable global property
|
||||
Item {
|
||||
id: model
|
||||
property var data;
|
||||
}
|
||||
|
||||
property alias modelData: model.data
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.TextMessage
|
||||
TextMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.NoticeMessage
|
||||
NoticeMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.EmoteMessage
|
||||
TextMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.ImageMessage
|
||||
ImageMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Sticker
|
||||
ImageMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.FileMessage
|
||||
FileMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.VideoMessage
|
||||
PlayableMediaMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.AudioMessage
|
||||
PlayableMediaMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Redacted
|
||||
Pill {
|
||||
text: qsTr("redacted")
|
||||
height: chooser.childrenRect.height
|
||||
|
||||
DelegateChooser {
|
||||
id: chooser
|
||||
//role: "type" //< not supported in our custom implementation, have to use roleValue
|
||||
roleValue: model.data.type
|
||||
anchors.fill: parent
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.TextMessage
|
||||
TextMessage {}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Encryption
|
||||
Pill {
|
||||
text: qsTr("Encryption enabled")
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.NoticeMessage
|
||||
NoticeMessage {}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Name
|
||||
NoticeMessage {
|
||||
notice: model.roomName ? qsTr("room name changed to: %1").arg(model.roomName) : qsTr("removed room name")
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.EmoteMessage
|
||||
TextMessage {}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Topic
|
||||
NoticeMessage {
|
||||
notice: model.roomTopic ? qsTr("topic changed to: %1").arg(model.roomTopic) : qsTr("removed topic")
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.ImageMessage
|
||||
ImageMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Sticker
|
||||
ImageMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.FileMessage
|
||||
FileMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.VideoMessage
|
||||
PlayableMediaMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.AudioMessage
|
||||
PlayableMediaMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Redacted
|
||||
Pill {
|
||||
text: qsTr("redacted")
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Encryption
|
||||
Pill {
|
||||
text: qsTr("Encryption enabled")
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Name
|
||||
NoticeMessage {
|
||||
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.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
Placeholder {}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
Placeholder {}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user