feat: responsive compact mail view + folder unread badges
- CompactMailDelegate: card-based view for narrow panels (<420px) * Avatar with hash-based color, sender + date (relative format) * Subject line with attachment icon * Action buttons: flag, delete, category, more (context menu) * Unread highlight with light blue background + bold sender - MailListView: automatic switch between tree/table (wide) and list/cards (narrow) * QStackedWidget with QTreeView + QListView sharing same proxy model * resizeEvent threshold at 420px * Signals forwarded to MainMainWindow for actions - FolderTreeDelegate: custom paint for folder tree * Unread count badge (blue pill) right-aligned on folder nodes * Account nodes bold, folder nodes normal weight * Proper indentation per depth level - MailItemDao::countUnreadByFolderId() for real-time unread counts - MainMainWindow handlers for compact actions (flag, delete, category, more menu)
This commit is contained in:
+125
-25
@@ -11,12 +11,18 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <QHeaderView>
|
||||
#include <QResizeEvent>
|
||||
#include <QListView>
|
||||
#include <QStyledItemDelegate>
|
||||
#include "ui/delegates/CompactMailDelegate.h"
|
||||
|
||||
MailListView::MailListView(QWidget *parent) : QWidget(parent), m_sourceModel(nullptr) {
|
||||
MailListView::MailListView(QWidget *parent) : QWidget(parent), m_sourceModel(nullptr)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void MailListView::setupUI() {
|
||||
void MailListView::setupUI()
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
@@ -81,14 +87,18 @@ void MailListView::setupUI() {
|
||||
|
||||
layout->addWidget(filterBar);
|
||||
|
||||
// Tree View
|
||||
// View Stack (TreeView for normal, ListView for compact)
|
||||
m_viewStack = new QStackedWidget();
|
||||
layout->addWidget(m_viewStack, 1);
|
||||
|
||||
// --- Normal Tree View ---
|
||||
m_treeView = new QTreeView();
|
||||
m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_treeView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_treeView->setAlternatingRowColors(true);
|
||||
m_treeView->header()->setStretchLastSection(false);
|
||||
m_treeView->header()->setSectionsClickable(true);
|
||||
m_treeView->setSortingEnabled(false); // We handle sorting in the model
|
||||
m_treeView->setSortingEnabled(false);
|
||||
m_treeView->setFrameShape(QFrame::NoFrame);
|
||||
m_treeView->setRootIsDecorated(true);
|
||||
m_treeView->setItemsExpandable(true);
|
||||
@@ -101,16 +111,14 @@ void MailListView::setupUI() {
|
||||
"QHeaderView::section { background-color: #f5f5f7; padding: 8px; border: none; border-bottom: 1px solid #d1d1d6; font-weight: 600; color: #555; }"
|
||||
);
|
||||
|
||||
// Tree model
|
||||
m_treeModel = new EmailTreeModel(this);
|
||||
m_proxyModel = new QSortFilterProxyModel(this);
|
||||
m_proxyModel->setSourceModel(m_treeModel);
|
||||
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_proxyModel->setFilterKeyColumn(-1); // Search all columns
|
||||
m_proxyModel->setFilterKeyColumn(-1);
|
||||
|
||||
m_treeView->setModel(m_proxyModel);
|
||||
|
||||
// Column widths
|
||||
m_treeView->header()->setSectionResizeMode(EmailTreeModel::ColSubject, QHeaderView::Stretch);
|
||||
m_treeView->header()->setSectionResizeMode(EmailTreeModel::ColSender, QHeaderView::Stretch);
|
||||
m_treeView->header()->setSectionResizeMode(EmailTreeModel::ColDate, QHeaderView::ResizeToContents);
|
||||
@@ -125,10 +133,46 @@ void MailListView::setupUI() {
|
||||
}
|
||||
});
|
||||
|
||||
// Expand all groups by default
|
||||
m_treeView->expandAll();
|
||||
|
||||
layout->addWidget(m_treeView);
|
||||
// --- Compact List View ---
|
||||
m_listView = new QListView();
|
||||
m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_listView->setUniformItemSizes(true);
|
||||
m_listView->setFrameShape(QFrame::NoFrame);
|
||||
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_listView->setStyleSheet(
|
||||
"QListView { background-color: #ffffff; border: none; outline: none; }"
|
||||
"QListView::item { border: none; }"
|
||||
"QListView::item:selected { background: transparent; }"
|
||||
"QListView::item:hover { background: transparent; }"
|
||||
);
|
||||
|
||||
m_compactDelegate = new CompactMailDelegate(this);
|
||||
m_listView->setItemDelegate(m_compactDelegate);
|
||||
|
||||
// Connect compact delegate signals
|
||||
connect(m_compactDelegate, &CompactMailDelegate::flagRequested,
|
||||
this, &MailListView::onCompactFlagRequested);
|
||||
connect(m_compactDelegate, &CompactMailDelegate::deleteRequested,
|
||||
this, &MailListView::onCompactDeleteRequested);
|
||||
connect(m_compactDelegate, &CompactMailDelegate::categoryRequested,
|
||||
this, &MailListView::onCompactCategoryRequested);
|
||||
connect(m_compactDelegate, &CompactMailDelegate::moreRequested,
|
||||
this, &MailListView::onCompactMoreRequested);
|
||||
connect(m_compactDelegate, &CompactMailDelegate::mailClicked,
|
||||
this, &MailListView::onCompactMailClicked);
|
||||
|
||||
// Same proxy model for list view (single column)
|
||||
m_listView->setModel(m_proxyModel);
|
||||
m_listView->setModelColumn(0); // Use first column (subject)
|
||||
|
||||
// Add both views to stack
|
||||
m_viewStack->addWidget(m_treeView); // index 0 = normal
|
||||
m_viewStack->addWidget(m_listView); // index 1 = compact
|
||||
|
||||
// Show normal by default
|
||||
m_viewStack->setCurrentIndex(0);
|
||||
|
||||
// Connect filter checkboxes
|
||||
connect(m_unreadOnlyCheck, &QCheckBox::toggled, this, &MailListView::onFilterChanged);
|
||||
@@ -136,28 +180,57 @@ void MailListView::setupUI() {
|
||||
connect(m_hasAttachmentsCheck, &QCheckBox::toggled, this, &MailListView::onFilterChanged);
|
||||
}
|
||||
|
||||
void MailListView::setModel(EmailListModel *model) {
|
||||
void MailListView::setModel(EmailListModel *model)
|
||||
{
|
||||
m_sourceModel = model;
|
||||
|
||||
// Connect to source model changes to refresh tree
|
||||
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, &MailListView::refreshTreeModel);
|
||||
connect(m_sourceModel, &QAbstractItemModel::layoutChanged, this, &MailListView::refreshTreeModel);
|
||||
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &MailListView::refreshTreeModel);
|
||||
connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &MailListView::refreshTreeModel);
|
||||
|
||||
|
||||
// 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::rowsRemoved, this, &MailListView::refreshViews);
|
||||
|
||||
// Initial population
|
||||
refreshTreeModel();
|
||||
refreshViews();
|
||||
}
|
||||
|
||||
void MailListView::refreshTreeModel() {
|
||||
void MailListView::refreshViews()
|
||||
{
|
||||
if (!m_sourceModel) return;
|
||||
|
||||
|
||||
const QVector<MailItem> &emails = m_sourceModel->emails();
|
||||
m_treeModel->setEmails(emails);
|
||||
m_treeView->expandAll();
|
||||
|
||||
// List view uses same proxy model, just refresh
|
||||
m_listView->update();
|
||||
}
|
||||
|
||||
void MailListView::onRowSelected(const QModelIndex &index) {
|
||||
void MailListView::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
updateViewMode();
|
||||
}
|
||||
|
||||
void MailListView::updateViewMode()
|
||||
{
|
||||
int viewportWidth = m_viewStack->width(); // available width
|
||||
bool wantCompact = (viewportWidth < m_compactThreshold);
|
||||
|
||||
if (wantCompact != m_compactMode) {
|
||||
m_compactMode = wantCompact;
|
||||
m_viewStack->setCurrentIndex(m_compactMode ? 1 : 0);
|
||||
|
||||
if (m_compactMode) {
|
||||
m_treeView->header()->hide();
|
||||
} else {
|
||||
m_treeView->header()->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MailListView::onRowSelected(const QModelIndex &index)
|
||||
{
|
||||
if (!index.isValid()) return;
|
||||
QModelIndex proxyIdx = m_proxyModel->mapToSource(index);
|
||||
if (proxyIdx.isValid() && proxyIdx.data(EmailTreeModel::MailIdRole).isValid()) {
|
||||
@@ -166,20 +239,47 @@ void MailListView::onRowSelected(const QModelIndex &index) {
|
||||
}
|
||||
}
|
||||
|
||||
void MailListView::onSearchTextChanged(const QString &text) {
|
||||
void MailListView::onSearchTextChanged(const QString &text)
|
||||
{
|
||||
m_proxyModel->setFilterFixedString(text);
|
||||
}
|
||||
|
||||
void MailListView::onFilterChanged() {
|
||||
void MailListView::onFilterChanged()
|
||||
{
|
||||
if (!m_sourceModel) return;
|
||||
m_sourceModel->setShowUnreadOnly(m_unreadOnlyCheck->isChecked());
|
||||
m_sourceModel->setShowFlaggedOnly(m_flaggedOnlyCheck->isChecked());
|
||||
m_sourceModel->setShowHasAttachments(m_hasAttachmentsCheck->isChecked());
|
||||
// refreshTreeModel will be called automatically via modelReset signal
|
||||
}
|
||||
|
||||
void MailListView::onGroupByChanged(int index) {
|
||||
void MailListView::onGroupByChanged(int index)
|
||||
{
|
||||
bool enabled = (index == 1); // 0 = Sin agrupación, 1 = Agrupar por fecha
|
||||
m_treeModel->setGroupMode(enabled ? EmailTreeModel::GroupByDate : EmailTreeModel::NoGrouping);
|
||||
m_treeView->expandAll();
|
||||
}
|
||||
|
||||
void MailListView::onCompactFlagRequested(int mailId)
|
||||
{
|
||||
emit flagRequested(mailId);
|
||||
}
|
||||
|
||||
void MailListView::onCompactDeleteRequested(int mailId)
|
||||
{
|
||||
emit deleteRequested(mailId);
|
||||
}
|
||||
|
||||
void MailListView::onCompactCategoryRequested(int mailId)
|
||||
{
|
||||
emit categoryRequested(mailId);
|
||||
}
|
||||
|
||||
void MailListView::onCompactMoreRequested(int mailId, const QPoint &globalPos)
|
||||
{
|
||||
emit moreRequested(mailId, globalPos);
|
||||
}
|
||||
|
||||
void MailListView::onCompactMailClicked(int mailId)
|
||||
{
|
||||
emit emailSelected(mailId);
|
||||
}
|
||||
Reference in New Issue
Block a user