smsapp: Make messages in ConversationDisplay always stick to the bottom

This commit mostly reverses the direction of the ListView used by ConversationDisplay to make it bottom-to-top. This matches the way most chat apps work today (e.g. Discord), by leaving an empty space at the top when there are not enough messages to fill the screen.

Testing it, it seems like a bunch of the custom logic for scrolling down when there are new messages and staying focused on the current message while loading new ones can now be transparently handled by the ListView, so this trims down the code quite a lot.

While re-working the message loading, I also switched it to use a constant threshold rather than a proportional one, which fixes cases where scrolling down could load older messages.

[Enregistrement d'écran_20250707_103723.webm](/uploads/99efd26958e73fed1ce6f0e04bae51f4/Enregistrement_d_%C3%A9cran_20250707_103723.webm)

Overall, this makes the ConversationDisplay scrolling much smoother.
This commit is contained in:
Yuki Joou
2025-11-17 23:07:21 +01:00
committed by Simon Redman
parent 4e53bcdd5d
commit 82c805b7bb
3 changed files with 19 additions and 60 deletions

View File

@@ -237,4 +237,18 @@ void ConversationModel::requestAttachmentPath(const qint64 &partID, const QStrin
m_conversationsInterface->requestAttachmentFile(partID, uniqueIdentifier);
}
bool ConversationModel::canFetchMore(const QModelIndex & /* parent */) const
{
// TODO: Is there any way to know when we're at the end of the conversation and can't fetch more?
// Would be neat to show an indication to the user in such a case (and stop fetching more data)
// but would also likely require sending that information from the phone, as I don't see an
// obvious way to know that using currently available information...
return true;
}
void ConversationModel::fetchMore(const QModelIndex & /* parent */)
{
requestMoreMessages();
}
#include "moc_conversationmodel.cpp"

View File

@@ -61,6 +61,9 @@ public:
Q_INVOKABLE void requestAttachmentPath(const qint64 &partID, const QString &UniqueIdentifier);
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
Q_SIGNALS:
void loadingFinished();
void filePathReceived(QString filePath, QString fileName);

View File

@@ -66,13 +66,14 @@ Kirigami.ScrollablePage
id: viewport
model: QSortFilterProxyModel {
id: model
sortOrder: Qt.AscendingOrder
sortOrder: Qt.DescendingOrder
sortRole: ConversationModel.DateRole
sourceModel: conversationModel
}
spacing: Kirigami.Units.largeSpacing
highlightMoveDuration: 0
verticalLayoutDirection: Qt.BottomToTop
Controls.BusyIndicator {
running: !isInitalized
@@ -117,69 +118,10 @@ Kirigami.ScrollablePage
width: viewport.width
ListView.onAdd: {
if (!isInitalized) {
return
}
if (index == viewport.count - 1) {
// This message is being inserted at the newest position
// We want to scroll to show it if the user is "almost" looking at it
// Define some fudge area. If the message is being drawn offscreen but within
// this distance, we move to show it anyway.
// Selected to be genericMessage.height because that value scales for different
// font sizes / DPI / etc. -- Better ideas are welcome!
// Double the value works nicely
var offscreenFudge = 2 * genericMessage.height
var viewportYBottom = viewport.contentY + viewport.height
if (y < viewportYBottom + genericMessage.height) {
viewport.highlightMoveDuration = -1
viewport.currentIndex = index
}
}
}
onMessageCopyRequested: {
SmsHelper.copyToClipboard(message)
}
}
/// As the user scrolls, load more messages when they get to the top.
/// This used to use the onMovementEnded signal, but at some point
/// that signal stopped being emitted reliably when scrolling with
/// the mouse. onContentYChanged is fine for our use, just a bit noisy.
onContentYChanged: {
if (!isInitalized) {
return
}
// If we have scrolled near to the top, request more messages
// This threshold of visibleArea.yPosition has been chosen experimentally, but
// should generally be OK because it is defined as a ratio of the content to the visible
// area. As more messages get loaded into our view, this constant will start to be
// less-sane, meaning we will request messages earlier as the user scrolls back in time.
// This is probably a good thing, because it means that scrolling back further and further
// quickly, will be more likely to be smooth.
// Combined with `atYBeginning`, the view scrolls smoothly for me, long past the point where
// the rest of the app is stable, and past the point where Android can fetch messages fast
// enough.
if (visibleArea.yPosition < 0.075 || atYBeginning) {
// "Lock" the view to the message currently at the beginning of the view
// This prevents the view from snapping to the top of the messages we are about to request
currentIndex = 0 // Index 0 is the beginning of the view
preferredHighlightBegin = visibleArea.yPosition
preferredHighlightEnd = preferredHighlightBegin + currentItem.height
highlightRangeMode = ListView.StrictlyEnforceRange
highlightMoveDuration = 0
// Get more messages
conversationModel.requestMoreMessages()
}
}
}
footer: SendingArea {