diff --git a/src/qml/Component/Timeline/SectionDelegate.qml b/src/qml/Component/Timeline/SectionDelegate.qml
index e94fba922..a8ecc047e 100644
--- a/src/qml/Component/Timeline/SectionDelegate.qml
+++ b/src/qml/Component/Timeline/SectionDelegate.qml
@@ -3,15 +3,45 @@
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
+import QtQuick.Controls 2.15 as QQC2
+import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
-Kirigami.Heading {
- level: 4
- text: model.showSection ? section : ""
- color: Kirigami.Theme.disabledTextColor
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- topPadding: Kirigami.Units.largeSpacing * 2
- bottomPadding: Kirigami.Units.smallSpacing
+import org.kde.neochat 1.0
+
+QQC2.ItemDelegate {
+ id: sectionDelegate
+
+ property alias labelText: sectionLabel.text
+ property var maxWidth: Number.POSITIVE_INFINITY
+
+ topPadding: Kirigami.Units.largeSpacing
+ bottomPadding: 0 // Note not 0 by default
+
+ contentItem: ColumnLayout {
+ spacing: Kirigami.Units.smallSpacing
+ Layout.fillWidth: true
+
+ Kirigami.Heading {
+ id: sectionLabel
+ level: 4
+ color: Kirigami.Theme.disabledTextColor
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ Layout.fillWidth: true
+ Layout.maximumWidth: maxWidth
+ }
+ Kirigami.Separator {
+ Layout.minimumHeight: 2
+ Layout.fillWidth: true
+ Layout.maximumWidth: maxWidth
+ }
+ }
+
+ background: Rectangle {
+ color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
+ Kirigami.Theme.inherit: false
+ Kirigami.Theme.colorSet: Kirigami.Theme.Window
+ }
}
diff --git a/src/qml/Component/Timeline/StateDelegate.qml b/src/qml/Component/Timeline/StateDelegate.qml
index 2e9a035b4..3309b96d9 100644
--- a/src/qml/Component/Timeline/StateDelegate.qml
+++ b/src/qml/Component/Timeline/StateDelegate.qml
@@ -11,13 +11,14 @@ import org.kde.neochat 1.0
Control {
id: stateDelegate
+
+ readonly property bool sectionVisible: model.showSection
+
// extraWidth defines how the delegate can grow after the listView gets very wide
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width: Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
width: delegateMaxWidth
-// anchors.leftMargin: Kirigami.Units.largeSpacing
-// anchors.rightMargin: Kirigami.Units.largeSpacing
state: Config.compactLayout ? "alignLeft" : "alignCenter"
// Align left when in compact mode and center when using bubbles
@@ -46,56 +47,60 @@ Control {
}
]
- height: sectionDelegate.height + rowLayout.height
- SectionDelegate {
- id: sectionDelegate
+ height: columnLayout.implicitHeight + columnLayout.anchors.topMargin
+
+ ColumnLayout {
+ id: columnLayout
+ spacing: sectionVisible ? Kirigami.Units.largeSpacing : 0
anchors.top: parent.top
+ anchors.topMargin: sectionVisible ? 0 : Kirigami.Units.largeSpacing
anchors.left: parent.left
anchors.right: parent.right
- visible: model.showSection
- height: visible ? implicitHeight : 0
- }
- RowLayout {
- id: rowLayout
- height: label.contentHeight
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing + (Config.compactLayout ? Kirigami.Units.largeSpacing * 1.25 : 0)
- anchors.rightMargin: Kirigami.Units.largeSpacing
-
- Kirigami.Avatar {
- id: icon
- Layout.preferredWidth: Kirigami.Units.iconSizes.small
- Layout.preferredHeight: Kirigami.Units.iconSizes.small
- Layout.alignment: Qt.AlignTop
-
- name: model.displayNameForInitials
- source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
- color: author.color
-
- Component {
- id: userDetailDialog
-
- UserDetailDialog {}
- }
-
- MouseArea {
- anchors.fill: parent
- onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
- }
+ SectionDelegate {
+ id: sectionDelegate
+ Layout.fillWidth: true
+ visible: sectionVisible
+ labelText: sectionVisible ? section : ""
}
- Label {
- id: label
- Layout.alignment: Qt.AlignVCenter
+ RowLayout {
+ id: rowLayout
+ implicitHeight: label.contentHeight
Layout.fillWidth: true
- Layout.preferredHeight: icon.height
- wrapMode: Text.WordWrap
- textFormat: Text.RichText
- text: `${model.authorDisplayName} ${aggregateDisplay}`
- onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
+ Layout.leftMargin: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing * 1.5 + (Config.compactLayout ? Kirigami.Units.largeSpacing * 1.25 : 0)
+ Layout.rightMargin: Kirigami.Units.largeSpacing
+
+ Kirigami.Avatar {
+ id: icon
+ Layout.preferredWidth: Kirigami.Units.iconSizes.small
+ Layout.preferredHeight: Kirigami.Units.iconSizes.small
+
+ name: author.displayName
+ source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
+ color: author.color
+
+ Component {
+ id: userDetailDialog
+
+ UserDetailDialog {}
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
+ }
+ }
+
+ Label {
+ id: label
+ Layout.alignment: Qt.AlignVCenter
+ Layout.fillWidth: true
+ wrapMode: Text.WordWrap
+ textFormat: Text.RichText
+ text: `${currentRoom.htmlSafeMemberName(author.id)} ${aggregateDisplay}`
+ onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
+ }
}
}
}
diff --git a/src/qml/Component/Timeline/TimelineContainer.qml b/src/qml/Component/Timeline/TimelineContainer.qml
index 6a59c8772..d1813e54e 100644
--- a/src/qml/Component/Timeline/TimelineContainer.qml
+++ b/src/qml/Component/Timeline/TimelineContainer.qml
@@ -14,6 +14,8 @@ QQC2.ItemDelegate {
default property alias innerObject : column.children
// readonly property bool failed: marks == EventStatus.SendingFailed
+ readonly property bool sectionVisible: model.showSection
+
property bool isEmote: false
property bool cardBackground: true
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
@@ -55,7 +57,7 @@ QQC2.ItemDelegate {
leftInset: Kirigami.Units.smallSpacing
rightInset: Kirigami.Units.smallSpacing
width: delegateMaxWidth
- height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing))
+ height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + loader.anchors.topMargin + avatar.anchors.topMargin
background: Rectangle {
visible: timelineContainer.hovered
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
@@ -110,11 +112,11 @@ QQC2.ItemDelegate {
SectionDelegate {
id: sectionDelegate
- width: parent.width
- anchors.left: avatar.left
- anchors.leftMargin: Kirigami.Units.smallSpacing
- visible: model.showSection
+ anchors.left: timelineContainer.left
+ anchors.right: timelineContainer.right
+ visible: sectionVisible
height: visible ? implicitHeight : 0
+ labelText: model.showSection ? section : ""
}
Kirigami.Avatar {
@@ -304,10 +306,9 @@ QQC2.ItemDelegate {
left: bubble.left
right: parent.right
top: bubble.bottom
- topMargin: active && Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
+ topMargin: active ? Kirigami.Units.smallSpacing : 0
}
height: active ? item.implicitHeight : 0
- //Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
active: eventType !== MessageEventModel.State && eventType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0
visible: active
sourceComponent: ReactionDelegate { }
diff --git a/src/qml/Page/RoomPage.qml b/src/qml/Page/RoomPage.qml
index c1cf68487..edaa83f13 100644
--- a/src/qml/Page/RoomPage.qml
+++ b/src/qml/Page/RoomPage.qml
@@ -187,8 +187,12 @@ Kirigami.ScrollablePage {
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
readonly property bool isLoaded: page.width * page.height > 10
+ // Spacing needs to be zero or the top sectionLabel overlay will be disrupted.
+ // This is because itemAt returns null in the spaces.
+ // All spacing should be handled by the delegates themselves
spacing: 0
-
+ // Ensures that the top item is not covered by sectionBanner if the page is scrolled all the way up
+ // topMargin: sectionBanner.height
verticalLayoutDirection: ListView.BottomToTop
highlightMoveDuration: 500
@@ -226,6 +230,40 @@ Kirigami.ScrollablePage {
hasScrolledUpBefore = true;
}
+ // Not rendered because the sections are part of the TimelineContainer.qml, this is only so that items have the section property available for use by sectionBanner.
+ // This is due to the fact that the ListView verticalLayout is BottomToTop.
+ // This also flips the sections which would appear at the bottom but for a timeline they still need to be at the top (bottom from the qml perspective).
+ // There is currently no option to put section headings at the bottom in qml.
+ section.property: "section"
+
+ readonly property var sectionBannerItem: contentHeight >= height ? itemAtIndex(sectionBannerIndex()) : undefined
+
+ function sectionBannerIndex() {
+ let center = messageListView.x + messageListView.width / 2;
+ let yStart = messageListView.y + messageListView.contentY;
+ let index = -1
+ let i = 0
+ while (index === -1 && i < 100) {
+ index = messageListView.indexAt(center, yStart + i);
+ i++;
+ }
+ return index
+ }
+
+ footer: SectionDelegate {
+ id: sectionBanner
+
+ anchors.left: parent.left
+ anchors.leftMargin: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.x : 0
+ anchors.right: parent.right
+
+ maxWidth: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0
+ z: 3
+ visible: messageListView.sectionBannerItem && messageListView.sectionBannerItem.ListView.section != ""
+ labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
+ }
+ footerPositioning: ListView.OverlayHeader
+
QQC2.Popup {
anchors.centerIn: parent
@@ -589,7 +627,7 @@ Kirigami.ScrollablePage {
let center = messageListView.x + messageListView.width / 2;
let index = -1
let i = 0
- while(index === -1 && i < 100) {
+ while (index === -1 && i < 100) {
index = messageListView.indexAt(center, messageListView.y + messageListView.contentY + i);
i++;
}
@@ -600,7 +638,7 @@ Kirigami.ScrollablePage {
let center = messageListView.x + messageListView.width / 2;
let index = -1
let i = 0
- while(index === -1 && i < 100) {
+ while (index === -1 && i < 100) {
index = messageListView.indexAt(center, messageListView.y + messageListView.contentY + messageListView.height - i);
i++
}