feat: Pagination/virtualization (fetchMore) for EmailListModel

- EmailListModel: QAbstractListModel with canFetchMore/fetchMore
  * Batch loading (default 50 emails per fetch)
  * Total count query for scrollbar
  * TotalCountChanged signal
  * Paginated DB queries: findAllPaginated, findByFolderIdPaginated
  * Count queries: countAll, countByFolderId

- MailItemDao: new paginated methods
  * findAllPaginated(offset, limit, filters...)
  * findByFolderIdPaginated(folderId, offset, limit, filters...)
  * countAll(filters...), countByFolderId(folderId, filters...)

- MailListView: scroll-triggered fetchMore
  * Connects verticalScrollBar valueChanged on both TreeView and ListView
  * Triggers fetchMore at 90% scroll threshold
  * onRowsInserted updates EmailTreeModel when new emails arrive
  * Works in both normal (tree) and compact (list) views
This commit is contained in:
2026-08-25 22:20:06 +02:00
parent 6e6fdf9c21
commit 68d237c873
77 changed files with 16718 additions and 18159 deletions
+47 -1
View File
@@ -14,6 +14,7 @@
#include <QResizeEvent>
#include <QListView>
#include <QStyledItemDelegate>
#include <QScrollBar>
#include "ui/delegates/CompactMailDelegate.h"
MailListView::MailListView(QWidget *parent) : QWidget(parent), m_sourceModel(nullptr)
@@ -217,8 +218,13 @@ void MailListView::setModel(EmailListModel *model)
// Connect to source model changes to refresh both views
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, &MailListView::refreshViews);
connect(m_sourceModel, &QAbstractItemModel::layoutChanged, this, &MailListView::refreshViews);
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &MailListView::refreshViews);
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &MailListView::onRowsInserted);
connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &MailListView::refreshViews);
connect(m_sourceModel, &EmailListModel::totalCountChanged, this, &MailListView::onTotalCountChanged);
// Connect tree view scroll for fetchMore
connect(m_treeView->verticalScrollBar(), &QScrollBar::valueChanged, this, &MailListView::onTreeViewScrolled);
connect(m_listView->verticalScrollBar(), &QScrollBar::valueChanged, this, &MailListView::onListViewScrolled);
// Initial population
refreshViews();
@@ -228,6 +234,7 @@ void MailListView::refreshViews()
{
if (!m_sourceModel) return;
// For paginated model, just update the tree model with currently loaded emails
const QVector<MailItem> &emails = m_sourceModel->emails();
m_treeModel->setEmails(emails);
m_treeView->expandAll();
@@ -346,4 +353,43 @@ void MailListView::onDisplayModeChanged(int index)
CompactMailDelegate::DisplayMode mode = static_cast<CompactMailDelegate::DisplayMode>(index);
m_compactDelegate->setDisplayMode(mode);
m_listView->update();
}
void MailListView::onTotalCountChanged(int count)
{
Q_UNUSED(count);
// Could update a status label showing "X of Y emails"
}
void MailListView::onTreeViewScrolled(int value)
{
if (!m_sourceModel) return;
QScrollBar* scrollBar = m_treeView->verticalScrollBar();
int maxValue = scrollBar->maximum();
if (maxValue > 0 && value >= maxValue * 0.9) { // 90% threshold
m_sourceModel->fetchMore(QModelIndex());
}
}
void MailListView::onListViewScrolled(int value)
{
if (!m_sourceModel) return;
QScrollBar* scrollBar = m_listView->verticalScrollBar();
int maxValue = scrollBar->maximum();
if (maxValue > 0 && value >= maxValue * 0.9) { // 90% threshold
m_sourceModel->fetchMore(QModelIndex());
}
}
void MailListView::onRowsInserted(const QModelIndex &parent, int first, int last)
{
Q_UNUSED(parent);
Q_UNUSED(first);
Q_UNUSED(last);
// New emails loaded via fetchMore - refresh tree model
if (m_sourceModel) {
const QVector<MailItem> &emails = m_sourceModel->emails();
m_treeModel->setEmails(emails);
m_treeView->expandAll();
}
}