mirror of https://github.com/Nheko-Reborn/nheko
delegate-rework
parent
43c8e64ed3
commit
184806bf71
@ -0,0 +1,320 @@ |
||||
// 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 type |
||||
required property bool isEditable |
||||
|
||||
required property QtObject messageContextMenu |
||||
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 |
||||
|
||||
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) |
||||
} |
||||
}, |
||||
RowLayout { |
||||
id: gridContainer |
||||
|
||||
width: wrapper.width |
||||
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 |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
Item { |
||||
Layout.preferredWidth: wrapper.avatarMargin |
||||
} |
||||
|
||||
AbstractButton { |
||||
ToolTip.delay: Nheko.tooltipDelay |
||||
ToolTip.text: qsTr("Part of a thread") |
||||
ToolTip.visible: hovered |
||||
Layout.fillHeight: true |
||||
visible: wrapper.threadId |
||||
Layout.preferredWidth: 4 |
||||
|
||||
onClicked: wrapper.room.thread = wrapper.threadId |
||||
|
||||
Rectangle { |
||||
id: threadLine |
||||
|
||||
anchors.fill: parent |
||||
color: TimelineManager.userColor(wrapper.threadId, palette.base) |
||||
} |
||||
} |
||||
ColumnLayout { |
||||
id: contentColumn |
||||
Layout.fillWidth: true |
||||
|
||||
AbstractButton { |
||||
id: replyRow |
||||
visible: wrapper.reply |
||||
Layout.fillWidth: true |
||||
Layout.maximumHeight: timelineView.height / 8 |
||||
Layout.preferredWidth: replyRowLay.implicitWidth |
||||
Layout.preferredHeight: replyRowLay.implicitHeight |
||||
|
||||
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) |
||||
|
||||
clip: true |
||||
|
||||
NhekoCursorShape { |
||||
anchors.fill: parent |
||||
cursorShape: Qt.PointingHandCursor |
||||
} |
||||
|
||||
contentItem: RowLayout { |
||||
id: replyRowLay |
||||
|
||||
anchors.fill: parent |
||||
|
||||
|
||||
Rectangle { |
||||
id: replyLine |
||||
Layout.fillHeight: true |
||||
color: replyRow.userColor |
||||
Layout.preferredWidth: 4 |
||||
} |
||||
|
||||
ColumnLayout { |
||||
spacing: 0 |
||||
|
||||
AbstractButton { |
||||
id: replyUserButton |
||||
Layout.fillWidth: true |
||||
contentItem: ElidedLabel { |
||||
id: userName_ |
||||
fullText: wrapper.reply?.userName ?? '' |
||||
color: replyRow.userColor |
||||
textFormat: Text.RichText |
||||
width: parent.width |
||||
elideWidth: width |
||||
} |
||||
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) |
||||
} |
||||
} |
||||
} |
||||
|
||||
data: [ |
||||
replyRow, wrapper.main, |
||||
] |
||||
} |
||||
|
||||
Item { |
||||
// spacer to fill width if needed |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
RowLayout { |
||||
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.preferredWidth: implicitWidth |
||||
spacing: 2 |
||||
visible: !isStateEvent |
||||
|
||||
StatusIndicator { |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter |
||||
eventId: wrapper.eventId |
||||
height: parent.iconSize |
||||
status: wrapper.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?" + ((wrapper.eventId == wrapper.room.edit) ? palette.highlight : palette.buttonText) |
||||
sourceSize.height: parent.iconSize * Screen.devicePixelRatio |
||||
sourceSize.width: parent.iconSize * Screen.devicePixelRatio |
||||
visible: wrapper.isEdited || wrapper.eventId == wrapper.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(wrapper.threadId, palette.base) |
||||
height: parent.iconSize |
||||
image: ":/icons/icons/ui/thread.svg" |
||||
visible: wrapper.threadId |
||||
width: parent.iconSize |
||||
|
||||
onClicked: wrapper.room.thread = threadId |
||||
} |
||||
EncryptionIndicator { |
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter |
||||
encrypted: wrapper.isEncrypted |
||||
height: parent.iconSize |
||||
sourceSize.height: parent.iconSize * Screen.devicePixelRatio |
||||
sourceSize.width: parent.iconSize * Screen.devicePixelRatio |
||||
trust: wrapper.trustlevel |
||||
visible: wrapper.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(wrapper.timestamp, Qt.DefaultLocaleLongDate) |
||||
ToolTip.visible: ma.hovered |
||||
color: palette.inactive.text |
||||
font.pointSize: fontMetrics.font.pointSize * parent.scaling |
||||
text: wrapper.timestamp.toLocaleTimeString(Locale.ShortFormat) |
||||
|
||||
HoverHandler { |
||||
id: ma |
||||
|
||||
} |
||||
} |
||||
} |
||||
}, |
||||
Reactions { |
||||
id: reactionRow |
||||
|
||||
eventId: wrapper.eventId |
||||
layoutDirection: row.bubbleOnRight ? 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 |
||||
} |
||||
} |
||||
] |
||||
} |
@ -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.horizontalCenter: parent.horizontalCenter |
||||
|
||||
powerlevel: userPowerlevel |
||||
height: fontMetrics.lineSpacing |
||||
width: fontMetrics.lineSpacing |
||||
|
||||
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 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue