diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index 590d5bb8..9ebe0a40 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -145,7 +145,6 @@ Control {
roleValue: "user"
RowLayout {
-
anchors.centerIn: centerRowContent ? parent : undefined
spacing: rowSpacing
@@ -171,7 +170,6 @@ Control {
roleValue: "emoji"
RowLayout {
-
anchors.centerIn: parent
spacing: rowSpacing
@@ -207,7 +205,6 @@ Control {
roleValue: "command"
RowLayout {
-
anchors.centerIn: parent
spacing: rowSpacing
@@ -226,7 +223,6 @@ Control {
roleValue: "room"
RowLayout {
-
anchors.centerIn: centerRowContent ? parent : undefined
spacing: rowSpacing
@@ -251,7 +247,6 @@ Control {
roleValue: "roomAliases"
RowLayout {
-
anchors.centerIn: parent
spacing: rowSpacing
diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml
index 0174e0f6..caaea440 100644
--- a/resources/qml/ForwardCompleter.qml
+++ b/resources/qml/ForwardCompleter.qml
@@ -54,25 +54,9 @@ Popup {
Reply {
id: replyPreview
- property var modelData: room ? room.getDump(mid, "") : {}
-
- blurhash: modelData.blurhash ?? ""
- body: modelData.body ?? ""
- encryptionError: modelData.encryptionError ?? ""
- eventId: modelData.eventId ?? ""
- filename: modelData.filename ?? ""
- filesize: modelData.filesize ?? ""
- formattedBody: modelData.formattedBody ?? ""
- isOnlyEmoji: modelData.isOnlyEmoji ?? false
- originalWidth: modelData.originalWidth ?? 0
- proportionalHeight: modelData.proportionalHeight ?? 1
- type: modelData.type ?? MtxEvent.UnknownMessage
- typeString: modelData.typeString ?? ""
- url: modelData.url ?? ""
+ eventId: mid
userColor: TimelineManager.userColor(modelData.userId, palette.window)
- userId: modelData.userId ?? ""
- userName: modelData.userName ?? ""
- width: parent.width
+ maxWidth: parent.width
}
MatrixTextField {
id: roomTextInput
diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index 521e9280..c804e72e 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -4,33 +4,33 @@
// TODO: using any Qt 6 API version will screw up the reply text color. We need to
// figure out a more permanent fix than just importing the old version.
-import QtQuick 2.15
+//import QtQuick 2.15
+import QtQuick
import QtQuick.Controls
import im.nheko
-TextEdit {
+TextArea {
id: r
property alias cursorShape: cs.cursorShape
- //leftInset: 0
- //bottomInset: 0
- //rightInset: 0
- //topInset: 0
- //leftPadding: 0
- //bottomPadding: 0
- //rightPadding: 0
- //topPadding: 0
- //background: null
-
ToolTip.text: hoveredLink
ToolTip.visible: hoveredLink || false
+ background: null
+ bottomInset: 0
+ bottomPadding: 0
// this always has to be enabled, otherwise you can't click links anymore!
//enabled: selectByMouse
color: palette.text
focus: false
+ leftInset: 0
+ leftPadding: 0
readOnly: true
+ rightInset: 0
+ rightPadding: 0
textFormat: TextEdit.RichText
+ topInset: 0
+ topPadding: 0
wrapMode: Text.Wrap
// Setting a tooltip delay makes the hover text empty .-.
@@ -40,9 +40,9 @@ TextEdit {
}
onLinkActivated: Nheko.openLink(link)
- //// propagate events up
- //onPressAndHold: (event) => event.accepted = false
- //onPressed: (event) => event.accepted = (event.button == Qt.LeftButton)
+ // propagate events up
+ onPressAndHold: event => event.accepted = false
+ onPressed: event => event.accepted = (event.button == Qt.LeftButton)
NhekoCursorShape {
id: cs
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 894e58b4..98b9748a 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -20,12 +20,13 @@ Item {
property int availableWidth: width
property int padding: Nheko.paddingMedium
property string searchString: ""
+ property Room roommodel: room
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections {
function onHideMenu() {
- messageContextMenu.close();
- replyContextMenu.close();
+ messageContextMenuC.close();
+ replyContextMenuC.close();
}
target: MainWindow
@@ -51,182 +52,35 @@ Item {
//onModelChanged: if (room) room.sendReset()
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds
- displayMarginBeginning: height / 2
- displayMarginEnd: height / 2
+ displayMarginBeginning: height / 4
+ displayMarginEnd: height / 4
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
- delegate: Item {
- id: wrapper
-
- required property string blurhash
- required property string body
- required property string callType
- required property var day
- required property string duration
- required property int encryptionError
- required property string eventId
- required property string filename
- required property string filesize
- required property string formattedBody
- required property int index
- required property bool isEditable
- required property bool isEdited
- required property bool isEncrypted
- required property bool isOnlyEmoji
- required property bool isSender
- required property bool isStateEvent
- required property int notificationlevel
- required property int originalWidth
- property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
- property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
- property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
- required property double proportionalHeight
- required property var reactions
- required property int relatedEventCacheBuster
- required property string replyTo
- required property string roomName
- required property string roomTopic
- property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
- required property int status
- required property string threadId
- required property string thumbnailUrl
- required property var timestamp
- required property int trustlevel
- required property int type
- required property string typeString
- required property string url
- required property string userId
- required property string userName
- required property int userPowerlevel
-
- ListView.delayRemove: true
- anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
- height: (section.item?.height ?? 0) + timelinerow.height
- width: chat.delegateMaxWidth
-
- Loader {
- id: section
-
- property var day: wrapper.day
- property bool isSender: wrapper.isSender
- property bool isStateEvent: wrapper.isStateEvent
- property int parentWidth: parent.width
- property var previousMessageDay: wrapper.previousMessageDay
- property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
- property string previousMessageUserId: wrapper.previousMessageUserId
- property date timestamp: wrapper.timestamp
- property string userId: wrapper.userId
- property string userName: wrapper.userName
- property int userPowerlevel: wrapper.userPowerlevel
-
- active: previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
- //asynchronous: true
- sourceComponent: sectionHeader
- visible: status == Loader.Ready
- z: 4
- }
- TimelineRow {
- id: timelinerow
-
- blurhash: wrapper.blurhash
- body: wrapper.body
- callType: wrapper.callType
- duration: wrapper.duration
- encryptionError: wrapper.encryptionError
- eventId: chat.model, wrapper.eventId
- filename: wrapper.filename
- filesize: wrapper.filesize
- formattedBody: wrapper.formattedBody
- index: wrapper.index
- isEditable: wrapper.isEditable
- isEdited: wrapper.isEdited
- isEncrypted: wrapper.isEncrypted
- isOnlyEmoji: wrapper.isOnlyEmoji
- isSender: wrapper.isSender
- isStateEvent: wrapper.isStateEvent
- notificationlevel: wrapper.notificationlevel
- originalWidth: wrapper.originalWidth
- proportionalHeight: wrapper.proportionalHeight
- reactions: wrapper.reactions
- relatedEventCacheBuster: wrapper.relatedEventCacheBuster
- replyTo: wrapper.replyTo
- roomName: wrapper.roomName
- roomTopic: wrapper.roomTopic
- status: wrapper.status
- threadId: wrapper.threadId
- thumbnailUrl: wrapper.thumbnailUrl
- timestamp: wrapper.timestamp
- trustlevel: wrapper.trustlevel
- type: chat.model, wrapper.type
- typeString: wrapper.typeString
- url: wrapper.url
- userId: wrapper.userId
- userName: wrapper.userName
- width: wrapper.width
- y: section.visible && section.active ? section.y + section.height : 0
-
- background: Rectangle {
- id: scrollHighlight
-
- color: palette.highlight
- enabled: false
- opacity: 0
- visible: true
- z: 1
-
- states: State {
- name: "revealed"
- when: wrapper.scrolledToThis
- }
- transitions: Transition {
- from: ""
- to: "revealed"
-
- SequentialAnimation {
- PropertyAnimation {
- duration: 500
- easing.type: Easing.InOutQuad
- from: 0
- properties: "opacity"
- target: scrollHighlight
- to: 1
- }
- PropertyAnimation {
- duration: 500
- easing.type: Easing.InOutQuad
- from: 1
- properties: "opacity"
- target: scrollHighlight
- to: 0
- }
- ScriptAction {
- script: room.eventShown()
- }
- }
- }
- }
+ Component {
+ id: defaultMessageStyle
- onHoveredChanged: {
- if (!Settings.mobileMode && hovered) {
- if (!messageActions.hovered) {
- messageActions.attached = timelinerow;
- messageActions.model = timelinerow;
- }
- }
- }
+ TimelineDefaultMessageStyle {
+ messageActions: messageActionsC
+ messageContextMenu: messageContextMenuC
+ replyContextMenu: replyContextMenuC
+ scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
- Connections {
- function onMovementEnded() {
- if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
- chat.model.currentIndex = index;
- }
+ }
+ Component {
+ id: bubbleMessageStyle
- target: chat
+ TimelineBubbleMessageStyle {
+ messageActions: messageActionsC
+ messageContextMenu: messageContextMenuC
+ replyContextMenu: replyContextMenuC
+ scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
+
+ delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
footer: Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge
@@ -260,19 +114,19 @@ Item {
source: room
}
Control {
- id: messageActions
+ id: messageActionsC
property Item attached: null
// use comma to update on scroll
- property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
property alias model: row.model
hoverEnabled: true
padding: Nheko.paddingSmall
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
- x: attached ? attachedPos.x : 0
- y: attached ? attachedPos.y + Nheko.paddingSmall : 0
z: 10
+ parent: chat.contentItem
+ anchors.bottom: attached?.top
+ anchors.right: attached?.right
background: Rectangle {
border.color: palette.buttonText
@@ -285,7 +139,7 @@ Item {
property var model
- spacing: messageActions.padding
+ spacing: messageActionsC.padding
Repeater {
model: Settings.recentReactions
@@ -422,7 +276,7 @@ Item {
image: ":/icons/icons/ui/options.svg"
width: 16
- onClicked: messageContextMenu.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
+ onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
}
}
}
@@ -504,148 +358,9 @@ Item {
room.setCurrentIndex(room.currentIndex);
}
}
- Component {
- id: sectionHeader
-
- Column {
- bottomPadding: Settings.bubbles ? (isSender && previousMessageDay == day ? 0 : 2) : 3
- spacing: 8
- topPadding: userName_.visible ? 4 : 0
- visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
- width: parentWidth
-
- Label {
- id: dateBubble
-
- anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
- color: palette.text
- height: Math.round(fontMetrics.height * 1.4)
- horizontalAlignment: Text.AlignHCenter
- text: room ? room.formatDateSeparator(timestamp) : ""
- verticalAlignment: Text.AlignVCenter
- visible: room && previousMessageDay !== day
- width: contentWidth * 1.2
-
- background: Rectangle {
- color: palette.window
- radius: parent.height / 2
- }
- }
- Row {
- id: userInfo
-
- property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width
-
- height: userName_.height
- spacing: 8
- visible: !isStateEvent && (!isSender || !Settings.bubbles)
-
- Avatar {
- id: messageUserAvatar
-
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: userid
- ToolTip.visible: messageUserAvatar.hovered
- displayName: userName
- height: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1)
- url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
- userid: userId
- width: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1)
-
- onClicked: room.openUserProfile(userId)
- }
- Connections {
- function onRoomAvatarUrlChanged() {
- messageUserAvatar.url = room.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
- }
- function onScrollToIndex(index) {
- chat.positionViewAtIndex(index, ListView.Center);
- }
-
- target: room
- }
-
- AbstractButton {
- id: userNameButton
-
- PowerlevelIndicator {
- id: powerlevelIndicator
-
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- height: fontMetrics.ascent
- width: height
- powerlevel: userPowerlevel
- permissions: room ? room.permissions : null
- visible: isAdmin || isModerator
- }
-
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: userId
- ToolTip.visible: hovered
- leftPadding: powerlevelIndicator.visible ? 16 : 0
- leftInset: 0
- rightInset: 0
- rightPadding: 0
-
- contentItem: Label {
- id: userName_
-
- color: TimelineManager.userColor(userId, palette.base)
- text: TimelineManager.escapeEmoji(userNameTextMetrics.elidedText)
- textFormat: Text.RichText
- }
-
- onClicked: room.openUserProfile(userId)
-
- TextMetrics {
- id: userNameTextMetrics
-
- elide: Text.ElideRight
- elideWidth: userInfo.remainingWidth - Math.min(statusMsg.implicitWidth, userInfo.remainingWidth / 3)
- text: userName
- }
- NhekoCursorShape {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- }
- }
- Label {
- id: statusMsg
-
- property string userStatus: Presence.userStatus(userId)
-
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: qsTr("%1's status message").arg(userName)
- ToolTip.visible: statusMsgHoverHandler.hovered
- anchors.baseline: userNameButton.baseline
- color: palette.buttonText
- elide: Text.ElideRight
- font.italic: true
- font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.8)
- text: userStatus.replace(/\n/g, " ")
- textFormat: Text.PlainText
- width: Math.min(implicitWidth, userInfo.remainingWidth - userName_.width - parent.spacing)
-
- HoverHandler {
- id: statusMsgHoverHandler
-
- }
- Connections {
- function onPresenceChanged(id) {
- if (id == userId)
- statusMsg.userStatus = Presence.userStatus(userId);
- }
-
- target: Presence
- }
- }
- }
- }
- }
}
Platform.Menu {
- id: messageContextMenu
+ id: messageContextMenuC
property string eventId
property int eventType
@@ -700,22 +415,22 @@ Item {
onTriggered: function () {
topBar.searchString = "";
- room.showEvent(messageContextMenu.eventId);
+ room.showEvent(messageContextMenuC.eventId);
}
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
- visible: messageContextMenu.text
+ visible: messageContextMenuC.text
- onTriggered: Clipboard.text = messageContextMenu.text
+ onTriggered: Clipboard.text = messageContextMenuC.text
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
- visible: messageContextMenu.link
+ visible: messageContextMenuC.link
- onTriggered: Clipboard.text = messageContextMenu.link
+ onTriggered: Clipboard.text = messageContextMenuC.link
}
Platform.MenuItem {
id: reactionOption
@@ -724,7 +439,7 @@ Item {
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
onTriggered: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(null, room.roomId, function (plaintext, markdown) {
- room.input.reaction(messageContextMenu.eventId, plaintext);
+ room.input.reaction(messageContextMenuC.eventId, plaintext);
TimelineManager.focusMessageInput();
})
}
@@ -732,41 +447,41 @@ Item {
text: qsTr("Repl&y")
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
- onTriggered: room.reply = (messageContextMenu.eventId)
+ onTriggered: room.reply = (messageContextMenuC.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Edit")
- visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
+ visible: messageContextMenuC.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
- onTriggered: room.edit = (messageContextMenu.eventId)
+ onTriggered: room.edit = (messageContextMenuC.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Thread")
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
- onTriggered: room.thread = (messageContextMenu.threadId || messageContextMenu.eventId)
+ onTriggered: room.thread = (messageContextMenuC.threadId || messageContextMenuC.eventId)
}
Platform.MenuItem {
enabled: visible
- text: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
+ text: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
- onTriggered: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? room.unpin(messageContextMenu.eventId) : room.pin(messageContextMenu.eventId)
+ onTriggered: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? room.unpin(messageContextMenuC.eventId) : room.pin(messageContextMenuC.eventId)
}
Platform.MenuItem {
text: qsTr("&Read receipts")
- onTriggered: room.showReadReceipts(messageContextMenu.eventId)
+ onTriggered: room.showReadReceipts(messageContextMenuC.eventId)
}
Platform.MenuItem {
text: qsTr("&Forward")
- visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
+ visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker || messageContextMenuC.eventType == MtxEvent.TextMessage || messageContextMenuC.eventType == MtxEvent.LocationMessage || messageContextMenuC.eventType == MtxEvent.EmoteMessage || messageContextMenuC.eventType == MtxEvent.NoticeMessage
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
- forwardMess.setMessageEventId(messageContextMenu.eventId);
+ forwardMess.setMessageEventId(messageContextMenuC.eventId);
forwardMess.open();
timelineRoot.destroyOnClose(forwardMess);
}
@@ -777,23 +492,23 @@ Item {
Platform.MenuItem {
text: qsTr("View raw message")
- onTriggered: room.viewRawMessage(messageContextMenu.eventId)
+ onTriggered: room.viewRawMessage(messageContextMenuC.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
- visible: messageContextMenu.isEncrypted
+ visible: messageContextMenuC.isEncrypted
- onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
+ onTriggered: room.viewDecryptedRawMessage(messageContextMenuC.eventId)
}
Platform.MenuItem {
text: qsTr("Remo&ve message")
- visible: (room ? room.permissions.canRedact() : false) || messageContextMenu.isSender
+ visible: (room ? room.permissions.canRedact() : false) || messageContextMenuC.isSender
onTriggered: function () {
var dialog = removeReason.createObject(timelineRoot);
- dialog.eventId = messageContextMenu.eventId;
+ dialog.eventId = messageContextMenuC.eventId;
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
@@ -802,23 +517,23 @@ Item {
Platform.MenuItem {
enabled: visible
text: qsTr("&Save as")
- visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
+ visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
- onTriggered: room.saveMedia(messageContextMenu.eventId)
+ onTriggered: room.saveMedia(messageContextMenuC.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Open in external program")
- visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
+ visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
- onTriggered: room.openMedia(messageContextMenu.eventId)
+ onTriggered: room.openMedia(messageContextMenuC.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy link to eve&nt")
- visible: messageContextMenu.eventId
+ visible: messageContextMenuC.eventId
- onTriggered: room.copyLinkToEvent(messageContextMenu.eventId)
+ onTriggered: room.copyLinkToEvent(messageContextMenuC.eventId)
}
}
Component {
@@ -828,7 +543,7 @@ Item {
}
}
Platform.Menu {
- id: replyContextMenu
+ id: replyContextMenuC
property string eventId
property string link
@@ -844,23 +559,23 @@ Item {
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
- visible: replyContextMenu.text
+ visible: replyContextMenuC.text
- onTriggered: Clipboard.text = replyContextMenu.text
+ onTriggered: Clipboard.text = replyContextMenuC.text
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
- visible: replyContextMenu.link
+ visible: replyContextMenuC.link
- onTriggered: Clipboard.text = replyContextMenu.link
+ onTriggered: Clipboard.text = replyContextMenuC.link
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Go to quoted message")
visible: true
- onTriggered: room.showEvent(replyContextMenu.eventId)
+ onTriggered: room.showEvent(replyContextMenuC.eventId)
}
}
RoundButton {
diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml
index eff62fc1..5b994145 100644
--- a/resources/qml/Reactions.qml
+++ b/resources/qml/Reactions.qml
@@ -74,10 +74,10 @@ Flow {
anchors.verticalCenter: divider.verticalCenter
fillMode: Image.PreserveAspectFit
height: textMetrics.height
+ mipmap: true
source: modelData.key.startsWith("mxc://") ? (modelData.key.replace("mxc://", "image://MxcImage/") + "?scale") : ""
visible: modelData.key.startsWith("mxc://")
width: textMetrics.height
- mipmap: true
}
Rectangle {
id: divider
diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml
index ce24297c..5657c08a 100644
--- a/resources/qml/ReplyPopup.qml
+++ b/resources/qml/ReplyPopup.qml
@@ -29,24 +29,10 @@ Rectangle {
anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall
- blurhash: modelData.blurhash ?? ""
- body: modelData.body ?? ""
- encryptionError: modelData.encryptionError ?? 0
- eventId: modelData.eventId ?? ""
- filename: modelData.filename ?? ""
- filesize: modelData.filesize ?? ""
- formattedBody: modelData.formattedBody ?? ""
- isOnlyEmoji: modelData.isOnlyEmoji ?? false
- originalWidth: modelData.originalWidth ?? 0
- proportionalHeight: modelData.proportionalHeight ?? 1
- type: modelData.type ?? MtxEvent.UnknownMessage
- typeString: modelData.typeString ?? ""
- url: modelData.url ?? ""
+ eventId: room.reply ?? ""
userColor: TimelineManager.userColor(modelData.userId, palette.window)
- userId: modelData.userId ?? ""
- userName: modelData.userName ?? ""
visible: room && room.reply
- width: parent.width
+ maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
}
ImageButton {
id: closeReplyButton
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 20e5b95b..0c432189 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -728,9 +728,9 @@ Page {
}
Platform.MenuItem {
text: qsTr("Mark as read")
+
onTriggered: Rooms.getRoomById(roomContextMenu.roomid).markRoomAsRead()
}
-
Platform.MenuItem {
text: qsTr("Room settings")
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 1e8a6a27..09a8f442 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -355,7 +355,6 @@ Pane {
onAccepted: UIA.continue3pidReceived()
}
-
Connections {
function onConfirm3pidToken() {
uiaConfirmationLinkDialog.open();
@@ -363,6 +362,18 @@ Pane {
function onEmail() {
uiaEmailPrompt.show();
}
+ function onFallbackAuth(fallback) {
+ var component = Qt.createComponent("qrc:/resources/qml/dialogs/FallbackAuthDialog.qml");
+ if (component.status == Component.Ready) {
+ var dialog = component.createObject(timelineRoot, {
+ "fallback": fallback
+ });
+ dialog.show();
+ destroyOnClose(dialog);
+ } else {
+ console.error("Failed to create component: " + component.errorString());
+ }
+ }
function onPassword() {
console.log("UIA: password needed");
uiaPassPrompt.show();
@@ -385,18 +396,6 @@ Pane {
console.error("Failed to create component: " + component.errorString());
}
}
- function onFallbackAuth(fallback) {
- var component = Qt.createComponent("qrc:/resources/qml/dialogs/FallbackAuthDialog.qml");
- if (component.status == Component.Ready) {
- var dialog = component.createObject(timelineRoot, {
- "fallback": fallback
- });
- dialog.show();
- destroyOnClose(dialog);
- } else {
- console.error("Failed to create component: " + component.errorString());
- }
- }
target: UIA
}
diff --git a/resources/qml/TimelineBubbleMessageStyle.qml b/resources/qml/TimelineBubbleMessageStyle.qml
new file mode 100644
index 00000000..2df3d917
--- /dev/null
+++ b/resources/qml/TimelineBubbleMessageStyle.qml
@@ -0,0 +1,334 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "./components"
+import "./delegates"
+import "./emoji"
+import "./ui"
+import "./dialogs"
+import Qt.labs.platform 1.1 as Platform
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+import im.nheko
+
+TimelineEvent {
+ id: wrapper
+ ListView.delayRemove: true
+ width: chat.delegateMaxWidth
+ height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
+ anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
+ //room: chatRoot.roommodel
+
+ required property var day
+ required property bool isSender
+ required property int index
+ property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
+ property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
+ property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
+
+ required property date timestamp
+ required property string userId
+ required property string userName
+ required property string threadId
+ required property int userPowerlevel
+ required property bool isEdited
+ required property bool isEncrypted
+ required property var reactions
+ required property int status
+ required property int trustlevel
+ required property int notificationlevel
+ required property int type
+ required property bool isEditable
+
+ required property QtObject messageContextMenu
+ required property QtObject replyContextMenu
+ required property Item messageActions
+
+ property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
+
+ property alias hovered: messageHover.hovered
+ property bool scrolledToThis: false
+
+ mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4
+ replyInset: mainInset + 4 + Nheko.paddingSmall
+
+ property int bubbleMargin: 40
+
+ maxWidth: chat.delegateMaxWidth - avatarMargin - bubbleMargin
+
+ data: [
+ Loader {
+ id: section
+
+ active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
+ //asynchronous: true
+ sourceComponent: TimelineSectionHeader {
+ day: wrapper.day
+ isSender: wrapper.isSender
+ isStateEvent: wrapper.isStateEvent
+ parentWidth: wrapper.width
+ previousMessageDay: wrapper.previousMessageDay
+ previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
+ previousMessageUserId: wrapper.previousMessageUserId
+ timestamp: wrapper.timestamp
+ userId: wrapper.userId
+ userName: wrapper.userName
+ userPowerlevel: wrapper.userPowerlevel
+ }
+ visible: status == Loader.Ready
+ z: 4
+ },
+ Rectangle {
+ anchors.fill: gridContainer
+ property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base)
+ property color threadBackgroundColor: wrapper.threadId ? Qt.tint(palette.base, Qt.hsla(threadColor.hslHue, 0.7, threadColor.hslLightness, 0.1)) : "transparent"
+ color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor
+
+ // this looks better without margins
+ TapHandler {
+ acceptedButtons: Qt.RightButton
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+
+ onSingleTapped: messageContextMenu.show(wrapper.eventId, wrapper.threadId, wrapper.type, wrapper.isSender, wrapper.isEncrypted, wrapper.isEditable, wrapper.main.hoveredLink, wrapper.main.copyText)
+ }
+ },
+ Rectangle {
+ id: scrollHighlight
+ anchors.fill: gridContainer
+
+ color: palette.highlight
+ enabled: false
+ opacity: 0
+ visible: true
+ z: 1
+
+ states: State {
+ name: "revealed"
+ when: wrapper.scrolledToThis
+ }
+ transitions: Transition {
+ from: ""
+ to: "revealed"
+
+ SequentialAnimation {
+ PropertyAnimation {
+ duration: 500
+ easing.type: Easing.InOutQuad
+ from: 0
+ properties: "opacity"
+ target: scrollHighlight
+ to: 1
+ }
+ PropertyAnimation {
+ duration: 500
+ easing.type: Easing.InOutQuad
+ from: 1
+ properties: "opacity"
+ target: scrollHighlight
+ to: 0
+ }
+ ScriptAction {
+ script: wrapper.room.eventShown()
+ }
+ }
+ }
+ },
+ Item {
+ id: gridContainer
+
+ width: wrapper.width - wrapper.avatarMargin
+ implicitHeight: messageBubble.implicitHeight
+ x: wrapper.avatarMargin
+ y: section.visible && section.active ? section.y + section.height : 0
+
+ HoverHandler {
+ id: messageHover
+ blocking: false
+ onHoveredChanged: () => {
+ if (!Settings.mobileMode && hovered) {
+ if (!messageActions.hovered) {
+ messageActions.model = wrapper;
+ messageActions.attached = wrapper;
+ messageActions.anchors.bottomMargin = -gridContainer.y
+ //messageActions.anchors.rightMargin = metadata.width
+ }
+ }
+ }
+
+ }
+
+
+ AbstractButton {
+ id: messageBubble
+
+ anchors.left: (wrapper.isStateEvent || wrapper.isSender) ? undefined : parent.left
+ anchors.right: (wrapper.isStateEvent || !wrapper.isSender) ? undefined : parent.right
+ anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined
+
+ property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base)
+
+ contentItem: Item {
+ id: contentPlacementContainer
+
+ property bool fitsMetadata: ((wrapper.main?.width ?? 0) + wrapper.mainInset + metadata.width) < wrapper.maxWidth
+
+ // This doesnt work because of tables. They might have content in the top of the cell, while the background reaches to the bottom. Maybe using the textDocument we could do more?
+ // property bool fitsMetadataInside: wrapper.main?.positionAt ? (wrapper.main.positionAt(wrapper.main.width, wrapper.main.height - 4) == wrapper.main.positionAt(wrapper.main.width - metadata.width, wrapper.main.height - 4)) : false
+ property bool fitsMetadataInside: false
+
+ implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0))
+ implicitHeight: contentColumn.implicitHeight + ((fitsMetadata || fitsMetadataInside) ? 0 : metadata.height)
+
+ TimelineMetadata {
+ id: metadata
+
+ scaling: 0.75
+
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ visible: !wrapper.isStateEvent
+
+ eventId: wrapper.eventId
+ status: wrapper.status
+ trustlevel: wrapper.trustlevel
+ isEdited: wrapper.isEdited
+ isEncrypted: wrapper.isEncrypted
+ threadId: wrapper.threadId
+ timestamp: wrapper.timestamp
+ room: wrapper.room
+ }
+
+ Column {
+ id: contentColumn
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ AbstractButton {
+ id: replyRow
+ visible: wrapper.reply
+
+ height: replyLine.height
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
+
+ clip: true
+
+ NhekoCursorShape {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ contentItem: Row {
+ id: replyRowLay
+
+ spacing: Nheko.paddingSmall
+
+ Rectangle {
+ id: replyLine
+ height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
+ color: replyRow.userColor
+ width: 4
+ }
+
+ Column {
+ spacing: 0
+
+ id: replyCol
+
+ AbstractButton {
+ id: replyUserButton
+
+ contentItem: Label {
+ id: userName_
+ text: wrapper.reply?.userName ?? ''
+ color: replyRow.userColor
+ textFormat: Text.RichText
+ width: wrapper.maxWidth
+ //elideWidth: wrapper.maxWidth
+ }
+ onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
+ }
+ data: [
+ replyUserButton,
+ wrapper.reply,
+ ]
+ }
+ }
+
+ background: Rectangle {
+ //width: replyRow.implicitContentWidth
+ color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
+ }
+
+ onClicked: {
+ let link = wrapper.reply.hoveredLink
+ if (link) {
+ Nheko.openLink(link)
+ } else {
+ console.log("Scrolling to "+wrapper.replyTo);
+ wrapper.room.showEvent(wrapper.replyTo)
+ }
+ }
+ onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX-replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo)
+ TapHandler {
+ acceptedButtons: Qt.RightButton
+ onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
+ }
+ }
+
+ data: [replyRow, wrapper.main]
+ }
+ }
+
+ padding: wrapper.isStateEvent ? 0 : 4
+ background: Rectangle {
+ color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent"
+ radius: 4
+ border.color: Nheko.theme.red
+ border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
+ }
+ }
+ },
+ Reactions {
+ id: reactionRow
+
+ eventId: wrapper.eventId
+ layoutDirection: (!wrapper.isStateEvent && wrapper.isSender) ? Qt.RightToLeft : Qt.LeftToRight
+ reactions: wrapper.reactions
+ width: wrapper.width - wrapper.avatarMargin
+ x: wrapper.avatarMargin
+
+ anchors {
+ //left: row.bubbleOnRight ? undefined : row.left
+ //right: row.bubbleOnRight ? row.right : undefined
+ top: gridContainer.bottom
+ topMargin: -4
+ }
+ },
+ Rectangle {
+ id: unreadRow
+
+ color: palette.highlight
+ height: visible ? 3 : 0
+ visible: (wrapper.index > 0 && (wrapper.room.fullyReadEventId == wrapper.eventId))
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: reactionRow.bottom
+ topMargin: 5
+ }
+ }
+ ]
+}
+
diff --git a/resources/qml/TimelineDefaultMessageStyle.qml b/resources/qml/TimelineDefaultMessageStyle.qml
new file mode 100644
index 00000000..e9a0712d
--- /dev/null
+++ b/resources/qml/TimelineDefaultMessageStyle.qml
@@ -0,0 +1,327 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "./components"
+import "./delegates"
+import "./emoji"
+import "./ui"
+import "./dialogs"
+import Qt.labs.platform 1.1 as Platform
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+import im.nheko
+
+TimelineEvent {
+ id: wrapper
+ ListView.delayRemove: true
+ width: chat.delegateMaxWidth
+ height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
+ anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
+ //room: chatRoot.roommodel
+
+ required property var day
+ required property bool isSender
+ required property int index
+ property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
+ property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
+ property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
+
+ required property date timestamp
+ required property string userId
+ required property string userName
+ required property string threadId
+ required property int userPowerlevel
+ required property bool isEdited
+ required property bool isEncrypted
+ required property var reactions
+ required property int status
+ required property int trustlevel
+ required property int notificationlevel
+ required property int type
+ required property bool isEditable
+
+ required property QtObject messageContextMenu
+ required property QtObject replyContextMenu
+ required property Item messageActions
+
+ property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
+
+ property alias hovered: messageHover.hovered
+ property bool scrolledToThis: false
+
+ mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0)
+ replyInset: mainInset + 4 + Nheko.paddingSmall
+
+ maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width
+
+ data: [
+ Loader {
+ id: section
+
+ active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
+ //asynchronous: true
+ sourceComponent: TimelineSectionHeader {
+ day: wrapper.day
+ isSender: wrapper.isSender
+ isStateEvent: wrapper.isStateEvent
+ parentWidth: wrapper.width
+ previousMessageDay: wrapper.previousMessageDay
+ previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
+ previousMessageUserId: wrapper.previousMessageUserId
+ timestamp: wrapper.timestamp
+ userId: wrapper.userId
+ userName: wrapper.userName
+ userPowerlevel: wrapper.userPowerlevel
+ }
+ visible: status == Loader.Ready
+ z: 4
+ },
+ Rectangle {
+ anchors.fill: gridContainer
+ color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : "transparent"
+
+ // this looks better without margins
+ TapHandler {
+ acceptedButtons: Qt.RightButton
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+
+ onSingleTapped: messageContextMenu.show(wrapper.eventId, wrapper.threadId, wrapper.type, wrapper.isSender, wrapper.isEncrypted, wrapper.isEditable, wrapper.main.hoveredLink, wrapper.main.copyText)
+ }
+ },
+ Rectangle {
+ id: scrollHighlight
+ anchors.fill: gridContainer
+
+ color: palette.highlight
+ enabled: false
+ opacity: 0
+ visible: true
+ z: 1
+
+ states: State {
+ name: "revealed"
+ when: wrapper.scrolledToThis
+ }
+ transitions: Transition {
+ from: ""
+ to: "revealed"
+
+ SequentialAnimation {
+ PropertyAnimation {
+ duration: 500
+ easing.type: Easing.InOutQuad
+ from: 0
+ properties: "opacity"
+ target: scrollHighlight
+ to: 1
+ }
+ PropertyAnimation {
+ duration: 500
+ easing.type: Easing.InOutQuad
+ from: 1
+ properties: "opacity"
+ target: scrollHighlight
+ to: 0
+ }
+ ScriptAction {
+ script: wrapper.room.eventShown()
+ }
+ }
+ }
+ },
+ Rectangle {
+ anchors.top: gridContainer.top
+ anchors.left: gridContainer.left
+ anchors.topMargin: -2
+ anchors.leftMargin: -2
+ color: "transparent"
+ border.color: Nheko.theme.red
+ border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
+ radius: 4
+ height: contentColumn.implicitHeight + 4
+ width: contentColumn.implicitWidth + 4
+ },
+ Row {
+ id: gridContainer
+
+ width: wrapper.width - wrapper.avatarMargin
+ x: wrapper.avatarMargin
+ y: section.visible && section.active ? section.y + section.height : 0
+ spacing: Nheko.paddingSmall
+
+ HoverHandler {
+ id: messageHover
+ blocking: false
+ onHoveredChanged: () => {
+ if (!Settings.mobileMode && hovered) {
+ if (!messageActions.hovered) {
+ messageActions.model = wrapper;
+ messageActions.attached = wrapper;
+ messageActions.anchors.bottomMargin = -gridContainer.y
+ messageActions.anchors.rightMargin = metadata.width
+ }
+ }
+ }
+
+ }
+
+ AbstractButton {
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: qsTr("Part of a thread")
+ ToolTip.visible: hovered
+ height: contentColumn.height
+ visible: wrapper.threadId
+ width: 4
+
+ onClicked: wrapper.room.thread = wrapper.threadId
+
+ Rectangle {
+ id: threadLine
+
+ anchors.fill: parent
+ 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
+
+ height: replyLine.height
+
+ property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
+
+ clip: true
+
+ NhekoCursorShape {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ }
+
+ contentItem: Row {
+ id: replyRowLay
+
+ spacing: Nheko.paddingSmall
+
+ Rectangle {
+ id: replyLine
+ height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
+ color: replyRow.userColor
+ width: 4
+ }
+
+ Column {
+ spacing: 0
+
+ id: replyCol
+
+ AbstractButton {
+ id: replyUserButton
+
+ contentItem: Label {
+ id: userName_
+ text: wrapper.reply?.userName ?? ''
+ color: replyRow.userColor
+ textFormat: Text.RichText
+ width: wrapper.maxWidth
+ //elideWidth: wrapper.maxWidth
+ }
+ onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
+ }
+ data: [
+ replyUserButton,
+ wrapper.reply,
+ ]
+ }
+ }
+
+ background: Rectangle {
+ //width: replyRow.implicitContentWidth
+ color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
+ }
+
+ onClicked: {
+ let link = wrapper.reply.hoveredLink
+ if (link) {
+ Nheko.openLink(link)
+ } else {
+ console.log("Scrolling to "+wrapper.replyTo);
+ wrapper.room.showEvent(wrapper.replyTo)
+ }
+ }
+ onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX-replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo)
+ TapHandler {
+ acceptedButtons: Qt.RightButton
+ onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
+ }
+ }
+
+ data: [
+ replyRow, wrapper.main,
+ ]
+ }
+
+ },
+ TimelineMetadata {
+ id: metadata
+
+ scaling: 1
+
+ anchors.right: parent.right
+ y: section.visible && section.active ? section.y + section.height : 0
+
+ visible: !wrapper.isStateEvent
+
+ eventId: wrapper.eventId
+ status: wrapper.status
+ trustlevel: wrapper.trustlevel
+ isEdited: wrapper.isEdited
+ isEncrypted: wrapper.isEncrypted
+ threadId: wrapper.threadId
+ timestamp: wrapper.timestamp
+ room: wrapper.room
+ },
+ Reactions {
+ id: reactionRow
+
+ eventId: wrapper.eventId
+ reactions: wrapper.reactions
+ width: wrapper.width - wrapper.avatarMargin
+ x: wrapper.avatarMargin
+
+ anchors {
+ top: gridContainer.bottom
+ topMargin: -4
+ }
+ },
+ Rectangle {
+ id: unreadRow
+
+ color: palette.highlight
+ height: visible ? 3 : 0
+ visible: (wrapper.index > 0 && (wrapper.room.fullyReadEventId == wrapper.eventId))
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: reactionRow.bottom
+ topMargin: 5
+ }
+ }
+ ]
+}
diff --git a/resources/qml/TimelineEvent.qml b/resources/qml/TimelineEvent.qml
new file mode 100644
index 00000000..7e60328d
--- /dev/null
+++ b/resources/qml/TimelineEvent.qml
@@ -0,0 +1,266 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "./components"
+import "./delegates"
+import "./emoji"
+import "./ui"
+import "./dialogs"
+import Qt.labs.platform 1.1 as Platform
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.13
+import im.nheko 1.0
+
+EventDelegateChooser {
+ id: wrapper
+
+ required property bool isStateEvent
+
+ EventDelegateChoice {
+ roleValues: [MtxEvent.TextMessage, MtxEvent.NoticeMessage, MtxEvent.ElementEffectMessage, MtxEvent.UnknownMessage,]
+
+ TextMessage {
+ required property string formattedBody
+ required property int type
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ //Layout.maximumWidth: implicitWidth
+
+ color: type == MtxEvent.NoticeMessage ? palette.buttonText : palette.text
+ font.italic: type == MtxEvent.NoticeMessage
+ formatted: formattedBody
+ keepFullText: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.EmoteMessage,]
+
+ TextMessage {
+ required property string formattedBody
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ //Layout.maximumWidth: implicitWidth
+
+ color: TimelineManager.userColor(userId, palette.base)
+ font.italic: true
+ formatted: TimelineManager.escapeEmoji(userName) + " " + formattedBody
+ keepFullText: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.CanonicalAlias, MtxEvent.ServerAcl, MtxEvent.Name, MtxEvent.Topic, MtxEvent.Avatar, MtxEvent.PinnedEvents, MtxEvent.ImagePackInRoom, MtxEvent.SpaceParent, MtxEvent.RoomCreate, MtxEvent.PowerLevels, MtxEvent.PolicyRuleUser, MtxEvent.PolicyRuleRoom, MtxEvent.PolicyRuleServer, MtxEvent.RoomJoinRules, MtxEvent.RoomHistoryVisibility, MtxEvent.RoomGuestAccess,]
+
+ TextMessage {
+ required property string formattedStateEvent
+ required property string userId
+ required property string userName
+
+ body: ''
+ color: palette.buttonText
+ font.italic: true
+ font.pointSize: Settings.fontSize * 0.8
+ formatted: ''
+ horizontalAlignment: Text.AlignHCenter
+ isOnlyEmoji: false
+ keepFullText: true
+ text: formattedStateEvent
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.CallInvite,]
+
+ TextMessage {
+ required property string callType
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ body: formatted
+ color: palette.buttonText
+ font.italic: true
+ formatted: {
+ switch (callType) {
+ case "voice":
+ return qsTr("%1 placed a voice call.").arg(TimelineManager.escapeEmoji(userName));
+ case "video":
+ return qsTr("%1 placed a video call.").arg(TimelineManager.escapeEmoji(userName));
+ default:
+ return qsTr("%1 placed a call.").arg(TimelineManager.escapeEmoji(userName));
+ }
+ }
+ isOnlyEmoji: false
+ keepFullText: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.CallAnswer, MtxEvent.CallReject, MtxEvent.CallSelectAnswer, MtxEvent.CallHangUp, MtxEvent.CallCandidates, MtxEvent.CallNegotiate,]
+
+ TextMessage {
+ required property int type
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ body: formatted
+ color: palette.buttonText
+ font.italic: true
+ formatted: {
+ switch (type) {
+ case MtxEvent.CallAnswer:
+ return qsTr("%1 answered the call.").arg(TimelineManager.escapeEmoji(userName));
+ case MtxEvent.CallReject:
+ return qsTr("%1 rejected the call.").arg(TimelineManager.escapeEmoji(userName));
+ case MtxEvent.CallSelectAnswer:
+ return qsTr("%1 selected answer.").arg(TimelineManager.escapeEmoji(userName));
+ case MtxEvent.CallHangUp:
+ return qsTr("%1 ended the call.").arg(TimelineManager.escapeEmoji(userName));
+ case MtxEvent.CallCandidates:
+ return qsTr("%1 is negotiating the call...").arg(TimelineManager.escapeEmoji(userName));
+ case MtxEvent.CallNegotiate:
+ return qsTr("%1 is negotiating the call...").arg(TimelineManager.escapeEmoji(userName));
+ }
+ }
+ isOnlyEmoji: false
+ keepFullText: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.ImageMessage, MtxEvent.Sticker,]
+
+ ImageMessage {
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ //Layout.maximumWidth: tempWidth
+ //Layout.maximumHeight: timelineView.height / 8
+ containerHeight: timelineView.height
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.FileMessage,]
+
+ FileMessage {
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.VideoMessage, MtxEvent.AudioMessage,]
+
+ PlayableMediaMessage {
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.Encrypted,]
+
+ Encrypted {
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.Encryption,]
+
+ EncryptionEnabled {
+ required property string userId
+
+ Layout.fillWidth: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.Redacted]
+
+ Redacted {
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.Member]
+
+ ColumnLayout {
+ id: member
+
+ required property string formattedStateEvent
+ required property Room room
+ required property string userId
+ required property string userName
+
+ NoticeMessage {
+ Layout.fillWidth: true
+ body: formatted
+ formatted: member.formattedStateEvent
+ isOnlyEmoji: false
+ isReply: EventDelegateChooser.isReply
+ isStateEvent: true
+ keepFullText: true
+ }
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Allow them in")
+ visible: member.room.showAcceptKnockButton(member.eventId)
+
+ onClicked: member.room.acceptKnock(member.eventId)
+ }
+ }
+ }
+ EventDelegateChoice {
+ roleValues: [MtxEvent.Tombstone]
+
+ ColumnLayout {
+ id: tombstone
+
+ required property string body
+ required property string eventId
+ required property Room room
+ required property string userId
+ required property string userName
+
+ NoticeMessage {
+ Layout.fillWidth: true
+ body: formatted
+ formatted: qsTr("This room was replaced for the following reason: %1").arg(tombstone.body)
+ isOnlyEmoji: false
+ isReply: EventDelegateChooser.isReply
+ isStateEvent: true
+ keepFullText: true
+ }
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Go to replacement room")
+
+ onClicked: tombstone.room.joinReplacementRoom(tombstone.eventId)
+ }
+ }
+ }
+ EventDelegateChoice {
+ roleValues: []
+
+ MatrixText {
+ required property string typeString
+ required property string userId
+ required property string userName
+
+ Layout.fillWidth: true
+ text: "Unsupported: " + typeString
+ }
+ }
+}
diff --git a/resources/qml/TimelineMetadata.qml b/resources/qml/TimelineMetadata.qml
new file mode 100644
index 00000000..53282fc5
--- /dev/null
+++ b/resources/qml/TimelineMetadata.qml
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "./components"
+import "./delegates"
+import "./emoji"
+import "./ui"
+import "./dialogs"
+import Qt.labs.platform 1.1 as Platform
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+import im.nheko
+
+RowLayout {
+ id: metadata
+
+ property int iconSize: Math.floor(fontMetrics.ascent * scaling)
+ required property double scaling
+
+ required property string eventId
+ required property int status
+ required property int trustlevel
+ required property bool isEdited
+ required property bool isEncrypted
+ required property string threadId
+ required property date timestamp
+ required property Room room
+
+ spacing: 2
+
+ StatusIndicator {
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ eventId: metadata.eventId
+ height: parent.iconSize
+ status: metadata.status
+ width: parent.iconSize
+ }
+ Image {
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: qsTr("Edited")
+ ToolTip.visible: editHovered.hovered
+ height: parent.iconSize
+ source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((metadata.eventId == metadata.room.edit) ? palette.highlight : palette.buttonText)
+ sourceSize.height: parent.iconSize * Screen.devicePixelRatio
+ sourceSize.width: parent.iconSize * Screen.devicePixelRatio
+ visible: metadata.isEdited || metadata.eventId == metadata.room.edit
+ width: parent.iconSize
+
+ HoverHandler {
+ id: editHovered
+
+ }
+ }
+ ImageButton {
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: qsTr("Part of a thread")
+ ToolTip.visible: hovered
+ buttonTextColor: TimelineManager.userColor(metadata.threadId, palette.base)
+ height: parent.iconSize
+ image: ":/icons/icons/ui/thread.svg"
+ visible: metadata.threadId
+ width: parent.iconSize
+
+ onClicked: metadata.room.thread = threadId
+ }
+ EncryptionIndicator {
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ encrypted: metadata.isEncrypted
+ height: parent.iconSize
+ sourceSize.height: parent.iconSize * Screen.devicePixelRatio
+ sourceSize.width: parent.iconSize * Screen.devicePixelRatio
+ trust: metadata.trustlevel
+ visible: metadata.room.isEncrypted
+ width: parent.iconSize
+ }
+ Label {
+ id: ts
+
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ Layout.preferredWidth: implicitWidth
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: Qt.formatDateTime(metadata.timestamp, Qt.DefaultLocaleLongDate)
+ ToolTip.visible: ma.hovered
+ color: palette.inactive.text
+ font.pointSize: fontMetrics.font.pointSize * parent.scaling
+ text: metadata.timestamp.toLocaleTimeString(Locale.ShortFormat)
+
+ HoverHandler {
+ id: ma
+
+ }
+ }
+}
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
deleted file mode 100644
index 16a31a3c..00000000
--- a/resources/qml/TimelineRow.qml
+++ /dev/null
@@ -1,349 +0,0 @@
-// SPDX-FileCopyrightText: Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import "./delegates"
-import "./emoji"
-import QtQuick 2.15
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.2
-import QtQuick.Window 2.13
-import im.nheko 1.0
-
-AbstractButton {
- id: r
-
- required property string blurhash
- required property string body
- required property string callType
- required property int duration
- required property int encryptionError
- required property string eventId
- required property string filename
- required property string filesize
- required property string formattedBody
- required property int index
- required property bool isEditable
- required property bool isEdited
- required property bool isEncrypted
- required property bool isOnlyEmoji
- required property bool isSender
- required property bool isStateEvent
- required property int notificationlevel
- required property int originalWidth
- required property double proportionalHeight
- required property var reactions
- required property int relatedEventCacheBuster
- required property string replyTo
- required property string roomName
- required property string roomTopic
- required property int status
- required property string threadId
- required property string thumbnailUrl
- required property var timestamp
- required property int trustlevel
- required property int type
- required property string typeString
- required property string url
- required property string userId
- required property string userName
-
- height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height
- hoverEnabled: true
-
- states: State {
- name: "dragging"
- when: draghandler.active
- }
- transitions: Transition {
- from: "dragging"
- to: ""
-
- PropertyAnimation {
- duration: 100
- easing.type: Easing.InOutQuad
- properties: "x"
- target: r
- to: 0
- }
- }
-
- onClicked: {
- let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y);
- if (link) {
- Nheko.openLink(link);
- }
- }
- onDoubleClicked: room.reply = eventId
- onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
-
- Rectangle {
- anchors.fill: parent
- color: (Settings.messageHoverHighlight && hovered) ? palette.alternateBase : "transparent"
-
- // this looks better without margins
- TapHandler {
- acceptedButtons: Qt.RightButton
- acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
- gesturePolicy: TapHandler.ReleaseWithinBounds
-
- onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
- }
- }
- DragHandler {
- id: draghandler
-
- xAxis.maximum: 100
- xAxis.minimum: -100
- yAxis.enabled: false
-
- onActiveChanged: {
- if (!active && (x < -70 || x > 70))
- room.reply = eventId;
- }
- }
- AbstractButton {
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: qsTr("Part of a thread")
- ToolTip.visible: hovered
- anchors.left: parent.left
- anchors.leftMargin: Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8) // align bubble with section header
- height: parent.height
- visible: threadId
- width: 4
-
- onClicked: room.thread = threadId
-
- Rectangle {
- id: threadLine
-
- anchors.fill: parent
- color: TimelineManager.userColor(threadId, palette.base)
- }
- }
- Rectangle {
- id: row
-
- property color bgColor: palette.base
- property bool bubbleOnRight: isSender && Settings.bubbles
- property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1)
- property color userColor: TimelineManager.userColor(userId, palette.base)
-
- anchors.horizontalCenter: isStateEvent ? parent.horizontalCenter : undefined
- anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
- anchors.leftMargin: (isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
- anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
- border.color: Nheko.theme.red
- border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
- color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
- height: msg.height + msg.anchors.margins * 2
- radius: 4
- width: Settings.bubbles ? Math.min(maxWidth, Math.max(reply.implicitWidth + 8, contentItem.implicitWidth + metadata.width + 20)) : maxWidth
-
- GridLayout {
- id: msg
-
- columnSpacing: 2
- columns: Settings.bubbles ? 1 : 2
- rowSpacing: 0
- rows: Settings.bubbles ? 3 : 2
-
- anchors {
- left: parent.left
- leftMargin: 4
- margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
- right: parent.right
- rightMargin: 4
- top: parent.top
- }
-
- // fancy reply, if this is a reply
- Reply {
- id: reply
-
- function fromModel(role) {
- return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
- }
-
- Layout.bottomMargin: visible ? 2 : 0
- Layout.column: 0
- Layout.fillWidth: true
- Layout.maximumWidth: Settings.bubbles ? Number.MAX_VALUE : implicitWidth
- Layout.preferredHeight: height
- Layout.row: 0
- blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
- body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
- callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? ""
- duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
- encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
- eventId: fromModel(Room.EventId) ?? ""
- filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
- filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
- formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
- isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
- isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
- originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
- proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
- relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
- roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
- roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
- thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
- type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
- typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
- url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
- userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
- userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
- userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
- visible: replyTo
- }
-
- // actual message content
- MessageDelegate {
- id: contentItem
-
- Layout.column: 0
- Layout.fillWidth: true
- Layout.preferredHeight: height
- Layout.row: 1
- blurhash: r.blurhash
- body: r.body
- callType: r.callType
- duration: r.duration
- encryptionError: r.encryptionError
- eventId: r.eventId
- filename: r.filename
- filesize: r.filesize
- formattedBody: r.formattedBody
- isOnlyEmoji: r.isOnlyEmoji
- isReply: false
- isStateEvent: r.isStateEvent
- metadataWidth: metadata.width
- originalWidth: r.originalWidth
- proportionalHeight: r.proportionalHeight
- relatedEventCacheBuster: r.relatedEventCacheBuster
- roomName: r.roomName
- roomTopic: r.roomTopic
- thumbnailUrl: r.thumbnailUrl
- type: r.type
- typeString: r.typeString ?? ""
- url: r.url
- userId: r.userId
- userName: r.userName
- }
- Row {
- id: metadata
-
- property int iconSize: Math.floor(fontMetrics.ascent * scaling)
- property double scaling: Settings.bubbles ? 0.75 : 1
-
- Layout.alignment: Qt.AlignTop | Qt.AlignRight
- Layout.bottomMargin: -2
- Layout.column: Settings.bubbles ? 0 : 1
- Layout.preferredWidth: implicitWidth
- Layout.row: Settings.bubbles ? 2 : 0
- Layout.rowSpan: Settings.bubbles ? 1 : 2
- Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
- spacing: 2
- visible: !isStateEvent
-
- StatusIndicator {
- Layout.alignment: Qt.AlignRight | Qt.AlignTop
- anchors.verticalCenter: ts.verticalCenter
- eventId: r.eventId
- height: parent.iconSize
- status: r.status
- width: parent.iconSize
- }
- Image {
- Layout.alignment: Qt.AlignRight | Qt.AlignTop
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: qsTr("Edited")
- ToolTip.visible: editHovered.hovered
- anchors.verticalCenter: ts.verticalCenter
- height: parent.iconSize
- source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
- sourceSize.height: parent.iconSize * Screen.devicePixelRatio
- sourceSize.width: parent.iconSize * Screen.devicePixelRatio
- visible: isEdited || eventId == room.edit
- width: parent.iconSize
-
- HoverHandler {
- id: editHovered
-
- }
- }
- ImageButton {
- Layout.alignment: Qt.AlignRight | Qt.AlignTop
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: qsTr("Part of a thread")
- ToolTip.visible: hovered
- anchors.verticalCenter: ts.verticalCenter
- buttonTextColor: TimelineManager.userColor(threadId, palette.base)
- height: parent.iconSize
- image: ":/icons/icons/ui/thread.svg"
- visible: threadId
- width: parent.iconSize
-
- onClicked: room.thread = threadId
- }
- EncryptionIndicator {
- Layout.alignment: Qt.AlignRight | Qt.AlignTop
- anchors.verticalCenter: ts.verticalCenter
- encrypted: isEncrypted
- height: parent.iconSize
- sourceSize.height: parent.iconSize * Screen.devicePixelRatio
- sourceSize.width: parent.iconSize * Screen.devicePixelRatio
- trust: trustlevel
- visible: room.isEncrypted
- width: parent.iconSize
- }
- Label {
- id: ts
-
- Layout.alignment: Qt.AlignRight | Qt.AlignTop
- Layout.preferredWidth: implicitWidth
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
- ToolTip.visible: ma.hovered
- color: palette.inactive.text
- font.pointSize: fontMetrics.font.pointSize * parent.scaling
- text: timestamp.toLocaleTimeString(Locale.ShortFormat)
-
- HoverHandler {
- id: ma
-
- }
- }
- }
- }
- }
- Reactions {
- id: reactionRow
-
- eventId: r.eventId
- layoutDirection: row.bubbleOnRight ? Qt.RightToLeft : Qt.LeftToRight
- reactions: r.reactions
- width: row.maxWidth
-
- anchors {
- left: row.bubbleOnRight ? undefined : row.left
- right: row.bubbleOnRight ? row.right : undefined
- top: row.bottom
- topMargin: -4
- }
- }
- Rectangle {
- id: unreadRow
-
- color: palette.highlight
- height: visible ? 3 : 0
- visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
-
- anchors {
- left: parent.left
- right: parent.right
- top: reactionRow.bottom
- topMargin: 5
- }
- }
-}
diff --git a/resources/qml/TimelineSectionHeader.qml b/resources/qml/TimelineSectionHeader.qml
new file mode 100644
index 00000000..91d076d2
--- /dev/null
+++ b/resources/qml/TimelineSectionHeader.qml
@@ -0,0 +1,164 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import Qt.labs.platform 1.1 as Platform
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.13
+import im.nheko 1.0
+
+Column {
+
+ required property var day
+ required property bool isSender
+ required property bool isStateEvent
+ required property int parentWidth
+ required property var previousMessageDay
+ required property bool previousMessageIsStateEvent
+ required property string previousMessageUserId
+ required property date timestamp
+ required property string userId
+ required property string userName
+ required property string userPowerlevel
+
+ bottomPadding: Settings.bubbles ? (isSender && previousMessageDay == day ? 0 : 2) : 3
+ spacing: 8
+ topPadding: userName_.visible ? 4 : 0
+ visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
+ width: parentWidth
+
+ Label {
+ id: dateBubble
+
+ anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+ color: palette.text
+ height: Math.round(fontMetrics.height * 1.4)
+ horizontalAlignment: Text.AlignHCenter
+ text: room ? room.formatDateSeparator(timestamp) : ""
+ verticalAlignment: Text.AlignVCenter
+ visible: room && previousMessageDay !== day
+ width: contentWidth * 1.2
+
+ background: Rectangle {
+ color: palette.window
+ radius: parent.height / 2
+ }
+ }
+ Row {
+ id: userInfo
+
+ property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width
+
+ height: userName_.height
+ spacing: 8
+ visible: !isStateEvent && (!isSender || !Settings.bubbles)
+
+ Avatar {
+ id: messageUserAvatar
+
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: userid
+ ToolTip.visible: messageUserAvatar.hovered
+ displayName: userName
+ height: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1)
+ url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
+ userid: userId
+ width: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1)
+
+ onClicked: room.openUserProfile(userId)
+ }
+ Connections {
+ function onRoomAvatarUrlChanged() {
+ messageUserAvatar.url = room.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
+ }
+ function onScrollToIndex(index) {
+ chat.positionViewAtIndex(index, ListView.Center);
+ }
+
+ target: room
+ }
+
+ AbstractButton {
+ id: userNameButton
+
+ PowerlevelIndicator {
+ id: powerlevelIndicator
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+
+ powerlevel: userPowerlevel
+ height: fontMetrics.ascent
+ width: height
+
+ sourceSize.width: fontMetrics.lineSpacing
+ sourceSize.height: fontMetrics.lineSpacing
+
+ permissions: room ? room.permissions : null
+ visible: isAdmin || isModerator
+ }
+
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: userId
+ ToolTip.visible: hovered
+ leftPadding: powerlevelIndicator.visible ? 16 : 0
+ leftInset: 0
+ rightInset: 0
+ rightPadding: 0
+
+ contentItem: Label {
+ id: userName_
+
+ color: TimelineManager.userColor(userId, palette.base)
+ text: TimelineManager.escapeEmoji(userNameTextMetrics.elidedText)
+ textFormat: Text.RichText
+ }
+
+ onClicked: room.openUserProfile(userId)
+
+ TextMetrics {
+ id: userNameTextMetrics
+
+ elide: Text.ElideRight
+ elideWidth: userInfo.remainingWidth - Math.min(statusMsg.implicitWidth, userInfo.remainingWidth / 3)
+ text: userName
+ }
+ NhekoCursorShape {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ Label {
+ id: statusMsg
+
+ property string userStatus: Presence.userStatus(userId)
+
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: qsTr("%1's status message").arg(userName)
+ ToolTip.visible: statusMsgHoverHandler.hovered
+ anchors.baseline: userNameButton.baseline
+ color: palette.buttonText
+ elide: Text.ElideRight
+ font.italic: true
+ font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.8)
+ text: userStatus.replace(/\n/g, " ")
+ textFormat: Text.PlainText
+ width: Math.min(implicitWidth, userInfo.remainingWidth - userName_.width - parent.spacing)
+
+ HoverHandler {
+ id: statusMsgHoverHandler
+
+ }
+ Connections {
+ function onPresenceChanged(id) {
+ if (id == userId)
+ statusMsg.userStatus = Presence.userStatus(userId);
+ }
+
+ target: Presence
+ }
+ }
+ }
+}
+
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 699595e6..aba91763 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -285,25 +285,10 @@ Pane {
property var e: room ? room.getDump(modelData, "pins") : {}
- Layout.fillWidth: true
- Layout.preferredHeight: height
- blurhash: e.blurhash ?? ""
- body: e.body ?? ""
- encryptionError: e.encryptionError ?? 0
+ maxWidth: pinnedMessages.width
+ //Layout.preferredHeight: height
eventId: e.eventId ?? ""
- filename: e.filename ?? ""
- filesize: e.filesize ?? ""
- formattedBody: e.formattedBody ?? ""
- isOnlyEmoji: e.isOnlyEmoji ?? false
- keepFullText: true
- originalWidth: e.originalWidth ?? 0
- proportionalHeight: e.proportionalHeight ?? 1
- type: e.type ?? MtxEvent.UnknownMessage
- typeString: e.typeString ?? ""
- url: e.url ?? ""
userColor: TimelineManager.userColor(e.userId, palette.window)
- userId: e.userId ?? ""
- userName: e.userName ?? ""
Connections {
function onPinnedMessagesChanged() {
diff --git a/resources/qml/delegates/Encrypted.qml b/resources/qml/delegates/Encrypted.qml
index fdfe958e..7aeeb28a 100644
--- a/resources/qml/delegates/Encrypted.qml
+++ b/resources/qml/delegates/Encrypted.qml
@@ -8,37 +8,34 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import im.nheko 1.0
-Rectangle {
+Control {
id: r
required property int encryptionError
required property string eventId
- radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
- width: parent.width? parent.width : 0
- implicitWidth: encryptedText.implicitWidth+24+Nheko.paddingMedium*3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout
- height: contents.implicitHeight + Nheko.paddingMedium * 2
- color: palette.alternateBase
+ padding: Nheko.paddingMedium
+ implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2
+ Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
+ Layout.fillWidth: true
- RowLayout {
+ contentItem: RowLayout {
id: contents
- anchors.fill: parent
- anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Image {
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
Layout.alignment: Qt.AlignVCenter
- width: 24
- height: width
+ Layout.preferredWidth: 24
+ Layout.preferredHeight: 24
}
- Column {
+ ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true
- MatrixText {
+ Label {
id: encryptedText
text: {
switch (encryptionError) {
@@ -58,8 +55,11 @@ Rectangle {
return qsTr("Unknown decryption error");
}
}
+ textFormat: Text.PlainText
+ wrapMode: Label.WordWrap
color: palette.text
- width: parent.width
+ Layout.fillWidth: true
+ Layout.maximumWidth: implicitWidth + 1
}
Button {
@@ -72,4 +72,9 @@ Rectangle {
}
+ background: Rectangle {
+ color: palette.alternateBase
+ radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium
+ visible: !Settings.bubbles // the bubble in a bubble looks odd
+ }
}
diff --git a/resources/qml/delegates/EncryptionEnabled.qml b/resources/qml/delegates/EncryptionEnabled.qml
index 0e2b7fc0..40894543 100644
--- a/resources/qml/delegates/EncryptionEnabled.qml
+++ b/resources/qml/delegates/EncryptionEnabled.qml
@@ -3,27 +3,24 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
-import QtQuick 2.15
-import QtQuick.Layouts 1.15
-import im.nheko 1.0
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import im.nheko
-Rectangle {
+Control {
id: r
- required property string username
+ required property string userName
- radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
- width: parent.width ? Math.min(parent.width, 700) : 0
- height: contents.implicitHeight + Nheko.paddingMedium * 2
- color: palette.alternateBase
- border.color: Nheko.theme.green
- border.width: 2
+ padding: Nheko.paddingMedium
+ //implicitHeight: contents.implicitHeight + padd * 2
+ Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
+ Layout.fillWidth: true
- RowLayout {
+ contentItem: RowLayout {
id: contents
- anchors.fill: parent
- anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Image {
@@ -33,26 +30,36 @@ Rectangle {
Layout.preferredHeight: 24
}
- Column {
+ ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true
MatrixText {
- text: qsTr("%1 enabled end-to-end encryption").arg(r.username)
+ text: qsTr("%1 enabled end-to-end encryption").arg(r.userName)
font.bold: true
font.pointSize: 14
color: palette.text
- width: parent.width
+ Layout.fillWidth: true
+ Layout.maximumWidth: implicitWidth + 1
}
- MatrixText {
+ Label {
text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.")
- color: palette.text
- width: parent.width
+ textFormat: Text.PlainText
+ wrapMode: Label.WordWrap
+ Layout.fillWidth: true
+ Layout.maximumWidth: implicitWidth + 1
}
}
}
+ background: Rectangle {
+ radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
+ height: contents.implicitHeight + Nheko.paddingMedium * 2
+ color: palette.alternateBase
+ border.color: Nheko.theme.green
+ border.width: 2
+ }
}
diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml
index 82b82c1b..9f350123 100644
--- a/resources/qml/delegates/FileMessage.qml
+++ b/resources/qml/delegates/FileMessage.qml
@@ -2,26 +2,30 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
-import QtQuick 2.12
-import QtQuick.Layouts 1.2
-import im.nheko 1.0
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import im.nheko
+
+Control {
+ id: evRoot
-Item {
required property string eventId
required property string filename
required property string filesize
- height: rowa.height + (Settings.bubbles? 16: 24)
- implicitWidth: rowa.implicitWidth + metadataWidth
- property int metadataWidth
- property bool fitsMetadata: true
+ padding: Settings.bubbles? 8 : 12
+ //Layout.preferredHeight: rowa.implicitHeight + padding
+ //Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding
+ property int metadataWidth: 0
+ property bool fitsMetadata: false
+
+ Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2
- RowLayout {
+ contentItem: RowLayout {
id: rowa
- anchors.centerIn: parent
- width: parent.width - (Settings.bubbles? 16 : 24)
- spacing: 15
+ spacing: 16
Rectangle {
id: button
@@ -63,6 +67,7 @@ Item {
id: filename_
Layout.fillWidth: true
+ Layout.maximumWidth: implicitWidth + 1
text: filename
textFormat: Text.PlainText
elide: Text.ElideRight
@@ -73,6 +78,7 @@ Item {
id: filesize_
Layout.fillWidth: true
+ Layout.maximumWidth: implicitWidth + 1
text: filesize
textFormat: Text.PlainText
elide: Text.ElideRight
@@ -83,11 +89,9 @@ Item {
}
- Rectangle {
+ background: Rectangle {
color: palette.alternateBase
- z: -1
- radius: 10
- anchors.fill: parent
+ radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml
index 20d727c3..9c93c25b 100644
--- a/resources/qml/delegates/ImageMessage.qml
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -2,29 +2,31 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
-import QtQuick 2.15
-import QtQuick.Window 2.15
-import QtQuick.Controls 2.3
-import im.nheko 1.0
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import im.nheko
AbstractButton {
required property int type
required property int originalWidth
+ required property int originalHeight
required property double proportionalHeight
required property string url
required property string blurhash
required property string body
required property string filename
- required property bool isReply
required property string eventId
- property double divisor: isReply ? 5 : 3
+ required property int containerHeight
+ property double divisor: EventDelegateChooser.isReply ? 10 : 4
- property int tempWidth: originalWidth < 1? 400: originalWidth
+ EventDelegateChooser.keepAspectRatio: true
+ EventDelegateChooser.maxWidth: originalWidth
+ EventDelegateChooser.maxHeight: containerHeight / divisor
+ EventDelegateChooser.aspectRatio: proportionalHeight
- implicitWidth: Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1))
- width: Math.min(parent?.width ?? 2000,implicitWidth)
- height: width*proportionalHeight
hoverEnabled: true
+ enabled: !EventDelegateChooser.isReply
state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible"
states: [
@@ -116,6 +118,7 @@ AbstractButton {
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
asynchronous: true
fillMode: Image.PreserveAspectFit
+ horizontalAlignment: Image.AlignLeft
smooth: true
mipmap: true
@@ -127,21 +130,23 @@ AbstractButton {
id: mxcimage
visible: loaded
- anchors.fill: parent
roomm: room
play: !Settings.animateImagesOnHover || parent.hovered
eventId: parent.eventId
+
+ anchors.fill: parent
}
Image {
id: blurhash_
- anchors.fill: parent
source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText)
asynchronous: true
fillMode: Image.PreserveAspectFit
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);
@@ -150,6 +155,7 @@ AbstractButton {
id: overlay
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 68f65062..00000000
--- a/resources/qml/delegates/MessageDelegate.qml
+++ /dev/null
@@ -1,779 +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
- }
-
- }
-
- 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
- }
-
- }
-
- 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 {
- width: parent?.width ?? 100
-
- 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..99928369 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 ? 10 : 4
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/Redacted.qml b/resources/qml/delegates/Redacted.qml
index 4a9700dc..3c496f08 100644
--- a/resources/qml/delegates/Redacted.qml
+++ b/resources/qml/delegates/Redacted.qml
@@ -2,25 +2,22 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
-import QtQuick 2.15
-import QtQuick.Controls 2.15
-import QtQuick.Layouts 1.15
-import im.nheko 1.0
-
-Rectangle{
-
- height: redactedLayout.implicitHeight + Nheko.paddingSmall
- implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
- width: Math.min(parent.width,implicitWidth+1)
- radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
- color: palette.alternateBase
- property int metadataWidth
- property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4
-
- RowLayout {
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import im.nheko
+
+Control {
+ id: msgRoot
+
+ property int metadataWidth: 0
+ property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4
+
+ required property string eventId
+ required property Room room
+
+ contentItem: RowLayout {
id: redactedLayout
- anchors.centerIn: parent
- width: parent.width - 2 * Nheko.paddingMedium
spacing: Nheko.paddingSmall
Image {
@@ -34,12 +31,11 @@ Rectangle{
id: redactedLabel
Layout.margins: 0
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
- Layout.preferredWidth: implicitWidth
+ Layout.maximumWidth: implicitWidth + 1
Layout.fillWidth: true
- property var redactedPair: room.formatRedactedEvent(eventId)
+ property var redactedPair: room.formatRedactedEvent(msgRoot.eventId)
text: redactedPair["first"]
wrapMode: Label.WordWrap
- color: palette.text
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
@@ -48,4 +44,13 @@ Rectangle{
}
}
}
+
+ padding: Nheko.paddingSmall
+
+ Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2
+
+ background: Rectangle {
+ color: palette.alternateBase
+ radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
+ }
}
diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml
index 4d4983ac..ece838b7 100644
--- a/resources/qml/delegates/Reply.qml
+++ b/resources/qml/delegates/Reply.qml
@@ -14,128 +14,88 @@ AbstractButton {
id: r
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 bool isStateEvent
- property string userId
- property string userName
- property string thumbnailUrl
- property string roomTopic
- property string roomName
- property string callType
- property int duration
- property int encryptionError
- property int relatedEventCacheBuster
- property int maxWidth
property bool keepFullText: false
- height: replyContainer.height
- implicitHeight: replyContainer.height
- implicitWidth: visible? colorLine.width+Math.max(replyContainer.implicitWidth,userName_.fullTextWidth) : 0 // visible? seems to be causing issues
+ required property string eventId
+
+ property var room_: room
+
+ property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : ""
+ property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : ""
+ implicitHeight: replyContainer.implicitHeight
+ implicitWidth: replyContainer.implicitWidth
+ required property int maxWidth
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
- Rectangle {
- id: colorLine
-
- anchors.top: replyContainer.top
- anchors.bottom: replyContainer.bottom
- width: 4
- color: TimelineManager.userColor(userId, palette.base)
- }
-
onClicked: {
- let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorLine.width, pressY - userName_.implicitHeight);
+ let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight);
if (link) {
Nheko.openLink(link)
} else {
room.showEvent(r.eventId)
}
}
- onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX-colorLine.width, pressY - userName_.implicitHeight), r.eventId)
+ onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight), r.eventId)
- ColumnLayout {
- id: replyContainer
+ contentItem: TimelineEvent {
+ id: timelineEvent
- anchors.left: colorLine.right
- width: parent.width - 4
- spacing: 0
+ isStateEvent: false
+ room: room_
+ eventId: r.eventId
+ replyTo: ""
+ mainInset: 4 + Nheko.paddingMedium
+ maxWidth: r.maxWidth
- TapHandler {
- acceptedButtons: Qt.RightButton
- onSingleTapped: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight), r.eventId)
- gesturePolicy: TapHandler.ReleaseWithinBounds
- acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
- }
+ //height: replyContainer.implicitHeight
+ data: Row {
+ id: replyContainer
+
+ spacing: Nheko.paddingSmall
- AbstractButton {
- Layout.leftMargin: 4
- Layout.fillWidth: true
- contentItem: ElidedLabel {
- id: userName_
- fullText: userName
- color: r.userColor
- textFormat: Text.RichText
- width: parent.width
- elideWidth: width
+ Rectangle {
+ id: colorline
+
+ width: 4
+ height: content.height
+
+ color: TimelineManager.userColor(r.userId, palette.base)
}
- onClicked: room.openUserProfile(userId)
- }
- MessageDelegate {
- Layout.leftMargin: 4
- Layout.preferredHeight: height
- 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
- duration: r.duration
- originalWidth: r.originalWidth
- isOnlyEmoji: r.isOnlyEmoji
- isStateEvent: r.isStateEvent
- userId: r.userId
- userName: r.userName
- roomTopic: r.roomTopic
- roomName: r.roomName
- callType: r.callType
- relatedEventCacheBuster: r.relatedEventCacheBuster
- encryptionError: r.encryptionError
- // This is disabled so that left clicking the reply goes to its location
- enabled: false
- Layout.fillWidth: true
- isReply: true
- keepFullText: r.keepFullText
- }
+ Column {
+ id: content
+ spacing: 0
+
+ AbstractButton {
+ id: usernameBtn
+
+ contentItem: Label {
+ id: userName_
+ text: r.userName
+ color: r.userColor
+ textFormat: Text.RichText
+ width: timelineEvent.main?.width
+ }
+ onClicked: room.openUserProfile(r.userId)
+ }
+
+ data: [
+ usernameBtn, timelineEvent.main,
+ ]
+ }
+ }
}
- Rectangle {
+ background: Rectangle {
id: backgroundItem
z: -1
- anchors.fill: replyContainer
- property color userColor: TimelineManager.userColor(userId, palette.base)
+ property color userColor: TimelineManager.userColor(r.userId, palette.base)
property color bgColor: palette.base
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
}
diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml
index aabb7136..cc54ddab 100644
--- a/resources/qml/delegates/TextMessage.qml
+++ b/resources/qml/delegates/TextMessage.qml
@@ -9,11 +9,12 @@ 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
+
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
- property int metadataWidth
+ property int metadataWidth: 100
property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
// table border-collapse doesn't seem to work
@@ -39,11 +40,8 @@ MatrixText {
}" : "") + // TODO(Nico): Figure out how to support mobile
"</style>
" + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
- width: parent?.width ?? 0
- height: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
- clip: !keepFullText
+
selectByMouse: !isReply
-// enabled: !Settings.mobileMode
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
NhekoCursorShape {
diff --git a/resources/qml/ui/TimelineEffects.qml b/resources/qml/ui/TimelineEffects.qml
index 72237e31..35c54c04 100644
--- a/resources/qml/ui/TimelineEffects.qml
+++ b/resources/qml/ui/TimelineEffects.qml
@@ -9,6 +9,7 @@ Item {
id: effectRoot
readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan)
required property bool shouldEffectsRun
+ visible: effectRoot.shouldEffectsRun
function pulseConfetti()
{
@@ -23,8 +24,9 @@ Item {
ParticleSystem {
id: particleSystem
- Component.onCompleted: pause();
+ Component.onCompleted: stop();
paused: !effectRoot.shouldEffectsRun
+ running: effectRoot.shouldEffectsRun
}
Emitter {
@@ -89,26 +91,47 @@ Item {
enabled: false
anchors.horizontalCenter: effectRoot.horizontalCenter
y: -60
- emitRate: effectRoot.width / 50
+ emitRate: effectRoot.width / 30
lifeSpan: 10000
system: particleSystem
velocity: PointDirection {
x: 0
- y: 300
+ y: 400
xVariation: 0
yVariation: 75
}
- ItemParticle {
- system: particleSystem
- groups: ["rain"]
- fade: false
- delegate: Rectangle {
- width: 2
- height: 30 + 30 * Math.random()
- radius: 2
+ // causes high CPU load, see: https://bugreports.qt.io/browse/QTBUG-117923
+ //ItemParticle {
+ // system: particleSystem
+ // groups: ["rain"]
+ // fade: false
+ // visible: effectRoot.shouldEffectsRun
+ // delegate: Rectangle {
+ // width: 2
+ // height: 30 + 30 * Math.random()
+ // radius: 2
+ // color: "#0099ff"
+ // }
+ //}
+
+ ImageParticle {
+ system: particleSystem
+ groups: ["rain"]
+ source: "qrc:/confettiparticle.svg"
+ rotationVelocity: 0
+ rotationVelocityVariation: 0
+ colorVariation: 0
color: "#0099ff"
+ entryEffect: ImageParticle.None
+ xVector: PointDirection {
+ x: 0.01
+ y: 0
+ }
+ yVector: PointDirection {
+ x: 0
+ y: 5
+ }
}
}
}
-}
|