Prepare for reuseItems in timeline

The actual reuseItems is still blocked on a few upstream bugs.
This commit is contained in:
Nicolas Werner 2021-07-12 00:24:33 +02:00
parent f7ffcb4846
commit bd26624ed8
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
17 changed files with 481 additions and 144 deletions

View File

@ -50,9 +50,24 @@ Popup {
Reply { Reply {
id: replyPreview id: replyPreview
modelData: room ? room.getDump(mid, "") : { property var modelData: room ? room.getDump(mid, "") : {
} }
userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window) userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window)
blurhash: modelData.blurhash ?? ""
body: modelData.body ?? ""
formattedBody: modelData.formattedBody ?? ""
eventId: modelData.eventId ?? ""
filename: modelData.filename ?? ""
filesize: modelData.filesize ?? ""
proportionalHeight: modelData.proportionalHeight ?? 1
type: modelData.type ?? MtxEvent.UnknownMessage
typeString: modelData.typeString ?? ""
url: modelData.url ?? ""
originalWidth: modelData.originalWidth ?? 0
isOnlyEmoji: modelData.isOnlyEmoji ?? false
userId: modelData.userId ?? ""
userName: modelData.userName ?? ""
} }
MatrixTextField { MatrixTextField {

View File

@ -7,8 +7,8 @@ import "./emoji"
import "./ui" import "./ui"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.12 import QtQuick 2.15
import QtQuick.Controls 2.3 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
@ -25,6 +25,9 @@ ScrollView {
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2 property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2
model: room model: room
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
//onModelChanged: if (room) room.sendReset()
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
pixelAligned: true pixelAligned: true
spacing: 4 spacing: 4
@ -84,7 +87,7 @@ ScrollView {
ToolTip.text: qsTr("Edit") ToolTip.text: qsTr("Edit")
onClicked: { onClicked: {
if (row.model.isEditable) if (row.model.isEditable)
chat.model.editAction(row.model.id); chat.model.editAction(row.model.eventId);
} }
} }
@ -98,7 +101,7 @@ ScrollView {
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("React") ToolTip.text: qsTr("React")
emojiPicker: emojiPopup emojiPicker: emojiPopup
event_id: row.model ? row.model.id : "" event_id: row.model ? row.model.eventId : ""
} }
ImageButton { ImageButton {
@ -110,7 +113,7 @@ ScrollView {
image: ":/icons/icons/ui/mail-reply.png" image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Reply") ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(row.model.id) onClicked: chat.model.replyAction(row.model.eventId)
} }
ImageButton { ImageButton {
@ -121,7 +124,7 @@ ScrollView {
image: ":/icons/icons/ui/vertical-ellipsis.png" image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Options") ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(row.model.id, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
} }
} }
@ -212,16 +215,16 @@ ScrollView {
topPadding: 4 topPadding: 4
bottomPadding: 4 bottomPadding: 4
spacing: 8 spacing: 8
visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day) visible: (previousMessageUserId !== userId || previousMessageDay !== day)
width: parentWidth width: parentWidth
height: ((modelData && modelData.previousMessageDay !== modelData.day) ? dateBubble.height + 8 + userName.height : userName.height) + 8 height: ((previousMessageDay !== day) ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label { Label {
id: dateBubble id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: modelData && modelData.previousMessageDay !== modelData.day visible: previousMessageDay !== day
text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : "" text: chat.model.formatDateSeparator(timestamp)
color: Nheko.colors.text color: Nheko.colors.text
height: Math.round(fontMetrics.height * 1.4) height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2 width: contentWidth * 1.2
@ -236,7 +239,7 @@ ScrollView {
} }
Row { Row {
height: userName.height height: userName_.height
spacing: 8 spacing: 8
Avatar { Avatar {
@ -244,10 +247,10 @@ ScrollView {
width: Nheko.avatarSize width: Nheko.avatarSize
height: Nheko.avatarSize height: Nheko.avatarSize
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : "" url: chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
displayName: modelData ? modelData.userName : "" displayName: userName
userid: modelData ? modelData.userId : "" userid: userId
onClicked: chat.model.openUserProfile(modelData.userId) onClicked: chat.model.openUserProfile(userId)
ToolTip.visible: avatarHover.hovered ToolTip.visible: avatarHover.hovered
ToolTip.text: userid ToolTip.text: userid
@ -260,22 +263,22 @@ ScrollView {
Connections { Connections {
target: chat.model target: chat.model
onRoomAvatarUrlChanged: { onRoomAvatarUrlChanged: {
messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""; messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
} }
onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible) onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible)
} }
Label { Label {
id: userName id: userName_
text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : "" text: TimelineManager.escapeEmoji(userName)
color: TimelineManager.userColor(modelData ? modelData.userId : "", Nheko.colors.window) color: TimelineManager.userColor(userId, Nheko.colors.window)
textFormat: Text.RichText textFormat: Text.RichText
ToolTip.visible: displayNameHover.hovered ToolTip.visible: displayNameHover.hovered
ToolTip.text: modelData ? modelData.userId : "" ToolTip.text: userId
TapHandler { TapHandler {
onSingleTapped: chat.model.openUserProfile(modelData.userId) onSingleTapped: chat.model.openUserProfile(userId)
} }
CursorShape { CursorShape {
@ -291,7 +294,7 @@ ScrollView {
Label { Label {
color: Nheko.colors.buttonText color: Nheko.colors.buttonText
text: modelData ? TimelineManager.userStatus(modelData.userId) : "" text: TimelineManager.userStatus(userId)
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
@ -307,7 +310,35 @@ ScrollView {
delegate: Item { delegate: Item {
id: wrapper id: wrapper
property bool scrolledToThis: model.id === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash
required property string body
required property string formattedBody
required property string eventId
required property string filename
required property string filesize
required property string url
required property string thumbnailUrl
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property string replyTo
required property string userId
required property var reactions
required property int trustlevel
required property var timestamp
required property int status
required property int index
required property string previousMessageUserId
required property string day
required property string previousMessageDay
required property string userName
property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth width: chat.delegateMaxWidth
@ -362,10 +393,15 @@ ScrollView {
Loader { Loader {
id: section id: section
property var modelData: model
property int parentWidth: parent.width property int parentWidth: parent.width
property string userId: wrapper.userId
property string previousMessageUserId: wrapper.previousMessageUserId
property string day: wrapper.day
property string previousMessageDay: wrapper.previousMessageDay
property string userName: wrapper.userName
property var timestamp: wrapper.timestamp
active: model.previousMessageUserId !== undefined && model.previousMessageUserId !== model.userId || model.previousMessageDay !== model.day active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day
//asynchronous: true //asynchronous: true
sourceComponent: sectionHeader sourceComponent: sectionHeader
visible: status == Loader.Ready visible: status == Loader.Ready
@ -376,6 +412,30 @@ ScrollView {
property alias hovered: hoverHandler.hovered property alias hovered: hoverHandler.hovered
proportionalHeight: wrapper.proportionalHeight
type: chat.model, wrapper.type
typeString: wrapper.typeString
originalWidth: wrapper.originalWidth
blurhash: wrapper.blurhash
body: wrapper.body
formattedBody: wrapper.formattedBody
eventId: chat.model, wrapper.eventId
filename: wrapper.filename
filesize: wrapper.filesize
url: wrapper.url
thumbnailUrl: wrapper.thumbnailUrl
isOnlyEmoji: wrapper.isOnlyEmoji
isSender: wrapper.isSender
isEncrypted: wrapper.isEncrypted
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
replyTo: wrapper.replyTo
userId: wrapper.userId
userName: wrapper.userName
reactions: wrapper.reactions
trustlevel: wrapper.trustlevel
timestamp: wrapper.timestamp
status: wrapper.status
y: section.visible && section.active ? section.y + section.height : 0 y: section.visible && section.active ? section.y + section.height : 0
HoverHandler { HoverHandler {
@ -386,7 +446,7 @@ ScrollView {
if (hovered) { if (hovered) {
if (!messageActionHover.hovered) { if (!messageActionHover.hovered) {
messageActions.attached = timelinerow; messageActions.attached = timelinerow;
messageActions.model = model; messageActions.model = timelinerow;
} }
} }
} }

View File

@ -21,15 +21,30 @@ Rectangle {
Reply { Reply {
id: replyPreview id: replyPreview
property var modelData: room ? room.getDump(room.reply, room.id) : {
}
visible: room && room.reply visible: room && room.reply
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 2 * 22 + 3 * 16 anchors.leftMargin: 2 * 22 + 3 * 16
anchors.right: closeReplyButton.left anchors.right: closeReplyButton.left
anchors.rightMargin: 2 * 22 + 3 * 16 anchors.rightMargin: 2 * 22 + 3 * 16
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
modelData: room ? room.getDump(room.reply, room.id) : {
}
userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window) userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window)
blurhash: modelData.blurhash ?? ""
body: modelData.body ?? ""
formattedBody: modelData.formattedBody ?? ""
eventId: modelData.eventId ?? ""
filename: modelData.filename ?? ""
filesize: modelData.filesize ?? ""
proportionalHeight: modelData.proportionalHeight ?? 1
type: modelData.type ?? MtxEvent.UnknownMessage
typeString: modelData.typeString ?? ""
url: modelData.url ?? ""
originalWidth: modelData.originalWidth ?? 0
isOnlyEmoji: modelData.isOnlyEmoji ?? false
userId: modelData.userId ?? ""
userName: modelData.userName ?? ""
} }
ImageButton { ImageButton {

View File

@ -9,14 +9,17 @@ import im.nheko 1.0
ImageButton { ImageButton {
id: indicator id: indicator
required property int status
required property string eventId
width: 16 width: 16
height: 16 height: 16
hoverEnabled: true hoverEnabled: true
changeColorOnHover: (model.state == MtxEvent.Read) changeColorOnHover: (status == MtxEvent.Read)
cursor: (model.state == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
ToolTip.visible: hovered && model.state != MtxEvent.Empty ToolTip.visible: hovered && status != MtxEvent.Empty
ToolTip.text: { ToolTip.text: {
switch (model.state) { switch (status) {
case MtxEvent.Failed: case MtxEvent.Failed:
return qsTr("Failed"); return qsTr("Failed");
case MtxEvent.Sent: case MtxEvent.Sent:
@ -30,12 +33,12 @@ ImageButton {
} }
} }
onClicked: { onClicked: {
if (model.state == MtxEvent.Read) if (status == MtxEvent.Read)
room.readReceiptsAction(model.id); room.readReceiptsAction(eventId);
} }
image: { image: {
switch (model.state) { switch (status) {
case MtxEvent.Failed: case MtxEvent.Failed:
return ":/icons/icons/ui/remove-symbol.png"; return ":/icons/icons/ui/remove-symbol.png";
case MtxEvent.Sent: case MtxEvent.Sent:

View File

@ -11,6 +11,33 @@ import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
id: r
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash
required property string body
required property string formattedBody
required property string eventId
required property string filename
required property string filesize
required property string url
required property string thumbnailUrl
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property string replyTo
required property string userId
required property string userName
required property var reactions
required property int trustlevel
required property var timestamp
required property int status
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
height: row.height height: row.height
@ -28,19 +55,21 @@ Item {
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) onSingleTapped: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }
TapHandler { TapHandler {
onLongPressed: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) onLongPressed: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
onDoubleTapped: chat.model.reply = model.id onDoubleTapped: chat.model.reply = eventId
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }
RowLayout { RowLayout {
id: row id: row
property var replyData: chat.model.getDump(replyTo, eventId)
anchors.rightMargin: 1 anchors.rightMargin: 1
anchors.leftMargin: Nheko.avatarSize + 16 anchors.leftMargin: Nheko.avatarSize + 16
anchors.left: parent.left anchors.left: parent.left
@ -55,9 +84,23 @@ Item {
// fancy reply, if this is a reply // fancy reply, if this is a reply
Reply { Reply {
visible: model.replyTo visible: replyTo
modelData: chat.model.getDump(model.replyTo, model.id) userColor: TimelineManager.userColor(userId, Nheko.colors.base)
userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.base) blurhash: row.replyData.blurhash ?? ""
body: row.replyData.body ?? ""
formattedBody: row.replyData.formattedBody ?? ""
eventId: row.replyData.eventId ?? ""
filename: row.replyData.filename ?? ""
filesize: row.replyData.filesize ?? ""
proportionalHeight: row.replyData.proportionalHeight ?? 1
type: row.replyData.type ?? MtxEvent.UnknownMessage
typeString: row.replyData.typeString ?? ""
url: row.replyData.url ?? ""
originalWidth: row.replyData.originalWidth ?? 0
isOnlyEmoji: row.replyData.isOnlyEmoji ?? false
userId: row.replyData.userId ?? ""
userName: row.replyData.userName ?? ""
thumbnailUrl: row.replyData.thumbnailUrl ?? ""
} }
// actual message content // actual message content
@ -65,14 +108,29 @@ Item {
id: contentItem id: contentItem
width: parent.width width: parent.width
modelData: model blurhash: r.blurhash
body: r.body
formattedBody: r.formattedBody
eventId: r.eventId
filename: r.filename
filesize: r.filesize
proportionalHeight: r.proportionalHeight
type: r.type
typeString: r.typeString ?? ""
url: r.url
thumbnailUrl: r.thumbnailUrl
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
userId: r.userId
userName: r.userName
isReply: false
} }
Reactions { Reactions {
id: reactionRow id: reactionRow
reactions: model.reactions reactions: r.reactions
eventId: model.id eventId: r.eventId
} }
} }
@ -81,19 +139,21 @@ Item {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
width: 16 width: 16
status: r.status
eventId: r.eventId
} }
EncryptionIndicator { EncryptionIndicator {
visible: room.isEncrypted visible: room.isEncrypted
encrypted: model.isEncrypted encrypted: isEncrypted
trust: model.trustlevel trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
} }
Image { Image {
visible: model.isEdited || model.id == chat.model.edit visible: isEdited || eventId == chat.model.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
@ -101,7 +161,7 @@ Item {
width: 16 width: 16
sourceSize.width: 16 sourceSize.width: 16
sourceSize.height: 16 sourceSize.height: 16
source: "image://colorimage/:/icons/icons/ui/edit.png?" + ((model.id == chat.model.edit) ? Nheko.colors.highlight : Nheko.colors.buttonText) source: "image://colorimage/:/icons/icons/ui/edit.png?" + ((eventId == chat.model.edit) ? Nheko.colors.highlight : Nheko.colors.buttonText)
ToolTip.visible: editHovered.hovered ToolTip.visible: editHovered.hovered
ToolTip.text: qsTr("Edited") ToolTip.text: qsTr("Edited")
@ -113,11 +173,11 @@ Item {
Label { Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: model.timestamp.toLocaleTimeString(Locale.ShortFormat) text: timestamp.toLocaleTimeString(Locale.ShortFormat)
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth) width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: Nheko.inactiveColors.text color: Nheko.inactiveColors.text
ToolTip.visible: ma.hovered ToolTip.visible: ma.hovered
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
HoverHandler { HoverHandler {
id: ma id: ma

View File

@ -7,6 +7,10 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
required property string eventId
required property string filename
required property string filesize
height: row.height + 24 height: row.height + 24
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
@ -34,7 +38,7 @@ Item {
} }
TapHandler { TapHandler {
onSingleTapped: room.saveMedia(model.data.id) onSingleTapped: room.saveMedia(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }
@ -49,20 +53,20 @@ Item {
id: col id: col
Text { Text {
id: filename id: filename_
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.filename text: filename
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: Nheko.colors.text
} }
Text { Text {
id: filesize id: filesize_
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.filesize text: filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: Nheko.colors.text
@ -77,7 +81,7 @@ Item {
z: -1 z: -1
radius: 10 radius: 10
height: row.height + 24 height: row.height + 24
width: 44 + 24 + 24 + Math.max(Math.min(filesize.width, filesize.implicitWidth), Math.min(filename.width, filename.implicitWidth)) width: 44 + 24 + 24 + Math.max(Math.min(filesize_.width, filesize_.implicitWidth), Math.min(filename_.width, filename_.implicitWidth))
} }
} }

View File

@ -6,20 +6,28 @@ import QtQuick 2.12
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) required property int type
property double tempHeight: tempWidth * model.data.proportionalHeight required property int originalWidth
property double divisor: model.isReply ? 5 : 3 required property double proportionalHeight
required property string url
required property string blurhash
required property string body
required property string filename
required property bool isReply
property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 200 : originalWidth)
property double tempHeight: tempWidth * proportionalHeight
property double divisor: isReply ? 5 : 3
property bool tooHigh: tempHeight > timelineView.height / divisor property bool tooHigh: tempHeight > timelineView.height / divisor
height: Math.round(tooHigh ? timelineView.height / divisor : tempHeight) height: Math.round(tooHigh ? timelineView.height / divisor : tempHeight)
width: Math.round(tooHigh ? (timelineView.height / divisor) / model.data.proportionalHeight : tempWidth) width: Math.round(tooHigh ? (timelineView.height / divisor) / proportionalHeight : tempWidth)
Image { Image {
id: blurhash id: blurhash_
anchors.fill: parent anchors.fill: parent
visible: img.status != Image.Ready visible: img.status != Image.Ready
source: model.data.blurhash ? ("image://blurhash/" + model.data.blurhash) : ("image://colorimage/:/icons/icons/ui/do-not-disturb-rounded-sign@2x.png?" + Nheko.colors.buttonText) source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/do-not-disturb-rounded-sign@2x.png?" + Nheko.colors.buttonText)
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize.width: parent.width sourceSize.width: parent.width
@ -30,16 +38,16 @@ Item {
id: img id: img
anchors.fill: parent anchors.fill: parent
source: model.data.url.replace("mxc://", "image://MxcImage/") source: url.replace("mxc://", "image://MxcImage/")
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true smooth: true
mipmap: true mipmap: true
TapHandler { TapHandler {
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready enabled: type == MtxEvent.ImageMessage && img.status == Image.Ready
onSingleTapped: { onSingleTapped: {
TimelineManager.openImageOverlay(model.data.url, model.data.id); TimelineManager.openImageOverlay(url, room.data.eventId);
eventPoint.accepted = true; eventPoint.accepted = true;
} }
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
@ -73,7 +81,7 @@ Item {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
// See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530
text: model.data.filename ? model.data.filename : model.data.body text: filename ? filename : body
color: Nheko.colors.text color: Nheko.colors.text
} }

View File

@ -6,32 +6,41 @@ import QtQuick 2.6
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
property alias modelData: model.data id: d
property alias isReply: model.isReply
required property bool isReply
property alias child: chooser.child property alias child: chooser.child
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash
required property string body
required property string formattedBody
required property string eventId
required property string filename
required property string filesize
required property string url
required property string thumbnailUrl
required property bool isOnlyEmoji
required property string userId
required property string userName
height: chooser.childrenRect.height height: chooser.childrenRect.height
// Workaround to have an assignable global property
Item {
id: model
property var data
property bool isReply: false
}
DelegateChooser { DelegateChooser {
id: chooser id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue //role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type roleValue: type
anchors.fill: parent anchors.fill: parent
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.UnknownMessage roleValue: MtxEvent.UnknownMessage
Placeholder { Placeholder {
typeString: d.typeString
text: "Unretrieved event" text: "Unretrieved event"
} }
@ -41,6 +50,10 @@ Item {
roleValue: MtxEvent.TextMessage roleValue: MtxEvent.TextMessage
TextMessage { TextMessage {
formatted: d.formattedBody
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
} }
} }
@ -49,6 +62,10 @@ Item {
roleValue: MtxEvent.NoticeMessage roleValue: MtxEvent.NoticeMessage
NoticeMessage { NoticeMessage {
formatted: d.formattedBody
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
} }
} }
@ -57,8 +74,11 @@ Item {
roleValue: MtxEvent.EmoteMessage roleValue: MtxEvent.EmoteMessage
NoticeMessage { NoticeMessage {
formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody
color: TimelineManager.userColor(modelData.userId, Nheko.colors.window) color: TimelineManager.userColor(d.userId, Nheko.colors.window)
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
} }
} }
@ -67,6 +87,14 @@ Item {
roleValue: MtxEvent.ImageMessage roleValue: MtxEvent.ImageMessage
ImageMessage { ImageMessage {
type: d.type
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
url: d.url
blurhash: d.blurhash
body: d.body
filename: d.filename
isReply: d.isReply
} }
} }
@ -75,6 +103,14 @@ Item {
roleValue: MtxEvent.Sticker roleValue: MtxEvent.Sticker
ImageMessage { ImageMessage {
type: d.type
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
url: d.url
blurhash: d.blurhash
body: d.body
filename: d.filename
isReply: d.isReply
} }
} }
@ -83,6 +119,9 @@ Item {
roleValue: MtxEvent.FileMessage roleValue: MtxEvent.FileMessage
FileMessage { FileMessage {
eventId: d.eventId
filename: d.filename
filesize: d.filesize
} }
} }
@ -91,6 +130,14 @@ Item {
roleValue: MtxEvent.VideoMessage roleValue: MtxEvent.VideoMessage
PlayableMediaMessage { PlayableMediaMessage {
proportionalHeight: d.proportionalHeight
type: d.type
originalWidth: d.originalWidth
thumbnailUrl: d.thumbnailUrl
eventId: d.eventId
url: d.url
body: d.body
filesize: d.filesize
} }
} }
@ -99,6 +146,14 @@ Item {
roleValue: MtxEvent.AudioMessage roleValue: MtxEvent.AudioMessage
PlayableMediaMessage { PlayableMediaMessage {
proportionalHeight: d.proportionalHeight
type: d.type
originalWidth: d.originalWidth
thumbnailUrl: d.thumbnailUrl
eventId: d.eventId
url: d.url
body: d.body
filesize: d.filesize
} }
} }
@ -134,7 +189,10 @@ Item {
roleValue: MtxEvent.Name roleValue: MtxEvent.Name
NoticeMessage { NoticeMessage {
text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name") body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
} }
} }
@ -143,7 +201,10 @@ Item {
roleValue: MtxEvent.Topic roleValue: MtxEvent.Topic
NoticeMessage { NoticeMessage {
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic") body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
} }
} }
@ -152,7 +213,10 @@ Item {
roleValue: MtxEvent.Avatar roleValue: MtxEvent.Avatar
NoticeMessage { NoticeMessage {
text: qsTr("%1 changed the room avatar").arg(model.data.userName) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("%1 changed the room avatar").arg(d.userName)
} }
} }
@ -161,7 +225,10 @@ Item {
roleValue: MtxEvent.RoomCreate roleValue: MtxEvent.RoomCreate
NoticeMessage { NoticeMessage {
text: qsTr("%1 created and configured room: %2").arg(model.data.userName).arg(model.data.roomId) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(model.data.roomId)
} }
} }
@ -170,14 +237,17 @@ Item {
roleValue: MtxEvent.CallInvite roleValue: MtxEvent.CallInvite
NoticeMessage { NoticeMessage {
text: { body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: {
switch (model.data.callType) { switch (model.data.callType) {
case "voice": case "voice":
return qsTr("%1 placed a voice call.").arg(model.data.userName); return qsTr("%1 placed a voice call.").arg(d.userName);
case "video": case "video":
return qsTr("%1 placed a video call.").arg(model.data.userName); return qsTr("%1 placed a video call.").arg(d.userName);
default: default:
return qsTr("%1 placed a call.").arg(model.data.userName); return qsTr("%1 placed a call.").arg(d.userName);
} }
} }
} }
@ -188,7 +258,10 @@ Item {
roleValue: MtxEvent.CallAnswer roleValue: MtxEvent.CallAnswer
NoticeMessage { NoticeMessage {
text: qsTr("%1 answered the call.").arg(model.data.userName) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("%1 answered the call.").arg(d.userName)
} }
} }
@ -197,7 +270,10 @@ Item {
roleValue: MtxEvent.CallHangUp roleValue: MtxEvent.CallHangUp
NoticeMessage { NoticeMessage {
text: qsTr("%1 ended the call.").arg(model.data.userName) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("%1 ended the call.").arg(d.userName)
} }
} }
@ -206,7 +282,10 @@ Item {
roleValue: MtxEvent.CallCandidates roleValue: MtxEvent.CallCandidates
NoticeMessage { NoticeMessage {
text: qsTr("Negotiating call...") body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("Negotiating call...")
} }
} }
@ -216,7 +295,10 @@ Item {
roleValue: MtxEvent.PowerLevels roleValue: MtxEvent.PowerLevels
NoticeMessage { NoticeMessage {
text: room.formatPowerLevelEvent(model.data.id) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: room.formatPowerLevelEvent(d.eventId)
} }
} }
@ -225,7 +307,10 @@ Item {
roleValue: MtxEvent.RoomJoinRules roleValue: MtxEvent.RoomJoinRules
NoticeMessage { NoticeMessage {
text: room.formatJoinRuleEvent(model.data.id) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: room.formatJoinRuleEvent(d.eventId)
} }
} }
@ -234,7 +319,10 @@ Item {
roleValue: MtxEvent.RoomHistoryVisibility roleValue: MtxEvent.RoomHistoryVisibility
NoticeMessage { NoticeMessage {
text: room.formatHistoryVisibilityEvent(model.data.id) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: room.formatHistoryVisibilityEvent(d.eventId)
} }
} }
@ -243,7 +331,10 @@ Item {
roleValue: MtxEvent.RoomGuestAccess roleValue: MtxEvent.RoomGuestAccess
NoticeMessage { NoticeMessage {
text: room.formatGuestAccessEvent(model.data.id) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: room.formatGuestAccessEvent(d.eventId)
} }
} }
@ -252,7 +343,10 @@ Item {
roleValue: MtxEvent.Member roleValue: MtxEvent.Member
NoticeMessage { NoticeMessage {
text: room.formatMemberEvent(model.data.id) body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: room.formatMemberEvent(d.eventId)
} }
} }
@ -261,7 +355,10 @@ Item {
roleValue: MtxEvent.KeyVerificationRequest roleValue: MtxEvent.KeyVerificationRequest
NoticeMessage { NoticeMessage {
text: "KeyVerificationRequest" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationRequest"
} }
} }
@ -270,7 +367,10 @@ Item {
roleValue: MtxEvent.KeyVerificationStart roleValue: MtxEvent.KeyVerificationStart
NoticeMessage { NoticeMessage {
text: "KeyVerificationStart" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationStart"
} }
} }
@ -279,7 +379,10 @@ Item {
roleValue: MtxEvent.KeyVerificationReady roleValue: MtxEvent.KeyVerificationReady
NoticeMessage { NoticeMessage {
text: "KeyVerificationReady" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationReady"
} }
} }
@ -288,7 +391,10 @@ Item {
roleValue: MtxEvent.KeyVerificationCancel roleValue: MtxEvent.KeyVerificationCancel
NoticeMessage { NoticeMessage {
text: "KeyVerificationCancel" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationCancel"
} }
} }
@ -297,7 +403,10 @@ Item {
roleValue: MtxEvent.KeyVerificationKey roleValue: MtxEvent.KeyVerificationKey
NoticeMessage { NoticeMessage {
text: "KeyVerificationKey" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationKey"
} }
} }
@ -306,7 +415,10 @@ Item {
roleValue: MtxEvent.KeyVerificationMac roleValue: MtxEvent.KeyVerificationMac
NoticeMessage { NoticeMessage {
text: "KeyVerificationMac" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationMac"
} }
} }
@ -315,7 +427,10 @@ Item {
roleValue: MtxEvent.KeyVerificationDone roleValue: MtxEvent.KeyVerificationDone
NoticeMessage { NoticeMessage {
text: "KeyVerificationDone" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationDone"
} }
} }
@ -324,7 +439,10 @@ Item {
roleValue: MtxEvent.KeyVerificationDone roleValue: MtxEvent.KeyVerificationDone
NoticeMessage { NoticeMessage {
text: "KeyVerificationDone" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationDone"
} }
} }
@ -333,13 +451,17 @@ Item {
roleValue: MtxEvent.KeyVerificationAccept roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage { NoticeMessage {
text: "KeyVerificationAccept" body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationAccept"
} }
} }
DelegateChoice { DelegateChoice {
Placeholder { Placeholder {
typeString: d.typeString
} }
} }

View File

@ -6,7 +6,9 @@ import ".."
import im.nheko 1.0 import im.nheko 1.0
MatrixText { MatrixText {
text: qsTr("unimplemented event: ") + model.data.typeString required property string typeString
text: qsTr("unimplemented event: ") + typeString
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
color: Nheko.inactiveColors.text color: Nheko.inactiveColors.text
} }

View File

@ -12,10 +12,21 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: bg id: bg
required property double proportionalHeight
required property int type
required property int originalWidth
required property string thumbnailUrl
required property string eventId
required property string url
required property string body
required property string filesize
radius: 10 radius: 10
color: Nheko.colors.alternateBase color: Nheko.colors.alternateBase
height: Math.round(content.height + 24) height: Math.round(content.height + 24)
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
ListView.onPooled: height = 4
ListView.onReused: height = Math.round(content.height + 24)
Column { Column {
id: content id: content
@ -26,18 +37,18 @@ Rectangle {
Rectangle { Rectangle {
id: videoContainer id: videoContainer
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : model.data.width) property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
property double tempHeight: tempWidth * model.data.proportionalHeight property double tempHeight: tempWidth * proportionalHeight
property double divisor: model.isReply ? 4 : 2 property double divisor: isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineView.height / divisor property bool tooHigh: tempHeight > timelineView.height / divisor
visible: model.data.type == MtxEvent.VideoMessage visible: type == MtxEvent.VideoMessage
height: tooHigh ? timelineView.height / divisor : tempHeight height: tooHigh ? timelineView.height / divisor : tempHeight
width: tooHigh ? (timelineView.height / divisor) / model.data.proportionalHeight : tempWidth width: tooHigh ? (timelineView.height / divisor) / proportionalHeight : tempWidth
Image { Image {
anchors.fill: parent anchors.fill: parent
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/") source: thumbnailUrl.replace("mxc://", "image://MxcImage/")
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
@ -121,7 +132,7 @@ Rectangle {
onClicked: { onClicked: {
switch (button.state) { switch (button.state) {
case "": case "":
room.cacheMedia(model.data.id); room.cacheMedia(eventId);
break; break;
case "stopped": case "stopped":
media.play(); media.play();
@ -176,7 +187,7 @@ Rectangle {
Connections { Connections {
target: room target: room
onMediaCached: { onMediaCached: {
if (mxcUrl == model.data.url) { if (mxcUrl == url) {
media.source = cacheUrl; media.source = cacheUrl;
button.state = "stopped"; button.state = "stopped";
console.log("media loaded: " + mxcUrl + " at " + cacheUrl); console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
@ -192,14 +203,14 @@ Rectangle {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.body text: body
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: Nheko.colors.text
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.data.filesize text: filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: Nheko.colors.text

View File

@ -9,16 +9,30 @@ import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
id: replyComponent id: r
property alias modelData: reply.modelData
property color userColor: "red" property color userColor: "red"
property double proportionalHeight
property int type
property string typeString
property int originalWidth
property string blurhash
property string body
property string formattedBody
property string eventId
property string filename
property string filesize
property string url
property bool isOnlyEmoji
property string userId
property string userName
property string thumbnailUrl
width: parent.width width: parent.width
height: replyContainer.height height: replyContainer.height
TapHandler { TapHandler {
onSingleTapped: chat.model.showEvent(modelData.id) onSingleTapped: chat.model.showEvent(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }
@ -33,7 +47,7 @@ Item {
anchors.top: replyContainer.top anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom anchors.bottom: replyContainer.bottom
width: 4 width: 4
color: TimelineManager.userColor(reply.modelData.userId, Nheko.colors.window) color: TimelineManager.userColor(userId, Nheko.colors.window)
} }
Column { Column {
@ -44,14 +58,14 @@ Item {
width: parent.width - 8 width: parent.width - 8
Text { Text {
id: userName id: userName_
text: TimelineManager.escapeEmoji(reply.modelData.userName) text: TimelineManager.escapeEmoji(userName)
color: replyComponent.userColor color: r.userColor
textFormat: Text.RichText textFormat: Text.RichText
TapHandler { TapHandler {
onSingleTapped: chat.model.openUserProfile(reply.modelData.userId) onSingleTapped: chat.model.openUserProfile(userId)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }
@ -60,6 +74,21 @@ Item {
MessageDelegate { MessageDelegate {
id: reply id: reply
blurhash: r.blurhash
body: r.body
formattedBody: r.formattedBody
eventId: r.eventId
filename: r.filename
filesize: r.filesize
proportionalHeight: r.proportionalHeight
type: r.type
typeString: r.typeString ?? ""
url: r.url
thumbnailUrl: r.thumbnailUrl
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
userId: r.userId
userName: r.userName
enabled: false enabled: false
width: parent.width width: parent.width
isReply: true isReply: true
@ -72,7 +101,7 @@ Item {
z: -1 z: -1
height: replyContainer.height height: replyContainer.height
width: Math.min(Math.max(reply.implicitWidth, userName.implicitWidth) + 8 + 4, parent.width) width: Math.min(Math.max(reply.implicitWidth, userName_.implicitWidth) + 8 + 4, parent.width)
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.1) color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.1)
} }

View File

@ -6,8 +6,11 @@ import ".."
import im.nheko 1.0 import im.nheko 1.0
MatrixText { MatrixText {
property string formatted: model.data.formattedBody required property string body
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : model.data.body required property bool isOnlyEmoji
required property bool isReply
required property string formatted
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
// table border-collapse doesn't seem to work // table border-collapse doesn't seem to work
text: " text: "
@ -31,5 +34,5 @@ MatrixText {
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : undefined height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : undefined
clip: isReply clip: isReply
selectByMouse: !Settings.mobileMode && !isReply selectByMouse: !Settings.mobileMode && !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
} }

View File

@ -35,7 +35,7 @@ Rectangle {
height: Nheko.avatarSize height: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: CallManager.callParty displayName: CallManager.callParty
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id) onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
} }
Label { Label {

View File

@ -42,7 +42,7 @@ Rectangle {
height: Nheko.avatarSize height: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: CallManager.callParty displayName: CallManager.callParty
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id) onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
} }
Label { Label {

View File

@ -79,7 +79,7 @@ Popup {
height: Nheko.avatarSize height: Nheko.avatarSize
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: room.roomName displayName: room.roomName
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id) onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
} }
Button { Button {

View File

@ -427,11 +427,11 @@ TimelineModel::roleNames() const
{Filename, "filename"}, {Filename, "filename"},
{Filesize, "filesize"}, {Filesize, "filesize"},
{MimeType, "mimetype"}, {MimeType, "mimetype"},
{Height, "height"}, {OriginalHeight, "originalHeight"},
{Width, "width"}, {OriginalWidth, "originalWidth"},
{ProportionalHeight, "proportionalHeight"}, {ProportionalHeight, "proportionalHeight"},
{Id, "id"}, {EventId, "eventId"},
{State, "state"}, {State, "status"},
{IsEdited, "isEdited"}, {IsEdited, "isEdited"},
{IsEditable, "isEditable"}, {IsEditable, "isEditable"},
{IsEncrypted, "isEncrypted"}, {IsEncrypted, "isEncrypted"},
@ -556,9 +556,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return QVariant(utils::humanReadableFileSize(filesize(event))); return QVariant(utils::humanReadableFileSize(filesize(event)));
case MimeType: case MimeType:
return QVariant(QString::fromStdString(mimetype(event))); return QVariant(QString::fromStdString(mimetype(event)));
case Height: case OriginalHeight:
return QVariant(qulonglong{media_height(event)}); return QVariant(qulonglong{media_height(event)});
case Width: case OriginalWidth:
return QVariant(qulonglong{media_width(event)}); return QVariant(qulonglong{media_width(event)});
case ProportionalHeight: { case ProportionalHeight: {
auto w = media_width(event); auto w = media_width(event);
@ -569,7 +569,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return QVariant(prop > 0 ? prop : 1.); return QVariant(prop > 0 ? prop : 1.);
} }
case Id: { case EventId: {
if (auto replaces = relations(event).replaces()) if (auto replaces = relations(event).replaces())
return QVariant(QString::fromStdString(replaces.value())); return QVariant(QString::fromStdString(replaces.value()));
else else
@ -660,11 +660,11 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
m.insert(names[Filename], data(event, static_cast<int>(Filename))); m.insert(names[Filename], data(event, static_cast<int>(Filename)));
m.insert(names[Filesize], data(event, static_cast<int>(Filesize))); m.insert(names[Filesize], data(event, static_cast<int>(Filesize)));
m.insert(names[MimeType], data(event, static_cast<int>(MimeType))); m.insert(names[MimeType], data(event, static_cast<int>(MimeType)));
m.insert(names[Height], data(event, static_cast<int>(Height))); m.insert(names[OriginalHeight], data(event, static_cast<int>(OriginalHeight)));
m.insert(names[Width], data(event, static_cast<int>(Width))); m.insert(names[OriginalWidth], data(event, static_cast<int>(OriginalWidth)));
m.insert(names[ProportionalHeight], m.insert(names[ProportionalHeight],
data(event, static_cast<int>(ProportionalHeight))); data(event, static_cast<int>(ProportionalHeight)));
m.insert(names[Id], data(event, static_cast<int>(Id))); m.insert(names[EventId], data(event, static_cast<int>(EventId)));
m.insert(names[State], data(event, static_cast<int>(State))); m.insert(names[State], data(event, static_cast<int>(State)));
m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited))); m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited)));
m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable))); m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable)));

View File

@ -192,10 +192,10 @@ public:
Filename, Filename,
Filesize, Filesize,
MimeType, MimeType,
Height, OriginalHeight,
Width, OriginalWidth,
ProportionalHeight, ProportionalHeight,
Id, EventId,
State, State,
IsEdited, IsEdited,
IsEditable, IsEditable,
@ -245,6 +245,11 @@ public:
Q_INVOKABLE void showEvent(QString eventId); Q_INVOKABLE void showEvent(QString eventId);
Q_INVOKABLE void copyLinkToEvent(QString eventId) const; Q_INVOKABLE void copyLinkToEvent(QString eventId) const;
void cacheMedia(QString eventId, std::function<void(const QString filename)> callback); void cacheMedia(QString eventId, std::function<void(const QString filename)> callback);
Q_INVOKABLE void sendReset()
{
beginResetModel();
endResetModel();
}
std::vector<::Reaction> reactions(const std::string &event_id) std::vector<::Reaction> reactions(const std::string &event_id)
{ {