Browse Source

chatgui: track read position via debounced scroll, only fully visible msgs are read

- 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)
topo_upd
Evgeny 2 months ago
parent
commit
0c4862c66a
  1. 4
      tools/chatgui/db/db_manager.cpp
  2. 8
      tools/chatgui/src/mainwindow.cpp
  3. 60
      tools/chatgui/src/messagelist.cpp
  4. 8
      tools/chatgui/src/messagelist.h

4
tools/chatgui/db/db_manager.cpp

@ -117,6 +117,10 @@ QList<ChannelRow> 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;

8
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() {

60
tools/chatgui/src/messagelist.cpp

@ -12,6 +12,7 @@
#include <QPixmap>
#include <QDateTime>
#include <QFile>
#include <QScrollBar>
#include <QApplication>
#include <QDebug>
#include <vector>
@ -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);
}
}

8
tools/chatgui/src/messagelist.h

@ -3,11 +3,13 @@
#include <QWidget>
#include <QStandardItemModel>
#include <QHash>
#include <QTimer>
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<quint64, QColor> m_avatarCache;
qint64 m_lastReadMsgId = 0;
QTimer* m_readDebounce;
void saveChannelState();
void restoreScrollPosition(const QString& channelId);
void updateReadPosition();
};

Loading…
Cancel
Save