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 {
id: replyPreview
modelData: room ? room.getDump(mid, "") : {
property var modelData: room ? room.getDump(mid, "") : {
}
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 {

View File

@ -7,8 +7,8 @@ import "./emoji"
import "./ui"
import Qt.labs.platform 1.1 as Platform
import QtGraphicalEffects 1.0
import QtQuick 2.12
import QtQuick.Controls 2.3
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
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
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
pixelAligned: true
spacing: 4
@ -84,7 +87,7 @@ ScrollView {
ToolTip.text: qsTr("Edit")
onClicked: {
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.text: qsTr("React")
emojiPicker: emojiPopup
event_id: row.model ? row.model.id : ""
event_id: row.model ? row.model.eventId : ""
}
ImageButton {
@ -110,7 +113,7 @@ ScrollView {
image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(row.model.id)
onClicked: chat.model.replyAction(row.model.eventId)
}
ImageButton {
@ -121,7 +124,7 @@ ScrollView {
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
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
bottomPadding: 4
spacing: 8
visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day)
visible: (previousMessageUserId !== userId || previousMessageDay !== day)
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 {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: modelData && modelData.previousMessageDay !== modelData.day
text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : ""
visible: previousMessageDay !== day
text: chat.model.formatDateSeparator(timestamp)
color: Nheko.colors.text
height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2
@ -236,7 +239,7 @@ ScrollView {
}
Row {
height: userName.height
height: userName_.height
spacing: 8
Avatar {
@ -244,10 +247,10 @@ ScrollView {
width: Nheko.avatarSize
height: Nheko.avatarSize
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
displayName: modelData ? modelData.userName : ""
userid: modelData ? modelData.userId : ""
onClicked: chat.model.openUserProfile(modelData.userId)
url: chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
displayName: userName
userid: userId
onClicked: chat.model.openUserProfile(userId)
ToolTip.visible: avatarHover.hovered
ToolTip.text: userid
@ -260,22 +263,22 @@ ScrollView {
Connections {
target: chat.model
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)
}
Label {
id: userName
id: userName_
text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : ""
color: TimelineManager.userColor(modelData ? modelData.userId : "", Nheko.colors.window)
text: TimelineManager.escapeEmoji(userName)
color: TimelineManager.userColor(userId, Nheko.colors.window)
textFormat: Text.RichText
ToolTip.visible: displayNameHover.hovered
ToolTip.text: modelData ? modelData.userId : ""
ToolTip.text: userId
TapHandler {
onSingleTapped: chat.model.openUserProfile(modelData.userId)
onSingleTapped: chat.model.openUserProfile(userId)
}
CursorShape {
@ -291,7 +294,7 @@ ScrollView {
Label {
color: Nheko.colors.buttonText
text: modelData ? TimelineManager.userStatus(modelData.userId) : ""
text: TimelineManager.userStatus(userId)
textFormat: Text.PlainText
elide: Text.ElideRight
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
@ -307,7 +310,35 @@ ScrollView {
delegate: Item {
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
width: chat.delegateMaxWidth
@ -362,10 +393,15 @@ ScrollView {
Loader {
id: section
property var modelData: model
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
sourceComponent: sectionHeader
visible: status == Loader.Ready
@ -376,6 +412,30 @@ ScrollView {
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
HoverHandler {
@ -386,7 +446,7 @@ ScrollView {
if (hovered) {
if (!messageActionHover.hovered) {
messageActions.attached = timelinerow;
messageActions.model = model;
messageActions.model = timelinerow;
}
}
}

View File

@ -21,15 +21,30 @@ Rectangle {
Reply {
id: replyPreview
property var modelData: room ? room.getDump(room.reply, room.id) : {
}
visible: room && room.reply
anchors.left: parent.left
anchors.leftMargin: 2 * 22 + 3 * 16
anchors.right: closeReplyButton.left
anchors.rightMargin: 2 * 22 + 3 * 16
anchors.bottom: parent.bottom
modelData: room ? room.getDump(room.reply, room.id) : {
}
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 {

View File

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

View File

@ -11,6 +11,33 @@ import QtQuick.Window 2.2
import im.nheko 1.0
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.right: parent.right
height: row.height
@ -28,19 +55,21 @@ Item {
TapHandler {
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
}
TapHandler {
onLongPressed: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
onDoubleTapped: chat.model.reply = model.id
onLongPressed: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
onDoubleTapped: chat.model.reply = eventId
gesturePolicy: TapHandler.ReleaseWithinBounds
}
RowLayout {
id: row
property var replyData: chat.model.getDump(replyTo, eventId)
anchors.rightMargin: 1
anchors.leftMargin: Nheko.avatarSize + 16
anchors.left: parent.left
@ -55,9 +84,23 @@ Item {
// fancy reply, if this is a reply
Reply {
visible: model.replyTo
modelData: chat.model.getDump(model.replyTo, model.id)
userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.base)
visible: replyTo
userColor: TimelineManager.userColor(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
@ -65,14 +108,29 @@ Item {
id: contentItem
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 {
id: reactionRow
reactions: model.reactions
eventId: model.id
reactions: r.reactions
eventId: r.eventId
}
}
@ -81,19 +139,21 @@ Item {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
status: r.status
eventId: r.eventId
}
EncryptionIndicator {
visible: room.isEncrypted
encrypted: model.isEncrypted
trust: model.trustlevel
encrypted: isEncrypted
trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
Layout.preferredWidth: 16
}
Image {
visible: model.isEdited || model.id == chat.model.edit
visible: isEdited || eventId == chat.model.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
Layout.preferredWidth: 16
@ -101,7 +161,7 @@ Item {
width: 16
sourceSize.width: 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.text: qsTr("Edited")
@ -113,11 +173,11 @@ Item {
Label {
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)
color: Nheko.inactiveColors.text
ToolTip.visible: ma.hovered
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
HoverHandler {
id: ma

View File

@ -7,6 +7,10 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0
Item {
required property string eventId
required property string filename
required property string filesize
height: row.height + 24
width: parent ? parent.width : undefined
@ -34,7 +38,7 @@ Item {
}
TapHandler {
onSingleTapped: room.saveMedia(model.data.id)
onSingleTapped: room.saveMedia(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds
}
@ -49,20 +53,20 @@ Item {
id: col
Text {
id: filename
id: filename_
Layout.fillWidth: true
text: model.data.filename
text: filename
textFormat: Text.PlainText
elide: Text.ElideRight
color: Nheko.colors.text
}
Text {
id: filesize
id: filesize_
Layout.fillWidth: true
text: model.data.filesize
text: filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: Nheko.colors.text
@ -77,7 +81,7 @@ Item {
z: -1
radius: 10
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
Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 5 : 3
required property int type
required property int originalWidth
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
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 {
id: blurhash
id: blurhash_
anchors.fill: parent
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
fillMode: Image.PreserveAspectFit
sourceSize.width: parent.width
@ -30,16 +38,16 @@ Item {
id: img
anchors.fill: parent
source: model.data.url.replace("mxc://", "image://MxcImage/")
source: url.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
TapHandler {
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
enabled: type == MtxEvent.ImageMessage && img.status == Image.Ready
onSingleTapped: {
TimelineManager.openImageOverlay(model.data.url, model.data.id);
TimelineManager.openImageOverlay(url, room.data.eventId);
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
@ -73,7 +81,7 @@ Item {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
// 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
}

View File

@ -6,32 +6,41 @@ import QtQuick 2.6
import im.nheko 1.0
Item {
property alias modelData: model.data
property alias isReply: model.isReply
id: d
required property bool isReply
property alias child: chooser.child
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
// Workaround to have an assignable global property
Item {
id: model
property var data
property bool isReply: false
}
DelegateChooser {
id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type
roleValue: type
anchors.fill: parent
DelegateChoice {
roleValue: MtxEvent.UnknownMessage
Placeholder {
typeString: d.typeString
text: "Unretrieved event"
}
@ -41,6 +50,10 @@ Item {
roleValue: MtxEvent.TextMessage
TextMessage {
formatted: d.formattedBody
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
}
}
@ -49,6 +62,10 @@ Item {
roleValue: MtxEvent.NoticeMessage
NoticeMessage {
formatted: d.formattedBody
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
}
}
@ -57,8 +74,11 @@ Item {
roleValue: MtxEvent.EmoteMessage
NoticeMessage {
formatted: TimelineManager.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
color: TimelineManager.userColor(modelData.userId, Nheko.colors.window)
formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody
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
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
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
FileMessage {
eventId: d.eventId
filename: d.filename
filesize: d.filesize
}
}
@ -91,6 +130,14 @@ Item {
roleValue: MtxEvent.VideoMessage
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
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
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
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
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
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
NoticeMessage {
text: {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: {
switch (model.data.callType) {
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":
return qsTr("%1 placed a video call.").arg(model.data.userName);
return qsTr("%1 placed a video call.").arg(d.userName);
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
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
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
NoticeMessage {
text: qsTr("Negotiating call...")
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("Negotiating call...")
}
}
@ -216,7 +295,10 @@ Item {
roleValue: MtxEvent.PowerLevels
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
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
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
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
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
NoticeMessage {
text: "KeyVerificationRequest"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationRequest"
}
}
@ -270,7 +367,10 @@ Item {
roleValue: MtxEvent.KeyVerificationStart
NoticeMessage {
text: "KeyVerificationStart"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationStart"
}
}
@ -279,7 +379,10 @@ Item {
roleValue: MtxEvent.KeyVerificationReady
NoticeMessage {
text: "KeyVerificationReady"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationReady"
}
}
@ -288,7 +391,10 @@ Item {
roleValue: MtxEvent.KeyVerificationCancel
NoticeMessage {
text: "KeyVerificationCancel"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationCancel"
}
}
@ -297,7 +403,10 @@ Item {
roleValue: MtxEvent.KeyVerificationKey
NoticeMessage {
text: "KeyVerificationKey"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationKey"
}
}
@ -306,7 +415,10 @@ Item {
roleValue: MtxEvent.KeyVerificationMac
NoticeMessage {
text: "KeyVerificationMac"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationMac"
}
}
@ -315,7 +427,10 @@ Item {
roleValue: MtxEvent.KeyVerificationDone
NoticeMessage {
text: "KeyVerificationDone"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationDone"
}
}
@ -324,7 +439,10 @@ Item {
roleValue: MtxEvent.KeyVerificationDone
NoticeMessage {
text: "KeyVerificationDone"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationDone"
}
}
@ -333,13 +451,17 @@ Item {
roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage {
text: "KeyVerificationAccept"
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: "KeyVerificationAccept"
}
}
DelegateChoice {
Placeholder {
typeString: d.typeString
}
}

View File

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

View File

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

View File

@ -9,16 +9,30 @@ import QtQuick.Window 2.2
import im.nheko 1.0
Item {
id: replyComponent
id: r
property alias modelData: reply.modelData
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
height: replyContainer.height
TapHandler {
onSingleTapped: chat.model.showEvent(modelData.id)
onSingleTapped: chat.model.showEvent(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds
}
@ -33,7 +47,7 @@ Item {
anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom
width: 4
color: TimelineManager.userColor(reply.modelData.userId, Nheko.colors.window)
color: TimelineManager.userColor(userId, Nheko.colors.window)
}
Column {
@ -44,14 +58,14 @@ Item {
width: parent.width - 8
Text {
id: userName
id: userName_
text: TimelineManager.escapeEmoji(reply.modelData.userName)
color: replyComponent.userColor
text: TimelineManager.escapeEmoji(userName)
color: r.userColor
textFormat: Text.RichText
TapHandler {
onSingleTapped: chat.model.openUserProfile(reply.modelData.userId)
onSingleTapped: chat.model.openUserProfile(userId)
gesturePolicy: TapHandler.ReleaseWithinBounds
}
@ -60,6 +74,21 @@ Item {
MessageDelegate {
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
width: parent.width
isReply: true
@ -72,7 +101,7 @@ Item {
z: -1
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)
}

View File

@ -6,8 +6,11 @@ import ".."
import im.nheko 1.0
MatrixText {
property string formatted: model.data.formattedBody
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : model.data.body
required property string 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
text: "
@ -31,5 +34,5 @@ MatrixText {
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : undefined
clip: 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
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: CallManager.callParty
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.id)
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
}
Label {

View File

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

View File

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

View File

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

View File

@ -192,10 +192,10 @@ public:
Filename,
Filesize,
MimeType,
Height,
Width,
OriginalHeight,
OriginalWidth,
ProportionalHeight,
Id,
EventId,
State,
IsEdited,
IsEditable,
@ -245,6 +245,11 @@ public:
Q_INVOKABLE void showEvent(QString eventId);
Q_INVOKABLE void copyLinkToEvent(QString eventId) const;
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)
{