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.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
height: Math.max(contentItem.height, 16)
|
//height: Math.max(model.replyTo ? reply.height + contentItem.height + 4 : contentItem.height, 16)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignTop
|
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 {
|
Rectangle {
|
||||||
// property int idx: timelineManager.timeline.idToIndex(replyTo)
|
id: colorLine
|
||||||
// text: "" + (idx != -1 ? timelineManager.timeline.data(timelineManager.timeline.index(idx, 0), 2) : "nothing")
|
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 {
|
MessageDelegate {
|
||||||
id: contentItem
|
id: contentItem
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height
|
|
||||||
|
modelData: model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: timelineManager.timeline.saveMedia(model.id)
|
onClicked: timelineManager.timeline.saveMedia(model.data.id)
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,14 +40,14 @@ Rectangle {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: model.body
|
text: model.data.body
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
color: colors.text
|
color: colors.text
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: model.filesize
|
text: model.data.filesize
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
color: colors.text
|
color: colors.text
|
||||||
|
@ -3,26 +3,26 @@ import QtQuick 2.6
|
|||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.width)
|
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width)
|
||||||
property double tempHeight: tempWidth * model.proportionalHeight
|
property double tempHeight: tempWidth * model.data.proportionalHeight
|
||||||
|
|
||||||
property bool tooHigh: tempHeight > chat.height - 40
|
property bool tooHigh: tempHeight > chat.height - 40
|
||||||
|
|
||||||
height: tooHigh ? chat.height - 40 : tempHeight
|
height: tooHigh ? chat.height - 40 : tempHeight
|
||||||
width: tooHigh ? (chat.height - 40) / model.proportionalHeight : tempWidth
|
width: tooHigh ? (chat.height - 40) / model.data.proportionalHeight : tempWidth
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: img
|
id: img
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
source: model.url.replace("mxc://", "image://MxcImage/")
|
source: model.data.url.replace("mxc://", "image://MxcImage/")
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
enabled: model.type == MtxEvent.ImageMessage
|
enabled: model.data.type == MtxEvent.ImageMessage
|
||||||
anchors.fill: parent
|
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 QtQuick 2.6
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
DelegateChooser {
|
Item {
|
||||||
//role: "type" //< not supported in our custom implementation, have to use roleValue
|
// Workaround to have an assignable global property
|
||||||
roleValue: model.type
|
Item {
|
||||||
|
id: model
|
||||||
|
property var data;
|
||||||
|
}
|
||||||
|
|
||||||
|
property alias modelData: model.data
|
||||||
|
|
||||||
DelegateChoice {
|
height: chooser.childrenRect.height
|
||||||
roleValue: MtxEvent.TextMessage
|
|
||||||
TextMessage {}
|
DelegateChooser {
|
||||||
}
|
id: chooser
|
||||||
DelegateChoice {
|
//role: "type" //< not supported in our custom implementation, have to use roleValue
|
||||||
roleValue: MtxEvent.NoticeMessage
|
roleValue: model.data.type
|
||||||
NoticeMessage {}
|
anchors.fill: parent
|
||||||
}
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MtxEvent.EmoteMessage
|
roleValue: MtxEvent.TextMessage
|
||||||
TextMessage {}
|
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")
|
|
||||||
}
|
}
|
||||||
}
|
DelegateChoice {
|
||||||
DelegateChoice {
|
roleValue: MtxEvent.NoticeMessage
|
||||||
roleValue: MtxEvent.Encryption
|
NoticeMessage {}
|
||||||
Pill {
|
|
||||||
text: qsTr("Encryption enabled")
|
|
||||||
}
|
}
|
||||||
}
|
DelegateChoice {
|
||||||
DelegateChoice {
|
roleValue: MtxEvent.EmoteMessage
|
||||||
roleValue: MtxEvent.Name
|
TextMessage {}
|
||||||
NoticeMessage {
|
|
||||||
notice: model.roomName ? qsTr("room name changed to: %1").arg(model.roomName) : qsTr("removed room name")
|
|
||||||
}
|
}
|
||||||
}
|
DelegateChoice {
|
||||||
DelegateChoice {
|
roleValue: MtxEvent.ImageMessage
|
||||||
roleValue: MtxEvent.Topic
|
ImageMessage {}
|
||||||
NoticeMessage {
|
}
|
||||||
notice: model.roomTopic ? qsTr("topic changed to: %1").arg(model.roomTopic) : qsTr("removed topic")
|
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 ".."
|
import ".."
|
||||||
|
|
||||||
MatrixText {
|
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
|
text: notice
|
||||||
width: parent ? parent.width : undefined
|
width: parent ? parent.width : undefined
|
||||||
font.italic: true
|
font.italic: true
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import ".."
|
import ".."
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: qsTr("unimplemented event: ") + model.type
|
text: qsTr("unimplemented event: ") + model.data.type
|
||||||
width: parent ? parent.width : undefined
|
width: parent ? parent.width : undefined
|
||||||
color: inactiveColors.text
|
color: inactiveColors.text
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,12 @@ Rectangle {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: videoContainer
|
id: videoContainer
|
||||||
visible: model.type == MtxEvent.VideoMessage
|
visible: model.data.type == MtxEvent.VideoMessage
|
||||||
width: Math.min(parent.width, model.width ? model.width : 400) // some media has 0 as size...
|
width: Math.min(parent.width, model.data.width ? model.data.width : 400) // some media has 0 as size...
|
||||||
height: width*model.proportionalHeight
|
height: width*model.data.proportionalHeight
|
||||||
Image {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: model.thumbnailUrl.replace("mxc://", "image://MxcImage/")
|
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: {
|
onClicked: {
|
||||||
switch (button.state) {
|
switch (button.state) {
|
||||||
case "": timelineManager.timeline.cacheMedia(model.id); break;
|
case "": timelineManager.timeline.cacheMedia(model.data.id); break;
|
||||||
case "stopped":
|
case "stopped":
|
||||||
media.play(); console.log("play");
|
media.play(); console.log("play");
|
||||||
button.state = "playing"
|
button.state = "playing"
|
||||||
@ -120,7 +120,7 @@ Rectangle {
|
|||||||
Connections {
|
Connections {
|
||||||
target: timelineManager.timeline
|
target: timelineManager.timeline
|
||||||
onMediaCached: {
|
onMediaCached: {
|
||||||
if (mxcUrl == model.url) {
|
if (mxcUrl == model.data.url) {
|
||||||
media.source = "file://" + cacheUrl
|
media.source = "file://" + cacheUrl
|
||||||
button.state = "stopped"
|
button.state = "stopped"
|
||||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
|
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
|
||||||
@ -145,14 +145,14 @@ Rectangle {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: model.body
|
text: model.data.body
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
color: colors.text
|
color: colors.text
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: model.filesize
|
text: model.data.filesize
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
color: colors.text
|
color: colors.text
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import ".."
|
import ".."
|
||||||
|
|
||||||
MatrixText {
|
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
|
width: parent ? parent.width : undefined
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,14 @@ TimelineModel::rowCount(const QModelIndex &parent) const
|
|||||||
return (int)this->eventOrder.size();
|
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
|
QVariant
|
||||||
TimelineModel::data(const QModelIndex &index, int role) const
|
TimelineModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
@ -263,11 +271,13 @@ TimelineModel::data(const QModelIndex &index, int role) const
|
|||||||
return QVariant(toRoomEventType(event));
|
return QVariant(toRoomEventType(event));
|
||||||
case Body:
|
case Body:
|
||||||
return QVariant(utils::replaceEmoji(QString::fromStdString(body(event))));
|
return QVariant(utils::replaceEmoji(QString::fromStdString(body(event))));
|
||||||
case FormattedBody:
|
case FormattedBody: {
|
||||||
|
const static QRegularExpression replyFallback(
|
||||||
|
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
|
||||||
return QVariant(
|
return QVariant(
|
||||||
utils::replaceEmoji(utils::linkifyMessage(formattedBodyWithFallback(event)))
|
utils::replaceEmoji(utils::linkifyMessage(formattedBodyWithFallback(event)))
|
||||||
.remove("<mx-reply>")
|
.remove(replyFallback));
|
||||||
.remove("</mx-reply>"));
|
}
|
||||||
case Url:
|
case Url:
|
||||||
return QVariant(QString::fromStdString(url(event)));
|
return QVariant(QString::fromStdString(url(event)));
|
||||||
case ThumbnailUrl:
|
case ThumbnailUrl:
|
||||||
|
@ -181,6 +181,7 @@ public slots:
|
|||||||
void setCurrentIndex(int index);
|
void setCurrentIndex(int index);
|
||||||
int currentIndex() const { return idToIndex(currentId); }
|
int currentIndex() const { return idToIndex(currentId); }
|
||||||
void markEventsAsRead(const std::vector<QString> &event_ids);
|
void markEventsAsRead(const std::vector<QString> &event_ids);
|
||||||
|
QVariantMap getDump(QString eventId) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// Add old events at the top of the timeline.
|
// Add old events at the top of the timeline.
|
||||||
|
Loading…
Reference in New Issue
Block a user