8c44c5e2d0
This requires Qt 5.9 (to calculate overshoot). The default scroll behaviour of list views has far too much inertia. This should make scrolling feel more like scrolling the other scroll areas of nheko.
190 lines
4.7 KiB
QML
190 lines
4.7 KiB
QML
import QtQuick 2.9
|
|
import QtQuick.Controls 2.1
|
|
import QtQuick.Layouts 1.2
|
|
import QtGraphicalEffects 1.0
|
|
import QtQuick.Window 2.2
|
|
|
|
import im.nheko 1.0
|
|
|
|
import "./delegates"
|
|
|
|
Item {
|
|
property var colors: currentActivePalette
|
|
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
|
|
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
|
|
property int avatarSize: 40
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: colors.window
|
|
|
|
Text {
|
|
visible: !timelineManager.timeline && !timelineManager.isInitialSync
|
|
anchors.centerIn: parent
|
|
text: qsTr("No room open")
|
|
font.pointSize: 24
|
|
color: colors.windowText
|
|
}
|
|
|
|
BusyIndicator {
|
|
anchors.centerIn: parent
|
|
running: timelineManager.isInitialSync
|
|
height: 200
|
|
width: 200
|
|
}
|
|
|
|
ListView {
|
|
id: chat
|
|
|
|
cacheBuffer: 2000
|
|
|
|
visible: timelineManager.timeline != null
|
|
anchors.fill: parent
|
|
|
|
anchors.leftMargin: 4
|
|
anchors.rightMargin: scrollbar.width
|
|
|
|
model: timelineManager.timeline
|
|
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
|
|
onVerticalOvershootChanged: contentY = contentY - verticalOvershoot
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.NoButton
|
|
propagateComposedEvents: true
|
|
z: -1
|
|
onWheel: {
|
|
if (wheel.angleDelta != 0) {
|
|
chat.contentY = chat.contentY - wheel.angleDelta.y
|
|
if (wheel.angleDelta.y > 0 && chat.contentY > chat.contentHeight - chat.height)
|
|
chat.contentY = chat.contentHeight - chat.height
|
|
else if (wheel.angleDelta < 0 && chat.contentY < 0)
|
|
chat.contentY = 0
|
|
wheel.accepted = true
|
|
chat.forceLayout()
|
|
chat.updatePosition()
|
|
}
|
|
}
|
|
}
|
|
|
|
onModelChanged: {
|
|
if (model) {
|
|
currentIndex = model.currentIndex
|
|
if (model.currentIndex == count - 1) {
|
|
positionViewAtEnd()
|
|
} else {
|
|
positionViewAtIndex(model.currentIndex, ListView.End)
|
|
}
|
|
}
|
|
}
|
|
|
|
ScrollBar.vertical: ScrollBar {
|
|
id: scrollbar
|
|
parent: chat.parent
|
|
anchors.top: chat.top
|
|
anchors.left: chat.right
|
|
anchors.bottom: chat.bottom
|
|
onPressedChanged: if (!pressed) chat.updatePosition()
|
|
}
|
|
|
|
property bool atBottom: false
|
|
onCountChanged: {
|
|
if (atBottom) {
|
|
var newIndex = count - 1 // last index
|
|
positionViewAtEnd()
|
|
currentIndex = newIndex
|
|
model.currentIndex = newIndex
|
|
}
|
|
|
|
if (contentHeight < height && model) {
|
|
model.fetchHistory();
|
|
}
|
|
}
|
|
|
|
onAtYBeginningChanged: if (atYBeginning) { chat.model.currentIndex = 0; chat.currentIndex = 0; model.fetchHistory(); }
|
|
|
|
function updatePosition() {
|
|
for (var y = chat.contentY + chat.height; y > chat.height; y -= 9) {
|
|
var i = chat.itemAt(100, y);
|
|
if (!i) continue;
|
|
if (!i.isFullyVisible()) continue;
|
|
chat.model.currentIndex = i.getIndex();
|
|
chat.currentIndex = i.getIndex()
|
|
atBottom = i.getIndex() == count - 1;
|
|
break;
|
|
}
|
|
}
|
|
onMovementEnded: updatePosition()
|
|
|
|
spacing: 4
|
|
delegate: TimelineRow {
|
|
function isFullyVisible() {
|
|
return height > 1 && (y - chat.contentY - 1) + height < chat.height
|
|
}
|
|
function getIndex() {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
section {
|
|
property: "section"
|
|
delegate: Column {
|
|
topPadding: 4
|
|
bottomPadding: 4
|
|
spacing: 8
|
|
|
|
width: parent.width
|
|
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
|
|
|
Label {
|
|
id: dateBubble
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
visible: section.includes(" ")
|
|
text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1])))
|
|
color: colors.windowText
|
|
|
|
height: contentHeight * 1.2
|
|
width: contentWidth * 1.2
|
|
horizontalAlignment: Text.AlignHCenter
|
|
background: Rectangle {
|
|
radius: parent.height / 2
|
|
color: colors.dark
|
|
}
|
|
}
|
|
Row {
|
|
height: userName.height
|
|
spacing: 4
|
|
Avatar {
|
|
width: avatarSize
|
|
height: avatarSize
|
|
url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
|
|
displayName: chat.model.displayName(section.split(" ")[0])
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: chat.model.openUserProfile(section.split(" ")[0])
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
}
|
|
|
|
Text {
|
|
id: userName
|
|
text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0]))
|
|
color: chat.model.userColor(section.split(" ")[0], colors.window)
|
|
textFormat: Text.RichText
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: chat.model.openUserProfile(section.split(" ")[0])
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|