From 0c4862c66ad3f17f6cef214aca59606026f59284 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 24 Jul 2026 00:30:44 +0300 Subject: [PATCH] chatgui: track read position via debounced scroll, only fully visible msgs are read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Debounce timer (300ms) advances lastReadMsgId only when message is fully in viewport - Unread badge no longer resets on channel select — only when actual scroll reads msgs - saveChannelState() uses tracked m_lastReadMsgId instead of viewport bottom - getChannels() reads lastReadMsgId from ui_state (was reading unused channels column) --- tools/chatgui/db/db_manager.cpp | 4 +++ tools/chatgui/src/mainwindow.cpp | 8 ++++- tools/chatgui/src/messagelist.cpp | 60 +++++++++++++++++++++++++++---- tools/chatgui/src/messagelist.h | 8 +++++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 974b566f..69ec5dc5 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -117,6 +117,10 @@ QList DbManager::getChannels() const { } sqlite3_finalize(stmt); for (auto& ch : list) { + ch.lastReadMsgId = getUiStateInt64( + QStringLiteral("ch_%1_last_read").arg(ch.channelId), 0); + ch.lastPosMsgId = getUiStateInt64( + QStringLiteral("ch_%1_last_pos").arg(ch.channelId), 0); ch.unreadCount = getUnreadCount(ch.channelId, ch.lastReadMsgId); } return list; diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index 83b36168..6bf9f55d 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -212,7 +212,6 @@ void MainWindow::setupConnects() { this, [this](const QString& channelId) { QSettings().setValue("last_channel_id", channelId); m_currentChannelId = channelId; - m_channelList->setChannelUnreadCount(channelId, 0); m_messageList->loadChannel(channelId); m_accountList->setChannel(channelId); QByteArray cidBA = channelId.toUtf8(); @@ -223,6 +222,13 @@ void MainWindow::setupConnects() { connect(m_channelList, &ChannelList::joinRequested, this, &MainWindow::showJoinDialog); connect(m_channelList, &ChannelList::createGroupRequested, this, &MainWindow::onCreateGroupRequested); connect(m_channelList, &ChannelList::settingsRequested, this, &MainWindow::showSettings); + connect(m_messageList, &MessageList::readPositionChanged, + this, [this](const QString& channelId, qint64 lastReadMsgId) { + Q_UNUSED(lastReadMsgId); + if (m_channelList && m_db) + m_channelList->setChannelUnreadCount(channelId, + m_db->getUnreadCount(channelId, lastReadMsgId)); + }); } void MainWindow::setupTray() { diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index 562daa7f..4053ac4f 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -163,6 +164,13 @@ MessageList::MessageList(DbManager* db, QWidget *parent) if (!wasActive && m_animHoverActive) AnimTimer::instance()->activate(); else if (wasActive && !m_animHoverActive) AnimTimer::instance()->deactivate(); }); + + m_readDebounce = new QTimer(this); + m_readDebounce->setSingleShot(true); + m_readDebounce->setInterval(300); + connect(m_readDebounce, &QTimer::timeout, this, &MessageList::updateReadPosition); + connect(m_view->verticalScrollBar(), &QScrollBar::valueChanged, + m_readDebounce, QOverload<>::of(&QTimer::start)); } void MessageList::loadChannel(const QString& channelId) { @@ -175,6 +183,9 @@ void MessageList::loadChannel(const QString& channelId) { m_model->clear(); m_avatarCache.clear(); m_currentChannelId = channelId; + m_readDebounce->stop(); + m_lastReadMsgId = m_db->getUiStateInt64( + QStringLiteral("ch_%1_last_read").arg(channelId), 0); if (!m_db || !m_db->isOpen()) return; auto msgs = m_db->getMessages(channelId, 100); @@ -247,18 +258,12 @@ void MessageList::saveChannelState() { int vpHeight = m_view->viewport()->height(); QModelIndex topIdx = m_view->indexAt(QPoint(0, 0)); - QModelIndex bottomIdx = m_view->indexAt(QPoint(0, vpHeight - 1)); - qint64 lastReadMsgId = 0; qint64 lastPosMsgId = 0; - if (topIdx.isValid()) lastPosMsgId = topIdx.data(MsgIdRole).toLongLong(); - if (bottomIdx.isValid()) - lastReadMsgId = bottomIdx.data(MsgIdRole).toLongLong(); - else if (m_model->rowCount() > 0) - lastReadMsgId = m_model->item(m_model->rowCount() - 1)->data(MsgIdRole).toLongLong(); + qint64 lastReadMsgId = m_lastReadMsgId; if (lastReadMsgId == 0 && lastPosMsgId == 0) return; QByteArray cid = m_currentChannelId.toUtf8(); @@ -298,3 +303,44 @@ void MessageList::restoreScrollPosition(const QString& channelId) { m_view->scrollToBottom(); } + +void MessageList::updateReadPosition() { + if (m_currentChannelId.isEmpty() || m_model->rowCount() == 0) return; + + int vpHeight = m_view->viewport()->height(); + int lastFullyVisibleRow = -1; + qint64 newReadMsgId = m_lastReadMsgId; + + for (int row = m_model->rowCount() - 1; row >= 0; --row) { + QModelIndex idx = m_model->index(row, 0); + QRect vr = m_view->visualRect(idx); + if (vr.isValid() && vr.bottom() <= vpHeight) { + lastFullyVisibleRow = row; + break; + } + } + + if (lastFullyVisibleRow >= 0) { + qint64 msgId = m_model->item(lastFullyVisibleRow)->data(MsgIdRole).toLongLong(); + if (msgId > m_lastReadMsgId) + newReadMsgId = msgId; + } + + if (newReadMsgId > m_lastReadMsgId) { + m_lastReadMsgId = newReadMsgId; + + QByteArray cid = m_currentChannelId.toUtf8(); + QByteArray key = "ch_" + cid + "_last_read"; + QByteArray valStr = QByteArray::number(m_lastReadMsgId); + size_t klen = key.size(), vlen = valStr.size(); + size_t total = klen + 1 + vlen + 1; + if (total <= 256) { + void* arg = u_malloc(total); + memcpy(arg, key.constData(), klen + 1); + memcpy((char*)arg + klen + 1, valStr.constData(), vlen + 1); + gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg); + } + + emit readPositionChanged(m_currentChannelId, m_lastReadMsgId); + } +} diff --git a/tools/chatgui/src/messagelist.h b/tools/chatgui/src/messagelist.h index 4af75c75..192da3a8 100644 --- a/tools/chatgui/src/messagelist.h +++ b/tools/chatgui/src/messagelist.h @@ -3,11 +3,13 @@ #include #include #include +#include class ChatView; class InputBar; class EmojiPanel; class DbManager; +class ChannelList; class MessageList : public QWidget { Q_OBJECT @@ -19,6 +21,9 @@ public: void addMessage(const QString& channelId, quint64 authorNodeId, const QByteArray& content, qint64 timestamp); +signals: + void readPositionChanged(const QString& channelId, qint64 lastReadMsgId); + private: ChatView *m_view; QStandardItemModel *m_model; @@ -28,7 +33,10 @@ private: QString m_currentChannelId; bool m_animHoverActive = false; QHash m_avatarCache; + qint64 m_lastReadMsgId = 0; + QTimer* m_readDebounce; void saveChannelState(); void restoreScrollPosition(const QString& channelId); + void updateReadPosition(); };