Split up TimelineView into separate components
1 files changed, 202 insertions, 0 deletions
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
new file mode 100644
index 00000000..09220a71
--- /dev/null
+++ b/resources/qml/MessageView.qml
@@ -0,0 +1,202 @@
+import "./delegates"
+import QtGraphicalEffects 1.0
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.2
+import im.nheko 1.0
+
+ListView {
+ id: chat
+
+ property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2)
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ cacheBuffer: 400
+ model: TimelineManager.timeline
+ boundsBehavior: Flickable.StopAtBounds
+ pixelAligned: true
+ spacing: 4
+ verticalLayoutDirection: ListView.BottomToTop
+ onCountChanged: {
+ if (atYEnd)
+ model.currentIndex = 0;
+
+ } // Mark last event as read, since we are at the bottom
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ }
+
+ Shortcut {
+ sequence: StandardKey.MoveToPreviousPage
+ onActivated: {
+ chat.contentY = chat.contentY - chat.height / 2;
+ chat.returnToBounds();
+ }
+ }
+
+ Shortcut {
+ sequence: StandardKey.MoveToNextPage
+ onActivated: {
+ chat.contentY = chat.contentY + chat.height / 2;
+ chat.returnToBounds();
+ }
+ }
+
+ Shortcut {
+ sequence: StandardKey.Cancel
+ onActivated: chat.model.reply = undefined
+ }
+
+ Shortcut {
+ sequence: "Alt+Up"
+ onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
+ }
+
+ Shortcut {
+ sequence: "Alt+Down"
+ onActivated: {
+ var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
+ chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
+ }
+ }
+
+ section {
+ property: "section"
+ }
+
+ Component {
+ id: sectionHeader
+
+ Column {
+ property var modelData
+ property string section
+ property string nextSection
+
+ topPadding: 4
+ bottomPadding: 4
+ spacing: 8
+ visible: !!modelData
+ width: parent.width
+ height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
+
+ Label {
+ id: dateBubble
+
+ anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+ visible: section.includes(" ")
+ text: chat.model.formatDateSeparator(modelData.timestamp)
+ color: colors.text
+ height: fontMetrics.height * 1.4
+ width: contentWidth * 1.2
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ background: Rectangle {
+ radius: parent.height / 2
+ color: colors.window
+ }
+
+ }
+
+ Row {
+ height: userName.height
+ spacing: 8
+
+ Avatar {
+ width: avatarSize
+ height: avatarSize
+ url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
+ displayName: modelData.userName
+ userid: modelData.userId
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: chat.model.openUserProfile(modelData.userId)
+ cursorShape: Qt.PointingHandCursor
+ propagateComposedEvents: true
+ }
+
+ }
+
+ Label {
+ id: userName
+
+ text: TimelineManager.escapeEmoji(modelData.userName)
+ color: TimelineManager.userColor(modelData.userId, colors.window)
+ textFormat: Text.RichText
+
+ MouseArea {
+ anchors.fill: parent
+ Layout.alignment: Qt.AlignHCenter
+ onClicked: chat.model.openUserProfile(modelData.userId)
+ cursorShape: Qt.PointingHandCursor
+ propagateComposedEvents: true
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ ScrollBar.vertical: ScrollBar {
+ id: scrollbar
+ }
+
+ delegate: Item {
+ id: wrapper
+
+ // This would normally be previousSection, but our model's order is inverted.
+ property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
+ property Item section
+
+ anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+ width: chat.delegateMaxWidth
+ height: section ? section.height + timelinerow.height : timelinerow.height
+ onSectionBoundaryChanged: {
+ if (sectionBoundary) {
+ var properties = {
+ "modelData": model.dump,
+ "section": ListView.section,
+ "nextSection": ListView.nextSection
+ };
+ section = sectionHeader.createObject(wrapper, properties);
+ } else {
+ section.destroy();
+ section = null;
+ }
+ }
+
+ TimelineRow {
+ id: timelinerow
+
+ y: section ? section.y + section.height : 0
+ }
+
+ Connections {
+ function onMovementEnded() {
+ if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
+ chat.model.currentIndex = index;
+
+ }
+
+ target: chat
+ }
+
+ }
+
+ footer: BusyIndicator {
+ anchors.horizontalCenter: parent.horizontalCenter
+ running: chat.model && chat.model.paginationInProgress
+ height: 50
+ width: 50
+ z: 3
+ }
+
+}
|