diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 07cb5ce2..a064bd15 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -13,72 +13,45 @@ import im.nheko 1.0
AbstractButton {
id: r
- required property double proportionalHeight
- required property int type
- required property string typeString
- required property int originalWidth
required property string blurhash
required property string body
- required property string formattedBody
+ required property string callType
+ required property int duration
+ required property int encryptionError
required property string eventId
required property string filename
required property string filesize
- required property string url
- required property string thumbnailUrl
- required property bool isOnlyEmoji
- required property bool isSender
- required property bool isEncrypted
+ required property 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
- required property string roomTopic
- required property string roomName
- required property string callType
- required property var reactions
- required property int trustlevel
- required property int notificationlevel
- required property int encryptionError
- required property int duration
- required property var timestamp
- required property int status
- required property int index
- required property int relatedEventCacheBuster
+ height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height
hoverEnabled: true
-
width: parent.width
- height: row.height+(reactionRow.height > 0 ? reactionRow.height-2 : 0 )+unreadRow.height
-
- Rectangle {
- color: (Settings.messageHoverHighlight && hovered) ? palette.alternateBase : "transparent"
- anchors.fill: parent
- // this looks better without margins
- TapHandler {
- acceptedButtons: Qt.RightButton
- onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
- gesturePolicy: TapHandler.ReleaseWithinBounds
- acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
- }
- }
-
- onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
- onDoubleClicked: room.reply = eventId
-
- DragHandler {
- id: draghandler
- yAxis.enabled: false
- xAxis.maximum: 100
- xAxis.minimum: -100
- onActiveChanged: {
- if(!active && (x < -70 || x > 70))
- room.reply = eventId
- }
- }
states: State {
name: "dragging"
when: draghandler.active
@@ -86,265 +59,292 @@ AbstractButton {
transitions: Transition {
from: "dragging"
to: ""
+
PropertyAnimation {
- target: r
- properties: "x"
+ duration: 100
easing.type: Easing.InOutQuad
+ properties: "x"
+ target: r
to: 0
- duration: 100
}
}
onClicked: {
- let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX-row.x-msg.x, pressY-row.y-msg.y-contentItem.y);
+ 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)
+ 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 {
- anchors.leftMargin: Settings.smallAvatars? 0 : (Nheko.avatarSize + 8) // align bubble with section header
+ 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
- height: parent.height
+
+ onClicked: room.thread = threadId
Rectangle {
id: threadLine
- color: TimelineManager.userColor(threadId, palette.base)
anchors.fill: parent
+ color: TimelineManager.userColor(threadId, palette.base)
}
-
- ToolTip.visible: hovered
- ToolTip.delay: Nheko.tooltipDelay
- ToolTip.text: qsTr("Part of a thread")
- onClicked: room.thread = threadId
}
-
Rectangle {
id: row
- property bool bubbleOnRight : isSender && Settings.bubbles
- anchors.leftMargin: (isStateEvent || Settings.smallAvatars? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
- anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
- anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
- anchors.horizontalCenter: isStateEvent? parent.horizontalCenter : undefined
- property int maxWidth: (parent.width-(Settings.smallAvatars || isStateEvent? 0 : Nheko.avatarSize+8))*(Settings.bubbles && !isStateEvent? 0.9 : 1)
- width: Settings.bubbles? Math.min(maxWidth,Math.max(reply.implicitWidth+8,contentItem.implicitWidth+metadata.width+20)) : maxWidth
- height: msg.height+msg.anchors.margins*2
- property color userColor: TimelineManager.userColor(userId, palette.base)
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
- border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
- border.color: Nheko.theme.red
+ 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
- top: parent.top
- right: parent.right
- margins: (Settings.bubbles && ! isStateEvent)? 4 : 2
leftMargin: 4
+ margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
+ right: parent.right
rightMargin: 4
+ top: parent.top
}
- id: msg
- rowSpacing: 0
- columnSpacing: 2
- columns: Settings.bubbles? 1 : 2
- rows: Settings.bubbles? 3 : 2
// fancy reply, if this is a reply
Reply {
- Layout.row: 0
- Layout.column: 0
- Layout.fillWidth: true
- Layout.maximumWidth: Settings.bubbles? Number.MAX_VALUE : implicitWidth
- Layout.bottomMargin: visible? 2 : 0
- Layout.preferredHeight: height
id: reply
function fromModel(role) {
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
}
- visible: replyTo
- userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
+
+ 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) ?? ""
- formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
+ callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
+ 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) ?? ""
- originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
- isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
- isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
+ userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
- thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
- duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
- roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
- roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
- callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
- encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
- relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
+ visible: replyTo
}
// actual message content
MessageDelegate {
- Layout.row: 1
+ id: contentItem
+
Layout.column: 0
Layout.fillWidth: true
Layout.preferredHeight: height
- id: contentItem
-
+ Layout.row: 1
blurhash: r.blurhash
body: r.body
- formattedBody: r.formattedBody
+ 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
- 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
- encryptionError: r.encryptionError
- relatedEventCacheBuster: r.relatedEventCacheBuster
- isReply: false
- metadataWidth: metadata.width
}
-
Row {
id: metadata
- Layout.column: Settings.bubbles? 0 : 1
- Layout.row: Settings.bubbles? 2 : 0
- Layout.rowSpan: Settings.bubbles? 1 : 2
- Layout.bottomMargin: -2
- Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0
+
+ 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
- visible: !isStateEvent
+ Layout.row: Settings.bubbles ? 2 : 0
+ Layout.rowSpan: Settings.bubbles ? 1 : 2
+ Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
spacing: 2
-
- property double scaling: Settings.bubbles? 0.75 : 1
-
- property int iconSize: Math.floor(fontMetrics.ascent*scaling)
+ visible: !isStateEvent
StatusIndicator {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ anchors.verticalCenter: ts.verticalCenter
+ eventId: r.eventId
height: parent.iconSize
- width: parent.iconSize
status: r.status
- eventId: r.eventId
- anchors.verticalCenter: ts.verticalCenter
+ width: parent.iconSize
}
-
Image {
- visible: isEdited || eventId == room.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop
- height: parent.iconSize
- width: parent.iconSize
- sourceSize.width: parent.iconSize * Screen.devicePixelRatio
- sourceSize.height: parent.iconSize * Screen.devicePixelRatio
- source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
- ToolTip.visible: editHovered.hovered
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 {
- visible: threadId
Layout.alignment: Qt.AlignRight | Qt.AlignTop
- height: parent.iconSize
- width: parent.iconSize
- image: ":/icons/icons/ui/thread.svg"
- buttonTextColor: TimelineManager.userColor(threadId, palette.base)
- ToolTip.visible: hovered
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 {
- visible: room.isEncrypted
- encrypted: isEncrypted
- trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ anchors.verticalCenter: ts.verticalCenter
+ encrypted: isEncrypted
height: parent.iconSize
- width: parent.iconSize
- sourceSize.width: parent.iconSize * Screen.devicePixelRatio
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
- anchors.verticalCenter: ts.verticalCenter
+ 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
- text: timestamp.toLocaleTimeString(Locale.ShortFormat)
- color: palette.inactive.text
- ToolTip.visible: ma.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
- font.pointSize: fontMetrics.font.pointSize*parent.scaling
+ 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
- left: row.bubbleOnRight? undefined : row.left
- right: row.bubbleOnRight? row.right : undefined
}
- width: row.maxWidth
- layoutDirection: row.bubbleOnRight? Qt.RightToLeft : Qt.LeftToRight
-
- id: reactionRow
-
- reactions: r.reactions
- eventId: r.eventId
}
-
Rectangle {
id: unreadRow
+
+ color: palette.highlight
+ height: visible ? 3 : 0
+ visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
+
anchors {
- top: reactionRow.bottom
- topMargin: 5
left: parent.left
right: parent.right
+ top: reactionRow.bottom
+ topMargin: 5
}
- color: palette.highlight
-
- visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
- height: visible ? 3 : 0
-
}
}
|