summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--resources/qml/TimelineDefaultMessageStyle.qml20
-rw-r--r--resources/qml/TimelineEvent.qml10
-rw-r--r--resources/qml/delegates/ImageMessage.qml30
-rw-r--r--resources/qml/delegates/MessageDelegate.qml780
-rw-r--r--resources/qml/delegates/PlayableMediaMessage.qml2
-rw-r--r--resources/qml/delegates/Reply.qml50
-rw-r--r--resources/qml/delegates/TextMessage.qml3
-rw-r--r--src/timeline/EventDelegateChooser.cpp120
-rw-r--r--src/timeline/EventDelegateChooser.h96
10 files changed, 211 insertions, 901 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7e02bf34..cbe2b20e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -742,7 +742,6 @@ set(QML_SOURCES
         resources/qml/delegates/Encrypted.qml
         resources/qml/delegates/FileMessage.qml
         resources/qml/delegates/ImageMessage.qml
-        resources/qml/delegates/MessageDelegate.qml
         resources/qml/delegates/NoticeMessage.qml
         resources/qml/delegates/Pill.qml
         resources/qml/delegates/Placeholder.qml
diff --git a/resources/qml/TimelineDefaultMessageStyle.qml b/resources/qml/TimelineDefaultMessageStyle.qml
index 0866fab6..8beaa8f0 100644
--- a/resources/qml/TimelineDefaultMessageStyle.qml
+++ b/resources/qml/TimelineDefaultMessageStyle.qml
@@ -18,7 +18,7 @@ TimelineEvent {
     id: wrapper
     ListView.delayRemove: true
     width: chat.delegateMaxWidth
-    height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 20)
+    height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
     anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
     //room: chatRoot.roommodel
 
@@ -51,6 +51,9 @@ TimelineEvent {
     property alias hovered: messageHover.hovered
     property bool scrolledToThis: false
 
+    mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4
+    replyInset: mainInset + 4 + Nheko.paddingSmall
+
     maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width
 
     data: [
@@ -182,16 +185,21 @@ TimelineEvent {
                     color: TimelineManager.userColor(wrapper.threadId, palette.base)
                 }
             }
+
+            Item {
+                visible: wrapper.isStateEvent
+                width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2
+                height: 1
+            }
+
             Column {
                 id: contentColumn
 
                 AbstractButton {
                     id: replyRow
                     visible: wrapper.reply
-                    //Layout.fillWidth: true
-                    //Layout.maximumHeight: timelineView.height / 8
-                    //Layout.preferredWidth: replyRowLay.implicitWidth
-                    //Layout.preferredHeight: replyRowLay.implicitHeight
+
+                    height: replyLine.height
 
                     property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
 
@@ -209,7 +217,7 @@ TimelineEvent {
 
                         Rectangle {
                             id: replyLine
-                            height: replyCol.height
+                            height: Math.min( wrapper.reply?.height, timelineView.height / 5) + Nheko.paddingSmall + replyUserButton.height
                             color: replyRow.userColor
                             width: 4
                         }
diff --git a/resources/qml/TimelineEvent.qml b/resources/qml/TimelineEvent.qml
index f1b6bd2a..5120fd12 100644
--- a/resources/qml/TimelineEvent.qml
+++ b/resources/qml/TimelineEvent.qml
@@ -62,12 +62,10 @@ EventDelegateChooser {
             required property string userId
             required property string userName
 
-            Layout.fillWidth: true
-            //Layout.maximumWidth: implicitWidth
-
             body: ''
             color: palette.buttonText
             font.italic: true
+            font.pointSize: Settings.fontSize * 0.8
             formatted: ''
             horizontalAlignment: Text.AlignHCenter
             isOnlyEmoji: false
@@ -202,7 +200,6 @@ EventDelegateChooser {
             id: member
 
             required property string formattedStateEvent
-            required property bool isReply
             required property Room room
             required property string userId
             required property string userName
@@ -212,7 +209,7 @@ EventDelegateChooser {
                 body: formatted
                 formatted: member.formattedStateEvent
                 isOnlyEmoji: false
-                isReply: member.isReply
+                isReply: EventDelegateChooser.isReply
                 isStateEvent: true
                 keepFullText: true
             }
@@ -233,7 +230,6 @@ EventDelegateChooser {
 
             required property string body
             required property string eventId
-            required property bool isReply
             required property Room room
             required property string userId
             required property string userName
@@ -243,7 +239,7 @@ EventDelegateChooser {
                 body: formatted
                 formatted: qsTr("This room was replaced for the following reason: %1").arg(tombstone.body)
                 isOnlyEmoji: false
-                isReply: tombstone.isReply
+                isReply: EventDelegateChooser.isReply
                 isStateEvent: true
                 keepFullText: true
             }
diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml
index 0369d5a1..06a1a9e7 100644
--- a/resources/qml/delegates/ImageMessage.qml
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -5,7 +5,6 @@
 import QtQuick
 import QtQuick.Window
 import QtQuick.Controls
-import QtQuick.Layouts
 import im.nheko
 
 AbstractButton {
@@ -17,16 +16,17 @@ AbstractButton {
     required property string blurhash
     required property string body
     required property string filename
-    required property bool isReply
     required property string eventId
     required property int containerHeight
-    property double divisor: isReply ? 5 : 3
+    property double divisor: EventDelegateChooser.isReply ? 5 : 3
+
+    EventDelegateChooser.keepAspectRatio: true
+    EventDelegateChooser.maxWidth: originalWidth
+    EventDelegateChooser.maxHeight: containerHeight / divisor
+    EventDelegateChooser.aspectRatio: proportionalHeight
 
-    //Layout.maximumWidth: originalWidth
-    Layout.maximumHeight: Math.min(originalHeight, containerHeight / divisor)
-    implicitWidth: height/proportionalHeight
-    implicitHeight: Math.min(Layout.maximumHeight, width*proportionalHeight)
     hoverEnabled: true
+    enabled: !EventDelegateChooser.isReply
 
     state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible"
     states: [
@@ -133,8 +133,8 @@ AbstractButton {
         roomm: room
         play: !Settings.animateImagesOnHover || parent.hovered
         eventId: parent.eventId
-        width: parent.implicitWidth
-        height: parent.implicitHeight
+
+        anchors.fill: parent
     }
 
     Image {
@@ -143,10 +143,10 @@ AbstractButton {
         source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText)
         asynchronous: true
         fillMode: Image.PreserveAspectFit
-        sourceSize.width: parent.implicitWidth * Screen.devicePixelRatio
-        sourceSize.height: parent.implicitHeight * Screen.devicePixelRatio
-        width: parent.implicitWidth
-        height: parent.implicitHeight
+        sourceSize.width: parent.width * Screen.devicePixelRatio
+        sourceSize.height: parent.height * Screen.devicePixelRatio
+
+        anchors.fill: parent
     }
 
     onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight);
@@ -154,8 +154,8 @@ AbstractButton {
     Item {
         id: overlay
 
-        width: parent.implicitWidth
-        height: parent.implicitHeight
+        anchors.fill: parent
+
         visible: parent.hovered
 
         Rectangle {
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
deleted file mode 100644
index cd1bdcd4..00000000
--- a/resources/qml/delegates/MessageDelegate.qml
+++ /dev/null
@@ -1,780 +0,0 @@
-// SPDX-FileCopyrightText: Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import QtQuick 2.6
-import QtQuick.Controls 2.1
-import QtQuick.Layouts 1.2
-import im.nheko 1.0
-
-Item {
-    id: d
-
-    required property bool isReply
-    property bool keepFullText: !isReply
-    property alias child: chooser.child
-    //implicitWidth: chooser.child?.implicitWidth ?? 0
-    required property double proportionalHeight
-    required property int type
-    required property string typeString
-    required property int originalWidth
-    required property int duration
-    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 isStateEvent
-    required property string userId
-    required property string userName
-    required property string roomTopic
-    required property string roomName
-    required property string callType
-    required property int encryptionError
-    required property int relatedEventCacheBuster
-    property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false
-    property int metadataWidth
-
-    implicitWidth: chooser.child?.implicitWidth
-
-    height: chooser.child ? chooser.child.height : Nheko.paddingLarge
-
-    DelegateChooser {
-        id: chooser
-
-        //role: "type" //< not supported in our custom implementation, have to use roleValue
-        roleValue: type
-        //anchors.fill: parent
-
-        width: parent?.width ?? 0 // this should get rid of "cannot read property 'width' of null"
-
-        DelegateChoice {
-            roleValue: MtxEvent.UnknownEvent
-
-            Placeholder {
-                typeString: d.typeString
-                text: "Unretrieved event"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Tombstone
-
-
-            ColumnLayout {
-                width: parent.width
-
-                NoticeMessage {
-                    body: formatted
-                    isOnlyEmoji: false
-                    isReply: d.isReply
-                    keepFullText: d.keepFullText
-                    isStateEvent: d.isStateEvent
-                    Layout.fillWidth: true
-                    formatted: qsTr("This room was replaced for the following reason: %1").arg(d.body)
-                }
-
-                Button {
-                    Layout.alignment: Qt.AlignHCenter
-                    text: qsTr("Go to replacement room")
-                    onClicked: room.joinReplacementRoom(eventId)
-                }
-
-            }
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.TextMessage
-
-            TextMessage {
-                formatted: d.formattedBody
-                body: d.body
-                isOnlyEmoji: d.isOnlyEmoji
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.UnknownMessage
-
-            TextMessage {
-                formatted: d.formattedBody
-                body: d.body
-                isOnlyEmoji: d.isOnlyEmoji
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.ElementEffectMessage
-
-            TextMessage {
-                formatted: d.formattedBody
-                body: d.body
-                isOnlyEmoji: d.isOnlyEmoji
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.NoticeMessage
-
-            NoticeMessage {
-                formatted: d.formattedBody
-                body: d.body
-                isOnlyEmoji: d.isOnlyEmoji
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.EmoteMessage
-
-            NoticeMessage {
-                formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody
-                color: TimelineManager.userColor(d.userId, palette.base)
-                body: d.body
-                isOnlyEmoji: d.isOnlyEmoji
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            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
-                eventId: d.eventId
-                metadataWidth: d.metadataWidth
-                containerHeight: timelineView.height
-            }
-
-        }
-
-        DelegateChoice {
-            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
-                eventId: d.eventId
-                metadataWidth: d.metadataWidth
-                containerHeight: timelineView.height
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.FileMessage
-
-            FileMessage {
-                eventId: d.eventId
-                filename: d.filename
-                filesize: d.filesize
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            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
-                duration: d.duration
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            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
-                duration: d.duration
-                metadataWidth: d.metadataWidth
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Redacted
-
-            Redacted {
-                metadataWidth: d.metadataWidth
-            }
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Redaction
-
-            Pill {
-                text: qsTr("%1 removed a message").arg(d.userName)
-                isStateEvent: d.isStateEvent
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Encryption
-
-            EncryptionEnabled {
-                username: d.userName
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Encrypted
-
-            Encrypted {
-                encryptionError: d.encryptionError
-                eventId: d.eventId
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.ServerAcl
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 changed which servers are allowed in this room.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Name
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.roomName ? qsTr("%2 changed the room name to: %1").arg(d.roomName).arg(d.userName) : qsTr("%1 removed the room name").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Topic
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.roomTopic ? qsTr("%2 changed the topic to: %1").arg(d.roomTopic).arg(d.userName): qsTr("%1 removed the topic").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Avatar
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 changed the room avatar").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.PinnedEvents
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 changed the pinned messages.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.ImagePackInRoom
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId)
-            }
-
-        }
-
-
-        DelegateChoice {
-            roleValue: MtxEvent.CanonicalAlias
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.SpaceParent
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 changed the parent communities for this room.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.RoomCreate
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallInvite
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: {
-                    switch (d.callType) {
-                    case "voice":
-                        return qsTr("%1 placed a voice call.").arg(d.userName);
-                    case "video":
-                        return qsTr("%1 placed a video call.").arg(d.userName);
-                    default:
-                        return qsTr("%1 placed a call.").arg(d.userName);
-                    }
-                }
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallAnswer
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 answered the call.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallReject
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 rejected the call.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallSelectAnswer
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 select answer").arg(d.userName)
-                // formatted: qsTr("Call answered elsewhere")
-            }
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallHangUp
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 ended the call.").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallCandidates
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 is negotiating the call...").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.CallNegotiate
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: qsTr("%1 is negotiating the call...").arg(d.userName)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.PowerLevels
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.PolicyRuleUser
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatPolicyRule(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.PolicyRuleRoom
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatPolicyRule(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.PolicyRuleServer
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatPolicyRule(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.RoomJoinRules
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.RoomHistoryVisibility
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.RoomGuestAccess
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId)
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.Member
-
-            ColumnLayout {
-
-                NoticeMessage {
-                    body: formatted
-                    isOnlyEmoji: false
-                    isReply: d.isReply
-                    keepFullText: d.keepFullText
-                    isStateEvent: d.isStateEvent
-                    Layout.fillWidth: true
-                    formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId)
-                }
-
-                Button {
-                    visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId)
-                    Layout.alignment: Qt.AlignHCenter
-                    text: qsTr("Allow them in")
-                    onClicked: room.acceptKnock(eventId)
-                }
-
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationRequest
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationRequest"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationStart
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationStart"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationReady
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationReady"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationCancel
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationCancel"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationKey
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationKey"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationMac
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationMac"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationDone
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationDone"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationDone
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationDone"
-            }
-
-        }
-
-        DelegateChoice {
-            roleValue: MtxEvent.KeyVerificationAccept
-
-            NoticeMessage {
-                body: formatted
-                isOnlyEmoji: false
-                isReply: d.isReply
-                keepFullText: d.keepFullText
-                isStateEvent: d.isStateEvent
-                formatted: "KeyVerificationAccept"
-            }
-
-        }
-
-        DelegateChoice {
-            Placeholder {
-                typeString: d.typeString
-            }
-
-        }
-
-    }
-
-}
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index fb7bf0cc..ac4a82b0 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -22,7 +22,7 @@ Item {
     required property string url
     required property string body
     required property string filesize
-    property double divisor: isReply ? 4 : 2
+    property double divisor: EventDelegateChooser.isReply ? 5 : 3
     property int tempWidth: originalWidth < 1? 400: originalWidth
     implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500
     width: Math.min(parent?.width ?? implicitWidth, implicitWidth)
diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml
index 52cc982d..1598e8c0 100644
--- a/resources/qml/delegates/Reply.qml
+++ b/resources/qml/delegates/Reply.qml
@@ -48,51 +48,43 @@ AbstractButton {
         room: room_
         eventId: r.eventId
         replyTo: ""
+        mainInset: 4 + Nheko.paddingMedium
+        maxWidth: r.maxWidth
 
         //height: replyContainer.implicitHeight
-        data: GridLayout {
+        data: Row {
             id: replyContainer
 
-            width: r.maxWidth
-            columns: 2
-            rows: 2
-            columnSpacing: Nheko.paddingMedium
-            rowSpacing: Nheko.paddingSmall
+            spacing: Nheko.paddingSmall
 
             Rectangle {
                 id: colorline
 
-                Layout.preferredWidth: 4
-                Layout.rowSpan: 2
-                Layout.fillHeight: true
-
-                Layout.row: 0
-                Layout.column: 0
+                width: 4
 
                 color: TimelineManager.userColor(r.userId, palette.base)
             }
 
-            AbstractButton {
-                id: usernameBtn
-                Layout.fillWidth: true
+            Column {
+                spacing: 0
 
-                Layout.row: 0
-                Layout.column: 1
+                AbstractButton {
+                    id: usernameBtn
 
-                contentItem: ElidedLabel {
-                    id: userName_
-                    fullText: r.userName
-                    color: r.userColor
-                    textFormat: Text.RichText
-                    width: parent.width
-                    elideWidth: width
+                    contentItem: Label {
+                        id: userName_
+                        text: r.userName
+                        color: r.userColor
+                        textFormat: Text.RichText
+                        width: timelineEvent.main?.width
+                    }
+                    onClicked: room.openUserProfile(r.userId)
                 }
-                onClicked: room.openUserProfile(r.userId)
-            }
 
-            data: [
-                colorline, usernameBtn, timelineEvent.main,
-            ]
+                data: [
+                    usernameBtn, timelineEvent.main,
+                ]
+            }
 
         }
     }
diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml
index 03623924..e9254eed 100644
--- a/resources/qml/delegates/TextMessage.qml
+++ b/resources/qml/delegates/TextMessage.qml
@@ -10,7 +10,7 @@ import im.nheko
 MatrixText {
     required property string body
     required property bool isOnlyEmoji
-    required property bool isReply
+    property bool isReply: EventDelegateChooser.isReply
     required property bool keepFullText
     required property string formatted
 
@@ -45,7 +45,6 @@ MatrixText {
 
     //EventDelegateChooser.fillWidth: true
 
-    clip: !keepFullText
     selectByMouse: !Settings.mobileMode && !isReply
     enabled: !Settings.mobileMode && !isReply
     hoverEnabled: !Settings.mobileMode
diff --git a/src/timeline/EventDelegateChooser.cpp b/src/timeline/EventDelegateChooser.cpp
index 0060907d..a8629b3e 100644
--- a/src/timeline/EventDelegateChooser.cpp
+++ b/src/timeline/EventDelegateChooser.cpp
@@ -97,6 +97,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
         return;
 
     item->setParentItem(&chooser);
+    item->setParent(&chooser);
 
     auto roleNames = chooser.room_->roleNames();
     QHash<QByteArray, int> nameToRole;
@@ -106,8 +107,6 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
 
     QHash<int, int> roleToPropIdx;
     std::vector<QModelRoleData> roles;
-    bool isReplyNeeded = false;
-
     // Workaround for https://bugreports.qt.io/browse/QTBUG-98846
     QHash<QString, RequiredPropertyKey> requiredProperties;
     for (const auto &[propKey, prop] :
@@ -124,10 +123,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
         if (!prop.isRequired() && !requiredProperties.contains(prop.name()))
             continue;
 
-        if (prop.name() == std::string_view("isReply")) {
-            isReplyNeeded = true;
-            roleToPropIdx.insert(-1, i);
-        } else if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) {
+        if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) {
             roleToPropIdx.insert(*role, i);
             roles.emplace_back(*role);
 
@@ -141,6 +137,11 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
     chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles);
 
     Qt::beginPropertyUpdateGroup();
+    auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
+      qmlAttachedPropertiesObject<EventDelegateChooser>(obj));
+    Q_ASSERT(attached != nullptr);
+    attached->setIsReply(this->forReply);
+
     for (const auto &role : roles) {
         const auto &roleName = roleNames[role.role()];
         // nhlog::ui()->critical("Setting role {}, {} to {}",
@@ -155,22 +156,25 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj)
             QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req);
     }
 
-    if (isReplyNeeded) {
-        const auto roleName = QByteArray("isReply");
-        // nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply);
-
-        // nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name());
-        mo->property(roleToPropIdx[-1]).write(obj, forReply);
-
-        if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end())
-            QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req);
-    }
     Qt::endPropertyUpdateGroup();
 
     // setInitialProperties(rolesToSet);
 
     auto update =
       [this, obj, roleToPropIdx = std::move(roleToPropIdx)](const QList<int> &changedRoles) {
+          if (changedRoles.empty() || changedRoles.contains(TimelineModel::Roles::Type)) {
+              int type = chooser.room_
+                           ->dataById(currentId,
+                                      TimelineModel::Roles::Type,
+                                      forReply ? chooser.eventId_ : QString())
+                           .toInt();
+              if (type != oldType) {
+                  nhlog::ui()->debug("Type changed!");
+                  reset(currentId);
+                  return;
+              }
+          }
+
           std::vector<QModelRoleData> rolesToRequest;
 
           if (changedRoles.empty()) {
@@ -233,6 +237,7 @@ EventDelegateChooser::DelegateIncubator::reset(QString id)
       chooser.room_
         ->dataById(id, TimelineModel::Roles::Type, forReply ? chooser.eventId_ : QString())
         .toInt();
+    this->oldType = role;
 
     for (const auto choice : qAsConst(chooser.choices_)) {
         const auto &choiceValue = choice->roleValues();
@@ -293,41 +298,58 @@ EventDelegateChooser::updatePolish()
 
     nhlog::ui()->critical("POLISHING {}", (void *)this);
 
-    if (mainChild) {
-        auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
-          qmlAttachedPropertiesObject<EventDelegateChooser>(mainChild));
-        Q_ASSERT(attached != nullptr);
-
-        // in theory we could also reset the width, but that doesn't seem to work nicely for text
-        // areas because of how they cache it.
-        mainChild->setWidth(maxWidth_);
-        mainChild->ensurePolished();
-        auto width = mainChild->implicitWidth();
-
-        if (width > maxWidth_ || attached->fillWidth())
-            width = maxWidth_;
-
-        nhlog::ui()->debug(
-          "Made event delegate width: {}, {}", width, mainChild->metaObject()->className());
-        mainChild->setWidth(width);
-        mainChild->ensurePolished();
-    }
-
-    if (replyChild) {
-        auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
-          qmlAttachedPropertiesObject<EventDelegateChooser>(replyChild));
-        Q_ASSERT(attached != nullptr);
+    auto layoutItem = [this](QQuickItem *item, int inset) {
+        if (item) {
+            auto attached = qobject_cast<EventDelegateChooserAttachedType *>(
+              qmlAttachedPropertiesObject<EventDelegateChooser>(item));
+            Q_ASSERT(attached != nullptr);
+
+            int maxWidth = maxWidth_ - inset;
+
+            // in theory we could also reset the width, but that doesn't seem to work nicely for
+            // text areas because of how they cache it.
+            if (attached->maxWidth() > 0)
+                item->setWidth(attached->maxWidth());
+            else
+                item->setWidth(maxWidth);
+            item->ensurePolished();
+            auto width = item->implicitWidth();
+
+            if (width < 1 || width > maxWidth)
+                width = maxWidth;
+
+            if (attached->maxWidth() > 0 && width > attached->maxWidth())
+                width = attached->maxWidth();
+
+            if (attached->keepAspectRatio()) {
+                auto height = width * attached->aspectRatio();
+                if (attached->maxHeight() && height > attached->maxHeight()) {
+                    height = attached->maxHeight();
+                    width  = height / attached->aspectRatio();
+                }
+
+                item->setHeight(height);
+            }
 
-        // in theory we could also reset the width, but that doesn't seem to work nicely for text
-        // areas because of how they cache it.
-        replyChild->setWidth(maxWidth_);
-        replyChild->ensurePolished();
-        auto width = replyChild->implicitWidth();
+            nhlog::ui()->debug(
+              "Made event delegate width: {}, {}", width, item->metaObject()->className());
+            item->setWidth(width);
+            item->ensurePolished();
+        }
+    };
 
-        if (width > maxWidth_ || attached->fillWidth())
-            width = maxWidth_;
+    layoutItem(mainChild, mainInset_);
+    layoutItem(replyChild, replyInset_);
+}
 
-        replyChild->setWidth(width);
-        replyChild->ensurePolished();
+void
+EventDelegateChooserAttachedType::polishChooser()
+{
+    auto p = parent();
+    if (p) {
+        auto chooser = qobject_cast<EventDelegateChooser *>(p->parent());
+        if (chooser) {
+            chooser->polish();
+        }
     }
 }
diff --git a/src/timeline/EventDelegateChooser.h b/src/timeline/EventDelegateChooser.h
index ff67ccd8..ce79444a 100644
--- a/src/timeline/EventDelegateChooser.h
+++ b/src/timeline/EventDelegateChooser.h
@@ -17,27 +17,78 @@
 class EventDelegateChooserAttachedType : public QObject
 {
     Q_OBJECT
-    Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged)
+    Q_PROPERTY(bool keepAspectRatio READ keepAspectRatio WRITE setKeepAspectRatio NOTIFY
+                 keepAspectRatioChanged)
+    Q_PROPERTY(double aspectRatio READ aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged)
+    Q_PROPERTY(int maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged)
+    Q_PROPERTY(int maxHeight READ maxHeight WRITE setMaxHeight NOTIFY maxHeightChanged)
+    Q_PROPERTY(bool isReply READ isReply WRITE setIsReply NOTIFY isReplyChanged)
+
     QML_ANONYMOUS
 public:
     EventDelegateChooserAttachedType(QObject *parent)
       : QObject(parent)
     {
     }
-    bool fillWidth() const { return fillWidth_; }
-    void setFillWidth(bool fill)
+
+    bool keepAspectRatio() const { return keepAspectRatio_; }
+    void setKeepAspectRatio(bool fill)
+    {
+        if (fill != keepAspectRatio_) {
+            keepAspectRatio_ = fill;
+            emit keepAspectRatioChanged();
+            polishChooser();
+        }
+    }
+
+    double aspectRatio() const { return aspectRatio_; }
+    void setAspectRatio(double fill)
     {
-        fillWidth_ = fill;
-        emit fillWidthChanged();
+        aspectRatio_ = fill;
+        emit aspectRatioChanged();
+        polishChooser();
     }
+
+    int maxWidth() const { return maxWidth_; }
+    void setMaxWidth(int fill)
+    {
+        maxWidth_ = fill;
+        emit maxWidthChanged();
+        polishChooser();
+    }
+
+    int maxHeight() const { return maxHeight_; }
+    void setMaxHeight(int fill)
+    {
+        maxHeight_ = fill;
+        emit maxHeightChanged();
+    }
+
+    bool isReply() const { return isReply_; }
+    void setIsReply(bool fill)
+    {
+        if (fill != isReply_) {
+            isReply_ = fill;
+            emit isReplyChanged();
+            polishChooser();
+        }
+    }
+
 signals:
-    void fillWidthChanged();
+    void keepAspectRatioChanged();
+    void aspectRatioChanged();
+    void maxWidthChanged();
+    void maxHeightChanged();
+    void isReplyChanged();
 
 private:
-    bool fillWidth_ = false, keepAspectRatio = false;
-    double aspectRatio = 1.;
-    int maxWidth       = -1;
-    int maxHeight      = -1;
+    void polishChooser();
+
+    double aspectRatio_ = 1.;
+    int maxWidth_       = -1;
+    int maxHeight_        = -1;
+    bool keepAspectRatio_ = false;
+    bool isReply_         = false;
 };
 
 class EventDelegateChoice : public QObject
@@ -84,6 +135,8 @@ class EventDelegateChooser : public QQuickItem
     Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL)
     Q_PROPERTY(bool sameWidth READ sameWidth WRITE setSameWidth NOTIFY sameWidthChanged)
     Q_PROPERTY(int maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged)
+    Q_PROPERTY(int replyInset READ replyInset WRITE setReplyInset NOTIFY replyInsetChanged)
+    Q_PROPERTY(int mainInset READ mainInset WRITE setMainInset NOTIFY mainInsetChanged)
 
 public:
     QQmlListProperty<EventDelegateChoice> choices();
@@ -103,7 +156,7 @@ public:
         sameWidth_ = width;
         emit sameWidthChanged();
     }
-    bool maxWidth() const { return maxWidth_; }
+    int maxWidth() const { return maxWidth_; }
     void setMaxWidth(int width)
     {
         maxWidth_ = width;
@@ -111,6 +164,22 @@ public:
         polish();
     }
 
+    int replyInset() const { return replyInset_; }
+    void setReplyInset(int width)
+    {
+        replyInset_ = width;
+        emit replyInsetChanged();
+        polish();
+    }
+
+    int mainInset() const { return mainInset_; }
+    void setMainInset(int width)
+    {
+        mainInset_ = width;
+        emit mainInsetChanged();
+        polish();
+    }
+
     void setRoom(TimelineModel *m)
     {
         if (m != room_) {
@@ -161,6 +230,8 @@ signals:
     void replyToChanged();
     void sameWidthChanged();
     void maxWidthChanged();
+    void replyInsetChanged();
+    void mainInsetChanged();
 
 private:
     struct DelegateIncubator final : public QQmlIncubator
@@ -183,6 +254,7 @@ private:
         QString instantiatedId;
         int instantiatedRole = -1;
         QAbstractItemModel *instantiatedModel = nullptr;
+        int oldType                           = -1;
     };
 
     QVariant roleValue_;
@@ -194,6 +266,8 @@ private:
     QString replyId;
     bool sameWidth_ = false;
     int maxWidth_   = 400;
+    int replyInset_ = 0;
+    int mainInset_  = 0;
 
     static void appendChoice(QQmlListProperty<EventDelegateChoice> *, EventDelegateChoice *);
     static qsizetype choiceCount(QQmlListProperty<EventDelegateChoice> *);