From 0da3c3a67742cecf488056cf558424fbbc346265 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 00:16:33 +0100 Subject: [PATCH] Follow new messages near the bottom and always follow accepted sends --- tools/chatgui/src/mainwindow.cpp | 4 +-- tools/chatgui/src/messagelist.cpp | 50 ++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index b790a8935..e47d3331a 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -1311,7 +1311,7 @@ void MainWindow::onDmMsgReceived(const QString& convId, quint64 author) { QString key = QStringLiteral("dm:") + convId; showIncomingMessageNotification(key, author); if (m_currentChannelId == key) { - m_messageList->refresh(); + m_messageList->refresh(author == m_db->myNodeId()); return; } if (author == m_db->myNodeId()) return; @@ -1539,7 +1539,7 @@ void MainWindow::onMessageReceived(const char* ch_id, int ch_id_len, uint64_t au SoundManager::instance()->play("msg_incoming"); if (m_messageList) { if (cid == m_currentChannelId) { - m_messageList->refresh(); + m_messageList->refresh(!initialSync && m_db && author == m_db->myNodeId()); } else if (m_channelList && !initialSync && m_db && author != m_db->myNodeId()) { int unread = m_channelList->getChannelUnreadCount(cid) + 1; DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "MainWindow::onMessageReceived increment unread ch=%s %d->%d", diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index c1f0771be..068666e1a 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -617,11 +617,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent connect(msgDelegate, &MessageDelegate::reactionRequested, this, &MessageList::requestReaction); connect(msgDelegate, &MessageDelegate::pollRequested, this, &MessageList::showPoll); connect(msgDelegate, &MessageDelegate::replyRequested, this, &MessageList::jumpToMessage); - connect(m_view, &ChatView::latestRequested, this, [this]() { - if (m_historyAnchor.isEmpty()) return; - m_historyAnchor.clear(); loadChannel(m_currentChannelId); - QTimer::singleShot(0, m_view, &QListView::scrollToBottom); - }); + connect(m_view, &ChatView::latestRequested, this, &MessageList::showLatestMessages); msgDelegate->setDownloadCallback([](const QModelIndex& index) { QString target = index.data(MsgChannelIdRole).toString(); qint64 id = index.data(MsgIdRole).toLongLong(); @@ -662,6 +658,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent layout->addWidget(m_emojiPanel); m_inputBar = new InputBar(m_emojiPanel, recorder, this); + connect(m_inputBar, &InputBar::messageAccepted, this, &MessageList::showLatestMessages); setFocusProxy(m_inputBar); layout->addWidget(m_inputBar); m_inputBar->installFileDropTarget(m_view->viewport()); @@ -1019,8 +1016,34 @@ void MessageList::loadChannel(const QString& channelId) { m_inputBar->setFocus(); } -void MessageList::refresh() { +bool MessageList::nearHistoryBottom() const { + const auto* scrollbar = m_view->verticalScrollBar(); + const int gap = scrollbar->maximum() - scrollbar->value(); + const int threshold = 10 * m_view->fontMetrics().lineSpacing(); + GUI_DEBUG("history follow: ch=%s gap=%d threshold=%d near=%d", qPrintable(m_currentChannelId), gap, threshold, gap <= threshold); + return gap <= threshold; +} + +void MessageList::scrollHistoryToBottom() { + m_view->doItemsLayout(); m_view->scrollToBottom(); + GUI_DEBUG("history bottom: ch=%s rows=%d position=%d maximum=%d", qPrintable(m_currentChannelId), m_model->rowCount(), + m_view->verticalScrollBar()->value(), m_view->verticalScrollBar()->maximum()); + QTimer::singleShot(0, this, [this, channel = m_currentChannelId, generation = m_currentDmGeneration, anchor = m_historyAnchor] { + if (channel != m_currentChannelId || generation != m_currentDmGeneration || anchor != m_historyAnchor) return; + m_view->doItemsLayout(); m_view->scrollToBottom(); + GUI_DEBUG("history bottom after layout: ch=%s position=%d maximum=%d", qPrintable(channel), + m_view->verticalScrollBar()->value(), m_view->verticalScrollBar()->maximum()); + }); +} + +void MessageList::showLatestMessages() { + if (!m_historyAnchor.isEmpty()) { m_historyAnchor.clear(); loadChannel(m_currentChannelId); } + scrollHistoryToBottom(); +} + +void MessageList::refresh(bool outgoing) { if (m_currentChannelId.isEmpty()) return; + if (outgoing) showLatestMessages(); if (isDmChannel(m_currentChannelId)) { requestDmMessages(m_currentChannelId); @@ -1039,10 +1062,12 @@ void MessageList::refresh() { m_localNicks = m_db->getLocalNicknames(m_currentChannelId); QScrollBar* sb = m_view->verticalScrollBar(); - bool wasAtBottom = sb && (sb->value() >= sb->maximum()); + const bool wasAtBottom = nearHistoryBottom(); + bool added = false; for (const auto& m : newMsgs) { if (present.contains(QString::fromLatin1(m.messageId.toHex()))) continue; + added = true; AccountRow acc = m_db->getAccount(m.authorNodeId); QString author = authorName(m.authorNodeId); QString letter = acc.avatarLetter.isEmpty() ? author.mid(0, 1) : acc.avatarLetter; @@ -1071,7 +1096,7 @@ void MessageList::refresh() { } m_model->setSortRole(MsgRawTimestampRole); m_model->sort(0); - if (wasAtBottom) m_view->scrollToBottom(); + if (added && wasAtBottom) scrollHistoryToBottom(); m_readDebounce->start(); } @@ -1586,6 +1611,10 @@ void MessageList::onDmMessages(const uint8_t* data, int len) { } auto* scrollbar = m_view->verticalScrollBar(); bool atBottom = m_model->rowCount() == 0 || scrollbar->value() >= scrollbar->maximum(); + const bool nearBottom = nearHistoryBottom(); + qint64 previousLatest = 0; + for (int row = 0; row < m_model->rowCount(); ++row) previousLatest = qMax(previousLatest, m_model->item(row)->data(MsgIdRole).toLongLong()); + bool newMessage = false, outgoingMessage = false; int previousScroll = scrollbar->value(); m_model->clear(); m_avatarCache.clear(); @@ -1594,6 +1623,7 @@ void MessageList::onDmMessages(const uint8_t* data, int len) { auto message = entry.toObject(); int dir = message.value("dir").toInt(); qint64 seq = (qint64)message.value("seq").toDouble(); + if (seq > previousLatest) { newMessage = true; if (dir == 1) outgoingMessage = true; } qint64 ts = (qint64)message.value("ts").toDouble(); quint64 author = message.value("author").toString().toULongLong(); QString text = message.value("data").toString(); @@ -1669,8 +1699,8 @@ void MessageList::onDmMessages(const uint8_t* data, int len) { m_model->appendRow(item); } - if (atBottom) m_view->scrollToBottom(); - else scrollbar->setValue(previousScroll); + if (atBottom || outgoingMessage || (newMessage && nearBottom)) scrollHistoryToBottom(); + else { m_view->doItemsLayout(); scrollbar->setValue(previousScroll); } } /* ── рация ── */