Reimplement reply delegate by moving out the timeline event without layout

This commit is contained in:
Nicolas Werner 2023-08-25 20:43:04 +02:00
parent aef0cb9884
commit b187440e68
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
13 changed files with 352 additions and 460 deletions

View file

@ -713,6 +713,7 @@ set(QML_SOURCES
resources/qml/UploadBox.qml
resources/qml/MessageInput.qml
resources/qml/MessageView.qml
resources/qml/TimelineEvent.qml
resources/qml/PrivacyScreen.qml
resources/qml/Reactions.qml
resources/qml/ReplyPopup.qml

View file

@ -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

View file

@ -54,24 +54,8 @@ 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
}
MatrixTextField {

View file

@ -11,25 +11,24 @@ TextArea {
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
selectByMouse: !Settings.mobileMode
textFormat: TextEdit.RichText
topInset: 0
topPadding: 0
wrapMode: Text.Wrap
// Setting a tooltip delay makes the hover text empty .-.
@ -40,8 +39,8 @@ TextArea {
onLinkActivated: Nheko.openLink(link)
// propagate events up
onPressAndHold: (event) => event.accepted = false
onPressed: (event) => event.accepted = (event.button == Qt.LeftButton)
onPressAndHold: event => event.accepted = false
onPressed: event => event.accepted = (event.button == Qt.LeftButton)
NhekoCursorShape {
id: cs

View file

@ -59,7 +59,7 @@ Item {
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
delegate: EventDelegateChooser {
delegate: TimelineEvent {
id: wrapper
ListView.delayRemove: true
width: chat.delegateMaxWidth
@ -69,7 +69,6 @@ Item {
required property var day
required property bool isSender
required property bool isStateEvent
//required property var previousMessageDay
//required property bool previousMessageIsStateEvent
//required property string previousMessageUserId
@ -145,6 +144,8 @@ Item {
}
ColumnLayout {
spacing: 0
AbstractButton {
id: replyUserButton
Layout.fillWidth: true
@ -222,314 +223,6 @@ Item {
opacity: 0.2
}
]
EventDelegateChoice {
roleValues: [
MtxEvent.TextMessage,
MtxEvent.NoticeMessage,
MtxEvent.ElementEffectMessage,
MtxEvent.UnknownMessage,
]
TextMessage {
keepFullText: true
required property string userId
required property string userName
required property string formattedBody
required property int type
color: type == MtxEvent.NoticeMessage ? palette.buttonText : palette.text
font.italic: type == MtxEvent.NoticeMessage
formatted: formattedBody
Layout.fillWidth: true
//Layout.maximumWidth: implicitWidth
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.EmoteMessage,
]
TextMessage {
keepFullText: true
required property string userId
required property string userName
required property string formattedBody
formatted: TimelineManager.escapeEmoji(userName) + " " + formattedBody
color: TimelineManager.userColor(userId, palette.base)
font.italic: true
Layout.fillWidth: true
//Layout.maximumWidth: implicitWidth
}
}
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 {
keepFullText: true
required property string userId
required property string userName
required property string formattedStateEvent
isOnlyEmoji: false
text: formattedStateEvent
formatted: ''
body: ''
horizontalAlignment: Text.AlignHCenter
color: palette.buttonText
font.italic: true
Layout.fillWidth: true
//Layout.maximumWidth: implicitWidth
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.CallInvite,
]
TextMessage {
keepFullText: true
required property string userId
required property string userName
required property string callType
isOnlyEmoji: false
body: formatted
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));
}
}
color: palette.buttonText
font.italic: true
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.CallAnswer,
MtxEvent.CallReject,
MtxEvent.CallSelectAnswer,
MtxEvent.CallHangUp,
MtxEvent.CallCandidates,
MtxEvent.CallNegotiate,
]
TextMessage {
keepFullText: true
required property string userId
required property string userName
required property int type
isOnlyEmoji: false
body: formatted
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));
}
}
color: palette.buttonText
font.italic: true
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.ImageMessage,
MtxEvent.Sticker,
]
ImageMessage {
Layout.fillWidth: true
containerHeight: timelineView.height
Layout.maximumWidth: tempWidth
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.FileMessage,
]
FileMessage {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.VideoMessage,
MtxEvent.AudioMessage,
]
PlayableMediaMessage {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.Encrypted,
]
Encrypted {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.Encryption,
]
EncryptionEnabled {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.Redacted
]
Redacted {
Layout.fillWidth: true
required property string userId
required property string userName
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.Member
]
ColumnLayout {
id: member
required property string userId
required property string userName
required property bool isReply
required property Room room
required property string formattedStateEvent
NoticeMessage {
body: formatted
isOnlyEmoji: false
isReply: tombstone.isReply
keepFullText: true
isStateEvent: true
Layout.fillWidth: true
formatted: member.formattedStateEvent
}
Button {
visible: room.showAcceptKnockButton(eventId)
Layout.alignment: Qt.AlignHCenter
text: qsTr("Allow them in")
onClicked: room.acceptKnock(member.eventId)
}
}
}
EventDelegateChoice {
roleValues: [
MtxEvent.Tombstone
]
ColumnLayout {
id: tombstone
required property string userId
required property string userName
required property string body
required property bool isReply
required property Room room
required property string eventId
NoticeMessage {
body: formatted
isOnlyEmoji: false
isReply: tombstone.isReply
keepFullText: true
isStateEvent: true
Layout.fillWidth: true
formatted: qsTr("This room was replaced for the following reason: %1").arg(tombstone.body)
}
Button {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Go to replacement room")
onClicked: tombstone.room.joinReplacementRoom(tombstone.eventId)
}
}
}
EventDelegateChoice {
roleValues: [
]
MatrixText {
Layout.fillWidth: true
required property string typeString
text: "Unsupported: " + typeString
required property string userId
required property string userName
}
}
}
footer: Item {
anchors.horizontalCenter: parent.horizontalCenter

View file

@ -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

View file

@ -29,22 +29,8 @@ 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
}

View file

@ -728,9 +728,9 @@ Page {
}
Platform.MenuItem {
text: qsTr("Mark as read")
onTriggered: Rooms.getRoomById(roomContextMenu.roomid).markRoomAsRead()
}
Platform.MenuItem {
text: qsTr("Room settings")

View file

@ -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
}

View file

@ -0,0 +1,255 @@
// 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
Layout.fillWidth: true
//Layout.maximumWidth: implicitWidth
body: ''
color: palette.buttonText
font.italic: true
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 {
Layout.fillWidth: true
Layout.maximumWidth: tempWidth
containerHeight: timelineView.height
}
}
EventDelegateChoice {
roleValues: [MtxEvent.FileMessage,]
FileMessage {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [MtxEvent.VideoMessage, MtxEvent.AudioMessage,]
PlayableMediaMessage {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [MtxEvent.Encrypted,]
Encrypted {
Layout.fillWidth: true
}
}
EventDelegateChoice {
roleValues: [MtxEvent.Encryption,]
EncryptionEnabled {
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 bool isReply
required property Room room
required property string userId
required property string userName
NoticeMessage {
Layout.fillWidth: true
body: formatted
formatted: member.formattedStateEvent
isOnlyEmoji: false
isReply: member.isReply
isStateEvent: true
keepFullText: true
}
Button {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Allow them in")
visible: room.showAcceptKnockButton(eventId)
onClicked: room.acceptKnock(member.eventId)
}
}
}
EventDelegateChoice {
roleValues: [MtxEvent.Tombstone]
ColumnLayout {
id: tombstone
required property string body
required property string eventId
required property bool isReply
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: tombstone.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
}
}
}

View file

@ -147,6 +147,7 @@ AbstractButton {
columns: Settings.bubbles ? 1 : 2
rowSpacing: 0
rows: Settings.bubbles ? 3 : 2
/*
anchors {
left: parent.left

View file

@ -286,24 +286,9 @@ 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
//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() {

View file

@ -14,102 +14,96 @@ 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
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 {
contentItem: TimelineEvent {
id: timelineEvent
isStateEvent: false
room: room_
eventId: r.eventId
replyTo: ""
width: parent.width
height: replyContainer.implicitHeight
//height: replyContainer.implicitHeight
data: GridLayout {
id: replyContainer
anchors.left: colorLine.right
width: parent.width - 4
spacing: 0
width: parent.width
columns: 2
rows: 2
columnSpacing: Nheko.paddingMedium
rowSpacing: Nheko.paddingSmall
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
Rectangle {
id: colorline
Layout.preferredWidth: 4
Layout.rowSpan: 2
Layout.fillHeight: true
Layout.row: 0
Layout.column: 0
color: TimelineManager.userColor(r.userId, palette.base)
}
AbstractButton {
Layout.leftMargin: 4
id: usernameBtn
Layout.fillWidth: true
Layout.row: 0
Layout.column: 1
contentItem: ElidedLabel {
id: userName_
fullText: userName
fullText: r.userName
color: r.userColor
textFormat: Text.RichText
width: parent.width
elideWidth: width
}
onClicked: room.openUserProfile(userId)
onClicked: room.openUserProfile(r.userId)
}
Rectangle {
Layout.leftMargin: 4
Layout.preferredHeight: 20
Layout.fillWidth: true
color: "green"
}
data: [
colorline, 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))
}