Merge branch 'message-bubbles' of https://github.com/maltee1/nheko into maltee1-message-bubbles

pull/939/head
Nicolas Werner 3 years ago
commit ddcd4850f1
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 27
      resources/qml/MessageView.qml
  2. 1
      resources/qml/ReplyPopup.qml
  3. 182
      resources/qml/TimelineRow.qml
  4. 4
      resources/qml/delegates/Encrypted.qml
  5. 4
      resources/qml/delegates/FileMessage.qml
  6. 8
      resources/qml/delegates/ImageMessage.qml
  7. 49
      resources/qml/delegates/MessageDelegate.qml
  8. 3
      resources/qml/delegates/NoticeMessage.qml
  9. 6
      resources/qml/delegates/Pill.qml
  10. 2
      resources/qml/delegates/Placeholder.qml
  11. 11
      resources/qml/delegates/PlayableMediaMessage.qml
  12. 7
      resources/qml/delegates/Redacted.qml
  13. 25
      resources/qml/delegates/Reply.qml
  14. 2
      resources/qml/delegates/TextMessage.qml
  15. 62
      src/UserSettingsPage.cpp
  16. 13
      src/UserSettingsPage.h
  17. 13
      src/timeline/TimelineModel.cpp
  18. 2
      src/timeline/TimelineModel.h

@ -33,7 +33,7 @@ ScrollView {
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
// Mark timeline as read
@ -249,12 +249,12 @@ ScrollView {
id: sectionHeader
Column {
topPadding: 4
bottomPadding: 4
topPadding: userName_.visible? 4: 0
bottomPadding: Settings.bubbles? (isSender? 0 : 2) : 3
spacing: 8
visible: (previousMessageUserId !== userId || previousMessageDay !== day)
visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
width: parentWidth
height: ((previousMessageDay !== day) ? dateBubble.height + 8 + userName.height : userName.height) + 8
height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent? 0 : userName.height +8 )
Label {
id: dateBubble
@ -278,18 +278,19 @@ ScrollView {
Row {
height: userName_.height
spacing: 8
visible: !isStateEvent && (!isSender || !Settings.bubbles)
Avatar {
id: messageUserAvatar
width: Nheko.avatarSize
height: Nheko.avatarSize
width: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
height: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
displayName: userName
userid: userId
onClicked: room.openUserProfile(userId)
ToolTip.visible: avatarHover.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userid
HoverHandler {
@ -317,7 +318,7 @@ ScrollView {
color: TimelineManager.userColor(userId, Nheko.colors.base)
textFormat: Text.RichText
ToolTip.visible: displayNameHover.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId
TapHandler {
@ -379,6 +380,8 @@ ScrollView {
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property bool isStateEvent
required property bool previousMessageIsStateEvent
required property string replyTo
required property string userId
required property string roomTopic
@ -455,11 +458,14 @@ ScrollView {
property string previousMessageUserId: wrapper.previousMessageUserId
property string day: wrapper.day
property string previousMessageDay: wrapper.previousMessageDay
property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
property bool isStateEvent: wrapper.isStateEvent
property bool isSender: wrapper.isSender
property string userName: wrapper.userName
property date timestamp: wrapper.timestamp
z: 4
active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day
active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
//asynchronous: true
sourceComponent: sectionHeader
visible: status == Loader.Ready
@ -487,6 +493,7 @@ ScrollView {
isEncrypted: wrapper.isEncrypted
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
isStateEvent: wrapper.isStateEvent
replyTo: wrapper.replyTo
userId: wrapper.userId
userName: wrapper.userName

@ -47,6 +47,7 @@ Rectangle {
userId: modelData.userId ?? ""
userName: modelData.userName ?? ""
encryptionError: modelData.encryptionError ?? ""
width: parent.width
}
ImageButton {

@ -31,6 +31,7 @@ Item {
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property bool isStateEvent
required property string replyTo
required property string userId
required property string userName
@ -44,9 +45,8 @@ Item {
required property int status
required property int relatedEventCacheBuster
anchors.left: parent.left
anchors.right: parent.right
height: row.height
width: parent.width
height: childrenRect.height
Rectangle {
color: (Settings.messageHoverHighlight && hoverHandler.hovered) ? Nheko.colors.alternateBase : "transparent"
@ -71,27 +71,48 @@ Item {
gesturePolicy: TapHandler.ReleaseWithinBounds
}
RowLayout {
Control {
id: row
property bool bubbleOnRight : isSender && Settings.bubbles
property int bubblePadding: (parent.width-(Settings.smallAvatars? 0 : Nheko.avatarSize+8))/10
anchors.rightMargin: isSender || !Settings.bubbles? 0 : bubblePadding
anchors.leftMargin: (Settings.smallAvatars? 0 : Nheko.avatarSize+8) + (bubbleOnRight? bubblePadding : 0) // align bubble with section header
anchors.left: bubbleOnRight? undefined : parent.left
anchors.right: bubbleOnRight? parent.right : undefined
property int maxWidth: parent.width-anchors.leftMargin-anchors.rightMargin
width: Settings.bubbles? Math.min(maxWidth,implicitWidth+4) : maxWidth
leftPadding: 4
rightPadding: (Settings.bubbles && !isStateEvent)? 4: 2
topPadding: (Settings.bubbles && !isStateEvent)? 4: 2
bottomPadding: topPadding
background: Rectangle {
property color userColor: TimelineManager.userColor(userId, Nheko.colors.base)
property color bgColor: Nheko.colors.base
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2))
radius: 4
visible: Settings.bubbles && !isStateEvent
}
anchors.rightMargin: 1
anchors.leftMargin: Nheko.avatarSize + 16
anchors.left: parent.left
anchors.right: parent.right
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 4
Layout.topMargin: 1
Layout.bottomMargin: 1
contentItem: GridLayout {
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.bottomMargin: visible? 2 : 0
Layout.preferredHeight: height
Layout.maximumWidth: implicitWidth
id: reply
function fromModel(role) {
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
}
visible: replyTo
userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, Nheko.colors.base)
blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
@ -106,6 +127,7 @@ Item {
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
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
@ -118,9 +140,13 @@ Item {
// actual message content
MessageDelegate {
Layout.row: 1
Layout.column: 0
Layout.fillWidth: true
Layout.preferredHeight: height
Layout.maximumWidth: implicitWidth
id: contentItem
width: parent.width
blurhash: r.blurhash
body: r.body
formattedBody: r.formattedBody
@ -134,6 +160,7 @@ Item {
thumbnailUrl: r.thumbnailUrl
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
isStateEvent: r.isStateEvent
userId: r.userId
userName: r.userName
roomTopic: r.roomTopic
@ -144,67 +171,82 @@ Item {
isReply: false
}
Reactions {
id: reactionRow
RowLayout {
id: metadata
Layout.column: Settings.bubbles? 0 : 1
Layout.row: Settings.bubbles? 2 : 0
Layout.rowSpan: Settings.bubbles? 1 : 2
Layout.bottomMargin: -2
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.preferredWidth: implicitWidth
visible: !isStateEvent
property double scaling: Settings.bubbles? 0.75 : 1
StatusIndicator {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16*parent.scaling
Layout.preferredWidth: 16*parent.scaling
status: r.status
eventId: r.eventId
}
reactions: r.reactions
eventId: r.eventId
}
Image {
visible: isEdited || eventId == chat.model.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16*parent.scaling
Layout.preferredWidth: 16*parent.scaling
sourceSize.width: 16 * Screen.devicePixelRatio*parent.scaling
sourceSize.height: 16 * Screen.devicePixelRatio*parent.scaling
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == chat.model.edit) ? Nheko.colors.highlight : Nheko.colors.buttonText)
ToolTip.visible: editHovered.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edited")
HoverHandler {
id: editHovered
}
}
}
StatusIndicator {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
status: r.status
eventId: r.eventId
}
EncryptionIndicator {
visible: room.isEncrypted
encrypted: isEncrypted
trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16*parent.scaling
Layout.preferredWidth: 16*parent.scaling
sourceSize.width: 16 * Screen.devicePixelRatio*parent.scaling
sourceSize.height: 16 * Screen.devicePixelRatio*parent.scaling
}
Image {
visible: isEdited || eventId == chat.model.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
Layout.preferredWidth: 16
height: 16
width: 16
sourceSize.width: 16 * Screen.devicePixelRatio
sourceSize.height: 16 * Screen.devicePixelRatio
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == chat.model.edit) ? Nheko.colors.highlight : Nheko.colors.buttonText)
ToolTip.visible: editHovered.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edited")
HoverHandler {
id: editHovered
}
Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredWidth: implicitWidth
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
color: Nheko.inactiveColors.text
ToolTip.visible: ma.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
font.pointSize: 10*parent.scaling
HoverHandler {
id: ma
}
}
}
}
EncryptionIndicator {
visible: room.isEncrypted
encrypted: isEncrypted
trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
Layout.preferredWidth: 16
}
Reactions {
anchors {
top: row.bottom
topMargin: -2
left: row.left
}
Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: Nheko.inactiveColors.text
ToolTip.visible: ma.hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
HoverHandler {
id: ma
}
}
id: reactionRow
reactions: r.reactions
eventId: r.eventId
}
}

@ -16,7 +16,8 @@ Rectangle {
required property string eventId
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
width: parent.width
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: Nheko.colors.alternateBase
@ -39,6 +40,7 @@ Rectangle {
Layout.fillWidth: true
MatrixText {
id: encryptedText
text: {
switch (encryptionError) {
case Olm.MissingSession:

@ -14,6 +14,7 @@ Item {
height: row.height + 24
width: parent.width
implicitWidth: row.implicitWidth
RowLayout {
id: row
@ -86,8 +87,7 @@ Item {
color: Nheko.colors.alternateBase
z: -1
radius: 10
height: row.height + 24
width: 44 + 24 + 24 + Math.max(Math.min(filesize_.width, filesize_.implicitWidth), Math.min(filename_.width, filename_.implicitWidth))
anchors.fill: parent
}
}

@ -17,13 +17,11 @@ Item {
required property string filename
required property bool isReply
required property string eventId
property double tempWidth: Math.min(parent.width, originalWidth < 1 ? 200 : originalWidth)
property double tempHeight: tempWidth * proportionalHeight
property double divisor: isReply ? 5 : 3
property bool tooHigh: tempHeight > timelineView.height / divisor
height: Math.round(tooHigh ? timelineView.height / divisor : tempHeight)
width: Math.round(tooHigh ? (timelineView.height / divisor) / proportionalHeight : tempWidth)
implicitWidth: Math.round(originalWidth*Math.min((timelineView.height/divisor)/(originalWidth*proportionalHeight), 1))
width: parent.width
height: width*proportionalHeight
Image {
id: blurhash_

@ -13,7 +13,7 @@ Item {
required property bool isReply
property alias child: chooser.child
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : 0
required property double proportionalHeight
required property int type
required property string typeString
@ -27,6 +27,7 @@ Item {
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
@ -42,7 +43,9 @@ Item {
//role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: type
anchors.fill: parent
//anchors.fill: parent
width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null"
DelegateChoice {
roleValue: MtxEvent.UnknownMessage
@ -74,6 +77,7 @@ Item {
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
isStateEvent: d.isStateEvent
}
}
@ -87,6 +91,7 @@ Item {
body: d.body
isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply
isStateEvent: d.isStateEvent
}
}
@ -172,7 +177,7 @@ Item {
roleValue: MtxEvent.Redacted
Redacted {
delegateWidth: d.width
//delegateWidth: d.width
}
}
@ -180,7 +185,8 @@ Item {
roleValue: MtxEvent.Redaction
Pill {
text: qsTr("removed")
text: qsTr("%1 removed a message").arg(d.userName)
isStateEvent: d.isStateEvent
}
}
@ -189,7 +195,8 @@ Item {
roleValue: MtxEvent.Encryption
Pill {
text: qsTr("Encryption enabled")
text: qsTr("%1 enabled encryption").arg(d.userName)
isStateEvent: d.isStateEvent
}
}
@ -211,7 +218,8 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: d.roomName ? qsTr("room name changed to: %1").arg(d.roomName) : qsTr("removed room name")
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)
}
}
@ -223,7 +231,8 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: d.roomTopic ? qsTr("topic changed to: %1").arg(d.roomTopic) : qsTr("removed topic")
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)
}
}
@ -235,6 +244,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the room avatar").arg(d.userName)
}
@ -247,6 +257,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the pinned messages.").arg(d.userName)
}
@ -259,6 +270,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the stickers and emotes in this room.").arg(d.userName)
}
@ -271,6 +283,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName)
}
@ -283,6 +296,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the parent spaces for this room.").arg(d.userName)
}
@ -295,6 +309,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId)
}
@ -307,6 +322,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: {
switch (d.callType) {
case "voice":
@ -328,6 +344,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 answered the call.").arg(d.userName)
}
@ -340,6 +357,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: qsTr("%1 ended the call.").arg(d.userName)
}
@ -352,7 +370,8 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
formatted: qsTr("Negotiating call...")
isStateEvent: d.isStateEvent
formatted: qsTr("%1 is negotiating the call...").arg(d.userName)
}
}
@ -365,6 +384,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId)
}
@ -377,6 +397,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId)
}
@ -389,6 +410,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId)
}
@ -401,6 +423,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId)
}
@ -416,6 +439,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
Layout.fillWidth: true
formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId)
}
@ -438,6 +462,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationRequest"
}
@ -450,6 +475,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationStart"
}
@ -462,6 +488,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationReady"
}
@ -474,6 +501,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationCancel"
}
@ -486,6 +514,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationKey"
}
@ -498,6 +527,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationMac"
}
@ -510,6 +540,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationDone"
}
@ -522,6 +553,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationDone"
}
@ -534,6 +566,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: "KeyVerificationAccept"
}

@ -5,7 +5,10 @@
import im.nheko 1.0
TextMessage {
property bool isStateEvent
font.italic: true
color: Nheko.colors.buttonText
font.pointSize: isStateEvent? 0.75*fontMetrics.font.pointSize : 1*fontMetrics.font.pointSize
}

@ -8,10 +8,12 @@ import QtQuick.Controls 2.1
import im.nheko 1.0
Label {
property bool isStateEvent
color: Nheko.colors.text
horizontalAlignment: Text.AlignHCenter
height: contentHeight * 1.2
width: contentWidth * 1.2
//height: contentHeight * 1.2
//width: contentWidth * 1.2
font.pointSize: isStateEvent? 0.75*fontMetrics.font.pointSize : 1*fontMetrics.font.pointSize
background: Rectangle {
radius: parent.height / 2

@ -10,6 +10,6 @@ MatrixText {
required property string typeString
text: qsTr("unimplemented event: ") + typeString
width: parent.width
// width: parent.width
color: Nheko.inactiveColors.text
}

@ -22,13 +22,12 @@ Item {
required property string url
required property string body
required property string filesize
property double tempWidth: Math.min(parent.width, originalWidth < 1 ? 400 : originalWidth)
property double tempHeight: tempWidth * proportionalHeight
property double divisor: isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineRoot.height / divisor
height: (type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80) + fileInfoLabel.height
width: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
property int tempWidth: originalWidth < 1? 400: originalWidth
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500
width: parent.width
height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
implicitHeight: height
MxcMedia {
id: mxcmedia

@ -10,15 +10,16 @@ import im.nheko 1.0
Rectangle{
required property real delegateWidth
height: redactedLayout.implicitHeight + Nheko.paddingSmall
width: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
width: parent.width
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
color: Nheko.colors.alternateBase
RowLayout {
id: redactedLayout
anchors.centerIn: parent
width: parent.width
spacing: Nheko.paddingSmall
Image {
@ -32,8 +33,8 @@ Rectangle{
id: redactedLabel
Layout.margins: 0
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: implicitWidth
Layout.fillWidth: true
Layout.maximumWidth: delegateWidth - 4 * Nheko.paddingSmall - trashImg.width - 2 * Nheko.paddingMedium
property var redactedPair: room.formatRedactedEvent(eventId)
text: redactedPair["first"]
wrapMode: Label.WordWrap

@ -26,6 +26,7 @@ Item {
property string filesize
property string url
property bool isOnlyEmoji
property bool isStateEvent
property string userId
property string userName
property string thumbnailUrl
@ -34,9 +35,11 @@ Item {
property string callType
property int encryptionError
property int relatedEventCacheBuster
property int maxWidth
width: parent.width
height: replyContainer.height
implicitHeight: replyContainer.height
implicitWidth: visible? colorLine.width+replyContainer.implicitWidth : 0
CursorShape {
anchors.fill: parent
@ -52,12 +55,12 @@ Item {
color: TimelineManager.userColor(userId, Nheko.colors.base)
}
Column {
ColumnLayout {
id: replyContainer
anchors.left: colorLine.right
anchors.leftMargin: 4
width: parent.width - 8
width: parent.width - 4
spacing: 0
TapHandler {
acceptedButtons: Qt.LeftButton
@ -80,6 +83,7 @@ Item {
}
Text {
Layout.leftMargin: 4
id: userName_
text: TimelineManager.escapeEmoji(userName)
@ -94,8 +98,9 @@ Item {
}
MessageDelegate {
Layout.leftMargin: 4
Layout.preferredHeight: height
id: reply
blurhash: r.blurhash
body: r.body
formattedBody: r.formattedBody
@ -109,6 +114,7 @@ Item {
thumbnailUrl: r.thumbnailUrl
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
isStateEvent: r.isStateEvent
userId: r.userId
userName: r.userName
roomTopic: r.roomTopic
@ -118,7 +124,7 @@ Item {
encryptionError: r.encryptionError
// This is disabled so that left clicking the reply goes to its location
enabled: false
width: parent.width
Layout.fillWidth: true
isReply: true
}
@ -128,9 +134,10 @@ Item {
id: backgroundItem
z: -1
height: replyContainer.height
width: Math.min(Math.max(reply.implicitWidth, userName_.implicitWidth) + 8 + 4, parent.width)
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.1)
anchors.fill: replyContainer
property color userColor: TimelineManager.userColor(userId, Nheko.colors.base)
property color bgColor: Nheko.colors.base
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
}
}

@ -34,7 +34,7 @@ MatrixText {
</style>
" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap; background-color: " + Nheko.colors.alternateBase + "'>").replace("<del>", "<s>").replace("</del>", "</s>").replace("<strike>", "<s>").replace("</strike>", "</s>")
width: parent.width
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : undefined
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
clip: isReply
selectByMouse: !Settings.mobileMode && !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize

@ -69,7 +69,9 @@ UserSettings::load(std::optional<QString> profile)
settings.value(QStringLiteral("user/timeline/message_hover_highlight"), false).toBool();
enlargeEmojiOnlyMessages_ =
settings.value(QStringLiteral("user/timeline/enlarge_emoji_only_msg"), false).toBool();
markdown_ = settings.value(QStringLiteral("user/markdown_enabled"), true).toBool();
markdown_ = settings.value(QStringLiteral("user/markdown_enabled"), true).toBool();
bubbles_ = settings.value(QStringLiteral("user/bubbles_enabled"), false).toBool();
smallAvatars_ = settings.value(QStringLiteral("user/small_avatars_enabled"), false).toBool();
animateImagesOnHover_ =
settings.value(QStringLiteral("user/animate_images_on_hover"), false).toBool();
typingNotifications_ =
@ -251,6 +253,26 @@ UserSettings::setMarkdown(bool state)
save();
}
void
UserSettings::setBubbles(bool state)
{
if (state == bubbles_)
return;
bubbles_ = state;
emit bubblesChanged(state);
save();
}
void
UserSettings::setSmallAvatars(bool state)
{
if (state == smallAvatars_)
return;
smallAvatars_ = state;
emit smallAvatarsChanged(state);
save();
}
void
UserSettings::setAnimateImagesOnHover(bool state)
{
@ -705,6 +727,8 @@ UserSettings::save()
settings.setValue(QStringLiteral("read_receipts"), readReceipts_);
settings.setValue(QStringLiteral("group_view"), groupView_);
settings.setValue(QStringLiteral("markdown_enabled"), markdown_);
settings.setValue(QStringLiteral("bubbles_enabled"), bubbles_);
settings.setValue(QStringLiteral("small_avatars_enabled"), smallAvatars_);
settings.setValue(QStringLiteral("animate_images_on_hover"), animateImagesOnHover_);
settings.setValue(QStringLiteral("desktop_notifications"), hasDesktopNotifications_);
settings.setValue(QStringLiteral("alert_on_notification"), hasAlertOnNotification_);
@ -806,6 +830,10 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Group's sidebar");
case Markdown:
return tr("Send messages as Markdown");
case Bubbles:
return tr("Enable message bubbles");
case SmallAvatars:
return tr("Enable small Avatars");
case AnimateImagesOnHover:
return tr("Play animated images only on hover");
case TypingNotifications:
@ -926,6 +954,10 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return i->groupView();
case Markdown:
return i->markdown();
case Bubbles:
return i->bubbles();
case SmallAvatars:
return i->smallAvatars();
case AnimateImagesOnHover:
return i->animateImagesOnHover();
case TypingNotifications:
@ -1052,6 +1084,11 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr(
"Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain "
"text.");
case Bubbles:
return tr(
"Messages get a bubble background. This also triggers some layout changes (WIP).");
case SmallAvatars:
return tr("Avatars are resized to fit above the message.");
case AnimateImagesOnHover:
return tr("Plays media like GIFs or WEBPs only when explicitly hovering over them.");
case TypingNotifications:
@ -1168,6 +1205,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case StartInTray:
case GroupView:
case Markdown:
case Bubbles:
case SmallAvatars:
case AnimateImagesOnHover:
case TypingNotifications:
case SortByImportance:
@ -1385,6 +1424,20 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
} else
return false;
}
case Bubbles: {
if (value.userType() == QMetaType::Bool) {
i->setBubbles(value.toBool());
return true;
} else
return false;
}
case SmallAvatars: {
if (value.userType() == QMetaType::Bool) {
i->setSmallAvatars(value.toBool());
return true;
} else
return false;
}
case AnimateImagesOnHover: {
if (value.userType() == QMetaType::Bool) {
i->setAnimateImagesOnHover(value.toBool());
@ -1747,7 +1800,12 @@ UserSettingsModel::UserSettingsModel(QObject *p)
connect(s.get(), &UserSettings::markdownChanged, this, [this]() {
emit dataChanged(index(Markdown), index(Markdown), {Value});
});
connect(s.get(), &UserSettings::bubblesChanged, this, [this]() {
emit dataChanged(index(Bubbles), index(Bubbles), {Value});
});
connect(s.get(), &UserSettings::smallAvatarsChanged, this, [this]() {
emit dataChanged(index(SmallAvatars), index(SmallAvatars), {Value});
});
connect(s.get(), &UserSettings::groupViewStateChanged, this, [this]() {
emit dataChanged(index(GroupView), index(GroupView), {Value});
});

@ -40,6 +40,8 @@ class UserSettings : public QObject
Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged)
Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged)
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
Q_PROPERTY(bool bubbles READ bubbles WRITE setBubbles NOTIFY bubblesChanged)
Q_PROPERTY(bool smallAvatars READ smallAvatars WRITE setSmallAvatars NOTIFY smallAvatarsChanged)
Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover
NOTIFY animateImagesOnHoverChanged)
Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications NOTIFY
@ -141,6 +143,8 @@ public:
void setEmojiFontFamily(QString family);
void setGroupView(bool state);
void setMarkdown(bool state);
void setBubbles(bool state);
void setSmallAvatars(bool state);
void setAnimateImagesOnHover(bool state);
void setReadReceipts(bool state);
void setTypingNotifications(bool state);
@ -193,6 +197,8 @@ public:
bool privacyScreen() const { return privacyScreen_; }
int privacyScreenTimeout() const { return privacyScreenTimeout_; }
bool markdown() const { return markdown_; }
bool bubbles() const { return bubbles_; }
bool smallAvatars() const { return smallAvatars_; }
bool animateImagesOnHover() const { return animateImagesOnHover_; }
bool typingNotifications() const { return typingNotifications_; }
bool sortByImportance() const { return sortByImportance_; }
@ -251,6 +257,8 @@ signals:
void trayChanged(bool state);
void startInTrayChanged(bool state);
void markdownChanged(bool state);
void bubblesChanged(bool state);
void smallAvatarsChanged(bool state);
void animateImagesOnHoverChanged(bool state);
void typingNotificationsChanged(bool state);
void buttonInTimelineChanged(bool state);
@ -307,6 +315,8 @@ private:
bool startInTray_;
bool groupView_;
bool markdown_;
bool bubbles_;
bool smallAvatars_;
bool animateImagesOnHover_;
bool typingNotifications_;
bool sortByImportance_;
@ -386,7 +396,8 @@ class UserSettingsModel : public QAbstractListModel
ReadReceipts,
ButtonsInTimeline,
Markdown,
Bubbles,
SmallAvatars,
SidebarSection,
GroupView,
SortByImportance,

@ -467,6 +467,7 @@ TimelineModel::roleNames() const
{UserId, "userId"},
{UserName, "userName"},
{PreviousMessageDay, "previousMessageDay"},
{PreviousMessageIsStateEvent, "previousMessageIsStateEvent"},
{Day, "day"},
{Timestamp, "timestamp"},
{Url, "url"},
@ -483,6 +484,7 @@ TimelineModel::roleNames() const
{IsEdited, "isEdited"},
{IsEditable, "isEditable"},
{IsEncrypted, "isEncrypted"},
{IsStateEvent, "isStateEvent"},
{Trustlevel, "trustlevel"},
{EncryptionError, "encryptionError"},
{ReplyTo, "replyTo"},
@ -680,6 +682,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
*encrypted_event);
}
case IsStateEvent: {
return is_state_event(event);
}
case Trustlevel: {
auto encrypted_event = events.get(event_id(event), "", false);
@ -744,6 +749,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
m.insert(names[IsEdited], data(event, static_cast<int>(IsEdited)));
m.insert(names[IsEditable], data(event, static_cast<int>(IsEditable)));
m.insert(names[IsEncrypted], data(event, static_cast<int>(IsEncrypted)));
m.insert(names[IsStateEvent], data(event, static_cast<int>(IsStateEvent)));
m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo)));
m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
@ -776,7 +782,8 @@ TimelineModel::data(const QModelIndex &index, int role) const
if (!event)
return "";
if (role == PreviousMessageDay || role == PreviousMessageUserId) {
if (role == PreviousMessageDay || role == PreviousMessageUserId ||
role == PreviousMessageIsStateEvent) {
int prevIdx = rowCount() - index.row() - 2;
if (prevIdx < 0)
return {};
@ -785,8 +792,10 @@ TimelineModel::data(const QModelIndex &index, int role) const
return {};
if (role == PreviousMessageUserId)
return data(*tempEv, UserId);
else
else if (role == PreviousMessageDay)
return data(*tempEv, Day);
else
return data(*tempEv, IsStateEvent);
}
return data(*event, role);

@ -210,6 +210,7 @@ public:
UserId,
UserName,
PreviousMessageDay,
PreviousMessageIsStateEvent,
Day,
Timestamp,
Url,
@ -226,6 +227,7 @@ public:
IsEdited,
IsEditable,
IsEncrypted,
IsStateEvent,
Trustlevel,
EncryptionError,
ReplyTo,

Loading…
Cancel
Save