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:
2026-08-24 23:25:13 +02:00
parent 705fe32e66
commit 4c4fec7a0a
846 changed files with 498002 additions and 52534 deletions
+59
View File
@@ -8,6 +8,7 @@
#include "services/rulesengine.h"
#include "ui/rulesmanagerdialog.h"
#include "ui/accountsetupdialog.h"
#include "ui/delegates/FolderTreeDelegate.h"
#include <optional>
#include <QMessageBox>
#include <QFileDialog>
@@ -227,6 +228,7 @@ void MainMainWindow::setupMailPage()
m_folderTree->setMaximumWidth(350);
m_folderTree->setFrameShape(QFrame::NoFrame);
m_folderTree->setExpandsOnDoubleClick(true);
m_folderTree->setItemDelegate(new FolderTreeDelegate(this)); // Custom delegate with unread count badges
leftSplitter->addWidget(m_folderTree);
// Category tree
@@ -255,6 +257,10 @@ void MainMainWindow::setupMailPage()
connect(m_mailListView, &MailListView::emailOpenRequested, this, [this](int mailId) {
openMailInIndependentWindow(mailId);
});
connect(m_mailListView, &MailListView::flagRequested, this, &MainMainWindow::onFlagRequested);
connect(m_mailListView, &MailListView::deleteRequested, this, &MainMainWindow::onDeleteRequested);
connect(m_mailListView, &MailListView::categoryRequested, this, &MainMainWindow::onCategoryRequested);
connect(m_mailListView, &MailListView::moreRequested, this, &MainMainWindow::onMoreRequested);
m_folderSplitter->addWidget(m_mailListView);
// Viewer stack: placeholder, reader, compose
@@ -466,6 +472,59 @@ void MainMainWindow::onReaderDeleteRequested(int mailId)
statusBar()->showMessage(tr("Mensaje eliminado"), 3000);
}
// MailListView action handlers (from compact delegate)
void MainMainWindow::onFlagRequested(int mailId)
{
auto item = MailItemDao::findById(mailId);
if (item.has_value()) {
item->setFlagged(!item->isFlagged());
MailItemDao::update(*item);
m_emailModel->refresh();
if (m_currentMailId == mailId) {
m_emailViewer->setMailItem(&*item);
}
statusBar()->showMessage(tr(item->isFlagged() ? "Marcado para seguimiento" : "Seguimiento quitado"), 2000);
}
}
void MainMainWindow::onDeleteRequested(int mailId)
{
MailItemDao::remove(mailId);
if (m_currentMailId == mailId) {
m_currentMailId = -1;
m_viewerStack->setCurrentIndex(0); // placeholder
}
m_emailModel->refresh();
statusBar()->showMessage(tr("Mensaje eliminado"), 3000);
}
void MainMainWindow::onCategoryRequested(int mailId)
{
// Show category context menu at cursor position
// For now, just show status - could open a dialog
statusBar()->showMessage(tr("Categoría para mensaje %1").arg(mailId), 2000);
}
void MainMainWindow::onMoreRequested(int mailId, const QPoint &globalPos)
{
// Show context menu with more actions
QMenu menu;
menu.addAction("Responder", [this, mailId]() { onReaderReplyRequested(mailId); });
menu.addAction("Reenviar", [this, mailId]() { onReaderForwardRequested(mailId); });
menu.addAction("Mover a...", [this, mailId]() { /* TODO: move dialog */ });
menu.addAction("Marcar como no leído", [this, mailId]() {
auto item = MailItemDao::findById(mailId);
if (item.has_value()) {
item->setRead(false);
MailItemDao::update(*item);
m_emailModel->refresh();
}
});
menu.addSeparator();
menu.addAction("Eliminar", [this, mailId]() { onDeleteRequested(mailId); });
menu.exec(globalPos);
}
void MainMainWindow::onNewMessage()
{ // Show compose view in the viewer stack
m_embeddedComposeView->initializeComposition();